Added grafadreka.

More work on drakes.

Added grafadreka den site.

Working on refining drake AI.

Fixing drake languages.
This commit is contained in:
MistakeNot4892
2022-08-11 23:21:04 +10:00
parent bbddee97bb
commit dcbf10e28a
31 changed files with 859 additions and 55 deletions

View File

@@ -142,4 +142,3 @@
adjust_instability(controlled_mobs.len) adjust_instability(controlled_mobs.len)
to_chat(user, "<span class='notice'>You command your [controlled_mobs.len > 1 ? "entities" : "[controlled_mobs[1]]"] to move \ to_chat(user, "<span class='notice'>You command your [controlled_mobs.len > 1 ? "entities" : "[controlled_mobs[1]]"] to move \
towards \the [T].</span>") towards \the [T].</span>")

View File

@@ -915,4 +915,9 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
// this gets called when the item gets chucked by the vending machine // this gets called when the item gets chucked by the vending machine
/obj/item/proc/vendor_action(var/obj/machinery/vending/V) /obj/item/proc/vendor_action(var/obj/machinery/vending/V)
return return
// Called when a simple animal finishes eating the item.
/obj/item/proc/animal_consumed(var/mob/user)
user.visible_message(SPAN_NOTICE("\The [user] finishes eating \the [src]."), SPAN_NOTICE("You finish eating \the [src]."))
qdel(src)

View File

@@ -123,6 +123,15 @@
return pick(prob(1);/mob/living/simple_mob/animal/sif/kururak/hibernate, return pick(prob(1);/mob/living/simple_mob/animal/sif/kururak/hibernate,
prob(20);/mob/living/simple_mob/animal/sif/kururak) prob(20);/mob/living/simple_mob/animal/sif/kururak)
/obj/random/mob/sif/grafadreka
name = "Random Grafadreka"
desc = "This is a random grafadreka, either waking or hibernating."
icon_state = "frost"
/obj/random/mob/sif/grafadreka/item_to_spawn()
return pick(prob(3);/mob/living/simple_mob/animal/sif/grafadreka/wild/hibernate,
prob(12);/mob/living/simple_mob/animal/sif/grafadreka/wild)
/obj/random/mob/spider /obj/random/mob/spider
name = "Random Spider" //Spiders should patrol where they spawn. name = "Random Spider" //Spiders should patrol where they spawn.
desc = "This is a random boring spider." desc = "This is a random boring spider."
@@ -397,6 +406,11 @@
/mob/living/simple_mob/animal/sif/kururak, /mob/living/simple_mob/animal/sif/kururak,
/mob/living/simple_mob/animal/sif/kururak /mob/living/simple_mob/animal/sif/kururak
), ),
prob(3);list(
/mob/living/simple_mob/animal/sif/grafadreka/wild,
/mob/living/simple_mob/animal/sif/grafadreka/wild,
/mob/living/simple_mob/animal/sif/grafadreka/wild
),
prob(5);list( prob(5);list(
/mob/living/simple_mob/animal/sif/glitterfly, /mob/living/simple_mob/animal/sif/glitterfly,
/mob/living/simple_mob/animal/sif/glitterfly, /mob/living/simple_mob/animal/sif/glitterfly,

View File

@@ -76,7 +76,7 @@
return TRUE return TRUE
if(try_grab_occupant(user)) if(try_grab_occupant(user))
return TRUE return TRUE
if(!do_after(user, src, 1 SECOND)) if(!do_after(user, 1 SECOND, src))
return TRUE return TRUE
if(length(contents)) if(length(contents))
to_chat(user, SPAN_WARNING("You can't hide in \the [src]; it's occupied.")) to_chat(user, SPAN_WARNING("You can't hide in \the [src]; it's occupied."))
@@ -104,7 +104,6 @@
/obj/structure/animal_den/ghost_join /obj/structure/animal_den/ghost_join
var/mob/living/critter var/mob/living/critter
var/ban_check = "Critter" var/ban_check = "Critter"
var/welcome_text
/obj/structure/animal_den/ghost_join/Initialize() /obj/structure/animal_den/ghost_join/Initialize()
. = ..() . = ..()
@@ -120,40 +119,39 @@
critter = null critter = null
/obj/structure/animal_den/ghost_join/examine(mob/user, infix, suffix) /obj/structure/animal_den/ghost_join/examine(mob/user, infix, suffix)
. = ..() var/list/output = ..()
if(isobserver(user)) if(isobserver(user))
if(critter) if(critter)
if(ban_check && ckey_is_jobbanned(user.ckey, ban_check)) if(ban_check && ckey_is_jobbanned(user.ckey, ban_check))
to_chat(user, SPAN_WARNING("You are banned from [ban_check] roles and cannot join via this den.")) output += SPAN_WARNING("You are banned from [ban_check] roles and cannot join via this den.")
else if(user.MayRespawn(TRUE)) else if(user.MayRespawn(TRUE))
to_chat(user, SPAN_NOTICE("<b>Click on the den to join as \a [critter].</b>")) output += SPAN_NOTICE("<b>Click on the den to join as \a [critter].</b>")
else else
to_chat(user, SPAN_WARNING("This den is no longer available for joining.")) output += SPAN_WARNING("This den is no longer available for joining.")
return output
/obj/structure/animal_den/ghost_join/attack_ghost(mob/user) /obj/structure/animal_den/ghost_join/attack_ghost(mob/user)
if(critter) if(!critter)
transfer_personality(user) return ..()
if(ban_check && ckey_is_jobbanned(user.ckey, ban_check))
to_chat(user, SPAN_WARNING("You are banned from [ban_check] roles and cannot join via this den."))
return return
return ..() transfer_personality(user)
/obj/structure/animal_den/ghost_join/proc/transfer_personality(var/mob/user) /obj/structure/animal_den/ghost_join/proc/transfer_personality(var/mob/user)
set waitfor = FALSE set waitfor = FALSE
if(!critter) if(!critter)
return return
// Transfer over mind and key.
if(user.mind) if(user.mind)
user.mind.transfer_to(critter) user.mind.transfer_to(critter)
else critter.key = user.key
critter.key = user.key
var/mob/living/critter_ref = critter var/mob/living/critter_ref = critter
critter = null critter = null
// Sleep long enough for them to login and get any other text out of the way.
sleep(1)
to_chat(critter_ref, SPAN_NOTICE("<b>You are \a [critter]!</b>"))
if(welcome_text)
to_chat(critter_ref, SPAN_NOTICE(welcome_text))
// Sleep long enough for the logged-in critter to update state and regen icon. // Sleep long enough for the logged-in critter to update state and regen icon.
sleep(SSmobs.wait) sleep(SSmobs.wait)
if(critter_ref.resting) // get up lazybones if(critter_ref.resting) // get up lazybones

View File

@@ -97,9 +97,13 @@
if(!can_forage(critter)) if(!can_forage(critter))
to_chat(critter, SPAN_WARNING("You cannot see any edible fruit on \the [src].")) to_chat(critter, SPAN_WARNING("You cannot see any edible fruit on \the [src]."))
return TRUE return TRUE
critter.set_AI_busy(TRUE)
show_animal_foraging_message(critter) show_animal_foraging_message(critter)
if(!do_after(critter, 2 SECONDS, src) || QDELETED(src) || !can_forage(user)) critter.setClickCooldown(5 SECONDS)
if(!do_after(critter, 5 SECONDS, src) || QDELETED(src) || !can_forage(user))
critter.set_AI_busy(FALSE)
return TRUE return TRUE
critter.set_AI_busy(FALSE)
show_animal_eating_message(critter) show_animal_eating_message(critter)
playsound(critter, 'sound/items/eatfood.ogg', rand(10,50), 1) playsound(critter, 'sound/items/eatfood.ogg', rand(10,50), 1)
harvest_count++ harvest_count++

View File

@@ -47,6 +47,7 @@ var/global/list/turf_edge_cache = list()
to_chat(critter, SPAN_WARNING("There is already a den here.")) to_chat(critter, SPAN_WARNING("There is already a den here."))
return TRUE return TRUE
critter.visible_message(SPAN_NOTICE("\The [critter] begins digging industriously.")) critter.visible_message(SPAN_NOTICE("\The [critter] begins digging industriously."))
critter.setClickCooldown(10 SECONDS)
if(!do_after(critter, 10 SECONDS, src)) if(!do_after(critter, 10 SECONDS, src))
return TRUE return TRUE
if(locate(/obj/structure/animal_den) in contents) if(locate(/obj/structure/animal_den) in contents)

View File

@@ -97,4 +97,4 @@
// Apply movement delay. // Apply movement delay.
// Player movement has more factors but its all in the client and fixing that would be its own project. // Player movement has more factors but its all in the client and fixing that would be its own project.
setMoveCooldown(movement_delay()) setMoveCooldown(movement_delay())
return return

View File

@@ -23,4 +23,5 @@ var/global/list/_default_mob_emotes = list(
/decl/emote/audible/choke, /decl/emote/audible/choke,
/decl/emote/audible/moan, /decl/emote/audible/moan,
/decl/emote/audible/gnarl, /decl/emote/audible/gnarl,
/decl/emote/audible/snarl
) )

View File

@@ -33,6 +33,10 @@
emote_message_3p = "chokes." emote_message_3p = "chokes."
conscious = FALSE conscious = FALSE
/decl/emote/audible/snarl
key = "snarl"
emote_message_3p = "snarls menacingly."
/decl/emote/audible/gnarl /decl/emote/audible/gnarl
key = "gnarl" key = "gnarl"
emote_message_3p = "gnarls and shows USER_THEIR teeth." emote_message_3p = "gnarls and shows USER_THEIR teeth."

View File

@@ -29,8 +29,8 @@ var/global/list/emotes_by_key
var/emote_message_radio_synthetic // As above, but for synthetics. var/emote_message_radio_synthetic // As above, but for synthetics.
var/emote_message_muffled // A message to show if the emote is audible and the user is muzzled. var/emote_message_muffled // A message to show if the emote is audible and the user is muzzled.
var/list/emote_sound // A sound for the emote to play. var/list/emote_sound // A sound for the emote to play.
// Can either be a single sound, a list of sounds to pick from, or an // Can either be a single sound, a list of sounds to pick from, or an
// associative array of gender to single sounds/a list of sounds. // associative array of gender to single sounds/a list of sounds.
var/list/emote_sound_synthetic // As above, but used when check_synthetic() is true. var/list/emote_sound_synthetic // As above, but used when check_synthetic() is true.
var/emote_volume = 50 // Volume of sound to play. var/emote_volume = 50 // Volume of sound to play.
@@ -83,7 +83,7 @@ var/global/list/emotes_by_key
var/mob/M = user var/mob/M = user
if(M.restrained()) if(M.restrained())
to_chat(user, SPAN_WARNING("You are restrained and cannot do that.")) to_chat(user, SPAN_WARNING("You are restrained and cannot do that."))
return return FALSE
var/atom/target var/atom/target
if(can_target() && extra_params) if(can_target() && extra_params)
@@ -104,7 +104,7 @@ var/global/list/emotes_by_key
if(!target) if(!target)
to_chat(user, SPAN_WARNING("You cannot see a '[extra_params]' within range.")) to_chat(user, SPAN_WARNING("You cannot see a '[extra_params]' within range."))
return return FALSE
var/use_1p = get_emote_message_1p(user, target, extra_params) var/use_1p = get_emote_message_1p(user, target, extra_params)
if(use_1p) if(use_1p)
@@ -136,7 +136,7 @@ var/global/list/emotes_by_key
var/mob/living/L = user var/mob/living/L = user
if(L.silent) if(L.silent)
M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired, runemessage = "opens their mouth silently!") M.visible_message(message = "[user] opens their mouth silently!", self_message = "You cannot say anything!", blind_message = emote_message_impaired, runemessage = "opens their mouth silently!")
return return FALSE
else else
M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio, runemessage = prefinal_3p) M.audible_message(message = use_3p, self_message = use_1p, deaf_message = emote_message_impaired, hearing_distance = use_range, radio_message = use_radio, runemessage = prefinal_3p)
else else
@@ -144,6 +144,7 @@ var/global/list/emotes_by_key
do_extra(user, target) do_extra(user, target)
do_sound(user) do_sound(user)
return TRUE
/decl/emote/proc/replace_target_tokens(var/msg, var/atom/target) /decl/emote/proc/replace_target_tokens(var/msg, var/atom/target)
. = msg . = msg

