mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-08-25 22:28:44 +01:00
[MIRROR] Intent based struggles (#12242)
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
co-authored by
Kashargul
parent
a7ce5cc712
commit
97e8bcf6e1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -413,7 +413,6 @@
|
||||
active_armourpen = 25
|
||||
projectile_parry_chance = 40
|
||||
colorable = TRUE
|
||||
item_flags = DROPDEL | NOSTRIP
|
||||
|
||||
hitcost = 75
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"])
|
||||
|
||||
+9
-9
@@ -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"],
|
||||
|
||||
|
||||
+2
-2
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<HTMLDivElement>) => void;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -55,7 +55,9 @@ export const CanvasBackedImage = (props: {
|
||||
};
|
||||
}, [props.render]);
|
||||
|
||||
return bitmap ? <img src={bitmap} width={64} height={64} /> : null;
|
||||
return bitmap ? (
|
||||
<img src={bitmap} width={64} height={64} draggable={false} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const drawColorizedIconToOffscreenCanvas = async (
|
||||
|
||||
@@ -70,6 +70,7 @@ export const CanvasBackedImage = (props: {
|
||||
style={{
|
||||
visibility: bitmap && !loadFailed ? 'visible' : 'hidden',
|
||||
}}
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content="Adjust how long lights flicker when you phase in! (Min 10 Max 20 times!)">
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip="Adjust how long lights flicker when you phase in! (Min 10 Max 20 times!)"
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
@@ -120,9 +120,10 @@ export const ShadekinConfig = (props) => {
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content="Adjust the % chance for lights to break when you phase in! (Default 0. Min 0. Max 25)">
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip="Adjust the % chance for lights to break when you phase in! (Default 0. Min 0. Max 25)"
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
@@ -141,9 +142,10 @@ export const ShadekinConfig = (props) => {
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content="Adjust the range in which lights flicker when you phase in! (Default 4. Min 4. Max 10)">
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip="Adjust the range in which lights flicker when you phase in! (Default 4. Min 4. Max 10)"
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
|
||||
@@ -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 (
|
||||
<Window title={title} width={325} height={windowHeight}>
|
||||
{timeout && <Loader value={timeout} />}
|
||||
@@ -94,7 +99,7 @@ export const TextInputModal = (props) => {
|
||||
<Stack.Item>
|
||||
<InputButtons
|
||||
input={input}
|
||||
message={`${input.length}/${max_length || '∞'}`}
|
||||
message={`${char_length}/${max_length || '∞'}`}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Overlay } from '../types';
|
||||
|
||||
const imageCache = new Map<string, Promise<HTMLImageElement | null>>();
|
||||
|
||||
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<string>('');
|
||||
|
||||
const [src, setSrc] = useState<string | null>(null);
|
||||
const blobRef = useRef<string>('');
|
||||
const lastOverlayKeyRef = useRef<string>('');
|
||||
|
||||
const render = useCallback(
|
||||
async (canvas: OffscreenCanvas, ctx: OffscreenCanvasRenderingContext2D) => {
|
||||
ctx.clearRect(0, 0, targetSize, targetSize);
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
const mainCanvasRef = useRef<OffscreenCanvas | null>(null);
|
||||
const mainCtxRef = useRef<OffscreenCanvasRenderingContext2D | null>(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<OffscreenCanvas | null>(null);
|
||||
const tempCtxRef = useRef<OffscreenCanvasRenderingContext2D | null>(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;
|
||||
};
|
||||
|
||||
+9
-6
@@ -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<string, string>;
|
||||
/** Our displayed tooltip behind the input element */
|
||||
tooltip: string;
|
||||
tooltip: ReactNode;
|
||||
/** The position of the tooltip if static */
|
||||
tooltipPosition: ComponentProps<typeof Floating>['placement'];
|
||||
}>,
|
||||
@@ -68,9 +69,11 @@ export const VorePanelEditCheckboxes = (
|
||||
</Floating>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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<typeof Floating>['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}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
)}
|
||||
</Stack.Item>
|
||||
)}
|
||||
|
||||
+21
-15
@@ -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<typeof Floating>['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 ? (
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Dropdown
|
||||
fluid
|
||||
color={color}
|
||||
onSelected={(value) =>
|
||||
act(action, { attribute: subAction, val: value })
|
||||
}
|
||||
onSelected={saneAct}
|
||||
options={options}
|
||||
selected={entry}
|
||||
icon={icon}
|
||||
@@ -63,9 +67,11 @@ export const VorePanelEditDropdown = (
|
||||
</Stack.Item>
|
||||
{tooltip && (
|
||||
<Stack.Item>
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -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<typeof Floating>['placement'];
|
||||
/** The amount of fractional digits shown */
|
||||
@@ -79,9 +74,11 @@ export const VorePanelEditNumber = (
|
||||
</Stack.Item>
|
||||
{tooltip && (
|
||||
<Stack.Item>
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -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<typeof Floating>['placement'];
|
||||
/** Color of the button and displayed text */
|
||||
|
||||
@@ -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<typeof Floating>['placement'];
|
||||
}>,
|
||||
@@ -63,9 +58,11 @@ export const VorePanelEditText = (
|
||||
</Stack.Item>
|
||||
{tooltip && (
|
||||
<Stack.Item>
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box className="VorePanel__floatingButton">?</Box>
|
||||
</Tooltip>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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<typeof Floating>['placement'];
|
||||
/** Color of the button */
|
||||
color: string;
|
||||
/** Event based selection, usually not used */
|
||||
selected: boolean;
|
||||
}>,
|
||||
) => {
|
||||
const { tooltip, tooltipPosition, displayText, color, selected } = props;
|
||||
|
||||
return (
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
<Box
|
||||
backgroundColor={color}
|
||||
className={`VorePanel__floatingButton VorePanel__noCursor${selected ? ' VorePanel__selected' : ''}`}
|
||||
>
|
||||
{displayText}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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<string, string>;
|
||||
/** Disable our special highlighting used on belly messages */
|
||||
|
||||
+22
@@ -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 }) => (
|
||||
<Stack.Item key={key}>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip(!!intent_data[key])}
|
||||
displayText={label}
|
||||
color={intent_data[key] ? color : 'transparent'}
|
||||
selected={intent_data.current_intent === key}
|
||||
/>
|
||||
</Stack.Item>
|
||||
))
|
||||
);
|
||||
};
|
||||
@@ -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<React.SetStateAction<string>>;
|
||||
@@ -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={
|
||||
<Stack>
|
||||
{!!intent_data && <VoreContentIntent intent_data={intent_data} />}
|
||||
<Stack.Item>
|
||||
<Input
|
||||
width="200px"
|
||||
|
||||
+5
-2
@@ -2,18 +2,20 @@ import { useBackend } from 'tgui/backend';
|
||||
import { Button, Section, Stack } from 'tgui-core/components';
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
import { preyAbilityToData } from '../constants';
|
||||
import type { InsideData, PreyAbilityData } from '../types';
|
||||
import type { InsideData, IntentData, PreyAbilityData } from '../types';
|
||||
import { VoreContentsPanel } from './VoreContentsPanel';
|
||||
|
||||
export const VoreContentsPreyPanel = (props: {
|
||||
inside: InsideData;
|
||||
prey_abilities: PreyAbilityData[] | null;
|
||||
intent_data: IntentData | null;
|
||||
show_pictures: BooleanLike;
|
||||
icon_overflow: BooleanLike;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
|
||||
const { inside, prey_abilities, show_pictures, icon_overflow } = props;
|
||||
const { inside, prey_abilities, intent_data, show_pictures, icon_overflow } =
|
||||
props;
|
||||
|
||||
return (
|
||||
<Section fill>
|
||||
@@ -46,6 +48,7 @@ export const VoreContentsPreyPanel = (props: {
|
||||
<Stack.Item grow>
|
||||
<VoreContentsPanel
|
||||
contents={inside.contents}
|
||||
intent_data={intent_data}
|
||||
belly={inside.ref}
|
||||
show_pictures={show_pictures}
|
||||
icon_overflow={icon_overflow}
|
||||
|
||||
+38
-8
@@ -1,6 +1,10 @@
|
||||
import { LabeledList, Section, Stack } from 'tgui-core/components';
|
||||
import { Box, LabeledList, Section, Stack } from 'tgui-core/components';
|
||||
|
||||
import { noSelectionName } from '../constants';
|
||||
import {
|
||||
intentMappings,
|
||||
interactionModes,
|
||||
noSelectionName,
|
||||
} from '../constants';
|
||||
import type { BellyInteractionData, DropdownEntry } from '../types';
|
||||
import { VorePanelEditDropdown } from '../VorePanelElements/VorePanelEditDropdown';
|
||||
import { VorePanelEditNumber } from '../VorePanelElements/VorePanelEditNumber';
|
||||
@@ -27,15 +31,41 @@ export const VoreSelectedBellyInteractions = (props: {
|
||||
<Section
|
||||
title="Belly Interactions"
|
||||
buttons={
|
||||
<VorePanelEditSwitch
|
||||
<VorePanelEditDropdown
|
||||
icon={escapable ? 'toggle-on' : 'toggle-off'}
|
||||
editMode={editMode}
|
||||
action="set_attribute"
|
||||
subAction="b_escapable"
|
||||
editMode={editMode}
|
||||
active={!!escapable}
|
||||
entry={interactionModes[escapable].displayText ?? 'Disabled'}
|
||||
options={interactionModes}
|
||||
color={escapable ? 'green' : editMode ? undefined : 'red'}
|
||||
tooltip={
|
||||
"These control how your belly responds to someone using 'resist' while inside you. 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 one resists and triggers digestion/absorption. If multiple trigger at the same time, only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur."
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
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.
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box bold>Intent mappings:</Box>
|
||||
{intentMappings.map(({ label, color, description }) => (
|
||||
<Box key={label}>
|
||||
<Box inline color={color}>
|
||||
{label}
|
||||
</Box>
|
||||
: {description}
|
||||
</Box>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -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)' },
|
||||
];
|
||||
|
||||
@@ -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 = () => {
|
||||
<VoreContentsPreyPanel
|
||||
inside={inside}
|
||||
prey_abilities={prey_abilities}
|
||||
intent_data={intent_data}
|
||||
show_pictures={show_pictures}
|
||||
icon_overflow={icon_overflow}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -694,10 +694,13 @@ export const generateBellyString = (belly: Belly, index: number) => {
|
||||
result += `<div id="settingsAccordion${index}-collapseThree" class="accordion-collapse collapse" aria-labelledby="settingsAccordion${index}-headingThree">`;
|
||||
result += '<div class="accordion-body">';
|
||||
result += `<b>Belly Interactions (${
|
||||
escapable
|
||||
? '<span style="color: green;">Enabled'
|
||||
: '<span style="color: red;">Disabled'
|
||||
escapable === 1
|
||||
? '<span style="color: green;">Enabled (Default)'
|
||||
: escapable === 2
|
||||
? '<span style="color: green;">Enabled (Intent)'
|
||||
: '<span style="color: red;">Disabled'
|
||||
}</span>)</b>`;
|
||||
|
||||
result += '<ul class="list-group">';
|
||||
|
||||
const interactionItems = [
|
||||
|
||||
@@ -150,7 +150,7 @@ export type Belly = {
|
||||
disable_hud: BooleanLike;
|
||||
|
||||
// Interactions
|
||||
escapable: BooleanLike;
|
||||
escapable: number;
|
||||
|
||||
escapechance: number;
|
||||
escapechance_absorbed: number;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user