diff --git a/code/game/objects/items/glassjar.dm b/code/game/objects/items/glassjar.dm
index 2e73e89e67c..ce5bfbe91e6 100644
--- a/code/game/objects/items/glassjar.dm
+++ b/code/game/objects/items/glassjar.dm
@@ -6,7 +6,7 @@
w_class = ITEMSIZE_SMALL
matter = list("glass" = 200)
flags = NOBLUDGEON
- var/list/accept_mobs = list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse)
+ var/list/accept_mobs = list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse, /mob/living/simple_mob/animal/sif/leech, /mob/living/simple_mob/animal/sif/frostfly, /mob/living/simple_mob/animal/sif/glitterfly)
var/contains = 0 // 0 = nothing, 1 = money, 2 = animal, 3 = spiderling
/obj/item/glass_jar/New()
@@ -96,6 +96,12 @@
for(var/mob/M in src)
var/image/victim = image(M.icon, M.icon_state)
victim.pixel_y = 6
+ victim.color = M.color
+ if(M.plane == PLANE_LIGHTING_ABOVE) // This will only show up on the ground sprite, due to the HuD being over it, so we need both images.
+ var/image/victim_glow = image(M.icon, M.icon_state)
+ victim_glow.pixel_y = 6
+ victim_glow.color = M.color
+ underlays += victim_glow
underlays += victim
name = "glass jar with [M]"
desc = "A small jar with [M] inside."
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index b13d4d68440..dfe5c85b63b 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -24,7 +24,7 @@
// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
- . = hearers(vision_range, holder) - src // Remove ourselves to prevent suicidal decisions.
+ . = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder.
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm
index 64be119d7c4..7d1ab792fff 100644
--- a/code/modules/mob/_modifiers/modifiers_misc.dm
+++ b/code/modules/mob/_modifiers/modifiers_misc.dm
@@ -269,6 +269,20 @@ the artifact triggers the rage.
return FALSE
return TRUE
+/datum/modifier/poisoned/paralysis
+ desc = "You have poison inside of you. It will cause harm over a long span of time if not cured, and may cause temporary paralysis."
+ on_created_text = "You feel incredibly weak..."
+ damage_per_tick = 0.75
+
+/datum/modifier/poisoned/paralysis/tick()
+ ..()
+ if(prob(5))
+ holder.Paralyse(3)
+
+/datum/modifier/poisoned/paralysis/on_applied()
+ ..()
+ holder.Paralyse(4)
+
// Pulse modifier.
/datum/modifier/false_pulse
name = "false pulse"
@@ -278,4 +292,4 @@ the artifact triggers the rage.
on_expired_text = "You feel.. different."
stacks = MODIFIER_STACK_EXTEND
- pulse_set_level = PULSE_NORM
+ pulse_set_level = PULSE_NORM
\ No newline at end of file
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index 584dc51ad5a..25a6ed122ab 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -104,6 +104,10 @@ var/list/holder_mob_icon_cache = list()
/obj/item/weapon/holder/borer
origin_tech = list(TECH_BIO = 6)
+/obj/item/weapon/holder/leech
+ color = "#003366"
+ origin_tech = list(TECH_BIO = 5, TECH_PHORON = 2)
+
/obj/item/weapon/holder/fish
attack_verb = list("fished", "disrespected", "smacked", "smackereled")
hitsound = 'sound/effects/slime_squish.ogg'
diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm
index 0cd734ec066..c70094d5792 100644
--- a/code/modules/mob/living/simple_mob/simple_mob.dm
+++ b/code/modules/mob/living/simple_mob/simple_mob.dm
@@ -47,6 +47,7 @@
var/movement_sound = null // If set, will play this sound when it moves on its own will.
var/turn_sound = null // If set, plays the sound when the mob's dir changes in most cases.
var/movement_shake_radius = 0 // If set, moving will shake the camera of all living mobs within this radius slightly.
+ var/aquatic_movement = 0 // If set, the mob will move through fluids with no hinderance.
//Mob interaction
var/response_help = "tries to help" // If clicked on help intent
@@ -230,8 +231,11 @@
// Turf related slowdown
var/turf/T = get_turf(src)
- if(T && T.movement_cost && !hovering) // Flying mobs ignore turf-based slowdown.
- tally += T.movement_cost
+ if(T && T.movement_cost && !hovering) // Flying mobs ignore turf-based slowdown. Aquatic mobs ignore water slowdown, and can gain bonus speed in it.
+ if(istype(T,/turf/simulated/floor/water) && aquatic_movement)
+ tally -= aquatic_movement - 1
+ else
+ tally += T.movement_cost
if(purge)//Purged creatures will move more slowly. The more time before their purge stops, the slower they'll move.
if(tally <= 0)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/duck.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/duck.dm
new file mode 100644
index 00000000000..0aa74f9e6b7
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/duck.dm
@@ -0,0 +1,58 @@
+// Crystal-feather "ducks" are rather weak, but will become aggressive if you have food.
+
+/datum/category_item/catalogue/fauna/crystalduck
+ name = "Sivian Fauna - Crystal-Feather Duck"
+ desc = "Classification: S Anatidae vitriae \
+
\
+ Small, asocial omnivores with glistening, razor-thin feathers valued for their use as a reflective glitter by poachers. \
+ The Crystal-Feather Duck commonly forms at most a familial group of four, a male, two females, and a single 'chosen' young. Primarily detrivorous browsers, \
+ supplementing their diet with animals living below thinner shore ice-sheets. \
+ Family units have been observed to form gangs and scavenge from Sivian domeciles and \
+ various food transports during stressful months. \
+ It is advised to seal and hide any form of food near even lone individuals, as they will become \
+ increasingly aggressive."
+ value = CATALOGUER_REWARD_EASY
+
+/mob/living/simple_mob/animal/sif/duck
+ name = "crystal-feather duck"
+ desc = "A glittering flightless bird."
+ tt_desc = "S Anatidae vitriae"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/crystalduck)
+
+ faction = "duck"
+
+ icon_state = "duck"
+ icon_living = "duck"
+ icon_dead = "duck_dead"
+ icon = 'icons/mob/animal.dmi'
+ has_eye_glow = TRUE
+
+ maxHealth = 50
+ health = 50
+
+ movement_cooldown = 0
+
+ melee_damage_lower = 2
+ melee_damage_upper = 10
+ base_attack_cooldown = 1 SECOND
+ attack_edge = 1 // Razor-edged wings, and 'claws' made for digging through ice.
+ attacktext = list("nipped", "bit", "cut", "clawed")
+
+ say_list_type = /datum/say_list/duck
+ ai_holder_type = /datum/ai_holder/simple_mob/retaliate/cooperative
+
+/datum/say_list/duck
+ speak = list("Wack!", "Wock?", "Wack.")
+ emote_see = list("ruffles its wings","looks around", "preens itself")
+ emote_hear = list("quacks", "giggles")
+
+/mob/living/simple_mob/animal/sif/duck/IIsAlly(mob/living/L)
+ . = ..()
+
+ var/has_food = FALSE
+ for(var/obj/item/I in L.get_contents()) // Do they have food?
+ if(istype(I, /obj/item/weapon/reagent_containers/food))
+ has_food = TRUE
+ break
+ if(has_food) // Yes? Gimme the food.
+ return FALSE
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm
new file mode 100644
index 00000000000..5711868d157
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm
@@ -0,0 +1,169 @@
+// Frostflies are large, flightless insects with glittering wings, used as a means of deploying their gaseous self-defense mechanism.
+/datum/category_item/catalogue/fauna/frostfly
+ name = "Sivian Fauna - Frostfly"
+ desc = "Classification: S Carabidae glacios \
+
\
+ A large, flightless insectoid with bioluminescent wings. \
+ Frostflies utilize their vestigial wings as a method of dispersing a chemical that produces a rapid \
+ endothermic reaction on contact with the air, resulting in the flash-freezing of nearby materials. \
+
\
+ Carnivorous in nature, they use their cryogenic compounds to trap smaller prey, or frighten predators. \
+ Individuals are known to slalom when facing other creatures, dispersing clouds of gas, and spitting \
+ condensed globs of the compound. These masses of mucous and ice seem to be intended to impede movement. \
+
\
+ Travelers are advised to avoid frostfly swarms whenever possible, as they will become aggressive \
+ to anything other than Diyaabs, which they seem to have formed a tangential symbiosis with."
+ value = CATALOGUER_REWARD_MEDIUM
+
+/mob/living/simple_mob/animal/sif/frostfly
+ name = "frostfly"
+ desc = "A large insect with glittering wings."
+ tt_desc = "S Carabidae glacios"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/frostfly)
+
+ faction = "diyaab"
+
+ icon_state = "firefly"
+ icon_living = "firefly"
+ icon_dead = "firefly_dead"
+ icon_rest = "firefly_dead"
+ icon = 'icons/mob/animal.dmi'
+ has_eye_glow = TRUE
+
+ maxHealth = 65
+ health = 65
+
+ pass_flags = PASSTABLE
+
+ var/energy = 100
+ var/max_energy = 100
+
+ movement_cooldown = 0.5
+
+ melee_damage_lower = 5
+ melee_damage_upper = 10
+ base_attack_cooldown = 1.5 SECONDS
+ attacktext = list("nipped", "bit", "pinched")
+
+ projectiletype = /obj/item/projectile/energy/blob/freezing
+
+ special_attack_cooldown = 5 SECONDS
+ special_attack_min_range = 0
+ special_attack_max_range = 4
+
+ armor = list(
+ "melee" = 20,
+ "bullet" = 10,
+ "laser" = 5,
+ "energy" = 0,
+ "bomb" = 10,
+ "bio" = 100,
+ "rad" = 100
+ )
+
+ // The frostfly's body is incredibly cold at all times, natural resistance to things trying to burn it.
+ armor_soak = list(
+ "melee" = 0,
+ "bullet" = 0,
+ "laser" = 15,
+ "energy" = 0,
+ "bomb" = 0,
+ "bio" = 0,
+ "rad" = 0
+ )
+
+ var/datum/effect/effect/system/smoke_spread/frost/smoke_special
+
+ say_list_type = /datum/say_list/frostfly
+ ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly
+
+/mob/living/simple_mob/animal/sif/frostfly/get_cold_protection()
+ return 1 // It literally produces a cryogenic mist inside itself. Cold doesn't bother it.
+
+/mob/living/simple_mob/animal/sif/frostfly/Initialize()
+ ..()
+ smoke_special = new
+ verbs += /mob/living/proc/ventcrawl
+ verbs += /mob/living/proc/hide
+
+/datum/say_list/frostfly
+ speak = list("Zzzz.", "Kss.", "Zzt?")
+ emote_see = list("flutters its wings","looks around", "rubs its mandibles")
+ emote_hear = list("chitters", "clicks", "chirps")
+
+ say_understood = list("Ssst.")
+ say_cannot = list("Zzrt.")
+ say_maybe_target = list("Ki?")
+ say_got_target = list("Ksst!")
+ say_threaten = list("Kszsz.","Kszzt...","Kzzi!")
+ say_stand_down = list("Sss.","Zt.","! clicks.")
+ say_escalate = list("Rszt!")
+
+ threaten_sound = 'sound/effects/refill.ogg'
+ stand_down_sound = 'sound/effects/sparks5.ogg'
+
+/mob/living/simple_mob/animal/sif/frostfly/handle_special()
+ ..()
+
+ if(energy < max_energy)
+ energy++
+
+/mob/living/simple_mob/animal/sif/frostfly/Stat()
+ ..()
+ if(client.statpanel == "Status")
+ statpanel("Status")
+ if(emergency_shuttle)
+ var/eta_status = emergency_shuttle.get_status_panel_eta()
+ if(eta_status)
+ stat(null, eta_status)
+ stat("Energy", energy)
+
+/mob/living/simple_mob/animal/sif/frostfly/should_special_attack(atom/A)
+ if(energy >= 20)
+ return TRUE
+ return FALSE
+
+/mob/living/simple_mob/animal/sif/frostfly/do_special_attack(atom/A)
+ . = TRUE
+ switch(a_intent)
+ if(I_DISARM)
+ if(energy < 20)
+ return FALSE
+
+ energy -= 20
+
+ if(smoke_special)
+ smoke_special.set_up(7,0,src)
+ smoke_special.start()
+ return TRUE
+
+ return FALSE
+
+/datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly
+ can_flee = TRUE
+ dying_threshold = 0.5
+ flee_when_outmatched = TRUE
+ run_if_this_close = 3
+
+/datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly/special_flee_check()
+ var/mob/living/simple_mob/animal/sif/frostfly/F = holder
+ if(F.energy < F.max_energy * 0.2)
+ return TRUE
+ return FALSE
+
+/datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly/pre_special_attack(atom/A)
+ if(isliving(A))
+ holder.a_intent = I_DISARM
+ else
+ holder.a_intent = I_HURT
+
+/datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly/post_ranged_attack(atom/A)
+ var/mob/living/simple_mob/animal/sif/frostfly/F = holder
+ if(istype(A,/mob/living))
+ var/new_dir = turn(F.dir, -90)
+ if(prob(50))
+ new_dir = turn(F.dir, 90)
+ holder.IMove(get_step(holder, new_dir))
+ holder.face_atom(A)
+
+ F.energy = max(0, F.energy - 1) // The AI will eventually flee.
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/glitterfly.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/glitterfly.dm
new file mode 100644
index 00000000000..e455f221684
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/glitterfly.dm
@@ -0,0 +1,102 @@
+// Glitterflies! Little butterfly-like creatures that float around and be cute.
+
+/datum/category_item/catalogue/fauna/glitterfly
+ name = "Sivian Fauna - Glitterfly"
+ desc = "Classification: S Lepidoptera adamas \
+
\
+ An herbivorous insectoid beloved for its glittering wings. \
+
\
+ The creature has four wings mirrored laterally, which reflect \
+ the ambient light to provide a measure of camouflage."
+ value = CATALOGUER_REWARD_TRIVIAL
+
+/datum/category_item/catalogue/fauna/glitterfly_rare
+ name = "Sivian Fauna - Adela's Glitterfly"
+ desc = "Classification: S Lepidoptera adamas \
+
\
+ This mutation of the glitterfly is incredibly rare, one of the few and first known individuals\
+ known to exist with it is owned by little-known Sivian ecologist Adela Svingelm, and kept within its own \
+ climate-controlled habitat."
+ value = CATALOGUER_REWARD_HARD
+
+/mob/living/simple_mob/animal/sif/glitterfly
+ name = "glitterfly"
+ desc = "A shiny butterfly!"
+ tt_desc = "S Lepidoptera adamas"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/glitterfly)
+
+ faction = "neutral"
+
+ icon_state = "butterfly"
+ icon_living = "butterfly"
+ icon_dead = "butterfly_dead"
+ icon = 'icons/mob/animal.dmi'
+
+ maxHealth = 10
+ health = 10
+
+ movement_cooldown = -1
+ hovering = TRUE
+
+ melee_damage_lower = 1
+ melee_damage_upper = 2
+ attack_armor_pen = 80
+ attack_sharp = TRUE
+
+ density = FALSE // Non-dense, so things can walk through their groups unhindered.
+
+ pass_flags = PASSTABLE
+
+ attacktext = list("bit", "buffeted", "slashed")
+
+ say_list_type = /datum/say_list/glitterfly
+ ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive/glitterfly
+
+/mob/living/simple_mob/animal/sif/glitterfly/Initialize()
+ ..()
+ var/colorlist = list(rgb(rand(100,255), rand(100,255), rand(100,255)) = 10, rgb(rand(5,100), rand(5,100), rand(5,100)) = 2, "#222222" = 1)
+ color = pickweight(colorlist)
+
+ default_pixel_y = rand(5,12)
+ pixel_y = default_pixel_y
+
+/mob/living/simple_mob/animal/sif/glitterfly/rare
+ name = "sparkling glitterfly"
+ desc = "An incredibly shiny butterfly!"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/glitterfly, /datum/category_item/catalogue/fauna/glitterfly_rare)
+ maxHealth = 30
+ health = 30
+
+ movement_cooldown = -2
+
+ melee_damage_upper = 5
+
+ plane = PLANE_LIGHTING_ABOVE
+
+/mob/living/simple_mob/animal/sif/glitterfly/rare/Initialize()
+ ..()
+
+/datum/say_list/glitterfly
+ speak = list("Pi..","Po...", "Pa...")
+ emote_see = list("vibrates","flutters", "twirls")
+ emote_hear = list("pips", "clicks", "chirps")
+
+/datum/ai_holder/simple_mob/melee/evasive/glitterfly
+ hostile = FALSE
+ can_flee = TRUE
+ flee_when_outmatched = TRUE
+ outmatched_threshold = 100
+ max_home_distance = 5
+
+/datum/ai_holder/simple_mob/melee/evasive/glitterfly/handle_special_strategical()
+ if(prob(1))
+ var/friendly_animal_corpse = FALSE
+ for(var/mob/living/simple_mob/animal/A in view(vision_range,holder))
+ if(holder.IIsAlly(A) && A.stat == DEAD)
+ friendly_animal_corpse = TRUE
+ break
+
+ if(friendly_animal_corpse)
+ hostile = TRUE
+ return
+ hostile = initial(hostile)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm
new file mode 100644
index 00000000000..22c2d020829
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm
@@ -0,0 +1,404 @@
+// Kururaks, large pack-hunting felinids that reside in coastal regions. Less slowdown in water, speed on rocky turf.
+
+/datum/category_item/catalogue/fauna/kururak
+ name = "Sivian Fauna - Kururak"
+ desc = "Classification: S Felidae fluctursora \
+
\
+ An uncommon sight to many Sivian residents, these creatures are hypercarnivores, with\
+ their diets almost exclusively consisting of other fauna. This is achieved in the frozen \
+ environments of Sif via the means of fishing, even going to the lengths of evolving a \
+ third, dense lung only inflated for long dives. \
+
\
+ One of the most distinguishing features of these animals are their four tails, capped in \
+ reflective 'mirrors'. These mirrors are used for tricking fish and other prey into ambushes,\
+ or distract would-be rivals. \
+
\
+ Kururak packs are incredibly dangerous if faced alone, and should only be approached if prepared \
+ for a fight."
+ value = CATALOGUER_REWARD_HARD
+
+/mob/living/simple_mob/animal/sif/kururak
+ name = "kururak"
+ desc = "A large animal with sleek fur."
+ tt_desc = "S Felidae fluctursora"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/kururak)
+
+ faction = "kururak"
+
+ icon_state = "bigcat"
+ icon_living = "bigcat"
+ icon_dead = "bigcat_dead"
+ icon_rest = "bigcat_rest"
+ icon = 'icons/mob/64x64.dmi'
+
+ default_pixel_x = -16
+ pixel_x = -16
+
+ maxHealth = 200
+ health = 200
+
+ universal_understand = 1
+
+ movement_cooldown = 1
+
+ melee_damage_lower = 15
+ melee_damage_upper = 25
+ attack_armor_pen = 40
+ base_attack_cooldown = 2 SECONDS
+ attacktext = list("gouged", "bit", "cut", "clawed", "whipped")
+
+ armor = list(
+ "melee" = 30,
+ "bullet" = 15,
+ "laser" = 5,
+ "energy" = 0,
+ "bomb" = 10,
+ "bio" = 100,
+ "rad" = 100
+ )
+
+ armor_soak = list(
+ "melee" = 5,
+ "bullet" = 5,
+ "laser" = 5,
+ "energy" = 0,
+ "bomb" = 0,
+ "bio" = 0,
+ "rad" = 0
+ )
+
+ say_list_type = /datum/say_list/kururak
+ ai_holder_type = /datum/ai_holder/simple_mob/intentional/kururak
+
+ special_attack_min_range = 0
+ special_attack_max_range = 4
+ special_attack_cooldown = 30 SECONDS
+
+ // Players have 2 seperate cooldowns for these, while the AI must choose one. Both respect special_attack_cooldown
+ var/last_strike_time = 0
+ var/last_flash_time = 0
+
+ var/instinct // The points used by Kururaks to decide Who Is The Boss
+ var/obey_pack_rule = TRUE // Decides if the Kururak will automatically assign itself to follow the one with the highest instinct.
+
+/datum/say_list/kururak
+ speak = list("Kurr?","|R|rrh..", "Ksss...")
+ emote_see = list("scratches its ear","flutters its tails", "flicks an ear", "shakes out its hair")
+ emote_hear = list("chirps", "clicks", "grumbles", "chitters")
+
+/mob/living/simple_mob/animal/sif/kururak/leader // Going to be the starting leader. Has some base buffs to make it more likely to stay the leader.
+ maxHealth = 250
+ health = 250
+ instinct = 50
+
+/mob/living/simple_mob/animal/sif/kururak/Initialize()
+ ..()
+ if(!instinct)
+ if(prob(20))
+ instinct = rand(6, 10)
+ return
+ instinct = rand(0, 5)
+
+/mob/living/simple_mob/animal/sif/kururak/IIsAlly(mob/living/L)
+ . = ..()
+ if(!.)
+ if(issilicon(L)) // Metal things are usually reflective, or in general aggrivating.
+ return FALSE
+ if(ishuman(L)) // Might be metal, but they're humanoid shaped.
+ var/mob/living/carbon/human/H = L
+ if(H.get_active_hand())
+ var/obj/item/I = H.get_active_hand()
+ if(I.force >= 1.20 * melee_damage_upper)
+ return TRUE
+ else if(istype(L, /mob/living/simple_mob))
+ var/mob/living/simple_mob/S = L
+ if(S.melee_damage_upper > 1.20 * melee_damage_upper)
+ return TRUE
+
+/mob/living/simple_mob/animal/sif/kururak/handle_special()
+ ..()
+ if(client)
+ pack_gauge()
+
+/mob/living/simple_mob/animal/sif/kururak/apply_melee_effects(atom/A) // Only gains instinct.
+ instinct += rand(1, 2)
+ return
+
+/mob/living/simple_mob/animal/sif/kururak/should_special_attack(atom/A)
+ return has_modifier_of_type(/datum/modifier/ace)
+
+/mob/living/simple_mob/animal/sif/kururak/do_special_attack(atom/A)
+ . = TRUE
+ switch(a_intent)
+ if(I_DISARM) // Ranged mob flash, will also confuse borgs rather than stun.
+ tail_flash(A)
+ if(I_GRAB) // Armor-ignoring hit, causes agonizing wounds.
+ set_AI_busy(TRUE)
+ rending_strike(A)
+ set_AI_busy(FALSE)
+
+/mob/living/simple_mob/animal/sif/kururak/verb/do_flash()
+ set category = "Abilities"
+ set name = "Tail Blind"
+ set desc = "Disorient a creature within range."
+
+ if(world.time < last_flash_time + special_attack_cooldown)
+ to_chat(src, span("warning", "You do not have the focus to do this so soon.."))
+ return
+
+ last_flash_time = world.time
+ tail_flash()
+
+/mob/living/simple_mob/animal/sif/kururak/proc/tail_flash(atom/A)
+ set waitfor = FALSE
+
+ if(stat)
+ to_chat(src, span("warning","You cannot move your tails in this state.."))
+ return
+
+ if(!A && src.client)
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(src.Adjacent(C))
+ choices += C
+
+ for(var/obj/mecha/M in view(1,src))
+ if(src.Adjacent(M))
+ choices += M
+
+ if(!choices.len)
+ choices["radial"] = get_turf(src)
+
+ A = input(src,"What do we wish to flash?") in null|choices
+
+
+ visible_message(span("alien","\The [src] flares its tails!"))
+ if(isliving(A))
+ var/mob/living/L = A
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ if(C.stat != DEAD)
+ var/safety = C.eyecheck()
+ if(safety <= 0)
+ var/flash_strength = 5
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ flash_strength *= H.species.flash_mod
+ if(flash_strength > 0)
+ to_chat(H, span("alien","You are disoriented by \the [src]!"))
+ H.Confuse(flash_strength + 5)
+ H.Blind(flash_strength)
+ H.eye_blurry = max(H.eye_blurry, flash_strength + 5)
+ H.flash_eyes()
+ H.adjustHalLoss(flash_strength / 5)
+ H.apply_damage(flash_strength * H.species.flash_burn/5, BURN, BP_HEAD, 0, 0, "Photon burns")
+
+ else if(issilicon(L))
+ if(isrobot(L))
+ var/flashfail = FALSE
+ var/mob/living/silicon/robot/R = L
+ if(R.has_active_type(/obj/item/borg/combat/shield))
+ var/obj/item/borg/combat/shield/shield = locate() in R
+ if(shield)
+ if(shield.active)
+ shield.adjust_flash_count(R, 1)
+ flashfail = TRUE
+ if(!flashfail)
+ to_chat(R, span("alien","Your optics are scrambled by \the [src]!"))
+ R.Confuse(10)
+ R.flash_eyes()
+
+ else
+ L.Confuse(10)
+ L.flash_eyes()
+
+ else
+ for(var/mob/living/carbon/C in oviewers(special_attack_max_range, null))
+ var/safety = C.eyecheck()
+ if(!safety)
+ if(!C.blinded)
+ C.flash_eyes()
+ for(var/mob/living/silicon/robot/R in oviewers(special_attack_max_range, null))
+ if(R.has_active_type(/obj/item/borg/combat/shield))
+ var/obj/item/borg/combat/shield/shield = locate() in R
+ if(shield)
+ if(shield.active)
+ continue
+ R.flash_eyes()
+
+/mob/living/simple_mob/animal/sif/kururak/verb/do_strike()
+ set category = "Abilities"
+ set name = "Rending Strike"
+ set desc = "Strike viciously at an entity within range."
+
+ if(world.time < last_strike_time + special_attack_cooldown)
+ to_chat(src, span("warning", "Your claws cannot take that much stress in so short a time.."))
+ return
+
+ last_strike_time = world.time
+ rending_strike()
+
+/mob/living/simple_mob/animal/sif/kururak/proc/rending_strike(atom/A)
+ if(stat)
+ to_chat(src, span("warning","You cannot strike in this state.."))
+ return
+
+ if(!A && src.client)
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(src.Adjacent(C))
+ choices += C
+
+ for(var/obj/mecha/M in view(1,src))
+ if(src.Adjacent(M))
+ choices += M
+
+ if(!choices.len)
+ to_chat(src, span("warning","There are no viable targets within range..."))
+ return
+
+ A = input(src,"What do we wish to strike?") in null|choices
+
+ if(!A || !src) return
+
+ if(!(src.Adjacent(A))) return
+
+ var/damage_to_apply = rand(melee_damage_lower, melee_damage_upper) + 10
+ if(isliving(A))
+ visible_message(span("danger","\The [src] rakes its claws across [A]."))
+ var/mob/living/L = A
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ H.apply_damage(damage_to_apply, BRUTE, BP_TORSO, 0, 0, "Animal claws")
+
+ else
+ L.adjustBruteLoss(damage_to_apply)
+
+ L.add_modifier(/datum/modifier/grievous_wounds, 60 SECONDS)
+
+ else if(istype(A, /obj/mecha))
+ visible_message(span("danger","\The [src] rakes its claws against \the [A]."))
+ var/obj/mecha/M = A
+ M.take_damage(damage_to_apply)
+ if(prob(3) && do_after(src, 5))
+ visible_message(span("critical","\The [src]'s strike ripped \the [M]'s access hatch open, allowing it to drag [M.occupant] out!"))
+ M.go_out()
+
+ else
+ A.attack_generic(src, damage_to_apply, "rakes its claws against") // Well it's not a mob, and it's not a mech.
+
+/mob/living/simple_mob/animal/sif/kururak/verb/rally_pack() // Mostly for telling other players to follow you. AI Kururaks will auto-follow, if set to.
+ set name = "Rally Pack"
+ set desc = "Tries to command your fellow pack members to follow you."
+ set category = "Abilities"
+
+ if(has_modifier_of_type(/datum/modifier/ace))
+ for(var/mob/living/simple_mob/animal/sif/kururak/K in hearers(7, src))
+ if(K == src)
+ continue
+ if(!K.ai_holder)
+ continue
+ if(K.faction != src.faction)
+ continue
+ var/datum/ai_holder/AI = K.ai_holder
+ to_chat(K, span("notice","The pack leader wishes for you to follow them."))
+ AI.set_follow(src)
+
+/mob/living/simple_mob/animal/sif/kururak/proc/detect_instinct() // Will return the Kururak within 10 tiles that has the highest instinct.
+ var/mob/living/simple_mob/animal/sif/kururak/A
+
+ var/pack_count = 0
+
+ for(var/mob/living/simple_mob/animal/sif/kururak/K in hearers(10, src))
+ if(K == src)
+ continue
+ if(K.stat != DEAD)
+ pack_count++
+ if(K.instinct > src.instinct)
+ A = K
+
+ if(!A && pack_count)
+ A = src
+
+ return A
+
+/mob/living/simple_mob/animal/sif/kururak/proc/pack_gauge() // Check incase we have a client.
+ var/mob/living/simple_mob/animal/sif/kururak/highest_instinct = detect_instinct()
+ if(highest_instinct == src)
+ add_modifier(/datum/modifier/ace, 60 SECONDS)
+ else
+ remove_modifiers_of_type(/datum/modifier/ace)
+
+/datum/ai_holder/simple_mob/intentional/kururak
+ hostile = FALSE
+ retaliate = TRUE
+ cooperative = TRUE
+ can_flee = TRUE
+ flee_when_dying = TRUE
+
+/datum/ai_holder/simple_mob/intentional/kururak/handle_special_strategical()
+ follow_distance = rand(initial(follow_distance), initial(follow_distance) + 2)
+ var/mob/living/simple_mob/animal/sif/kururak/K = holder
+
+ if(istype(K))
+ var/mob/living/simple_mob/animal/sif/kururak/highest_instinct = K.detect_instinct()
+ if(highest_instinct == K)
+ K.add_modifier(/datum/modifier/ace, 60 SECONDS)
+ else
+ K.remove_modifiers_of_type(/datum/modifier/ace)
+
+ if(holder.has_modifier_of_type(/datum/modifier/ace))
+ if(leader && istype(leader, /mob/living/simple_mob/animal/sif/kururak)) // Kururaks will not follow another kururak if they're the pack leader.
+ lose_follow()
+
+ else if(highest_instinct)
+ set_follow(highest_instinct)
+
+ if(holder.has_modifier_of_type(/datum/modifier/ace))
+ hostile = TRUE
+ else
+ hostile = initial(hostile)
+
+/datum/ai_holder/simple_mob/intentional/kururak/pre_special_attack(atom/A)
+ holder.a_intent = I_HURT
+ if(isliving(A))
+ var/mob/living/L = A
+ if(holder.Adjacent(L))
+ holder.a_intent = I_GRAB
+
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ if(!C.eyecheck())
+ if(holder.a_intent != I_GRAB)
+ holder.a_intent = I_DISARM
+
+ if(issilicon(L) && holder.a_intent != I_GRAB)
+ holder.a_intent = I_DISARM
+
+ else if(istype(A, /obj/mecha))
+ holder.a_intent = I_GRAB
+
+/datum/ai_holder/simple_mob/intentional/kururak/post_melee_attack()
+ if(holder.has_modifier_of_type(/datum/modifier/ace))
+ request_help()
+
+// Kururak Ace modifier, given to the one with the highest Instinct.
+
+/datum/modifier/ace
+ name = "Ace"
+ desc = "You are universally superior, in terms of physical prowess."
+ on_created_text = "You feel superior."
+ on_expired_text = "You feel your superiority lessen..."
+ stacks = MODIFIER_STACK_EXTEND
+
+ mob_overlay_state = "ace"
+
+ max_health_flat = 25
+ max_health_percent = 1.2
+ disable_duration_percent = 0.8
+ incoming_damage_percent = 0.7
+ incoming_healing_percent = 1.5
+ outgoing_melee_damage_percent = 1.5
+ evasion = 20
+ bleeding_rate_percent = 0.7
+ attack_speed_percent = 0.8
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm
new file mode 100644
index 00000000000..c59e292a4db
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm
@@ -0,0 +1,503 @@
+// Small creatures that will embed themselves in unsuspecting victim's bodies, drink their blood, and/or eat their organs. Steals some things from borers.
+
+/datum/category_item/catalogue/fauna/iceleech
+ name = "Sivian Fauna - River Leech"
+ desc = "Classification: S Hirudinea phorus \
+
\
+ An incredibly dangerous species of worm phorogenically mutated from a Sivian river leech, \
+ believed to have resulted from corporate mining in the Ullran Expanse; these accusations are \
+ unfounded, however speculation remains.\
+
\
+ The creatures' heads hold four long prehensile tendrils surrounding a central beak, which are \
+ used as locomotive and grappling appendages. Each is capped in a hollow tooth, capable of pumping \
+ chemicals into an unsuspecting host. \
+
\
+ The rear half of the creature is entirely musculature, capped with a sharp, arrowhead-shaped fin."
+ value = CATALOGUER_REWARD_MEDIUM
+
+/mob/living/simple_mob/animal/sif/leech
+ name = "river leech"
+ desc = "What appears to be an oversized leech."
+ tt_desc = "S Hirudinea phorus"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/iceleech)
+
+ faction = "leech"
+
+ icon_state = "leech"
+ item_state = "brainslug"
+ icon_living = "leech"
+ icon_dead = "leech_dead"
+ icon = 'icons/mob/animal.dmi'
+
+ density = FALSE // Non-dense, so things can pass over them.
+
+ status_flags = CANPUSH
+ pass_flags = PASSTABLE
+
+ maxHealth = 100
+ health = 100
+
+ universal_understand = 1
+
+ special_attack_min_range = 0
+ special_attack_max_range = 1
+
+ var/obj/item/organ/external/host_bodypart // Where in the body we are infesting.
+ var/docile = FALSE
+ var/chemicals = 0
+ var/max_chemicals = 400
+ var/list/bodypart_targets = list(BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_TORSO,BP_GROIN,BP_HEAD)
+ var/infest_target = BP_TORSO // The currently chosen bodypart to infest.
+ var/mob/living/carbon/host // Our humble host.
+ var/list/produceable_chemicals = list("inaprovaline","anti_toxin","alkysine","bicaridine","tramadol","kelotane","leporazine","iron","phoron","condensedcapsaicin_v","frostoil")
+ var/randomized_reagent = "iron" // The reagent chosen at random to be produced, if there's no one piloting the worm.
+ var/passive_reagent = "paracetamol" // Reagent passively produced by the leech. Should usually be a painkiller.
+
+ var/feeding_delay = 30 SECONDS // How long do we have to wait to bite our host's organs?
+ var/last_feeding = 0
+
+ intent = I_HELP
+
+ holder_type = /obj/item/weapon/holder/leech
+
+ movement_cooldown = 0
+ aquatic_movement = -2
+
+ melee_damage_lower = 1
+ melee_damage_upper = 5
+ attack_armor_pen = 15
+ attack_sharp = TRUE
+ attacktext = list("nipped", "bit", "pinched")
+
+ armor = list(
+ "melee" = 10,
+ "bullet" = 15,
+ "laser" = -10,
+ "energy" = 0,
+ "bomb" = 10,
+ "bio" = 100,
+ "rad" = 100
+ )
+
+ armor_soak = list(
+ "melee" = 5,
+ "bullet" = 5,
+ "laser" = 0,
+ "energy" = 0,
+ "bomb" = 0,
+ "bio" = 0,
+ "rad" = 0
+ )
+
+ say_list_type = /datum/say_list/leech
+ ai_holder_type = /datum/ai_holder/simple_mob/intentional/leech
+
+/mob/living/simple_mob/animal/sif/leech/IIsAlly(mob/living/L)
+ . = ..()
+
+ var/mob/living/carbon/human/H = L
+ if(!istype(H))
+ return .
+
+ if(istype(L.buckled, /obj/vehicle) || L.hovering) // Ignore people hovering or on boats.
+ return TRUE
+
+ if(!.)
+ var/has_organ = FALSE
+ var/obj/item/organ/internal/O = H.get_active_hand()
+ if(istype(O) && O.robotic < ORGAN_ROBOT && !(O.status & ORGAN_DEAD))
+ has_organ = TRUE
+ return has_organ
+
+/datum/say_list/leech
+ speak = list("...", "Sss..", ". . .","Gss..")
+ emote_see = list("vibrates","looks around", "stares", "extends a proboscis")
+ emote_hear = list("chitters", "clicks", "gurgles")
+
+/mob/living/simple_mob/animal/sif/leech/Initialize()
+ ..()
+
+ verbs += /mob/living/proc/ventcrawl
+ verbs += /mob/living/proc/hide
+
+/mob/living/simple_mob/animal/sif/leech/Stat()
+ ..()
+ if(client.statpanel == "Status")
+ statpanel("Status")
+ if(emergency_shuttle)
+ var/eta_status = emergency_shuttle.get_status_panel_eta()
+ if(eta_status)
+ stat(null, eta_status)
+ stat("Chemicals", chemicals)
+
+/mob/living/simple_mob/animal/sif/leech/do_special_attack(atom/A)
+ . = TRUE
+ if(istype(A, /mob/living/carbon))
+ switch(a_intent)
+ if(I_DISARM) // Poison
+ set_AI_busy(TRUE)
+ poison_inject(src, A)
+ set_AI_busy(FALSE)
+ if(I_GRAB) // Infesting!
+ set_AI_busy(TRUE)
+ do_infest(src, A)
+ set_AI_busy(FALSE)
+
+/mob/living/simple_mob/animal/sif/leech/handle_special()
+ if(prob(5))
+ randomized_reagent = pick(produceable_chemicals)
+
+ var/turf/T = get_turf(src)
+ if(istype(T, /turf/simulated/floor/water) && src.loc == T && !stat) // Are we sitting in water, and alive?
+ alpha = max(5, alpha - 10)
+ if(chemicals + 1 < max_chemicals / 3)
+ chemicals++
+ else
+ alpha = min(255, alpha + 20)
+
+ if(!client && !host)
+ infest_target = pick(bodypart_targets)
+
+ if(host && !stat && !host.stat)
+ if(ai_holder)
+ ai_holder.hostile = FALSE
+ ai_holder.lose_target()
+ alpha = 5
+ if(host.reagents.has_reagent("cordradaxon") && !docile) // Overwhelms the leech with food.
+ var/message = "We feel the rush of cardiac pluripotent cells in your host's blood, lulling us into docility."
+ to_chat(src, span("warning", message))
+ docile = TRUE
+ if(chemicals + 5 <= max_chemicals)
+ chemicals += 5
+
+ else if(docile)
+ var/message = "We shake off our lethargy as the pluripotent cell count declines in our host's blood."
+ to_chat(src, span("notice", message))
+ docile = FALSE
+
+ if(!host.reagents.has_reagent(passive_reagent))
+ host.reagents.add_reagent(passive_reagent, 5)
+ chemicals -= 3
+
+ if(!docile && ishuman(host) && chemicals < max_chemicals)
+ var/mob/living/carbon/human/H = host
+ H.vessel.remove_reagent("blood", 1)
+ if(!H.reagents.has_reagent("inaprovaline"))
+ H.reagents.add_reagent("inaprovaline", 1)
+ chemicals += 2
+
+ if(!client && !docile) // Automatic 'AI' to manage damage levels.
+ if(host.getBruteLoss() >= 30 && chemicals > 50)
+ host.reagents.add_reagent("bicaridine", 5)
+ chemicals -= 30
+
+ if(host.getToxLoss() >= 30 && chemicals > 50)
+ var/randomchem = pickweight("tramadol" = 7, "anti_toxin" = 15, "frostoil" = 3)
+ host.reagents.add_reagent(randomchem, 5)
+ chemicals -= 50
+
+ if(host.getFireLoss() >= 30 && chemicals > 50)
+ host.reagents.add_reagent("kelotane", 5)
+ host.reagents.add_reagent("leporazine", 2)
+ chemicals -= 50
+
+ if(host.getOxyLoss() >= 30 && chemicals > 50)
+ host.reagents.add_reagent("iron", 10)
+ chemicals -= 40
+
+ if(host.getBrainLoss() >= 10 && chemicals > 100)
+ host.reagents.add_reagent("alkysine", 5)
+ host.reagents.add_reagent("tramadol", 3)
+ chemicals -= 100
+
+ if(prob(30) && chemicals > 50)
+ inject_meds(randomized_reagent)
+
+ var/heartless_mod = 0
+ if(ishuman(host)) // Species without hearts mean the worm gets hungry faster, if AI controlled.
+ var/mob/living/carbon/human/H = host
+ if(!H.species.has_organ[O_HEART])
+ heartless_mod = 1
+
+ if(prob(15 + (20 * heartless_mod)))
+ feed_on_organ()
+ else
+ if(ai_holder)
+ ai_holder.hostile = initial(ai_holder.hostile)
+
+ if(host && host.stat == DEAD && istype(get_turf(host), /turf/simulated/floor/water))
+ leave_host()
+
+/mob/living/simple_mob/animal/sif/leech/verb/infest()
+ set category = "Abilities"
+ set name = "Infest"
+ set desc = "Infest a suitable humanoid host."
+
+ if(docile)
+ to_chat(src, span("alium","We are too tired to do this..."))
+ return
+
+ do_infest(usr)
+
+/mob/living/simple_mob/animal/sif/leech/proc/do_infest(var/mob/living/user, var/mob/living/target = null)
+ if(host)
+ to_chat(user, span("alien", "We are already within a host."))
+ return
+
+ if(stat)
+ to_chat(user, span("warning","We cannot infest a target in your current state."))
+ return
+
+ var/mob/living/carbon/M = target
+
+ if(!M && src.client)
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(src.Adjacent(C))
+ choices += C
+
+ if(!choices.len)
+ to_chat(user, span("warning","There are no viable hosts within range..."))
+ return
+
+ M = input(src,"Who do we wish to infest?") in null|choices
+
+ if(!M || !src) return
+
+ if(!(src.Adjacent(M))) return
+
+ if(!istype(M) || M.isSynthetic())
+ to_chat(user, "\The [M] cannot be infested.")
+ return
+
+ if(istype(M,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = M
+
+ var/obj/item/organ/external/E = H.organs_by_name[infest_target]
+ if(!E || E.is_stump() || E.robotic >= ORGAN_ROBOT)
+ to_chat(src,"\The [H] does not have an infestable [infest_target]!")
+
+ var/list/covering_clothing = E.get_covering_clothing()
+ for(var/obj/item/clothing/C in covering_clothing)
+ if(C.armor["melee"] >= 20 + attack_armor_pen)
+ to_chat(user, span("notice","We cannot get through that host's protective gear."))
+ return
+
+ if(!do_after(src,2))
+ to_chat(user, span("notice", "As [M] moves away, we are dislodged and fall to the ground."))
+ return
+
+ if(!M || !src)
+ return
+
+ if(src.stat)
+ to_chat(user, span("warning","We cannot infest a target in your current state."))
+ return
+
+ if(M in view(1, src))
+ to_chat(user,span("alien", "We burrow into [M]'s flesh."))
+ if(!M.stat)
+ to_chat(M, span("critical", "You feel a sharp pain as something digs into your flesh!"))
+
+ src.host = M
+ src.forceMove(M)
+ if(ai_holder)
+ ai_holder.hostile = FALSE
+ ai_holder.lose_target()
+
+ if(istype(M,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = M
+ host_bodypart = H.get_organ(infest_target)
+ host_bodypart.implants |= src
+
+ return
+ else
+ to_chat(user, span("notice","They are no longer in range."))
+ return
+
+/mob/living/simple_mob/animal/sif/leech/verb/uninfest()
+ set category = "Abilities"
+ set name = "Uninfest"
+ set desc = "Leave your current host."
+
+ if(docile)
+ to_chat(src, span("alium","We are too tired to do this..."))
+ return
+
+ leave_host()
+
+/mob/living/simple_mob/animal/sif/leech/proc/leave_host()
+ if(!host)
+ return
+
+ if(ai_holder)
+ ai_holder.hostile = initial(ai_holder.hostile)
+ ai_holder.lose_target()
+
+ host_bodypart.implants -= src
+ host_bodypart = null
+
+ forceMove(get_turf(host))
+
+ reset_view(null)
+
+ host = null
+
+/mob/living/simple_mob/animal/sif/leech/verb/inject_victim()
+ set category = "Abilities"
+ set name = "Incapacitate Potential Host"
+ set desc = "Inject an organic host with an incredibly painful mixture of chemicals."
+
+ if(docile)
+ to_chat(src, span("alium","We are too tired to do this..."))
+ return
+
+ var/mob/living/carbon/M
+ if(src.client)
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(src.Adjacent(C))
+ choices += C
+
+ if(!choices.len)
+ to_chat(src, span("warning","There are no viable hosts within range..."))
+ return
+
+ M = input(src,"Who do we wish to inject?") in null|choices
+
+ if(!M || stat)
+ return
+
+ poison_inject(usr, M)
+
+/mob/living/simple_mob/animal/sif/leech/proc/poison_inject(var/mob/living/user, var/mob/living/carbon/L)
+ if(!L || !Adjacent(L) || stat)
+ return
+
+ var/mob/living/carbon/human/H = L
+
+ if(!istype(H) || H.isSynthetic())
+ to_chat(user, span("warning","You cannot inject this target..."))
+
+ var/obj/item/organ/external/E = H.organs_by_name[infest_target]
+ if(!E || E.is_stump() || E.robotic >= ORGAN_ROBOT)
+ to_chat(src,"\The [H] does not have an infestable [infest_target]!")
+
+ var/list/covering_clothing = E.get_covering_clothing()
+ for(var/obj/item/clothing/C in covering_clothing)
+ if(C.armor["melee"] >= 40 + attack_armor_pen)
+ to_chat(user, span("notice","You cannot get through that host's protective gear."))
+ return
+
+ H.add_modifier(/datum/modifier/poisoned/paralysis, 15 SECONDS)
+
+/mob/living/simple_mob/animal/sif/leech/verb/medicate_host()
+ set category = "Abilities"
+ set name = "Produce Chemicals (50)"
+ set desc = "Inject your host with possibly beneficial chemicals, to keep the blood flowing."
+
+ if(docile)
+ to_chat(src, span("alium","We are too tired to do this..."))
+ return
+
+ if(!host || chemicals <= 50)
+ to_chat(usr, span("alien","We cannot produce any chemicals right now."))
+ return
+
+ if(host)
+ var/chem = input("Select a chemical to produce.", "Chemicals") as null|anything in produceable_chemicals
+ inject_meds(chem)
+
+/mob/living/simple_mob/animal/sif/leech/proc/inject_meds(var/chem)
+ if(host)
+ chemicals = max(1, chemicals - 50)
+ host.reagents.add_reagent(chem, 5)
+ to_chat(src, span("alien","We injected \the [host] with five units of [chem]."))
+
+/mob/living/simple_mob/animal/sif/leech/verb/feed_on_organ()
+ set category = "Abilities"
+ set name = "Feed on Organ"
+ set desc = "Extend probosci to feed on a piece of your host's organs."
+
+ if(docile)
+ to_chat(src, span("alium","We are too tired to do this..."))
+ return
+
+ if(host && world.time >= last_feeding + feeding_delay)
+ var/list/host_internal_organs = host.internal_organs
+
+ for(var/obj/item/organ/internal/O in host_internal_organs) // Remove organs with maximum damage.
+ if(O.damage >= O.max_damage)
+ host_internal_organs -= O
+
+ var/target
+ if(client)
+ target = input("Select an organ to feed on.", "Organs") as null|anything in host_internal_organs
+ if(!target)
+ to_chat(src, span("alien","We decide not to feed."))
+ return
+
+ if(!target)
+ target = pick(host_internal_organs)
+
+ if(target)
+ bite_organ(target)
+
+ else
+ to_chat(src, span("warning","We cannot feed now."))
+
+/mob/living/simple_mob/animal/sif/leech/proc/bite_organ(var/obj/item/organ/internal/O)
+ last_feeding = world.time
+
+ if(O)
+ to_chat(src, span("alien","We feed on [O]."))
+ O.take_damage(2,silent=prob(10))
+ chemicals = min(max_chemicals, chemicals + 60)
+ host.add_modifier(/datum/modifier/grievous_wounds, 60 SECONDS)
+ adjustBruteLoss(rand(-10,-60))
+ adjustFireLoss(rand(-10,-60))
+
+/datum/ai_holder/simple_mob/intentional/leech
+ hostile = TRUE
+ retaliate = TRUE
+ vision_range = 3
+ mauling = TRUE
+ returns_home = TRUE
+ can_flee = TRUE
+ home_low_priority = TRUE // If we've got a target, we're going for them.
+ max_home_distance = 1 // Low to ensure the creature doesn't leave the water unless it has a host.
+
+/datum/ai_holder/simple_mob/intentional/leech/handle_special_strategical()
+ var/mob/living/simple_mob/animal/sif/leech/SL = holder
+ if(!SL.host && !istype(get_turf(SL), /turf/simulated/floor/water))
+ var/list/nearby_water = list()
+ for(var/turf/simulated/floor/water/W in view(holder, 10))
+ nearby_water |= W
+ if(nearby_water && nearby_water.len)
+ var/turf/T = pick(nearby_water)
+ if(T && can_attack(T))
+ home_turf = T
+
+/datum/ai_holder/simple_mob/intentional/leech/special_flee_check()
+ var/mob/living/simple_mob/animal/sif/leech/SL = holder
+
+ if(!SL.host && !istype(get_turf(SL), /turf/simulated/floor/water))
+ return TRUE
+
+/datum/ai_holder/simple_mob/intentional/leech/pre_special_attack(atom/A)
+ if(isliving(A))
+ var/mob/living/L = A
+ if(ishuman(L) && !L.isSynthetic())
+ if(L.incapacitated() || (L.stat && L.stat != DEAD) || L.resting || L.paralysis)
+ holder.a_intent = I_GRAB // Infesting time.
+ else
+ holder.a_intent = I_DISARM // They're standing up! Try to drop or stun them.
+ else
+ holder.a_intent = I_HURT // Otherwise, bite.
+
+ else if(istype(A, /obj/item))
+ var/obj/item/I = A
+ if(istype(I, /obj/item/weapon/reagent_containers/food/snacks))
+ holder.a_intent = I_HURT
+ else
+ holder.a_intent = I_HURT
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm
new file mode 100644
index 00000000000..b2e913bfb24
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/racoon.dm
@@ -0,0 +1,355 @@
+// Sakimm are small scavengers with an adoration for shiny things. They won't attack you for them, but you will be their friend holding something like a coin.
+
+/datum/category_item/catalogue/fauna/sakimm
+ name = "Sivian Fauna - Sakimm"
+ desc = "Classification: S Procyon cogitae \
+
\
+ Small, social omnivores known to collect objects within their dens. \
+ The Sakimm form colonies that have been known to grow up to a hundred individuals. Primarily carnivorous hunters, \
+ they often supplement their diets with nuts, roots, and other fruits. \
+ Individuals are known to steal food and reflective objects from unsuspecting Sivian residents. \
+ It is advised to keep any valuable items within dull wraps when venturing near the den of a Sakimm."
+ value = CATALOGUER_REWARD_EASY
+
+/mob/living/simple_mob/animal/sif/sakimm
+ name = "sakimm"
+ desc = "What appears to be an oversized rodent with hands."
+ tt_desc = "S Procyon cogitae"
+ catalogue_data = list(/datum/category_item/catalogue/fauna/sakimm)
+
+ faction = "sakimm"
+
+ icon_state = "raccoon"
+ icon_living = "raccoon"
+ icon_dead = "raccoon_dead"
+ icon_rest = "raccoon_dead"
+ icon = 'icons/mob/animal.dmi'
+
+ maxHealth = 50
+ health = 50
+ has_hands = TRUE
+ humanoid_hands = TRUE
+
+ pass_flags = PASSTABLE
+
+ universal_understand = 1
+
+ movement_cooldown = 0
+
+ melee_damage_lower = 5
+ melee_damage_upper = 15
+ base_attack_cooldown = 1 SECOND
+ attacktext = list("nipped", "bit", "cut", "clawed")
+
+ armor = list(
+ "melee" = 15,
+ "bullet" = 5,
+ "laser" = 5,
+ "energy" = 0,
+ "bomb" = 10,
+ "bio" = 100,
+ "rad" = 100
+ )
+
+ armor_soak = list(
+ "melee" = 2,
+ "bullet" = 2,
+ "laser" = 0,
+ "energy" = 0,
+ "bomb" = 0,
+ "bio" = 0,
+ "rad" = 0
+ )
+
+ say_list_type = /datum/say_list/sakimm
+ ai_holder_type = /datum/ai_holder/simple_mob/retaliate/cooperative/sakimm
+
+ var/obj/item/clothing/head/hat = null // The hat the Sakimm may be wearing.
+ var/list/friend_loot_list = list(/obj/item/weapon/coin) // What will make this animal non-hostile if held?
+ var/randomize_size = TRUE
+
+/mob/living/simple_mob/animal/sif/sakimm/verb/remove_hat()
+ set name = "Remove Hat"
+ set desc = "Remove the animal's hat. You monster."
+ set category = "Abilities"
+ set src in view(1)
+
+ drop_hat(usr)
+
+/mob/living/simple_mob/animal/sif/sakimm/proc/drop_hat(var/mob/user)
+ if(hat)
+ hat.forceMove(get_turf(user))
+ hat = null
+ update_icon()
+ if(user == src)
+ to_chat(user, "You removed your hat.")
+ return
+ to_chat(user, "You removed \the [src]'s hat. You monster.")
+ else
+ if(user == src)
+ to_chat(user, "You are not wearing a hat!")
+ return
+ to_chat(user, "\The [src] is not wearing a hat!")
+
+/mob/living/simple_mob/animal/sif/sakimm/verb/give_hat()
+ set name = "Give Hat"
+ set desc = "Give the animal a hat. You hero."
+ set category = "Abilities"
+ set src in view(1)
+
+ take_hat(usr)
+
+/mob/living/simple_mob/animal/sif/sakimm/proc/take_hat(var/mob/user)
+ if(hat)
+ if(user == src)
+ to_chat(user, "You already have a hat!")
+ return
+ to_chat(user, "\The [src] already has a hat!")
+ else
+ if(user == src)
+ if(istype(get_active_hand(), /obj/item/clothing/head))
+ hat = get_active_hand()
+ drop_from_inventory(hat, src)
+ hat.forceMove(src)
+ to_chat(user, "You put on the hat.")
+ update_icon()
+ return
+ else if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+
+ if(istype(H.get_active_hand(), /obj/item/clothing/head) && !get_active_hand())
+ var/obj/item/clothing/head/newhat = H.get_active_hand()
+ H.drop_from_inventory(newhat, get_turf(src))
+ if(!stat)
+ intent = I_HELP
+ newhat.attack_hand(src)
+ else if(src.get_active_hand())
+ to_chat(user, "\The [src] seems busy with \the [get_active_hand()] already!")
+
+ else
+ to_chat(user, "You aren't holding a hat...")
+
+/datum/say_list/sakimm
+ speak = list("Shurr.", "|R|rr?", "Hss.")
+ emote_see = list("sniffs","looks around", "rubs its hands")
+ emote_hear = list("chitters", "clicks")
+
+/mob/living/simple_mob/animal/sif/sakimm/Destroy()
+ if(hat)
+ drop_hat(src)
+ ..()
+
+/mob/living/simple_mob/animal/sif/sakimm/update_icon()
+ overlays.Cut()
+ ..()
+ if(hat)
+ var/hat_state = hat.item_state ? hat.item_state : hat.icon_state
+ var/image/I = image('icons/mob/head.dmi', src, hat_state)
+ I.pixel_y = -15 // Sakimm are tiny!
+ I.appearance_flags = RESET_COLOR
+ add_overlay(I)
+
+/mob/living/simple_mob/animal/sif/sakimm/Initialize()
+ . = ..()
+
+ verbs += /mob/living/proc/ventcrawl
+ verbs += /mob/living/proc/hide
+
+ if(randomize_size)
+ adjust_scale(rand(8, 11) / 10)
+
+/mob/living/simple_mob/animal/sif/sakimm/IIsAlly(mob/living/L)
+ . = ..()
+
+ var/mob/living/carbon/human/H = L
+ if(!istype(H))
+ return .
+
+ if(!.)
+ var/has_loot = FALSE
+ var/obj/item/I = H.get_active_hand()
+ if(I)
+ for(var/item_type in friend_loot_list)
+ if(istype(I, item_type))
+ has_loot = TRUE
+ break
+ return has_loot
+
+/datum/ai_holder/simple_mob/retaliate/cooperative/sakimm/handle_special_strategical() // Just needs to take hats.
+ var/mob/living/simple_mob/animal/sif/sakimm/S = holder
+
+ if(holder.get_active_hand() && istype(holder.get_active_hand(), /obj/item/clothing/head) && !S.hat)
+ var/obj/item/I = holder.get_active_hand()
+ S.take_hat(S)
+ holder.visible_message("\The [holder] wears \the [I]")
+
+/mob/living/simple_mob/animal/sif/sakimm/intelligent
+ desc = "What appears to be an oversized rodent with hands. This one has a curious look in its eyes."
+ ai_holder_type = /datum/ai_holder/simple_mob/intentional/sakimm
+ randomize_size = FALSE // Most likely to have a hat.
+
+/datum/ai_holder/simple_mob/intentional/sakimm
+ hostile = FALSE
+ retaliate = TRUE
+ vision_range = 10
+ can_flee = TRUE
+ flee_when_dying = TRUE
+
+ var/greed = 0 // The probability we will try to steal something. Increases over time if we are not holding something, or wearing a hat.
+ var/list/steal_loot_list = list(/obj/item/weapon/coin, /obj/item/weapon/gun, /obj/item/weapon/fossil, /obj/item/stack/material, /obj/item/weapon/material, /obj/item/weapon/reagent_containers/food/snacks, /obj/item/clothing/head, /obj/item/weapon/reagent_containers/glass, /obj/item/device/flashlight, /obj/item/stack/medical, /obj/item/seeds, /obj/item/weapon/spacecash)
+ var/hoard_items = TRUE
+ var/hoard_distance = 1 // How far an item can be from the Sakimm's home turf to be counted inside its 'hoard'.
+ var/original_home_distance = null
+ var/search_delay = 2 SECONDS // How often can we look for item targets?
+ var/last_search = 0
+
+/datum/ai_holder/simple_mob/intentional/sakimm/New()
+ ..()
+ original_home_distance = max_home_distance
+
+/datum/ai_holder/simple_mob/intentional/sakimm/post_melee_attack(atom/A)
+ if(istype(A, /obj/item) && !holder.get_active_hand() && holder.Adjacent(A))
+ var/obj/item/I = A
+ I.attack_hand(holder)
+ lose_target()
+ if(istype(A,/mob/living) && holder.Adjacent(A)) // Not the dumbest tool in the shed. If we're fighting, we're gonna dance around them.
+ holder.IMove(get_step(holder, pick(alldirs)))
+ holder.face_atom(A)
+ request_help() // And we're going to call friends, too.
+
+/datum/ai_holder/simple_mob/intentional/sakimm/list_targets()
+ . = hearers(vision_range, holder) - holder
+
+ var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
+
+ for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines))
+ if(can_see(holder, HM, vision_range))
+ . += HM
+
+ if(holder.get_active_hand()) // We don't want item targets if we have an item!
+ return .
+
+ if(world.time <= last_search + search_delay) // Don't spam searching for item targets, since they can be in areas with a -lot- of items.
+ return .
+
+ for(var/obj/item/I in view(holder, vision_range))
+ last_search = world.time
+ if(!hoard_items || get_dist(I, home_turf) <= 1)
+ continue
+ for(var/itemtype in steal_loot_list)
+ if(istype(I, itemtype))
+ if(!I.anchored)
+ . += I
+ break
+
+ . -= holder.contents
+
+/datum/ai_holder/simple_mob/intentional/sakimm/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
+ var/can_pick_mobs = TRUE
+
+ if(!hostile)
+ can_pick_mobs = FALSE
+
+ . = list()
+ if(!has_targets_list)
+ possible_targets = list_targets()
+
+ for(var/possible_target in possible_targets)
+ var/atom/A = possible_target
+ if(found(A))
+ . = list(A)
+ break
+ if(istype(A, /mob/living) && !can_pick_mobs)
+ continue
+ if(can_attack(A)) // Can we attack it?
+ . += A
+ continue
+
+ for(var/obj/item/I in .)
+ last_search = world.time
+ if(!hoard_items || get_dist(I, home_turf) <= 1)
+ . -= I
+
+ var/new_target = pick_target(.)
+ give_target(new_target)
+ return new_target
+
+/datum/ai_holder/simple_mob/intentional/sakimm/pre_melee_attack(atom/A)
+ if(isliving(A))
+ var/mob/living/L = A
+ if(holder.get_active_hand()) // Are we holding something? If so, drop it, we have a new target to kill, and we shouldn't use their weapons.
+ holder.drop_from_inventory(holder.get_active_hand(), get_turf(holder))
+
+ if(ishuman(L))
+ if(L.incapacitated(INCAPACITATION_DISABLED)) // Is our target on the ground? If so, let's scratch!
+ if(prob(30)) // Unless we decide to cut and run, and take their stuff while we're at it.
+ var/turf/T = get_turf(L)
+ var/obj/item/IT = locate() in T.contents
+ if(IT)
+ lose_target()
+ give_target(IT)
+ return
+ holder.a_intent = I_HURT
+ else
+ holder.a_intent = I_DISARM // Otherwise, try to disarm them!
+ else
+ holder.a_intent = I_HURT // We can't disarm you, so we're going to hurt you.
+
+ else if(istype(A, /obj/item))
+ var/obj/item/I = A
+ if(istype(I, /obj/item/weapon/reagent_containers/food/snacks)) // If we can't pick it up, or it's edible, go to harm.
+ holder.a_intent = I_HURT
+ else
+ holder.a_intent = I_HELP
+
+ else
+ holder.a_intent = I_HURT
+
+/datum/ai_holder/simple_mob/intentional/sakimm/should_go_home()
+ if((!returns_home && !holder.get_active_hand()) || !home_turf) // If we have an item, we want to go home.
+ return FALSE
+ if(get_dist(holder, home_turf) > max_home_distance)
+ if(!home_low_priority)
+ return TRUE
+ else if(!leader && !target)
+ return TRUE
+ return FALSE
+
+/datum/ai_holder/simple_mob/intentional/sakimm/handle_special_tactic()
+ var/mob/living/simple_mob/animal/sif/sakimm/S = holder
+ if(S.hat)
+ hoard_items = FALSE
+ else
+ hoard_items = TRUE
+
+/datum/ai_holder/simple_mob/intentional/sakimm/handle_special_strategical()
+ var/mob/living/simple_mob/animal/sif/sakimm/S = holder
+ var/carrying_item = FALSE
+
+ if(holder.get_active_hand()) // Do we have loot?
+ if(istype(holder) && istype(holder.get_active_hand(), /obj/item/clothing/head) && !S.hat)
+ var/obj/item/I = holder.get_active_hand()
+ S.take_hat(S)
+ holder.visible_message("\The [holder] wears \the [I]")
+ carrying_item = TRUE
+
+ if(istype(holder) && S.hat) // Do we have a hat? Hats are loot.
+ carrying_item = TRUE
+
+ if(!carrying_item) // Not carrying or wearing anything? We want to carry something more.
+ greed++
+ greed = min(95, greed)
+ else
+ greed = 0
+ if(!target && prob(5 + greed) && !holder.get_active_hand())
+ find_target()
+ if(holder.get_active_hand() && hoard_items)
+ lose_target()
+ max_home_distance = 1
+ if(get_dist(holder, home_turf) <= max_home_distance)
+ holder.drop_from_inventory(holder.get_active_hand(), get_turf(holder))
+ if(!holder.get_active_hand())
+ max_home_distance = original_home_distance
+
+/datum/ai_holder/simple_mob/intentional/sakimm/special_flee_check()
+ return holder.get_active_hand()
diff --git a/code/modules/projectiles/projectile/blob.dm b/code/modules/projectiles/projectile/blob.dm
index 926c78a0a03..7ebc5cdfad7 100644
--- a/code/modules/projectiles/projectile/blob.dm
+++ b/code/modules/projectiles/projectile/blob.dm
@@ -26,13 +26,14 @@
..()
/obj/item/projectile/energy/blob/on_impact(var/atom/A)
- var/turf/location = get_turf(src)
- var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem
- S.attach(location)
- S.set_up(reagents, splatter_volume, 0, location)
- playsound(location, 'sound/effects/slime_squish.ogg', 30, 1, -3)
- spawn(0)
- S.start()
+ if(splatter)
+ var/turf/location = get_turf(src)
+ var/datum/effect/effect/system/smoke_spread/chem/S = new /datum/effect/effect/system/smoke_spread/chem
+ S.attach(location)
+ S.set_up(reagents, splatter_volume, 0, location)
+ playsound(location, 'sound/effects/slime_squish.ogg', 30, 1, -3)
+ spawn(0)
+ S.start()
..()
/obj/item/projectile/energy/blob/proc/ready_chemicals()
@@ -61,3 +62,11 @@
splatter = TRUE
flammability = 0.25
my_chems = list("fuel", "mold")
+
+/obj/item/projectile/energy/blob/freezing
+ my_chems = list("frostoil")
+ modifier_type_to_apply = /datum/modifier/chilled
+ modifier_duration = 1 MINUTE
+
+/obj/item/projectile/energy/blob/freezing/splattering
+ splatter = TRUE
diff --git a/html/changelogs/Mechoid - SifAnimalExpansion1.yml b/html/changelogs/Mechoid - SifAnimalExpansion1.yml
new file mode 100644
index 00000000000..b8e67e02b2e
--- /dev/null
+++ b/html/changelogs/Mechoid - SifAnimalExpansion1.yml
@@ -0,0 +1,38 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Mechoid
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "The Shimm, Frostfly, Glitterfly, River Leech, Crystal-Feathered Duck, and Kururak are now able to be used on the map and PoIs."
+ - tweak: "Glass jars can now hold glitterflies, river leeches, and frostflies."
+ - tweak: "Glass jars now respect mobs that exist over the lighting plane, adding a special overlay visible when on the ground."
diff --git a/icons/mob/64x64.dmi b/icons/mob/64x64.dmi
index a829ee2c6fd..9c0c5b61e31 100644
Binary files a/icons/mob/64x64.dmi and b/icons/mob/64x64.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 2f22e0ddaaa..1f7c06a0371 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/modifier_effects.dmi b/icons/mob/modifier_effects.dmi
index a28dc6a5205..99a4f6e4ed0 100644
Binary files a/icons/mob/modifier_effects.dmi and b/icons/mob/modifier_effects.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index 977e49d5799..43f1593a20c 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -2358,7 +2358,13 @@
#include "code\modules\mob\living\simple_mob\subtypes\animal\pets\parrot.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\diyaab.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\fluffy_vr.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\duck.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\frostfly.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\glitterfly.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\hooligan_crab.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\kururak.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\leech.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\racoon.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\savik.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\shantak.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\sif.dm"