diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm index 14e20ffb13..7697ca72e7 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm @@ -96,3 +96,9 @@ //VOREStation edit ends /obj/effect/projectile/impact/pointdefense icon_state = "impact_pointdef" + +/obj/effect/projectile/impact/rainbow + icon_state = "impact_rainbow" + light_range = 3 + light_power = 1 + light_color = "#ffffff" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm index 0eeeed0f94..16807572d5 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm @@ -108,3 +108,9 @@ //VOREStation edit ends /obj/effect/projectile/muzzle/pointdefense icon_state = "muzzle_pointdef" + +/obj/effect/projectile/muzzle/rainbow + icon_state = "muzzle_rainbow" + light_range = 3 + light_power = 1 + light_color = "#ffffff" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index cc14471b09..f644106c3c 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -135,3 +135,9 @@ //VOREStation edit ends /obj/effect/projectile/tracer/pointdefense icon_state = "beam_pointdef" + +/obj/effect/projectile/tracer/rainbow + icon_state = "rainbow" + light_range = 3 + light_power = 1 + light_color = "#ffffff" diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/ddraig.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/ddraig.dm new file mode 100644 index 0000000000..50881eb2bc --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/glamour/ddraig.dm @@ -0,0 +1,441 @@ +/mob/living/simple_mob/vore/ddraig + name = "ddraig" + desc = "A massive drake-like creature with dark purple scales and a seemingly exposed skull." + tt_desc = "Draconis glamoris" + icon = 'icons/mob/vore96x96.dmi' + icon_dead = "ddraig-dead" + icon_living = "ddraig" + icon_state = "ddraig" + icon_rest = "ddraig_rest" + faction = FACTION_DRAGON + old_x = -32 + old_y = 0 + vis_height = 92 + melee_damage_lower = 20 + melee_damage_upper = 15 + friendly = list("nudges", "sniffs on", "rumbles softly at", "nuzzles") + default_pixel_x = -32 + pixel_x = -32 + pixel_y = 0 + response_help = "bumps" + response_disarm = "shoves" + response_harm = "bites" + movement_cooldown = -1 + harm_intent_damage = 10 + melee_damage_lower = 15 + melee_damage_upper = 25 + maxHealth = 1000 + attacktext = list("mauled") + see_in_dark = 8 + minbodytemp = 0 + ai_holder_type = /datum/ai_holder/simple_mob/vore/ddraig + max_buckled_mobs = 1 + mount_offset_y = 32 + can_buckle = TRUE + buckle_movable = TRUE + buckle_lying = FALSE + minbodytemp = 0 + + var/flames + var/firebreathtimer + + special_attack_min_range = 2 + special_attack_max_range = 6 + special_attack_cooldown = 30 SECONDS + + var/leap_warmup = 2 SECOND // How long the leap telegraphing is. + var/leap_sound = 'sound/weapons/spiderlunge.ogg' + +/mob/living/simple_mob/vore/ddraig + + vore_bump_chance = 25 + vore_digest_chance = 50 + vore_escape_chance = 5 + vore_pounce_chance = 100 + vore_active = 1 + vore_icons = 3 + vore_icons = SA_ICON_LIVING | SA_ICON_REST + vore_capacity = 3 + swallowTime = 50 + vore_ignores_undigestable = TRUE + vore_default_mode = DM_DIGEST + vore_pounce_maxhealth = 125 + vore_bump_emote = "tries to devour" + +/mob/living/simple_mob/vore/ddraig/Login() + . = ..() + if(!riding_datum) + riding_datum = new /datum/riding/simple_mob(src) + verbs |= /mob/living/simple_mob/proc/animal_mount + verbs |= /mob/living/proc/toggle_rider_reins + verbs |= /mob/living/proc/set_size + verbs |= /mob/living/proc/polymorph + verbs |= /mob/living/proc/invisibility + movement_cooldown = -1 + +/mob/living/simple_mob/vore/ddraig/init_vore() + . = ..() + var/obj/belly/B = vore_selected + B.name = "stomach" + B.desc = "The enormous beast snaps it's boney jaws around your form, effortlessly lifting you from the ground. Throwing it's head backwards, your body is tossed up momentarily as the maw parts wider, only for you to descend rapidly moments later. You're caught in the creature's throat for a moment, contracting dark purple flesh holding tightly onto you, there's no going back at this point. The peristaltic motions squeeze you down this tunnel towards your final destination, where you're soon relieved of the intense squelching to be pushed into a move flexible, stretching chamber. Immediately coated in caustic oozes, the world around you seems more than eager to ensure that you're soaked over every inch, wrinkled walls twisting and grinding around your body. The drake's stomach clenches and compresses over you rhythmically, attempting to eagerly add you to the soup of fluids that fill this sloshing gut. Omnipresent sounds of groaning, gurgling and burbling bodily functions signify just how active this process already is." + B.vore_sound = "Tauric Swallow" + B.release_sound = "Pred Escape" + B.mode_flags = DM_FLAG_THICKBELLY + B.fancy_vore = 1 + B.selective_preference = DM_DIGEST + B.vore_verb = "devour" + B.digest_brute = 3 + B.digest_burn = 2 + B.digest_oxy = 0 + B.digestchance = 50 + B.absorbchance = 0 + B.escapechance = 3 + B.escape_stun = 5 + B.contamination_color = "grey" + B.contamination_flavor = "Wet" + B.emote_lists[DM_DIGEST] = list( + "The drake growls in annoyance before clenching those wrinkled walls tight against your form, grinding away at you!", + "As the beast wanders about, you're forced to slip and slide around amidst a pool of thick digestive goop, sinking briefly into the thick, heavy walls!", + "You can barely hear the drake let out a pleased rumble as its stomach eagerly gurgles around its newfound meal!", + "As the thinning air begins to make you feel dizzy, menacing bworps and grumbles fill that dark, constantly shifting organ!", + "The constant, rhythmic kneading and massaging starts to take its toll along with the muggy heat, making you feel weaker and weaker!", + "The drake happily wanders around while digesting its meal, almost like it is trying to show off the hanging gut you've given it. Not like it made much of a difference on his already borderline obese form anyway~") + +/mob/living/simple_mob/vore/ddraig/do_special_attack(atom/A) + . = TRUE + if(ckey) + return + var/specialattack = rand(1,3) + if(rand == 1) + lunge(A) + if(rand == 2) + firebreathstart(A) + if(rand == 3) + tfbeam(A) + +/mob/living/simple_mob/vore/ddraig/proc/lunge(atom/A) //Mostly copied from hunter.dm + set waitfor = FALSE + if(!isliving(A)) + return FALSE + var/mob/living/L = A + if(!L.devourable || !L.allowmobvore || !L.can_be_drop_prey || !L.throw_vore || L.unacidable) + return FALSE + + set_AI_busy(TRUE) + visible_message(span_warning("\The [src] rears back, ready to lunge!")) + to_chat(L, span_danger("\The [src] focuses on you!")) + // Telegraph, since getting stunned suddenly feels bad. + do_windup_animation(A, leap_warmup) + sleep(leap_warmup) // For the telegraphing. + + if(L.z != z) //Make sure you haven't disappeared to somewhere we can't go + set_AI_busy(FALSE) + return FALSE + + // Do the actual leap. + status_flags |= LEAPING // Lets us pass over everything. + visible_message(span_critical("\The [src] leaps at \the [L]!")) + throw_at(get_step(L, get_turf(src)), special_attack_max_range+1, 1, src) + playsound(src, leap_sound, 75, 1) + + sleep(5) // For the throw to complete. It won't hold up the AI ticker due to waitfor being false. + + if(status_flags & LEAPING) + status_flags &= ~LEAPING // Revert special passage ability. + + set_AI_busy(FALSE) + if(Adjacent(L)) //We leapt at them but we didn't manage to hit them, let's see if we're next to them + L.Weaken(2) //get knocked down, idiot + +/mob/living/simple_mob/vore/ddraig/proc/firebreathstart(var/atom/A) //Borrowed from le big dragon + glow_toggle = 1 + set_light(glow_range, glow_intensity, glow_color) //Setting it here so the light starts immediately + if(!enraged) + set_AI_busy(TRUE) + flames = 1 + build_icons() + firebreathtimer = addtimer(CALLBACK(src, PROC_REF(firebreathend), A), charge_warmup, TIMER_STOPPABLE) + playsound(src, "sound/magic/Fireball.ogg", 50, 1) + +/mob/living/simple_mob/vore/ddraig/proc/firebreathend(var/atom/A) + //make sure our target still exists and is on a turf + if(QDELETED(A) || !isturf(get_turf(A))) + set_AI_busy(FALSE) + return + var/obj/item/projectile/P = new /obj/item/projectile/bullet/dragon(get_turf(src)) + src.visible_message(span_danger("\The [src] spews fire at \the [A]!")) + playsound(src, "sound/weapons/Flamer.ogg", 50, 1) + P.launch_projectile(A, BP_TORSO, src) + set_AI_busy(FALSE) + glow_toggle = 0 + flames = 0 + build_icons() + +/mob/living/simple_mob/vore/ddraig/proc/tfbeam(var/atom/A) + if(!isturf(get_turf(A))) + return + var/obj/item/projectile/P = new /obj/item/projectile/beam/mouselaser/ddraig(get_turf(src)) + src.visible_message(span_danger("\The [src] breathes a beam at \the [A]!")) + playsound(src, "sound/weapons/sparkle.ogg", 50, 1) + P.launch_projectile(A, BP_TORSO, src) + +/obj/item/projectile/beam/mouselaser/ddraig + tf_admin_pref_override = TRUE //It will TF them regardless of their prefs because it is only very temporary + icon_state = "rainbow" + muzzle_type = /obj/effect/projectile/muzzle/rainbow + tracer_type = /obj/effect/projectile/tracer/rainbow + impact_type = /obj/effect/projectile/impact/rainbow + +/obj/item/projectile/beam/mouselaser/on_hit(var/atom/target) + var/list/tf_list = list("mouse" = /mob/living/simple_mob/animal/passive/mouse, + "rat" = /mob/living/simple_mob/animal/passive/mouse/rat, + "dust jumper" = /mob/living/simple_mob/vore/alienanimals/dustjumper, + "woof" = /mob/living/simple_mob/vore/woof, + "corgi" = /mob/living/simple_mob/animal/passive/dog/corgi, + "cat" = /mob/living/simple_mob/animal/passive/cat, + "chicken" = /mob/living/simple_mob/animal/passive/chicken, + "cow" = /mob/living/simple_mob/animal/passive/cow, + "lizard" = /mob/living/simple_mob/animal/passive/lizard, + "rabbit" = /mob/living/simple_mob/vore/rabbit, + "fox" = /mob/living/simple_mob/animal/passive/fox, + "fennec" = /mob/living/simple_mob/vore/fennec, + "cute fennec" = /mob/living/simple_mob/animal/passive/fennec, + "fennix" = /mob/living/simple_mob/vore/fennix, + "red panda" = /mob/living/simple_mob/vore/redpanda, + "opossum" = /mob/living/simple_mob/animal/passive/opossum, + "horse" = /mob/living/simple_mob/vore/horse, + "goose" = /mob/living/simple_mob/animal/space/goose, + "sheep" = /mob/living/simple_mob/vore/sheep) + tf_type = pick(tf_list) + . = ..() + spawn(30 SECONDS) + target.revert_mob_tf() //TF them back after 30 seconds, basically takes them out of the fight for a short time. + +/datum/ai_holder/simple_mob/vore/ddraig + var/used_invis = 0 + +/datum/ai_holder/simple_mob/vore/ddraig/engage_target() + ai_log("engage_target() : Entering.", AI_LOG_DEBUG) + + if(holder.cloaked) + set_stance(STANCE_FLEE) + return + + if((holder.health < (holder.maxHealth / 4)) && !used_invis) + holder.cloak() + used_invis = 1 + spawn(60 SECONDS) + holder.uncloak() + + // Can we still see them? + if(!target || !can_attack(target)) + ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) + if(lose_target()) // We lost them (returns TRUE if we found something else to do) + ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE) + return + + var/distance = get_dist(holder, target) + ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE) + holder.face_atom(target) + last_conflict_time = world.time + + // Do a 'special' attack, if one is allowed. +// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range)) + if(holder.ICheckSpecialAttack(target)) + ai_log("engage_target() : Attempting a special attack.", AI_LOG_TRACE) + on_engagement(target) + if(special_attack(target)) // If this fails, then we try a regular melee/ranged attack. + ai_log("engage_target() : Successful special attack. Exiting.", AI_LOG_DEBUG) + return + + // Stab them. + else if(distance <= 1 && !pointblank) + ai_log("engage_target() : Attempting a melee attack.", AI_LOG_TRACE) + on_engagement(target) + melee_attack(target) + + else if(distance <= 1 && !holder.ICheckRangedAttack(target)) // Doesn't have projectile, but is pointblank + ai_log("engage_target() : Attempting a melee attack.", AI_LOG_TRACE) + on_engagement(target) + melee_attack(target) + + // Shoot them. + else if(holder.ICheckRangedAttack(target) && (distance <= max_range(target)) ) + on_engagement(target) + if(firing_lanes && !test_projectile_safety(target)) + // Nudge them a bit, maybe they can shoot next time. + var/turf/T = get_step(holder, pick(cardinal)) + if(T) + holder.IMove(T) // IMove() will respect movement cooldown. + holder.face_atom(target) + ai_log("engage_target() : Could not safely fire at target. Exiting.", AI_LOG_DEBUG) + return + + ai_log("engage_target() : Attempting a ranged attack.", AI_LOG_TRACE) + ranged_attack(target) + + // Run after them. + else if(!stand_ground) + ai_log("engage_target() : Target ([target]) too far away. Exiting.", AI_LOG_DEBUG) + set_stance(STANCE_APPROACH) + +////////////////////////////Player controlled verbs/////////////////////////////// + +/mob/living/proc/polymorph() + set name = "Polymorph" + set desc = "Take the form of a non-humanoid creature." + set category = "Abilities" + + var/list/beast_options = list("Rabbit" = /mob/living/simple_mob/vore/rabbit, + "Red Panda" = /mob/living/simple_mob/vore/redpanda, + "Fennec" = /mob/living/simple_mob/vore/fennec, + "Giant Frog" = /mob/living/simple_mob/vore/aggressive/frog, + "Giant Rat" = /mob/living/simple_mob/vore/aggressive/rat, + "Wolf" = /mob/living/simple_mob/vore/wolf, + "Dire Wolf" = /mob/living/simple_mob/vore/wolf/direwolf, + "Fox" = /mob/living/simple_mob/animal/passive/fox/beastmode, + "Panther" = /mob/living/simple_mob/vore/aggressive/panther, + "Giant Snake" = /mob/living/simple_mob/vore/aggressive/giant_snake, + "Otie" = /mob/living/simple_mob/vore/otie, + "Squirrel" = /mob/living/simple_mob/vore/squirrel, + "Raptor" = /mob/living/simple_mob/vore/raptor, + "Giant Bat" = /mob/living/simple_mob/vore/bat, + "Horse" = /mob/living/simple_mob/vore/horse, + "Horse (Big)" = /mob/living/simple_mob/vore/horse/big, + "Kelpie" = /mob/living/simple_mob/vore/horse/kelpie, + "Bear" = /mob/living/simple_mob/animal/space/bear/brown/beastmode, + "Seagull" = /mob/living/simple_mob/vore/seagull, + "Sheep" = /mob/living/simple_mob/vore/sheep, + "Azure Tit" = /mob/living/simple_mob/animal/passive/bird/azure_tit/beastmode, + "Robin" = /mob/living/simple_mob/animal/passive/bird/european_robin/beastmode, + "Cat" = /mob/living/simple_mob/animal/passive/cat/black/beastmode, + "Tamaskan Dog" = /mob/living/simple_mob/animal/passive/dog/tamaskan, + "Corgi" = /mob/living/simple_mob/animal/passive/dog/corgi, + "Bull Terrier" = /mob/living/simple_mob/animal/passive/dog/bullterrier, + "Duck" = /mob/living/simple_mob/animal/sif/duck, + "Cow" = /mob/living/simple_mob/animal/passive/cow, + "Chicken" = /mob/living/simple_mob/animal/passive/chicken, + "Goat" = /mob/living/simple_mob/animal/goat, + "Penguin" = /mob/living/simple_mob/animal/passive/penguin, + "Goose" = /mob/living/simple_mob/animal/space/goose + ) + + var/chosen_beast = tgui_input_list(src, "Which form would you like to take?", "Choose Beast Form", beast_options) + + if(!chosen_beast) + return + + + var/mob/living/M = src + log_debug("polymorph start") + if(!istype(M)) + log_debug("polymorph istype") + return + + if(M.stat) //We can let it undo the TF, because the person will be dead, but otherwise things get weird. + log_debug("polymorph stat") + to_chat(src, span_warning("You can't do that in your condition.")) + return + + if(M.health <= 10) //We can let it undo the TF, because the person will be dead, but otherwise things get weird. + log_debug("polymorph injured") + to_chat(src, span_warning("You are too injured to transform into a beast.")) + return + + visible_message("\The [src] begins significantly shifting their form.") + if(!do_after(src, 10 SECONDS, src, exclusive = TASK_USER_EXCLUSIVE)) + visible_message("\The [src] ceases shifting their form.") + return 0 + + var/image/coolanimation = image('icons/obj/glamour.dmi', null, "animation") + coolanimation.plane = PLANE_LIGHTING_ABOVE + src.overlays += coolanimation + spawn(10) + src.overlays -= coolanimation + + log_debug("polymorph not dead") + var/mob/living/new_mob = spawn_polymorph_mob(beast_options[chosen_beast]) + new_mob.faction = M.faction + + if(new_mob && isliving(new_mob)) + log_debug("polymorph new_mob") + for(var/obj/belly/B as anything in new_mob.vore_organs) + log_debug("polymorph new_mob belly") + new_mob.vore_organs -= B + qdel(B) + new_mob.vore_organs = list() + new_mob.name = M.name + new_mob.real_name = M.real_name + new_mob.verbs |= /mob/living/proc/revert_beast_form + new_mob.verbs |= /mob/living/proc/set_size + for(var/lang in M.languages) + new_mob.languages |= lang + M.copy_vore_prefs_to_mob(new_mob) + new_mob.vore_selected = M.vore_selected + if(ishuman(M)) + log_debug("polymorph ishuman part2") + var/mob/living/carbon/human/H = M + if(ishuman(new_mob)) + log_debug("polymorph ishuman(newmob)") + var/mob/living/carbon/human/N = new_mob + N.gender = H.gender + N.identifying_gender = H.identifying_gender + else + log_debug("polymorph gender else") + new_mob.gender = H.gender + else + log_debug("polymorph gender else 2") + new_mob.gender = M.gender + if(ishuman(new_mob)) + var/mob/living/carbon/human/N = new_mob + N.identifying_gender = M.gender + + for(var/obj/belly/B as anything in M.vore_organs) + B.loc = new_mob + B.forceMove(new_mob) + B.owner = new_mob + M.vore_organs -= B + new_mob.vore_organs += B + + new_mob.ckey = M.ckey + if(M.ai_holder && new_mob.ai_holder) + var/datum/ai_holder/old_AI = M.ai_holder + old_AI.set_stance(STANCE_SLEEP) + var/datum/ai_holder/new_AI = new_mob.ai_holder + new_AI.hostile = old_AI.hostile + new_AI.retaliate = old_AI.retaliate + M.loc = new_mob + M.forceMove(new_mob) + new_mob.tf_mob_holder = M + new_mob.visible_message("\The [src] has transformed into \the [chosen_beast]!") + +/mob/living/proc/spawn_polymorph_mob(var/chosen_beast) + log_debug("polymorph proc spawn mob") + var/tf_type = chosen_beast + log_debug("polymorph [tf_type]") + if(!ispath(tf_type)) + log_debug("polymorph tf_type fail") + return + log_debug("polymorph tf_type pass") + var/new_mob = new tf_type(get_turf(src)) + return new_mob + +/mob/living/proc/invisibility() + set name = "Invisibility" + set desc = "Change your appearance to match your surroundings, becoming completely invisible to the naked eye." + set category = "Abilities" + + if(stat) + to_chat(src, span_warning("You can't go invisible when weakened like this.")) + return + + if(!cloaked) + cloak() + block_hud = 1 + hud_updateflag = 1 + to_chat(src, span_warning("Your fur shimmers and shifts around you, hiding you from the naked eye.")) + else + uncloak() + block_hud = 0 + hud_updateflag = 1 + to_chat(src, span_warning("The brustling of your fur settles down and you become visible once again.")) diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm new file mode 100644 index 0000000000..3484263762 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/glamour/fluffball.dm @@ -0,0 +1,212 @@ +/mob/living/simple_mob/vore/scrubble + name = "fluffball" + desc = "A small, rotund humanoid creature. It is difficult to make out physical features of it as it covers most of its face behind big floppy, fluffy ears with only beady yellow eyes looking out. Most of its body is covered by a thick, soft tail that it wraps around itself and holds onto with small stumpy arms." + catalogue_data = list(/datum/category_item/catalogue/fauna/scrubble) + tt_desc = "glamoris fluffalia" + icon = 'icons/mob/vore.dmi' + icon_dead = "fluffball-dead" + icon_living = "fluffball" + icon_state = "fluffball" + faction = FACTION_SCRUBBLE + friendly = list("nudges", "sniffs on", "rumbles softly at", "nuzzles") + response_help = "bumps" + response_disarm = "shoves" + response_harm = "attacks" + movement_cooldown = 0 + harm_intent_damage = 2 + melee_damage_lower = 1 + melee_damage_upper = 4 + maxHealth = 50 + attacktext = list("tail whips") + see_in_dark = 8 + minbodytemp = 0 + ai_holder_type = /datum/ai_holder/simple_mob/hostile/fluffball + say_list_type = /datum/say_list/fluffball + + faction = FACTION_GLAMOUR + + vore_bump_chance = 25 + vore_digest_chance = 50 + vore_escape_chance = 5 + vore_pounce_chance = 1000 + vore_active = 1 + vore_icons = 1 + vore_icons = SA_ICON_LIVING + vore_capacity = 1 + swallowTime = 50 + vore_ignores_undigestable = TRUE + vore_default_mode = DM_SELECT + vore_pounce_maxhealth = 1000 + vore_bump_emote = "pounces on" + vore_pounce_falloff = 0 //Always eat someone at full health + vore_standing_too = 1 + +/mob/living/simple_mob/vore/fluffball/init_vore() + ..() + var/obj/belly/B = vore_selected + B.name = "tail" + B.desc = "The small critter seems to suddenly panic, lunging at you with its massive fluffy tail, using it like a weapon. Despite the appearance of the tail, it seems to be much larger on the inside, suddenly engulfing you completely in a world of endless softness. Inside, you are bound up nice and tight in an oddly comfortable prison of hair, it ripples over your body tickling every bit of exposed body on offer." + B.mode_flags = DM_FLAG_THICKBELLY + B.belly_fullscreen = "yet_another_tumby" + B.digest_brute = 1 + B.digest_burn = 1 + B.digest_oxy = 0 + B.digestchance = 10 + B.absorbchance = 60 + B.escapechance = 10 + B.selective_preference = DM_ABSORB + B.escape_stun = 5 + B.transferlocation_absorb = "fluff" + + var/obj/belly/fluff = new /obj/belly(src) + fluff.immutable = TRUE + fluff.name = "fluff" + 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.escapechance = 40 // high chance of STARTING a successful escape attempt + fluff.escapechance_absorbed = 5 // m i n e + fluff.vore_verb = "soak" + fluff.count_absorbed_prey_for_sprite = FALSE + fluff.absorbed_struggle_messages_inside = list( + "You try and push free from %pred's %belly, but can't seem to will yourself to move.", + "Your fruitless mental struggles only cause %pred to giggle lightly.", + "You can't make any progress freeing yourself from %pred's %belly.") + fluff.escape_attempt_absorbed_messages_owner = list( + "%prey is attempting to free themselves from your %belly!") + + fluff.escape_attempt_absorbed_messages_prey = list( + "You try to force yourself out of %pred's %belly.", + "You strain and push, attempting to reach out of %pred's %belly.", + "You work up the will to try and force yourself free of %pred's clutches.") + + fluff.escape_absorbed_messages_owner = list( + "%prey forces themselves free of your %belly!") + + fluff.escape_absorbed_messages_prey = list( + "You finally manage to wrest yourself free from %pred's %belly, re-asserting your more usual form.", + "You heave and push, eventually spilling out from %pred's %belly, eliciting a happy chirp from your former captor.") + + fluff.escape_absorbed_messages_outside = list( + "%prey suddenly forces themselves free of %pred's %belly!") + + fluff.escape_fail_absorbed_messages_owner = list( + "%prey's attempt to escape form your %belly has failed!") + + fluff.escape_fail_absorbed_messages_prey = list( + "Before you manage to reach freedom, you feel yourself getting dragged back into %pred's %belly!", + "%pred laughs lightly, simply pressing your wrigging form back into her %belly before you get anywhere.", + "Try as you might, you barely make an impression before %pred simply clenches with the most minimal effort, binding you back into her %belly.", + "Unfortunately, %pred seems to have absolutely no intention of letting you go, and your futile effort goes nowhere.", + "Strain as you might, you can't keep up the effort long enough before you sink back into %pred's %belly.") + + +/datum/say_list/fluffball + emote_hear = list("makes a shy squeal","whimpers","lets out a little squeak") + emote_see = list("hides its face","cuddles up to its own tail","stands there awkwardly","avoids eye contact") + +/datum/category_item/catalogue/fauna/fluffball + name = "Extra-Realspace Fauna - Fluffball" + desc = "Classification: Glamoris Fluffalia\ +