View File

@@ -230,14 +230,22 @@
/obj/item/reagent_containers/food/snacks/attack_generic(var/mob/living/user) /obj/item/reagent_containers/food/snacks/attack_generic(var/mob/living/user)
if(!isanimal(user) && !isalien(user)) if(!isanimal(user) && !isalien(user))
return return
user.visible_message("<b>[user]</b> nibbles away at \the [src].","You nibble away at \the [src].") if(isanimal(user))
var/mob/living/simple_mob/animal/critter = user
critter.eat_food_item(src, bitesize)
else
user.visible_message("<b>[user]</b> nibbles away at \the [src].","You nibble away at \the [src].")
if(reagents)
reagents.trans_to_mob(user, bitesize, CHEM_INGEST)
bitecount++ bitecount++
if(reagents)
reagents.trans_to_mob(user, bitesize, CHEM_INGEST)
spawn(5) spawn(5)
if(!src && !user.client) if(!src && !user.client)
user.custom_emote(1,"[pick("burps", "cries for more", "burps twice", "looks at the area where the food was")]") user.custom_emote(1,"[pick("burps", "cries for more", "burps twice", "looks at the area where the food was")]")
qdel(src) qdel(src)
if(!QDELETED(src))
On_Consume(user)
/obj/item/reagent_containers/food/snacks/animal_consumed(var/mob/user)
On_Consume(user) On_Consume(user)
////////////////////////////////////////////////// //////////////////////////////////////////////////

View File

@@ -75,4 +75,16 @@
flags = RESTRICTED flags = RESTRICTED
machine_understands = 0 machine_understands = 0
space_chance = 100 space_chance = 100
syllables = list("chirp", "squawk", "tweet") syllables = list("chirp", "squawk", "tweet")
/datum/language/drake
name = "Drake"
desc = "Hiss hiss, feed me siffets."
speech_verb = "hisses"
ask_verb = "chirps"
exclaim_verb = "rumbles"
key = "D"
flags = RESTRICTED
machine_understands = 0
space_chance = 30
syllables = list("hss", "ssh", "khs", "hrr", "rrr", "rrn")

