Merge pull request #8829 from Spookerton/spkrtn/cng/gnarl

updates drakes & drake harnesses
This commit is contained in:
Atermonera
2022-11-26 23:41:20 -08:00
committed by GitHub
12 changed files with 944 additions and 828 deletions

View File

@@ -0,0 +1,180 @@
/obj/item/storage/internal/animal_harness
abstract_type = /obj/item/storage/internal/animal_harness
name = "animal harness"
color = COLOR_BEASTY_BROWN
max_w_class = ITEMSIZE_LARGE
max_storage_space = INVENTORY_STANDARD_SPACE
/// Null, or a list of (/obj/item/foo = "item key"), or (/path = ("item key", proc/handler)).
var/list/attachable_types
/// Null, or a list of ("item key" = null|Instance)
var/list/attached_items
/// The time it takes for the wearer to adjust this harness' attachments.
var/self_attach_delay = 5 SECONDS
/// The time it takes for someone else to adjust this harness' attachments.
var/other_attach_delay = 3 SECONDS
/obj/item/storage/internal/animal_harness/Destroy()
var/mob/living/simple_mob/animal/wearer = loc
if (istype(wearer) && wearer.harness == src)
wearer.harness = null
for (var/key in attached_items)
var/obj/item/item = attached_items[key]
qdel(item)
LAZYCLEARLIST(attached_items)
attachable_types = null
return ..()
/obj/item/storage/internal/animal_harness/Initialize()
. = ..()
if (!attachable_types)
return
attached_items = list()
for (var/path in attachable_types)
var/key = attachable_types[path]
attached_items[key] = null
CreateAttachments()
/obj/item/storage/internal/animal_harness/LateInitializeName()
return
/obj/item/storage/internal/animal_harness/attackby(obj/item/item, mob/living/user, silent)
. = TRUE
if (user == loc) // Animals can only shove stuff in their storage.
return ..()
for (var/path in attachable_types)
if (!istype(item, path))
continue
var/key = attachable_types[path]
var/handler
if (islist(key))
handler = key[2]
key = key[1]
if (attached_items[key])
if (!silent)
var/datum/gender/gender
if (ismob(loc))
var/mob/living/simple_mob/animal/owner = loc
gender = gender_datums[owner.get_visible_gender()]
to_chat(user, SPAN_WARNING("\The [loc] already has \a [key] attached to [gender ? gender.his : "its"] harness."))
return
if (!user.unEquip(item, target = loc))
return
if (!silent)
user.visible_message(SPAN_NOTICE("\The [user] attaches \the [item] to \the [loc]'s harness."))
attached_items[key] = item
if (handler)
call(src, handler)(item)
return
return ..()
/obj/item/storage/internal/animal_harness/proc/GetAttachedKeys()
var/list/obj/item/result = list()
for (var/key in attached_items)
if (attached_items[key])
result += key
return result
/obj/item/storage/internal/animal_harness/proc/GetAttachedItems()
var/list/obj/item/result = list()
for (var/key in attached_items)
var/obj/item/item = attached_items[key]
if (item)
result += item
return result
/obj/item/storage/internal/animal_harness/proc/RemoveAttachment(obj/item/attachment)
if (istext(attachment))
var/obj/item/item = attached_items[attachment]
if (!item)
return
item.dropInto(get_turf(src))
attached_items[attachment] = null
return item
for (var/key in attached_items)
if (attached_items[key] == attachment)
attachment.dropInto(get_turf(src))
attached_items[key] = null
return attachment
/obj/item/storage/internal/animal_harness/proc/CreateAttachments()
return
/mob/living/simple_mob/animal
var/obj/item/storage/internal/animal_harness/harness
/mob/living/simple_mob/animal/Destroy()
if (istype(harness))
QDEL_NULL(harness)
return ..()
/mob/living/simple_mob/animal/Initialize()
. = ..()
if (ispath(harness))
harness = new harness (src)
verbs += /mob/living/simple_mob/animal/proc/RemoveAttachmentVerb
/mob/living/simple_mob/animal/examine(mob/living/user)
. = ..()
if (!harness)
return
. += "\The [src] is wearing \a [harness]."
for (var/obj/item/item in harness.GetAttachedItems())
. += "There is \a [item] attached."
/mob/living/simple_mob/animal/proc/RemoveAttachmentVerb()
set name = "Remove Harness Attachment"
set category = "IC"
set src in view(1)
var/mob/living/user = usr
if (!istype(user))
return
if (!Adjacent(user) || user.incapacitated())
to_chat(user, SPAN_WARNING("You are in no condition to do that."))
return
if (!user.check_dexterity(MOB_DEXTERITY_SIMPLE_MACHINES))
return
var/list/attached = harness.GetAttachedKeys()
var/datum/gender/gender = gender_datums[get_visible_gender()]
if (!length(attached))
to_chat(user, SPAN_WARNING("\The [src] has nothing attached to [gender.his] [harness.name]."))
return
var/obj/item/response = input(user, "Select attachment to remove:") as null | anything in attached
if (!response)
return
if (!(response in harness.GetAttachedKeys()))
to_chat(user, SPAN_WARNING("\The [response] is already missing from \the [harness]."))
return
if (!Adjacent(user) || user.incapacitated())
to_chat(user, SPAN_WARNING("You are in no condition to do that."))
return
user.visible_message(
SPAN_ITALIC("\The [user] begins to remove \a [response] from [(user == src) ? "[gender.his]" : "\the [src]'s"] harness."),
SPAN_ITALIC("You begin to remove \the [response] from [(user == src) ? "your" : "\the [src]'s"] harness."),
range = 5
)
if (!do_after(user, (user == src) ? harness.self_attach_delay : harness.other_attach_delay, loc))
return
if (QDELETED(harness))
return
if (!(response in harness.GetAttachedKeys()))
to_chat(user, SPAN_WARNING("\The [response] is already missing from \the [src]."))
return
var/obj/item/removed = harness.RemoveAttachment(response)
user.put_in_hands(removed)

View File

@@ -1,120 +1,3 @@
/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.</span>"
on_expired_text = "<span class = 'notice'>The last of the sap in your wounds fizzles away.</span>"
/datum/modifier/sifsap_salve/tick()
if(holder.stat == DEAD || holder.isSynthetic())
expire()
if(istype(holder, /mob/living/simple_mob/animal/sif))
var/mob/living/simple_mob/animal/sif/critter = holder
if(critter.health >= (critter.getMaxHealth() * critter.sap_heal_threshold))
return
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'
/obj/item/projectile/drake_spit/weak
stun = 0
weaken = 0
eyeblur = 2
/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"
)
/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'
broadcast_sound ='sound/effects/drakehowl_far.ogg'
emote_cooldown = 20 SECONDS
broadcast_distance = 90
/decl/emote/audible/drake_howl/broadcast_emote_to(var/send_sound, var/mob/target, var/direction)
if((. = ..()))
to_chat(target, SPAN_NOTICE("You hear an eerie howl from somewhere to the [dir2text(direction)]."))
/mob/living/simple_mob/animal/sif/grafadreka/get_available_emotes()
if(!is_baby)
return global._default_mob_emotes | /decl/emote/audible/drake_howl
return global._default_mob_emotes
// Overriding this to handle sitting.
/mob/living/simple_mob/animal/sif/grafadreka/lay_down()
. = ..()
if(!resting && sitting)
sitting = FALSE
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/verb/sit_down()
set name = "Sit Down"
set category = "IC"
if(sitting)
resting = FALSE
sitting = FALSE
else
resting = TRUE
sitting = TRUE
to_chat(src, SPAN_NOTICE("You are now [sitting ? "sitting" : "getting up"]."))
update_canmove()
update_icon()
/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."
@@ -211,195 +94,276 @@ Field studies suggest analytical abilities on par with some species of cepholapo
var/list/original_armor
var/global/list/wounds_being_tended_by_drakes = list()
/mob/living/simple_mob/animal/sif/grafadreka/proc/can_tend_wounds(var/mob/living/friend)
/// A convenience value for whether this drake is the trained subtype.
var/trained_drake = FALSE
// We can't heal robots.
if(friend.isSynthetic())
return FALSE
// Check if someone else is looking after them already.
if(global.wounds_being_tended_by_drakes["\ref[friend]"] > world.time)
return FALSE
// Humans need to have a bleeding external organ to qualify.
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
// Sif animals need to be able to regenerate past their current HP value.
if(istype(friend, /mob/living/simple_mob/animal/sif))
var/mob/living/simple_mob/animal/sif/critter = friend
return critter.health < (critter.getMaxHealth() * critter.sap_heal_threshold)
// Other animals just need to be injured.
return (friend.health < friend.maxHealth)
/mob/living/simple_mob/animal/sif/grafadreka/Initialize()
charisma = rand(5, 15)
stored_sap = rand(20, 30)
nutrition = rand(400,500)
if(gender == NEUTER)
if (gender == NEUTER)
gender = pick(MALE, FEMALE)
attacktext = claw_attacktext.Copy()
setup_colours()
create_reagents(50)
. = ..()
original_armor = armor
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/examine(var/mob/living/user)
/mob/living/simple_mob/animal/sif/grafadreka/Stat()
. = ..()
if(istype(user, /mob/living/simple_mob/animal/sif/grafadreka) || isobserver(user))
if (statpanel("Status"))
stat("Nutrition:", "[nutrition]/[max_nutrition]")
stat("Stored sap:", "[stored_sap]/[max_stored_sap]")
/mob/living/simple_mob/animal/sif/grafadreka/Login()
. = ..()
charisma = (client && !is_baby) ? INFINITY : 0
/mob/living/simple_mob/animal/sif/grafadreka/Logout()
. = ..()
if (!client)
charisma = rand(5, 15)
/mob/living/simple_mob/animal/sif/grafadreka/examine(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)
if (stored_sap >= 20)
. += SPAN_NOTICE("[G.His] sap reserves are high.")
else if(stored_sap >= 10)
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)
/mob/living/simple_mob/animal/sif/grafadreka/do_interaction(atom/atom)
if (atom.interaction_grafadreka(src))
return ATTACK_SUCCESSFUL
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/get_available_emotes()
if (!is_baby)
return global._default_mob_emotes | /decl/emote/audible/drake_howl
return global._default_mob_emotes
/mob/living/simple_mob/animal/sif/grafadreka/lay_down()
if (sitting)
to_chat(src, SPAN_NOTICE("You are now lying down."))
sitting = FALSE
update_icon()
else
..()
/mob/living/simple_mob/animal/sif/grafadreka/can_projectile_attack(atom/target)
if (a_intent != I_HURT || world.time < next_spit)
return FALSE
if(!has_sap(2))
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))
/mob/living/simple_mob/animal/sif/grafadreka/shoot_target(atom/target)
if (world.time < next_spit || !has_sap(2))
return FALSE
. = ..()
if(.)
if (.)
next_spit = world.time + spit_cooldown
setMoveCooldown(1 SECOND)
spend_sap(2)
/mob/living/simple_mob/animal/sif/grafadreka/get_dietary_food_modifier(var/datum/reagent/nutriment/food)
if(food.allergen_type & ALLERGEN_MEAT)
/mob/living/simple_mob/animal/sif/grafadreka/get_dietary_food_modifier(datum/reagent/nutriment/food)
if (food.allergen_type & ALLERGEN_MEAT)
return ..()
return 0.25 // Quarter nutrition from non-meat.
/mob/living/simple_mob/animal/sif/grafadreka/handle_reagent_transfer(var/datum/reagents/holder, var/amount = 1, var/chem_type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0)
/mob/living/simple_mob/animal/sif/grafadreka/handle_reagent_transfer(datum/reagents/holder, amount = 1, chem_type = CHEM_BLOOD, multiplier = 1, copy = 0)
return holder.trans_to_holder(reagents, amount, multiplier, copy)
/mob/living/simple_mob/animal/sif/grafadreka/Life()
. = ..()
if(stat == CONSCIOUS)
if (stat == CONSCIOUS)
// Don't make clientless drakes lose nutrition or they'll all go feral.
if(!resting && client)
if (!resting && client)
remove_nutrition(0.3)
// Very slowly regenerate enough sap to defend ourselves. spit is 2 sap,
// spit cooldown is 8s, life is 2s, so this is one free spit per 12 seconds.
if(stored_sap < 10)
if (stored_sap < 10)
add_sap(0.35)
// Process food and sap chems.
if(reagents?.total_volume)
for(var/datum/reagent/chem in reagents.reagent_list)
if (reagents?.total_volume)
for (var/datum/reagent/chem in reagents.reagent_list)
var/removed = clamp(chem.ingest_met, REM, chem.volume)
chem.affect_animal(src, removed)
reagents.remove_reagent(chem.id, removed)
/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(round(stored_sap + amt, 0.01), 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(round(stored_sap - amt, 0.01), 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))
if (istype(loc, /turf/space))
return
var/health_slowdown_threshold = round(maxHealth * 0.65)
if(health < health_slowdown_threshold)
if (health < health_slowdown_threshold)
. += round(5 * (1-(health / health_slowdown_threshold)), 0.1)
var/nut_slowdown_threshold = round(max_nutrition * 0.65)
if(nutrition < nut_slowdown_threshold)
if (nutrition < nut_slowdown_threshold)
. += round(5 * (1-(nutrition / nut_slowdown_threshold)), 0.1)
/mob/living/simple_mob/animal/sif/grafadreka/update_icon()
. = ..()
if(sitting && stat == CONSCIOUS)
if (sitting && stat == CONSCIOUS)
icon_state = "[initial(icon_state)]_sitting"
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 && !sleeping)
if (stat == CONSCIOUS && !sleeping)
I = image(icon, "[icon_state]-eye_overlay")
I.color = eye_colour
add_images += I
if(stat != DEAD)
if (stat != DEAD)
var/glow = add_glow()
if(glow)
if (glow)
add_images += glow
for(var/image/adding in add_images)
for (var/image/adding in add_images)
adding.appearance_flags |= (RESET_COLOR|PIXEL_SCALE|KEEP_APART)
if(offset_compiled_icon)
if (offset_compiled_icon)
adding.pixel_x = offset_compiled_icon // 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.
current_icon_state = icon_state
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/do_tame(obj/obj, mob/user)
. = ..()
attacked_by_neutral = FALSE
/mob/living/simple_mob/animal/sif/grafadreka/handle_special()
..()
if(client || world.time >= next_leader_check)
next_leader_check = world.time + (60 SECONDS)
check_leader_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)
if (health == maxHealth)
to_chat(src, SPAN_WARNING("You are unwounded."))
else
to_chat(src, SPAN_WARNING("You cannot tend any of your wounds."))
else
if (friend.health == friend.maxHealth)
return ..()
to_chat(src, SPAN_WARNING("You cannot tend any of \the [friend]'s wounds."))
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)
var/friend_ref = "\ref[friend]"
global.wounds_being_tended_by_drakes[friend_ref] = world.time + (8 SECONDS)
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))
global.wounds_being_tended_by_drakes -= friend_ref
set_AI_busy(FALSE)
return TRUE
global.wounds_being_tended_by_drakes -= friend_ref
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)
E.organ_clamp()
H.bloodstr.add_reagent("sifsap", rand(1,2))
for (var/datum/wound/W in E.wounds)
W.salve()
W.disinfect()
// Everyone else is just poisoned.
else if (!istype(friend, /mob/living/simple_mob/animal/sif))
friend.adjustToxLoss(rand(10,20))
return TRUE
return ..()
// 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/rejuvenate()
remove_modifiers_of_type(/datum/modifier/sifsap_salve, TRUE)
stored_sap = rand(20, 30)
..()
/mob/living/simple_mob/animal/sif/grafadreka/has_appetite()
return reagents && abs(reagents.total_volume - reagents.maximum_volume) >= 10
/mob/living/simple_mob/animal/sif/grafadreka/proc/add_glow()
var/image/I = image(icon, "[icon_state]-glow")
I.color = glow_colour
@@ -407,140 +371,99 @@ var/global/list/wounds_being_tended_by_drakes = list()
I.alpha = 35 + round(220 * clamp(stored_sap/max_stored_sap, 0, 1))
return I
/mob/living/simple_mob/animal/sif/grafadreka/get_eye_color()
return eye_colour
/mob/living/simple_mob/animal/sif/grafadreka/do_tame(var/obj/O, var/mob/user)
. = ..()
attacked_by_neutral = FALSE
/mob/living/simple_mob/animal/sif/grafadreka/proc/has_sap(amount)
return stored_sap >= amount
/mob/living/simple_mob/animal/sif/grafadreka/handle_special()
..()
if(client || world.time >= next_leader_check)
next_leader_check = world.time + (60 SECONDS)
check_leader_status()
/mob/living/simple_mob/animal/sif/grafadreka/do_help_interaction(atom/A)
/mob/living/simple_mob/animal/sif/grafadreka/proc/add_sap(amount)
stored_sap = clamp(round(stored_sap + amount, 0.01), 0, max_stored_sap)
update_icon()
return TRUE
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)
if(health == maxHealth)
to_chat(src, SPAN_WARNING("You are unwounded."))
else
to_chat(src, SPAN_WARNING("You cannot tend any of your wounds."))
else
if(friend.health == friend.maxHealth)
return ..()
to_chat(src, SPAN_WARNING("You cannot tend any of \the [friend]'s wounds."))
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)
var/friend_ref = "\ref[friend]"
global.wounds_being_tended_by_drakes[friend_ref] = world.time + (8 SECONDS)
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))
global.wounds_being_tended_by_drakes -= friend_ref
set_AI_busy(FALSE)
return TRUE
global.wounds_being_tended_by_drakes -= friend_ref
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)
E.organ_clamp()
H.bloodstr.add_reagent("sifsap", rand(1,2))
for(var/datum/wound/W in E.wounds)
W.salve()
W.disinfect()
// Everyone else is just poisoned.
else if(!istype(friend, /mob/living/simple_mob/animal/sif))
friend.adjustToxLoss(rand(10,20))
/mob/living/simple_mob/animal/sif/grafadreka/proc/spend_sap(amount)
if (has_sap(amount))
stored_sap = clamp(round(stored_sap - amount, 0.01), 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)
var/global/list/wounds_being_tended_by_drakes = list()
/mob/living/simple_mob/animal/sif/grafadreka/proc/can_tend_wounds(mob/living/friend)
// We can't heal robots.
if (friend.isSynthetic())
return FALSE
// Check if someone else is looking after them already.
if (global.wounds_being_tended_by_drakes["\ref[friend]"] > world.time)
return FALSE
// Humans need to have a bleeding external organ to qualify.
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
// Sif animals need to be able to regenerate past their current HP value.
if (istype(friend, /mob/living/simple_mob/animal/sif))
var/mob/living/simple_mob/animal/sif/critter = friend
return critter.health < (critter.getMaxHealth() * critter.sap_heal_threshold)
// Other animals just need to be injured.
return (friend.health < friend.maxHealth)
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/proc/get_pack_leader()
var/pack = FALSE
var/mob/living/simple_mob/animal/sif/grafadreka/leader
if(!is_baby)
if (!is_baby)
leader = src
for(var/mob/living/simple_mob/animal/sif/grafadreka/follower in hearers(7, loc))
if(follower == src || follower.is_baby || follower.stat == DEAD || follower.faction != faction)
for (var/mob/living/simple_mob/animal/sif/grafadreka/follower in hearers(7, loc))
if (follower == src || follower.is_baby || follower.stat == DEAD || follower.faction != faction)
continue
pack = TRUE
if(!leader || follower.charisma > leader.charisma)
if (!leader || follower.charisma > leader.charisma)
leader = follower
if(pack)
if (pack)
return leader
/mob/living/simple_mob/animal/sif/grafadreka/proc/check_leader_status()
var/mob/living/simple_mob/animal/sif/grafadreka/leader = get_pack_leader()
if(src == leader)
if (src == leader)
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/proc/can_bite(var/mob/living/M)
return istype(M) && (M.lying || M.confused || M.incapacitated())
/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 = !can_bite(A)
if(last_attack_was_claws != attacking_with_claws)
if(attacking_with_claws) // Use claws.
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()
@@ -551,83 +474,33 @@ var/global/list/wounds_being_tended_by_drakes = list()
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/sit_down()
set name = "Sit Down"
set category = "IC"
if (sitting)
resting = FALSE
sitting = FALSE
else
resting = TRUE
sitting = TRUE
to_chat(src, SPAN_NOTICE("You are now [sitting ? "sitting" : "getting up"]."))
update_canmove()
update_icon()
/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))
if (!has_modifier_of_type(/datum/modifier/ace))
to_chat(src, SPAN_WARNING("You aren't the pack leader! Sit down!"))
return
audible_message("<b>\The [src]</b> barks loudly and rattles its neck spines.")
for(var/mob/living/simple_mob/animal/sif/grafadreka/drake in hearers(world.view * 3, src))
if(drake == src || drake.faction != faction)
for (var/mob/living/simple_mob/animal/sif/grafadreka/drake in hearers(world.view * 3, src))
if (drake == src || drake.faction != faction)
continue
if(drake.client)
if (drake.client)
to_chat(drake, SPAN_NOTICE("<b>The pack leader wishes for you to follow them.</b>"))
else if(drake.ai_holder)
else if (drake.ai_holder)
drake.ai_holder.set_follow(src)
/mob/living/simple_mob/animal/sif/grafadreka/has_appetite()
return reagents && abs(reagents.total_volume - reagents.maximum_volume) >= 10
/mob/living/simple_mob/animal/sif/grafadreka/Login()
. = ..()
charisma = (client && !is_baby) ? INFINITY : 0
/mob/living/simple_mob/animal/sif/grafadreka/Logout()
. = ..()
if(!client)
charisma = rand(5, 15)
/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 = "drake den"
critter = /mob/living/simple_mob/animal/sif/grafadreka
/obj/structure/animal_den/ghost_join/grafadreka_hatchling
name = "drake hatchling den"
critter = /mob/living/simple_mob/animal/sif/grafadreka/hatchling
// 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/hatchling
name = "grafadreka hatchling"
icon = 'icons/mob/drake_baby.dmi'
mob_size = MOB_SMALL
desc = "An immature snow drake, not long out of the shell."
is_baby = TRUE
offset_compiled_icon = null
melee_damage_lower = 3
melee_damage_upper = 5
attack_armor_pen = 2
bite_melee_damage_lower = 5
bite_melee_damage_upper = 10
bite_attack_armor_pen = 16
projectiletype = /obj/item/projectile/drake_spit/weak
maxHealth = 60
health = 60

