From 74ed9b613e8420889729eb29e920e1653a4870d2 Mon Sep 17 00:00:00 2001 From: NeonNik2245 <106491639+NeonNik2245@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:21:51 +0300 Subject: [PATCH 1/7] Adds anosmia quirk (#82206) --- code/__DEFINES/traits/declarations.dm | 1 + code/_globalvars/traits/_traits.dm | 1 + code/_globalvars/traits/admin_tooling.dm | 1 + code/datums/components/bakeable.dm | 17 +++++++++++++++-- code/datums/diseases/advance/symptoms/fire.dm | 4 ++-- code/datums/mutations/olfaction.dm | 4 ++++ code/datums/quirks/negative_quirks/anosmia.dm | 9 +++++++++ .../changeling/powers/pheromone_receptors.dm | 3 +++ .../food_and_drinks/machinery/deep_fryer.dm | 6 +++++- .../modules/food_and_drinks/machinery/oven.dm | 6 +++++- .../modules/mining/lavaland/megafauna_loot.dm | 5 ++++- .../chemistry/reagents/food_reagents.dm | 5 ++++- .../reagent_containers/cups/glassbottle.dm | 3 ++- .../surgery/organs/internal/lungs/_lungs.dm | 19 +++++++++++++------ tgstation.dme | 1 + 15 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 code/datums/quirks/negative_quirks/anosmia.dm diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 9d2895aecb11..4e3d71d39b20 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -787,6 +787,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai //quirk traits #define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance" +#define TRAIT_ANOSMIA "anosmia" #define TRAIT_HEAVY_DRINKER "heavy_drinker" #define TRAIT_AGEUSIA "ageusia" #define TRAIT_HEAVY_SLEEPER "heavy_sleeper" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 01e13fb5df4a..de79baa12626 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -130,6 +130,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_ALWAYS_NO_ACCESS" = TRAIT_ALWAYS_NO_ACCESS, "TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED, "TRAIT_ANGELIC" = TRAIT_ANGELIC, + "TRAIT_ANOSMIA" = TRAIT_ANOSMIA, "TRAIT_ANTENNAE" = TRAIT_ANTENNAE, "TRAIT_ANTICONVULSANT" = TRAIT_ANTICONVULSANT, "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 8c878d4c0481..eda60215ef3d 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -33,6 +33,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_AGENDER" = TRAIT_AGENDER, "TRAIT_AGEUSIA" = TRAIT_AGEUSIA, "TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE, + "TRAIT_ANOSMIA" = TRAIT_ANOSMIA, "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC, "TRAIT_ANXIOUS" = TRAIT_ANXIOUS, "TRAIT_BADDNA" = TRAIT_BADDNA, diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm index a745be2b1a57..afc71936f1b9 100644 --- a/code/datums/components/bakeable.dm +++ b/code/datums/components/bakeable.dm @@ -90,11 +90,24 @@ baked_result.pixel_y = original_object.pixel_y used_tray.AddToPlate(baked_result) + var/list/asomnia_hadders = list() + for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, used_oven)) + if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) + asomnia_hadders += smeller + if(positive_result) - used_oven.visible_message(span_notice("You smell something great coming from [used_oven]."), blind_message = span_notice("You smell something great...")) + used_oven.visible_message( + span_notice("You smell something great coming from [used_oven]."), + blind_message = span_notice("You smell something great..."), + ignored_mobs = asomnia_hadders, + ) BLACKBOX_LOG_FOOD_MADE(baked_result.type) else - used_oven.visible_message(span_warning("You smell a burnt smell coming from [used_oven]."), blind_message = span_warning("You smell a burnt smell...")) + used_oven.visible_message( + span_warning("You smell a burnt smell coming from [used_oven]."), + blind_message = span_warning("You smell a burnt smell..."), + ignored_mobs = asomnia_hadders, + ) SEND_SIGNAL(parent, COMSIG_ITEM_BAKED, baked_result) qdel(parent) diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm index 0e9f3272268b..12038151ba43 100644 --- a/code/datums/diseases/advance/symptoms/fire.dm +++ b/code/datums/diseases/advance/symptoms/fire.dm @@ -81,8 +81,8 @@ living_mob.show_message(span_hear("You hear a crackling noise."), type = MSG_AUDIBLE) else if(prob(50) && !HAS_TRAIT(living_mob, TRAIT_RESISTHEAT)) to_chat(living_mob, span_warning("You feel hot.")) - else - to_chat(living_mob, span_warning("You smell smoke.")) + else if(!HAS_TRAIT(living_mob, TRAIT_ANOSMIA)) //Anosmia quirk holder can't smell anything. + to_chat(living_mob, span_warning("You smell smoket.")) /* Alkali perspiration diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm index 9faa83837d2a..8a5eb31c9d63 100644 --- a/code/datums/mutations/olfaction.dm +++ b/code/datums/mutations/olfaction.dm @@ -40,6 +40,10 @@ to_chat(owner, span_warning("You have no nose!")) return FALSE + if(HAS_TRAIT(living_cast_on, TRAIT_ANOSMIA)) //Anosmia quirk holders can't smell anything + to_chat(owner, span_warning("You can't smell!")) + return FALSE + return TRUE /datum/action/cooldown/spell/olfaction/cast(mob/living/cast_on) diff --git a/code/datums/quirks/negative_quirks/anosmia.dm b/code/datums/quirks/negative_quirks/anosmia.dm new file mode 100644 index 000000000000..bbbf599aeaab --- /dev/null +++ b/code/datums/quirks/negative_quirks/anosmia.dm @@ -0,0 +1,9 @@ +/datum/quirk/item_quirk/anosmia + name = "Anosmia" + desc = "For some reason, you can't smell anything." + icon = FA_ICON_HEAD_SIDE_COUGH_SLASH + value = -2 + mob_trait = TRAIT_ANOSMIA + gain_text = span_notice("You find yourself unable to smell anything!") + lose_text = span_danger("Suddenly, you can smell again!") + medical_record_text = "Patient has lost their sensation of smell." diff --git a/code/modules/antagonists/changeling/powers/pheromone_receptors.dm b/code/modules/antagonists/changeling/powers/pheromone_receptors.dm index 18fda4bf4ff0..0e468159a3c7 100644 --- a/code/modules/antagonists/changeling/powers/pheromone_receptors.dm +++ b/code/modules/antagonists/changeling/powers/pheromone_receptors.dm @@ -23,6 +23,9 @@ /datum/action/changeling/pheromone_receptors/sting_action(mob/living/carbon/user) ..() var/datum/antagonist/changeling/changeling = IS_CHANGELING(user) + if(HAS_TRAIT(user, TRAIT_ANOSMIA)) //Anosmia quirk holders can't smell anything + to_chat(user, span_warning("We can't smell!")) + return if(!receptors_active) to_chat(user, span_warning("We search for the scent of any nearby changelings.")) changeling.chem_recharge_slowdown += 0.25 diff --git a/code/modules/food_and_drinks/machinery/deep_fryer.dm b/code/modules/food_and_drinks/machinery/deep_fryer.dm index e694521ed449..8d934e979ddc 100644 --- a/code/modules/food_and_drinks/machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/machinery/deep_fryer.dm @@ -148,7 +148,11 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( audible_message(span_notice("[src] dings!")) else if (cook_time >= DEEPFRYER_BURNTIME && !frying_burnt) frying_burnt = TRUE - visible_message(span_warning("[src] emits an acrid smell!")) + var/list/asomnia_hadders = list() + for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src)) + if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) + asomnia_hadders += smeller + visible_message(span_warning("[src] emits an acrid smell!"), ignored_mobs = asomnia_hadders) use_energy(active_power_usage) diff --git a/code/modules/food_and_drinks/machinery/oven.dm b/code/modules/food_and_drinks/machinery/oven.dm index 98f1586bc273..14015d109089 100644 --- a/code/modules/food_and_drinks/machinery/oven.dm +++ b/code/modules/food_and_drinks/machinery/oven.dm @@ -90,7 +90,11 @@ baked_item.fire_act(1000) //Hot hot hot! if(SPT_PROB(10, seconds_per_tick)) - visible_message(span_danger("You smell a burnt smell coming from [src]!")) + var/list/asomnia_hadders = list() + for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src)) + if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) + asomnia_hadders += smeller + visible_message(span_danger("You smell a burnt smell coming from [src]!"), ignored_mobs = asomnia_hadders) set_smoke_state(worst_cooked_food_state) update_appearance() use_energy(active_power_usage) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index d9862fa391cf..b087810e44d5 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -298,7 +298,10 @@ if(prob(7.5)) wearer.cause_hallucination(/datum/hallucination/oh_yeah, "H.E.C.K suit", haunt_them = TRUE) else - to_chat(wearer, span_warning("[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")) + if(HAS_TRAIT(wearer, TRAIT_ANOSMIA)) //Anosmia quirk holder cannot fell any smell + to_chat(wearer, span_warning("[pick("You hear faint whispers.","You feel hot.","You hear a roar in the distance.")]")) + else + to_chat(wearer, span_warning("[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")) /obj/item/clothing/head/hooded/hostile_environment name = "H.E.C.K. helmet" diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 26a27e9cbad3..5883d3bf9d29 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -538,7 +538,10 @@ . = ..() if(isvampire(affected_mob)) //incapacitating but not lethal. Unfortunately, vampires cannot vomit. if(SPT_PROB(min((current_cycle-1)/2, 12.5), seconds_per_tick)) - to_chat(affected_mob, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) + if(HAS_TRAIT(affected_mob, TRAIT_ANOSMIA)) + to_chat(affected_mob, span_danger("You feel that something is wrong, your strength is leaving you! You can barely think...")) + else + to_chat(affected_mob, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) affected_mob.Paralyze(10) affected_mob.set_jitter_if_lower(20 SECONDS) else diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index b180cf74ff68..a8c5d3da7128 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -995,7 +995,8 @@ desc = "Fermented prison wine made from fruit, sugar, and despair. You probably shouldn't drink this around Security." icon_state = "trashbag1" // pruno releases air as it ferments, we don't want to simulate this in atmos, but we can make it look like it did for (var/mob/living/M in view(2, get_turf(src))) // letting people and/or narcs know when the pruno is done - to_chat(M, span_info("A pungent smell emanates from [src], like fruit puking out its guts.")) + if(HAS_TRAIT(M, TRAIT_ANOSMIA)) + to_chat(M, span_info("A pungent smell emanates from [src], like fruit puking out its guts.")) playsound(get_turf(src), 'sound/effects/bubbles2.ogg', 25, TRUE) /** diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 52d3bc50833e..8df23a1e1e64 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -286,7 +286,8 @@ var/ratio = (breath.gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10 breather.apply_damage(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type, spread_damage = TRUE) - breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) + if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) + breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) /// Handles NOT having too much o2. only relevant if safe_oxygen_max has a value /obj/item/organ/lungs/proc/safe_oxygen(mob/living/carbon/breather, datum/gas_mixture/breath, old_o2_pp) @@ -305,7 +306,8 @@ if(nitro_pp < safe_nitro_min) // Suffocation side-effects. // Not safe to check the old pp because of can_breath_vacuum - breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro) + if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) + breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro) var/gas_breathed = handle_suffocation(breather, nitro_pp, safe_nitro_min, breath.gases[/datum/gas/nitrogen][MOLES]) if(nitro_pp) breathe_gas_volume(breath, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, volume = gas_breathed) @@ -336,7 +338,8 @@ breather.emote("cough") if((world.time - breather.co2overloadtime) > 12 SECONDS) - breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) + if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) + breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) breather.Unconscious(6 SECONDS) // Lets hurt em a little, let them know we mean business. breather.apply_damage(3, co2_damage_type, spread_damage = TRUE) @@ -355,7 +358,8 @@ // Suffocation side-effects. if(plasma_pp < safe_plasma_min) // Could check old_plasma_pp but vacuum breathing hates me - breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas) + if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) + breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas) // Breathe insufficient amount of Plasma, exhale CO2. var/gas_breathed = handle_suffocation(breather, plasma_pp, safe_plasma_min, breath.gases[/datum/gas/plasma][MOLES]) if(plasma_pp) @@ -378,7 +382,8 @@ // If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so. if(old_plasma_pp < safe_plasma_max) - breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) + if(!HAS_TRAIT(breather, TRAIT_ANOSMIA)) + breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) var/ratio = (breath.gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10 breather.apply_damage(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type, spread_damage = TRUE) @@ -478,6 +483,8 @@ miasma_disease.name = "Unknown" breather.AirborneContractDisease(miasma_disease, TRUE) // Miasma side effects + if (HAS_TRAIT(breather, TRAIT_ANOSMIA)) //Anosmia quirk holder cannot smell miasma, but can get diseases from it. + return switch(miasma_pp) if(0.25 to 5) // At lower pp, give out a little warning @@ -529,7 +536,7 @@ return // More N2O, more severe side-effects. Causes stun/sleep. - if(old_n2o_pp < n2o_para_min) + if(old_n2o_pp < n2o_para_min && !HAS_TRAIT(breather, TRAIT_ANOSMIA)) breather.throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o) // give them one second of grace to wake up and run away a bit! diff --git a/tgstation.dme b/tgstation.dme index e523ceb7a193..10cfa0fe2651 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1705,6 +1705,7 @@ #include "code\datums\quirks\negative_quirks\addict.dm" #include "code\datums\quirks\negative_quirks\all_nighter.dm" #include "code\datums\quirks\negative_quirks\allergic.dm" +#include "code\datums\quirks\negative_quirks\anosmia.dm" #include "code\datums\quirks\negative_quirks\bad_back.dm" #include "code\datums\quirks\negative_quirks\big_hands.dm" #include "code\datums\quirks\negative_quirks\blindness.dm" From 2195a5031d6f61934ce7129b4b9996f38cc082f3 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 20:28:11 -0600 Subject: [PATCH 2/7] Smell-o-vision --- code/__DEFINES/living.dm | 6 + code/datums/components/bakeable.dm | 9 +- code/datums/diseases/advance/symptoms/fire.dm | 4 +- .../mood_events/generic_negative_events.dm | 8 +- code/datums/mood_events/needs_events.dm | 8 -- code/datums/mutations/olfaction.dm | 2 +- code/datums/wounds/internal_bleeding.dm | 5 + .../effects/decals/cleanable/humans.dm | 9 ++ .../objects/effects/decals/cleanable/misc.dm | 19 +++ .../effects/decals/cleanable/robots.dm | 22 +--- code/game/objects/items/cigs_lighters.dm | 2 + code/game/objects/structures/bonfire.dm | 2 + code/game/objects/structures/headpike.dm | 1 + code/game/turfs/open/_open.dm | 2 + .../atmospherics/gasmixtures/gas_types.dm | 14 +- code/modules/fishing/fish/_fish.dm | 2 + .../food_and_drinks/machinery/deep_fryer.dm | 2 +- .../modules/food_and_drinks/machinery/oven.dm | 2 +- code/modules/hydroponics/grown/flowers.dm | 8 ++ code/modules/hydroponics/hydroponics.dm | 4 + code/modules/mob/living/life.dm | 3 + code/modules/mob/living/living_defines.dm | 5 + .../projectiles/ammunition/_ammunition.dm | 28 ++-- code/modules/projectiles/guns/ballistic.dm | 9 ++ code/modules/projectiles/guns/energy.dm | 1 + .../chemistry/reagents/other_reagents.dm | 26 +++- .../reagent_containers/cups/glassbottle.dm | 2 +- code/modules/reagents/reagent_dispenser.dm | 4 + code/modules/surgery/bodyparts/head.dm | 2 + .../surgery/organs/internal/lungs/_lungs.dm | 28 +--- .../surgery/organs/internal/tongue/_tongue.dm | 3 + maplestation.dme | 6 + .../carbon/human/rad_rework/radiation.dm | 16 ++- .../human/species_types/animid/animid_dog.dm | 16 +++ .../code/modules/smells/_smell.dm | 122 ++++++++++++++++++ .../code/modules/smells/living_procs.dm | 116 +++++++++++++++++ .../code/modules/smells/smell_element.dm | 79 ++++++++++++ .../code/modules/smells/smell_holder.dm | 66 ++++++++++ .../code/modules/smells/smell_mood.dm | 52 ++++++++ .../code/modules/smells/temporay_smell.dm | 55 ++++++++ 40 files changed, 686 insertions(+), 84 deletions(-) create mode 100644 maplestation_modules/code/modules/smells/_smell.dm create mode 100644 maplestation_modules/code/modules/smells/living_procs.dm create mode 100644 maplestation_modules/code/modules/smells/smell_element.dm create mode 100644 maplestation_modules/code/modules/smells/smell_holder.dm create mode 100644 maplestation_modules/code/modules/smells/smell_mood.dm create mode 100644 maplestation_modules/code/modules/smells/temporay_smell.dm diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 56cc96b506f0..ea622ea1b8b0 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -211,3 +211,9 @@ #define examining_span_normal(msg) span_infoplain(span_italics(msg)) /// For consistent examine span formatting (small size) #define examining_span_small(msg) span_slightly_smaller(span_infoplain(span_italics(msg))) + +#define SMELL_INTENSITY_FAINT 1 +#define SMELL_INTENSITY_WEAK 6 +#define SMELL_INTENSITY_MODERATE 12 +#define SMELL_INTENSITY_STRONG 24 +#define SMELL_INTENSITY_OVERPOWERING 48 diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm index afc71936f1b9..12c4b9969b09 100644 --- a/code/datums/components/bakeable.dm +++ b/code/datums/components/bakeable.dm @@ -92,15 +92,16 @@ var/list/asomnia_hadders = list() for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, used_oven)) - if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) - asomnia_hadders += smeller + if(!smeller.can_smell()) + asomnia_hadders += smeller if(positive_result) used_oven.visible_message( - span_notice("You smell something great coming from [used_oven]."), + span_notice("You smell something great coming from [used_oven]."), blind_message = span_notice("You smell something great..."), ignored_mobs = asomnia_hadders, ) + new /obj/effect/abstract/smell/oven/good(used_oven.loc) BLACKBOX_LOG_FOOD_MADE(baked_result.type) else used_oven.visible_message( @@ -108,6 +109,8 @@ blind_message = span_warning("You smell a burnt smell..."), ignored_mobs = asomnia_hadders, ) + new /obj/effect/abstract/smell/oven/bad(used_oven.loc) + SEND_SIGNAL(parent, COMSIG_ITEM_BAKED, baked_result) qdel(parent) diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm index 12038151ba43..c3edc3d59c23 100644 --- a/code/datums/diseases/advance/symptoms/fire.dm +++ b/code/datums/diseases/advance/symptoms/fire.dm @@ -81,8 +81,8 @@ living_mob.show_message(span_hear("You hear a crackling noise."), type = MSG_AUDIBLE) else if(prob(50) && !HAS_TRAIT(living_mob, TRAIT_RESISTHEAT)) to_chat(living_mob, span_warning("You feel hot.")) - else if(!HAS_TRAIT(living_mob, TRAIT_ANOSMIA)) //Anosmia quirk holder can't smell anything. - to_chat(living_mob, span_warning("You smell smoket.")) + else if(living_mob.can_smell()) //Anosmia quirk holder can't smell anything. + to_chat(living_mob, span_warning("You smell smoke.")) /* Alkali perspiration diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 16c4f8d51785..7d05dfeeaab3 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -590,14 +590,14 @@ timeout = 1 MINUTES /datum/mood_event/smoke_in_face - description = "Cigarette smoke is disgusting." + description = "They just blew disgusting smoke in my face!" mood_change = -3 timeout = 30 SECONDS /datum/mood_event/smoke_in_face/add_effects(param) - // if(HAS_TRAIT(owner, TRAIT_ANOSMIA)) - // description = "Cigarette smoke is unpleasant." - // mood_change = -1 + if(!owner.can_smell()) + description = "They just blew some unpleasant smoke in my face." + mood_change = -1 if(HAS_TRAIT(owner, TRAIT_SMOKER)) description = "Blowing smoke in my face, really?" mood_change = 0 diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index 31b34585035e..8f988ac53672 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -61,14 +61,6 @@ description = "Oh god, that's disgusting..." mood_change = -8 -/datum/mood_event/disgust/bad_smell - description = "I can smell something horribly decayed inside this room." - mood_change = -6 - -/datum/mood_event/disgust/nauseating_stench - description = "The stench of rotting carcasses is unbearable!" - mood_change = -12 - /datum/mood_event/disgust/dirty_food description = "That was too dirty to eat..." mood_change = -6 diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm index 8a5eb31c9d63..fb7c9a92e900 100644 --- a/code/datums/mutations/olfaction.dm +++ b/code/datums/mutations/olfaction.dm @@ -40,7 +40,7 @@ to_chat(owner, span_warning("You have no nose!")) return FALSE - if(HAS_TRAIT(living_cast_on, TRAIT_ANOSMIA)) //Anosmia quirk holders can't smell anything + if(!living_cast_on.can_smell()) //Anosmia quirk holders can't smell anything to_chat(owner, span_warning("You can't smell!")) return FALSE diff --git a/code/datums/wounds/internal_bleeding.dm b/code/datums/wounds/internal_bleeding.dm index ea8bec76c78e..879d6b352cfc 100644 --- a/code/datums/wounds/internal_bleeding.dm +++ b/code/datums/wounds/internal_bleeding.dm @@ -77,6 +77,11 @@ if(QDELETED(src)) return + if(SPT_PROB(1, seconds_per_tick)) + var/datum/blood_type/blood_type = victim.get_blood_type() + if(blood_type) + to_chat(victim, span_notice("You can taste [blood_type.reagent_type::name].")) + switch(limb.body_zone) if(BODY_ZONE_HEAD) if(SPT_PROB(2, seconds_per_tick)) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 58596cbfabc8..ed130983e525 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,5 +1,8 @@ // NON-MODULE CHANGE : This whole file +#define BLOOD_SMELL_INTENSITY(blood) floor((initial(blood.bloodiness) || blood.bloodiness) * 0.2) +#define BLOOD_SMELL_RADIUS(blood) ceil((initial(blood.bloodiness) || blood.bloodiness) * 0.04) + /obj/effect/decal/cleanable/blood name = "pool of blood" desc = "It's weird and gooey. Perhaps it's the chef's cooking?" @@ -43,6 +46,7 @@ else if(can_dry) START_PROCESSING(SSblood_drying, src) // update_atom_colour() // this is already called by parent via add_atom_colour + AddElement(/datum/element/smell, /datum/smell/blood, BLOOD_SMELL_INTENSITY(src), BLOOD_SMELL_RADIUS(src)) /obj/effect/decal/cleanable/blood/Destroy() STOP_PROCESSING(SSblood_drying, src) @@ -151,6 +155,7 @@ update_appearance() update_atom_colour() STOP_PROCESSING(SSblood_drying, src) + RemoveElement(/datum/element/smell, /datum/smell/blood, BLOOD_SMELL_INTENSITY(src), BLOOD_SMELL_RADIUS(src)) return TRUE /obj/effect/decal/cleanable/blood/lazy_init_reagents() @@ -175,6 +180,9 @@ merger.adjust_bloodiness(bloodiness) merger.slow_dry(1 SECONDS * bloodiness * BLOOD_PER_UNIT_MODIFIER) +#undef BLOOD_SMELL_INTENSITY +#undef BLOOD_SMELL_RADIUS + /obj/effect/decal/cleanable/blood/old bloodiness = 0 dried = TRUE @@ -434,6 +442,7 @@ . = ..() setDir(pick(GLOB.cardinals)) AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 10) + AddElement(/datum/element/smell, /datum/smell/decay, SMELL_INTENSITY_STRONG, 1) /obj/effect/decal/cleanable/blood/drip name = "drop of blood" diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index e3011512936d..c676f1f3d1ab 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -161,6 +161,13 @@ random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") beauty = -150 +/obj/effect/decal/cleanable/vomit/Initialize(mapload, list/datum/disease/diseases) + . = ..() + leave_smell() + +/obj/effect/decal/cleanable/vomit/proc/leave_smell() + AddElement(/datum/element/smell, "vomit", SMELL_INTENSITY_STRONG, 1) + /obj/effect/decal/cleanable/vomit/attack_hand(mob/user, list/modifiers) . = ..() if(.) @@ -187,6 +194,9 @@ icon_state = "vomitnanite_1" random_icon_states = list("vomitnanite_1", "vomitnanite_2", "vomitnanite_3", "vomitnanite_4") +/obj/effect/decal/cleanable/vomit/nanites/leave_smell() + return + /obj/effect/decal/cleanable/vomit/nebula name = "nebula vomit" desc = "Gosh, how... beautiful." @@ -198,6 +208,9 @@ . = ..() update_appearance(UPDATE_OVERLAYS) +/obj/effect/decal/cleanable/vomit/nebula/leave_smell() + return + /obj/effect/decal/cleanable/vomit/nebula/update_overlays() . = ..() . += emissive_appearance(icon, icon_state, src, alpha = src.alpha) @@ -211,6 +224,9 @@ icon_state += "-old" AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 10) +/obj/effect/decal/cleanable/vomit/old/leave_smell() + return + /obj/effect/decal/cleanable/vomit/old/black_bile name = "black bile" desc = "There's something wiggling in there..." @@ -319,6 +335,7 @@ /obj/effect/decal/cleanable/garbage/Initialize(mapload) . = ..() AddElement(/datum/element/swabable, CELL_LINE_TABLE_SLUDGE, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 15) + AddElement(/datum/element/smell, "rotting garbage", SMELL_INTENSITY_MODERATE, 2, "stench") /obj/effect/decal/cleanable/ants name = "space ants" @@ -438,6 +455,8 @@ if(burn_stacks) burn_amount = max(min(burn_stacks, 10), 1) + AddElement(/datum/element/smell, "fuel", floor(SMELL_INTENSITY_MODERATE * burn_amount * 0.1), 1, "stench") + /obj/effect/decal/cleanable/fuel_pool/fire_act(exposed_temperature, exposed_volume) . = ..() ignite() diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 25d491285955..4b89b0397749 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -86,25 +86,9 @@ /obj/effect/decal/cleanable/oil/Initialize(mapload, list/datum/disease/diseases) . = ..() - AddElement(/datum/element/easy_ignite) // NON-MODULE CHANGE - add_blood_DNA(list("UNKNOWN DNA" = /datum/blood_type/oil)) // NON-MODULE CHANGE : For bloody shoes // Yeah don't think about it too much - -// /obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) -// var/attacked_by_hot_thing = I.get_temperature() -// if(attacked_by_hot_thing) -// user.visible_message(span_warning("[user] tries to ignite [src] with [I]!"), span_warning("You try to ignite [src] with [I].")) -// log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) -// fire_act(attacked_by_hot_thing) -// return -// return ..() - -// /obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) -// if(exposed_temperature < 480) -// return -// visible_message(span_danger("[src] catches fire!")) -// var/turf/T = get_turf(src) -// qdel(src) -// new /obj/effect/hotspot(T) + AddElement(/datum/element/easy_ignite) + AddElement(/datum/element/smell, /datum/smell/oil, SMELL_INTENSITY_STRONG, 2) + add_blood_DNA(list("UNKNOWN DNA" = /datum/blood_type/oil)) /obj/effect/decal/cleanable/oil/streak icon_state = "streak1" diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 30532e6915d2..9febfa9e3736 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -466,6 +466,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM /// Handles processing the reagents in the cigarette. /obj/item/cigarette/proc/handle_reagents(mob/living/carbon/smoker, seconds_per_tick) reagents.expose_temperature(heat, 0.05) + if(reagents.has_reagent(/datum/reagent/drug/nicotine, 1, check_subtypes = TRUE)) + new /obj/effect/abstract/smell/cigarette_smoke(get_turf(smoker)) if(reagents.total_volume <= 0) //may have reacted and gone to 0 after expose_temperature return diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index f3f666ba3f1a..7477d7b90462 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -115,6 +115,7 @@ bonfire_burn() particles = new /particles/bonfire() START_PROCESSING(SSobj, src) + AddElement(/datum/element/smell, "smoke", SMELL_INTENSITY_STRONG, 5) /obj/structure/bonfire/fire_act(exposed_temperature, exposed_volume) start_burning() @@ -173,6 +174,7 @@ set_light(0) QDEL_NULL(particles) STOP_PROCESSING(SSobj, src) + RemoveElement(/datum/element/smell, "smoke", SMELL_INTENSITY_STRONG, 5) /obj/structure/bonfire/buckle_mob(mob/living/buckled_mob, force = FALSE, check_loc = TRUE) if(..()) diff --git a/code/game/objects/structures/headpike.dm b/code/game/objects/structures/headpike.dm index 9fdcb4139725..a7d50c2f607b 100644 --- a/code/game/objects/structures/headpike.dm +++ b/code/game/objects/structures/headpike.dm @@ -22,6 +22,7 @@ if(mapload) CheckParts() pixel_x = rand(-8, 8) + AddElement(/datum/element/smell, /datum/smell/decay, SMELL_INTENSITY_MODERATE, 2) /obj/structure/headpike/Destroy() QDEL_NULL(victim) diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 472c889e9979..e180ef1abb01 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -13,6 +13,8 @@ var/broken = FALSE var/burnt = FALSE + /// Assoc list of smell datum to sum of intensities present on this turf + VAR_FINAL/list/collective_smells /// Returns a list of every turf state considered "broken". /// Will be randomly chosen if a turf breaks at runtime. diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index 32acadb3bee5..ed655f1e59b4 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -66,7 +66,8 @@ var/desc ///RGB code for use when a generic color representing the gas is needed. Colors taken from contants.ts var/primary_color - + /// Smell string or typepath for this gas + var/smell /datum/gas/oxygen id = GAS_O2 @@ -110,6 +111,7 @@ base_value = 1.5 desc = "A flammable gas with many other curious properties. It's research is one of NT's primary objective." primary_color = "#ffc0cb" + smell = /datum/smell/plasma /datum/gas/water_vapor id = GAS_WATER_VAPOR @@ -149,6 +151,7 @@ base_value = 1.5 desc = "Causes drowsiness, euphoria, and eventually unconsciousness." primary_color = "#ffe4c4" + smell = "sweet" /datum/gas/nitrium id = GAS_NITRIUM @@ -162,6 +165,7 @@ base_value = 6 desc = "An experimental performance enhancing gas. Nitrium can have amplified effects as more of it gets into your bloodstream." primary_color = "#a52a2a" + smell = "coffee" /datum/gas/tritium id = GAS_TRITIUM @@ -187,6 +191,7 @@ base_value = 1.5 desc = "A powerful hallucinogenic nerve agent able to induce cognitive damage." primary_color = "#9370db" + smell = "sweet" /datum/gas/pluoxium id = GAS_PLUOXIUM @@ -197,6 +202,7 @@ base_value = 2.5 desc = "A gas that could supply even more oxygen to the bloodstream when inhaled, without being an oxidizer." primary_color = "#7b68ee" + smell = "disinfectant" /datum/gas/miasma id = GAS_MIASMA @@ -209,6 +215,7 @@ base_value = 1 desc = "Not necessarily a gas, miasma refers to biological pollutants found in the atmosphere." primary_color = COLOR_OLIVE + smell = /datum/smell/miasma /datum/gas/freon id = GAS_FREON @@ -245,6 +252,7 @@ base_value = 5.5 desc = "Causes deep, regenerative sleep." primary_color = "#fa8072" + smell = "sweet" /datum/gas/proto_nitrate id = GAS_PROTO_NITRATE @@ -257,6 +265,7 @@ base_value = 2.5 desc = "A very volatile gas that reacts differently with various gases." primary_color = "#adff2f" + smell = "ozone" /datum/gas/zauker id = GAS_ZAUKER @@ -269,6 +278,7 @@ base_value = 7 desc = "A highly toxic gas, it's production is highly regulated on top of being difficult. It also breaks down when in contact with nitrogen." primary_color = "#006400" + smell = "death" /datum/gas/halon id = GAS_HALON @@ -304,6 +314,7 @@ base_value = 10 desc = "We still don't know what it does, but it sells for a lot." primary_color = COLOR_MAROON + smell = "yourself" /obj/effect/overlay/gas icon = 'icons/effects/atmospherics.dmi' @@ -326,4 +337,3 @@ /obj/effect/overlay/gas/Initialize(mapload) . = ..() SET_PLANE_W_SCALAR(src, initial(plane), plane_offset) - diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 1800d911d6ad..125fe9c4201f 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -184,6 +184,8 @@ register_context() register_item_context() + AddElement(/datum/element/smell, "fish", SMELL_INTENSITY_WEAK, 2) + /obj/item/fish/add_item_context(atom/source, list/context, obj/item/held_item, mob/user) if(HAS_TRAIT(source, TRAIT_CATCH_AND_RELEASE)) context[SCREENTIP_CONTEXT_RMB] = "Release" diff --git a/code/modules/food_and_drinks/machinery/deep_fryer.dm b/code/modules/food_and_drinks/machinery/deep_fryer.dm index 8d934e979ddc..1c56a21d1229 100644 --- a/code/modules/food_and_drinks/machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/machinery/deep_fryer.dm @@ -150,7 +150,7 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( frying_burnt = TRUE var/list/asomnia_hadders = list() for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src)) - if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) + if(!smeller.can_smell()) asomnia_hadders += smeller visible_message(span_warning("[src] emits an acrid smell!"), ignored_mobs = asomnia_hadders) diff --git a/code/modules/food_and_drinks/machinery/oven.dm b/code/modules/food_and_drinks/machinery/oven.dm index 14015d109089..12c829fee0dd 100644 --- a/code/modules/food_and_drinks/machinery/oven.dm +++ b/code/modules/food_and_drinks/machinery/oven.dm @@ -92,7 +92,7 @@ if(SPT_PROB(10, seconds_per_tick)) var/list/asomnia_hadders = list() for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src)) - if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) + if(!smeller.can_smell()) asomnia_hadders += smeller visible_message(span_danger("You smell a burnt smell coming from [src]!"), ignored_mobs = asomnia_hadders) set_smoke_state(worst_cooked_food_state) diff --git a/code/modules/hydroponics/grown/flowers.dm b/code/modules/hydroponics/grown/flowers.dm index b895aa7622f3..729ddc276f20 100644 --- a/code/modules/hydroponics/grown/flowers.dm +++ b/code/modules/hydroponics/grown/flowers.dm @@ -32,6 +32,10 @@ drop_sound = 'maplestation_modules/sound/items/drop/herb.ogg' pickup_sound = 'maplestation_modules/sound/items/pickup/herb.ogg' +/obj/item/food/grown/poppy/Initialize(mapload, obj/item/seeds/new_seed) + . = ..() + AddElement(/datum/element/smell, "flowers", SMELL_INTENSITY_WEAK, 1, "fragrance") + // Lily /obj/item/seeds/poppy/lily name = "lily seed pack" @@ -309,6 +313,10 @@ bite_consumption_mod = 2 foodtypes = VEGETABLES | GROSS +/obj/item/food/grown/rose/Initialize(mapload, obj/item/seeds/new_seed) + . = ..() + AddElement(/datum/element/smell, "roses", SMELL_INTENSITY_WEAK, 1, "fragrance") + /obj/item/food/grown/rose/equipped(mob/user, slot, initial) . = ..() if(slot & ITEM_SLOT_MASK) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index cc72372bcfed..7c4e8b663576 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1146,6 +1146,10 @@ self_sustaining_overlay_icon_state = null maxnutri = 15 +/obj/machinery/hydroponics/soil/Initialize(mapload) + . = ..() + AddElement(/datum/element/smell, "fresh soil", SMELL_INTENSITY_WEAK, 1) + /obj/machinery/hydroponics/soil/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) return NONE diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 6481efb8c75b..032e18d8dfe0 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -60,7 +60,10 @@ handle_gravity(seconds_per_tick, times_fired) if(stat != DEAD) + if(COOLDOWN_FINISHED(src, smell_cd) && !isnull(mind) && can_smell()) + smell_something() body_temperature_alerts() + handle_wounds(seconds_per_tick, times_fired) if(staminaloss) adjustStaminaLoss(-1 * (stat == DEAD ? 100 : 2.5) * seconds_per_tick) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 29c3f91dc38d..e72cc854e726 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -280,3 +280,8 @@ /// First element is the current martial art - any other elements are "saved" for if they unlearn the first one /// Reference handling is done by the martial arts themselves var/list/datum/martial_art/martial_arts + + /// List of smell datums we smelled recently, we get accustomed to it over time + VAR_FINAL/list/recently_smelled + /// Cooldown between smell attempts + COOLDOWN_DECLARE(smell_cd) diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm index 20744c971669..a61b8bfd86d6 100644 --- a/code/modules/projectiles/ammunition/_ammunition.dm +++ b/code/modules/projectiles/ammunition/_ammunition.dm @@ -34,10 +34,6 @@ ///pacifism check for boolet, set to FALSE if bullet is non-lethal var/harmful = TRUE -/obj/item/ammo_casing/spent - name = "spent bullet casing" - loaded_projectile = null - /obj/item/ammo_casing/Initialize(mapload) . = ..() if(projectile_type) @@ -100,10 +96,6 @@ icon_state = "[initial(icon_state)][loaded_projectile ? "-live" : null]" return ..() -/obj/item/ammo_casing/update_desc() - desc = "[initial(desc)][loaded_projectile ? null : " This one is spent."]" - return ..() - /* * On accidental consumption, 'spend' the ammo, and add in some gunpowder */ @@ -154,3 +146,23 @@ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), src, 'sound/items/welder.ogg', 20, 1), bounce_delay) //If the turf is made of water and the shell casing is still hot, make a sizzling sound when it's ejected. else if(T?.bullet_bounce_sound) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), src, T.bullet_bounce_sound, 20, 1), bounce_delay) //Soft / non-solid turfs that shouldn't make a sound when a shell casing is ejected over them. + +/obj/item/ammo_casing/proc/is_spent(mapload = FALSE) + if(!mapload) + AddComponent(/datum/component/temporary_smell, \ + duration = 4 MINUTES, \ + smell = "gunpowder", \ + intensity = SMELL_INTENSITY_FAINT, \ + radius = 1, \ + wash_types = CLEAN_TYPE_FINGERPRINTS, \ + ) + + name = "spent [name]" + desc += " This one is spent." + +/obj/item/ammo_casing/spent + loaded_projectile = null + +/obj/item/ammo_casing/spent/Initialize(mapload) + . = ..() + is_spent(mapload) diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index ca8561867302..33e1807446a0 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -256,8 +256,17 @@ /obj/item/gun/ballistic/handle_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE) if(!semi_auto && from_firing) return + AddComponent(/datum/component/temporary_smell, \ + duration = 30 SECONDS, \ + smell = "gunpowder", \ + intensity = SMELL_INTENSITY_WEAK, \ + radius = 2, \ + wash_types = CLEAN_TYPE_FINGERPRINTS, \ + ) + var/obj/item/ammo_casing/casing = chambered //Find chambered round if(istype(casing)) //there's a chambered round + casing.is_spent() if(QDELING(casing)) stack_trace("Trying to move a qdeleted casing of type [casing.type]!") chambered = null diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index a434a7b3a3b8..c2d6ddedbb87 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -212,6 +212,7 @@ cell.use(shot.e_cost)//... drain the cell cell chambered = null //either way, released the prepared shot recharge_newshot() //try to charge a new shot + new /obj/effect/abstract/smell/ozone(get_turf(src)) /obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) if(!chambered && can_shoot()) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 92c0bfb3beb1..641ef95fc305 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1173,6 +1173,7 @@ taste_description = "bitterness" ph = 10.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS + smell_type = /obj/effect/abstract/smell/reagent/disinfectant /datum/reagent/space_cleaner/sterilizine/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) . = ..() @@ -1352,16 +1353,28 @@ taste_description = "sourness" reagent_weight = 0.6 //so it sprays further penetrates_skin = VAPOR - var/clean_types = CLEAN_WASH ph = 5.5 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS|REAGENT_AFFECTS_WOUNDS + var/clean_types = CLEAN_WASH + /// What type of smell to produce when exposed + var/obj/effect/abstract/smell/smell_type = /obj/effect/abstract/smell/reagent/cleaning_chemicals + /datum/reagent/space_cleaner/expose_obj(obj/exposed_obj, reac_volume) . = ..() - exposed_obj?.wash(clean_types) + exposed_obj.wash(clean_types) + exposed_obj.AddComponent( \ + /datum/component/temporary_smell, \ + duration = smell_type::duration, \ + smell = smell_type::smell, \ + intensity = smell_type::intensity * (reac_volume / 5), \ + radius = 1, \ + category = smell_type::category \ + ) /datum/reagent/space_cleaner/expose_turf(turf/exposed_turf, reac_volume) . = ..() + new smell_type(exposed_turf, reac_volume) if(reac_volume < 1) return @@ -1380,6 +1393,15 @@ if(methods & (TOUCH|VAPOR)) exposed_mob.wash(clean_types) + exposed_mob.AddComponent( \ + /datum/component/temporary_smell, \ + duration = smell_type::duration, \ + smell = smell_type::smell, \ + intensity = smell_type::intensity * (reac_volume / 5), \ + radius = 1, \ + category = smell_type::category \ + ) + /datum/reagent/space_cleaner/on_burn_wound_processing(datum/wound/flesh/burn_wound) burn_wound.sanitization += 0.3 if(prob(5)) diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index a8c5d3da7128..53b8483e1ea2 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -995,7 +995,7 @@ desc = "Fermented prison wine made from fruit, sugar, and despair. You probably shouldn't drink this around Security." icon_state = "trashbag1" // pruno releases air as it ferments, we don't want to simulate this in atmos, but we can make it look like it did for (var/mob/living/M in view(2, get_turf(src))) // letting people and/or narcs know when the pruno is done - if(HAS_TRAIT(M, TRAIT_ANOSMIA)) + if(M.can_smell()) to_chat(M, span_info("A pungent smell emanates from [src], like fruit puking out its guts.")) playsound(get_turf(src), 'sound/effects/bubbles2.ogg', 25, TRUE) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 1619c3893875..7919a71f0f16 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -411,6 +411,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30 anchored = TRUE reagent_id = /datum/reagent/consumable/nutraslop +/obj/structure/reagent_dispensers/servingdish/Initialize(mapload) + . = ..() + AddComponent(/datum/element/smell, "slop", SMELL_INTENSITY_MODERATE, 1, "stench") + /obj/structure/reagent_dispensers/plumbed name = "stationary water tank" anchored = TRUE diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 6e9bd4b990f0..dd8cee2b015b 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -200,6 +200,8 @@ AddElement(/datum/element/toy_talk) if(!IS_ORGANIC_LIMB(src)) head_flags &= ~HEAD_SHOW_ORGANS_ON_EXAMINE + // specifically to facilitate finding decapitated heads + AddElement(/datum/element/smell, /datum/smell/decay, SMELL_INTENSITY_MODERATE, 2) /obj/item/bodypart/head/get_voice(add_id_name) return "The head of [real_name]" diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 8df23a1e1e64..0f316d8e6c37 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -483,33 +483,9 @@ miasma_disease.name = "Unknown" breather.AirborneContractDisease(miasma_disease, TRUE) // Miasma side effects - if (HAS_TRAIT(breather, TRAIT_ANOSMIA)) //Anosmia quirk holder cannot smell miasma, but can get diseases from it. + if (!breather.can_smell()) //Anosmia quirk holder cannot smell miasma, but can get diseases from it. return - switch(miasma_pp) - if(0.25 to 5) - // At lower pp, give out a little warning - breather.clear_mood_event("smell") - if(prob(5)) - to_chat(breather, span_notice("There is an unpleasant smell in the air.")) - if(5 to 15) - //At somewhat higher pp, warning becomes more obvious - if(prob(15)) - to_chat(breather, span_warning("You smell something horribly decayed inside this room.")) - breather.add_mood_event("smell", /datum/mood_event/disgust/bad_smell) - if(15 to 30) - //Small chance to vomit. By now, people have internals on anyway - if(prob(5)) - to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!")) - breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) - breather.vomit(VOMIT_CATEGORY_DEFAULT) - if(30 to INFINITY) - //Higher chance to vomit. Let the horror start - if(prob(15)) - to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!")) - breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) - breather.vomit(VOMIT_CATEGORY_DEFAULT) - else - breather.clear_mood_event("smell") + // In a full miasma atmosphere with 101.34 pKa, about 10 disgust per breath, is pretty low compared to threshholds // Then again, this is a purely hypothetical scenario and hardly reachable breather.adjust_disgust(0.1 * miasma_pp) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 61bd02e8e5f6..8dccbe567bcc 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -44,6 +44,9 @@ /// Whether this tongue modifies speech via signal var/modifies_speech = FALSE + /// Additive modifier to how sensitive to smell the tongue is (we don't have a nose organ) + var/smell_sensitivity = 0 + /obj/item/organ/tongue/Initialize(mapload) . = ..() // Setup the possible languages list diff --git a/maplestation.dme b/maplestation.dme index 94fc3a30277b..f13c1579cd1f 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6765,6 +6765,12 @@ #include "maplestation_modules\code\modules\research\xenobiology\cores.dm" #include "maplestation_modules\code\modules\research\xenobiology\potions.dm" #include "maplestation_modules\code\modules\robotic_limb_detach\robot_limb_detach_quirk.dm" +#include "maplestation_modules\code\modules\smells\_smell.dm" +#include "maplestation_modules\code\modules\smells\living_procs.dm" +#include "maplestation_modules\code\modules\smells\smell_element.dm" +#include "maplestation_modules\code\modules\smells\smell_holder.dm" +#include "maplestation_modules\code\modules\smells\smell_mood.dm" +#include "maplestation_modules\code\modules\smells\temporay_smell.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_arms.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_digi.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_reskins.dm" diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/rad_rework/radiation.dm b/maplestation_modules/code/modules/mob/living/carbon/human/rad_rework/radiation.dm index 9bd1ad5fa98d..ef23e8d57e6b 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/rad_rework/radiation.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/rad_rework/radiation.dm @@ -99,24 +99,28 @@ playsound(owner, pick('sound/effects/wounds/sizzle1.ogg', 'sound/effects/wounds/sizzle2.ogg'), 50, vary = TRUE) /datum/status_effect/irradiated/proc/random_effects(time_since_irradiated, seconds_per_tick) + if(time_since_irradiated > 1 MINUTES && SPT_PROB(2, seconds_per_tick)) + var/datum/blood_type/blood_type = owner.get_blood_type() + if(time_since_irradiated > 2 MINUTES && prob(33)) + var/mob/living/carbon/get_sick = owner + get_sick.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 10) + else if(blood_type) + to_chat(owner, span_notice("You can taste [blood_type.reagent_type::name].")) + if(time_since_irradiated > 2 MINUTES && SPT_PROB(0.5, seconds_per_tick)) if(!owner.IsParalyzed()) owner.emote("collapse") owner.Paralyze(0.3 SECONDS) to_chat(owner, span_danger("You feel weak.")) - if(time_since_irradiated > 2 MINUTES && SPT_PROB(0.5, seconds_per_tick)) - var/mob/living/carbon/get_sick = owner - get_sick.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 10) - - if(time_since_irradiated > 2 MINUTES && SPT_PROB(0.5, seconds_per_tick) && owner.can_mutate()) + if(time_since_irradiated > 3 MINUTES && SPT_PROB(0.5, seconds_per_tick) && owner.can_mutate()) var/mob/living/carbon/get_mutated = owner to_chat(owner, span_danger("You mutate!")) get_mutated.easy_random_mutate(NEGATIVE | MINOR_NEGATIVE) get_mutated.emote("gasp") get_mutated.domutcheck() - if(time_since_irradiated > 1 MINUTES && SPT_PROB(7.5, seconds_per_tick)) + if(time_since_irradiated > 5 MINUTES && SPT_PROB(7.5, seconds_per_tick)) var/obj/item/bodypart/head/head = owner.get_bodypart(BODY_ZONE_HEAD) if(!isnull(head) && head.hairstyle != "Bald" && (head.head_flags & (HEAD_HAIR|HEAD_FACIAL_HAIR))) to_chat(owner, span_danger("Your hair starts to fall out in clumps...")) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_dog.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_dog.dm index d98c150e9c1f..585d3309b312 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_dog.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/animid/animid_dog.dm @@ -9,6 +9,21 @@ name = "Canid" icon = FA_ICON_DOG +/datum/animid_type/dog/get_extra_perks() + var/list/to_add = list() + + to_add += list( + list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = FA_ICON_GRIN_TONGUE_SQUINT, + SPECIES_PERK_NAME = "Sensitive Sniffer", + SPECIES_PERK_DESC = "[name]s have a strong sense of smell. \ + Pleasant and unpleasant odors alike are more potent to them.", + ), + ) + + return to_add + /obj/item/organ/tongue/dog name = "dog tongue" desc = "A long, rough tongue belonging to a dog." @@ -17,6 +32,7 @@ disliked_foodtypes = VEGETABLES | FRUIT | NUTS // most of them don't like their veggies tho toxic_foodtypes = TOXIC | SUGAR // chocolate + smell_sensitivity = SMELL_INTENSITY_MODERATE // ups all smells a bit /obj/item/organ/ears/dog name = "dog ears" diff --git a/maplestation_modules/code/modules/smells/_smell.dm b/maplestation_modules/code/modules/smells/_smell.dm new file mode 100644 index 000000000000..3da7d7a77f1b --- /dev/null +++ b/maplestation_modules/code/modules/smells/_smell.dm @@ -0,0 +1,122 @@ +/// Singleton datum representing a smell +/datum/smell + /// Text of the smell + var/text = "stinky" + /// The "type" of smell it is + var/category = "smell" + +/datum/smell/New(set_text, set_category) + if(set_text) + text = set_text + if(set_category) + category = set_category + +/// Can the passed mob smell this smell? +/datum/smell/proc/can_mob_smell(mob/living/whom) + return TRUE + +/// Any bonus effects when smelling this smell +/datum/smell/proc/on_smell(mob/living/whom, intensity) + return + +/// Format the smell to output text +/datum/smell/proc/format_smell(mob/living/for_whom, intensity, solo = FALSE, used_text = src.text, used_category = src.category) + switch(intensity) + if(SMELL_INTENSITY_FAINT to SMELL_INTENSITY_WEAK) + return "[solo ? "the" : "a"] faint [used_category] of [used_text]" + if(SMELL_INTENSITY_WEAK to SMELL_INTENSITY_MODERATE) + return "[solo ? "the" : "a"] weak [used_category] of [used_text]" + if(SMELL_INTENSITY_MODERATE to SMELL_INTENSITY_STRONG) + return "the [used_category] of [used_text]" + if(SMELL_INTENSITY_STRONG to SMELL_INTENSITY_OVERPOWERING) + return "[solo ? "the" : "a"] strong [used_category] of [used_text]" + if(SMELL_INTENSITY_OVERPOWERING to INFINITY) + return "[solo ? "the" : "an"] overpowering [used_category] of [used_text]" + + stack_trace("Invalid intensity [intensity] passed to /datum/smell/format_smell") + return used_text // fallback + +// Blood +/datum/smell/blood + text = span_danger("blood") // melbert todo : needs special handing for special bloodtypes + category = "stench" + +/datum/smell/blood/on_smell(mob/living/whom, intensity) + whom.add_mood_event("blood-smell", /datum/mood_event/blood_smell) + +/datum/smell/blood/format_smell(mob/living/for_whom, intensity, solo, used_text, used_category) + if(HAS_TRAIT(for_whom, TRAIT_MORBID) || isvampire(for_whom)) + used_category = "scent" + return ..() + +// Oil +/datum/smell/oil + text = "oil" + category = "odor" + +/datum/smell/oil/on_smell(mob/living/whom, intensity) + whom.add_mood_event("oil-smell", /datum/mood_event/oil_smell) + +// Cigarette Smoke +/datum/smell/cigarette_smoke + text = "cigarette smoke" + category = "odor" + +/datum/smell/cigarette_smoke/on_smell(mob/living/whom, intensity) + whom.add_mood_event("cigarette-smoke", /datum/mood_event/cigarette_smoke) + +// Plasma +/datum/smell/plasma + text = span_purple("plasma") + category = "odor" + +// Miasma +/datum/smell/miasma + text = span_green("miasma") + category = "stench" + +/datum/smell/miasma/on_smell(mob/living/whom, intensity) + switch(intensity) + if(SMELL_INTENSITY_MODERATE to SMELL_INTENSITY_STRONG) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/bad_smell) + + if(SMELL_INTENSITY_STRONG to SMELL_INTENSITY_OVERPOWERING) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/really_bad_smell) + if(prob(5) && iscarbon(whom)) + var/mob/living/carbon/carb_whom = whom + carb_whom.vomit(VOMIT_CATEGORY_DEFAULT) + + if(SMELL_INTENSITY_OVERPOWERING to INFINITY) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/nauseating_stench) + if(prob(15) && iscarbon(whom)) + var/mob/living/carbon/carb_whom = whom + carb_whom.vomit(VOMIT_CATEGORY_DEFAULT) + +// Decay (mimics miasma) +/datum/smell/decay + text = span_warning("decay") + category = "stench" + +/datum/smell/decay/on_smell(mob/living/whom, intensity) + switch(intensity) + if(0 to SMELL_INTENSITY_MODERATE) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/minor_bad_smell) + + if(SMELL_INTENSITY_MODERATE to SMELL_INTENSITY_OVERPOWERING) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/bad_smell) + + if(SMELL_INTENSITY_OVERPOWERING to INFINITY) + whom.add_mood_event("decay-smell", /datum/mood_event/disgust/really_bad_smell) + +/datum/smell/good_food + text = "something good" + category = "aroma" + +/datum/smell/good_food/on_smell(mob/living/whom, intensity) + whom.add_mood_event("good-food-aroma", /datum/mood_event/good_food_aroma) + +/datum/smell/burnt_food + text = "something bad" + +/datum/smell/burnt_food/on_smell(mob/living/whom, intensity) + whom.add_mood_event("burnt-food-aroma", /datum/mood_event/burnt_food_aroma) diff --git a/maplestation_modules/code/modules/smells/living_procs.dm b/maplestation_modules/code/modules/smells/living_procs.dm new file mode 100644 index 000000000000..525164656614 --- /dev/null +++ b/maplestation_modules/code/modules/smells/living_procs.dm @@ -0,0 +1,116 @@ +/// Can this mob smell things? +/mob/proc/can_smell() + if(HAS_TRAIT(src, TRAIT_ANOSMIA)) + return FALSE + + if(stat == DEAD) + return FALSE + + return TRUE + +/mob/living/carbon/human/can_smell() + if(!..()) + return FALSE + + if(failed_last_breath || internal || external) + return FALSE + + if(has_smoke_protection()) + return FALSE + + return TRUE + +/// Attempt to smell things around us +/mob/living/proc/smell_something() + var/turf/open/smellable = get_turf(src) + if(!istype(smellable)) + return + + var/datum/gas_mixture/air = smellable.return_air() + var/total_moles = air?.total_moles() + if(total_moles <= 0) + return + + var/list/collective_smells_with_gasses = LAZYLISTDUPLICATE(smellable.collective_smells) + var/pressuremod = 0 + for(var/datum/gas/gas_type as anything in air.gases) + if(!gas_type::smell) + continue + + pressuremod ||= clamp(round(air.return_pressure() / ONE_ATMOSPHERE, 0.1), 0.1, 4.0) + var/datum/smell/gas_smell = get_smell(gas_type::smell) + switch(air.gases[gas_type][MOLES] / total_moles) + if(0.05 to 0.25) + LAZYADDASSOC(collective_smells_with_gasses, gas_smell, (SMELL_INTENSITY_WEAK * pressuremod)) + if(0.25 to 0.5) + LAZYADDASSOC(collective_smells_with_gasses, gas_smell, (SMELL_INTENSITY_MODERATE * pressuremod)) + if(0.5 to 0.75) + LAZYADDASSOC(collective_smells_with_gasses, gas_smell, (SMELL_INTENSITY_STRONG * pressuremod)) + if(0.75 to 1.0) + LAZYADDASSOC(collective_smells_with_gasses, gas_smell, (SMELL_INTENSITY_OVERPOWERING * pressuremod)) + + if(!LAZYLEN(collective_smells_with_gasses)) + return + + // all smells currently being experienced sorted by effective intensity + var/list/all_smells = list() + var/obj/item/organ/tongue/tongue = get_organ_by_type(__IMPLIED_TYPE__) + var/obj/item/organ/brain/brain = get_organ_by_type(__IMPLIED_TYPE__) + + for(var/smell_effect_untyped, smell_intensity in collective_smells_with_gasses) + var/datum/smell/smell_effect = smell_effect_untyped + var/effective_intensity = smell_intensity - LAZYACCESS(recently_smelled, smell_effect) + tongue?.smell_sensitivity - (brain?.damage * 0.1) + if(effective_intensity <= 0 || !smell_effect.can_mob_smell(src)) + continue + + all_smells[smell_effect] += effective_intensity + // insertion sort as we go to keep highest intensity first + // this list is small, probably averages 4 elements. so it's fine + var/original_index = all_smells.Find(smell_effect) + var/correct_index = original_index + while(correct_index > 1) + if(all_smells[correct_index - 1] > all_smells[correct_index]) + break + correct_index -= 1 + if(correct_index != original_index) + all_smells.Swap(original_index, correct_index) + + if(!length(all_smells)) + return + + var/list/readable_smells = list() + var/highest_intensity = all_smells[all_smells[1]] + + for(var/i in 1 to length(all_smells)) + var/datum/smell/smell_effect = all_smells[i] + var/smell_intensity = all_smells[smell_effect] + // subsequent smells have a chance to be ignored based on how strong the highest intensity smell is + if(i > 1 && prob(50 + highest_intensity - smell_intensity - tongue?.smell_sensitivity + (brain?.damage * 0.1))) + break + + smell_effect.on_smell(src, smell_intensity) + readable_smells += smell_effect.format_smell(src, smell_intensity, solo = (length(all_smells) == 1)) + // every time you smell something, further smells are -2 intensity, which compounds + LAZYADDASSOC(recently_smelled, smell_effect, 2) + // after 5 minutes of not smelling it, we lose all acclimation to it. + // however the reset timer is restarted each time we smell it + addtimer(CALLBACK(src, PROC_REF(remove_recent_smell), smell_effect), 5 MINUTES, TIMER_UNIQUE|TIMER_DELETE_ME|TIMER_OVERRIDE) + + to_chat(src, span_smallnoticeital("[capitalize(english_list(readable_smells))] fills the air.")) + COOLDOWN_START(src, smell_cd, clamp((15 MINUTES) / (values_sum(all_smells) / LAZYLEN(all_smells)), 30 SECONDS, 5 MINUTES)) + +/mob/living/proc/remove_recent_smell(datum/smell/smell_effect) + LAZYREMOVE(recently_smelled, smell_effect) + +/mob/living/carbon/human/death(gibbed, cause_of_death) + . = ..() + if(gibbed) + return + if(HAS_TRAIT(src, TRAIT_NO_ORGAN_DECAY)) + return + // melbert todo make this more complicated later + AddElement(/datum/element/smell, /datum/smell/decay, SMELL_INTENSITY_STRONG, 2) + +/mob/living/carbon/human/revive(full_heal_flags, excess_healing, force_grab_ghost) + . = ..() + RemoveElement(/datum/element/smell, /datum/smell/decay, SMELL_INTENSITY_STRONG, 2) diff --git a/maplestation_modules/code/modules/smells/smell_element.dm b/maplestation_modules/code/modules/smells/smell_element.dm new file mode 100644 index 000000000000..d08f95e56c22 --- /dev/null +++ b/maplestation_modules/code/modules/smells/smell_element.dm @@ -0,0 +1,79 @@ +/// Returns singleton for a passed smell typepath or string+category +/proc/get_smell(smell, category = /datum/smell::category) + var/static/list/smell_register = list() + + . = smell_register[smell] + if(isnull(.)) + if(ispath(smell, /datum/smell)) + . = new smell() + smell_register[smell] = . + + else if(istext(smell)) + . = new /datum/smell(smell, category) + smell_register["[smell]-[category]"] = . + + else + stack_trace("Invalid smell input passed to get_smell: [smell || "null"]") + +/// The actual datum that handles emitting a smell from an atom +/// NB: Currently nested contents don't propagate smell out to turf. Maybe later +/datum/element/smell + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + + /// Smell singleton this element owns + var/datum/smell/smell + /// How strong the smell is + var/intensity + /// How big the smell radius is + var/radius + +/datum/element/smell/Attach(datum/target, smell = "stink", intensity = 1, radius = 2, category) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + + if(isnull(src.smell)) + src.smell = get_smell(smell, category) + src.intensity = intensity + src.radius = radius + + var/atom/atom_target = target + if(isturf(atom_target.loc)) + mark_turfs(target, atom_target.loc) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(update_turfs)) + +/datum/element/smell/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_MOVABLE_MOVED) + var/atom/atom_target = target + if(isturf(atom_target.loc)) + unmark_turfs(target, atom_target.loc) + +/datum/element/smell/proc/update_turfs(atom/movable/source, atom/old_loc) + SIGNAL_HANDLER + + if(isturf(old_loc)) + unmark_turfs(source, old_loc) + if(isturf(source.loc)) + mark_turfs(source, source.loc) + +/// Used to calculate smell intensity at a target turf based on distance from center +#define CALCULATE_SMELL_INTENSITY(base_intensity, center_turf, target_turf, radius) \ + clamp(base_intensity * (get_dist(center_turf, target_turf) / max(1, radius)), SMELL_INTENSITY_FAINT, base_intensity) + +/datum/element/smell/proc/mark_turfs(atom/source, atom/center) + for(var/turf/open/nearby in RANGE_TURFS(radius, center)) + LAZYINITLIST(nearby.collective_smells) + nearby.collective_smells[smell] += CALCULATE_SMELL_INTENSITY(intensity, center, nearby, radius) + +/datum/element/smell/proc/unmark_turfs(atom/source, atom/center) + for(var/turf/open/nearby in RANGE_TURFS(radius, center)) + if(!LAZYLEN(nearby.collective_smells)) // ?? + continue + + nearby.collective_smells[smell] -= CALCULATE_SMELL_INTENSITY(intensity, center, nearby, radius) + if(nearby.collective_smells[smell] <= 0) + LAZYREMOVE(nearby.collective_smells, smell) + +#undef CALCULATE_SMELL_INTENSITY diff --git a/maplestation_modules/code/modules/smells/smell_holder.dm b/maplestation_modules/code/modules/smells/smell_holder.dm new file mode 100644 index 000000000000..6dcc0b48c919 --- /dev/null +++ b/maplestation_modules/code/modules/smells/smell_holder.dm @@ -0,0 +1,66 @@ +/// Dummy atom to use for a smell that persists in an area +/obj/effect/abstract/smell + invisibility = INVISIBILITY_MAXIMUM + /// Smell text or typepath + var/smell + /// Smell category + var/category + /// How strong the smell is + var/intensity = 1 + /// How big the smell radius is + var/radius = 2 + /// Optional, duration of the smell effect + var/duration + +/obj/effect/abstract/smell/Initialize(mapload) + . = ..() + if(duration) + QDEL_IN(src, duration) + AddElement(/datum/element/smell, smell, intensity, radius, category) + +// /obj/effect/abstract/smell/gunpowder +// smell = "gunpowder" +// intensity = SMELL_INTENSITY_FAINT +// duration = 10 MINUTES + +/obj/effect/abstract/smell/ozone + smell = "ozone" + category = "fragrance" + intensity = SMELL_INTENSITY_FAINT + duration = 10 MINUTES + +/obj/effect/abstract/smell/reagent + /// Intensity scales based on volume used and this factor + var/volume_scale_factor = 5 + +/obj/effect/abstract/smell/reagent/Initialize(mapload, volume = 1) + . = ..() + intensity *= clamp(round(volume / volume_scale_factor, 0.1), 0.1, 1) + +/obj/effect/abstract/smell/reagent/cleaning_chemicals + smell = "cleaning chemicals" + category = "fragrance" + intensity = SMELL_INTENSITY_WEAK + duration = 2 MINUTES + +/obj/effect/abstract/smell/reagent/disinfectant + smell = "disinfectant" + category = "scent" + intensity = SMELL_INTENSITY_MODERATE + duration = 2 MINUTES + +/obj/effect/abstract/smell/oven + intensity = SMELL_INTENSITY_MODERATE + duration = 5 MINUTES + radius = 3 + +/obj/effect/abstract/smell/oven/good + smell = /datum/smell/good_food + +/obj/effect/abstract/smell/oven/bad + smell = /datum/smell/burnt_food + +/obj/effect/abstract/smell/cigarette_smoke + smell = /datum/smell/cigarette_smoke + intensity = SMELL_INTENSITY_MODERATE + duration = 20 SECONDS diff --git a/maplestation_modules/code/modules/smells/smell_mood.dm b/maplestation_modules/code/modules/smells/smell_mood.dm new file mode 100644 index 000000000000..20256e67fba2 --- /dev/null +++ b/maplestation_modules/code/modules/smells/smell_mood.dm @@ -0,0 +1,52 @@ +/datum/mood_event/blood_smell + description = "The metallic scent of blood fills the air." + mood_change = -2 + timeout = 30 SECONDS + +/datum/mood_event/blood_smell/add_effects(...) + if(HAS_TRAIT(owner, TRAIT_MORBID) || isvampire(owner)) + mood_change = 0 + +/datum/mood_event/oil_smell + description = "The pungent odor of oil fills the air." + mood_change = -1 + timeout = 10 SECONDS + +/datum/mood_event/cigarette_smoke + description = "The acrid smell of cigarette smoke lingers in the air." + mood_change = -1 + timeout = 10 SECONDS + +/datum/mood_event/cigarette_smoke/add_effects(...) + if(HAS_TRAIT(owner, TRAIT_SMOKER)) + mood_change = 0 + +/datum/mood_event/disgust/minor_bad_smell + description = "There's an unpleasant smell in the air." + mood_change = -1 + timeout = 1 MINUTES + +/datum/mood_event/disgust/bad_smell + description = "I think something must have died in here." + mood_change = -3 + timeout = 1 MINUTES + +/datum/mood_event/disgust/really_bad_smell + description = "Something horribly decayed is in this room." + mood_change = -6 + timeout = 1 MINUTES + +/datum/mood_event/disgust/nauseating_stench + description = "The stench of rot is unbearable!" + mood_change = -12 + timeout = 1 MINUTES + +/datum/mood_event/good_food_aroma + description = "That smells delicious!" + mood_change = 2 + timeout = 2 MINUTES + +/datum/mood_event/burnt_food_aroma + description = "Did someone leave something burning?" + mood_change = -1 + timeout = 2 MINUTES diff --git a/maplestation_modules/code/modules/smells/temporay_smell.dm b/maplestation_modules/code/modules/smells/temporay_smell.dm new file mode 100644 index 000000000000..790da017eadf --- /dev/null +++ b/maplestation_modules/code/modules/smells/temporay_smell.dm @@ -0,0 +1,55 @@ +/// Attach this component to an atom to make it smell temporarily +/datum/component/temporary_smell + dupe_mode = COMPONENT_DUPE_SELECTIVE + /// Smell applied, tracked solely for dupe checking + VAR_FINAL/smell + /// Intensity of the smell applied, tracked solely for dupe checking + VAR_FINAL/intensity + /// Radius of the smell applied, tracked solely for dupe checking + VAR_FINAL/radius + /// Category of the smell applied, tracked solely for dupe checking + VAR_FINAL/category + + /// Flags indicating which cleaning types will remove this smell. If NONE, no cleaning type will remove it. + VAR_FINAL/wash_types = NONE + +/datum/component/temporary_smell/Initialize(duration = 10 MINUTES, smell, intensity, radius, category, wash_types = NONE) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + src.smell = smell + src.intensity = intensity + src.radius = radius + src.category = category + src.wash_types = wash_types + + AddElement(/datum/element/smell, smell, intensity, radius, category) + addtimer(CALLBACK(src, PROC_REF(clean_up)), duration, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME|TIMER_NO_HASH_WAIT) + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_wash)) + +/datum/component/temporary_smell/Destroy() + UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT) + return ..() + +/datum/component/temporary_smell/proc/clean_up(...) + SIGNAL_HANDLER + qdel(src) + +/datum/component/temporary_smell/proc/on_wash(datum/source, clean_types) + SIGNAL_HANDLER + + if(wash_types && (clean_types & wash_types)) + qdel(src) + +/datum/component/temporary_smell/CheckDupeComponent(datum/component/clone, duration, smell, intensity, radius, category) + if(smell != src.smell || category != src.category) + return FALSE + + if(intensity > src.intensity || radius > src.radius) + RemoveElement(/datum/element/smell, smell, src.intensity, src.radius, category) + AddElement(/datum/element/smell, smell, intensity, radius, category) + src.intensity = intensity + src.radius = radius + + addtimer(CALLBACK(src, PROC_REF(clean_up)), duration, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME|TIMER_NO_HASH_WAIT) + return TRUE From e15e40d2fa0333c904b37130a41d946bfd228471 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 20:29:07 -0600 Subject: [PATCH 3/7] Backwards --- maplestation_modules/code/modules/smells/living_procs.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maplestation_modules/code/modules/smells/living_procs.dm b/maplestation_modules/code/modules/smells/living_procs.dm index 525164656614..759a4300c8d3 100644 --- a/maplestation_modules/code/modules/smells/living_procs.dm +++ b/maplestation_modules/code/modules/smells/living_procs.dm @@ -59,7 +59,7 @@ for(var/smell_effect_untyped, smell_intensity in collective_smells_with_gasses) var/datum/smell/smell_effect = smell_effect_untyped - var/effective_intensity = smell_intensity - LAZYACCESS(recently_smelled, smell_effect) + tongue?.smell_sensitivity - (brain?.damage * 0.1) + var/effective_intensity = smell_intensity - LAZYACCESS(recently_smelled, smell_effect) + tongue?.smell_sensitivity + (brain?.damage * -0.1) if(effective_intensity <= 0 || !smell_effect.can_mob_smell(src)) continue @@ -85,7 +85,7 @@ var/datum/smell/smell_effect = all_smells[i] var/smell_intensity = all_smells[smell_effect] // subsequent smells have a chance to be ignored based on how strong the highest intensity smell is - if(i > 1 && prob(50 + highest_intensity - smell_intensity - tongue?.smell_sensitivity + (brain?.damage * 0.1))) + if(i > 1 && prob(50 + highest_intensity - smell_intensity - tongue?.smell_sensitivity - (brain?.damage * -0.1))) break smell_effect.on_smell(src, smell_intensity) From c33ad2952b07e1756f02daffccdaf4b5efe02c5b Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 20:36:40 -0600 Subject: [PATCH 4/7] Unneeded --- maplestation_modules/code/modules/smells/_smell.dm | 2 ++ maplestation_modules/code/modules/smells/living_procs.dm | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/maplestation_modules/code/modules/smells/_smell.dm b/maplestation_modules/code/modules/smells/_smell.dm index 3da7d7a77f1b..0a73c49e66a1 100644 --- a/maplestation_modules/code/modules/smells/_smell.dm +++ b/maplestation_modules/code/modules/smells/_smell.dm @@ -76,6 +76,7 @@ category = "stench" /datum/smell/miasma/on_smell(mob/living/whom, intensity) + whom.adjust_disgust(10) switch(intensity) if(SMELL_INTENSITY_MODERATE to SMELL_INTENSITY_STRONG) whom.add_mood_event("decay-smell", /datum/mood_event/disgust/bad_smell) @@ -98,6 +99,7 @@ category = "stench" /datum/smell/decay/on_smell(mob/living/whom, intensity) + whom.adjust_disgust(10) switch(intensity) if(0 to SMELL_INTENSITY_MODERATE) whom.add_mood_event("decay-smell", /datum/mood_event/disgust/minor_bad_smell) diff --git a/maplestation_modules/code/modules/smells/living_procs.dm b/maplestation_modules/code/modules/smells/living_procs.dm index 759a4300c8d3..60ee27ac0e4b 100644 --- a/maplestation_modules/code/modules/smells/living_procs.dm +++ b/maplestation_modules/code/modules/smells/living_procs.dm @@ -85,7 +85,7 @@ var/datum/smell/smell_effect = all_smells[i] var/smell_intensity = all_smells[smell_effect] // subsequent smells have a chance to be ignored based on how strong the highest intensity smell is - if(i > 1 && prob(50 + highest_intensity - smell_intensity - tongue?.smell_sensitivity - (brain?.damage * -0.1))) + if(i > 1 && prob(50 + highest_intensity - smell_intensity)) break smell_effect.on_smell(src, smell_intensity) From 11a12dc8b4ac78dfe76cb1660a79ec0b81cc7000 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 20:39:06 -0600 Subject: [PATCH 5/7] Oops --- code/modules/reagents/reagent_dispenser.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 7919a71f0f16..b0581a916719 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -413,7 +413,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30 /obj/structure/reagent_dispensers/servingdish/Initialize(mapload) . = ..() - AddComponent(/datum/element/smell, "slop", SMELL_INTENSITY_MODERATE, 1, "stench") + AddElement(/datum/element/smell, "slop", SMELL_INTENSITY_MODERATE, 1, "stench") /obj/structure/reagent_dispensers/plumbed name = "stationary water tank" From 47ad5af8f03145f08f3b6bb4c1ebc7ed66741340 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 20:41:01 -0600 Subject: [PATCH 6/7] Fix --- maplestation.dme | 2 +- .../smells/{temporay_smell.dm => temporary_smell.dm} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename maplestation_modules/code/modules/smells/{temporay_smell.dm => temporary_smell.dm} (88%) diff --git a/maplestation.dme b/maplestation.dme index f13c1579cd1f..7a37f9a557f2 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6770,7 +6770,7 @@ #include "maplestation_modules\code\modules\smells\smell_element.dm" #include "maplestation_modules\code\modules\smells\smell_holder.dm" #include "maplestation_modules\code\modules\smells\smell_mood.dm" -#include "maplestation_modules\code\modules\smells\temporay_smell.dm" +#include "maplestation_modules\code\modules\smells\temporary_smell.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_arms.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_digi.dm" #include "maplestation_modules\code\modules\surgery\bodyparts\cyber_reskins.dm" diff --git a/maplestation_modules/code/modules/smells/temporay_smell.dm b/maplestation_modules/code/modules/smells/temporary_smell.dm similarity index 88% rename from maplestation_modules/code/modules/smells/temporay_smell.dm rename to maplestation_modules/code/modules/smells/temporary_smell.dm index 790da017eadf..b3a509114efa 100644 --- a/maplestation_modules/code/modules/smells/temporay_smell.dm +++ b/maplestation_modules/code/modules/smells/temporary_smell.dm @@ -23,7 +23,7 @@ src.category = category src.wash_types = wash_types - AddElement(/datum/element/smell, smell, intensity, radius, category) + parent.AddElement(/datum/element/smell, smell, intensity, radius, category) addtimer(CALLBACK(src, PROC_REF(clean_up)), duration, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME|TIMER_NO_HASH_WAIT) RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_wash)) @@ -46,8 +46,8 @@ return FALSE if(intensity > src.intensity || radius > src.radius) - RemoveElement(/datum/element/smell, smell, src.intensity, src.radius, category) - AddElement(/datum/element/smell, smell, intensity, radius, category) + parent.RemoveElement(/datum/element/smell, smell, src.intensity, src.radius, category) + parent.AddElement(/datum/element/smell, smell, intensity, radius, category) src.intensity = intensity src.radius = radius From fcf60342cf734e8bb32782bb7ec7730471338109 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 21 Jan 2026 21:23:19 -0600 Subject: [PATCH 7/7] Fix --- maplestation_modules/code/modules/smells/temporary_smell.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maplestation_modules/code/modules/smells/temporary_smell.dm b/maplestation_modules/code/modules/smells/temporary_smell.dm index b3a509114efa..d655abf8739e 100644 --- a/maplestation_modules/code/modules/smells/temporary_smell.dm +++ b/maplestation_modules/code/modules/smells/temporary_smell.dm @@ -41,8 +41,8 @@ if(wash_types && (clean_types & wash_types)) qdel(src) -/datum/component/temporary_smell/CheckDupeComponent(datum/component/clone, duration, smell, intensity, radius, category) - if(smell != src.smell || category != src.category) +/datum/component/temporary_smell/CheckDupeComponent(datum/component/clone, duration, smell, intensity, radius, category, wash_types) + if(smell != src.smell || category != src.category || wash_types != src.wash_types) return FALSE if(intensity > src.intensity || radius > src.radius)