View File

@@ -1161,5 +1161,7 @@
/mob/living/proc/rip_out_internal_organ(var/zone, var/skip_wounding = FALSE, var/damage_descriptor) /mob/living/proc/rip_out_internal_organ(var/zone, var/skip_wounding = FALSE, var/damage_descriptor)
if(length(internal_organs)) if(length(internal_organs))
. = pick_n_take(internal_organs) . = pick_n_take(internal_organs)
if(ispath(.))
. = new .(src)
if(!skip_wounding) if(!skip_wounding)
take_damage(rand(10,20)) take_damage(rand(10,20))

View File

@@ -16,7 +16,7 @@
// Cooldown testing is done at click code (for players) and interface code (for AI). // Cooldown testing is done at click code (for players) and interface code (for AI).
setClickCooldown(get_attack_speed()) setClickCooldown(get_attack_speed())
// Returns a value, but will be lost if // Returns a value, but will be lost if
. = do_attack(A, their_T) . = do_attack(A, their_T)
if(melee_attack_delay) if(melee_attack_delay)
@@ -103,7 +103,7 @@
try_reload() try_reload()
return FALSE return FALSE
visible_message("<span class='danger'><b>\The [src]</b> fires at \the [A]!</span>") visible_message(SPAN_DANGER("\The [src] [projectileverb] at \the [A]!"))
shoot(A) shoot(A)
if(casingtype) if(casingtype)
new casingtype(loc) new casingtype(loc)
@@ -259,4 +259,4 @@
/mob/living/simple_mob/proc/special_pre_animation(atom/A) /mob/living/simple_mob/proc/special_pre_animation(atom/A)
do_windup_animation(A, special_attack_delay) // Semi-placeholder. do_windup_animation(A, special_attack_delay) // Semi-placeholder.
/mob/living/simple_mob/proc/special_post_animation(atom/A) /mob/living/simple_mob/proc/special_post_animation(atom/A)

View File

@@ -48,9 +48,12 @@
apply_damage(damage = harm_intent_damage, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE) apply_damage(damage = harm_intent_damage, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE)
L.visible_message("<span class='warning'>\The [L] [response_harm] \the [src]!</span>") L.visible_message("<span class='warning'>\The [L] [response_harm] \the [src]!</span>")
L.do_attack_animation(src) L.do_attack_animation(src)
IWasAttackedBy(L)
return return
/mob/living/simple_mob/proc/IWasAttackedBy(var/mob/living/attacker)
return
// When somoene clicks us with an item in hand // When somoene clicks us with an item in hand
/mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user) /mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user)
@@ -304,4 +307,3 @@
if(ignore_thickness) if(ignore_thickness)
return TRUE return TRUE
return !thick_armor return !thick_armor

View File

@@ -17,13 +17,31 @@
/mob/living/simple_mob/examine(mob/user) /mob/living/simple_mob/examine(mob/user)
. = ..() . = ..()
if(stat != DEAD && user && harvest_tool && (get_dist(user, src) <= 3)) if(user && (get_dist(user, src) <= 3))
. += "<span class='notice'>\The [src] can be [harvest_verb] with a [initial(harvest_tool.name)] every [round(harvest_cooldown, 0.1)] minutes.</span>"
var/time_to_harvest = (harvest_recent + harvest_cooldown) - world.time var/datum/gender/G = gender_datums[get_visible_gender()]
if(time_to_harvest > 0) if(stat == DEAD)
. += "<span class='notice'>It can be [harvest_verb] in [time_to_harvest / (1 MINUTE)] second(s).</span>" . += "<b><span class='cult'>[G.He] [G.is] dead.</span></b>"
return
if(harvest_tool)
. += SPAN_NOTICE("\The [src] can be [harvest_verb] with a [initial(harvest_tool.name)] every [round(harvest_cooldown, 0.1)] minutes.")
var/time_to_harvest = (harvest_recent + harvest_cooldown) - world.time
if(time_to_harvest > 0)
. += SPAN_NOTICE("It can be [harvest_verb] in [time_to_harvest / (1 MINUTE)] second(s).")
else
. += SPAN_NOTICE("It can be [harvest_verb] now.")
var/percent_health = health / maxHealth
if(percent_health >= 1)
. += SPAN_NOTICE("[G.He] [G.is] uninjured.")
else if(percent_health >= 0.7)
. += SPAN_WARNING("[G.He] [G.is] mildly injured.")
else if(percent_health >= 0.4)
. += SPAN_WARNING("[G.He] [G.is] moderately injured.")
else else
. += "<span class='notice'>It can be [harvest_verb] now.</span>" . += SPAN_DANGER("[G.He] [G.is] badly injured.")
/mob/living/simple_mob/proc/livestock_harvest(var/obj/item/tool, var/mob/living/user) /mob/living/simple_mob/proc/livestock_harvest(var/obj/item/tool, var/mob/living/user)
if(!LAZYLEN(harvest_results)) // Might be a unique interaction of an object using the proc to do something weird, or just someone's a donk. if(!LAZYLEN(harvest_results)) // Might be a unique interaction of an object using the proc to do something weird, or just someone's a donk.

View File

@@ -19,9 +19,12 @@
if(can_special_attack(A) && special_attack_target(A)) if(can_special_attack(A) && special_attack_target(A))
return return
if(projectiletype) if(can_projectile_attack(A))
shoot_target(A) shoot_target(A)
/mob/living/simple_mob/proc/can_projectile_attack(var/atom/A)
return !!projectiletype
/mob/living/simple_mob/proc/do_interaction(var/atom/A) /mob/living/simple_mob/proc/do_interaction(var/atom/A)
switch(a_intent) switch(a_intent)
if(I_HURT) if(I_HURT)

View File

@@ -120,6 +120,7 @@
var/special_attack_charges = null // If set, special attacks will work off of a charge system, and won't be usable if all charges are expended. Good for grenades. var/special_attack_charges = null // If set, special attacks will work off of a charge system, and won't be usable if all charges are expended. Good for grenades.
var/special_attack_cooldown = null // If set, special attacks will have a cooldown between uses. var/special_attack_cooldown = null // If set, special attacks will have a cooldown between uses.
var/last_special_attack = null // world.time when a special attack occured last, for cooldown calculations. var/last_special_attack = null // world.time when a special attack occured last, for cooldown calculations.
var/projectileverb = "fires"
//Damage resistances //Damage resistances
var/grab_resist = 0 // Chance for a grab attempt to fail. Note that this is not a true resist and is just a prob() of failure. var/grab_resist = 0 // Chance for a grab attempt to fail. Note that this is not a true resist and is just a prob() of failure.
@@ -285,4 +286,4 @@
add_overlay(hud_list) add_overlay(hud_list)
/decl/mob_organ_names /decl/mob_organ_names
var/list/hit_zones = list("body") //When in doubt, it's probably got a body. var/list/hit_zones = list("body") //When in doubt, it's probably got a body.

View File