View File

@@ -0,0 +1,97 @@
/obj/item/storage/internal/animal_harness/grafadreka
abstract_type = /obj/item/storage/internal/animal_harness/grafadreka
name = "grafadreka harness"
self_attach_delay = 3 SECONDS
// keys for animal_harness/attachable_type
var/const/ATTACHED_GPS = "gps"
var/const/ATTACHED_RADIO = "radio"
var/const/ATTACHED_ARMOR = "armor plate"
var/const/ATTACHED_LIGHT = "light"
/// An attachable_types list shared between drake harness instances.
var/static/list/grafadreka_attachable_types = list(
/obj/item/gps = ATTACHED_GPS,
/obj/item/radio = ATTACHED_RADIO,
/obj/item/clothing/accessory/armor = list(
ATTACHED_ARMOR,
/obj/item/storage/internal/animal_harness/grafadreka/proc/UpdateArmor
),
/obj/item/clothing/accessory/material/makeshift = list(
ATTACHED_ARMOR,
/obj/item/storage/internal/animal_harness/grafadreka/proc/UpdateArmor
),
/obj/item/flashlight = ATTACHED_LIGHT
)
/// The drake that owns this harness.
var/mob/living/simple_mob/animal/sif/grafadreka/trained/owner
/obj/item/storage/internal/animal_harness/grafadreka/Destroy()
attachable_types = null
owner = null
return ..()
/obj/item/storage/internal/animal_harness/grafadreka/Initialize(mapload)
attachable_types = grafadreka_attachable_types
. = ..()
owner = loc
if (!istype(owner))
log_debug("Drake harness created without a drake!")
return INITIALIZE_HINT_QDEL
/obj/item/storage/internal/animal_harness/grafadreka/proc/UpdateArmor()
if (!owner)
return
var/obj/item/clothing/accessory/armor = attached_items[ATTACHED_ARMOR]
if (!armor)
owner.armor = owner.original_armor
return
for (var/key in armor.armor)
armor[key] = max(owner.original_armor[key], armor.armor[key])
// Basic trained drake harness contents on spawn
/obj/item/storage/internal/animal_harness/grafadreka/trained/CreateAttachments()
attached_items[ATTACHED_RADIO] = new /obj/item/radio (owner)
new /obj/item/stack/medical/bruise_pack (src)
new /obj/item/stack/medical/ointment (src)
var/obj/item/storage/mre/mre_type = pick(typesof(/obj/item/storage/mre) - list(
/obj/item/storage/mre/menu11,
/obj/item/storage/mre/menu12,
/obj/item/storage/mre/menu13
))
new mre_type (src)
// Station/Science drake harness contents on spawn
/obj/item/storage/internal/animal_harness/grafadreka/expedition/CreateAttachments()
attached_items[ATTACHED_RADIO] = new /obj/item/radio (owner)
new /obj/item/stack/medical/bruise_pack (src)
new /obj/item/stack/medical/ointment (src)
new /obj/item/storage/mre/menu13 (src) // The good stuff
var/obj/item/gps/explorer/on/gps = new (owner)
gps.SetTag(owner.name)
attached_items[ATTACHED_GPS] = gps
attached_items[ATTACHED_LIGHT] = new /obj/item/flashlight/glowstick/grafadreka (owner)
/obj/item/flashlight/glowstick/grafadreka
name = "high duration glowstick"
action_button_name = null
/obj/item/flashlight/glowstick/grafadreka/Initialize()
. = ..()
var/obj/item/flashlight/glowstick/archetype = pick(typesof(/obj/item/flashlight/glowstick) - type)
flashlight_colour = initial(archetype.flashlight_colour)
icon_state = initial(archetype.icon_state)
item_state = initial(archetype.item_state)
fuel = rand(3200, 4800)
on = TRUE
update_icon()
START_PROCESSING(SSobj, src)

