[MDB Ignore] Refactors pills, patches, and generalizes stomach contents, nothing to see here. (#89549)

## About The Pull Request

Currently patches are a subtype of pills, and while they have the
``dissolveable`` var set to FALSE, barely anything checks it (because
people don't expect patches to be pills in disguise) so we end up
patches being dissolveable and implantable, which is far from ideal.
Both have been moved into an ``/obj/item/reagent_containers/applicator``
class, which handles their common logic and helps handling cases where
either one fits. As for gameplay changes:
* Pills no longer dissolve instantly, instead adding their contents to
your stomach after 3 seconds (by default). You can increase the timer by
dropping sugar onto them to thicken their coating, 1s per 1u applied, up
to a full minute. Coating can also be dissolved with water, similarly
-1s per 1u applied. Pills with no coating will work like before.
* Patches now only take half as long to apply (1.5s), but also slowly
trickle in their reagents instead of instantly applying all of them.
This is done via embedding so you could theoretically (if you get lucky)
stick a ranged patch at someone, although they are rather quick to rip
off. The implementation and idea itself are separate, but the idea for
having a visual display has been taken from
https://github.com/Monkestation/Monkestation2.0/pull/2558.

![dreamseeker_Ywd4jQcy3t](https://github.com/user-attachments/assets/7ce0e549-9ecd-4a8a-98ea-12e00754bdd9)
* In order to support the new pill mechanics, stomachs have received
contents. Pills and items that you accidentally swallow now go into your
stomach instead of your chest cavity, and may damage it if they're
sharp, requiring having them surgically cut out (cut the stomach open
with a scalpel, then cauterize it to mend the incision). Or maybe you
can get a bacchus's blessing, or a geneticist hulk to gut punch you,
that may also work. Alien devour ability also uses this system now. If
you get a critical slashing wound on your chest contents of your cut
apart stomach (if a surgeon forgot to mend it, or if you ate too much
glass shard for breakfast) may fall out. However, spacemen with the
strong stomach trait can eat as much glass cereal as they want.

Pill duration can also be chosen in ChemMaster when you have a pill
selected, 0 to 30 seconds.

![image](https://github.com/user-attachments/assets/1f40210e-74dd-49c2-8093-432a747ac8dd)

## Why It's Good For The Game

Patches and pills are extremely similar in their implemenation, former
being a worse version of sprays and pills, with only change being that
pills cannot be applied through helmets while patches and sprays ignore
both. This change makes them useful for separate cases, and allows
reenactment of some classic... movie, scenes, with the pill change. As
for stomach contents, this was probably the sanest way of implementing
pill handling, and everything else (item swallowing and cutting stomachs
open to remove a cyanide pill someone ate before it dissolves) kind of
snowballed from there. I pray to whatever gods that are out there that
this won't have some extremely absurd and cursed interactions (it
probably will).

## Changelog
🆑
add: Instead of dissolving instantly, pills now activate after 4
seconds. This timer can be increased by using a dropper filled with
sugar on them, 1s added per 1u dropped.
add: Patches now stick to you and slowly bleed their reagents, instead
of being strictly inferior to both pills and sprays.
add: Items that you accidentally swallow now go into your stomach
contents.
refactor: Patches are no longer considered pills by the game
refactor: All stomachs now have contents, instead of it being exclusive
to aliens. You can cut open a stomach to empty it with a scalpel, and
mend an existing incision with a cautery.
/🆑
This commit is contained in:
SmArtKar
2025-03-13 17:31:37 +01:00
committed by GitHub
parent 0af4b1c636
commit eb2796831b
85 changed files with 2227 additions and 1666 deletions

View File

@@ -32,8 +32,13 @@
/// Multiplier for hunger rate
var/hunger_modifier = 1
var/operated = FALSE //whether the stomach's been repaired with surgery and can be fixed again or not
/// Whether the stomach's been repaired with surgery and can be fixed again or not
var/operated = FALSE
/// List of all atoms within the stomach
var/list/atom/movable/stomach_contents = list()
/// Have we been cut open with a scalpel? If so, how much damage from it we still have from it and can be recovered with a cauterizing tool.
/// All healing goes towards recovering this.
var/cut_open_damage = 0
/obj/item/organ/stomach/Initialize(mapload)
. = ..()
@@ -43,6 +48,10 @@
else
reagents.flags |= REAGENT_HOLDER_ALIVE
/obj/item/organ/stomach/Destroy()
QDEL_LIST(stomach_contents)
return ..()
/obj/item/organ/stomach/on_life(seconds_per_tick, times_fired)
. = ..()
@@ -199,6 +208,133 @@
/obj/item/organ/stomach/proc/after_eat(atom/edible)
return
/obj/item/organ/stomach/proc/consume_thing(atom/movable/thing)
RegisterSignal(thing, COMSIG_MOVABLE_MOVED, PROC_REF(content_moved))
RegisterSignal(thing, COMSIG_QDELETING, PROC_REF(content_deleted))
stomach_contents += thing
thing.forceMove(owner || src) // We assert that if we have no owner, we will not be nullspaced
return TRUE
/obj/item/organ/stomach/proc/content_deleted(atom/movable/source)
SIGNAL_HANDLER
stomach_contents -= source
/obj/item/organ/stomach/proc/content_moved(atom/movable/source)
SIGNAL_HANDLER
if(source.loc == src || source.loc == owner) // not in us? out da list then
return
stomach_contents -= source
UnregisterSignal(source, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
/obj/item/organ/stomach/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(loc == null && owner)
for(var/atom/movable/thing as anything in stomach_contents)
thing.forceMove(owner)
else if(loc != null)
for(var/atom/movable/thing as anything in stomach_contents)
thing.forceMove(src)
/// Empties stomach contents on our current turf
/obj/item/organ/stomach/proc/empty_contents(chance = 100, damaging = FALSE, min_amount = 0)
var/emptied = 0
var/atom/movable/drop_as = owner || src
var/drop_loc = drop_as.drop_location()
for (var/atom/movable/nugget as anything in stomach_contents)
var/total_chance = chance
// If min_amount is set, make sure that we vomit at least some of our contents
if (min_amount)
total_chance += 100 / (length(stomach_contents) + 1 - min_amount)
if (!prob(total_chance))
continue
nugget.forceMove(drop_loc)
emptied += 1
min_amount -= 1
if (!damaging || QDELETED(owner))
continue
var/damage = 5
if (isitem(nugget))
var/obj/item/as_item = nugget
damage = as_item.w_class * 2
else if (isliving(nugget))
var/mob/living/as_living = nugget
damage = as_living.mob_size * 5
owner.apply_damage(damage, BRUTE, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND, wound_clothing = FALSE)
return emptied
/obj/item/organ/stomach/on_life(seconds_per_tick, times_fired)
. = ..()
if (!owner || SSmobs.times_fired % 3 != 0)
return
if (!length(stomach_contents))
return
var/obj/item/bodypart/chest/chest = owner.get_bodypart(zone)
var/datum/wound/slash/flesh/slash = chest.get_wound_type(/datum/wound/slash/flesh)
// A chance to spill out all the contents
if (cut_open_damage && slash?.severity >= WOUND_SEVERITY_CRITICAL)
if (SPT_PROB(chest.get_damage(), seconds_per_tick * 3))
var/emptied = empty_contents()
if (emptied > 0)
owner.apply_damage(emptied * 5, BRUTE, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND, wound_clothing = FALSE)
playsound(get_turf(src), 'sound/effects/splat.ogg', 50)
owner.visible_message(span_danger("Contents of [owner]'s intestines spill out from a huge cut in [owner.p_their()] [chest]!"),
span_userdanger("Contents of your intestines spill out from a huge cut in your [chest]!"))
return
// Digest the stuff in our stomach, just a bit
for (var/atom/movable/thing as anything in stomach_contents)
if (SEND_SIGNAL(thing, COMSIG_ATOM_STOMACH_DIGESTED, src, owner, seconds_per_tick) & COMPONENT_CANCEL_DIGESTION)
continue
var/acid_pwr = stomach_acid_power(thing, seconds_per_tick)
if (acid_pwr)
thing.acid_act(acid_pwr, 10)
// If you have strong stomach you can eat glass, literally
if (!isitem(thing) || HAS_TRAIT(owner, TRAIT_STRONG_STOMACH))
continue
var/obj/item/as_item = thing
// If your stomach is cut open, it will hurt like hell
if (cut_open_damage)
if (chest && !chest.cavity_item && as_item.w_class <= WEIGHT_CLASS_NORMAL)
// Oopsie!
chest.cavity_item = as_item
stomach_contents -= as_item
continue
owner.apply_damage(as_item.w_class * (as_item.sharpness ? 2 : 1), BRUTE, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND,
sharpness = as_item.sharpness, attacking_item = as_item, wound_clothing = FALSE)
if (!as_item.sharpness)
continue
var/was_failing = (organ_flags & ORGAN_FAILING)
apply_organ_damage(as_item.w_class)
// Damage caused organ failure
if (was_failing || !(organ_flags & ORGAN_FAILING))
continue
cut_open_damage = maxHealth * 0.5
if (HAS_TRAIT(owner, TRAIT_ANALGESIA))
continue
owner.visible_message(span_warning("[owner] doubles over in pain!"), span_userdanger("You feel a sharp, searing sensation in your stomach!"))
owner.Paralyze(1 SECONDS)
owner.adjust_eye_blur(5 SECONDS)
/// Acid power of whatever we're digesting
/obj/item/organ/stomach/proc/stomach_acid_power(atom/movable/nomnom, seconds_per_tick)
if (isliving(nomnom)) // NO VORE ALLOWED
return 0
// Yeah maybe don't, if something edible ended up here it should either handle itself or not be digested
if (IsEdible(nomnom))
return 0
if (HAS_TRAIT(owner, TRAIT_STRONG_STOMACH))
return 10
return 0
/obj/item/organ/stomach/proc/handle_disgust(mob/living/carbon/human/disgusted, seconds_per_tick, times_fired)
var/old_disgust = disgusted.old_disgust
var/disgust = disgusted.disgust
@@ -249,6 +385,8 @@
/obj/item/organ/stomach/on_mob_insert(mob/living/carbon/receiver, special, movement_flags)
. = ..()
receiver.hud_used?.hunger?.update_appearance()
RegisterSignal(receiver, COMSIG_CARBON_VOMITED, PROC_REF(on_vomit))
RegisterSignal(receiver, COMSIG_HUMAN_GOT_PUNCHED, PROC_REF(on_punched))
/obj/item/organ/stomach/on_mob_remove(mob/living/carbon/stomach_owner, special, movement_flags)
if(ishuman(stomach_owner))
@@ -256,8 +394,76 @@
human_owner.clear_alert(ALERT_DISGUST)
human_owner.clear_mood_event("disgust")
stomach_owner.hud_used?.hunger?.update_appearance()
UnregisterSignal(stomach_owner, list(COMSIG_CARBON_VOMITED, COMSIG_HUMAN_GOT_PUNCHED))
return ..()
/// If damage is high enough, we may end up vomiting out whatever we had stored
/obj/item/organ/stomach/proc/on_punched(datum/source, mob/living/carbon/human/attacker, damage, attack_type, obj/item/bodypart/affecting, final_armor_block, kicking)
SIGNAL_HANDLER
if (!length(stomach_contents) || damage < 9 || final_armor_block || kicking)
return
if (owner.vomit(MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE))
// Since we vomited with a force flag, we should've vomited out at least one item
owner.visible_message(span_danger("[owner] doubles over from [attacker]'s punch, vomiting out the contents of [owner.p_their()] stomach!"))
/// 60% chance to spew out each item when vomiting
/obj/item/organ/stomach/proc/on_vomit(mob/living/carbon/vomiter, distance, force)
SIGNAL_HANDLER
// If we're forced to vomit, try to spew out at least one item
empty_contents(chance = 60, damaging = TRUE, min_amount = (force ? 1 : 0))
/obj/item/organ/stomach/tool_act(mob/living/user, obj/item/tool, list/modifiers)
if (tool.tool_behaviour == TOOL_SCALPEL)
if (cut_open_damage > 0)
balloon_alert(user, "already cut open!")
return ITEM_INTERACT_FAILURE
balloon_alert(user, "cutting open...")
playsound(user, 'sound/items/handling/surgery/scalpel1.ogg', 75)
if (!do_after(user, 3 SECONDS, src))
balloon_alert(user, "interrupted!")
apply_organ_damage(tool.force)
return ITEM_INTERACT_FAILURE
playsound(user, 'sound/items/handling/surgery/scalpel2.ogg', 75)
var/emptied = empty_contents()
if (emptied > 0)
playsound(get_turf(src), 'sound/effects/splat.ogg', 50)
user.visible_message(span_warning("[user] cuts [src] open[emptied ? "!" : ", but it's empty."]"), span_notice("You cut [src] open[emptied ? "." : ", but there's nothing inside."]"))
cut_open_damage += apply_organ_damage(maxHealth * 0.5)
return ITEM_INTERACT_SUCCESS
if (tool.tool_behaviour != TOOL_CAUTERY)
return ..()
if (cut_open_damage <= 0)
balloon_alert(user, "fully intact!")
return ITEM_INTERACT_FAILURE
playsound(user, 'sound/items/handling/surgery/cautery1.ogg', 75)
balloon_alert(user, "mending the incision...")
if (!do_after(user, 3 SECONDS, src))
balloon_alert(user, "interrupted!")
apply_organ_damage(tool.force)
return ITEM_INTERACT_FAILURE
playsound(user, 'sound/items/handling/surgery/cautery2.ogg', 75)
balloon_alert(user, "incision mended")
apply_organ_damage(-cut_open_damage)
cut_open_damage = 0 // Just in case
return ITEM_INTERACT_SUCCESS
/obj/item/organ/stomach/apply_organ_damage(damage_amount, maximum, required_organ_flag)
. = ..()
// So after a while, or a bunch of stomach meds, even a cut stomach can recover
if (. < 0)
cut_open_damage = max(0, cut_open_damage + .)
/obj/item/organ/stomach/examine(mob/user)
. = ..()
if (cut_open_damage)
. += span_danger("It has a sizeable cut in it, exposing its insides!")
/obj/item/organ/stomach/bone
name = "mass of bones"
desc = "You have no idea what this strange ball of bones does."
@@ -301,6 +507,13 @@
emp_vulnerability = 40
metabolism_efficiency = 0.07
/obj/item/organ/stomach/cybernetic/tier2/stomach_acid_power(atom/movable/nomnom)
if (isliving(nomnom))
return 0
if (IsEdible(nomnom))
return 0
return 20
/obj/item/organ/stomach/cybernetic/tier3
name = "upgraded cybernetic stomach"
desc = "An upgraded version of the cybernetic stomach, designed to improve further upon organic stomachs. Handles disgusting food very well."
@@ -310,6 +523,13 @@
emp_vulnerability = 20
metabolism_efficiency = 0.1
/obj/item/organ/stomach/cybernetic/tier2/stomach_acid_power(atom/movable/nomnom)
if (isliving(nomnom))
return 0
if (IsEdible(nomnom))
return 0
return 35
/obj/item/organ/stomach/cybernetic/surplus
name = "surplus prosthetic stomach"
desc = "A mechanical plastic oval that utilizes sulfuric acid instead of stomach acid. \