@@ -26,41 +26,65 @@
var/forager = TRUE // Can eat from trees and bushes. var/forager = TRUE // Can eat from trees and bushes.
var/scavenger = FALSE // Can eat from corpses. var/scavenger = FALSE // Can eat from corpses.
var/burrower = FALSE // Can dig dens. var/burrower = FALSE // Can dig dens.
var/bitesize = 3 // How many reagents to take per nibble
/decl/mob_organ_names/quadruped //Most subtypes have this basic body layout. /decl/mob_organ_names/quadruped //Most subtypes have this basic body layout.
hit_zones = list("head", "torso", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "tail") hit_zones = list("head", "torso", "left foreleg", "right foreleg", "left hind leg", "right hind leg", "tail")
/mob/living/simple_mob/animal/do_interaction(var/atom/A) /mob/living/simple_mob/animal/do_interaction(var/atom/A)
// Can we do some critter interaction?
var/static/list/atom_types_with_animal_interactions = list( var/static/list/atom_types_with_animal_interactions = list(
/obj/structure/animal_den, /obj/structure/animal_den,
/obj/structure/flora, /obj/structure/flora,
/turf/simulated/floor/outdoors /turf/simulated/floor/outdoors,
/obj/item/organ,
/obj/item/reagent_containers/food
) )
for(var/checktype in atom_types_with_animal_interactions) for(var/checktype in atom_types_with_animal_interactions)
if(istype(A, checktype)) if(istype(A, checktype))
attack_generic(A, 0, "investigates") A.attack_generic(src, 0, "investigates")
return TRUE return TRUE
// Can we eat a dead guy?
if(scavenger && isliving(A) && a_intent == I_HURT) if(scavenger && isliving(A) && a_intent == I_HURT)
var/mob/living/M = A var/mob/living/M = A
if(M.stat == DEAD && length(M.internal_organs)) if(M.stat == DEAD && length(M.internal_organs))
to_chat(src, SPAN_NOTICE("You dig into the guts of \the [M], hunting for the sweetest meat.")) to_chat(src, SPAN_NOTICE("You dig into the guts of \the [M], hunting for the sweetest meat."))
if(do_after(src, M, 2 SECOND) && !QDELETED(M) && length(M.internal_organs)) set_AI_busy(TRUE)
var/obj/item/organ = M.rip_out_internal_organ(check_zone(zone_sel.selecting), damage_descriptor = "animal teeth") if(do_after(src, 2 SECONDS, M) && !QDELETED(M) && length(M.internal_organs))
var/obj/item/organ = M.rip_out_internal_organ(check_zone(zone_sel?.selecting), damage_descriptor = "animal teeth")
if(organ) if(organ)
visible_message(SPAN_DANGER("\The [src] rips \the [organ] out of \the [M] and devours [organ.gender == PLURAL ? "them" : "it"]!")) visible_message(SPAN_DANGER("\The [src] rips \the [organ] out of \the [M] and devours [organ.gender == PLURAL ? "them" : "it"]!"))
eat_food_item(organ) eat_food_item(organ)
if(!length(M.internal_organs) && M.gib_on_butchery) if(!length(M.internal_organs) && M.gib_on_butchery)
M.gib() M.gib()
set_AI_busy(FALSE)
return TRUE return TRUE
return ..() return ..()
/mob/living/simple_mob/animal/proc/eat_food_item(var/obj/item/snack, var/silent) /mob/living/simple_mob/animal/proc/eat_food_item(var/obj/item/snack, var/override_bitesize, var/silent)
if(!silent)
visible_message("<b>\The [src]</b> nibbles away at \the [snack].")
playsound(src, 'sound/items/eatfood.ogg', rand(10,50), 1)
if(istype(snack, /obj/item/organ)) if(istype(snack, /obj/item/organ))
var/obj/item/organ/organ = snack var/obj/item/organ/organ = snack
if(organ.meat_type) if(organ.meat_type)
eat_food_item(new organ.meat_type(src), silent = TRUE) snack = new organ.meat_type(src)
if(!silent) qdel(organ)
playsound(src, 'sound/items/eatfood.ogg', rand(10,50), 1)
if(snack.reagents?.total_volume) if(snack.reagents?.total_volume)
snack.reagents.trans_to_mob(src, snack.reagents.total_volume, CHEM_INGEST) var/use_bitesize = (override_bitesize || bitesize)
qdel(snack) var/removing_reagents = clamp(use_bitesize, 0, min(max(0, reagents?.maximum_volume - reagents?.total_volume), snack.reagents.total_volume))
if(reagents && removing_reagents)
snack.reagents.trans_to_holder(reagents, removing_reagents)
else
snack.reagents.trans_to_mob(src, use_bitesize, CHEM_INGEST)
// If there's some left, drop it onto the ground.
if(snack.reagents?.total_volume > 0)
snack.dropInto(loc)
else
snack.animal_consumed(src)

View File

@@ -57,4 +57,4 @@
return return
B.old_style_target(A, src) B.old_style_target(A, src)
B.fire() B.fire()
set_AI_busy(FALSE) set_AI_busy(FALSE)

View File

