Files
Bubberstation/code/datums/mutations/olfaction.dm
Ghom 14fb86e3e8 Mutation code cleanup, mutations now have sources to avoid concurrency problems. (#91346)
## About The Pull Request
This PR aims to clean or bring up to date portions of code about dna,
the dna console and mutations. This includes taking care of or removing
some of the awful choices like the pratically useless
`datum/mutation/human` pathing, or the class variable, in favor of using
sources to avoid potential issues with extraneous sources of a mutation.

The files changed are over a hundred just because I removed the
`datum/mutation/human` path, but the actual bulk of the code is mainly
shared between the datum/dna.dm, _mutations.dm and dna_console.dm.

## Why It's Good For The Game
Mutation shitcode is hurting my future plans for infusions a little.
Also it's a much needed refactor. Drafted 'till I'm sure it works
without issues.

## Changelog

🆑
refactor: Refactored mutation code backend. Report any issue.
/🆑
2025-06-08 13:57:10 +02:00

147 lines
5.5 KiB
Plaintext

/datum/mutation/olfaction
name = "Transcendent Olfaction"
desc = "Your sense of smell is comparable to that of a canine."
quality = POSITIVE
difficulty = 12
text_gain_indication = span_notice("Smells begin to make more sense...")
text_lose_indication = span_notice("Your sense of smell goes back to normal.")
power_path = /datum/action/cooldown/spell/olfaction
instability = POSITIVE_INSTABILITY_MODERATE
synchronizer_coeff = 1
/datum/mutation/olfaction/setup()
. = ..()
var/datum/action/cooldown/spell/olfaction/to_modify = .
if(!istype(to_modify)) // null or invalid
return
to_modify.sensitivity = GET_MUTATION_SYNCHRONIZER(src)
/datum/action/cooldown/spell/olfaction
name = "Remember the Scent"
desc = "Get a scent off of the item you're currently holding to track it. \
With an empty hand, you'll track the scent you've remembered."
button_icon_state = "nose"
cooldown_time = 10 SECONDS
spell_requirements = NONE
/// Weakref to the mob we're tracking
var/datum/weakref/tracking_ref
/// Our nose's sensitivity
var/sensitivity = 1
/datum/action/cooldown/spell/olfaction/is_valid_target(atom/cast_on)
if(!isliving(cast_on))
return FALSE
var/mob/living/living_cast_on = cast_on
if(ishuman(living_cast_on) && !living_cast_on.get_bodypart(BODY_ZONE_HEAD))
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)
. = ..()
// Can we sniff? is there miasma in the air?
var/datum/gas_mixture/air = cast_on.loc.return_air()
var/list/cached_gases = air.gases
if(cached_gases[/datum/gas/miasma])
cast_on.adjust_disgust(sensitivity * 45)
to_chat(cast_on, span_warning("With your overly sensitive nose, \
you get a whiff of stench and feel sick! Try moving to a cleaner area!"))
return
var/atom/sniffed = cast_on.get_active_held_item()
if(sniffed)
pick_up_target(cast_on, sniffed)
else
follow_target(cast_on)
/// Attempt to pick up a new target based on the fingerprints on [sniffed].
/datum/action/cooldown/spell/olfaction/proc/pick_up_target(mob/living/caster, atom/sniffed)
var/mob/living/carbon/old_target = tracking_ref?.resolve()
var/list/possibles = list()
var/list/prints = GET_ATOM_FINGERPRINTS(sniffed)
if(prints)
for(var/mob/living/carbon/to_check as anything in GLOB.carbon_list)
if(prints[md5(to_check.dna?.unique_identity)])
possibles |= to_check
// There are no finger prints on the atom, so nothing to track
if(!length(possibles))
to_chat(caster, span_warning("Despite your best efforts, there are no scents to be found on [sniffed]..."))
return
var/mob/living/carbon/new_target = tgui_input_list(caster, "Scent to remember", "Scent Tracking", sort_names(possibles))
if(QDELETED(src) || QDELETED(caster))
return
if(QDELETED(new_target))
// We don't have a new target OR an old target
if(QDELETED(old_target))
to_chat(caster, span_warning("You decide against remembering any scents. \
Instead, you notice your own nose in your peripheral vision. \
This goes on to remind you of that one time you started breathing manually and couldn't stop. \
What an awful day that was."))
tracking_ref = null
// We don't have a new target, but we have an old target to fall back on
else
to_chat(caster, span_notice("You return to tracking [old_target]. The hunt continues."))
on_the_trail(caster)
return
// We have a new target to track
to_chat(caster, span_notice("You pick up the scent of [new_target]. The hunt begins."))
tracking_ref = WEAKREF(new_target)
on_the_trail(caster)
/// Attempt to follow our current tracking target.
/datum/action/cooldown/spell/olfaction/proc/follow_target(mob/living/caster)
var/mob/living/carbon/current_target = tracking_ref?.resolve()
// Either our weakref failed to resolve (our target's gone),
// or we never had a target in the first place
if(QDELETED(current_target))
to_chat(caster, span_warning("You're not holding anything to smell, \
and you haven't smelled anything you can track. You smell your skin instead; it's kinda salty."))
tracking_ref = null
return
on_the_trail(caster)
/// Actually go through and give the user a hint of the direction our target is.
/datum/action/cooldown/spell/olfaction/proc/on_the_trail(mob/living/caster)
var/mob/living/carbon/current_target = tracking_ref?.resolve()
//Using get_turf to deal with those pesky closets that put your x y z to 0
var/turf/current_target_turf = get_turf(current_target)
var/turf/caster_turf = get_turf(caster)
if(!current_target)
to_chat(caster, span_warning("You're not tracking a scent, but the game thought you were. \
Something's gone wrong! Report this as a bug."))
stack_trace("[type] - on_the_trail was called when no tracking target was set.")
tracking_ref = null
return
if(current_target == caster)
to_chat(caster, span_warning("You smell out the trail to yourself. Yep, it's you."))
return
if(caster_turf.z < current_target_turf.z)
to_chat(caster, span_warning("The trail leads... way up above you? Huh. They must be really, really far away."))
return
else if(caster_turf.z > current_target_turf.z)
to_chat(caster, span_warning("The trail leads... way down below you? Huh. They must be really, really far away."))
return
var/direction_text = span_bold("[dir2text(get_dir(caster_turf, current_target_turf))]")
if(direction_text)
to_chat(caster, span_notice("You consider [current_target]'s scent. The trail leads [direction_text]."))