From 97e8bcf6e1684ef257609028dcfacd75b004bfcf Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:51:59 -0700 Subject: [PATCH] [MIRROR] Intent based struggles (#12242) Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/__defines/belly_modes_vr.dm | 5 + .../species/shadekin/powers/phase_shift.dm | 2 +- code/game/atoms_movable.dm | 4 +- .../objects/items/weapons/melee/energy.dm | 1 - .../mob/living/simple_mob/simple_mob_vr.dm | 2 +- .../subtypes/animal/alien animals/teppi.dm | 2 +- .../simple_mob/subtypes/glamour/fluffball.dm | 2 +- .../simple_mob/subtypes/vore/bigdragon.dm | 2 +- .../subtypes/vore/demon/demon_abilities.dm | 4 +- .../simple_mob/subtypes/vore/gryphon.dm | 2 +- .../living/simple_mob/subtypes/vore/lamia.dm | 2 +- .../simple_mob/subtypes/vore/macrophage.dm | 2 +- .../living/simple_mob/subtypes/vore/meowl.dm | 2 +- .../subtypes/vore/shadekin/shadekin.dm | 2 +- .../simple_mob/subtypes/vore/succubi.dm | 2 +- code/modules/vore/eating/belly_import.dm | 6 +- .../eating/{belly_obj_vr.dm => belly_obj.dm} | 2 +- code/modules/vore/eating/belly_obj_resist.dm | 255 ++++++++++-------- code/modules/vore/eating/belly_obj_vfx.dm | 202 +++++++------- .../vorepanel_set_attribute.dm | 20 +- .../panel_databackend/vorepanel_vore_data.dm | 25 +- code/modules/vore/eating/vorepanel_vr.dm | 11 +- tgui/bun.lock | 18 +- tgui/package.json | 4 +- tgui/packages/tgui-panel/package.json | 2 +- tgui/packages/tgui-say/package.json | 2 +- tgui/packages/tgui/components/NanoMap.tsx | 9 +- .../GhostSpawn/GhostJoin/SpawnElement.tsx | 3 +- .../bay_prefs/helper_components.tsx | 4 +- .../WikiCommon/WikiColorIcon.tsx | 1 + .../tgui/interfaces/ShadekinConfig.tsx | 24 +- .../tgui/interfaces/TextInputModal.tsx | 11 +- .../VorePanelElements/MultiOverlayImage.tsx | 182 +++++++------ .../VorePanelEditCheckboxes.tsx | 15 +- .../VorePanelElements/VorePanelEditColor.tsx | 18 +- .../VorePanelEditDropdown.tsx | 36 +-- .../VorePanelElements/VorePanelEditNumber.tsx | 21 +- .../VorePanelElements/VorePanelEditSwitch.tsx | 4 +- .../VorePanelElements/VorePanelEditText.tsx | 21 +- .../VorePanelElements/VorePanelTextArea.tsx | 2 +- .../VorePanelElements/VorePanelTooltip.tsx | 31 +++ .../VorePaneldEditTextTabs.tsx | 6 +- .../ContentTab/VoreContentIntent.tsx | 22 ++ .../VoreContentsPanel.tsx | 6 +- .../VoreContentsPreyPanel.tsx | 7 +- .../VoreSelectedBellyInteractions.tsx | 46 +++- .../tgui/interfaces/VorePanel/constants.ts | 49 ++++ .../tgui/interfaces/VorePanel/index.tsx | 2 + .../tgui/interfaces/VorePanel/types.ts | 15 +- .../VorePanelExportBellyString.tsx | 9 +- .../tgui/interfaces/VorePanelExport/types.ts | 2 +- tgui/packages/tgui/package.json | 2 +- .../tgui/styles/interfaces/VorePanel.scss | 41 ++- vorestation.dme | 2 +- 54 files changed, 721 insertions(+), 453 deletions(-) rename code/modules/vore/eating/{belly_obj_vr.dm => belly_obj.dm} (99%) create mode 100644 tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip.tsx create mode 100644 tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/ContentTab/VoreContentIntent.tsx diff --git a/code/__defines/belly_modes_vr.dm b/code/__defines/belly_modes_vr.dm index 52c5307e26..e8d6474eb8 100644 --- a/code/__defines/belly_modes_vr.dm +++ b/code/__defines/belly_modes_vr.dm @@ -95,3 +95,8 @@ //Message display flags #define MS_FLAG_STRUGGLE_OUTSIDE 0x1 #define MS_FLAG_STRUGGLE_ABSORBED_OUTSIDE 0x2 + +//Escapable resist options +#define B_ESCAPABLE_NONE 0 +#define B_ESCAPABLE_DEFAULT 1 +#define B_ESCAPABLE_INTENT 2 diff --git a/code/datums/components/species/shadekin/powers/phase_shift.dm b/code/datums/components/species/shadekin/powers/phase_shift.dm index 2ce2dadcf2..25c35012d5 100644 --- a/code/datums/components/species/shadekin/powers/phase_shift.dm +++ b/code/datums/components/species/shadekin/powers/phase_shift.dm @@ -248,7 +248,7 @@ name = get_visible_name() for(var/obj/belly/B as anything in vore_organs) - B.escapable = FALSE + B.escapable = B_ESCAPABLE_NONE var/obj/effect/temp_visual/shadekin/phase_out/phaseanim = new SK.phase_out_anim(src.loc) phaseanim.pixel_y = (src.size_multiplier - 1) * 16 // Pixel shift for the animation placement diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 4e36a8164c..89a415ea5f 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -290,8 +290,8 @@ if(!A) CRASH("Bump was called with no argument.") . = ..() - if(throwing) - throw_impact(A, throwing) + if(!QDELETED(throwing)) + throwing.finalize(hit = TRUE, t_target = A) if(QDELETED(A)) return diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 2d274871c9..7996b709ae 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -413,7 +413,6 @@ active_armourpen = 25 projectile_parry_chance = 40 colorable = TRUE - item_flags = DROPDEL | NOSTRIP hitcost = 75 diff --git a/code/modules/mob/living/simple_mob/simple_mob_vr.dm b/code/modules/mob/living/simple_mob/simple_mob_vr.dm index 988b407988..d865973697 100644 --- a/code/modules/mob/living/simple_mob/simple_mob_vr.dm +++ b/code/modules/mob/living/simple_mob/simple_mob_vr.dm @@ -252,7 +252,7 @@ B.contaminates = vore_default_contaminates B.contamination_flavor = vore_default_contamination_flavor B.contamination_color = vore_default_contamination_color - B.escapable = vore_escape_chance > 0 + B.escapable = vore_escape_chance > 0 ? B_ESCAPABLE_DEFAULT : B_ESCAPABLE_NONE B.escapechance = vore_escape_chance B.escapechance_absorbed = vore_escape_chance_absorbed B.digestchance = vore_digest_chance diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm index d2ef832f0a..095c4ae76b 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/teppi.dm @@ -287,7 +287,7 @@ GLOBAL_VAR_INIT(teppi_count, 0) // How mant teppi DO we have? p.digest_brute = 0 p.digest_burn = 0 p.absorbchance = 0 - p.escapable = TRUE + p.escapable = B_ESCAPABLE_DEFAULT p.escapechance = 40 p.digest_mode = DM_HEAL p.name = "propeutpericulum" //I'm no latin professor I just know that some organs and things are based on latin words diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm index b44276c985..277195131c 100644 --- a/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm +++ b/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm @@ -64,7 +64,7 @@ fluff.desc = "You find yourself sinking deeper and deeper into the fluff around you, steadily it wraps around your entire body, binding you up. It seems to grow tighter and tigher forever, although never to the point of discomfort. Before long, the tightness goes beyond a physical sensation, it starts to feel like it buries into you, becoming part of you. It is becoming hard to discern yourself from the fluff, you feel floaty, wavy and soft yourself. Eventually, you can't feel yourself at all, there's nothing but fur. Every movement you make feels pointless, simply causing the slightest rustling of fluff as though the hair was moving on its own." fluff.digest_mode = DM_HOLD // like, shes got you already, doesn't need to get you more fluff.mode_flags = DM_FLAG_FORCEPSAY - fluff.escapable = TRUE // good luck + fluff.escapable = B_ESCAPABLE_DEFAULT // good luck fluff.escapechance = 40 // high chance of STARTING a successful escape attempt fluff.escapechance_absorbed = 5 // m i n e fluff.vore_verb = "soak" diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm index 700bb2596f..36852e89d0 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm @@ -570,7 +570,7 @@ I think I covered everything. autotransferchance = 50 autotransferwait = 150 autotransfer_enabled = TRUE - escapable = TRUE + escapable = B_ESCAPABLE_DEFAULT escapechance = 100 escapetime = 15 fancy_vore = TRUE diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm index e1941db492..0fb57f6d9a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm @@ -93,7 +93,7 @@ health = maxHealth //Fullheal for(var/obj/belly/B as anything in vore_organs) - B.escapable = FALSE + B.escapable = B_ESCAPABLE_NONE cut_overlays() flick("phaseout",src) @@ -148,7 +148,7 @@ name = "Something" for(var/obj/belly/B as anything in vore_organs) - B.escapable = FALSE + B.escapable = B_ESCAPABLE_NONE cut_overlays() flick("phaseout",src) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/gryphon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/gryphon.dm index 9d3ee954d5..5038f2846f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/gryphon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/gryphon.dm @@ -212,7 +212,7 @@ /obj/belly/gryphon/beak name = "Beak" mode_flags = DM_FLAG_THICKBELLY - escapable = TRUE + escapable = B_ESCAPABLE_DEFAULT escapechance = 5 desc = "Noticing you alone, the gryphon lunges forwards and wraps its beak around your head, forcing you to stare into the dark abyss at the back of the gryphon's throat. It quickly tries to swallow you down, beak closing around your torso and head tilting back, letting gravity help make you disappear before anyone sees." struggle_messages_inside = list( diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/lamia.dm b/code/modules/mob/living/simple_mob/subtypes/vore/lamia.dm index 7ecdfe1d11..d81929947d 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/lamia.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/lamia.dm @@ -96,7 +96,7 @@ tail.contaminates = vore_default_contaminates tail.contamination_flavor = vore_default_contamination_flavor tail.contamination_color = vore_default_contamination_color - tail.escapable = TRUE // needed for transferchance + tail.escapable = B_ESCAPABLE_DEFAULT // needed for transferchance tail.escapechance = 0 // No directly escaping a tail, gotta squirm back out. tail.digestchance = vore_tail_digest_chance tail.absorbchance = vore_tail_absorb_chance diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/macrophage.dm b/code/modules/mob/living/simple_mob/subtypes/vore/macrophage.dm index c19a9a8c10..6118a77c3a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/macrophage.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/macrophage.dm @@ -148,7 +148,7 @@ fancy_vore = TRUE contamination_color = "green" vore_verb = "absorb" - escapable = TRUE + escapable = B_ESCAPABLE_DEFAULT escapechance = 20 desc = "In an attempt to get away from the giant virus, it's oversized envelope proteins dragged you right past it's matrix, encapsulating you deep inside it's capsid... The strange walls kneading and keeping you tight along within it's nucleoprotein." belly_fullscreen = "VBO_gematically_angular" diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm b/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm index dc01cc3c1e..b0d3abece8 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm @@ -62,7 +62,7 @@ chub.desc = "Your body quickly begins to feel very... different? In fact, you can't really feel your body much at all any more, but you certainly still feel something. The pressure of the gut that was practically crushing you before is relieved, but somehow still present as though you were now on the other side of the interaction. Your being feels much more spread out and practically intertwined with the world around, that world being the meowl itself. The strange cat-owl's purring feels like it's reverberating throughout your entire form, whatever that might be. Every time the critter shakes to ruffle its feathers, you feel yourself shake with it. Even the creatures emotions feel tangible to you, as though you share themselves, and mostly they are ones of fullness and content." chub.digest_mode = DM_HOLD // like, its got you already, doesn't need to get you more chub.mode_flags = DM_FLAG_FORCEPSAY - chub.escapable = TRUE // good luck + chub.escapable = B_ESCAPABLE_DEFAULT // good luck chub.escapechance = 40 // high chance of STARTING a successful escape attempt chub.escapechance_absorbed = 5 // m i n e chub.vore_verb = "soak" diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm index 44f8de9db3..b47105375a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm @@ -142,7 +142,7 @@ B.name = vore_stomach_name ? vore_stomach_name : "stomach" B.desc = vore_stomach_flavor ? vore_stomach_flavor : "Your surroundings are warm, soft, and slimy. Makes sense, considering you're inside \the [name]." B.digest_mode = vore_default_mode - B.escapable = vore_escape_chance > 0 + B.escapable = vore_escape_chance > 0 ? B_ESCAPABLE_DEFAULT : B_ESCAPABLE_NONE B.escapechance = vore_escape_chance B.digestchance = vore_digest_chance B.absorbchance = vore_absorb_chance diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/succubi.dm b/code/modules/mob/living/simple_mob/subtypes/vore/succubi.dm index bbf3119566..451e9c5e45 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/succubi.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/succubi.dm @@ -96,7 +96,7 @@ GLOBAL_LIST_INIT(succubus_safewords, list( curves.desc = "Your entire being is cast adrift, no longer tight as it was in the succubus's gut but still inexorably bound, a sensation of warmth surrounding your entire being - it's pleasantly comfortable, relaxing even, as though lulling you, tempting you into simply allowing yourself to drift off. It's difficult to focus on yourself at all, any sense of your own position having abandoned you - instead, you can simply feel an odd, gentle sensation of being occasionally rubbed, stroked, squeezed, your captor eager to enjoy her prize. Even trying to move seems to elicit a satisfied chuckle, almost as though she knows that, at least on some level, some part of you wanted to give yourself to her - and she seemingly has little intention of giving you back." curves.digest_mode = DM_HOLD // like, shes got you already, doesn't need to get you more curves.mode_flags = DM_FLAG_FORCEPSAY - curves.escapable = TRUE // good luck + curves.escapable = B_ESCAPABLE_DEFAULT // good luck curves.escapechance = 40 // high chance of STARTING a successful escape attempt curves.escapechance_absorbed = 5 // m i n e curves.vore_verb = "soak" diff --git a/code/modules/vore/eating/belly_import.dm b/code/modules/vore/eating/belly_import.dm index 82dc7dbd9a..deb6f94dc7 100644 --- a/code/modules/vore/eating/belly_import.dm +++ b/code/modules/vore/eating/belly_import.dm @@ -844,9 +844,11 @@ if(isnum(belly_data["escapable"])) var/new_escapable = belly_data["escapable"] if(new_escapable == 0) - new_belly.escapable = FALSE + new_belly.escapable = B_ESCAPABLE_NONE if(new_escapable == 1) - new_belly.escapable = TRUE + new_belly.escapable = B_ESCAPABLE_DEFAULT + if(new_escapable == 2) + new_belly.escapable = B_ESCAPABLE_INTENT if(isnum(belly_data["escapechance"])) var/new_escapechance = belly_data["escapechance"] diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj.dm similarity index 99% rename from code/modules/vore/eating/belly_obj_vr.dm rename to code/modules/vore/eating/belly_obj.dm index 626e89230c..6325b408e0 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj.dm @@ -30,7 +30,7 @@ var/digest_tox = 0 // Toxins damage per tick in digestion mode var/digest_clone = 0 // Clone damage per tick in digestion mode var/immutable = FALSE // Prevents this belly from being deleted - var/escapable = FALSE // Belly can be resisted out of at any time + var/escapable = B_ESCAPABLE_NONE // Belly can be resisted out of at any time var/escapetime = 10 SECONDS // Deciseconds, how long to escape this belly var/selectchance = 0 // % Chance of stomach switching to selective mode if prey struggles var/digestchance = 0 // % Chance of stomach beginning to digest if prey struggles diff --git a/code/modules/vore/eating/belly_obj_resist.dm b/code/modules/vore/eating/belly_obj_resist.dm index 1a60075c43..7e7c19f05e 100644 --- a/code/modules/vore/eating/belly_obj_resist.dm +++ b/code/modules/vore/eating/belly_obj_resist.dm @@ -1,18 +1,18 @@ //Handle a mob struggling // Called from /mob/living/carbon/relaymove() -/obj/belly/proc/relay_resist(mob/living/R, obj/item/C) - if (!(R in contents)) - if(!C) +/obj/belly/proc/relay_resist(mob/living/living_prey, obj/item/prey_item) + if (!(living_prey in contents)) + if(!prey_item) return // User is not in this belly - R.setClickCooldown(50) + living_prey.setClickCooldown(50) if(owner.stat) //If owner is stat (dead, KO) we can actually escape - resist_default_escape(R, C) + resist_default_escape(living_prey, prey_item) return if(displayed_message_flags & MS_FLAG_STRUGGLE_OUTSIDE) - resist_struggle_outside(R) + resist_struggle_outside(living_prey) if((vore_sprite_flags & DM_FLAG_VORESPRITE_BELLY) && (owner.vore_capacity_ex[belly_sprite_to_affect] >= 1) && !private_struggle && resist_triggers_animation && affects_vore_sprites) owner.vs_animate(belly_sprite_to_affect) @@ -20,60 +20,77 @@ if(!private_struggle) resist_play_sound() - if(prob(belchchance))//Unsure if this should go in escapable or not, leaving it here for now. + if((prob(belchchance) && escapable != B_ESCAPABLE_INTENT) || (living_prey.a_intent == I_HELP && escapable == B_ESCAPABLE_INTENT)) owner.emote("belch") - if(escapable) //If the stomach has escapable enabled. - if(resist_check_escapechance(R, C)) - return - if(resist_check_transferchance(R, C)) - return - if(resist_check_secondary_transferchance(R,C)) - return - if(resist_check_absorbchance(R)) - return - if(resist_check_digestchance(R)) - return - if(resist_check_selectchance(R)) - return - - var/struggle_user_message = span_valert(belly_format_string(struggle_messages_inside, R)) - to_chat(R, struggle_user_message) - to_chat(owner, span_vwarning("Your prey appears to be unable to make any progress in escaping your [lowertext(name)].")) - + if(!escapable) //If the stomach has escapable enabled. + var/struggle_user_message = span_valert(belly_format_string(struggle_messages_inside, living_prey)) + to_chat(living_prey, struggle_user_message) return - var/struggle_user_message = span_valert(belly_format_string(struggle_messages_inside, R)) - to_chat(R, struggle_user_message) + if(escapable == B_ESCAPABLE_INTENT && living_prey.a_intent != I_HELP) + switch(living_prey.a_intent) + if(I_HURT) + if(resist_check_escapechance(living_prey, prey_item)) + return + if(I_DISARM) + if(resist_check_transferchance(living_prey, prey_item)) + return + if(resist_check_secondary_transferchance(living_prey,prey_item)) + return + if(I_GRAB) + if(resist_check_absorbchance(living_prey)) + return + if(resist_check_digestchance(living_prey)) + return + if(resist_check_selectchance(living_prey)) + return + else + if(resist_check_escapechance(living_prey, prey_item)) + return + if(resist_check_transferchance(living_prey, prey_item)) + return + if(resist_check_secondary_transferchance(living_prey,prey_item)) + return + if(resist_check_absorbchance(living_prey)) + return + if(resist_check_digestchance(living_prey)) + return + if(resist_check_selectchance(living_prey)) + return -/obj/belly/proc/resist_default_escape(mob/living/R, obj/item/C) - var/escape_attempt_owner_message = span_vwarning(belly_format_string(escape_attempt_messages_owner, R)) - var/escape_attempt_prey_message = span_vwarning(belly_format_string(escape_attempt_messages_prey, R)) - var/escape_fail_owner_message = span_vwarning(belly_format_string(escape_fail_messages_owner, R)) - var/escape_fail_prey_message = span_vnotice(belly_format_string(escape_fail_messages_prey, R)) + var/struggle_user_message = span_valert(belly_format_string(struggle_messages_inside, living_prey)) + to_chat(living_prey, struggle_user_message) + to_chat(owner, span_vwarning("Your prey appears to be unable to make any progress in escaping your [lowertext(name)].")) + +/obj/belly/proc/resist_default_escape(mob/living/living_prey, obj/item/prey_item) + var/escape_attempt_owner_message = span_vwarning(belly_format_string(escape_attempt_messages_owner, living_prey)) + var/escape_attempt_prey_message = span_vwarning(belly_format_string(escape_attempt_messages_prey, living_prey)) + var/escape_fail_owner_message = span_vwarning(belly_format_string(escape_fail_messages_owner, living_prey)) + var/escape_fail_prey_message = span_vnotice(belly_format_string(escape_fail_messages_prey, living_prey)) escape_attempt_prey_message = span_vwarning("[escape_attempt_prey_message] (will take around [escapetime/10] seconds.)") - to_chat(R, escape_attempt_prey_message) + to_chat(living_prey, escape_attempt_prey_message) to_chat(owner, escape_attempt_owner_message) - if(do_after(R, escapetime, owner, target = src, timed_action_flags = IGNORE_INCAPACITATED)) + if(do_after(living_prey, escapetime, owner, target = src, timed_action_flags = IGNORE_INCAPACITATED)) if((owner.stat || escapable)) //Can still escape? - if(C) - release_specific_contents(C) + if(prey_item) + release_specific_contents(prey_item) return - if(R.loc == src) - release_specific_contents(R) + if(living_prey.loc == src) + release_specific_contents(living_prey) return - if(R.loc != src) //Aren't even in the belly. Quietly fail. + if(living_prey.loc != src) //Aren't even in the belly. Quietly fail. return //Belly became inescapable or mob revived - to_chat(R, escape_fail_prey_message) + to_chat(living_prey, escape_fail_prey_message) to_chat(owner, escape_fail_owner_message) -/obj/belly/proc/resist_struggle_outside(mob/living/R) - var/struggle_outer_message = span_valert(belly_format_string(struggle_messages_outside, R)) +/obj/belly/proc/resist_struggle_outside(mob/living/living_prey) + var/struggle_outer_message = span_valert(belly_format_string(struggle_messages_outside, living_prey)) if(private_struggle) to_chat(owner, struggle_outer_message) return @@ -93,59 +110,59 @@ var/sound/struggle_rustle = sound(get_sfx("rustle")) playsound(src, struggle_rustle, vary = 1, vol = 75, falloff = VORE_SOUND_FALLOFF, frequency = noise_freq, preference = /datum/preference/toggle/digestion_noises, volume_channel = VOLUME_CHANNEL_VORE) -/obj/belly/proc/resist_check_escapechance(mob/living/R, obj/item/C) +/obj/belly/proc/resist_check_escapechance(mob/living/living_prey, obj/item/prey_item) if(!prob(escapechance)) //Let's have it check to see if the prey escapes first. return FALSE - var/escape_attempt_owner_message = span_vwarning(belly_format_string(escape_attempt_messages_owner, R)) - var/escape_attempt_prey_message = span_vwarning(belly_format_string(escape_attempt_messages_prey, R)) - var/escape_fail_owner_message = span_vwarning(belly_format_string(escape_fail_messages_owner, R)) - var/escape_fail_prey_message = span_vnotice(belly_format_string(escape_fail_messages_prey, R)) - to_chat(R, escape_attempt_prey_message) + var/escape_attempt_owner_message = span_vwarning(belly_format_string(escape_attempt_messages_owner, living_prey)) + var/escape_attempt_prey_message = span_vwarning(belly_format_string(escape_attempt_messages_prey, living_prey)) + var/escape_fail_owner_message = span_vwarning(belly_format_string(escape_fail_messages_owner, living_prey)) + var/escape_fail_prey_message = span_vnotice(belly_format_string(escape_fail_messages_prey, living_prey)) + to_chat(living_prey, escape_attempt_prey_message) to_chat(owner, escape_attempt_owner_message) - if(do_after(R, escapetime, target = src)) - if(escapable && C) - var/escape_item_owner_message = span_vwarning(belly_format_string(escape_item_messages_owner, R, item = C)) - var/escape_item_prey_message = span_vwarning(belly_format_string(escape_item_messages_prey, R, item = C)) - var/escape_item_outside_message = span_vwarning(belly_format_string(escape_item_messages_outside, R, item = C)) + if(do_after(living_prey, escapetime, target = src)) + if(escapable && prey_item) + var/escape_item_owner_message = span_vwarning(belly_format_string(escape_item_messages_owner, living_prey, item = prey_item)) + var/escape_item_prey_message = span_vwarning(belly_format_string(escape_item_messages_prey, living_prey, item = prey_item)) + var/escape_item_outside_message = span_vwarning(belly_format_string(escape_item_messages_outside, living_prey, item = prey_item)) - release_specific_contents(C) - to_chat(R, escape_item_prey_message) + release_specific_contents(prey_item) + to_chat(living_prey, escape_item_prey_message) to_chat(owner, escape_item_owner_message) if(!private_struggle) for(var/mob/M in hearers(4, owner)) M.show_message(escape_item_outside_message, 2) return TRUE - if(escapable && (R.loc == src) && !R.absorbed) //Does the owner still have escapable enabled? - var/escape_owner_message = span_vwarning(belly_format_string(escape_messages_owner, R)) - var/escape_prey_message = span_vwarning(belly_format_string(escape_messages_prey, R)) - var/escape_outside_message = span_vwarning(belly_format_string(escape_messages_outside, R)) + if(escapable && (living_prey.loc == src) && !living_prey.absorbed) //Does the owner still have escapable enabled? + var/escape_owner_message = span_vwarning(belly_format_string(escape_messages_owner, living_prey)) + var/escape_prey_message = span_vwarning(belly_format_string(escape_messages_prey, living_prey)) + var/escape_outside_message = span_vwarning(belly_format_string(escape_messages_outside, living_prey)) - release_specific_contents(R) - to_chat(R, escape_prey_message) + release_specific_contents(living_prey) + to_chat(living_prey, escape_prey_message) to_chat(owner, escape_owner_message) if(!private_struggle) for(var/mob/M in hearers(4, owner)) M.show_message(escape_outside_message, 2) return TRUE - if(!(R.loc == src)) //Aren't even in the belly. Quietly fail. + if(!(living_prey.loc == src)) //Aren't even in the belly. Quietly fail. return TRUE //Belly became inescapable. - to_chat(R, escape_fail_prey_message) + to_chat(living_prey, escape_fail_prey_message) to_chat(owner, escape_fail_owner_message) return TRUE -/obj/belly/proc/resist_check_transferchance(mob/living/R, obj/item/C) +/obj/belly/proc/resist_check_transferchance(mob/living/living_prey, obj/item/prey_item) if(!prob(transferchance) || !transferlocation) //Next, let's have it see if they end up getting into an even bigger mess then when they started. return FALSE var/obj/belly/dest_belly - for(var/obj/belly/B as anything in owner.vore_organs) - if(B.name == transferlocation) - dest_belly = B + for(var/obj/belly/current_belly as anything in owner.vore_organs) + if(current_belly.name == transferlocation) + dest_belly = current_belly break if(!dest_belly) @@ -154,26 +171,26 @@ transferlocation = null return TRUE - var/primary_transfer_owner_message = span_vwarning(belly_format_string(primary_transfer_messages_owner, R, dest = transferlocation)) - var/primary_transfer_prey_message = span_vwarning(belly_format_string(primary_transfer_messages_prey, R, dest = transferlocation)) + var/primary_transfer_owner_message = span_vwarning(belly_format_string(primary_transfer_messages_owner, living_prey, dest = transferlocation)) + var/primary_transfer_prey_message = span_vwarning(belly_format_string(primary_transfer_messages_prey, living_prey, dest = transferlocation)) - to_chat(R, primary_transfer_prey_message) + to_chat(living_prey, primary_transfer_prey_message) to_chat(owner, primary_transfer_owner_message) - if(C) - transfer_contents(C, dest_belly) + if(prey_item) + transfer_contents(prey_item, dest_belly) return TRUE - transfer_contents(R, dest_belly) + transfer_contents(living_prey, dest_belly) return TRUE -/obj/belly/proc/resist_check_secondary_transferchance(mob/living/R, obj/item/C) +/obj/belly/proc/resist_check_secondary_transferchance(mob/living/living_prey, obj/item/prey_item) if(!prob(transferchance_secondary) || !transferlocation_secondary) //After the first potential mess getting into, run the secondary one which might be even bigger of a mess. return FALSE var/obj/belly/dest_belly - for(var/obj/belly/B as anything in owner.vore_organs) - if(B.name == transferlocation_secondary) - dest_belly = B + for(var/obj/belly/current_belly as anything in owner.vore_organs) + if(current_belly.name == transferlocation_secondary) + dest_belly = current_belly break if(!dest_belly) @@ -182,74 +199,74 @@ transferlocation_secondary = null return TRUE - var/secondary_transfer_owner_message = span_vwarning(belly_format_string(secondary_transfer_messages_owner, R, dest = transferlocation_secondary)) - var/secondary_transfer_prey_message = span_vwarning(belly_format_string(secondary_transfer_messages_prey, R, dest = transferlocation_secondary)) + var/secondary_transfer_owner_message = span_vwarning(belly_format_string(secondary_transfer_messages_owner, living_prey, dest = transferlocation_secondary)) + var/secondary_transfer_prey_message = span_vwarning(belly_format_string(secondary_transfer_messages_prey, living_prey, dest = transferlocation_secondary)) - to_chat(R, secondary_transfer_prey_message) + to_chat(living_prey, secondary_transfer_prey_message) to_chat(owner, secondary_transfer_owner_message) - if(C) - transfer_contents(C, dest_belly) + if(prey_item) + transfer_contents(prey_item, dest_belly) return TRUE - transfer_contents(R, dest_belly) + transfer_contents(living_prey, dest_belly) return TRUE -/obj/belly/proc/resist_check_absorbchance(mob/living/R) +/obj/belly/proc/resist_check_absorbchance(mob/living/living_prey) if(!prob(absorbchance) || digest_mode == DM_ABSORB) //After that, let's have it run the absorb chance. return FALSE - var/absorb_chance_owner_message = span_vwarning(belly_format_string(absorb_chance_messages_owner, R)) - var/absorb_chance_prey_message = span_vwarning(belly_format_string(absorb_chance_messages_prey, R)) + var/absorb_chance_owner_message = span_vwarning(belly_format_string(absorb_chance_messages_owner, living_prey)) + var/absorb_chance_prey_message = span_vwarning(belly_format_string(absorb_chance_messages_prey, living_prey)) - to_chat(R, absorb_chance_prey_message) + to_chat(living_prey, absorb_chance_prey_message) to_chat(owner, absorb_chance_owner_message) digest_mode = DM_ABSORB return TRUE -/obj/belly/proc/resist_check_digestchance(mob/living/R) +/obj/belly/proc/resist_check_digestchance(mob/living/living_prey) if(!prob(digestchance) || digest_mode == DM_DIGEST) //Then, let's see if it should run the digest chance. return FALSE - var/digest_chance_owner_message = span_vwarning(belly_format_string(digest_chance_messages_owner, R)) - var/digest_chance_prey_message = span_vwarning(belly_format_string(digest_chance_messages_prey, R)) + var/digest_chance_owner_message = span_vwarning(belly_format_string(digest_chance_messages_owner, living_prey)) + var/digest_chance_prey_message = span_vwarning(belly_format_string(digest_chance_messages_prey, living_prey)) - to_chat(R, digest_chance_prey_message) + to_chat(living_prey, digest_chance_prey_message) to_chat(owner, digest_chance_owner_message) digest_mode = DM_DIGEST return TRUE -/obj/belly/proc/resist_check_selectchance(mob/living/R) +/obj/belly/proc/resist_check_selectchance(mob/living/living_prey) if(!prob(selectchance) || digest_mode == DM_SELECT) //Finally, let's see if it should run the selective mode chance. return FALSE - var/select_chance_owner_message = span_vwarning(belly_format_string(select_chance_messages_owner, R)) - var/select_chance_prey_message = span_vwarning(belly_format_string(select_chance_messages_prey, R)) + var/select_chance_owner_message = span_vwarning(belly_format_string(select_chance_messages_owner, living_prey)) + var/select_chance_prey_message = span_vwarning(belly_format_string(select_chance_messages_prey, living_prey)) - to_chat(R, select_chance_prey_message) + to_chat(living_prey, select_chance_prey_message) to_chat(owner, select_chance_owner_message) digest_mode = DM_SELECT return TRUE // Absorbed resist handling -/obj/belly/proc/relay_absorbed_resist(mob/living/R) - if (!(R in contents) || !R.absorbed) +/obj/belly/proc/relay_absorbed_resist(mob/living/living_prey) + if (!(living_prey in contents) || !living_prey.absorbed) return // User is not in this belly or isn't actually absorbed - R.setClickCooldown(50) + living_prey.setClickCooldown(50) if(displayed_message_flags & MS_FLAG_STRUGGLE_ABSORBED_OUTSIDE) - absorbed_resist_struggle_outside(R) + absorbed_resist_struggle_outside(living_prey) if(!private_struggle) resist_play_sound() //absorb resists if(escapable || owner.stat) //If the stomach has escapable enabled or the owner is dead/unconscious - if(absorbed_resist_check_escapechance(R)) + if(absorbed_resist_check_escapechance(living_prey)) return - var/struggle_user_message = span_valert(belly_format_string(absorbed_struggle_messages_inside, R, use_absorbed_count = TRUE)) - to_chat(R, struggle_user_message) + var/struggle_user_message = span_valert(belly_format_string(absorbed_struggle_messages_inside, living_prey, use_absorbed_count = TRUE)) + to_chat(living_prey, struggle_user_message) -/obj/belly/proc/absorbed_resist_struggle_outside(mob/living/R) - var/struggle_outer_message = span_valert(belly_format_string(absorbed_struggle_messages_outside, R, use_absorbed_count = TRUE)) +/obj/belly/proc/absorbed_resist_struggle_outside(mob/living/living_prey) + var/struggle_outer_message = span_valert(belly_format_string(absorbed_struggle_messages_outside, living_prey, use_absorbed_count = TRUE)) if(private_struggle) to_chat(owner, struggle_outer_message) return @@ -257,35 +274,35 @@ for(var/mob/M in hearers(4, owner)) M.show_message(struggle_outer_message, 2) // hearable -/obj/belly/proc/absorbed_resist_check_escapechance(mob/living/R) +/obj/belly/proc/absorbed_resist_check_escapechance(mob/living/living_prey) if(!(prob(escapechance_absorbed) || owner.stat)) //Let's have it check to see if the prey's escape attempt starts. return FALSE - var/escape_attempt_absorbed_owner_message = span_vwarning(belly_format_string(escape_attempt_absorbed_messages_owner, R)) - var/escape_attempt_absorbed_prey_message = span_vwarning(belly_format_string(escape_attempt_absorbed_messages_prey, R)) + var/escape_attempt_absorbed_owner_message = span_vwarning(belly_format_string(escape_attempt_absorbed_messages_owner, living_prey)) + var/escape_attempt_absorbed_prey_message = span_vwarning(belly_format_string(escape_attempt_absorbed_messages_prey, living_prey)) - to_chat(R, escape_attempt_absorbed_prey_message) + to_chat(living_prey, escape_attempt_absorbed_prey_message) to_chat(owner, escape_attempt_absorbed_owner_message) - if(do_after(R, escapetime, target = src)) - if((escapable || owner.stat) && (R.loc == src) && prob(escapechance_absorbed)) //Does the escape attempt succeed? - var/escape_absorbed_owner_message = span_vwarning(belly_format_string(escape_absorbed_messages_owner, R)) - var/escape_absorbed_prey_message = span_vwarning(belly_format_string(escape_absorbed_messages_prey, R)) - var/escape_absorbed_outside_message = span_vwarning(belly_format_string(escape_absorbed_messages_outside, R)) + if(do_after(living_prey, escapetime, target = src)) + if((escapable || owner.stat) && (living_prey.loc == src) && prob(escapechance_absorbed)) //Does the escape attempt succeed? + var/escape_absorbed_owner_message = span_vwarning(belly_format_string(escape_absorbed_messages_owner, living_prey)) + var/escape_absorbed_prey_message = span_vwarning(belly_format_string(escape_absorbed_messages_prey, living_prey)) + var/escape_absorbed_outside_message = span_vwarning(belly_format_string(escape_absorbed_messages_outside, living_prey)) - release_specific_contents(R) - to_chat(R, escape_absorbed_prey_message) + release_specific_contents(living_prey) + to_chat(living_prey, escape_absorbed_prey_message) to_chat(owner, escape_absorbed_owner_message) if(!private_struggle) for(var/mob/M in hearers(4, owner)) M.show_message(escape_absorbed_outside_message, 2) return TRUE - if(!(R.loc == src)) //Aren't even in the belly. Quietly fail. + if(!(living_prey.loc == src)) //Aren't even in the belly. Quietly fail. return TRUE //Belly became inescapable or you failed your roll. - var/escape_fail_absorbed_owner_message = span_vwarning(belly_format_string(escape_fail_absorbed_messages_owner, R)) - var/escape_fail_absorbed_prey_message = span_vnotice(belly_format_string(escape_fail_absorbed_messages_prey, R)) + var/escape_fail_absorbed_owner_message = span_vwarning(belly_format_string(escape_fail_absorbed_messages_owner, living_prey)) + var/escape_fail_absorbed_prey_message = span_vnotice(belly_format_string(escape_fail_absorbed_messages_prey, living_prey)) - to_chat(R, escape_fail_absorbed_prey_message) + to_chat(living_prey, escape_fail_absorbed_prey_message) to_chat(owner, escape_fail_absorbed_owner_message) return TRUE diff --git a/code/modules/vore/eating/belly_obj_vfx.dm b/code/modules/vore/eating/belly_obj_vfx.dm index 5e42d283b5..f1f87efd9b 100644 --- a/code/modules/vore/eating/belly_obj_vfx.dm +++ b/code/modules/vore/eating/belly_obj_vfx.dm @@ -1,27 +1,27 @@ // SEND_SIGNAL(COMSIG_BELLY_UPDATE_VORE_FX) is sometimes used when calling vore_fx() to send belly visuals // to certain non-belly atoms. Not called here as vore_fx() is usually only called if a mob is in the belly. // Don't forget it if you need to rework vore_fx(). -/obj/belly/proc/vore_fx(mob/living/L, var/severity = 0) - if(!istype(L)) +/obj/belly/proc/vore_fx(mob/living/living_prey, var/severity = 0) + if(!istype(living_prey)) return - if(!L.client) + if(!living_prey.client) return - if(L.previewing_belly && L.previewing_belly != src) + if(living_prey.previewing_belly && living_prey.previewing_belly != src) return - if(L.previewing_belly == src && L.vore_selected != src) - L.previewing_belly = null + if(living_prey.previewing_belly == src && living_prey.vore_selected != src) + living_prey.previewing_belly = null return - if(!L.show_vore_fx) - L.clear_fullscreen("belly") - L.previewing_belly = null + if(!living_prey.show_vore_fx) + living_prey.clear_fullscreen("belly") + living_prey.previewing_belly = null return if(!belly_fullscreen) - L.clear_fullscreen("belly") - check_hud_disable(L) + living_prey.clear_fullscreen("belly") + check_hud_disable(living_prey) return if(colorization_enabled) - var/atom/movable/screen/fullscreen/F = L.overlay_fullscreen("belly", /atom/movable/screen/fullscreen/belly, severity) // preserving save data + var/atom/movable/screen/fullscreen/fullscreen_overlay = living_prey.overlay_fullscreen("belly", /atom/movable/screen/fullscreen/belly, severity) // preserving save data var/datum/belly_overlays/lookup_belly_path = text2path("/datum/belly_overlays/[lowertext(belly_fullscreen)]") if(!lookup_belly_path) var/used_fullscreen = belly_fullscreen @@ -29,30 +29,30 @@ belly_fullscreen = null log_runtime("Icon datum was not defined for [used_fullscreen]") - var/alpha = min(belly_fullscreen_alpha, L.max_voreoverlay_alpha) - F.icon = initial(lookup_belly_path.belly_icon) - F.cut_overlays() - var/image/I = image(F.icon, belly_fullscreen) //Would be cool if I could just include color and alpha in the image define so we don't have to copy paste - I.color = belly_fullscreen_color - I.alpha = alpha - F.add_overlay(I) - I = image(F.icon, belly_fullscreen+"-2") - I.color = belly_fullscreen_color2 - I.alpha = alpha - F.add_overlay(I) - I = image(F.icon, belly_fullscreen+"-3") - I.color = belly_fullscreen_color3 - I.alpha = alpha - F.add_overlay(I) - I = image(F.icon, belly_fullscreen+"-4") - I.color = belly_fullscreen_color4 - I.alpha = alpha - F.add_overlay(I) + var/alpha = min(belly_fullscreen_alpha, living_prey.max_voreoverlay_alpha) + fullscreen_overlay.icon = initial(lookup_belly_path.belly_icon) + fullscreen_overlay.cut_overlays() + var/image/overlay_image = image(fullscreen_overlay.icon, belly_fullscreen) //Would be cool if overlay_image could just include color and alpha in the image define so we don't have to copy paste + overlay_image.color = belly_fullscreen_color + overlay_image.alpha = alpha + fullscreen_overlay.add_overlay(overlay_image) + overlay_image = image(fullscreen_overlay.icon, belly_fullscreen+"-2") + overlay_image.color = belly_fullscreen_color2 + overlay_image.alpha = alpha + fullscreen_overlay.add_overlay(overlay_image) + overlay_image = image(fullscreen_overlay.icon, belly_fullscreen+"-3") + overlay_image.color = belly_fullscreen_color3 + overlay_image.alpha = alpha + fullscreen_overlay.add_overlay(overlay_image) + overlay_image = image(fullscreen_overlay.icon, belly_fullscreen+"-4") + overlay_image.color = belly_fullscreen_color4 + overlay_image.alpha = alpha + fullscreen_overlay.add_overlay(overlay_image) var/extra_mush = 0 var/extra_mush_color = mush_color - if(L.liquidbelly_visuals && ishuman(owner) && metabolism_overlay && metabolism_mush_ratio > 0) - var/mob/living/carbon/human/H = owner - var/datum/reagents/metabolism/ingested = H.ingested + if(living_prey.liquidbelly_visuals && ishuman(owner) && metabolism_overlay && metabolism_mush_ratio > 0) + var/mob/living/carbon/human/human_owner = owner + var/datum/reagents/metabolism/ingested = human_owner.ingested if(ingested && ingested.total_volume > 0) if(custom_ingested_color) extra_mush_color = custom_ingested_color @@ -60,36 +60,36 @@ extra_mush_color = ingested.get_color() extra_mush = ingested.total_volume * metabolism_mush_ratio if(!mush_overlay) - I = get_mush_overlay(extra_mush_color, min(custom_ingested_alpha, L.max_voreoverlay_alpha), max_ingested, ingested.total_volume) - F.add_overlay(I) - if(show_liquids && L.liquidbelly_visuals && mush_overlay && (owner.nutrition > 0 || max_mush == 0 || min_mush > 0 || (LAZYLEN(contents) * item_mush_val) > 0)) - I = get_mush_overlay(mush_color, min(mush_alpha, L.max_voreoverlay_alpha), max_mush, owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush) - var/stored_y = I.pixel_y - F.add_overlay(I) + overlay_image = get_mush_overlay(extra_mush_color, min(custom_ingested_alpha, living_prey.max_voreoverlay_alpha), max_ingested, ingested.total_volume) + fullscreen_overlay.add_overlay(overlay_image) + if(show_liquids && living_prey.liquidbelly_visuals && mush_overlay && (owner.nutrition > 0 || max_mush == 0 || min_mush > 0 || (LAZYLEN(contents) * item_mush_val) > 0)) + overlay_image = get_mush_overlay(mush_color, min(mush_alpha, living_prey.max_voreoverlay_alpha), max_mush, owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush) + var/stored_y = overlay_image.pixel_y + fullscreen_overlay.add_overlay(overlay_image) if(metabolism_overlay && metabolism_mush_ratio > 0 && extra_mush > 0) var/total_mush_content = owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush - I = get_mush_extra_overlay(extra_mush_color, min(mush_alpha, (extra_mush / max(total_mush_content, 1)) * mush_alpha), stored_y) - F.add_overlay(I) - if(show_liquids && L.liquidbelly_visuals && liquid_overlay && reagents.total_volume) - I = get_liquid_overlay() - I.alpha = min(I.alpha, L.max_voreoverlay_alpha) - F.add_overlay(I) - F.update_for_view(L.client.view) + overlay_image = get_mush_extra_overlay(extra_mush_color, min(mush_alpha, (extra_mush / max(total_mush_content, 1)) * mush_alpha), stored_y) + fullscreen_overlay.add_overlay(overlay_image) + if(show_liquids && living_prey.liquidbelly_visuals && liquid_overlay && reagents.total_volume) + overlay_image = get_liquid_overlay() + overlay_image.alpha = min(overlay_image.alpha, living_prey.max_voreoverlay_alpha) + fullscreen_overlay.add_overlay(overlay_image) + fullscreen_overlay.update_for_view(living_prey.client.view) return - var/atom/movable/screen/fullscreen/F = L.overlay_fullscreen("belly", /atom/movable/screen/fullscreen/belly/fixed, severity) //preserving save data - F.icon = 'icons/mob/vore_fullscreens/ui_lists/screen_full_vore.dmi' - F.cut_overlays() - F.add_overlay(image(F.icon, belly_fullscreen)) - F.add_overlay(image(F.icon, belly_fullscreen+"-2")) - F.add_overlay(image(F.icon, belly_fullscreen+"-3")) - F.add_overlay(image(F.icon, belly_fullscreen+"-4")) - var/image/I + var/atom/movable/screen/fullscreen/fullscreen_overlay = living_prey.overlay_fullscreen("belly", /atom/movable/screen/fullscreen/belly/fixed, severity) //preserving save data + fullscreen_overlay.icon = 'icons/mob/vore_fullscreens/ui_lists/screen_full_vore.dmi' + fullscreen_overlay.cut_overlays() + fullscreen_overlay.add_overlay(image(fullscreen_overlay.icon, belly_fullscreen)) + fullscreen_overlay.add_overlay(image(fullscreen_overlay.icon, belly_fullscreen+"-2")) + fullscreen_overlay.add_overlay(image(fullscreen_overlay.icon, belly_fullscreen+"-3")) + fullscreen_overlay.add_overlay(image(fullscreen_overlay.icon, belly_fullscreen+"-4")) + var/image/overlay_image var/extra_mush = 0 var/extra_mush_color = mush_color - if(L.liquidbelly_visuals && ishuman(owner) && metabolism_overlay && metabolism_mush_ratio > 0) - var/mob/living/carbon/human/H = owner - var/datum/reagents/metabolism/ingested = H.ingested + if(living_prey.liquidbelly_visuals && ishuman(owner) && metabolism_overlay && metabolism_mush_ratio > 0) + var/mob/living/carbon/human/human_owner = owner + var/datum/reagents/metabolism/ingested = human_owner.ingested if(ingested && ingested.total_volume > 0) if(custom_ingested_color) extra_mush_color = custom_ingested_color @@ -97,22 +97,22 @@ extra_mush_color = ingested.get_color() extra_mush = ingested.total_volume * metabolism_mush_ratio if(!mush_overlay) - I = get_mush_overlay(extra_mush_color, custom_ingested_alpha, max_ingested, ingested.total_volume) - F.add_overlay(I) - if(show_liquids && L.liquidbelly_visuals && mush_overlay && (owner.nutrition > 0 || max_mush == 0 || min_mush > 0 || (LAZYLEN(contents) * item_mush_val) > 0)) - I = get_mush_overlay(mush_color, mush_alpha, max_mush, owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush) - I.alpha = mush_alpha - var/stored_y = I.pixel_y - F.add_overlay(I) + overlay_image = get_mush_overlay(extra_mush_color, custom_ingested_alpha, max_ingested, ingested.total_volume) + fullscreen_overlay.add_overlay(overlay_image) + if(show_liquids && living_prey.liquidbelly_visuals && mush_overlay && (owner.nutrition > 0 || max_mush == 0 || min_mush > 0 || (LAZYLEN(contents) * item_mush_val) > 0)) + overlay_image = get_mush_overlay(mush_color, mush_alpha, max_mush, owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush) + overlay_image.alpha = mush_alpha + var/stored_y = overlay_image.pixel_y + fullscreen_overlay.add_overlay(overlay_image) if(metabolism_overlay && metabolism_mush_ratio > 0 && extra_mush > 0) var/total_mush_content = owner.nutrition + LAZYLEN(contents) * item_mush_val + extra_mush - I = get_mush_extra_overlay(extra_mush_color, min(mush_alpha, (extra_mush / max(total_mush_content, 1)) * mush_alpha), stored_y) - F.add_overlay(I) - if(show_liquids && L.liquidbelly_visuals && liquid_overlay && reagents.total_volume) - I = get_liquid_overlay() - F.add_overlay(I) - F.update_for_view(L.client.view) - check_hud_disable(L) + overlay_image = get_mush_extra_overlay(extra_mush_color, min(mush_alpha, (extra_mush / max(total_mush_content, 1)) * mush_alpha), stored_y) + fullscreen_overlay.add_overlay(overlay_image) + if(show_liquids && living_prey.liquidbelly_visuals && liquid_overlay && reagents.total_volume) + overlay_image = get_liquid_overlay() + fullscreen_overlay.add_overlay(overlay_image) + fullscreen_overlay.update_for_view(living_prey.client.view) + check_hud_disable(living_prey) /obj/belly/proc/check_hud_disable(var/mob/living/living_prey) if(disable_hud && living_prey != owner) @@ -121,45 +121,45 @@ living_prey.toggle_hud_vis(TRUE) /obj/belly/proc/get_mush_overlay(color, alpha, limit, content) - var/image/I = image('icons/mob/vore_fullscreens/bubbles.dmi', "mush") - I.color = color - I.pixel_y = -450 + (450 / max(limit, 1) * max(min(limit, content), 1)) - if(I.pixel_y < -450 + (450 / 100 * min_mush)) - I.pixel_y = -450 + (450 / 100 * min_mush) - I.alpha = alpha - return I + var/image/overlay_image = image('icons/mob/vore_fullscreens/bubbles.dmi', "mush") + overlay_image.color = color + overlay_image.pixel_y = -450 + (450 / max(limit, 1) * max(min(limit, content), 1)) + if(overlay_image.pixel_y < -450 + (450 / 100 * min_mush)) + overlay_image.pixel_y = -450 + (450 / 100 * min_mush) + overlay_image.alpha = alpha + return overlay_image /obj/belly/proc/get_mush_extra_overlay(color, alpha, stored_y) - var/image/I = image('icons/mob/vore_fullscreens/bubbles.dmi', "mush") - I.color = color - I.pixel_y = stored_y - I.alpha = alpha - return I + var/image/overlay_image = image('icons/mob/vore_fullscreens/bubbles.dmi', "mush") + overlay_image.color = color + overlay_image.pixel_y = stored_y + overlay_image.alpha = alpha + return overlay_image /obj/belly/proc/get_liquid_overlay() - var/image/I + var/image/overlay_image if(digest_mode == DM_HOLD && item_digest_mode == IM_HOLD) - I = image('icons/mob/vore_fullscreens/bubbles.dmi', "calm") + overlay_image = image('icons/mob/vore_fullscreens/bubbles.dmi', "calm") else - I = image('icons/mob/vore_fullscreens/bubbles.dmi', "bubbles") + overlay_image = image('icons/mob/vore_fullscreens/bubbles.dmi', "bubbles") if(custom_reagentcolor) - I.color = custom_reagentcolor + overlay_image.color = custom_reagentcolor else - I.color = reagentcolor + overlay_image.color = reagentcolor if(custom_reagentalpha) - I.alpha = custom_reagentalpha + overlay_image.alpha = custom_reagentalpha else - I.alpha = max(150, min(custom_max_volume, 255)) - (255 - belly_fullscreen_alpha) - I.pixel_y = -450 + min((450 / custom_max_volume * reagents.total_volume), 450 / 100 * max_liquid_level) - return I + overlay_image.alpha = max(150, min(custom_max_volume, 255)) - (255 - belly_fullscreen_alpha) + overlay_image.pixel_y = -450 + min((450 / custom_max_volume * reagents.total_volume), 450 / 100 * max_liquid_level) + return overlay_image -/obj/belly/proc/vore_preview(mob/living/L) - if(!istype(L) || !L.client) - L.previewing_belly = null +/obj/belly/proc/vore_preview(mob/living/living_prey) + if(!istype(living_prey) || !living_prey.client) + living_prey.previewing_belly = null return - L.previewing_belly = src - vore_fx(L) + living_prey.previewing_belly = src + vore_fx(living_prey) -/obj/belly/proc/clear_preview(mob/living/L) - L.previewing_belly = null - L.clear_fullscreen("belly") +/obj/belly/proc/clear_preview(mob/living/living_prey) + living_prey.previewing_belly = null + living_prey.clear_fullscreen("belly") diff --git a/code/modules/vore/eating/panel_databackend/vorepanel_set_attribute.dm b/code/modules/vore/eating/panel_databackend/vorepanel_set_attribute.dm index 59a921dbf0..fba180f2b6 100644 --- a/code/modules/vore/eating/panel_databackend/vorepanel_set_attribute.dm +++ b/code/modules/vore/eating/panel_databackend/vorepanel_set_attribute.dm @@ -614,15 +614,17 @@ host.vore_selected.emote_time = CLAMP(new_time, 60, 600) . = TRUE if("b_escapable") - if(host.vore_selected.escapable == 0) //Possibly escapable and special interactions. - host.vore_selected.escapable = 1 - to_chat(user,span_warning("Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings.")) - else if(host.vore_selected.escapable == 1) //Never escapable. - host.vore_selected.escapable = 0 - to_chat(user,span_warning("Prey will not be able to have special interactions with your [lowertext(host.vore_selected.name)].")) - else - tgui_alert_async(user, "Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1 - host.vore_selected.escapable = 0 + var/new_mode = text2num(params["val"]) + switch(new_mode) + if(B_ESCAPABLE_NONE) //Never escapable. + host.vore_selected.escapable = B_ESCAPABLE_NONE + to_chat(user,span_warning("Prey will not be able to have special interactions with your [lowertext(host.vore_selected.name)].")) + if(B_ESCAPABLE_DEFAULT) //Possibly escapable and special interactions. + host.vore_selected.escapable = B_ESCAPABLE_DEFAULT + to_chat(user,span_warning("Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings.")) + if(B_ESCAPABLE_INTENT) //Possibly escapable and special intent based interactions. + host.vore_selected.escapable = B_ESCAPABLE_INTENT + to_chat(user,span_warning("Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings and their intent.")) . = TRUE if("b_escapechance") var/escape_chance_input = text2num(params["val"]) diff --git a/code/modules/vore/eating/panel_databackend/vorepanel_vore_data.dm b/code/modules/vore/eating/panel_databackend/vorepanel_vore_data.dm index d2e1e3e230..a5df4128b0 100644 --- a/code/modules/vore/eating/panel_databackend/vorepanel_vore_data.dm +++ b/code/modules/vore/eating/panel_databackend/vorepanel_vore_data.dm @@ -93,21 +93,28 @@ 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() +/datum/vore_look/proc/get_prey_abilities(mob/owner, obj/belly/inside_belly) + if(!istype(inside_belly)) + return null - 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_intent_data(mob/owner, obj/belly/inside_belly) + if(!isbelly(inside_belly)) + return null + + return list( + "active" = inside_belly.escapable == B_ESCAPABLE_INTENT, + "current_intent" = owner.a_intent, + "help" = !!inside_belly.belchchance, + "disarm" = (inside_belly.transferchance && inside_belly.transferlocation) || (inside_belly.transferchance_secondary && inside_belly.transferlocation_secondary), + "grab" = (inside_belly.absorbchance && inside_belly.digest_mode != DM_ABSORB) || (inside_belly.digestchance && inside_belly.digest_mode != DM_DIGEST) || (inside_belly.selectchance && inside_belly.digest_mode != DM_SELECT), + "harm" = !!inside_belly.escapechance + ) + /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)) diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 0b69d1ff6d..7334d952a8 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -142,6 +142,7 @@ data["show_pictures"] = null data["icon_overflow"] = null data["prey_abilities"] = null + data["intent_data"] = null data["our_bellies"] = null data["selected"] = null data["soulcatcher"] = null @@ -168,7 +169,13 @@ // Content Data data["show_pictures"] = show_pictures data["icon_overflow"] = icon_overflow - data["prey_abilities"] = get_prey_abilities(host) + var/atom/hostloc = host.loc + // Allow VorePanel to show pred belly details even while indirectly inside + if(isliving(host)) + var/mob/living/human_host = host + hostloc = human_host.surrounding_belly() + data["prey_abilities"] = get_prey_abilities(host, hostloc) + data["intent_data"] = get_intent_data(host, hostloc) if(SOULCATCHER_TAB) // Soulcatcher and abilities @@ -907,7 +914,7 @@ return entries[index] = hex preset_colors = entries.Join(";") - return FALSE + return TRUE /datum/vore_look/proc/pick_from_inside(mob/user, params) var/atom/movable/target = locate(params["pick"]) diff --git a/tgui/bun.lock b/tgui/bun.lock index 49f8cbca24..42f5400553 100644 --- a/tgui/bun.lock +++ b/tgui/bun.lock @@ -5,11 +5,11 @@ "": { "name": "tgui-workspace", "devDependencies": { - "@happy-dom/global-registrator": "^20.0.11", + "@happy-dom/global-registrator": "^20.1.0", "@rspack/cli": "^1.7.1", "@rspack/core": "^1.7.1", "@types/bun": "^1.3.5", - "@types/react": "^19.2.7", + "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", "@types/webpack-env": "^1.18.8", "@types/wicg-file-system-access": "^2023.10.7", @@ -52,7 +52,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-json-tree": "^0.20.0", - "tgui-core": "^5.7.2", + "tgui-core": "^5.8.0", "tgui-dev-server": "workspace:*", }, }, @@ -76,7 +76,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "tgui": "workspace:*", - "tgui-core": "^5.7.2", + "tgui-core": "^5.8.0", "tgui-dev-server": "workspace:*", "zod": "^4.3.5", }, @@ -89,7 +89,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "tgui": "workspace:*", - "tgui-core": "^5.7.2", + "tgui-core": "^5.8.0", }, }, "packages/tgui-setup": { @@ -122,7 +122,7 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="], + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.1.0", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.1.0" } }, "sha512-8c2dSLe7/Rdiq5mAZ8G6hZhx+mp3QzUp1sQD6dmTilLf4bz5X0AAHvBW3AcpcUYGBiTamB4EFuXXtWi5ZVqtRg=="], "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], @@ -272,7 +272,7 @@ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], + "@types/react": ["@types/react@19.2.8", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], @@ -598,7 +598,7 @@ "handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="], - "happy-dom": ["happy-dom@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g=="], + "happy-dom": ["happy-dom@20.1.0", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-ebvqjBqzenBk2LjzNEAzoj7yhw7rW/R2/wVevMu6Mrq3MXtcI/RUz4+ozpcOcqVLEWPqLfg2v9EAU7fFXZUUJw=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -1046,7 +1046,7 @@ "tgui": ["tgui@workspace:packages/tgui"], - "tgui-core": ["tgui-core@5.7.2", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-KyV8Fp5yLYTPOFOJshdrbOplzvyyEPtMt+KLODtLG1NCrMtkrb09IuiXNl829QPOdB9Pr9HohSgqbPTHPRTHOg=="], + "tgui-core": ["tgui-core@5.8.0", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-rwyBJy45aehosMyqcHjkaSryagOqxTVWxe9AikncYRChykmDfSynncMk8BYtGkKyGQpddxLPHLQnetpDeD8QVw=="], "tgui-dev-server": ["tgui-dev-server@workspace:packages/tgui-dev-server"], diff --git a/tgui/package.json b/tgui/package.json index 03b3f8de59..f420aa99de 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -2,11 +2,11 @@ "name": "tgui-workspace", "version": "6.0.0", "devDependencies": { - "@happy-dom/global-registrator": "^20.0.11", + "@happy-dom/global-registrator": "^20.1.0", "@rspack/cli": "^1.7.1", "@rspack/core": "^1.7.1", "@types/bun": "^1.3.5", - "@types/react": "^19.2.7", + "@types/react": "^19.2.8", "@types/react-dom": "^19.2.3", "@types/webpack-env": "^1.18.8", "@types/wicg-file-system-access": "^2023.10.7", diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index 97995ad6fd..7485641ff1 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -9,7 +9,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "tgui": "workspace:*", - "tgui-core": "^5.7.2", + "tgui-core": "^5.8.0", "tgui-dev-server": "workspace:*", "zod": "^4.3.5" }, diff --git a/tgui/packages/tgui-say/package.json b/tgui/packages/tgui-say/package.json index f8ef73175e..cb776e36b1 100644 --- a/tgui/packages/tgui-say/package.json +++ b/tgui/packages/tgui-say/package.json @@ -6,7 +6,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "tgui": "workspace:*", - "tgui-core": "^5.7.2" + "tgui-core": "^5.8.0" }, "private": true } diff --git a/tgui/packages/tgui/components/NanoMap.tsx b/tgui/packages/tgui/components/NanoMap.tsx index 4a91dcb4ff..a48b077ce6 100644 --- a/tgui/packages/tgui/components/NanoMap.tsx +++ b/tgui/packages/tgui/components/NanoMap.tsx @@ -1,4 +1,9 @@ -import { Component, type CSSProperties, type PropsWithChildren } from 'react'; +import { + Component, + type CSSProperties, + type PropsWithChildren, + type ReactNode, +} from 'react'; import { resolveAsset } from 'tgui/assets'; import { useBackend } from 'tgui/backend'; import { @@ -229,7 +234,7 @@ type NanoMapMarkerProps = { y: number; zoom: number; icon: string; - tooltip: string; + tooltip: ReactNode; color: string; onClick?: (e: React.MouseEvent) => void; }; diff --git a/tgui/packages/tgui/interfaces/GhostSpawn/GhostJoin/SpawnElement.tsx b/tgui/packages/tgui/interfaces/GhostSpawn/GhostJoin/SpawnElement.tsx index 7a748bc1bb..99bd8fea3e 100644 --- a/tgui/packages/tgui/interfaces/GhostSpawn/GhostJoin/SpawnElement.tsx +++ b/tgui/packages/tgui/interfaces/GhostSpawn/GhostJoin/SpawnElement.tsx @@ -1,10 +1,11 @@ +import type { ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; import { Button, LabeledList, Stack } from 'tgui-core/components'; export const SpawnElement = (props: { title: string; disabled: boolean; - tooltip: string; + tooltip: ReactNode; action: string; }) => { const { act } = useBackend(); diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/helper_components.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/helper_components.tsx index 47e73a71ec..22428355cd 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/helper_components.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/helper_components.tsx @@ -55,7 +55,9 @@ export const CanvasBackedImage = (props: { }; }, [props.render]); - return bitmap ? : null; + return bitmap ? ( + + ) : null; }; export const drawColorizedIconToOffscreenCanvas = async ( diff --git a/tgui/packages/tgui/interfaces/PublicLibraryWiki/WikiCommon/WikiColorIcon.tsx b/tgui/packages/tgui/interfaces/PublicLibraryWiki/WikiCommon/WikiColorIcon.tsx index dee1c1b8c7..718b74ee27 100644 --- a/tgui/packages/tgui/interfaces/PublicLibraryWiki/WikiCommon/WikiColorIcon.tsx +++ b/tgui/packages/tgui/interfaces/PublicLibraryWiki/WikiCommon/WikiColorIcon.tsx @@ -70,6 +70,7 @@ export const CanvasBackedImage = (props: { style={{ visibility: bitmap && !loadFailed ? 'visible' : 'hidden', }} + draggable={false} /> ); }; diff --git a/tgui/packages/tgui/interfaces/ShadekinConfig.tsx b/tgui/packages/tgui/interfaces/ShadekinConfig.tsx index 70ae4d0ab5..74e0e5883e 100644 --- a/tgui/packages/tgui/interfaces/ShadekinConfig.tsx +++ b/tgui/packages/tgui/interfaces/ShadekinConfig.tsx @@ -1,7 +1,6 @@ import { useBackend } from 'tgui/backend'; import { Window } from 'tgui/layouts'; import { - Box, Button, ColorBox, LabeledList, @@ -9,9 +8,9 @@ import { NumberInput, Section, Stack, - Tooltip, } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; +import { VorePanelTooltip } from './VorePanel/VorePanelElements/VorePanelTooltip'; type Data = { stun_time: number; @@ -84,9 +83,10 @@ export const ShadekinConfig = (props) => { /> - - ? - + @@ -120,9 +120,10 @@ export const ShadekinConfig = (props) => { /> - - ? - + @@ -141,9 +142,10 @@ export const ShadekinConfig = (props) => { /> - - ? - + diff --git a/tgui/packages/tgui/interfaces/TextInputModal.tsx b/tgui/packages/tgui/interfaces/TextInputModal.tsx index 6704283e75..c063dad27f 100644 --- a/tgui/packages/tgui/interfaces/TextInputModal.tsx +++ b/tgui/packages/tgui/interfaces/TextInputModal.tsx @@ -41,7 +41,7 @@ export const TextInputModal = (props) => { const [input, setInput] = useState(placeholder || ''); - const onType = (value: string) => { + function onType(value: string) { if (value === input) { return; } @@ -49,7 +49,7 @@ export const TextInputModal = (props) => { ? sanitizeMultiline(value) : removeAllSkiplines(value); setInput(sanitizedInput); - }; + } const visualMultiline = multiline || input.length >= 30; // Dynamically changes the window height based on the message. @@ -67,6 +67,11 @@ export const TextInputModal = (props) => { act('cancel'); } } + + // This is the length of the input in terms of unicode code points. + // Should be equivalent to what length_char would output for the same string in BYOND. + const char_length = [...input].length; + return ( {timeout && } @@ -94,7 +99,7 @@ export const TextInputModal = (props) => { diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/MultiOverlayImage.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/MultiOverlayImage.tsx index 6d50e13c8b..78b9094465 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/MultiOverlayImage.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/MultiOverlayImage.tsx @@ -5,7 +5,7 @@ import type { Overlay } from '../types'; const imageCache = new Map>(); -function cachedGetImage(url: string) { +async function cachedGetImage(url: string) { const existing = imageCache.get(url); if (existing) return existing; @@ -31,102 +31,113 @@ export const MultiOverlayImage = (props: { gallery?: boolean; }) => { const { overlays, size, targetSize, alpha, gallery } = props; - const [src, setSrc] = useState(''); + + const [src, setSrc] = useState(null); const blobRef = useRef(''); + const lastOverlayKeyRef = useRef(''); - const render = useCallback( - async (canvas: OffscreenCanvas, ctx: OffscreenCanvasRenderingContext2D) => { - ctx.clearRect(0, 0, targetSize, targetSize); - ctx.imageSmoothingEnabled = true; + const mainCanvasRef = useRef(null); + const mainCtxRef = useRef(null); - const images = await Promise.all( - overlays.map(async (o, i) => { - const iconRef = o.icon ? getIconFromRefMap(o.icon) : null; - if (!iconRef) return null; - const url = `${iconRef}?state=${o.iconState}`; - const img = await cachedGetImage(url); - return img; - }), - ); - - for (let i = 0; i < overlays.length; i++) { - const overlay = overlays[i]; - const image = images[i]; - if (!image) continue; - - ctx.globalCompositeOperation = 'source-over'; - ctx.drawImage(image, 0, 0, size, size, 0, 0, targetSize, targetSize); - - if (overlay.color) { - const tempCanvas = new OffscreenCanvas(targetSize, targetSize); - const tempCtx = tempCanvas.getContext('2d'); - if (!tempCtx) continue; - - tempCtx.imageSmoothingEnabled = true; - tempCtx.drawImage( - image, - 0, - 0, - size, - size, - 0, - 0, - targetSize, - targetSize, - ); - - tempCtx.globalCompositeOperation = 'multiply'; - tempCtx.fillStyle = overlay.color; - tempCtx.fillRect(0, 0, targetSize, targetSize); - - tempCtx.globalCompositeOperation = 'destination-in'; - tempCtx.drawImage( - image, - 0, - 0, - size, - size, - 0, - 0, - targetSize, - targetSize, - ); - - ctx.drawImage(tempCanvas, 0, 0); - } - } - - ctx.globalCompositeOperation = 'source-over'; - }, - [overlays], - ); + const tempCanvasRef = useRef(null); + const tempCtxRef = useRef(null); const drawToBlob = useCallback(async () => { - const offscreen = new OffscreenCanvas(targetSize, targetSize); - const ctx = offscreen.getContext('2d'); - if (!ctx) return; + const overlayKey = overlays + .map((o) => `${o.icon}:${o.iconState}:${o.color ?? ''}`) + .join('|'); - try { - await render(offscreen, ctx); - const blob = await offscreen.convertToBlob(); - if (!blob) return; + if (overlayKey === lastOverlayKeyRef.current) return; + lastOverlayKeyRef.current = overlayKey; - const url = URL.createObjectURL(blob); - if (blobRef.current) URL.revokeObjectURL(blobRef.current); - blobRef.current = url; - setSrc(url); - } catch (e) { - console.error('Failed to render image', e); + const images = await Promise.all( + overlays.map(async (o) => { + if (!o.icon) return null; + const iconRef = getIconFromRefMap(o.icon); + if (!iconRef) return null; + return cachedGetImage(`${iconRef}?state=${o.iconState}`); + }), + ); + + if (images.some((img, i) => overlays[i].icon && !img)) return; + + if (!mainCanvasRef.current) + mainCanvasRef.current = new OffscreenCanvas(targetSize, targetSize); + if (!mainCtxRef.current) + mainCtxRef.current = mainCanvasRef.current.getContext('2d')!; + if (!tempCanvasRef.current) + tempCanvasRef.current = new OffscreenCanvas(targetSize, targetSize); + if (!tempCtxRef.current) + tempCtxRef.current = tempCanvasRef.current.getContext('2d')!; + + const ctx = mainCtxRef.current!; + const tempCtx = tempCtxRef.current!; + const tempCanvas = tempCanvasRef.current; + + ctx.clearRect(0, 0, targetSize, targetSize); + ctx.imageSmoothingEnabled = true; + + for (let i = 0; i < overlays.length; i++) { + const overlay = overlays[i]; + const image = images[i]; + if (!image) continue; + + ctx.globalCompositeOperation = 'source-over'; + ctx.drawImage(image, 0, 0, size, size, 0, 0, targetSize, targetSize); + + if (overlay.color) { + tempCtx.clearRect(0, 0, targetSize, targetSize); + tempCtx.imageSmoothingEnabled = true; + + tempCtx.drawImage( + image, + 0, + 0, + size, + size, + 0, + 0, + targetSize, + targetSize, + ); + + tempCtx.globalCompositeOperation = 'multiply'; + tempCtx.fillStyle = overlay.color; + tempCtx.fillRect(0, 0, targetSize, targetSize); + + tempCtx.globalCompositeOperation = 'destination-in'; + tempCtx.drawImage( + image, + 0, + 0, + size, + size, + 0, + 0, + targetSize, + targetSize, + ); + + tempCtx.globalCompositeOperation = 'source-over'; + ctx.drawImage(tempCanvas, 0, 0); + } } - }, [render]); + + const blob = await mainCanvasRef.current.convertToBlob(); + if (!blob) return; + + const url = URL.createObjectURL(blob); + + if (blobRef.current) URL.revokeObjectURL(blobRef.current); + blobRef.current = url; + setSrc(url); + }, [overlays]); useEffect(() => { drawToBlob(); return () => { - if (blobRef.current) { - URL.revokeObjectURL(blobRef.current); - blobRef.current = ''; - } + if (blobRef.current) URL.revokeObjectURL(blobRef.current); + blobRef.current = ''; }; }, [drawToBlob]); @@ -139,6 +150,7 @@ export const MultiOverlayImage = (props: { opacity: (alpha ?? 255) / 255, transform: gallery ? 'translate(1%, 3%)' : undefined, }} + draggable={false} /> ) : null; }; diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditCheckboxes.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditCheckboxes.tsx index 0fcae098b0..9195134f51 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditCheckboxes.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditCheckboxes.tsx @@ -1,8 +1,9 @@ -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; -import { Box, Button, Floating, Stack, Tooltip } from 'tgui-core/components'; +import { Box, Button, Floating, Stack } from 'tgui-core/components'; import type { CheckBoxEntry } from '../types'; +import { VorePanelTooltip } from './VorePanelTooltip'; export const VorePanelEditCheckboxes = ( props: { @@ -18,7 +19,7 @@ export const VorePanelEditCheckboxes = ( /** Our tooltips associated to the checkbox actions as Record mapping our options to a tooltip */ tooltipList: Record; /** Our displayed tooltip behind the input element */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; }>, @@ -68,9 +69,11 @@ export const VorePanelEditCheckboxes = ( - - ? - + )} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditColor.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditColor.tsx index 8fa9ca34b1..bb0055287d 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditColor.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditColor.tsx @@ -1,5 +1,6 @@ import { type ComponentProps, + type ReactNode, useEffect, useEffectEvent, useState, @@ -7,9 +8,10 @@ import { import { useBackend } from 'tgui/backend'; import { ColorSelector } from 'tgui/interfaces/ColorPickerModal'; import { type HsvaColor, hexToHsva, hsvaToHex } from 'tgui-core/color'; -import { Box, Floating, Stack, Tooltip } from 'tgui-core/components'; +import { Box, Floating, Stack } from 'tgui-core/components'; import { VorePanelColorBox } from './VorePanelCommonElements'; import { VorePanelEditNumber } from './VorePanelEditNumber'; +import { VorePanelTooltip } from './VorePanelTooltip'; export const VorePanelEditColor = ( props: { @@ -27,7 +29,7 @@ export const VorePanelEditColor = ( /** Optional label to show before the color box */ name_of: string; /** Our displayed tooltip behind the input element */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; /** Removes the spacing behind the color box */ @@ -71,7 +73,7 @@ export const VorePanelEditColor = ( timeoutId = setTimeout(() => { setInitialColor(back_color); setCurrentColor(hexToHsva(back_color)); - }, 300); + }, 100); } return () => { @@ -87,7 +89,7 @@ export const VorePanelEditColor = ( const timeoutId = setTimeout(() => { onRealtimeValue(hsvaToHex(currentColor)); - }, 300); + }, 100); return () => { clearTimeout(timeoutId); @@ -212,9 +214,11 @@ export const VorePanelEditColor = ( tooltip={tooltip} /> ) : ( - - ? - + )} )} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditDropdown.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditDropdown.tsx index 542859090a..06fc5c0764 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditDropdown.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditDropdown.tsx @@ -1,14 +1,9 @@ -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; -import { - Box, - Dropdown, - type Floating, - Stack, - Tooltip, -} from 'tgui-core/components'; +import { Box, Dropdown, type Floating, Stack } from 'tgui-core/components'; import type { DropdownEntry } from '../types'; +import { VorePanelTooltip } from './VorePanelTooltip'; export const VorePanelEditDropdown = ( props: { @@ -28,7 +23,7 @@ export const VorePanelEditDropdown = ( /** Icon of the dropdown */ icon: string; /** Our displayed tooltip behind the input element */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; }>, @@ -47,15 +42,24 @@ export const VorePanelEditDropdown = ( tooltipPosition, } = props; + const normalizedOptions: DropdownEntry[] = options.map((opt) => + typeof opt === 'string' ? { value: opt, displayText: opt } : opt, + ); + + const entryValue = + normalizedOptions.find((o) => o.displayText === entry)?.value ?? ''; + + function saneAct(value: string) { + if (value !== entryValue) act(action, { attribute: subAction, val: value }); + } + return editMode ? ( - act(action, { attribute: subAction, val: value }) - } + onSelected={saneAct} options={options} selected={entry} icon={icon} @@ -63,9 +67,11 @@ export const VorePanelEditDropdown = ( {tooltip && ( - - ? - + )} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditNumber.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditNumber.tsx index a73fba5632..af7bb3d3ba 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditNumber.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditNumber.tsx @@ -1,13 +1,8 @@ -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; -import { - Box, - type Floating, - NumberInput, - Stack, - Tooltip, -} from 'tgui-core/components'; +import { Box, type Floating, NumberInput, Stack } from 'tgui-core/components'; import { round, toFixed } from 'tgui-core/math'; +import { VorePanelTooltip } from './VorePanelTooltip'; export const VorePanelEditNumber = ( props: { @@ -33,7 +28,7 @@ export const VorePanelEditNumber = ( /** The color of the displayed text */ color: string; /** Our displayed tooltip behind the input element */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; /** The amount of fractional digits shown */ @@ -79,9 +74,11 @@ export const VorePanelEditNumber = ( {tooltip && ( - - ? - + )} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditSwitch.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditSwitch.tsx index b82bf89f44..d486038004 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditSwitch.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditSwitch.tsx @@ -1,4 +1,4 @@ -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; import { Box, Button, type Floating } from 'tgui-core/components'; @@ -16,7 +16,7 @@ export const VorePanelEditSwitch = ( /** Displayed text content */ content: string; /** Our displayed tooltip on button hover */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; /** Color of the button and displayed text */ diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditText.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditText.tsx index 92a4441563..1e3bd61ea4 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditText.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelEditText.tsx @@ -1,12 +1,7 @@ -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; -import { - Box, - type Floating, - Input, - Stack, - Tooltip, -} from 'tgui-core/components'; +import { Box, type Floating, Input, Stack } from 'tgui-core/components'; +import { VorePanelTooltip } from './VorePanelTooltip'; export const VorePanelEditText = ( props: { @@ -26,7 +21,7 @@ export const VorePanelEditText = ( /** The color of the displayed text */ color: string; /** Our displayed tooltip displayed the text */ - tooltip: string; + tooltip: ReactNode; /** The position of the tooltip if static */ tooltipPosition: ComponentProps['placement']; }>, @@ -63,9 +58,11 @@ export const VorePanelEditText = ( {tooltip && ( - - ? - + )} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTextArea.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTextArea.tsx index a6de3a5a6b..9e1bb2ffc1 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTextArea.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTextArea.tsx @@ -146,7 +146,7 @@ export const VorePanelEditTextArea = ( /** Our secondary backend action if we used a list as input on text area blur */ listAction: string; /** Our displayed tooltip displayed above all texts */ - tooltip: string; + tooltip: ReactNode; /** Do we force the input to always send the maxEntries as list length to byond */ exactLength: boolean; /** The amount of possible list entries. By default 10 */ diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip.tsx new file mode 100644 index 0000000000..0bc6fd8e33 --- /dev/null +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip.tsx @@ -0,0 +1,31 @@ +import type { ComponentProps, ReactNode } from 'react'; +import { Box, type Floating, Tooltip } from 'tgui-core/components'; + +export const VorePanelTooltip = ( + props: { + /** Our displayed tooltip displayed the text */ + tooltip: ReactNode; + /** Text of the button */ + displayText: string; + } & Partial<{ + /** The position of the tooltip if static */ + tooltipPosition: ComponentProps['placement']; + /** Color of the button */ + color: string; + /** Event based selection, usually not used */ + selected: boolean; + }>, +) => { + const { tooltip, tooltipPosition, displayText, color, selected } = props; + + return ( + + + {displayText} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePaneldEditTextTabs.tsx b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePaneldEditTextTabs.tsx index dcb007a7a1..9ad99f3408 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePaneldEditTextTabs.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VorePanelElements/VorePaneldEditTextTabs.tsx @@ -1,6 +1,6 @@ +import type { ReactNode } from 'react'; import { useBackend } from 'tgui/backend'; import { LabeledList, Section, Stack, Tabs } from 'tgui-core/components'; - import { VorePanelEditSwitch } from './VorePanelEditSwitch'; import { VorePanelEditTextArea } from './VorePanelTextArea'; @@ -15,7 +15,7 @@ export const VorePanelEditTextTabs = ( /** The backend action to perform on tab selection */ tabAction: string; /** Our displayed tooltip displayed above all texts */ - tooltip: string; + tooltip: ReactNode; /** The maximum length of each message */ maxLength: number; /** The current displayed message or message array */ @@ -48,7 +48,7 @@ export const VorePanelEditTextTabs = ( /** The data of the button to show its possible selected state */ button_data: boolean; /** The tooltip to display on button hover */ - button_tooltip: string; + button_tooltip: ReactNode; /** The icon of each tab as record, mapping the messageOptions to icons */ tabsToIcons: Record; /** Disable our special highlighting used on belly messages */ diff --git a/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/ContentTab/VoreContentIntent.tsx b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/ContentTab/VoreContentIntent.tsx new file mode 100644 index 0000000000..d469cf2c5f --- /dev/null +++ b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/ContentTab/VoreContentIntent.tsx @@ -0,0 +1,22 @@ +import { Stack } from 'tgui-core/components'; +import { intentMappings } from '../../constants'; +import type { IntentData } from '../../types'; +import { VorePanelTooltip } from '../../VorePanelElements/VorePanelTooltip'; + +export const VoreContentIntent = (props: { intent_data: IntentData }) => { + const { intent_data } = props; + + return ( + !!intent_data.active && + intentMappings.map(({ key, label, color, tooltip }) => ( + + + + )) + ); +}; diff --git a/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx index d8798e2ebd..56c900d135 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VoreSelectedBellyTabs/VoreContentsPanel.tsx @@ -15,10 +15,12 @@ import type { BooleanLike } from 'tgui-core/react'; import { createSearch } from 'tgui-core/string'; import { stats } from '../constants'; import { ourTypeToOptions } from '../functions'; -import type { ContentData, DropdownEntry } from '../types'; +import type { ContentData, DropdownEntry, IntentData } from '../types'; +import { VoreContentIntent } from './ContentTab/VoreContentIntent'; export const VoreContentsPanel = (props: { contents?: ContentData[] | null; + intent_data?: IntentData | null; targetBelly?: string; allow_absorbed_devour?: BooleanLike; onTargetBely?: React.Dispatch>; @@ -34,6 +36,7 @@ export const VoreContentsPanel = (props: { const { contents = [], + intent_data, targetBelly = '', onTargetBely, bellyDropdownNames, @@ -79,6 +82,7 @@ export const VoreContentsPanel = (props: { title="Contents" buttons={ + {!!intent_data && } { const { act } = useBackend(); - const { inside, prey_abilities, show_pictures, icon_overflow } = props; + const { inside, prey_abilities, intent_data, show_pictures, icon_overflow } = + props; return (
@@ -46,6 +48,7 @@ export const VoreContentsPreyPanel = (props: { Transfer > Absorb > Digest' will occur." + + + These control how your belly responds to someone using + 'resist' while inside you. You can switch between the + default behavior or giving prey intent-based options. The + percent chance to trigger each is listed below, and you can + change them to whatever you see fit. Setting them to 0% will + disable the possibility of that interaction. These only + function as long as interactions are turned on in general. + Keep in mind, the 'belly mode' interactions (digest/absorb) + will affect all prey in that belly: if multiple could + trigger at the same time, only the first in the order of + 'Escape > Transfer > Absorb > Digest' will occur. + + + Intent mappings: + {intentMappings.map(({ label, color, description }) => ( + + + {label} + + : {description} + + ))} + + } /> } diff --git a/tgui/packages/tgui/interfaces/VorePanel/constants.ts b/tgui/packages/tgui/interfaces/VorePanel/constants.ts index 310d5d9e91..c1fbefc79e 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/constants.ts +++ b/tgui/packages/tgui/interfaces/VorePanel/constants.ts @@ -154,3 +154,52 @@ export const preyAbilityToData = { color: 'red', }, } as const; + +export const intentMappings = [ + { + key: 'help', + label: 'Help', + color: 'green', + description: 'Belch chance', + tooltip: (active: boolean) => + active + ? 'Struggling in help intent might cause belch interactions.' + : 'Help intent struggle belch chance disabled.', + }, + { + key: 'disarm', + label: 'Disarm', + color: 'blue', + description: 'Transfer chance', + tooltip: (active: boolean) => + active + ? 'Struggling in disarm intent might cause auto transfer interactions.' + : 'Disarm intent struggle auto transfer chance disabled.', + }, + { + key: 'grab', + label: 'Grab', + color: 'yellow', + description: 'Belly mode change chances (Absorb/Digest/Select)', + tooltip: (active: boolean) => + active + ? 'Struggling in grab intent might cause digest mode change interactions.' + : 'Grab intent struggle digest mode change disabled.', + }, + { + key: 'harm', + label: 'Harm', + color: 'red', + description: 'Escape chance', + tooltip: (active: boolean) => + active + ? 'Struggling in harm intent might cause escape interactions.' + : 'Harm intent struggle escape chance disabled.', + }, +]; + +export const interactionModes = [ + { value: '0', displayText: 'Disabled' }, + { value: '1', displayText: 'Enabled (Default)' }, + { value: '2', displayText: 'Enabled (Intent)' }, +]; diff --git a/tgui/packages/tgui/interfaces/VorePanel/index.tsx b/tgui/packages/tgui/interfaces/VorePanel/index.tsx index 027c67ba4c..5d1feb8d61 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/index.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/index.tsx @@ -35,6 +35,7 @@ export const VorePanel = () => { show_pictures, icon_overflow, prey_abilities, + intent_data, host_mobtype, unsaved_changes, vore_words, @@ -75,6 +76,7 @@ export const VorePanel = () => { diff --git a/tgui/packages/tgui/interfaces/VorePanel/types.ts b/tgui/packages/tgui/interfaces/VorePanel/types.ts index 880a042ea6..939a2e5a45 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/types.ts +++ b/tgui/packages/tgui/interfaces/VorePanel/types.ts @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import type { BooleanLike } from 'tgui-core/react'; export type Data = { @@ -7,6 +8,7 @@ export type Data = { show_pictures: BooleanLike; icon_overflow: BooleanLike; prey_abilities: PreyAbilityData[] | null; + intent_data: IntentData | null; active_tab: number; persist_edit_mode: BooleanLike; presets: string; @@ -193,7 +195,7 @@ export type BellyVisualData = { }; export type BellyInteractionData = { - escapable: BooleanLike; + escapable: number; interacts: interactData; autotransfer_enabled: BooleanLike; autotransfer: AutotransferData; @@ -483,7 +485,7 @@ export type PreferenceData = { export type ActionButtonData = { name: string; - tooltip: string; + tooltip: ReactNode; disabled?: boolean; color?: string; needsConfirm?: boolean; @@ -499,3 +501,12 @@ export type Overlay = { iconState: string; color?: string; }; + +export type IntentData = { + active: BooleanLike; + current_intent: string; + help: BooleanLike; + disarm: BooleanLike; + grab: BooleanLike; + harm: BooleanLike; +}; diff --git a/tgui/packages/tgui/interfaces/VorePanelExport/VorePanelExportBellyString.tsx b/tgui/packages/tgui/interfaces/VorePanelExport/VorePanelExportBellyString.tsx index 2a2a87b1ca..773eb4628c 100644 --- a/tgui/packages/tgui/interfaces/VorePanelExport/VorePanelExportBellyString.tsx +++ b/tgui/packages/tgui/interfaces/VorePanelExport/VorePanelExportBellyString.tsx @@ -694,10 +694,13 @@ export const generateBellyString = (belly: Belly, index: number) => { result += `
`; result += '
'; result += `Belly Interactions (${ - escapable - ? 'Enabled' - : 'Disabled' + escapable === 1 + ? 'Enabled (Default)' + : escapable === 2 + ? 'Enabled (Intent)' + : 'Disabled' })`; + result += '
    '; const interactionItems = [ diff --git a/tgui/packages/tgui/interfaces/VorePanelExport/types.ts b/tgui/packages/tgui/interfaces/VorePanelExport/types.ts index 8370ff9c76..5efa3590d0 100644 --- a/tgui/packages/tgui/interfaces/VorePanelExport/types.ts +++ b/tgui/packages/tgui/interfaces/VorePanelExport/types.ts @@ -150,7 +150,7 @@ export type Belly = { disable_hud: BooleanLike; // Interactions - escapable: BooleanLike; + escapable: number; escapechance: number; escapechance_absorbed: number; diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json index 513320ff91..aac8f4bb63 100644 --- a/tgui/packages/tgui/package.json +++ b/tgui/packages/tgui/package.json @@ -15,7 +15,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "react-json-tree": "^0.20.0", - "tgui-core": "^5.7.2", + "tgui-core": "^5.8.0", "tgui-dev-server": "workspace:*" }, "private": true diff --git a/tgui/packages/tgui/styles/interfaces/VorePanel.scss b/tgui/packages/tgui/styles/interfaces/VorePanel.scss index 5c10554c19..945d28a7d1 100644 --- a/tgui/packages/tgui/styles/interfaces/VorePanel.scss +++ b/tgui/packages/tgui/styles/interfaces/VorePanel.scss @@ -1,3 +1,5 @@ +@use '../colors'; + .VorePanel__Floating { height: 200px; background-color: var(--color-section); @@ -23,7 +25,44 @@ transition-duration: var(--button-transition); transition-timing-function: var(--button-transition-timing); user-select: none; - background-color: var(--button-background-default); + + --vp-color: currentColor; + --vp-background: var(--button-background-default); + + background-color: var(--vp-background); + color: var(--vp-color); + + &:hover { + background-color: hsl( + from var(--vp-background) h s calc(l + var(--adjust-hover)) + ); + } + + &.color-bg-transparent { + --vp-background: hsl(from var(--vp-color) h s l / 0.2); + background-color: var(--vp-background); + opacity: 1; + } +} + +@each $color-name, $color-value in colors.$color-map { + .VorePanel__floatingButton.color-bg-#{$color-name} { + --vp-background: #{$color-value}; + + @each $low-color in colors.$low-contrast-color-map { + @if $color-name == $low-color { + --vp-color: var(--color-black); + } + } + } +} + +.VorePanel__floatingButton.VorePanel__noCursor { + cursor: default; +} + +.VorePanel__floatingButton.VorePanel__selected { + box-shadow: inset 0 0 0 2px var(--color-white); } .VorePanel__pasteArea { diff --git a/vorestation.dme b/vorestation.dme index 708c3f5f93..73e8ba5e9d 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -5001,10 +5001,10 @@ #include "code\modules\vore\eating\belly_dat_vr.dm" #include "code\modules\vore\eating\belly_import.dm" #include "code\modules\vore\eating\belly_messages.dm" +#include "code\modules\vore\eating\belly_obj.dm" #include "code\modules\vore\eating\belly_obj_liquids.dm" #include "code\modules\vore\eating\belly_obj_resist.dm" #include "code\modules\vore\eating\belly_obj_vfx.dm" -#include "code\modules\vore\eating\belly_obj_vr.dm" #include "code\modules\vore\eating\bellymodes_datum_vr.dm" #include "code\modules\vore\eating\bellymodes_vr.dm" #include "code\modules\vore\eating\contaminate_vr.dm"