@@ -0,0 +1,509 @@
/datum/modifier/sifsap_salve
name = "Sifsap Salve"
desc = "Your wounds have been salved with Sivian sap."
mob_overlay_state = "cyan_sparkles"
stacks = MODIFIER_STACK_FORBID
on_created_text = "<span class = 'notice'>The glowing sap seethes and bubbles in your wounds, tingling and stinging as infection is burned away.</span>"
on_expired_text = "<span class = 'notice'>The last of the sap in your wounds is absorbed by your body.</span>"
/datum/modifier/sifsap_salve/tick()
if(holder.stat == DEAD)
expire()
if(istype(holder, /mob/living/simple_mob/animal/sif))
if(holder.resting)
if(istype(holder.loc, /obj/structure/animal_den))
holder.adjustBruteLoss(-3)
holder.adjustFireLoss(-3)
holder.adjustToxLoss(-2)
else
holder.adjustBruteLoss(-2)
holder.adjustFireLoss(-2)
holder.adjustToxLoss(-1)
else
holder.adjustBruteLoss(-1)
holder.adjustFireLoss(-1)
/obj/item/projectile/drake_spit
name = "drake spit"
icon_state = "ice_1"
damage = 0
embed_chance = 0
damage_type = BRUTE
muzzle_type = null
hud_state = "monkey"
combustion = FALSE
stun = 3
weaken = 3
eyeblur = 5
fire_sound = 'sound/effects/splat.ogg'
/datum/category_item/catalogue/fauna/grafadreka
name = "Sivian Fauna - Grafadreka"
desc = {"Classification: S tesca pabulator
<br><br>
The reclusive grafadreka (Icelandic, lit. 'digging dragon'), also known as the snow drake, is a large reptillian pack predator similar in size and morphology to old Earth hyenas. They commonly dig shallow dens in dirt, snow or foliage, sometimes using them for concealment prior to an ambush. Biological cousins to the elusive kururak, they have heavy, low-slung bodies and powerful jaws suited to hunting land prey rather than fishing. Colonization and subsequent expansion have displaced many populations from their tundral territories into colder areas; as a result, their diet of Sivian prey animals has pivoted to a diet of giant spider meat.
<br><br>
Grafadrekas are capable of exerting bite pressures in excess of 900 PSI, which allows them to crack bones or carapace when scavenging for food. While they share the hypercarnivorous metabolism of their cousins, they have developed a symbiotic relationship with the bacteria responsible for the bioluminescence of Sivian trees. This assists with digesting plant matter, and gives their pelts a distinctive and eerie glow.
<br><br>
They have been observed to occasionally attack and kill colonists, generally when conditions are too poor to hunt their usual prey. Despite this, and despite their disposition being generally skittish and avoidant of colonists, some Sivian communities hold that they have been observed to guide or protect lost travellers.
<br><br>
Field studies suggest analytical abilities on par with some species of cepholapods, but their symbiotic physiology rapidly fails in captivity, making laboratory testing difficult. Their inability to make use of tools or form wider social groups beyond a handful of individuals has been hypothesised to prevent the expression of more complex social behaviors."}
value = CATALOGUER_REWARD_HARD
/decl/mob_organ_names/grafadreka
hit_zones = list(
"head",
"chest",
"left foreleg",
"right foreleg",
"left hind leg",
"right hind leg",
"face spines",
"body spines",
"tail spines",
"tail"
)
var/global/list/last_drake_howl = list()
/decl/emote/audible/drake_howl
key = "dhowl"
emote_message_3p = "lifts USER_THEIR head up and gives an eerie howl."
emote_sound = 'sound/effects/drakehowl_close.ogg'
var/cooldown = 20 SECONDS
/decl/emote/audible/drake_howl/do_emote(var/atom/user, var/extra_params)
if(world.time < last_drake_howl["\ref[user]"])
to_chat(user, SPAN_WARNING("You cannot howl again so soon."))
return FALSE
. = ..()
if(.)
last_drake_howl["\ref[user]"] = world.time + cooldown
/decl/emote/audible/drake_howl/do_sound(var/atom/user)
..()
var/turf/user_turf = get_turf(user)
if(!istype(user_turf))
return
var/list/close_listeners = hearers(world.view * 3, user_turf)
for(var/mob/M in player_list)
var/turf/T = get_turf(M)
if(!istype(T) || istype(T,/turf/space) || T.z != user_turf.z || (M in close_listeners) || M.ear_deaf > 0)
continue
M << 'sound/effects/drakehowl_far.ogg'
/mob/living/simple_mob/animal/sif/grafadreka/get_available_emotes()
return global._default_mob_emotes | /decl/emote/audible/drake_howl
/mob/living/simple_mob/animal/sif/grafadreka
name = "grafadreka"
desc = "A large, sleek snow drake with heavy claws, powerful jaws and many pale spines along its body."
player_msg = "You are a large Sivian pack predator in symbiosis with the local bioluminescent bacteria. You can eat glowing \
tree fruit to fuel your <b>ranged spitting attack</b> and <b>poisonous bite</b> (on <span class = 'danger'>harm intent</span>), as well as <b>healing saliva</b> \
(on <b><font color = '#009900'>help intent</font></b>).<br>There are humans moving through your territory; whether you help them get home safely, or treat them as a snack, \
is up to you."
color = "#608894"
icon = 'icons/mob/64x32.dmi'
catalogue_data = list(/datum/category_item/catalogue/fauna/grafadreka)
icon_state = "doggo"
icon_living = "doggo"
icon_dead = "doggo_lying"
icon_rest = "doggo_lying"
projectileverb = "spits"
friendly = list("headbutts", "grooms", "play-bites", "rubs against")
bitesize = 10 // chomp
has_langs = list("Drake")
tt_desc = "S tesca pabulator"
faction = "grafadreka"
maxHealth = 150
health = 150
movement_cooldown = 2
base_attack_cooldown = 1 SECOND
organ_names = /decl/mob_organ_names/grafadreka
say_list_type = /datum/say_list/grafadreka
ai_holder_type = /datum/ai_holder/simple_mob/intentional/grafadreka
scavenger = TRUE
burrower = TRUE
projectilesound = 'sound/effects/splat.ogg'
projectiletype = /obj/item/projectile/drake_spit
// Claw attacks.
attack_sharp = TRUE
melee_damage_lower = 8
melee_damage_upper = 18
attack_armor_pen = 15
attack_sound = 'sound/weapons/slice.ogg'
// Attack strings for swapping.
attacktext = null
var/static/list/claw_attacktext = list("slashed", "clawed", "swiped", "gouged")
var/static/list/bite_attacktext = list("savaged", "bitten", "mauled")
// Bite attacks.
var/const/bite_melee_damage_lower = 30
var/const/bite_melee_damage_upper = 40
var/const/bite_attack_armor_pen = 60
var/const/bite_attack_sound = 'sound/weapons/bite.ogg'
// Used to avoid setting vars every single attack.
var/attacking_with_claws = TRUE
// Set during initialize and used to generate overlays.
var/tmp/fur_colour
var/tmp/claw_colour
var/tmp/glow_colour
var/tmp/base_colour
var/tmp/eye_colour
var/next_spit = 0
var/spit_cooldown = 5 SECONDS
var/next_alpha_check = 0
var/dominance = 0 // A score used to determine pack leader.
var/stored_sap = 0
var/max_stored_sap = 100
var/attacked_by_human = FALSE
/mob/living/simple_mob/animal/sif/grafadreka/proc/can_tend_wounds(var/mob/living/friend)
if(ishuman(friend))
var/mob/living/carbon/human/H = friend
for(var/obj/item/organ/external/E in H.bad_external_organs)
if(E.status & ORGAN_BLEEDING)
return TRUE
return FALSE
return (friend.health < friend.maxHealth)
/mob/living/simple_mob/animal/sif/grafadreka/Initialize()
gender = pick(MALE, FEMALE)
attacktext = claw_attacktext.Copy()
setup_colours()
create_reagents(50)
. = ..()
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/examine(var/mob/living/user)
. = ..()
if(istype(user, /mob/living/simple_mob/animal/sif/grafadreka) || isobserver(user))
var/datum/gender/G = gender_datums[get_visible_gender()]
if(stored_sap >= 20)
. += SPAN_NOTICE("[G.His] sap reserves are high.")
else if(stored_sap >= 10)
. += SPAN_WARNING("[G.His] sap reserves are running low.")
else
. += SPAN_DANGER("[G.His] sap reserves are depleted.")
/mob/living/simple_mob/animal/sif/grafadreka/can_projectile_attack(var/atom/A)
if(a_intent != I_HURT || world.time < next_spit)
return FALSE
if(!has_sap(2))
to_chat(src, SPAN_WARNING("You have no sap to spit!"))
return FALSE
return ..()
// Checking this in the proc itself as AI doesn't seem to care about ranged attack cooldowns.
/mob/living/simple_mob/animal/sif/grafadreka/shoot_target(atom/A)
if(world.time < next_spit || !has_sap(2))
return FALSE
. = ..()
if(.)
next_spit = world.time + spit_cooldown
spend_sap(2)
/mob/living/simple_mob/animal/sif/grafadreka/Life()
. = ..()
// Process food and sap chems.
if(stat == CONSCIOUS) // Hibernating drakes don't get hungy.
// by default we lose nutrition. Hungry hungry drakes.
var/food_val = resting ? -0.2 : -0.5
// Very basic metabolism.
if(reagents?.total_volume)
for(var/datum/reagent/chem in reagents.reagent_list)
var/removing = min(REM, chem.volume)
if(istype(chem, /datum/reagent/nutriment))
var/datum/reagent/nutriment/food = chem
food_val += (food.nutriment_factor * removing) * ((food.allergen_type & ALLERGEN_MEAT) ? 0.5 : 0.1)
else if(istype(chem, /datum/reagent/toxin/sifslurry))
add_sap(removing * 3)
reagents.remove_reagent(chem.id, removing)
if(food_val != 0)
nutrition = clamp(nutrition + food_val, 0, max_nutrition)
/mob/living/simple_mob/animal/sif/grafadreka/proc/has_sap(var/amt)
return stored_sap >= amt
/mob/living/simple_mob/animal/sif/grafadreka/proc/add_sap(var/amt)
stored_sap = clamp(stored_sap + amt, 0, max_stored_sap)
update_icon()
return TRUE
/mob/living/simple_mob/animal/sif/grafadreka/proc/spend_sap(var/amt)
if(has_sap(amt))
stored_sap = clamp(stored_sap - amt, 0, max_stored_sap)
update_icon()
return TRUE
return FALSE
/mob/living/simple_mob/animal/sif/grafadreka/proc/setup_colours()
var/static/list/fur_colours = list(COLOR_SILVER, COLOR_WHITE, COLOR_GREEN_GRAY, COLOR_PALE_RED_GRAY, COLOR_BLUE_GRAY)
var/static/list/claw_colours = list(COLOR_GRAY, COLOR_SILVER, COLOR_WHITE, COLOR_GRAY15, COLOR_GRAY20, COLOR_GRAY40, COLOR_GRAY80)
var/static/list/glow_colours = list(COLOR_BLUE_LIGHT, COLOR_LIGHT_CYAN, COLOR_CYAN, COLOR_CYAN_BLUE)
var/static/list/base_colours = list("#608894", "#436974", "#7fa3ae")
var/static/list/eye_colours = list(COLOR_WHITE, COLOR_SILVER)
if(!glow_colour)
glow_colour = pick(glow_colours)
if(!fur_colour)
fur_colour = pick(fur_colours)
if(!claw_colour)
claw_colour = pick(claw_colours)
if(!base_colour)
base_colour = pick(base_colours)
if(!eye_colour)
eye_colour = pick(eye_colours)
/mob/living/simple_mob/animal/sif/grafadreka/movement_delay(oldloc, direct)
. = ..()
if(istype(loc, /turf/space))
return
var/health_deficiency = (getMaxHealth() - health)
if(health_deficiency >= 40)
. += (health_deficiency / 25)
var/hungry = (max_nutrition - nutrition) / 5
if (hungry >= 70)
. += hungry/50
/mob/living/simple_mob/animal/sif/grafadreka/update_icon()
. = ..()
var/list/add_images = list()
var/image/I = image(icon, "[icon_state]")
I.color = base_colour
add_images += I
I = image(icon, "[icon_state]-fur")
I.color = fur_colour
add_images += I
I = image(icon, "[icon_state]-claws")
I.color = claw_colour
add_images += I
if(stat == CONSCIOUS)
I = image(icon, "[icon_state]-eye_overlay")
I.color = eye_colour
add_images += I
if(stat != DEAD)
I = image(icon, "[icon_state]-glow")
I.color = glow_colour
I.plane = PLANE_LIGHTING_ABOVE
I.alpha = 35 + round(220 * clamp(stored_sap/max_stored_sap, 0, 1))
add_images += I
for(var/image/adding in add_images)
adding.appearance_flags |= (RESET_COLOR|PIXEL_SCALE|KEEP_APART)
adding.pixel_x = -16 // Offset here so that things like modifiers, runechat text, etc. are centered
add_overlay(adding)
// We do this last so the default mob icon_state can be used for the overlays.
icon_state = "blank"
color = COLOR_WHITE // Due to KEEP_TOGETHER etc. overlays ignore RESET_COLOR.
/mob/living/simple_mob/animal/sif/grafadreka/get_eye_color()
return eye_colour
/mob/living/simple_mob/animal/sif/grafadreka/handle_special()
..()
if(client || world.time >= next_alpha_check)
next_alpha_check = world.time + (60 SECONDS)
check_alpha_status()
/mob/living/simple_mob/animal/sif/grafadreka/do_help_interaction(atom/A)
if(isliving(A))
var/mob/living/friend = A
if(friend.stat == DEAD)
if(friend == src)
to_chat(src, SPAN_WARNING("\The [friend] is dead; tending their wounds is pointless."))
else
return ..()
return TRUE
if(!can_tend_wounds(friend))
if(friend == src)
to_chat(src, SPAN_WARNING("You are unwounded."))
else
return ..()
return TRUE
if(friend.has_modifier_of_type(/datum/modifier/sifsap_salve))
if(friend == src)
to_chat(src, SPAN_WARNING("You have already cleaned your wounds."))
else
return ..()
return TRUE
if(!has_sap(10))
if(friend == src)
to_chat(src, SPAN_WARNING("You don't have enough sap to clean your wounds."))
else
return ..()
return TRUE
if(friend == src)
visible_message(SPAN_NOTICE("\The [src] begins to drool a blue-glowing liquid, which they start slathering over their wounds."))
else
visible_message(SPAN_NOTICE("\The [src] begins to drool a blue-glowing liquid, which they start slathering over \the [friend]'s wounds."))
playsound(src, 'sound/effects/ointment.ogg', 25)
set_AI_busy(TRUE)
if(!do_after(src, 8 SECONDS, friend) || QDELETED(friend) || friend.has_modifier_of_type(/datum/modifier/sifsap_salve) || incapacitated() || !spend_sap(10))
set_AI_busy(FALSE)
return TRUE
set_AI_busy(FALSE)
if(friend == src)
visible_message(SPAN_NOTICE("\The [src] finishes licking at their wounds."))
else
visible_message(SPAN_NOTICE("\The [src] finishes licking at \the [friend]'s wounds."))
playsound(src, 'sound/effects/ointment.ogg', 25)
// Sivian animals get a heal buff from the modifier, others just
// get it to stop friendly drakes constantly licking their wounds.
friend.add_modifier(/datum/modifier/sifsap_salve, 60 SECONDS)
// Human wounds are closed, but they get sifsap via open wounds.
if(ishuman(friend))
var/mob/living/carbon/human/H = friend
for(var/obj/item/organ/external/E in H.organs)
if(E.status & ORGAN_BLEEDING)
H.bloodstr.add_reagent("sifsap", rand(1,2))
for(var/datum/wound/W in E.wounds)
W.salve()
// Everyone else is just poisoned.
else if(!istype(friend, /mob/living/simple_mob/animal/sif))
friend.adjustToxLoss(rand(10,20))
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/proc/get_local_alpha()
var/pack = FALSE
var/mob/living/simple_mob/animal/sif/grafadreka/alpha = src
for(var/mob/living/simple_mob/animal/sif/grafadreka/beta in hearers(7, loc))
if(beta != src && beta.stat != DEAD)
pack = TRUE
if(beta.dominance > alpha.dominance)
alpha = beta
if(pack)
return alpha
/mob/living/simple_mob/animal/sif/grafadreka/proc/check_alpha_status()
var/mob/living/simple_mob/animal/sif/grafadreka/alpha = get_local_alpha()
if(src == alpha)
add_modifier(/datum/modifier/ace, 60 SECONDS)
else
remove_modifiers_of_type(/datum/modifier/ace)
/mob/living/simple_mob/animal/sif/grafadreka/Stat()
. = ..()
if(statpanel("Status"))
stat("Nutrition:", "[nutrition]/[max_nutrition]")
stat("Stored sap:", "[stored_sap]/[max_stored_sap]")
/mob/living/simple_mob/animal/sif/grafadreka/apply_bonus_melee_damage(atom/A, damage_amount)
// Melee attack on incapacitated or prone enemies bites instead of slashing
var/last_attack_was_claws = attacking_with_claws
attacking_with_claws = TRUE
if(isliving(A))
var/mob/living/M = A
if(M.lying || M.incapacitated())
attacking_with_claws = FALSE
if(last_attack_was_claws != attacking_with_claws)
if(attacking_with_claws) // Use claws.
attack_armor_pen = initial(attack_armor_pen)
attack_sound = initial(attack_sound)
attacktext = claw_attacktext.Copy()
else // Use ur teef
damage_amount = max(damage_amount, rand(bite_melee_damage_lower, bite_melee_damage_upper))
attack_armor_pen = bite_attack_armor_pen
attack_sound = bite_attack_sound
attacktext = bite_attacktext.Copy()
. = ..()
// Eating sifsap makes bites toxic and changes our glow intensity.
/mob/living/simple_mob/animal/sif/grafadreka/apply_attack(atom/A, damage_to_do)
var/tox_damage = 0
if(!attacking_with_claws && isliving(A) && has_sap(5))
tox_damage = rand(5,15)
. = ..()
if(. && tox_damage && spend_sap(5))
var/mob/living/M = A
M.adjustToxLoss(tox_damage)
/mob/living/simple_mob/animal/sif/grafadreka/verb/rally_pack()
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))
to_chat(src, SPAN_WARNING("You aren't the pack leader! Sit down!"))
return
for(var/mob/living/simple_mob/animal/sif/grafadreka/drake in hearers(7, src))
if(drake == src || drake.faction != faction)
continue
if(drake.client)
to_chat(drake, SPAN_NOTICE("The pack leader wishes for you to follow them."))
else if(drake.ai_holder)
drake.ai_holder.set_follow(src)
audible_message("<b>\The [src]</b> barks loudly and rattles its neck spines.")
/datum/say_list/grafadreka
speak = list("Chff!","Skhh.", "Rrrss...")
emote_see = list("scratches its ears","grooms its spines", "sways its tail", "claws at the ground")
emote_hear = list("hisses", "rattles", "rasps", "barks")
/obj/structure/animal_den/ghost_join/grafadreka
name = "grafadreka den"
critter = /mob/living/simple_mob/animal/sif/grafadreka/wild/hibernate
// Subtypes!
/mob/living/simple_mob/animal/sif/grafadreka/rainbow/setup_colours()
glow_colour = get_random_colour(TRUE)
fur_colour = get_random_colour(TRUE)
claw_colour = get_random_colour(TRUE)
base_colour = get_random_colour(TRUE)
eye_colour = get_random_colour(TRUE)
..()
/mob/living/simple_mob/animal/sif/grafadreka/wild/Initialize()
dominance = rand(5, 15)
stored_sap = rand(20, 30)
. = ..()
/mob/living/simple_mob/animal/sif/grafadreka/wild/Login()
. = ..()
if(client)
dominance = INFINITY // Let players lead by default.
/mob/living/simple_mob/animal/sif/grafadreka/wild/Logout()
. = ..()
if(!client)
dominance = rand(5, 15)
/mob/living/simple_mob/animal/sif/grafadreka/wild/hibernate/Initialize()
. = ..()
dominance = 0
lay_down()

