Files
CHOMPStation2/code/modules/mob/living/simple_mob/simple_mob_vr.dm
Verkister e28722b64d A bunch of little fixes and event features.
-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.)
2019-10-31 14:27:18 +02:00

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)