mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-01-30 03:05:29 +00:00
-Adds simplemob radio. A "headset" that can be used and listened on by simplemobs. Adminspawn the item, shove it into your mob's contents, and link a reference to the item into the mob's mob_radio var. Additional keys can be added same way as regular headsets before "implanting" into the mob. -Adds leap/pounce verb for simplemobs. Essentially a copy of how the dogborg pounce works. -Couple fixes to borg/animal pounces. (makes the pounce "animation" reset to default_y instead of hardcoded crap. Also disables spin on simplemobs when pouncing. Unfun maybe, but that var can always be toggled ingame to get the sick ninja moves) -Adds adminbus switch to enable abuse mode on trasheater. (only skips the whitelist, blacklist still applies) -Fixes a bunch of icon updates on the vorecode. Humanmobs no longer get forced canary runtiming mob icon updates, and the existing mob icon updates are no longer simplemob exclusive (and thus broken for pAIs and such) -Earlyports a fix for mobs wandering off when grabbed. -Earlyports a fix for adding verbs to mobs via varedit. (Was utterly broken for anything that wasn't STRICTLY a root type of human, robot, or AI. Also now works for simplemobs as well.)
429 lines
17 KiB
Plaintext
429 lines
17 KiB
Plaintext
// Flags for specifying which states we have vore icon_states for.
|
|
#define SA_ICON_LIVING 0x01
|
|
#define SA_ICON_DEAD 0x02
|
|
#define SA_ICON_REST 0x04
|
|
|
|
/mob/living/simple_mob
|
|
base_attack_cooldown = 15
|
|
|
|
var/temperature_range = 40 // How close will they get to environmental temperature before their body stops changing its heat
|
|
|
|
var/vore_active = 0 // If vore behavior is enabled for this mob
|
|
|
|
var/vore_capacity = 1 // The capacity (in people) this person can hold
|
|
var/vore_max_size = RESIZE_HUGE // The max size this mob will consider eating
|
|
var/vore_min_size = RESIZE_TINY // The min size this mob will consider eating
|
|
var/vore_bump_chance = 0 // Chance of trying to eat anyone that bumps into them, regardless of hostility
|
|
var/vore_bump_emote = "grabs hold of" // Allow messages for bumpnom mobs to have a flavorful bumpnom
|
|
var/vore_pounce_chance = 5 // Chance of this mob knocking down an opponent
|
|
var/vore_pounce_cooldown = 0 // Cooldown timer - if it fails a pounce it won't pounce again for a while
|
|
var/vore_pounce_successrate = 100 // Chance of a pounce succeeding against a theoretical 0-health opponent
|
|
var/vore_pounce_falloff = 1 // Success rate falloff per %health of target mob.
|
|
var/vore_pounce_maxhealth = 80 // Mob will not attempt to pounce targets above this %health
|
|
var/vore_standing_too = 0 // Can also eat non-stunned mobs
|
|
var/vore_ignores_undigestable = 1 // Refuse to eat mobs who are undigestable by the prefs toggle.
|
|
var/swallowsound = null // What noise plays when you succeed in eating the mob.
|
|
|
|
var/vore_default_mode = DM_DIGEST // Default bellymode (DM_DIGEST, DM_HOLD, DM_ABSORB)
|
|
var/vore_default_flags = 0 // No flags
|
|
var/vore_digest_chance = 25 // Chance to switch to digest mode if resisted
|
|
var/vore_absorb_chance = 0 // Chance to switch to absorb mode if resisted
|
|
var/vore_escape_chance = 25 // Chance of resisting out of mob
|
|
|
|
var/vore_stomach_name // The name for the first belly if not "stomach"
|
|
var/vore_stomach_flavor // The flavortext for the first belly if not the default
|
|
|
|
var/vore_default_item_mode = IM_DIGEST_FOOD //How belly will interact with items
|
|
var/vore_default_contaminates = TRUE //Will it contaminate?
|
|
var/vore_default_contamination_flavor = "Generic" //Contamination descriptors
|
|
var/vore_default_contamination_color = "green" //Contamination color
|
|
|
|
var/vore_fullness = 0 // How "full" the belly is (controls icons)
|
|
var/vore_icons = 0 // Bitfield for which fields we have vore icons for.
|
|
var/life_disabled = 0 // For performance reasons
|
|
|
|
var/mount_offset_x = 5 // Horizontal riding offset.
|
|
var/mount_offset_y = 8 // Vertical riding offset
|
|
|
|
var/obj/item/device/radio/headset/mob_headset/mob_radio //Adminbus headset for simplemob shenanigans.
|
|
does_spin = FALSE
|
|
|
|
// Release belly contents before being gc'd!
|
|
/mob/living/simple_mob/Destroy()
|
|
release_vore_contents()
|
|
prey_excludes.Cut()
|
|
. = ..()
|
|
|
|
//For all those ID-having mobs
|
|
/mob/living/simple_mob/GetIdCard()
|
|
if(myid)
|
|
return myid
|
|
|
|
// Update fullness based on size & quantity of belly contents
|
|
/mob/living/simple_mob/proc/update_fullness()
|
|
var/new_fullness = 0
|
|
for(var/belly in vore_organs)
|
|
var/obj/belly/B = belly
|
|
for(var/mob/living/M in B)
|
|
new_fullness += M.size_multiplier
|
|
new_fullness = round(new_fullness, 1) // Because intervals of 0.25 are going to make sprite artists cry.
|
|
vore_fullness = min(vore_capacity, new_fullness)
|
|
|
|
/mob/living/simple_mob/update_icon()
|
|
. = ..()
|
|
if(vore_active)
|
|
update_fullness()
|
|
if(!vore_fullness)
|
|
return 0
|
|
else if((stat == CONSCIOUS) && (!icon_rest || !resting || !incapacitated(INCAPACITATION_DISABLED)) && (vore_icons & SA_ICON_LIVING))
|
|
icon_state = "[icon_living]-[vore_fullness]"
|
|
else if(stat >= DEAD && (vore_icons & SA_ICON_DEAD))
|
|
icon_state = "[icon_dead]-[vore_fullness]"
|
|
else if(((stat == UNCONSCIOUS) || resting || incapacitated(INCAPACITATION_DISABLED) ) && icon_rest && (vore_icons & SA_ICON_REST))
|
|
icon_state = "[icon_rest]-[vore_fullness]"
|
|
|
|
/mob/living/simple_mob/proc/will_eat(var/mob/living/M)
|
|
if(client) //You do this yourself, dick!
|
|
//ai_log("vr/wont eat [M] because we're player-controlled", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(!istype(M)) //Can't eat 'em if they ain't /mob/living
|
|
//ai_log("vr/wont eat [M] because they are not /mob/living", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(src == M) //Don't eat YOURSELF dork
|
|
//ai_log("vr/won't eat [M] because it's me!", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(vore_ignores_undigestable && !M.digestable) //Don't eat people with nogurgle prefs
|
|
//ai_log("vr/wont eat [M] because I am picky", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(!M.allowmobvore) // Don't eat people who don't want to be ate by mobs
|
|
//ai_log("vr/wont eat [M] because they don't allow mob vore", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(M in prey_excludes) // They're excluded
|
|
//ai_log("vr/wont eat [M] because they are excluded", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(M.size_multiplier < vore_min_size || M.size_multiplier > vore_max_size)
|
|
//ai_log("vr/wont eat [M] because they too small or too big", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
if(vore_capacity != 0 && (vore_fullness >= vore_capacity)) // We're too full to fit them
|
|
//ai_log("vr/wont eat [M] because I am too full", 3) //VORESTATION AI TEMPORARY REMOVAL
|
|
return 0
|
|
return 1
|
|
|
|
/mob/living/simple_mob/apply_attack(atom/A, damage_to_do)
|
|
if(isliving(A)) // Converts target to living
|
|
var/mob/living/L = A
|
|
|
|
//ai_log("vr/do_attack() [L]", 3)
|
|
// If we're not hungry, call the sideways "parent" to do normal punching
|
|
if(!vore_active)
|
|
return ..()
|
|
|
|
// If target is standing we might pounce and knock them down instead of attacking
|
|
var/pouncechance = CanPounceTarget(L)
|
|
if(pouncechance)
|
|
return PounceTarget(L, pouncechance)
|
|
|
|
// We're not attempting a pounce, if they're down or we can eat standing, do it as long as they're edible. Otherwise, hit normally.
|
|
if(will_eat(L) && (!L.canmove || vore_standing_too))
|
|
return EatTarget(L)
|
|
else
|
|
return ..()
|
|
else
|
|
return ..()
|
|
|
|
|
|
/mob/living/simple_mob/proc/CanPounceTarget(var/mob/living/M) //returns either FALSE or a %chance of success
|
|
if(!M.canmove || issilicon(M) || world.time < vore_pounce_cooldown) //eliminate situations where pouncing CANNOT happen
|
|
return FALSE
|
|
if(!prob(vore_pounce_chance) || !will_eat(M)) //mob doesn't want to pounce
|
|
return FALSE
|
|
if(vore_standing_too) //100% chance of hitting people we can eat on the spot
|
|
return 100
|
|
var/TargetHealthPercent = (M.health/M.getMaxHealth())*100 //now we start looking at the target itself
|
|
if (TargetHealthPercent > vore_pounce_maxhealth) //target is too healthy to pounce
|
|
return FALSE
|
|
else
|
|
return max(0,(vore_pounce_successrate - (vore_pounce_falloff * TargetHealthPercent)))
|
|
|
|
|
|
/mob/living/simple_mob/proc/PounceTarget(var/mob/living/M, var/successrate = 100)
|
|
vore_pounce_cooldown = world.time + 20 SECONDS // don't attempt another pounce for a while
|
|
if(prob(successrate)) // pounce success!
|
|
M.Weaken(5)
|
|
M.visible_message("<span class='danger'>\the [src] pounces on \the [M]!</span>!")
|
|
else // pounce misses!
|
|
M.visible_message("<span class='danger'>\the [src] attempts to pounce \the [M] but misses!</span>!")
|
|
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
|
|
|
if(will_eat(M) && (!M.canmove || vore_standing_too)) //if they're edible then eat them too
|
|
return EatTarget(M)
|
|
else
|
|
return //just leave them
|
|
|
|
// Attempt to eat target
|
|
// TODO - Review this. Could be some issues here
|
|
/mob/living/simple_mob/proc/EatTarget(var/mob/living/M)
|
|
//ai_log("vr/EatTarget() [M]",2) //VORESTATION AI TEMPORARY REMOVAL
|
|
//stop_automated_movement = 1 //VORESTATION AI TEMPORARY REMOVAL
|
|
var/old_target = M
|
|
set_AI_busy(1) //VORESTATION AI TEMPORARY EDIT
|
|
. = animal_nom(M)
|
|
playsound(src, swallowsound, 50, 1)
|
|
update_icon()
|
|
|
|
if(.)
|
|
// If we succesfully ate them, lose the target
|
|
set_AI_busy(0) // lose_target(M) //Unsure what to put here. Replaced with set_AI_busy(1) //VORESTATION AI TEMPORARY EDIT
|
|
return old_target
|
|
else if(old_target == M)
|
|
// If we didn't but they are still our target, go back to attack.
|
|
// but don't run the handler immediately, wait until next tick
|
|
// Otherwise we'll be in a possibly infinate loop
|
|
set_AI_busy(0) //VORESTATION AI TEMPORARY EDIT
|
|
//stop_automated_movement = 0 //VORESTATION AI TEMPORARY EDIT
|
|
|
|
/mob/living/simple_mob/death()
|
|
release_vore_contents()
|
|
. = ..()
|
|
|
|
// Make sure you don't call ..() on this one, otherwise you duplicate work.
|
|
/mob/living/simple_mob/init_vore()
|
|
if(!vore_active || no_vore)
|
|
return
|
|
|
|
if(!IsAdvancedToolUser())
|
|
verbs |= /mob/living/simple_mob/proc/animal_nom
|
|
verbs |= /mob/living/proc/shred_limb
|
|
|
|
if(LAZYLEN(vore_organs))
|
|
return
|
|
|
|
//A much more detailed version of the default /living implementation
|
|
var/obj/belly/B = new /obj/belly(src)
|
|
vore_selected = B
|
|
B.immutable = 1
|
|
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.mode_flags = vore_default_flags
|
|
B.item_digest_mode = vore_default_item_mode
|
|
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.escapechance = vore_escape_chance
|
|
B.digestchance = vore_digest_chance
|
|
B.absorbchance = vore_absorb_chance
|
|
B.human_prey_swallow_time = swallowTime
|
|
B.nonhuman_prey_swallow_time = swallowTime
|
|
B.vore_verb = "swallow"
|
|
B.emote_lists[DM_HOLD] = list( // We need more that aren't repetitive. I suck at endo. -Ace
|
|
"The insides knead at you gently for a moment.",
|
|
"The guts glorp wetly around you as some air shifts.",
|
|
"The predator takes a deep breath and sighs, shifting you somewhat.",
|
|
"The stomach squeezes you tight for a moment, then relaxes harmlessly.",
|
|
"The predator's calm breathing and thumping heartbeat pulses around you.",
|
|
"The warm walls kneads harmlessly against you.",
|
|
"The liquids churn around you, though there doesn't seem to be much effect.",
|
|
"The sound of bodily movements drown out everything for a moment.",
|
|
"The predator's movements gently force you into a different position.")
|
|
B.emote_lists[DM_DIGEST] = list(
|
|
"The burning acids eat away at your form.",
|
|
"The muscular stomach flesh grinds harshly against you.",
|
|
"The caustic air stings your chest when you try to breathe.",
|
|
"The slimy guts squeeze inward to help the digestive juices soften you up.",
|
|
"The onslaught against your body doesn't seem to be letting up; you're food now.",
|
|
"The predator's body ripples and crushes against you as digestive enzymes pull you apart.",
|
|
"The juices pooling beneath you sizzle against your sore skin.",
|
|
"The churning walls slowly pulverize you into meaty nutrients.",
|
|
"The stomach glorps and gurgles as it tries to work you into slop.")
|
|
|
|
/mob/living/simple_mob/Bumped(var/atom/movable/AM, yes)
|
|
if(ismob(AM))
|
|
var/mob/tmob = AM
|
|
if(will_eat(tmob) && !istype(tmob, type) && prob(vore_bump_chance) && !ckey) //check if they decide to eat. Includes sanity check to prevent cannibalism.
|
|
if(tmob.canmove && prob(vore_pounce_chance)) //if they'd pounce for other noms, pounce for these too, otherwise still try and eat them if they hold still
|
|
tmob.Weaken(5)
|
|
tmob.visible_message("<span class='danger'>\the [src] [vore_bump_emote] \the [tmob]!</span>!")
|
|
set_AI_busy(TRUE)
|
|
animal_nom(tmob)
|
|
update_icon()
|
|
set_AI_busy(FALSE)
|
|
..()
|
|
|
|
// Checks to see if mob doesn't like this kind of turf
|
|
/mob/living/simple_mob/IMove(newloc)
|
|
if(istype(newloc,/turf/unsimulated/floor/sky))
|
|
return MOVEMENT_FAILED //Mobs aren't that stupid, probably
|
|
return ..() // Procede as normal.
|
|
|
|
//Grab = Nomf
|
|
/mob/living/simple_mob/UnarmedAttack(var/atom/A, var/proximity)
|
|
. = ..()
|
|
|
|
if(a_intent == I_GRAB && isliving(A) && !has_hands)
|
|
animal_nom(A)
|
|
|
|
// Riding
|
|
/datum/riding/simple_mob
|
|
keytype = /obj/item/weapon/material/twohanded/fluff/riding_crop // Crack!
|
|
nonhuman_key_exemption = FALSE // If true, nonhumans who can't hold keys don't need them, like borgs and simplemobs.
|
|
key_name = "a riding crop" // What the 'keys' for the thing being rided on would be called.
|
|
only_one_driver = TRUE // If true, only the person in 'front' (first on list of riding mobs) can drive.
|
|
|
|
/datum/riding/simple_mob/handle_vehicle_layer()
|
|
ridden.layer = initial(ridden.layer)
|
|
|
|
/datum/riding/simple_mob/ride_check(mob/living/M)
|
|
var/mob/living/L = ridden
|
|
if(L.stat)
|
|
force_dismount(M)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/datum/riding/simple_mob/force_dismount(mob/M)
|
|
. =..()
|
|
ridden.visible_message("<span class='notice'>[M] stops riding [ridden]!</span>")
|
|
|
|
/datum/riding/simple_mob/get_offsets(pass_index) // list(dir = x, y, layer)
|
|
var/mob/living/simple_mob/L = ridden
|
|
var/scale = L.size_multiplier
|
|
|
|
var/list/values = list(
|
|
"[NORTH]" = list(0, L.mount_offset_y*scale, ABOVE_MOB_LAYER),
|
|
"[SOUTH]" = list(0, L.mount_offset_y*scale, BELOW_MOB_LAYER),
|
|
"[EAST]" = list(-L.mount_offset_x*scale, L.mount_offset_y*scale, ABOVE_MOB_LAYER),
|
|
"[WEST]" = list(L.mount_offset_x*scale, L.mount_offset_y*scale, ABOVE_MOB_LAYER))
|
|
|
|
return values
|
|
|
|
/mob/living/simple_mob/buckle_mob(mob/living/M, forced = FALSE, check_loc = TRUE)
|
|
if(forced)
|
|
return ..() // Skip our checks
|
|
if(!riding_datum)
|
|
return FALSE
|
|
if(lying)
|
|
return FALSE
|
|
if(!ishuman(M))
|
|
return FALSE
|
|
if(M in buckled_mobs)
|
|
return FALSE
|
|
if(M.size_multiplier > size_multiplier * 1.2)
|
|
to_chat(src,"<span class='warning'>This isn't a pony show! You need to be bigger for them to ride.</span>")
|
|
return FALSE
|
|
|
|
var/mob/living/carbon/human/H = M
|
|
|
|
if(H.loc != src.loc)
|
|
if(H.Adjacent(src))
|
|
H.forceMove(get_turf(src))
|
|
|
|
. = ..()
|
|
if(.)
|
|
buckled_mobs[H] = "riding"
|
|
|
|
/mob/living/simple_mob/attack_hand(mob/user as mob)
|
|
if(riding_datum && LAZYLEN(buckled_mobs))
|
|
//We're getting off!
|
|
if(user in buckled_mobs)
|
|
riding_datum.force_dismount(user)
|
|
//We're kicking everyone off!
|
|
if(user == src)
|
|
for(var/rider in buckled_mobs)
|
|
riding_datum.force_dismount(rider)
|
|
else
|
|
. = ..()
|
|
|
|
/mob/living/simple_mob/proc/animal_mount(var/mob/living/M in living_mobs(1))
|
|
set name = "Animal Mount/Dismount"
|
|
set category = "Abilities"
|
|
set desc = "Let people ride on you."
|
|
|
|
if(LAZYLEN(buckled_mobs))
|
|
for(var/rider in buckled_mobs)
|
|
riding_datum.force_dismount(rider)
|
|
return
|
|
if (stat != CONSCIOUS)
|
|
return
|
|
if(!can_buckle || !istype(M) || !M.Adjacent(src) || M.buckled)
|
|
return
|
|
if(buckle_mob(M))
|
|
visible_message("<span class='notice'>[M] starts riding [name]!</span>")
|
|
|
|
/mob/living/simple_mob/handle_message_mode(message_mode, message, verb, speaking, used_radios, alt_name)
|
|
if(mob_radio)
|
|
switch(message_mode)
|
|
if("intercom")
|
|
for(var/obj/item/device/radio/intercom/I in view(1, null))
|
|
I.talk_into(src, message, verb, speaking)
|
|
used_radios += I
|
|
if("headset")
|
|
if(mob_radio && istype(mob_radio,/obj/item/device/radio/headset/mob_headset))
|
|
mob_radio.talk_into(src,message,null,verb,speaking)
|
|
used_radios += mob_radio
|
|
else
|
|
if(message_mode)
|
|
if(mob_radio && istype(mob_radio,/obj/item/device/radio/headset/mob_headset))
|
|
mob_radio.talk_into(src,message, message_mode, verb, speaking)
|
|
used_radios += mob_radio
|
|
else
|
|
..()
|
|
|
|
/mob/living/simple_mob/proc/leap()
|
|
set name = "Pounce Target"
|
|
set category = "Abilities"
|
|
set desc = "Select a target to pounce at."
|
|
|
|
if(last_special > world.time)
|
|
to_chat(src, "Your legs need some more rest.")
|
|
return
|
|
|
|
if(incapacitated(INCAPACITATION_DISABLED))
|
|
to_chat(src, "You cannot leap in your current state.")
|
|
return
|
|
|
|
var/list/choices = list()
|
|
for(var/mob/living/M in view(3,src))
|
|
choices += M
|
|
choices -= src
|
|
|
|
var/mob/living/T = input(src,"Who do you wish to leap at?") as null|anything in choices
|
|
|
|
if(!T || !src || src.stat) return
|
|
|
|
if(get_dist(get_turf(T), get_turf(src)) > 3) return
|
|
|
|
if(last_special > world.time)
|
|
return
|
|
|
|
if(usr.incapacitated(INCAPACITATION_DISABLED))
|
|
to_chat(src, "You cannot leap in your current state.")
|
|
return
|
|
|
|
last_special = world.time + 10
|
|
status_flags |= LEAPING
|
|
pixel_y = pixel_y + 10
|
|
|
|
src.visible_message("<span class='danger'>\The [src] leaps at [T]!</span>")
|
|
src.throw_at(get_step(get_turf(T),get_turf(src)), 4, 1, src)
|
|
playsound(src.loc, 'sound/effects/bodyfall1.ogg', 50, 1)
|
|
pixel_y = default_pixel_y
|
|
|
|
sleep(5)
|
|
|
|
if(status_flags & LEAPING) status_flags &= ~LEAPING
|
|
|
|
if(!src.Adjacent(T))
|
|
to_chat(src, "<span class='warning'>You miss!</span>")
|
|
return
|
|
|
|
if(ishuman(T))
|
|
var/mob/living/carbon/human/H = T
|
|
if(H.species.lightweight == 1)
|
|
H.Weaken(3)
|
|
return
|
|
var/armor_block = run_armor_check(T, "melee")
|
|
var/armor_soak = get_armor_soak(T, "melee")
|
|
T.apply_damage(20, HALLOSS,, armor_block, armor_soak)
|
|
if(prob(33))
|
|
T.apply_effect(3, WEAKEN, armor_block) |