View File

@@ -0,0 +1,152 @@
/mob/living/simple_mob/animal/sif/grafadreka/ICheckRangedAttack(atom/A)
. = ..() && isliving(A)
if(.)
var/mob/living/M = A
if(M.lying || M.incapacitated())
. = FALSE // They're already stunned, go bite their nipples off.
/mob/living/simple_mob/animal/sif/grafadreka/IIsAlly(mob/living/L)
. = ..()
// Avoid humans unless we're very hungry, very angry, or they are small).
if(!. && ishuman(L) && !attacked_by_human)
. = !issmall(L) && (nutrition > max_nutrition * 0.15)
/mob/living/simple_mob/animal/sif/grafadreka/proc/IDoInteraction(var/obj/item/target)
set waitfor = FALSE
face_atom(target)
do_interaction(target)
/mob/living/simple_mob/animal/sif/grafadreka/IWasAttackedBy(var/mob/living/attacker)
if(ishuman(attacker))
attacked_by_human = TRUE
if(istype(ai_holder, /datum/ai_holder/simple_mob/intentional/grafadreka))
var/datum/ai_holder/simple_mob/intentional/grafadreka/drake_brain = ai_holder
drake_brain.hostile = TRUE
/datum/ai_holder/simple_mob/intentional/grafadreka
hostile = TRUE
retaliate = TRUE
cooperative = TRUE
can_flee = TRUE
flee_when_dying = TRUE
var/next_food_check
var/next_sap_check
var/next_heal_check
/datum/ai_holder/simple_mob/intentional/grafadreka/should_flee(force)
. = ..()
if(!. && ismob(target))
var/mob/living/prey = target
var/mob/living/simple_mob/animal/sif/grafadreka/drake = holder
// If they're incapacitated, don't need to spit on them again.
if(!istype(drake) || prey.lying || prey.incapacitated())
return FALSE
// We can spit at them - disengage so you can pew pew.
if(get_dist(target, holder) <= 1 && drake.has_sap(2))
return TRUE
/datum/ai_holder/simple_mob/intentional/grafadreka/pre_ranged_attack(atom/A)
holder.a_intent = I_HURT
/datum/ai_holder/simple_mob/intentional/grafadreka/pre_melee_attack(atom/A)
holder.a_intent = I_HURT
/datum/ai_holder/simple_mob/intentional/grafadreka/post_melee_attack()
if(holder.has_modifier_of_type(/datum/modifier/ace))
request_help()
return ..()
/datum/ai_holder/simple_mob/intentional/grafadreka/handle_special_strategical()
follow_distance = rand(initial(follow_distance), initial(follow_distance) + 2)
if(holder.has_modifier_of_type(/datum/modifier/ace))
if(leader)
lose_follow()
else
if(!leader)
var/mob/living/simple_mob/animal/sif/grafadreka/drake = holder
if(istype(drake))
set_follow(drake.get_local_alpha())
return ..()
/datum/ai_holder/simple_mob/intentional/grafadreka/handle_stance_strategical()
. = ..()
var/mob/living/simple_mob/animal/sif/grafadreka/drake = holder
if(!istype(drake))
return
if(drake.resting && stance != STANCE_IDLE)
drake.lay_down()
if(stance in STANCES_COMBAT)
return
// Heal anyone near us who needs it.
if(stance == STANCE_IDLE && drake.has_sap(10)) // explicit stance check so they stop trying to heal for 15 seconds during flee behavior
for(var/mob/living/friend in range(1, get_turf(holder)))
if(!holder.Adjacent(friend) || friend == target || friend.stat == DEAD)
continue
// Some specific checks here rather than IIsAlly() as we have some behavior to avoid
// attacking humans, but don't necessarily want to go lick them if they get a papercut.
if(!(friend in drake.friends) && !(friend in drake.tamers) && drake.faction != friend.faction)
continue
if(!friend.has_modifier_of_type(/datum/modifier/sifsap_salve) && drake.can_tend_wounds(friend))
drake.a_intent = I_HELP
drake.IDoInteraction(friend)
return
if(world.time >= next_heal_check)
next_heal_check = world.time + 15 SECONDS
for(var/mob/living/friend in view(world.view, drake))
if(target == friend || friend.stat == DEAD || friend.has_modifier_of_type(/datum/modifier/sifsap_salve))
continue
if(((friend in drake.friends) || (friend in drake.tamers) || drake.faction == friend.faction) && drake.can_tend_wounds(friend))
give_destination(get_turf(friend))
return
// These actions need space in our stomach to be worth considering at all.
if(drake.reagents && abs(drake.reagents.total_volume - drake.reagents.maximum_volume) >= 10)
// Find some sap if we're low and aren't already digesting some.
if(!drake.has_sap(20) && !drake.reagents.has_reagent("sifsap"))
for(var/obj/structure/flora/tree/sif/tree in range(1, holder))
if(holder.Adjacent(tree) && tree.can_forage(drake))
drake.a_intent = I_HELP
drake.IDoInteraction(tree)
return
if(world.time >= next_sap_check)
next_sap_check = world.time + 15 SECONDS
for(var/obj/structure/flora/tree/sif/tree in view(world.view, drake))
if(tree.can_forage(drake))
give_destination(get_turf(tree))
return
// Are we hungy?
if(drake.nutrition < drake.max_nutrition * 0.6)
var/atom/movable/food
for(var/thing in range(1, drake))
if(istype(thing, /obj/item/reagent_containers/food) || istype(thing, /obj/item/organ))
food = thing
break
if(isliving(thing))
var/mob/living/M = thing
if(M.stat == DEAD)
food = M
break
if(food)
drake.a_intent = I_HURT
drake.IDoInteraction(food)
return
if(world.time >= next_food_check)
food = null
next_food_check = world.time + 15 SECONDS
for(var/thing in view(world.view, drake))
if(istype(thing, /obj/item/reagent_containers/food) || istype(thing, /obj/item/organ))
food = thing
else if(isliving(thing))
var/mob/living/M = thing
if(M.stat == DEAD)
food = M
if(food)
give_destination(get_turf(food))
return

