Adds the "absorbed prey can devour" addon (#18906)

* we gots this

* removes verb implementation

* no clue why that was such a mess

* clean up the rest

* implementation

* cleanup

* .

* hide it otherwise

* quick fix

* missing stackitem

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
Olive
2025-12-30 03:15:26 +01:00
committed by GitHub
co-authored by Kashargul
parent 9304193db3
commit 31c4ccf064
45 changed files with 427 additions and 275 deletions
@@ -181,7 +181,6 @@
H.muffled = FALSE
H.forced_psay = FALSE
addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living, can_leave_dark)), 5 MINUTES, TIMER_DELETE_ME)
else
H.add_modifier(/datum/modifier/dark_respite, 25 MINUTES)
@@ -1364,3 +1364,50 @@ var/eggs = 0
src.visible_message(span_infoplain(span_red("[src] sinks their stinger into [T]!")))
T.bloodstr.add_reagent(REAGENT_ID_CONDENSEDCAPSAICINV,3)
last_special = world.time + (5 SECONDS) // Many little jabs instead of one big one
/mob/living/proc/absorb_devour()
if(!absorbed || !isbelly(loc))
return
if(!isliving(loc.loc))
return
var/mob/living/pred = loc.loc
var/obj/belly/belly = loc
var/list/targets = list()
for(var/mob/living/potentialtarget in range(1, pred))
if(!isliving(potentialtarget)) //Don't eat anything that isn't mob/living. Failsafe.
continue
if(potentialtarget == pred)
continue
if(potentialtarget.devourable)
targets += potentialtarget
if(!(targets.len))
to_chat(src, span_notice("No eligible targets found."))
return
var/mob/living/target = tgui_input_list(src, "Please select a target.", "Victim", targets)
if(!absorbed || !isbelly(loc))
return
if(!isliving(loc.loc))
return
if(!target)
return
if(!isliving(target)) //Safety.
to_chat(src, span_warning("You need to select a living target!"))
return
if (get_dist(src,target) >= 2)
to_chat(src, span_warning("You need to be closer to do that."))
return
target.visible_message(span_vnotice("\The [pred]'s [belly] seems interested in \the [target]."),\
span_vwarning("\The [pred]'s [belly] threatens to [lowertext(belly.vore_verb)] you!"))
to_chat(pred, span_vnotice("Your [belly] tries to [lowertext(belly.vore_verb)] \the [target].")) //people who want this will often be unaware pred players, so I'm making the warning a bit smaller text for them
to_chat(pred, span_vwarning("You look for a chance to [lowertext(belly.vore_verb)] \the [target]."))
var/starting_loc = target.loc
if(do_after(src, 5 SECONDS, target))
if(target.loc != starting_loc)
to_chat(src, span_notice("\The [target] is no longer within reach."))
return
if(target.buckled)
target.buckled.unbuckle_mob()
to_chat(src, span_vwarning("You manage to [lowertext(belly.vore_verb)] \the [target]!"))
to_chat(pred, span_vnotice("Your [belly] manages to [lowertext(belly.vore_verb)] \the [target]."))
to_chat(target, span_vwarning("You are [lowertext(belly.vore_verb)]ed by \The [pred]'s [belly]!"))
return pred.begin_instant_nom(src, target, pred, belly, FALSE)
+1 -1
View File
@@ -127,7 +127,7 @@
//Actual full digest modes
var/tmp/static/list/digest_modes = list(DM_HOLD,DM_DIGEST,DM_ABSORB,DM_DRAIN,DM_SELECT,DM_UNABSORB,DM_HEAL,DM_SHRINK,DM_GROW,DM_SIZE_STEAL,DM_EGG)
//Digest mode addon flags
var/tmp/static/list/mode_flag_list = list("Numbing" = DM_FLAG_NUMBING, "Stripping" = DM_FLAG_STRIPPING, "Leave Remains" = DM_FLAG_LEAVEREMAINS, "Muffles" = DM_FLAG_THICKBELLY, "Affect Worn Items" = DM_FLAG_AFFECTWORN, "Jams Sensors" = DM_FLAG_JAMSENSORS, "Complete Absorb" = DM_FLAG_FORCEPSAY, "Spare Prosthetics" = DM_FLAG_SPARELIMB, "Slow Body Digestion" = DM_FLAG_SLOWBODY, "Muffle Items" = DM_FLAG_MUFFLEITEMS, "TURBO MODE" = DM_FLAG_TURBOMODE)
var/tmp/static/list/mode_flag_list = list("Numbing" = DM_FLAG_NUMBING, "Stripping" = DM_FLAG_STRIPPING, "Leave Remains" = DM_FLAG_LEAVEREMAINS, "Muffles" = DM_FLAG_THICKBELLY, "Affect Worn Items" = DM_FLAG_AFFECTWORN, "Jams Sensors" = DM_FLAG_JAMSENSORS, "Complete Absorb" = DM_FLAG_FORCEPSAY, "Spare Prosthetics" = DM_FLAG_SPARELIMB, "Slow Body Digestion" = DM_FLAG_SLOWBODY, "Muffle Items" = DM_FLAG_MUFFLEITEMS, "TURBO MODE" = DM_FLAG_TURBOMODE, "Absorbed prey can devour" = DM_FLAG_ABSORBEDVORE)
//Item related modes
var/tmp/static/list/item_digest_modes = list(IM_HOLD,IM_DIGEST_FOOD,IM_DIGEST,IM_DIGEST_PARALLEL)
//drain modes
+14 -14
View File
@@ -186,6 +186,20 @@
if(L.absorbed && !issilicon(L))
L.Weaken(5)
//Thickbelly flag
if((mode_flags & DM_FLAG_THICKBELLY) && !L.muffled)
L.muffled = TRUE
//Fix muffled sometimes being sticky.
else if(!(mode_flags & DM_FLAG_THICKBELLY) && L.muffled)
L.muffled = FALSE
//Force psay
if((mode_flags & DM_FLAG_FORCEPSAY) && !L.forced_psay && L.absorbed)
L.forced_psay = TRUE
//Fix forcepsay sometimes being sticky.
else if(!(mode_flags & DM_FLAG_FORCEPSAY) && L.forced_psay)
L.forced_psay = FALSE
//Handle 'human'
if(ishuman(L))
var/mob/living/carbon/human/H = L
@@ -195,20 +209,6 @@
if(H.bloodstr.get_reagent_amount(REAGENT_ID_NUMBENZYME) < 2)
H.bloodstr.add_reagent(REAGENT_ID_NUMBENZYME,4)
//Thickbelly flag
if((mode_flags & DM_FLAG_THICKBELLY) && !H.muffled)
H.muffled = TRUE
//Fix muffled sometimes being sticky.
else if(!(mode_flags & DM_FLAG_THICKBELLY) && H.muffled)
H.muffled = FALSE
//Force psay
if((mode_flags & DM_FLAG_FORCEPSAY) && !H.forced_psay && H.absorbed)
H.forced_psay = TRUE
//Fix forcepsay sometimes being sticky.
else if(!(mode_flags & DM_FLAG_FORCEPSAY) && H.forced_psay)
H.forced_psay = FALSE
//Worn items flag
if((mode_flags & DM_FLAG_AFFECTWORN) && H.contaminate_pref)
for(var/slot in slots)
@@ -28,70 +28,86 @@
/datum/vore_look/proc/get_inside_data(mob/owner)
var/atom/hostloc = owner.loc
//Allow VorePanel to show pred belly details even while indirectly inside
// Allow VorePanel to show pred belly details even while indirectly inside
if(isliving(owner))
var/mob/living/H = owner
hostloc = H.surrounding_belly()
//End of indirect vorefx additions
if(!isbelly(hostloc))
return list()
var/list/inside = list()
if(isbelly(hostloc))
var/obj/belly/inside_belly = hostloc
var/mob/living/pred = inside_belly.owner
var/obj/belly/inside_belly = hostloc
var/mob/living/pred = inside_belly.owner
var/inside_desc = "No description."
if(owner.absorbed && inside_belly.absorbed_desc)
inside_desc = inside_belly.absorbed_desc
else if(inside_belly.desc)
inside_desc = inside_belly.desc
var/inside_desc = "No description."
if(owner.absorbed && inside_belly.absorbed_desc)
inside_desc = inside_belly.absorbed_desc
else if(inside_belly.desc)
inside_desc = inside_belly.desc
if(inside_desc != "No description.")
inside_desc = inside_belly.belly_format_string(inside_desc, owner, use_first_only = TRUE)
if(inside_desc != "No description.")
inside_desc = inside_belly.belly_format_string(inside_desc, owner, use_first_only = TRUE)
inside = list(
"absorbed" = owner.absorbed,
"belly_name" = inside_belly.name,
"belly_mode" = inside_belly.digest_mode,
"desc" = inside_desc,
"pred" = pred,
"ref" = "\ref[inside_belly]",
"liq_lvl" = inside_belly.reagents.total_volume,
"liq_reagent_type" = inside_belly.reagent_chosen,
"liuq_name" = inside_belly.reagent_name,
inside = list(
"absorbed" = owner.absorbed,
"belly_name" = inside_belly.name,
"belly_mode" = inside_belly.digest_mode,
"desc" = inside_desc,
"pred" = pred,
"ref" = "\ref[inside_belly]",
"liq_lvl" = inside_belly.reagents.total_volume,
"liq_reagent_type" = inside_belly.reagent_chosen,
"liuq_name" = inside_belly.reagent_name,
)
var/list/inside_contents = list()
for(var/atom/movable/O in inside_belly)
if(O == owner)
continue
var/our_type
if(isliving(O))
our_type = "Living"
else if(isitem(O))
our_type = "Item"
var/list/info = list(
"name" = "[O]",
"absorbed" = FALSE,
"stat" = 0,
"ref" = "\ref[O]",
"outside" = FALSE,
"our_type" = our_type
)
var/list/inside_contents = list()
for(var/atom/movable/O in inside_belly)
if(O == owner)
continue
var/our_type
if(isliving(O))
our_type = "Living"
else if(isitem(O))
our_type = "Item"
var/list/info = list(
"name" = "[O]",
"absorbed" = FALSE,
"stat" = 0,
"ref" = "\ref[O]",
"outside" = FALSE,
"our_type" = our_type
)
if(show_pictures) //disables icon mode
if(inside_belly.contents.len <= max_icon_content)
icon_overflow = FALSE
info["icon"] = cached_nom_icon(O)
else
icon_overflow = TRUE
if(isliving(O))
var/mob/living/M = O
info["stat"] = M.stat
if(M.absorbed)
info["absorbed"] = TRUE
UNTYPED_LIST_ADD(inside_contents, info)
inside["contents"] = inside_contents
if(show_pictures) //disables icon mode
if(inside_belly.contents.len <= max_icon_content)
icon_overflow = FALSE
info["icon"] = cached_nom_icon(O)
else
icon_overflow = TRUE
if(isliving(O))
var/mob/living/M = O
info["stat"] = M.stat
if(M.absorbed)
info["absorbed"] = TRUE
UNTYPED_LIST_ADD(inside_contents, info)
inside["contents"] = inside_contents
return inside
/datum/vore_look/proc/get_prey_abilities(mob/owner)
var/atom/hostloc = owner.loc
// Allow VorePanel to show pred belly details even while indirectly inside
if(isliving(owner))
var/mob/living/H = owner
hostloc = H.surrounding_belly()
if(!isbelly(hostloc))
return list()
var/obj/belly/inside_belly = hostloc
var/list/abilities = list()
if(inside_belly.mode_flags & DM_FLAG_ABSORBEDVORE)
UNTYPED_LIST_ADD(abilities, list("name" = "devour_as_absorbed", "available" = owner.absorbed))
return abilities
/datum/vore_look/proc/get_host_mobtype(mob/owner)
var/list/host_mobtype = list("is_cyborg" = FALSE, "is_vore_simple_mob" = FALSE)
if(isrobot(owner))
+23
View File
@@ -133,6 +133,7 @@
data["host_mobtype"] = null
data["show_pictures"] = null
data["icon_overflow"] = null
data["prey_abilities"] = null
data["our_bellies"] = null
data["selected"] = null
data["soulcatcher"] = null
@@ -159,6 +160,7 @@
// Content Data
data["show_pictures"] = show_pictures
data["icon_overflow"] = icon_overflow
data["prey_abilities"] = get_prey_abilities(host)
if(SOULCATCHER_TAB)
// Soulcatcher and abilities
@@ -237,6 +239,11 @@
host.persistend_edit_mode = !host.persistend_edit_mode
return TRUE
if("prey_ability")
if(!isliving(ui.user))
return FALSE
return perform_prey_ability(ui.user, params)
// Host is inside someone else, and is trying to interact with something else inside that person.
if("pick_from_inside")
return pick_from_inside(ui.user, params)
@@ -1361,6 +1368,22 @@
to_chat(user, span_vwarning("\The [target] is currently [condition], they will not be able to [condition_consequences]."))
return FALSE
/datum/vore_look/proc/perform_prey_ability(mob/living/user, params)
var/obj/belly/OB = locate(params["belly"])
if(!(user in OB))
return TRUE // Aren't here anymore, need to update menu
var/ability = params["ability"]
if(!(ability in list("devour_as_absorbed")))
return FALSE
switch(ability)
if("devour_as_absorbed")
user.absorb_devour()
return TRUE
/datum/vore_look/proc/sanitize_fixed_list(var/list/messages, type, delim = "\n\n", limit)
if(!limit)
CRASH("[type] set message called without limit!")