View File

@@ -0,0 +1,18 @@
/mob/living/simple_mob/animal/sif/grafadreka/hatchling
name = "grafadreka hatchling"
icon = 'icons/mob/drake_baby.dmi'
mob_size = MOB_SMALL
desc = "An immature snow drake, not long out of the shell."
is_baby = TRUE
offset_compiled_icon = null
melee_damage_lower = 3
melee_damage_upper = 5
attack_armor_pen = 2
bite_melee_damage_lower = 5
bite_melee_damage_upper = 10
bite_attack_armor_pen = 16
projectiletype = /obj/item/projectile/drake_spit/weak
maxHealth = 60
health = 60

View File

@@ -0,0 +1,115 @@
/// Behaviors for being booped by drakes. If falsy, falls through to other behaviors.
/atom/proc/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/drake)
return
/* Simple drake interactions */
/obj/structure/simple_door/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/drake)
. = TRUE
if (drake.a_intent == I_HURT)
return ..()
if (!state && !isSwitchingStates)
Open()
/obj/structure/loot_pile/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/drake)
. = TRUE
if (drake.a_intent == I_HURT)
return ..()
attack_hand(drake)
/obj/item/bikehorn/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/drake)
. = TRUE
if (drake.a_intent != I_HELP)
return ..()
if (!do_after(drake, 1 SECOND, src))
return
drake.visible_message(
SPAN_ITALIC("\The [drake] gnaws on \a [src]."),
SPAN_ITALIC("You gnaw on \the [src]."),
range = 5
)
attack_self(drake)
/* Trained drake interactions */
/mob/living/simple_mob/animal/sif/grafadreka/trained/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/drake)
if (drake == src && a_intent == I_GRAB)
DropItem()
return TRUE
return ..()
/obj/item/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/trained/drake)
if (drake.trained_drake && drake.a_intent == I_GRAB)
drake.CollectItem(src)
return TRUE
/obj/machinery/button/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/trained/drake)
. = TRUE
if (!drake.trained_drake || drake.a_intent == I_HURT)
return ..()
var/datum/gender/gender = gender_datums[drake.get_visible_gender()]
drake.visible_message(
SPAN_ITALIC("\The [drake] stands up awkwardly on [gender.his] hind legs and paws at \a [src]."),
SPAN_ITALIC("You rear up, attempting to push \the [src] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(drake, 5 SECONDS, src))
return
to_chat(drake, SPAN_NOTICE("After some effort, you manage to push \the [src]."))
attack_hand(drake)
/obj/machinery/access_button/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/trained/drake)
. = TRUE
if (!drake.trained_drake || drake.a_intent == I_HURT)
return ..()
var/datum/gender/gender = gender_datums[drake.get_visible_gender()]
drake.visible_message(
SPAN_ITALIC("\The [drake] stands up awkwardly on [gender.his] hind legs and paws at \a [src]."),
SPAN_ITALIC("You rear up, attempting to push \the [src] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(drake, 5 SECONDS, src))
return
to_chat(drake, SPAN_NOTICE("After some effort, you manage to push \the [src]."))
attack_hand(drake)
/obj/machinery/firealarm/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/trained/drake)
. = TRUE
if (!drake.trained_drake || drake.a_intent == I_HURT)
return ..()
var/datum/gender/gender = gender_datums[drake.get_visible_gender()]
drake.visible_message(
SPAN_ITALIC("\The [drake] stands up awkwardly on [gender.his] hind legs and paws at \a [src]."),
SPAN_ITALIC("You rear up, attempting to push \the [src] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(drake, 5 SECONDS, src))
return
to_chat(drake, SPAN_NOTICE("After some effort, you manage to push \the [src]."))
attack_hand(src)
/obj/machinery/conveyor_switch/interaction_grafadreka(mob/living/simple_mob/animal/sif/grafadreka/trained/drake)
. = TRUE
if (!drake.trained_drake || drake.a_intent == I_HURT)
return ..()
visible_message(
SPAN_ITALIC("\The [drake] pushes bodily against \a [src]."),
SPAN_ITALIC("You press your shoulder into \the [src], trying to change its direction."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(drake, 2 SECONDS, src))
return
to_chat(drake, SPAN_NOTICE("After some effort, you manage to push \the [src]."))
attack_hand(drake)

View File

@@ -0,0 +1,118 @@
/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.</span>"
on_expired_text = "<span class = 'notice'>The last of the sap in your wounds fizzles away.</span>"
/datum/modifier/sifsap_salve/tick()
if (holder.stat == DEAD || holder.isSynthetic())
expire()
if (istype(holder, /mob/living/simple_mob/animal/sif))
var/mob/living/simple_mob/animal/sif/critter = holder
if (critter.health >= (critter.getMaxHealth() * critter.sap_heal_threshold))
return
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)
/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
/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")
/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"
)
/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'
broadcast_sound ='sound/effects/drakehowl_far.ogg'
emote_cooldown = 20 SECONDS
broadcast_distance = 90
/decl/emote/audible/drake_howl/broadcast_emote_to(send_sound, mob/target, direction)
. = ..()
if (.)
to_chat(target, SPAN_NOTICE("You hear an eerie howl from somewhere to the [dir2text(direction)]."))
/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'
/obj/item/projectile/drake_spit/weak
stun = 0
weaken = 0
eyeblur = 2
/obj/structure/animal_den/ghost_join/grafadreka
name = "drake den"
critter = /mob/living/simple_mob/animal/sif/grafadreka
/obj/structure/animal_den/ghost_join/grafadreka_hatchling
name = "drake hatchling den"
critter = /mob/living/simple_mob/animal/sif/grafadreka/hatchling
/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)
..()

View File

@@ -0,0 +1,136 @@
/mob/living/simple_mob/animal/sif/grafadreka/trained
desc = "A large, sleek snow drake with heavy claws, powerful jaws and many pale spines along its body. This one is wearing some kind of harness; maybe it belongs to someone."
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>Using <font color='#e0a000'>grab intent</font> you can pick up and drop items on by clicking them or yourself, \
and can interact with some simple machines like buttons and levers.<br>Unlike your wild kin, you are <b>trained</b> and work happily with your two-legged packmates."
faction = "station"
ai_holder_type = null // These guys should not exist without players.
gender = PLURAL // Will take gender from prefs = set to non-NEUTER here to avoid randomizing in Initialize().
movement_cooldown = 1.5 // ~Red~ trained ones go faster.
dexterity = MOB_DEXTERITY_SIMPLE_MACHINES
harness = /obj/item/storage/internal/animal_harness/grafadreka/trained
trained_drake = TRUE
/// On clicking with an item, stuff that should use behaviors instead of being placed in storage.
var/static/list/allow_type_to_pass = list(
/obj/item/healthanalyzer,
/obj/item/stack/medical,
/obj/item/reagent_containers/syringe
)
/mob/living/simple_mob/animal/sif/grafadreka/trained/Destroy()
if (istype(harness))
QDEL_NULL(harness)
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/add_glow()
. = ..()
if (. && harness)
var/image/I = .
I.icon_state = "[I.icon_state]-pannier"
/mob/living/simple_mob/animal/sif/grafadreka/trained/Logout()
..()
if (stat != DEAD)
lying = TRUE
resting = TRUE
sitting = FALSE
Sleeping(2)
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/trained/Login()
..()
SetSleeping(0)
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/trained/attack_hand(mob/living/user)
// Permit headpats/smacks
if (!harness || user.a_intent == I_HURT || (user.a_intent == I_HELP && user.zone_sel?.selecting == BP_HEAD))
return ..()
return harness.handle_attack_hand(user)
// universal_understand is buggy and produces double lines, so we'll just do this hack instead.
/mob/living/simple_mob/animal/sif/grafadreka/trained/say_understands(mob/other, datum/language/speaking)
if (!speaking || speaking.name == LANGUAGE_GALCOM)
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/update_icon()
. = ..()
if (harness)
var/image/I = image(icon, "[current_icon_state]-pannier")
I.color = harness.color
I.appearance_flags |= (RESET_COLOR|PIXEL_SCALE|KEEP_APART)
if (offset_compiled_icon)
I.pixel_x = offset_compiled_icon
add_overlay(I)
/mob/living/simple_mob/animal/sif/grafadreka/trained/attackby(obj/item/item, mob/user)
if (user.a_intent == I_HURT)
return ..()
if (user.a_intent == I_HELP)
for (var/pass_type in allow_type_to_pass)
if (istype(item, pass_type))
return ..()
if (harness?.attackby(item, user))
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/DropItem()
if (!length(harness?.contents))
to_chat(src, SPAN_WARNING("You have nothing to drop."))
return
var/obj/item/response = input(src, "Select an item to drop:") as null | anything in harness.contents
if (!response)
return
var/datum/gender/gender = gender_datums[get_visible_gender()]
visible_message(
SPAN_ITALIC("\The [src] begins rooting around in the pouch on [gender.his] harness."),
SPAN_ITALIC("You begin working \the [response] out of your harness pouch."),
SPAN_ITALIC("You hear something rustling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 3 SECONDS, ignore_movement = TRUE))
return
harness.remove_from_storage(response, loc)
visible_message(
SPAN_ITALIC("\The [src] pulls \a [response] from [gender.his] harness and drops it."),
SPAN_NOTICE("You pull \the [response] from your harness and drop it."),
SPAN_WARNING("Clank!"),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
return
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/CollectItem(obj/item/item)
if (!item.simulated || item.abstract || !item.Adjacent(src))
return
if (!harness)
to_chat(src, SPAN_WARNING("Your harness is missing; you cannot store \the [item]."))
return
if (item.anchored)
to_chat(src, SPAN_WARNING("\The [item] is securely anchored; you can't take it."))
return
face_atom(item)
if (!do_after(src, 3 SECONDS, item))
return
var/datum/gender/gender = gender_datums[get_visible_gender()]
if (harness?.attackby(item, src, TRUE))
visible_message(
SPAN_ITALIC("\The [src] grabs \a [item] in [gender.his] teeth and noses it into [gender.his] harness pouch."),
SPAN_NOTICE("You grab \the [item] in your teeth and push it into your harness pouch."),
SPAN_ITALIC("You hear something rustling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
return
to_chat(src, SPAN_WARNING("There's not enough space in your harness pouch for \the [item] to fit!"))

View File

@@ -1,133 +0,0 @@
// It's just a backpack.
/obj/item/storage/internal/animal_harness
color = COLOR_BEASTY_BROWN
max_w_class = ITEMSIZE_LARGE
max_storage_space = INVENTORY_STANDARD_SPACE
var/obj/item/gps/attached_gps
var/obj/item/clothing/accessory/attached_plate
var/obj/item/radio/attached_radio
/obj/item/storage/internal/animal_harness/Initialize()
// Spawn some useful items. We can't use them, but anyone we find can.
new /obj/item/stack/medical/bruise_pack(src)
new /obj/item/stack/medical/ointment(src)
var/ration_type = pick(list(
/obj/item/storage/mre,
/obj/item/storage/mre/menu2,
/obj/item/storage/mre/menu3,
/obj/item/storage/mre/menu4,
/obj/item/storage/mre/menu5,
/obj/item/storage/mre/menu6,
/obj/item/storage/mre/menu7,
/obj/item/storage/mre/menu8,
/obj/item/storage/mre/menu9,
/obj/item/storage/mre/menu10
))
new ration_type(src)
. = ..() // Name is set lower in Initialize() so we set it again here.
name = "animal harness"
/obj/item/storage/internal/animal_harness/Destroy()
var/mob/living/simple_mob/animal/sif/grafadreka/trained/drake = loc
if(istype(drake) && drake.harness == src)
drake.harness = null
drake.armor = drake.original_armor
QDEL_NULL(attached_radio)
QDEL_NULL(attached_gps)
QDEL_NULL(attached_plate)
return ..()
/obj/item/storage/internal/animal_harness/attackby(obj/item/W, mob/user, silent)
if (user == loc) // Drakes can't attach stuff to themselves, only shove it in their storage.
return ..()
if (!istype(loc, /mob/living/simple_mob/animal/sif/grafadreka/trained)) // Only allow attaching behaviors when worn by an appropriate subject.
return ..()
if (istype(W, /obj/item/gps)) // Attach a tracker.
if (attached_gps)
if (!silent)
to_chat(user, SPAN_WARNING("There is already \a [attached_gps] attached to \the [loc]'s harness."))
else if (user.unEquip(W))
W.forceMove(loc)
if (!silent)
user.visible_message(SPAN_NOTICE("\The [user] attaches \the [W] to \the [loc]'s harness."))
attached_gps = W
return TRUE
if (istype(W, /obj/item/radio)) // Attach a radio.
if (attached_radio)
if (!silent)
to_chat(user, SPAN_WARNING("There is already \a [attached_radio] attached to \the [loc]'s harness."))
else if (user.unEquip(W))
W.forceMove(loc)
if (!silent)
user.visible_message(SPAN_NOTICE("\The [user] attaches \the [W] to \the [loc]'s harness."))
attached_radio = W
return TRUE
if (istype(W, /obj/item/clothing/accessory/armor) || istype(W, /obj/item/clothing/accessory/material/makeshift)) // Attach an armor plate.
if (attached_plate)
if (!silent)
to_chat(user, SPAN_WARNING("There is already \a [attached_plate] inside \the [loc]'s harness."))
else if (user.unEquip(W))
W.forceMove(loc)
if (!silent)
user.visible_message(SPAN_NOTICE("\The [user] secures \the [W] inside \the [loc]'s harness."))
attached_plate = W
var/mob/living/simple_mob/animal/sif/grafadreka/trained/drake = loc
drake.recalculate_armor()
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/recalculate_armor()
armor = list()
if(istype(harness) && harness.attached_plate)
for(var/armor_key in harness.attached_plate.armor)
armor[armor_key] = max(original_armor[armor_key], harness.attached_plate.armor[armor_key])
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_plate()
set name = "Remove Attached Plate"
set category = "IC"
set src in view(1)
if(istype(harness) && harness.attached_plate)
harness.attached_plate.dropInto(get_turf(src))
if(usr == src)
var/datum/gender/G = gender_datums[get_visible_gender()]
visible_message("\The [src] awkwardly pulls \the [harness.attached_plate] out of [G.his] harness and drops it.")
else
visible_message("\The [usr] removes \the [harness.attached_plate] from \the [src]'s harness.")
usr.put_in_hands(harness.attached_plate)
harness.attached_plate = null
armor = original_armor
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_plate
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_gps()
set name = "Remove Attached GPS"
set category = "IC"
set src in view(1)
if(istype(harness) && harness.attached_gps)
harness.attached_gps.dropInto(get_turf(src))
if(usr == src)
var/datum/gender/G = gender_datums[get_visible_gender()]
visible_message("\The [src] awkwardly pulls \the [harness.attached_gps] off [G.his] harness and drops it.")
else
visible_message("\The [usr] detaches \the [harness.attached_gps] from \the [src]'s harness.")
usr.put_in_hands(harness.attached_gps)
harness.attached_gps = null
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_gps
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_radio()
set name = "Remove Attached Radio"
set category = "IC"
set src in view(1)
if(istype(harness) && harness.attached_radio)
harness.attached_radio.dropInto(get_turf(src))
if(usr == src)
var/datum/gender/G = gender_datums[get_visible_gender()]
visible_message("\The [src] awkwardly pulls \the [harness.attached_radio] off [G.his] harness and drops it.")
else
visible_message("\The [usr] detaches \the [harness.attached_radio] from \the [src]'s harness.")
usr.put_in_hands(harness.attached_radio)
harness.attached_radio = null
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_radio

View File

@@ -1,298 +0,0 @@
/mob/living/simple_mob/animal/sif/grafadreka/trained
desc = "A large, sleek snow drake with heavy claws, powerful jaws and many pale spines along its body. This one is wearing some kind of harness; maybe it belongs to someone."
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>Using <font color='#e0a000'>grab intent</font> you can pick up and drop items on by clicking them or yourself, \
and can interact with some simple machines like buttons and levers.<br>Unlike your wild kin, you are <b>trained</b> and work happily with your two-legged packmates."
faction = "station"
ai_holder_type = null // These guys should not exist without players.
gender = PLURAL // Will take gender from prefs = set to non-NEUTER here to avoid randomizing in Initialize().
movement_cooldown = 1.5 // ~Red~ trained ones go faster.
dexterity = MOB_DEXTERITY_SIMPLE_MACHINES
/// If a valid harness path, the harness this drake will be wearing on Initialize.
var/obj/item/storage/internal/animal_harness/harness = /obj/item/storage/internal/animal_harness
/// On clicking with an item, stuff that should use behaviors instead of being placed in storage.
var/static/list/allow_type_to_pass = list(
/obj/item/healthanalyzer,
/obj/item/stack/medical,
/obj/item/reagent_containers/syringe
)
/// A type path -> proc path mapping for objects that drakes can use.
var/static/list/interactable_objects = list(
/obj/machinery/button = /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractButtonBasic,
/obj/machinery/access_button = /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractButtonAccess,
/obj/machinery/firealarm = /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractFireAlarm,
/obj/machinery/conveyor_switch = /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractConveyorSwitch
)
/mob/living/simple_mob/animal/sif/grafadreka/trained/Destroy()
if (istype(harness))
QDEL_NULL(harness)
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/Initialize()
if (ispath(harness, /obj/item/storage/internal/animal_harness))
harness = new harness(src)
harness.attached_radio = new /obj/item/radio(src)
regenerate_harness_verbs()
else
if (harness)
log_error("[type] initialized with an invalid harness [harness]")
harness = null
// Do this after creating the harness so the update icon proc doesn't runtime.
. = ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/examine(mob/living/user)
. = ..()
if (istype(harness))
. += "\The [src] is wearing \a [harness]."
for (var/obj/item/thing in list(harness.attached_gps, harness.attached_plate, harness.attached_radio))
. += "There is \a [thing] attached."
/mob/living/simple_mob/animal/sif/grafadreka/trained/add_glow()
. = ..()
if (. && harness)
var/image/I = .
I.icon_state = "[I.icon_state]-pannier"
/mob/living/simple_mob/animal/sif/grafadreka/trained/Logout()
..()
if (stat != DEAD)
lying = TRUE
resting = TRUE
sitting = FALSE
Sleeping(2)
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/trained/Login()
..()
SetSleeping(0)
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/trained/attack_hand(mob/living/user)
// Permit headpats/smacks
if (!harness || user.a_intent == I_HURT || (user.a_intent == I_HELP && user.zone_sel?.selecting == BP_HEAD))
return ..()
return harness.handle_attack_hand(user)
// universal_understand is buggy and produces double lines, so we'll just do this hack instead.
/mob/living/simple_mob/animal/sif/grafadreka/trained/say_understands(mob/other, datum/language/speaking)
if (!speaking || speaking.name == LANGUAGE_GALCOM)
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/update_icon()
. = ..()
if (istype(harness))
var/image/I = image(icon, "[current_icon_state]-pannier")
I.color = harness.color
I.appearance_flags |= (RESET_COLOR|PIXEL_SCALE|KEEP_APART)
if (offset_compiled_icon)
I.pixel_x = offset_compiled_icon
add_overlay(I)
/mob/living/simple_mob/animal/sif/grafadreka/trained/attackby(obj/item/item, mob/user)
// bonk
if (user.a_intent == I_HURT)
return ..()
if (user.a_intent == I_HELP)
for (var/pass_type in allow_type_to_pass)
if (istype(item, pass_type))
return ..()
// Open our storage, if we have it.
if (istype(harness) && harness.attackby(item, user))
regenerate_harness_verbs()
return TRUE
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/regenerate_harness_verbs()
if (!istype(harness))
verbs -= list(
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_gps,
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_plate,
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_radio
)
return
if (harness.attached_gps)
verbs |= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_gps
else
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_gps
if (harness.attached_plate)
verbs |= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_plate
else
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_plate
if (harness.attached_radio)
verbs |= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_radio
else
verbs -= /mob/living/simple_mob/animal/sif/grafadreka/trained/proc/remove_attached_radio
/mob/living/simple_mob/animal/sif/grafadreka/trained/attack_target(atom/atom)
if (a_intent == I_GRAB)
if (atom == src)
return DropItem()
if (isobj(atom))
var/interact_outcome = AttemptInteract(atom)
if (!isnull(interact_outcome))
return interact_outcome
if (isitem(atom))
return CollectItem(atom)
return ..()
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/DropItem()
if(!istype(harness) || !length(harness.contents))
to_chat(src, SPAN_WARNING("You have nothing to drop."))
return ATTACK_FAILED
var/list/attached = list(harness.attached_gps, harness.attached_radio, harness.attached_plate)
var/obj/item/response = input(src, "Select an item to drop:") as null | anything in attached + harness.contents
if (!response)
return ATTACK_FAILED
var/datum/gender/gender = gender_datums[get_visible_gender()]
var/is_special = (response in attached)
if (is_special)
visible_message(
SPAN_ITALIC("\The [src] begins tugging at \the [response] on [gender.his] harness."),
SPAN_ITALIC("You begin tugging \the [response] off your harness."),
SPAN_ITALIC("You hear something rustling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
else
visible_message(
SPAN_ITALIC("\The [src] begins rooting around in the pouch on [gender.his] harness."),
SPAN_ITALIC("You begin working \the [response] out of your harness pouch."),
SPAN_ITALIC("You hear something rustling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 5 SECONDS, ignore_movement = TRUE))
return ATTACK_FAILED
if (is_special)
if (!(response in src))
to_chat(src, SPAN_WARNING("\The [response] is already gone!"))
return ATTACK_FAILED
if (response == harness.attached_gps)
harness.attached_gps = null
else if (response == harness.attached_radio)
harness.attached_radio = null
else if (response == harness.attached_plate)
harness.attached_plate = null
response.dropInto(loc)
regenerate_harness_verbs()
else
harness.remove_from_storage(response, loc)
visible_message(
SPAN_ITALIC("\The [src] pulls \a [response] from [gender.his] harness and drops it."),
SPAN_NOTICE("You pull \the [response] from your harness and drop it."),
SPAN_WARNING("Clank!"),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
return ATTACK_SUCCESSFUL
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/CollectItem(obj/item/item)
if (!item.simulated || item.abstract || !item.Adjacent(src))
return ATTACK_FAILED
if (!istype(harness))
to_chat(src, SPAN_WARNING("Your harness is missing; you cannot store \the [item]."))
return ATTACK_FAILED
if (item.anchored)
to_chat(src, SPAN_WARNING("\The [item] is securely anchored; you can't take it."))
return ATTACK_FAILED
face_atom(item)
if (!do_after(src, 5 SECONDS, item))
return ATTACK_FAILED
var/datum/gender/gender = gender_datums[get_visible_gender()]
if (istype(harness) && harness.attackby(item, src, TRUE))
visible_message(
SPAN_ITALIC("\The [src] grabs \a [item] in [gender.his] teeth and noses it into [gender.his] harness pouch."),
SPAN_NOTICE("You grab \the [item] in your teeth and push it into your harness pouch."),
SPAN_ITALIC("You hear something rustling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
return ATTACK_SUCCESSFUL
to_chat(src, SPAN_WARNING("There's not enough space in your harness pouch for \the [item] to fit!"))
return ATTACK_FAILED
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/AttemptInteract(obj/obj)
if (!obj.Adjacent(src))
return
var/handler
for (var/type in interactable_objects)
if (ispath(obj.type, type))
handler = interactable_objects[type]
break
if (!handler)
return
return call(src, handler)(obj)
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractButtonBasic(obj/machinery/button/button)
var/datum/gender/gender = gender_datums[get_visible_gender()]
visible_message(
SPAN_ITALIC("\The [src] stands up awkwardly on [gender.his] hind legs and paws at \a [button]."),
SPAN_ITALIC("You rear up, attempting to push \the [button] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 5 SECONDS, button))
return ATTACK_FAILED
to_chat(src, SPAN_NOTICE("After some effort, you manage to push \the [button]."))
button.attack_hand(src)
return ATTACK_SUCCESSFUL
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractButtonAccess(obj/machinery/access_button/button)
var/datum/gender/gender = gender_datums[get_visible_gender()]
visible_message(
SPAN_ITALIC("\The [src] stands up awkwardly on [gender.his] hind legs and paws at \a [button]."),
SPAN_ITALIC("You rear up, attempting to push \the [button] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 5 SECONDS, button))
return ATTACK_FAILED
to_chat(src, SPAN_NOTICE("After some effort, you manage to push \the [button]."))
button.attack_hand(src)
return ATTACK_SUCCESSFUL
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractFireAlarm(obj/machinery/firealarm/alarm)
var/datum/gender/gender = gender_datums[get_visible_gender()]
visible_message(
SPAN_ITALIC("\The [src] stands up awkwardly on [gender.his] hind legs and paws at \a [alarm]."),
SPAN_ITALIC("You rear up, attempting to push \the [alarm] with your foreclaws."),
SPAN_WARNING("You hear something scratching and scrabbling."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 5 SECONDS, alarm))
return ATTACK_FAILED
to_chat(src, SPAN_NOTICE("After some effort, you manage to push \the [alarm]."))
alarm.attack_hand(src)
return ATTACK_SUCCESSFUL
/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/InteractConveyorSwitch(obj/machinery/conveyor_switch/lever)
visible_message(
SPAN_ITALIC("\The [src] pushes bodily against \a [lever]."),
SPAN_ITALIC("You press your shoulder into \the [lever], trying to change its direction."),
runemessage = CHAT_MESSAGE_DEFAULT_ACTION
)
if (!do_after(src, 2 SECONDS, lever))
return ATTACK_FAILED
to_chat(src, SPAN_NOTICE("After some effort, you manage to push \the [lever]."))
lever.attack_hand(src)
return ATTACK_SUCCESSFUL