View File

@@ -558,3 +558,10 @@ var/global/list/organ_cache = list()
return TRUE return TRUE
return FALSE return FALSE
/obj/item/organ/attack_generic(mob/user)
if(isanimal(user))
var/mob/living/simple_mob/animal/critter = user
critter.eat_food_item(src)
return TRUE
return ..()

View File

@@ -423,4 +423,4 @@
pellets = 6 pellets = 6
range_step = 1 range_step = 1
spread_step = 10 spread_step = 10
silenced = TRUE silenced = TRUE

View File

@@ -79,6 +79,8 @@
prob(45);/mob/living/simple_mob/animal/sif/shantak, prob(45);/mob/living/simple_mob/animal/sif/shantak,
prob(15);/mob/living/simple_mob/animal/sif/kururak/hibernate, prob(15);/mob/living/simple_mob/animal/sif/kururak/hibernate,
prob(10);/mob/living/simple_mob/animal/sif/kururak, prob(10);/mob/living/simple_mob/animal/sif/kururak,
prob(5);/mob/living/simple_mob/animal/sif/grafadreka/wild/hibernate,
prob(3);/mob/living/simple_mob/animal/sif/grafadreka/wild,
prob(3);/mob/living/simple_mob/animal/giant_spider, prob(3);/mob/living/simple_mob/animal/giant_spider,
prob(8);/mob/living/simple_mob/animal/giant_spider/hunter, prob(8);/mob/living/simple_mob/animal/giant_spider/hunter,
prob(3);/mob/living/simple_mob/animal/giant_spider/webslinger, prob(3);/mob/living/simple_mob/animal/giant_spider/webslinger,

