diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 743b23db0c..38d77abb2b 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -61,7 +61,16 @@ HALOGEN COUNTER - Radcount on mobs
flick("[icon_state]-scan", src) //makes it so that it plays the scan animation on a succesful scan
user.visible_message("[user] has analyzed [M]'s vitals.","You have analyzed [M]'s vitals.")
- if (!ishuman(M) || M.isSynthetic())
+ // Give a very simple bit of feedback for aminals.
+ if(isanimal(M))
+ dat += "Analyzing Results for \the [M]:
"
+ dat += "\tKey: Suffocation/Toxin/Burns/Brute
"
+ dat += "\tDamage Specifics: [M.getOxyLoss()] - [M.getToxLoss()] - [M.getFireLoss()] - [M.getBruteLoss()]
"
+ dat += "Body temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)
"
+ user.show_message(dat, 1)
+ return
+
+ else if (!ishuman(M) || M.isSynthetic())
//these sensors are designed for organic life
dat += "Analyzing Results for ERROR:\n\tOverall Status: ERROR
"
dat += "\tKey: Suffocation/Toxin/Burns/Brute
"
diff --git a/code/game/objects/structures/animal_den.dm b/code/game/objects/structures/animal_den.dm
index 9d21adf09c..ab337859eb 100644
--- a/code/game/objects/structures/animal_den.dm
+++ b/code/game/objects/structures/animal_den.dm
@@ -5,6 +5,7 @@
desc = "A rough-walled, shallow den dug into the earth."
density = FALSE
opacity = FALSE
+ layer = ABOVE_TURF_LAYER
/obj/structure/animal_den/Initialize()
icon_state = "den[rand(1,3)]"
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index 7a53e29f38..8911397099 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -4,7 +4,7 @@
var/hostile = FALSE // Do we try to hurt others?
var/retaliate = FALSE // Attacks whatever struck it first. Mobs will still attack back if this is false but hostile is true.
var/mauling = FALSE // Attacks unconscious mobs
- var/ignore_incapacitated = FALSE // If it's interested in attacking targets that are STUNNED.
+ var/ignore_incapacitated = FALSE // If it's interested in attacking targets that are STUNNED.
var/handle_corpse = FALSE // Allows AI to acknowledge corpses (e.g. nurse spiders)
var/atom/movable/target = null // The thing (mob or object) we're trying to kill.
@@ -132,7 +132,7 @@
return FALSE
if(L.incapacitated(INCAPACITATION_STUNNED)) // Are they stunned and do we care?
if(ignore_incapacitated)
- return FALSE
+ return FALSE
if(holder.IIsAlly(L))
return FALSE
return TRUE
@@ -166,7 +166,7 @@
ai_log("lose_target() : Entering.", AI_LOG_TRACE)
if(target)
ai_log("lose_target() : Had a target, checking if still valid.", AI_LOG_DEBUG)
- if(!can_attack(target, FALSE)) /// If it's not valid to chase, don't keep looking.
+ if(!can_attack(target, FALSE)) /// If it's not valid to chase, don't keep looking.
remove_target()
return find_target()
ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG)
@@ -251,6 +251,7 @@
if(holder.IIsAlly(attacker)) // I'll overlook it THIS time...
ai_log("react_to_attack() : Was attacked by [attacker], but they were an ally.", AI_LOG_TRACE)
return FALSE
+ holder.IWasAttackedBy(attacker)
if(target) // Already fighting someone. Switching every time we get hit would impact our combat performance.
if(!retaliate) // If we don't get to fight back, we don't fight back...
ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE)
diff --git a/code/modules/emotes/definitions/audible_whistle.dm b/code/modules/emotes/definitions/audible_whistle.dm
index dabd64af59..f2cc421d5e 100644
--- a/code/modules/emotes/definitions/audible_whistle.dm
+++ b/code/modules/emotes/definitions/audible_whistle.dm
@@ -28,9 +28,17 @@
/decl/emote/audible/whistle/summon
key = "swhistle"
- emote_message_1p = "You whistle a tune."
- emote_message_3p = "whistles a tune."
+ emote_message_1p = "You give an ear-piercing whistle."
+ emote_message_3p = "gives an ear-piercing whistle."
emote_sound = 'sound/voice/summon_whistle.ogg'
- emote_message_synthetic_1p = "You whistle a robotic tune."
- emote_message_synthetic_3p = "whistles a robotic tune."
+ emote_message_synthetic_1p = "You synthesise an ear-piercing whistle."
+ emote_message_synthetic_3p = "synthesises an ear-piercing whistle."
emote_sound_synthetic = 'sound/voice/summon_whistle_robot.ogg'
+ broadcast_sound = 'sound/voice/summon_whistle.ogg'
+ broadcast_sound_synthetic = 'sound/voice/summon_whistle_robot.ogg'
+ emote_cooldown = 20 SECONDS
+ broadcast_distance = 65
+
+/decl/emote/audible/whistle/summon/broadcast_emote_to(var/send_sound, var/mob/target, var/direction)
+ if((. = ..()))
+ to_chat(target, SPAN_NOTICE("You hear a piercing whistle from somewhere to the [dir2text(direction)]."))
diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm
index 5692a721cc..85cb69a7c9 100644
--- a/code/modules/emotes/emote_define.dm
+++ b/code/modules/emotes/emote_define.dm
@@ -37,12 +37,20 @@ var/global/list/emotes_by_key
var/emote_volume_synthetic = 50 // As above, but used when check_synthetic() is true.
var/emote_delay = 0 // Time in ds that this emote will block further emote use (spam prevention).
+ var/broadcast_sound // This sound will be passed to the entire connected z-chunk if set.
+ var/broadcast_sound_synthetic // As above but for synthetics.
+ var/broadcast_volume = 15 // Volume for broadcast sound.
+ var/broadcast_distance // How far does the sound broadcast.
+
var/message_type = VISIBLE_MESSAGE // Audible/visual flag
var/check_restraints // Can this emote be used while restrained?
var/check_range // falsy, or a range outside which the emote will not work
var/conscious = TRUE // Do we need to be awake to emote this?
var/emote_range = 0 // If >0, restricts emote visibility to viewers within range.
+ var/emote_cooldown // How long will we be on cooldown for this emote.
+ var/list/emote_cooldowns = list() // Assoc list of weakref to mob to next emote.
+
/decl/emote/Initialize()
. = ..()
if(key)
@@ -67,24 +75,41 @@ var/global/list/emotes_by_key
return emote_message_3p
/decl/emote/proc/get_emote_sound(var/atom/user)
- if(check_synthetic(user) && emote_sound_synthetic)
- return list(
+ var/synth = check_synthetic(user)
+ if(synth && emote_sound_synthetic)
+ . = list(
"sound" = emote_sound_synthetic,
"vol" = emote_volume_synthetic
)
- if(emote_sound)
- return list(
+ else if(emote_sound)
+ . = list(
"sound" = emote_sound,
"vol" = emote_volume
)
+ if(synth && broadcast_sound_synthetic)
+ LAZYINITLIST(.)
+ .["broadcast"] = broadcast_sound_synthetic
+ else if(broadcast_sound)
+ LAZYINITLIST(.)
+ .["broadcast"] = broadcast_sound
+
/decl/emote/proc/do_emote(var/atom/user, var/extra_params)
+
if(ismob(user) && check_restraints)
var/mob/M = user
if(M.restrained())
to_chat(user, SPAN_WARNING("You are restrained and cannot do that."))
return FALSE
+ if(emote_cooldown)
+ var/user_ref = "\ref[user]"
+ var/next_emote = emote_cooldowns[user_ref]
+ if(world.time < next_emote)
+ to_chat(user, SPAN_WARNING("You cannot use this emote again for another [round((next_emote - world.time)/(1 SECOND))] second\s."))
+ return FALSE
+ emote_cooldowns[user_ref] = (world.time + emote_cooldown)
+
var/atom/target
if(can_target() && extra_params)
var/target_dist
@@ -173,19 +198,65 @@ var/global/list/emotes_by_key
return
/decl/emote/proc/do_sound(var/atom/user)
+
+ var/turf/user_turf = get_turf(user)
+ if(!istype(user_turf))
+ return
+
var/list/use_sound = get_emote_sound(user)
if(!islist(use_sound) || length(use_sound) < 2)
return
+
var/sound_to_play = use_sound["sound"]
- if(!sound_to_play)
- return
- if(islist(sound_to_play))
- if(sound_to_play[user.gender])
- sound_to_play = sound_to_play[user.gender]
- if(islist(sound_to_play) && length(sound_to_play))
- sound_to_play = pick(sound_to_play)
if(sound_to_play)
- playsound(user.loc, sound_to_play, use_sound["vol"], 0)
+ if(islist(sound_to_play))
+ if(sound_to_play[user.gender])
+ sound_to_play = sound_to_play[user.gender]
+ if(islist(sound_to_play) && length(sound_to_play))
+ sound_to_play = pick(sound_to_play)
+ if(sound_to_play)
+ playsound(user.loc, sound_to_play, use_sound["vol"], 0)
+
+ var/sound_to_broadcast = use_sound["broadcast"]
+ if(!sound_to_broadcast)
+ return
+
+ // We can't always use GetConnectedZlevels() here because it includes horizontally connected z-levels, which don't work well with our distance checking.
+ var/list/affected_levels
+ if(isnull(broadcast_distance))
+ affected_levels = GetConnectedZlevels(user_turf.z)
+ else
+ affected_levels = list(user_turf.z)
+ // Climb to the top of the stack.
+ var/turf/checking = user_turf
+ while(checking && HasAbove(checking.z))
+ checking = GetAbove(checking)
+ affected_levels += checking.z
+ // Fall to the bottom of the stack.
+ checking = user_turf
+ while(checking && HasBelow(checking.z))
+ checking = GetBelow(checking)
+ affected_levels += checking.z
+
+ var/list/close_listeners = hearers(world.view, user_turf)
+ for(var/listener in player_list)
+ var/turf/T = get_turf(listener)
+ if(!istype(T) || !(T.z in affected_levels) || (listener in close_listeners))
+ continue
+ var/turf/reference_point = locate(T.x, T.y, user_turf.z)
+ if(!reference_point)
+ continue
+ var/direction = get_dir(reference_point, user_turf)
+ if(!direction)
+ continue
+ if(!isnull(broadcast_distance) && get_dist(reference_point, user_turf) > broadcast_distance)
+ continue
+ broadcast_emote_to(sound_to_broadcast, listener, direction)
+
+/decl/emote/proc/broadcast_emote_to(var/send_sound, var/mob/target, var/direction)
+ var/turf/sound_origin = get_turf(target)
+ target.playsound_local(get_step(sound_origin, direction) || sound_origin, send_sound, broadcast_volume)
+ return TRUE
/decl/emote/proc/mob_can_use(var/mob/user)
return istype(user) && user.stat != DEAD && (type in user.get_available_emotes())
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 77b51bbc15..e20f0e9e78 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -552,4 +552,12 @@
else
if(src.wear_mask) //if the mob is not human, it cleans the mask without asking for bitflags
if(src.wear_mask.clean_blood())
- src.update_inv_wear_mask(0)
\ No newline at end of file
+ src.update_inv_wear_mask(0)
+
+/mob/living/carbon/handle_reagent_transfer(var/datum/reagents/holder, var/amount = 1, var/chem_type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0)
+ if(chem_type == CHEM_BLOOD)
+ return holder.trans_to_holder(reagents, amount, multiplier, copy)
+ if(chem_type == CHEM_INGEST)
+ return ingest(holder, ingested, amount, multiplier, copy)
+ if(chem_type == CHEM_TOUCH)
+ return holder.trans_to_holder(touching, amount, multiplier, copy)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 7afe681d52..e07160cb19 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1184,3 +1184,6 @@
/mob/living/proc/get_snow_footprint_state()
if(!hovering) // Flying things shouldn't make footprints.
return "snow_footprints"
+
+/mob/living/proc/IWasAttackedBy(var/mob/living/attacker)
+ return
diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm
index d916136399..29f64bd9ef 100644
--- a/code/modules/mob/living/simple_mob/defense.dm
+++ b/code/modules/mob/living/simple_mob/defense.dm
@@ -5,8 +5,6 @@
// if(!P.SA_vulnerability || P.SA_vulnerability == intelligence_level)
if(P.SA_vulnerability & mob_class)
P.damage += P.SA_bonus_damage
- if(P.firer)
- IWasAttackedBy(P.firer)
. = ..()
@@ -49,10 +47,6 @@
apply_damage(damage = harm_intent_damage, damagetype = BRUTE, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE)
L.visible_message(SPAN_WARNING("\The [L] [response_harm] \the [src]!"))
L.do_attack_animation(src)
- IWasAttackedBy(L)
-
-/mob/living/simple_mob/proc/IWasAttackedBy(var/mob/living/attacker)
- return
// When somoene clicks us with an item in hand
/mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user)
@@ -120,8 +114,6 @@
if(supernatural && istype(O,/obj/item/nullrod))
effective_force *= 2
purge = 3
- if(user)
- IWasAttackedBy(user)
if(O.force <= resistance)
to_chat(user, SPAN_WARNING("This weapon is ineffective, it does no damage."))
return 2 //???
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm
index 72edf494d9..b6088e380b 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/animal.dm
@@ -53,6 +53,12 @@
/mob/living/simple_mob/animal/proc/has_appetite()
return TRUE
+/mob/living/simple_mob/animal/proc/add_nutrition(var/amt)
+ nutrition = clamp(nutrition + amt, 0, max_nutrition)
+
+/mob/living/simple_mob/animal/proc/remove_nutrition(var/amt)
+ nutrition = clamp(nutrition - amt, 0, max_nutrition)
+
/mob/living/simple_mob/animal/get_snow_footprint_state()
if(!hovering)
return "snow_animalprints"
@@ -123,3 +129,6 @@
snack.dropInto(loc)
else
snack.animal_consumed(src)
+
+/mob/living/simple_mob/animal/proc/get_dietary_food_modifier(var/datum/reagent/nutriment/food)
+ return 1
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka.dm
index b58cb034e6..a91c40577f 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka.dm
@@ -8,7 +8,7 @@
/datum/modifier/sifsap_salve/tick()
- if(holder.stat == DEAD)
+ if(holder.stat == DEAD || holder.isSynthetic())
expire()
if(istype(holder, /mob/living/simple_mob/animal/sif))
@@ -76,38 +76,17 @@ Field studies suggest analytical abilities on par with some species of cepholapo
"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
+ broadcast_sound ='sound/effects/drakehowl_far.ogg'
+ emote_cooldown = 20 SECONDS
+ broadcast_distance = 90
-/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/affected_levels = GetConnectedZlevels(user_turf.z)
- 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) || M.ear_deaf > 0 || (M in close_listeners) || !(T.z in affected_levels))
- continue
- var/turf/reference_point = locate(T.x, T.y, user_turf.z)
- if(reference_point)
- var/direction = get_dir(reference_point, user_turf)
- if(direction)
- to_chat(M, SPAN_NOTICE("You hear an eerie howl from somewhere to the [dir2text(direction)]"))
- M << 'sound/effects/drakehowl_far.ogg'
+/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)
@@ -224,27 +203,44 @@ var/global/list/last_drake_howl = list()
var/sitting = FALSE
var/next_spit = 0
var/spit_cooldown = 8 SECONDS
- var/next_alpha_check = 0
- var/dominance = 0 // A score used to determine pack leader.
+ var/next_leader_check = 0
+ var/charisma = 0 // A score used to determine pack leader.
var/stored_sap = 0
var/max_stored_sap = 60
- var/attacked_by_human = FALSE
+ var/attacked_by_neutral = FALSE
+ 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)
+
+ // 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()
- dominance = rand(5, 15)
+ charisma = rand(5, 15)
stored_sap = rand(20, 30)
nutrition = rand(400,500)
@@ -257,6 +253,7 @@ var/global/list/last_drake_howl = list()
. = ..()
+ original_armor = armor
update_icon()
/mob/living/simple_mob/animal/sif/grafadreka/examine(var/mob/living/user)
@@ -288,28 +285,34 @@ var/global/list/last_drake_howl = list()
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)
+ 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)
+ return holder.trans_to_holder(reagents, amount, multiplier, copy)
+
/mob/living/simple_mob/animal/sif/grafadreka/Life()
. = ..()
+ if(stat == CONSCIOUS)
+
+ // Don't make clientless drakes lose nutrition or they'll all go feral.
+ 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)
+ add_sap(0.35)
+
// Process food and sap chems.
- if(stat == CONSCIOUS && client) // Hibernating drakes don't get hungy.
- // by default we lose nutrition. Hungry hungry drakes.
-
- var/food_val = (resting || !client) ? 0 : -0.3 // Don't make clientless drakes lose nutrition or they'll all go feral.
-
- // 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.3 : 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)
+ if(reagents?.total_volume)
+ for(var/datum/reagent/chem in reagents.reagent_list)
+ var/removed = min(REM, min(chem.ingest_met, 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
@@ -409,14 +412,13 @@ var/global/list/last_drake_howl = list()
/mob/living/simple_mob/animal/sif/grafadreka/do_tame(var/obj/O, var/mob/user)
. = ..()
- if(attacked_by_human && ishuman(user) && ((user in tamers) || (user in friends)))
- attacked_by_human = FALSE
+ attacked_by_neutral = FALSE
/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()
+ 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)
@@ -460,12 +462,19 @@ var/global/list/last_drake_howl = list()
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)
@@ -495,23 +504,23 @@ var/global/list/last_drake_howl = list()
return ..()
-/mob/living/simple_mob/animal/sif/grafadreka/proc/get_local_alpha()
+/mob/living/simple_mob/animal/sif/grafadreka/proc/get_pack_leader()
var/pack = FALSE
- var/mob/living/simple_mob/animal/sif/grafadreka/alpha
+ var/mob/living/simple_mob/animal/sif/grafadreka/leader
if(!is_baby)
- alpha = src
- for(var/mob/living/simple_mob/animal/sif/grafadreka/beta in hearers(7, loc))
- if(beta == src || beta.is_baby || beta.stat == DEAD)
+ 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)
continue
pack = TRUE
- if(beta.dominance > alpha.dominance)
- alpha = beta
+ if(!leader || follower.charisma > leader.charisma)
+ leader = follower
if(pack)
- return alpha
+ return leader
-/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)
+/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)
add_modifier(/datum/modifier/ace, 60 SECONDS)
else
remove_modifiers_of_type(/datum/modifier/ace)
@@ -522,14 +531,13 @@ var/global/list/last_drake_howl = list()
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 = TRUE
- if(isliving(A))
- var/mob/living/M = A
- if(M.lying || M.incapacitated())
- attacking_with_claws = FALSE
+ attacking_with_claws = !can_bite(A)
if(last_attack_was_claws != attacking_with_claws)
if(attacking_with_claws) // Use claws.
@@ -576,15 +584,12 @@ var/global/list/last_drake_howl = list()
/mob/living/simple_mob/animal/sif/grafadreka/Login()
. = ..()
- if(client && !is_baby)
- dominance = INFINITY // Let players lead by default.
- else // But not if they are a baby.
- dominance = 0
+ charisma = (client && !is_baby) ? INFINITY : 0
/mob/living/simple_mob/animal/sif/grafadreka/Logout()
. = ..()
if(!client)
- dominance = rand(5, 15)
+ charisma = rand(5, 15)
/datum/say_list/grafadreka
speak = list("Chff!","Skhh.", "Rrrss...")
@@ -626,77 +631,3 @@ var/global/list/last_drake_howl = list()
projectiletype = /obj/item/projectile/drake_spit/weak
maxHealth = 60
health = 60
-
-/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 vest with a pannier; 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 ranged spitting attack and poisonous bite (on harm intent), as well as healing saliva \
- (on help intent).
Unlike your wild kin, you are trained 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().
- var/obj/item/storage/internal/pannier = /obj/item/storage/internal/drake_pannier
-
-// It's just a backpack.
-/obj/item/storage/internal/drake_pannier
- color = COLOR_BEASTY_BROWN
- max_w_class = ITEMSIZE_LARGE
- max_storage_space = INVENTORY_STANDARD_SPACE
-
-/obj/item/storage/internal/drake_pannier/Initialize()
- . = ..() // Name is set lower in Initialize() so we set it again here.
- name = "drake's pannier"
-
-/mob/living/simple_mob/animal/sif/grafadreka/trained/attackby(obj/item/O, mob/user)
- if(user.a_intent == I_HURT || (istype(O, /obj/item/stack/medical) && user.a_intent == I_HELP))
- return ..()
- if(pannier)
- return pannier.attackby(O, user)
- return ..()
-
-/mob/living/simple_mob/animal/sif/grafadreka/trained/add_glow()
- . = ..()
- if(. && pannier)
- 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/L)
- // Permit headpats/smacks
- if(!pannier || L.a_intent == I_HURT || (L.a_intent == I_HELP && L.zone_sel?.selecting == BP_HEAD))
- return ..()
- return pannier.handle_attack_hand(L)
-
-/mob/living/simple_mob/animal/sif/grafadreka/trained/Initialize()
- if(ispath(pannier))
- pannier = new pannier(src)
- . = ..()
-
-// 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(var/mob/other, var/datum/language/speaking = null)
- if(!speaking || speaking.name == LANGUAGE_GALCOM)
- return TRUE
- return ..()
-
-/mob/living/simple_mob/animal/sif/grafadreka/trained/update_icon()
- . = ..()
- if(pannier)
- var/image/I = image(icon, "[current_icon_state]-pannier")
- I.color = pannier.color
- I.appearance_flags |= (RESET_COLOR|PIXEL_SCALE|KEEP_APART)
- if(offset_compiled_icon)
- I.pixel_x = offset_compiled_icon
- add_overlay(I)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_ai.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_ai.dm
index 45dae78c65..980d136e75 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_ai.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_ai.dm
@@ -12,7 +12,7 @@
if(.)
var/mob/living/M = A
- if(M.lying || M.incapacitated())
+ if(M.lying || M.confused || M.incapacitated())
return FALSE // They're already stunned, go bite their nipples off.
for(var/mobtype in stun_immune_types)
@@ -21,21 +21,35 @@
/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)
+ // If we're starving or we've been attacked by a neutral party, all bets are off.
+ if(!. && (nutrition > max_nutrition * 0.15) && !attacked_by_neutral)
+ // Robots aren't every tasty.
+ if(isrobot(L))
+ return TRUE
+ // We don't normally target anything bigger than us.
+ if(L.mob_size > mob_size)
+ return TRUE
+ // Leave humans alone generally, unless they look like a snack.
+ if(ishuman(L) && !issmall(L))
+ return TRUE
+ // Avoid cannibalism.
+ if(istype(L, /mob/living/simple_mob/animal/sif/grafadreka))
+ return TRUE
/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/proc/is_supposedly_neutral(var/mob/living/person)
+ return istype(person) && (ishuman(person) || isrobot(person) || person.faction == "station")
+
+// If we get attacked by a nominally neutral mob, set us to angery mode.
/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
+ . = ..()
+ if(is_supposedly_neutral(attacker))
+ attacked_by_neutral = TRUE
+ ai_holder.hostile = TRUE
/datum/ai_holder/simple_mob/intentional/grafadreka
hostile = TRUE
@@ -50,11 +64,11 @@
/datum/ai_holder/simple_mob/intentional/grafadreka/should_flee(force)
. = ..()
- if(!. && ismob(target))
+ if(!. && isliving(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())
+ if(!istype(drake) || drake.can_bite(prey))
return FALSE
// We can spit at them - disengage so you can pew pew.
if(get_dist(target, holder) <= 1 && drake.has_sap(2) && (world.time >= drake.next_spit))
@@ -84,7 +98,7 @@
if(!leader)
var/mob/living/simple_mob/animal/sif/grafadreka/drake = holder
if(istype(drake))
- set_follow(drake.get_local_alpha())
+ set_follow(drake.get_pack_leader())
return ..()
/datum/ai_holder/simple_mob/intentional/grafadreka/handle_stance_strategical()
@@ -172,7 +186,7 @@
/mob/living/simple_mob/animal/sif/grafadreka/hatchling/IWasAttackedBy(var/mob/living/attacker)
. = ..()
- if(attacked_by_human)
+ if(is_supposedly_neutral(attacker))
for(var/mob/living/simple_mob/animal/sif/grafadreka/friend in viewers(world.view, src))
if(friend == src || !IIsAlly(friend))
continue
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_harness.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_harness.dm
new file mode 100644
index 0000000000..22740ebaa0
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_harness.dm
@@ -0,0 +1,132 @@
+// 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)
+ . = ..()
+
+/obj/item/storage/internal/animal_harness/attackby(obj/item/W, mob/user)
+
+ // Sanity check for some stuff below.
+ if(!istype(loc, /mob/living/simple_mob/animal/sif/grafadreka/trained))
+ return ..()
+
+ // Attach a tracker.
+ if(istype(W, /obj/item/gps))
+ if(attached_gps)
+ 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)
+ user.visible_message(SPAN_NOTICE("\The [user] attaches \the [W] to \the [loc]'s harness."))
+ attached_gps = W
+ return TRUE
+
+ // Attach a radio.
+ if(istype(W, /obj/item/radio))
+ if(attached_radio)
+ 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)
+ user.visible_message(SPAN_NOTICE("\The [user] attaches \the [W] to \the [loc]'s harness."))
+ attached_radio = W
+ return TRUE
+
+ // Attach an armor plate.
+ if(istype(W, /obj/item/clothing/accessory/armor) || istype(W, /obj/item/clothing/accessory/material/makeshift))
+ if(attached_plate)
+ to_chat(user, SPAN_WARNING("There is already \a [attached_plate] inside \the [loc]'s harness."))
+ else if(user.unEquip(W))
+ W.forceMove(loc)
+ 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
+
+ . = ..()
+
+/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/recalculate_armor()
+ armor = list()
+ if(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(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(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(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
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_trained.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_trained.dm
new file mode 100644
index 0000000000..f2ed6b342f
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka_trained.dm
@@ -0,0 +1,118 @@
+/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 ranged spitting attack and poisonous bite (on harm intent), as well as healing saliva \
+ (on help intent).
Unlike your wild kin, you are trained 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().
+ var/obj/item/storage/internal/animal_harness/harness = /obj/item/storage/internal/animal_harness
+
+/mob/living/simple_mob/animal/sif/grafadreka/trained/Initialize()
+ if(ispath(harness))
+ harness = new harness(src)
+ harness.attached_radio = new /obj/item/radio(src)
+ regenerate_harness_verbs()
+ . = ..()
+
+/mob/living/simple_mob/animal/sif/grafadreka/trained/Destroy()
+ if(istype(harness))
+ QDEL_NULL(harness)
+ return ..()
+
+/mob/living/simple_mob/animal/sif/grafadreka/trained/examine(mob/living/user)
+ . = ..()
+ if(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/L)
+ // Permit headpats/smacks
+ if(!harness || L.a_intent == I_HURT || (L.a_intent == I_HELP && L.zone_sel?.selecting == BP_HEAD))
+ return ..()
+ return harness.handle_attack_hand(L)
+
+// 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(var/mob/other, var/datum/language/speaking = null)
+ 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/O, mob/user)
+
+ // bonk
+ if(user.a_intent == I_HURT)
+ return ..()
+
+ // Pass some items down to heal or run injection etc.
+ var/static/list/allow_type_to_pass = list(
+ /obj/item/healthanalyzer,
+ /obj/item/stack/medical,
+ /obj/item/reagent_containers/syringe
+ )
+ if(user.a_intent == I_HELP)
+ for(var/pass_type in allow_type_to_pass)
+ if(istype(O, pass_type))
+ return ..()
+
+ // Open our storage, if we have it.
+ if(harness?.attackby(O, user))
+ regenerate_harness_verbs()
+ return TRUE
+
+ return ..()
+
+/mob/living/simple_mob/animal/sif/grafadreka/trained/proc/regenerate_harness_verbs()
+ if(!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
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 083bbefa8e..9f5a81c58c 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1187,4 +1187,9 @@
return mind.grab_ghost(force = force)
/mob/proc/get_sound_volume_multiplier()
- return !ear_deaf
\ No newline at end of file
+ return !ear_deaf
+
+/mob/proc/handle_reagent_transfer(var/datum/reagents/holder, var/amount = 1, var/chem_type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0)
+ var/datum/reagents/R = new /datum/reagents(amount)
+ . = holder.trans_to_holder(R, amount, multiplier, copy)
+ R.touch_mob(src)
diff --git a/code/modules/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm
index 2363ef1d71..0efa61409e 100644
--- a/code/modules/reagents/holder/holder.dm
+++ b/code/modules/reagents/holder/holder.dm
@@ -413,24 +413,9 @@
perm = L.reagent_permeability()
return trans_to_mob(target, amount, CHEM_TOUCH, perm, copy)
-/datum/reagents/proc/trans_to_mob(var/mob/target, var/amount = 1, var/type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0) // Transfer after checking into which holder...
- if(!target || !istype(target))
- return
- if(iscarbon(target))
- var/mob/living/carbon/C = target
- if(type == CHEM_BLOOD)
- var/datum/reagents/R = C.reagents
- return trans_to_holder(R, amount, multiplier, copy)
- if(type == CHEM_INGEST)
- var/datum/reagents/R = C.ingested
- return C.ingest(src, R, amount, multiplier, copy)
- if(type == CHEM_TOUCH)
- var/datum/reagents/R = C.touching
- return trans_to_holder(R, amount, multiplier, copy)
- else
- var/datum/reagents/R = new /datum/reagents(amount)
- . = trans_to_holder(R, amount, multiplier, copy)
- R.touch_mob(target)
+/datum/reagents/proc/trans_to_mob(var/mob/target, var/amount = 1, var/chem_type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0) // Transfer after checking into which holder...
+ if(istype(target))
+ return target.handle_reagent_transfer(src, amount, chem_type, multiplier, copy)
/datum/reagents/proc/trans_to_turf(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Turfs don't have any reagents (at least, for now). Just touch it.
if(!target)
@@ -517,7 +502,7 @@
trans_to(T, total_volume, multiplier, copy)
if (total_volume <= 0)
qdel(src)
-
+
/**
* Calls [/datum/reagent/proc/on_update] on every reagent in this holder
*
diff --git a/code/modules/reagents/readme.md b/code/modules/reagents/readme.md
index f9dac66b08..f8395fd253 100644
--- a/code/modules/reagents/readme.md
+++ b/code/modules/reagents/readme.md
@@ -118,7 +118,7 @@ About the Holder:
Checks mob's clothing if [clothes] is 1 and transfers [amount] reagents to mob's skin.
Don't call this directly. Call apply_to() instead.
- trans_to_mob(var/mob/target, var/amount = 1, var/type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0)
+ trans_to_mob(var/mob/target, var/amount = 1, var/chem_type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0)
Transfers [amount] reagents to the mob's appropriate holder, depending on [type]. Ignores protection.
trans_to_turf(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0)
@@ -302,4 +302,4 @@ About the Tools:
transfer code since you will not be able to use the standard
tools to manipulate it.
-*/
\ No newline at end of file
+*/
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 82f254068f..6b0a01e89c 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -219,7 +219,7 @@
update_icon()
if(trans)
- to_chat(user, "You inject [trans] units of the solution. The syringe now contains [src.reagents.total_volume] units.")
+ to_chat(user, SPAN_NOTICE("You inject [trans] unit\s of the solution. The syringe now contains [src.reagents.total_volume] unit\s."))
if(ismob(target))
add_attack_logs(user,target,"Injected with [src.name] containing [contained], trasferred [trans] units")
else
diff --git a/code/modules/reagents/reagents/_reagents.dm b/code/modules/reagents/reagents/_reagents.dm
index fa59c30a3a..a3322c3f06 100644
--- a/code/modules/reagents/reagents/_reagents.dm
+++ b/code/modules/reagents/reagents/_reagents.dm
@@ -170,6 +170,9 @@
remove_self(removed)
return
+/datum/reagent/proc/affect_animal(var/mob/living/simple_mob/animal/M, var/removed)
+ return
+
/datum/reagent/proc/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
return
diff --git a/code/modules/reagents/reagents/food_drinks.dm b/code/modules/reagents/reagents/food_drinks.dm
index 330276bd1e..e5013c64ab 100644
--- a/code/modules/reagents/reagents/food_drinks.dm
+++ b/code/modules/reagents/reagents/food_drinks.dm
@@ -36,6 +36,12 @@
if(data[taste]/totalFlavor < 0.1)
data -= taste
+#define ANIMAL_NUTRITION_MULTIPLIER 0.5
+/datum/reagent/nutriment/affect_animal(var/mob/living/simple_mob/animal/M, var/removed)
+ M.add_nutrition(nutriment_factor * removed * M.get_dietary_food_modifier(src) * ANIMAL_NUTRITION_MULTIPLIER)
+ return ..()
+#undef ANIMAL_NUTRITION_MULTIPLIER
+
/datum/reagent/nutriment/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
if(!injectable && alien != IS_SLIME)
M.adjustToxLoss(0.1 * removed)
@@ -45,9 +51,12 @@
/datum/reagent/nutriment/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
switch(alien)
- if(IS_DIONA) return
- if(IS_UNATHI) removed *= 0.5
- if(issmall(M)) removed *= 2 // Small bodymass, more effect from lower volume.
+ if(IS_DIONA)
+ return
+ if(IS_UNATHI)
+ removed *= 0.5
+ if(issmall(M))
+ removed *= 2 // Small bodymass, more effect from lower volume.
if(!(M.species.allergens & allergen_type)) //assuming it doesn't cause a horrible reaction, we'll be ok!
M.heal_organ_damage(0.5 * removed, 0)
M.adjust_nutrition(nutriment_factor * removed)
diff --git a/code/modules/reagents/reagents/medicine.dm b/code/modules/reagents/reagents/medicine.dm
index 41c81c214b..1baf30d128 100644
--- a/code/modules/reagents/reagents/medicine.dm
+++ b/code/modules/reagents/reagents/medicine.dm
@@ -52,6 +52,13 @@
overdose_mod = 0.25
scannable = 1
+/datum/reagent/bicaridine/affect_animal(var/mob/living/simple_mob/animal/M, var/removed)
+ if(istype(M, /mob/living/simple_mob/animal/sif/grafadreka))
+ var/mob/living/simple_mob/animal/sif/grafadreka/drake = M
+ drake.sap_heal_threshold = clamp(drake.sap_heal_threshold + (0.1 * removed), 0, 1)
+ M.heal_organ_damage(6 * removed, 0)
+ return ..()
+
/datum/reagent/bicaridine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
var/chem_effective = 1 * M.species.chem_strength_heal
if(alien == IS_SLIME)
diff --git a/code/modules/reagents/reagents/toxins.dm b/code/modules/reagents/reagents/toxins.dm
index f15e8443cd..3ef8626dc6 100644
--- a/code/modules/reagents/reagents/toxins.dm
+++ b/code/modules/reagents/reagents/toxins.dm
@@ -442,15 +442,22 @@
color = "#C6E2FF"
strength = 2
overdose = 20
+ ingest_met = REM
+ var/sap_regen_power = 7
/datum/reagent/toxin/sifslurry/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
if(alien == IS_DIONA) // Symbiotic bacteria.
M.adjust_nutrition(strength * removed)
return
- else
- M.add_modifier(/datum/modifier/slow_pulse, 30 SECONDS)
+ M.add_modifier(/datum/modifier/slow_pulse, 30 SECONDS)
..()
+/datum/reagent/toxin/sifslurry/affect_animal(var/mob/living/simple_mob/animal/M, var/removed)
+ if(istype(M, /mob/living/simple_mob/animal/sif/grafadreka))
+ var/mob/living/simple_mob/animal/sif/grafadreka/drake = M
+ drake.add_sap(removed * sap_regen_power)
+ return ..()
+
/datum/reagent/toxin/sifslurry/overdose(var/mob/living/carbon/M, var/alien, var/removed) // Overdose effect.
if(alien == IS_DIONA)
return
diff --git a/maps/cynosure/cynosure-2.dmm b/maps/cynosure/cynosure-2.dmm
index 62fd2f0e0a..8e79ff2150 100644
--- a/maps/cynosure/cynosure-2.dmm
+++ b/maps/cynosure/cynosure-2.dmm
@@ -13343,7 +13343,7 @@
/area/surface/station/security/lockerroom)
"gdi" = (
/obj/structure/table/sifwoodentable,
-/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/regular,
/turf/simulated/floor/wood/sif,
/area/surface/outpost/checkpoint)
"gdn" = (
@@ -15529,7 +15529,7 @@
/area/surface/station/hallway/primary/groundfloor/west/elevator)
"hbD" = (
/obj/structure/table/reinforced,
-/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/regular,
/obj/item/glass_jar{
pixel_x = 2;
pixel_y = 2
@@ -38701,7 +38701,7 @@
pixel_y = 32
},
/obj/item/storage/pill_bottle/spaceacillin,
-/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/regular,
/obj/item/roller,
/obj/item/bodybag/cryobag,
/turf/simulated/floor/tiled,
diff --git a/maps/cynosure/cynosure-3.dmm b/maps/cynosure/cynosure-3.dmm
index 531819beb3..fbd66b5cfa 100644
--- a/maps/cynosure/cynosure-3.dmm
+++ b/maps/cynosure/cynosure-3.dmm
@@ -23941,7 +23941,7 @@
},
/obj/item/roller,
/obj/item/bodybag/cryobag,
-/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/regular,
/obj/item/storage/pill_bottle/spaceacillin,
/turf/simulated/floor/tiled,
/area/surface/station/hallway/secondary/secondfloor/civilian)
diff --git a/maps/cynosure/cynosure-7.dmm b/maps/cynosure/cynosure-7.dmm
index 7e5e69df36..ed10fbc48a 100644
--- a/maps/cynosure/cynosure-7.dmm
+++ b/maps/cynosure/cynosure-7.dmm
@@ -38,7 +38,7 @@
/area/surface/outside/wilderness/mountains)
"fP" = (
/obj/structure/table/sifwoodentable,
-/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/regular,
/obj/item/stack/medical/splint,
/turf/simulated/floor/wood/sif,
/area/surface/wilderness/shack)
diff --git a/maps/cynosure/cynosure_jobs.dm b/maps/cynosure/cynosure_jobs.dm
index 833f683f45..81a55c91c9 100644
--- a/maps/cynosure/cynosure_jobs.dm
+++ b/maps/cynosure/cynosure_jobs.dm
@@ -1,5 +1,5 @@
#define CRASH_SURVIVOR_TITLE "Crash Survivor"
-#define DRAKE_SURVIVOR_TITLE "Working Drake"
+#define DRAKE_SURVIVOR_TITLE "Tamed Drake"
// Pilots
@@ -215,14 +215,22 @@ var/global/const/access_explorer = 43
var/mob/living/simple_mob/animal/sif/grafadreka/trained/doggo = new(player.loc)
// Copy over some prefs.
-
if(player.client && player.client.prefs)
+
var/datum/preferences/P = player.client.prefs
doggo.gender = P.identifying_gender
doggo.flavor_text = LAZYACCESS(P.flavor_texts, "general")
- doggo.eye_colour = rgb(P.r_eyes, P.g_eyes, P.b_eyes)
- doggo.fur_colour = rgb(P.r_facial, P.g_facial, P.b_facial)
- doggo.base_colour = rgb(P.r_hair, P.g_hair, P.b_hair)
+
+ // Protect against unset defaults creating a drake-shaped void.
+ var/col = rgb(P.r_eyes, P.g_eyes, P.b_eyes)
+ if(col != COLOR_BLACK)
+ doggo.eye_colour = col
+ col = rgb(P.r_facial, P.g_facial, P.b_facial)
+ if(col != COLOR_BLACK)
+ doggo.fur_colour = col
+ col = rgb(P.r_hair, P.g_hair, P.b_hair)
+ if(col != COLOR_BLACK)
+ doggo.base_colour = col
doggo.update_icon()
// Transfer over key.
diff --git a/maps/northern_star/polaris-5.dmm b/maps/northern_star/polaris-5.dmm
index ce587c6da7..b9e912fc14 100644
--- a/maps/northern_star/polaris-5.dmm
+++ b/maps/northern_star/polaris-5.dmm
@@ -323,7 +323,7 @@
"gk" = (/obj/structure/table/reinforced,/obj/structure/cable/blue{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/effect/floor_decal/industrial/warning,/obj/machinery/button/remote/blast_door{id = "xenobio2"; name = "Containment Blast Doors"; pixel_x = 0; pixel_y = 4; req_access = list(55)},/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/tiled/white,/area/outpost/research/xenobiology)
"gl" = (/obj/machinery/disposal,/obj/structure/disposalpipe/trunk{dir = 1},/obj/structure/cable/blue{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/effect/floor_decal/industrial/warning{dir = 6},/obj/structure/window/reinforced{dir = 8},/turf/simulated/floor/tiled/white,/area/outpost/research/xenobiology)
"gm" = (/obj/machinery/camera/network/research_outpost{c_tag = "OPR - Xenobiology Fore Starboard"; dir = 2},/obj/machinery/light{dir = 1},/obj/structure/cable/blue{d2 = 2; icon_state = "0-2"},/turf/simulated/floor/tiled,/area/outpost/research/xenobiology)
-"gn" = (/obj/structure/table/standard,/obj/item/clothing/suit/storage/hooded/wintercoat/science,/obj/item/clothing/suit/storage/hooded/wintercoat/science,/obj/item/storage/firstaid,/turf/simulated/floor/tiled/dark,/area/outpost/research/xenobiology)
+"gn" = (/obj/structure/table/standard,/obj/item/clothing/suit/storage/hooded/wintercoat/science,/obj/item/clothing/suit/storage/hooded/wintercoat/science,/obj/item/storage/firstaid/regular,/turf/simulated/floor/tiled/dark,/area/outpost/research/xenobiology)
"go" = (/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/airless,/area/mine/explored)
"gp" = (/obj/structure/window/reinforced{dir = 8},/obj/machinery/light/small{dir = 1},/obj/effect/floor_decal/industrial/warning/corner{icon_state = "warningcorner"; dir = 4},/turf/simulated/floor/tiled,/area/outpost/research/hallway/catwalk)
"gq" = (/obj/structure/cable/blue{d1 = 1; d2 = 4; icon_state = "1-4"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/effect/floor_decal/industrial/warning{dir = 1},/turf/simulated/floor/tiled,/area/outpost/research/hallway/catwalk)
@@ -2170,4 +2170,3 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
"}
-
diff --git a/maps/overmap/bearcat/bearcat.dmm b/maps/overmap/bearcat/bearcat.dmm
index c96ca1919b..cd69db0be2 100644
--- a/maps/overmap/bearcat/bearcat.dmm
+++ b/maps/overmap/bearcat/bearcat.dmm
@@ -223,7 +223,7 @@
"eo" = (/obj/effect/decal/cleanable/dirt,/obj/structure/cable,/obj/machinery/power/apc{dir = 4; name = "Cargo Hold APC"},/turf/simulated/floor{ icon_state = "bot"; dir = 2},/area/ship/scrap/cargo)
"ep" = (/obj/structure/table/standard,/obj/item/storage/pill_bottle/kelotane,/obj/item/storage/box/syringes,/obj/structure/cable,/obj/machinery/power/apc{name = "Medical Bay APC"},/obj/machinery/light_switch{pixel_x = -25},/turf/simulated/floor{icon_state = "white"},/area/ship/scrap/crew/medbay)
"eq" = (/obj/structure/table/standard,/obj/item/storage/pill_bottle/antitox,/obj/item/storage/pill_bottle/kelotane,/obj/item/clothing/tie/stethoscope,/turf/simulated/floor{icon_state = "white"},/area/ship/scrap/crew/medbay)
-"er" = (/obj/structure/table/standard,/obj/item/storage/firstaid,/obj/item/healthanalyzer,/turf/simulated/floor{icon_state = "white"},/area/ship/scrap/crew/medbay)
+"er" = (/obj/structure/table/standard,/obj/item/storage/firstaid/regular,/obj/item/healthanalyzer,/turf/simulated/floor{icon_state = "white"},/area/ship/scrap/crew/medbay)
"es" = (/obj/item/radio,/turf/simulated/floor/plating,/area)
"et" = (/obj/machinery/light/small{ icon_state = "bulb1"; dir = 1},/obj/item/coin/gold,/obj/item/coin/silver,/turf/simulated/floor/plating{dir = 2; icon_state = "warnplate"},/area/ship/scrap/maintenance/engine)
"eu" = (/turf/simulated/floor/plating{dir = 2; icon_state = "warnplate"},/area/ship/scrap/maintenance/engine)
diff --git a/maps/submaps/surface_submaps/plains/oldhotel.dmm b/maps/submaps/surface_submaps/plains/oldhotel.dmm
index f13f9a57fa..cb31c1aeec 100644
--- a/maps/submaps/surface_submaps/plains/oldhotel.dmm
+++ b/maps/submaps/surface_submaps/plains/oldhotel.dmm
@@ -1,5 +1,5 @@
"ai" = (/obj/random/trash,/turf/simulated/floor/carpet/purcarpet,/area/submap/oldhotel)
-"aJ" = (/obj/item/towel/random,/obj/item/towel/random,/obj/structure/table/standard,/obj/item/storage/firstaid,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/oldhotel)
+"aJ" = (/obj/item/towel/random,/obj/item/towel/random,/obj/structure/table/standard,/obj/item/storage/firstaid/regular,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/oldhotel)
"bb" = (/obj/machinery/light/small{dir = 4},/obj/item/multitool,/turf/simulated/floor/wood,/area/submap/oldhotel)
"bQ" = (/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/oldhotel)
"bW" = (/obj/item/material/shard,/turf/template_noop,/area/submap/oldhotel)
diff --git a/polaris.dme b/polaris.dme
index 21aba450cb..1b62dc655e 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -2481,6 +2481,8 @@
#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\grafadreka_harness.dm"
+#include "code\modules\mob\living\simple_mob\subtypes\animal\sif\grafadreka_trained.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\kururak.dm"