\ + A stout creature with an apparently quite round figure, known to habit the location known as the Glamour. It is generally difficult to identify any physical features on the creature \ + due to its anxiety around other creatures, it hides itself beneath its heavily furred ears and tail. Most creatures that approach it will find the fluffball fleeing quickly, \ + though it is known to act more calm around those carrying food, which it is quick to steal from people's hands if offered. However, they have been observed to use their tails as weapons when panicked and unable to flee." + value = CATALOGUER_REWARD_HARD + +/mob/living/simple_mob/vore/fluffball/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("\The [src] pounces on \the [M]!!") + else // pounce misses! + M.visible_message("\The [src] attempts to pounce \the [M] but misses!!") + playsound(src, '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 + +/mob/living/simple_mob/vore/fluffball/attackby(obj/item/W as obj, mob/user as mob) + ..() + if(W.istype(/obj/item/reagent_containers/food)) + user.drop_item(W) + qdel(W) + M.visible_message("\The [src] quickly steals \the [w] into its fluff, it seems to have become a little less shy!!") + if(ai_holder_type.istype(/datum/ai_holder/simple_mob/hostile/fluffball)) + L.ai_holder_type = /datum/ai_holder/simple_mob/vore/fluffball + L.initialize_ai_holder() + +//AI + +/datum/ai_holder/simple_mob/hostile/fluffball + can_flee = TRUE + vision_range = 2 //Only react if you get close + can_flee = TRUE // If they're even allowed to flee. + flee_when_dying = TRUE // If they should flee when low on health. + dying_threshold = 1.1 // Flee at max health + +/datum/ai_holder/simple_mob/hostile/fluffball/flee_from_target() + ai_log("flee_from_target() : Entering.", AI_LOG_DEBUG) + + if(!target || !should_flee() || !can_attack(target)) // can_attack() is used since it checks the same things we would need to anyways. + ai_log("flee_from_target() : Lost target to flee from.", AI_LOG_INFO) + lose_target() + set_stance(STANCE_IDLE) + ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG) + return + + var/mob/living/simple_mob/vore/H = holder + var/mob/living/L = target + var/distance = get_dist(holder, target) + if(distance <= 1) + if(H.will_eat(L) && H.CanPounceTarget(L)) + H.face_atom(L) + H.PounceTarget(L) + return + + ai_log("flee_from_target() : Stepping away.", AI_LOG_TRACE) + step_away(holder, target, 5) + ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG) + +/datum/ai_holder/simple_mob/hostile/fluffball/find_target(list/possible_targets, has_targets_list) + if(!isanimal(holder)) //Only simplemobs have the vars we need + return ..() + var/list/L = list() + if(!has_targets_list) + possible_targets = list_targets() + var/list/valid_mobs = list() + for(var/mob/living/possible_target in possible_targets) + var/mob/living/carbon/human/H = possible_target + if(H.istype()) + /obj/item/reagent_containers/food/L = H.get_active_hand() + /obj/item/reagent_containers/food/R = H.get_inactive_hand() + if(R.istype() || L.istype()) + continue + if(!can_attack(possible_target)) + continue + L |= possible_target + if(!isliving(possible_target)) + continue + if(vore_check(possible_target)) + valid_mobs |= possible_target + + var/new_target + if(valid_mobs.len) + new_target = pick(valid_mobs) + else if(hostile && L.len) + new_target = pick(L) + if(!new_target) + return null + give_target(new_target) + return new_target + +/datum/ai_holder/simple_mob/vore/fluffball + vision_range = 4 + hostile = FALSE + retaliate = TRUE + vore_hostile = FALSE + forgive_resting = TRUE + cooperative = FALSE diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/unicorn.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/unicorn.dm new file mode 100644 index 0000000000..12c7402aa0 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/glamour/unicorn.dm @@ -0,0 +1,71 @@ +/mob/living/simple_mob/vore/horse/unicorn + name = "unicorn" + icon_state = "unicorn" + icon_living = "unicorn" + icon_dead = "unicorn-dead" + icon = 'icons/mob/vore64x64.dmi' + desc = "A bright white horse-like creature with a golden horn protruding from its forehead. It's mane and hooves is a similarly golden." + tt_desc = "Equus unicornis" + + vore_capacity = 2 + maxHealth = 250 + health = 250 + meat_amount = 10 + + old_x = -16 + old_y = 0 + default_pixel_x = -16 + pixel_x = -16 + pixel_y = 0 + mount_offset_y = 22 + say_list_type = /datum/say_list/horse/unicorn + ai_holder_type = /datum/ai_holder/simple_mob/vore + minbodytemp = 0 + + vore_bump_chance = 75 + vore_pounce_chance = 75 + vore_pounce_maxhealth = 200 + vore_bump_emote = "chomps down on" + + faction = FACTION_GLAMOUR + + projectiletype = /obj/item/projectile/beam/rainbow + projectilesound = 'sound/weapons/sparkle.ogg' + projectile_dispersion = 7 + projectile_accuracy = -20 + +/mob/living/simple_mob/vore/horse/unicorn/init_vore() + ..() + var/obj/belly/B = vore_selected + B.name = "stomach" + B.desc = "With a final few gulps, the unicorn finishes swallowing you down into its hot, humid gut... and with a slosh, your weight makes the equine's belly hang down slightly like some sort of organic hammock. The thick, damp air is tinged with the smell of... candyfloss(?), and the surrounding flesh wastes no time in clenching and massaging down over its newfound fodder." + B.digest_brute = 3 + B.digest_burn = 3 + B.digestchance = 40 + B.absorbchance = 1 + B.escapechance = 7 + B.escape_stun = 5 + + B.emote_lists[DM_HOLD] = list( + "The unicorn's idle trotting helps its stomach gently churn around you, slimily squelching against your figure.", + "The equine predator takes a moment to intentionally clench its gut around you, encapsulating you in a strange, fleshy hug.", + "Some hot, viscous slime oozes down over your form, helping slicken you up during your stay.", + "During a moment of relative silence, you can hear the beast's soft, relaxed breathing as it casually goes about its day.", + "The thick, humid atmosphere within the unicorn's hanging belly works in tandem with its steady, metronome-like heartbeat to soothe you.", + "Your surroundings sway from side to side as the unicorn trots about, as if it is showing off its newest catch.") + + B.emote_lists[DM_DIGEST] = list( + "The unicorn huffs in annoyance before clenching those wrinkled walls tight against your form, grinding away at you!", + "As the beast trots about, you're forced to slip and slide around amidst a pool of thick digestive goop!", + "You can barely hear the unicorn let out a pleased nicker as its stomach eagerly gurgles around its newfound meal!", + "As the thinning air begins to make you feel dizzy, menacing bworps and grumbles fill that dark, constantly shifting organ!", + "The constant, rhythmic kneading and massaging starts to take its toll along with the muggy heat, making you feel weaker and weaker!", + "The unicorn happily trots around while digesting its meal, almost like it is trying to show off the hanging gut you've given it.") + +/datum/say_list/horse/unicorn + speak = list("...","?") + emote_hear = list("makes some sort of sparkling sound","whinnies with the most beautiful sounds") + emote_see = list("twinkles prettily", "shakes its mane", "looks so very regal") + +/mob/living/simple_mob/vore/horse/unicorn/hostile +ai_holder_type = /datum/ai_holder/simple_mob/vore/hostile diff --git a/icons/mob/vore.dmi b/icons/mob/vore.dmi index a0842ddb42..67209c83d7 100644 Binary files a/icons/mob/vore.dmi and b/icons/mob/vore.dmi differ diff --git a/icons/mob/vore64x32.dmi b/icons/mob/vore64x32.dmi index 072c39795a..9f9d0fd289 100644 Binary files a/icons/mob/vore64x32.dmi and b/icons/mob/vore64x32.dmi differ diff --git a/icons/mob/vore64x64.dmi b/icons/mob/vore64x64.dmi index 0c7458499b..151210857c 100644 Binary files a/icons/mob/vore64x64.dmi and b/icons/mob/vore64x64.dmi differ diff --git a/icons/mob/vore96x96.dmi b/icons/mob/vore96x96.dmi new file mode 100644 index 0000000000..3f6420148c Binary files /dev/null and b/icons/mob/vore96x96.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index ca6e269f7f..a0c5b9027e 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/icons/obj/projectiles_impact.dmi b/icons/obj/projectiles_impact.dmi index 054757d0f9..faa70e7406 100644 Binary files a/icons/obj/projectiles_impact.dmi and b/icons/obj/projectiles_impact.dmi differ diff --git a/icons/obj/projectiles_muzzle.dmi b/icons/obj/projectiles_muzzle.dmi index 52539c49a5..0a1591c484 100644 Binary files a/icons/obj/projectiles_muzzle.dmi and b/icons/obj/projectiles_muzzle.dmi differ diff --git a/icons/obj/projectiles_tracer.dmi b/icons/obj/projectiles_tracer.dmi index 2052f93e96..b46e19c664 100644 Binary files a/icons/obj/projectiles_tracer.dmi and b/icons/obj/projectiles_tracer.dmi differ