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"