View File

@@ -0,0 +1,26 @@
"a" = (/turf/template_noop,/area/template_noop)
"b" = (/turf/simulated/mineral/ignore_mapgen,/area/submap/GrafadrekaDen)
"c" = (/obj/random/junk,/obj/random/medical/pillbottle,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"d" = (/obj/random/junk,/obj/random/humanoidremains,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"e" = (/obj/random/mob/sif/grafadreka,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"f" = (/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"g" = (/obj/structure/animal_den/ghost_join/grafadreka,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"h" = (/obj/random/junk,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"i" = (/obj/random/outcrop,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"j" = (/obj/random/humanoidremains,/obj/random/coin,/obj/random/contraband,/obj/random/projectile/random,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"o" = (/obj/random/humanoidremains,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"D" = (/obj/structure/animal_den/ghost_join/grafadreka,/turf/simulated/mineral/floor/sif,/area/submap/GrafadrekaDen)
"P" = (/turf/simulated/mineral/ignore_mapgen,/area/submap/GrafadrekaDen)
(1,1,1) = {"
aaaaaaaaaa
aaabbbbaaa
aabbDbffaa
abbgoifbba
abbiffecba
abhffffdba
abbDfffbba
abbPjfbbba
aabbbffaaa
aaaaaaaaaa
"}

View File

@@ -217,6 +217,12 @@
mappath = 'maps/submaps/surface_submaps/wilderness/kururakden.dmm' mappath = 'maps/submaps/surface_submaps/wilderness/kururakden.dmm'
cost = 5 cost = 5
/datum/map_template/surface/wilderness/normal/GrafadrekaDen
name = "Grafadreka Den"
desc = "The den of a Grafadreka pack. May contain hibernating members."
mappath = 'maps/submaps/surface_submaps/wilderness/grafadreka_den.dmm'
cost = 5
/datum/map_template/surface/wilderness/normal/DecoupledEngine /datum/map_template/surface/wilderness/normal/DecoupledEngine
name = "Decoupled Engine" name = "Decoupled Engine"
desc = "A damaged fission engine jettisoned from a starship long ago." desc = "A damaged fission engine jettisoned from a starship long ago."
@@ -342,4 +348,3 @@
desc = "Gathering of acolytes gone wrong." desc = "Gathering of acolytes gone wrong."
mappath = 'maps/submaps/surface_submaps/wilderness/deathden.dmm' mappath = 'maps/submaps/surface_submaps/wilderness/deathden.dmm'
cost = 15 cost = 15

View File

@@ -100,6 +100,10 @@
name = "POI - Kururak Den" name = "POI - Kururak Den"
ambience = AMBIENCE_SIF ambience = AMBIENCE_SIF
/area/submap/GrafadrekaDen
name = "POI - Grafadreka Den"
ambience = AMBIENCE_SIF
/area/submap/DecoupledEngine /area/submap/DecoupledEngine
name = "POI - Decoupled Engine" name = "POI - Decoupled Engine"
ambience = AMBIENCE_FOREBODING ambience = AMBIENCE_FOREBODING

View File

@@ -2479,6 +2479,8 @@
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\duck.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\frostfly.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\glitterfly.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\sif\glitterfly.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\grafadreka.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\grafadreka_ai.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\hare.dm" #include "code\modules\mob\living\simple_mob\subtypes\animal\sif\hare.dm"
#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\hooligan_crab.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\kururak.dm"

Binary file not shown.

Binary file not shown.