diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 02b36614cb5..aa4b49ff6fc 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -155,6 +155,10 @@ // /mob/living/carbon signals #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) +// /mob/living/simple_animal/hostile signals +#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" + #define COMPONENT_HOSTILE_NO_ATTACK 1 + // /obj signals #define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled) #define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 14b712754fb..edc77bae4c3 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -13,6 +13,7 @@ #define CONDUCT 32 // conducts electricity (metal etc.) #define ABSTRACT 64 // for all things that are technically items but used for various different stuff, made it 128 because it could conflict with other flags other way #define ON_BORDER 128 // item has priority to check when entering or leaving +#define PREVENT_CLICK_UNDER 256 #define EARBANGPROTECT 1024 diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index c4478f62837..d9f4eff3e43 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -17,6 +17,8 @@ #define ismecha(A) (istype(A, /obj/mecha)) +#define isspacepod(A) (istype(A, /obj/spacepod)) + #define iseffect(A) (istype(A, /obj/effect)) #define is_cleanable(A) (istype(A, /obj/effect/decal/cleanable) || istype(A, /obj/effect/rune)) //if something is cleanable diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index b823efe2563..7695b8b5851 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -107,6 +107,9 @@ if(next_move > world.time) // in the year 2000... return + if(!modifiers["catcher"] && A.IsObscured()) + return + if(istype(loc,/obj/mecha)) if(!locate(/turf) in list(A,A.loc)) // Prevents inventory from being drilled return @@ -172,6 +175,24 @@ return +//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it +/atom/proc/IsObscured() + if(!isturf(loc)) //This only makes sense for things directly on turfs for now + return FALSE + var/turf/T = get_turf_pixel(src) + if(!T) + return FALSE + for(var/atom/movable/AM in T) + if(AM.flags & PREVENT_CLICK_UNDER && AM.density && AM.layer > layer) + return TRUE + return FALSE + +/turf/IsObscured() + for(var/atom/movable/AM in src) + if(AM.flags & PREVENT_CLICK_UNDER && AM.density) + return TRUE + return FALSE + // Default behavior: ignore double clicks, consider them normal clicks instead /mob/proc/DblClickOn(var/atom/A, var/params) return @@ -399,8 +420,8 @@ dir = direction /obj/screen/click_catcher - icon = 'icons/mob/screen_full.dmi' - icon_state = "passage0" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "catcher" plane = CLICKCATCHER_PLANE mouse_opacity = MOUSE_OPACITY_OPAQUE screen_loc = "CENTER" @@ -430,5 +451,7 @@ C.swap_hand() else var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr)) - T.Click(location, control, params) - return 1 + params += "&catcher=1" + if(T) + T.Click(location, control, params) + . = 1 \ No newline at end of file diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm index 81e052a85d8..e774e893851 100644 --- a/code/game/gamemodes/miniantags/morph/morph.dm +++ b/code/game/gamemodes/miniantags/morph/morph.dm @@ -28,7 +28,7 @@ melee_damage_upper = 20 see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - idle_vision_range = 1 // Only attack when target is close + vision_range = 1 // Only attack when target is close wander = 0 attacktext = "glomps" attack_sound = 'sound/effects/blobattack.ogg' @@ -54,7 +54,7 @@ . = ..() AddSpell(new /obj/effect/proc_holder/spell/targeted/smoke) AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall) - + /mob/living/simple_animal/hostile/morph/examine(mob/user) if(morphed) if(form) @@ -153,7 +153,7 @@ restore() /mob/living/simple_animal/hostile/morph/LoseAggro() - vision_range = idle_vision_range + vision_range = initial(vision_range) /mob/living/simple_animal/hostile/morph/AIShouldSleep(var/list/possible_targets) . = ..() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 65854bb1412..89321f2e1a8 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -10,6 +10,7 @@ power_channel = ENVIRON max_integrity = 350 armor = list(melee = 30, bullet = 30, laser = 20, energy = 20, bomb = 10, bio = 100, rad = 100) + flags = PREVENT_CLICK_UNDER var/closingLayer = CLOSED_DOOR_LAYER var/visible = 1 var/operating = FALSE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 16851097844..4090420525d 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -579,6 +579,7 @@ var/global/wcCommon = pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", "#8f dir = FULLTILE_WINDOW_DIR level = 3 fulltile = TRUE + flags = PREVENT_CLICK_UNDER /obj/structure/window/full/basic desc = "It looks thin and flimsy. A few knocks with... anything, really should shatter it." @@ -675,6 +676,7 @@ obj/structure/window/full/reinforced/ice dir = FULLTILE_WINDOW_DIR max_integrity = 200 fulltile = TRUE + flags = PREVENT_CLICK_UNDER reinf = TRUE heat_resistance = 1600 armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100) @@ -734,6 +736,7 @@ obj/structure/window/full/reinforced/ice smooth = SMOOTH_TRUE canSmoothWith = null fulltile = TRUE + flags = PREVENT_CLICK_UNDER dir = FULLTILE_WINDOW_DIR max_integrity = 120 level = 3 diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm index 9a88dd50259..015921634fe 100644 --- a/code/modules/mining/equipment/survival_pod.dm +++ b/code/modules/mining/equipment/survival_pod.dm @@ -77,6 +77,7 @@ dir = FULLTILE_WINDOW_DIR max_integrity = 100 fulltile = TRUE + flags = PREVENT_CLICK_UNDER reinf = TRUE heat_resistance = 1600 armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100) @@ -296,7 +297,7 @@ name = "air flow blocker" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF unacidable = TRUE - invisibility = INVISIBILITY_ABSTRACT + invisibility = INVISIBILITY_ABSTRACT //Signs /obj/structure/sign/mining diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm index e7036ad4f2f..7e55a60d387 100644 --- a/code/modules/mining/minebot.dm +++ b/code/modules/mining/minebot.dm @@ -15,7 +15,6 @@ a_intent = INTENT_HARM atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 - idle_vision_range = 5 move_to_delay = 10 health = 125 maxHealth = 125 @@ -145,7 +144,7 @@ /mob/living/simple_animal/hostile/mining_drone/proc/SetCollectBehavior() mode = MINEDRONE_COLLECT - idle_vision_range = 9 + vision_range = 9 search_objects = 2 wander = TRUE ranged = FALSE @@ -156,7 +155,7 @@ /mob/living/simple_animal/hostile/mining_drone/proc/SetOffenseBehavior() mode = MINEDRONE_ATTACK - idle_vision_range = 7 + vision_range = 7 search_objects = 0 wander = FALSE ranged = TRUE diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 636df89885d..20f6155ba0f 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -1,10 +1,26 @@ +/mob/living/simple_animal/attackby(obj/item/O, mob/living/user) + if(can_collar && !collar && istype(O, /obj/item/clothing/accessory/petcollar)) + var/obj/item/clothing/accessory/petcollar/C = O + if(user.drop_item()) + C.forceMove(src) + collar = C + collar.equipped(src) + regenerate_icons() + to_chat(usr, "You put \the [C] around \the [src]'s neck.") + if(C.tagname) + name = C.tagname + real_name = C.tagname + return + else + return ..() + /mob/living/simple_animal/attack_hand(mob/living/carbon/human/M) ..() switch(M.a_intent) if(INTENT_HELP) if(health > 0) - visible_message("[M] [response_help] [src].") + visible_message("[M] [response_help] [src].", "[M] [response_help] you.") playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) if(INTENT_GRAB) @@ -12,12 +28,12 @@ if(INTENT_HARM, INTENT_DISARM) M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - visible_message("[M] [response_harm] [src]!") + visible_message("[M] [response_harm] [src]!", "[M] [response_harm] you!") playsound(loc, attacked_sound, 25, 1, -1) attack_threshold_check(harm_intent_damage) add_attack_logs(M, src, "Melee attacked with fists") updatehealth() - return 1 + return TRUE /mob/living/simple_animal/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) @@ -39,10 +55,11 @@ /mob/living/simple_animal/attack_larva(mob/living/carbon/alien/larva/L) if(..()) //successful larva bite - var/damage = rand(5, 10) if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - attack_threshold_check(damage) + var/damage = rand(5, 10) + . = attack_threshold_check(damage) + if(.) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) /mob/living/simple_animal/attack_animal(mob/living/simple_animal/M) if(..()) @@ -51,21 +68,57 @@ /mob/living/simple_animal/attack_slime(mob/living/carbon/slime/M) ..() - var/damage = rand(1, 3) - + var/damage = rand(15, 25) if(M.is_adult) - damage = rand(20, 40) - else - damage = rand(5, 35) - + damage = rand(20, 35) attack_threshold_check(damage) - return -/mob/living/simple_animal/proc/attack_threshold_check(damage, damagetype = BRUTE) - if(damage <= force_threshold || !damage_coeff[damagetype]) - visible_message("[src] looks unharmed.") +/mob/living/simple_animal/proc/attack_threshold_check(damage, damagetype = BRUTE, armorcheck = "melee") + var/temp_damage = damage + if(!damage_coeff[damagetype]) + temp_damage = 0 else - apply_damage(damage, damagetype) + temp_damage *= damage_coeff[damagetype] + + if(temp_damage >= 0 && temp_damage <= force_threshold) + visible_message("[src] looks unharmed.") + return FALSE + else + apply_damage(damage, damagetype, null, getarmor(null, armorcheck)) + return TRUE + +/mob/living/simple_animal/bullet_act(obj/item/projectile/Proj) + if(!Proj) + return + apply_damage(Proj.damage, Proj.damage_type) + Proj.on_hit(src, 0) + return FALSE + +/mob/living/simple_animal/ex_act(severity, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return + ..() + var/bomb_armor = getarmor(null, "bomb") + switch(severity) + if(1) + if(prob(bomb_armor)) + adjustBruteLoss(500) + else + gib() + return + if(2) + var/bloss = 60 + if(prob(bomb_armor)) + bloss = bloss / 1.5 + adjustBruteLoss(bloss) + if(3) + var/bloss = 30 + if(prob(bomb_armor)) + bloss = bloss / 1.5 + adjustBruteLoss(bloss) + +/mob/living/simple_animal/blob_act(obj/structure/blob/B) + adjustBruteLoss(20) /mob/living/simple_animal/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect, end_pixel_y) if(!no_effect && !visual_effect_icon && melee_damage_upper) diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm new file mode 100644 index 00000000000..6899b40bea0 --- /dev/null +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -0,0 +1,37 @@ + +/mob/living/simple_animal/proc/adjustHealth(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE + var/oldbruteloss = bruteloss + bruteloss = Clamp(bruteloss + amount, 0, maxHealth) + if(oldbruteloss == bruteloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth() + +/mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE) + if(damage_coeff[BRUTE]) + return adjustHealth(amount * damage_coeff[BRUTE], updating_health) + +/mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE) + if(damage_coeff[BURN]) + return adjustHealth(amount * damage_coeff[BURN], updating_health) + +/mob/living/simple_animal/adjustOxyLoss(amount, updating_health = TRUE) + if(damage_coeff[OXY]) + return adjustHealth(amount * damage_coeff[OXY], updating_health) + +/mob/living/simple_animal/adjustToxLoss(amount, updating_health = TRUE) + if(damage_coeff[TOX]) + return adjustHealth(amount * damage_coeff[TOX], updating_health) + +/mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE) + if(damage_coeff[CLONE]) + return adjustHealth(amount * damage_coeff[CLONE], updating_health) + +/mob/living/simple_animal/adjustStaminaLoss(amount, updating_health = TRUE) + if(damage_coeff[STAMINA]) + return ..(amount*damage_coeff[STAMINA], updating_health) \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 4dea368ad5e..5f54f77c053 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -94,26 +94,26 @@ new cat_type(loc) -/mob/living/simple_animal/pet/cat/handle_automated_action() - ..() - if(prob(1)) - custom_emote(1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) - icon_state = "[icon_living]_rest" - resting = 1 - update_canmove() - else if (prob(1)) - custom_emote(1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) - icon_state = "[icon_living]_sit" - resting = 1 - update_canmove() - else if (prob(1)) - if (resting) - custom_emote(1, pick("gets up and meows.", "walks around.", "stops resting.")) - icon_state = "[icon_living]" - resting = 0 +/mob/living/simple_animal/pet/cat/Life() + if(!stat && !buckled && !client) + if(prob(1)) + custom_emote(1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) + icon_state = "[icon_living]_rest" + resting = 1 update_canmove() - else - custom_emote(1, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) + else if (prob(1)) + custom_emote(1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) + icon_state = "[icon_living]_sit" + resting = 1 + update_canmove() + else if (prob(1)) + if (resting) + custom_emote(1, pick("gets up and meows.", "walks around.", "stops resting.")) + icon_state = "[icon_living]" + resting = 0 + update_canmove() + else + custom_emote(1, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) //MICE! if(eats_mice && isturf(loc) && !incapacitated()) @@ -128,10 +128,11 @@ if(T.cooldown < (world.time - 400)) custom_emote(1, "bats \the [T] around with its paw!") T.cooldown = world.time + + ..() + make_babies() -/mob/living/simple_animal/pet/cat/handle_automated_movement() - ..() if(!stat && !resting && !buckled) turns_since_scan++ if(turns_since_scan > 5) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index bdb479997dc..729a7d0b47d 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -38,18 +38,20 @@ udder = null return ..() -/mob/living/simple_animal/hostile/retaliate/goat/handle_automated_movement() - ..() - //chance to go crazy and start wacking stuff - if(!enemies.len && prob(1)) - Retaliate() +/mob/living/simple_animal/hostile/retaliate/goat/Life(seconds, times_fired) + . = ..() + if(.) + //chance to go crazy and start wacking stuff + if(!enemies.len && prob(1)) + Retaliate() - if(enemies.len && prob(10)) - enemies = list() - LoseTarget() - visible_message("[src] calms down.") + if(enemies.len && prob(10)) + enemies = list() + LoseTarget() + visible_message("[src] calms down.") if(stat == CONSCIOUS) + udder.generateMilk() eat_plants() if(!pulledby) for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) @@ -58,11 +60,6 @@ if(locate(/obj/structure/spacevine) in step || locate(/obj/structure/glowshroom) in step) Move(step, get_dir(src, step)) -/mob/living/simple_animal/hostile/retaliate/goat/Life(seconds, times_fired) - . = ..() - if(stat == CONSCIOUS) - udder.generateMilk() - /mob/living/simple_animal/hostile/retaliate/goat/Retaliate() ..() visible_message("[src] gets an evil-looking gleam in their eye.") diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 2c9cbc355f7..da0a59c99b7 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -56,7 +56,7 @@ visible_message("[src] chews through [C].") /mob/living/simple_animal/mouse/handle_automated_speech() - ..() + ..() if(prob(speak_chance)) for(var/mob/M in view()) M << squeak_sound @@ -73,7 +73,7 @@ /mob/living/simple_animal/mouse/Life() ..() - if(prob(0.5) && !ckey) + if(!stat && prob(0.5) && !ckey) stat = UNCONSCIOUS icon_state = "mouse_[mouse_color]_sleep" wander = 0 @@ -119,7 +119,7 @@ desc = "It's toast." death() -/mob/living/simple_animal/mouse/death(gibbed) +/mob/living/simple_animal/mouse/death(gibbed) // Only execute the below if we successfully died playsound(src, squeak_sound, 40, 1) . = ..(gibbed) diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 6795f4ccec2..d6d23871b1f 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -76,20 +76,20 @@ venom_per_bite = 10 move_to_delay = 5 -/mob/living/simple_animal/hostile/poison/giant_spider/hunter/handle_automated_action() +/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_action() if(!..()) //AIStatus is off - return + return 0 if(AIStatus == AI_IDLE) //1% chance to skitter madly away if(!busy && prob(1)) stop_automated_movement = 1 - Goto(pick(orange(20, src)), move_to_delay) + Goto(pick(urange(20, src, 1)), move_to_delay) spawn(50) stop_automated_movement = 0 walk(src,0) return 1 -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(var/C) +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(C) spawn(100) if(busy == MOVING_TO_TARGET) if(cocoon_target == C && get_dist(src,cocoon_target) > 1) @@ -101,9 +101,9 @@ if(..()) var/list/can_see = view(src, 10) if(!busy && prob(30)) //30% chance to stop wandering and do something - //first, check for potential food nearby to cocoon + //first, check for potential food nearby to cocoon for(var/mob/living/C in can_see) - if(C.stat && !istype(C,/mob/living/simple_animal/hostile/poison/giant_spider)) + if(C.stat && !istype(C, /mob/living/simple_animal/hostile/poison/giant_spider) && !C.anchored) cocoon_target = C busy = MOVING_TO_TARGET Goto(C, move_to_delay) @@ -123,13 +123,14 @@ for(var/obj/O in can_see) if(O.anchored) continue - if(istype(O, /obj/item) || istype(O, /obj/structure) || istype(O, /obj/machinery)) - cocoon_target = O - busy = MOVING_TO_TARGET - stop_automated_movement = 1 - Goto(O, move_to_delay) - //give up if we can't reach them after 10 seconds - GiveUp(O) + + if(isitem(O) || isstructure(O) || ismachinery(O)) + cocoon_target = O + busy = MOVING_TO_TARGET + stop_automated_movement = 1 + Goto(O, move_to_delay) + //give up if we can't reach them after 10 seconds + GiveUp(O) else if(busy == MOVING_TO_TARGET && cocoon_target) if(get_dist(src, cocoon_target) <= 1) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index a96c78d09d6..da34a3b2827 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -2,60 +2,60 @@ faction = list("hostile") stop_automated_movement_when_pulled = 0 obj_damage = 40 - environment_smash = 1 //Set to 1 to break closets,tables,racks, etc; 2 for walls; 3 for rwalls + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //Bitflags. Set to ENVIRONMENT_SMASH_STRUCTURES to break closets,tables,racks, etc; ENVIRONMENT_SMASH_WALLS for walls; ENVIRONMENT_SMASH_RWALLS for rwalls var/atom/target - var/ranged = 0 - var/rapid = 0 - var/projectiletype + var/ranged = FALSE + var/rapid = 0 //How many shots per volley. + var/rapid_fire_delay = 2 //Time between rapid fire shots + + var/dodging = FALSE + var/approaching_target = FALSE //We should dodge now + var/in_melee = FALSE //We should sidestep now + var/dodge_prob = 30 + var/sidestep_per_cycle = 1 //How many sidesteps per npcpool cycle when in melee + + var/projectiletype //set ONLY it and NULLIFY casingtype var, if we have ONLY projectile var/projectilesound - var/casingtype + var/casingtype //set ONLY it and NULLIFY projectiletype, if we have projectile IN CASING var/move_to_delay = 3 //delay for the automated movement. var/list/friends = list() + var/list/emote_taunt = list() + var/taunt_chance = 0 + + var/rapid_melee = 1 //Number of melee attacks between each npc pool tick. Spread evenly. + var/melee_queue_distance = 4 //If target is close enough start preparing to hit them if we have rapid_melee enabled + var/ranged_message = "fires" //Fluff text for ranged mobs - var/ranged_cooldown = 0 //What the starting cooldown is on ranged attacks + var/ranged_cooldown = 0 //What the current cooldown on ranged attacks is, generally world.time + ranged_cooldown_time var/ranged_cooldown_time = 30 //How long, in deciseconds, the cooldown of ranged attacks is var/ranged_ignores_vision = FALSE //if it'll fire ranged attacks even if it lacks vision on its target, only works with environment smash var/check_friendly_fire = 0 // Should the ranged mob check for friendlies when shooting var/retreat_distance = null //If our mob runs from players when they're too close, set in tile distance. By default, mobs do not retreat. var/minimum_distance = 1 //Minimum approach distance, so ranged mobs chase targets down, but still keep their distance set in tiles to the target, set higher to make mobs keep distance + //These vars are related to how mobs locate and target var/robust_searching = 0 //By default, mobs have a simple searching method, set this to 1 for the more scrutinous searching (stat_attack, stat_exclusive, etc), should be disabled on most mobs var/vision_range = 9 //How big of an area to search for targets in, a vision of 9 attempts to find targets as soon as they walk into screen view var/aggro_vision_range = 9 //If a mob is aggro, we search in this radius. Defaults to 9 to keep in line with original simple mob aggro radius - var/idle_vision_range = 9 //If a mob is just idling around, it's vision range is limited to this. Defaults to 9 to keep in line with original simple mob aggro radius var/search_objects = 0 //If we want to consider objects when searching around, set this to 1. If you want to search for objects while also ignoring mobs until hurt, set it to 2. To completely ignore mobs, even when attacked, set it to 3 - var/list/wanted_objects = list() //A list of objects that will be checked against to attack, should we have search_objects enabled - var/stat_attack = 0 //Mobs with stat_attack to 1 will attempt to attack things that are unconscious, Mobs with stat_attack set to 2 will attempt to attack the dead. - var/stat_exclusive = 0 //Mobs with this set to 1 will exclusively attack things defined by stat_attack, stat_attack 2 means they will only attack corpses - var/attack_same = 0 //Set us to 1 to allow us to attack our own faction, or 2, to only ever attack our own faction - - //typecache of things this mob will attack in DestroySurroundings() if it has environment_smash - var/list/environment_target_typecache = list( - /obj/machinery/door/window, - /obj/structure/window, - /obj/structure/closet, - /obj/structure/table, - /obj/structure/grille, - /obj/structure/girder, - /obj/structure/rack, - /obj/structure/computerframe, - /obj/machinery/constructable_frame, - /obj/structure/barricade) //turned into a typecache in New() + var/search_objects_timer_id //Timer for regaining our old search_objects value after being attacked + var/search_objects_regain_time = 30 //the delay between being attacked and gaining our old search_objects value back + var/list/wanted_objects = list() //A typecache of objects types that will be checked against to attack, should we have search_objects enabled + var/stat_attack = CONSCIOUS //Mobs with stat_attack to UNCONSCIOUS will attempt to attack things that are unconscious, Mobs with stat_attack set to DEAD will attempt to attack the dead. + var/stat_exclusive = FALSE //Mobs with this set to TRUE will exclusively attack things defined by stat_attack, stat_attack DEAD means they will only attack corpses + var/attack_same = 0 //Set us to 1 to allow us to attack our own faction var/atom/targets_from = null //all range/attack/etc. calculations should be done from this atom, defaults to the mob itself, useful for Vehicles and such - var/list/emote_taunt = list() - var/taunt_chance = 0 + var/attack_all_objects = FALSE //if true, equivalent to having a wanted_objects list containing ALL objects. var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever - var/attack_all_objects = FALSE //if true, equivalent to having a wanted_objects list containing ALL objects. +/mob/living/simple_animal/hostile/Initialize(mapload) + . = ..() -/mob/living/simple_animal/hostile/New() - ..() if(!targets_from) targets_from = src - environment_target_typecache = typecacheof(environment_target_typecache) wanted_objects = typecacheof(wanted_objects) /mob/living/simple_animal/hostile/Destroy() @@ -73,16 +73,46 @@ if(AIStatus == AI_OFF) return 0 var/list/possible_targets = ListTargets() //we look around for potential targets and make it a list for later use. + if(environment_smash) EscapeConfinement() if(AICanContinue(possible_targets)) - DestroySurroundings() + if(!QDELETED(target) && !targets_from.Adjacent(target)) + DestroyPathToTarget() if(!MoveToTarget(possible_targets)) //if we lose our target if(AIShouldSleep(possible_targets)) // we try to acquire a new one toggle_ai(AI_IDLE) // otherwise we go idle return 1 +/mob/living/simple_animal/hostile/handle_automated_movement() + . = ..() + if(dodging && target && in_melee && isturf(loc) && isturf(target.loc)) + var/datum/cb = CALLBACK(src,.proc/sidestep) + if(sidestep_per_cycle > 1) //For more than one just spread them equally - this could changed to some sensible distribution later + var/sidestep_delay = SSnpcpool.wait / sidestep_per_cycle + for(var/i in 1 to sidestep_per_cycle) + addtimer(cb, (i - 1) * sidestep_delay) + else //Otherwise randomize it to make the players guessing. + addtimer(cb,rand(1, SSnpcpool.wait)) + +/mob/living/simple_animal/hostile/proc/sidestep() + if(!target || !isturf(target.loc) || !isturf(loc) || stat == DEAD) + return + var/target_dir = get_dir(src, target) + + var/static/list/cardinal_sidestep_directions = list(-90, -45, 0, 45, 90) + var/static/list/diagonal_sidestep_directions = list(-45, 0, 45) + var/chosen_dir = 0 + if (target_dir & (target_dir - 1)) + chosen_dir = pick(diagonal_sidestep_directions) + else + chosen_dir = pick(cardinal_sidestep_directions) + if(chosen_dir) + chosen_dir = turn(target_dir, chosen_dir) + Move(get_step(src, chosen_dir)) + face_atom(target) //Looks better if they keep looking at you when dodging + /mob/living/simple_animal/hostile/attacked_by(obj/item/I, mob/living/user) if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client && user) FindTarget(list(user), 1) @@ -97,7 +127,6 @@ //////////////HOSTILE MOB TARGETTING AND AGGRESSION//////////// - /mob/living/simple_animal/hostile/proc/ListTargets()//Step 1, find out what we can see if(!search_objects) . = hearers(vision_range, targets_from) - src //Remove self, so we don't suicide @@ -108,7 +137,11 @@ if(can_see(targets_from, HM, vision_range)) . += HM else - . = oview(vision_range, targets_from) + . = list() // The following code is only very slightly slower than just returning oview(vision_range, targets_from), but it saves us much more work down the line, particularly when bees are involved + for(var/obj/A in oview(vision_range, targets_from)) + . += A + for(var/mob/A in oview(vision_range, targets_from)) + . += A /mob/living/simple_animal/hostile/proc/FindTarget(var/list/possible_targets, var/HasTargetsList = 0)//Step 2, filter down possible targets to things we actually care about . = list() @@ -153,59 +186,61 @@ var/chosen_target = pick(Targets)//Pick the remaining targets (if any) at random return chosen_target -/mob/living/simple_animal/hostile/CanAttack(var/atom/the_target)//Can we actually attack a possible target? - if(!the_target) - return 0 - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return 0 +// Please do not add one-off mob AIs here, but override this function for your mob +/mob/living/simple_animal/hostile/CanAttack(atom/the_target)//Can we actually attack a possible target? + if(isturf(the_target) || !the_target || the_target.type == /atom/movable/lighting_object) // bail out on invalids + return FALSE + + if(ismob(the_target)) //Target is in godmode, ignore it. + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + + if(see_invisible < the_target.invisibility) //Target's invisible to us, forget it + return FALSE if(search_objects < 2) if(isliving(the_target)) var/mob/living/L = the_target - var/faction_check = faction_check(L) + var/faction_check = faction_check_mob(L) if(robust_searching) - if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive == 1) - return 0 - if(faction_check && !attack_same || !faction_check && attack_same == 2) - return 0 - if(L in friends) - return 0 - else - if(L.stat) - return 0 if(faction_check && !attack_same) - return 0 - return 1 + return FALSE + if(L.stat > stat_attack) + return FALSE + if(L in friends) + return FALSE + else + if((faction_check && !attack_same) || L.stat) + return FALSE + return TRUE - if(ishuman(the_target)) - var/mob/living/carbon/human/H = the_target - if(is_type_in_list(src, H.dna.species.ignored_by)) - return 0 - - if(istype(the_target, /obj/mecha)) + if(ismecha(the_target)) var/obj/mecha/M = the_target if(M.occupant)//Just so we don't attack empty mechs if(CanAttack(M.occupant)) - return 1 + return TRUE - if(istype(the_target, /obj/spacepod)) + if(isspacepod(the_target)) var/obj/spacepod/S = the_target - if(S.pilot) //Just so we don't attack empty pods + if(S.pilot)//Just so we don't attack empty mechs if(CanAttack(S.pilot)) - return 1 + return TRUE if(istype(the_target, /obj/machinery/porta_turret)) var/obj/machinery/porta_turret/P = the_target if(P.faction in faction) - return 0 + return FALSE if(!P.raised) //Don't attack invincible turrets - return 0 + return FALSE if(P.stat & BROKEN) //Or turrets that are already broken - return 0 - return 1 + return FALSE + return TRUE + if(isobj(the_target)) - if(attack_all_objects || is_type_in_list(the_target, wanted_objects)) - return 1 - return 0 + if(attack_all_objects || is_type_in_typecache(the_target, wanted_objects)) + return TRUE + + return FALSE /mob/living/simple_animal/hostile/proc/GiveTarget(new_target)//Step 4, give us our selected target target = new_target @@ -215,19 +250,39 @@ Aggro() return 1 -/mob/living/simple_animal/hostile/proc/MoveToTarget(var/list/possible_targets)//Step 5, handle movement between us and our target +//What we do after closing in +/mob/living/simple_animal/hostile/proc/MeleeAction(patience = TRUE) + if(rapid_melee > 1) + var/datum/callback/cb = CALLBACK(src, .proc/CheckAndAttack) + var/delay = SSnpcpool.wait / rapid_melee + for(var/i in 1 to rapid_melee) + addtimer(cb, (i - 1)*delay) + else + AttackingTarget() + if(patience) + GainPatience() + +/mob/living/simple_animal/hostile/proc/CheckAndAttack() + if(target && targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from) && !incapacitated()) + AttackingTarget() + +/mob/living/simple_animal/hostile/proc/MoveToTarget(list/possible_targets)//Step 5, handle movement between us and our target stop_automated_movement = 1 if(!target || !CanAttack(target)) LoseTarget() return 0 if(target in possible_targets) - if(target.z != z) + var/turf/T = get_turf(src) + if(target.z != T.z) LoseTarget() return 0 var/target_distance = get_dist(targets_from,target) if(ranged) //We ranged? Shoot at em if(!target.Adjacent(targets_from) && ranged_cooldown <= world.time) //But make sure they're not in range for a melee attack and our range attack is off cooldown OpenFire(target) + if(!Process_Spacemove()) //Drifting + walk(src,0) + return 1 if(retreat_distance != null) //If we have a retreat distance, check if we need to run from our target if(target_distance <= retreat_distance) //If target's closer than our retreat distance, run walk_away(src,target,retreat_distance,move_to_delay) @@ -237,8 +292,11 @@ Goto(target,move_to_delay,minimum_distance) if(target) if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from)) //If they're next to us, attack - AttackingTarget() - GainPatience() + MeleeAction() + else + if(rapid_melee > 1 && target_distance <= melee_queue_distance) + MeleeAction(FALSE) + in_melee = FALSE //If we're just preparing to strike do not enter sidestep mode return 1 return 0 if(environment_smash) @@ -256,14 +314,18 @@ return 0 /mob/living/simple_animal/hostile/proc/Goto(target, delay, minimum_distance) + if(target == src.target) + approaching_target = TRUE + else + approaching_target = FALSE walk_to(src, target, minimum_distance, delay) -/mob/living/simple_animal/hostile/adjustHealth(damage) +/mob/living/simple_animal/hostile/adjustHealth(damage, updating_health = TRUE) . = ..() if(!ckey && !stat && search_objects < 3 && damage > 0)//Not unconscious, and we don't ignore mobs if(search_objects)//Turn off item searching and ignore whatever item we were looking at, we're more concerned with fight or flight - search_objects = 0 target = null + LoseSearchObjects() if(AIStatus != AI_ON && AIStatus != AI_OFF) toggle_ai(AI_ON) FindTarget() @@ -271,7 +333,9 @@ FindTarget() /mob/living/simple_animal/hostile/proc/AttackingTarget() - target.attack_animal(src) + SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target) + in_melee = TRUE + return target.attack_animal(src) /mob/living/simple_animal/hostile/proc/Aggro() vision_range = aggro_vision_range @@ -281,11 +345,13 @@ /mob/living/simple_animal/hostile/proc/LoseAggro() stop_automated_movement = 0 - vision_range = idle_vision_range + vision_range = initial(vision_range) taunt_chance = initial(taunt_chance) /mob/living/simple_animal/hostile/proc/LoseTarget() target = null + approaching_target = FALSE + in_melee = FALSE walk(src, 0) LoseAggro() @@ -302,8 +368,7 @@ do_alert_animation(src) playsound(loc, 'sound/machines/chime.ogg', 50, 1, -1) for(var/mob/living/simple_animal/hostile/M in oview(distance, targets_from)) - var/list/L = M.faction&faction - if(L.len) + if(faction_check_mob(M, TRUE)) if(M.AIStatus == AI_OFF) return else @@ -323,21 +388,18 @@ return visible_message("[src] [ranged_message] at [A]!") - if(rapid) - spawn(1) - Shoot(A) - spawn(4) - Shoot(A) - spawn(6) - Shoot(A) + + if(rapid > 1) + var/datum/callback/cb = CALLBACK(src, .proc/Shoot, A) + for(var/i in 1 to rapid) + addtimer(cb, (i - 1)*rapid_fire_delay) else Shoot(A) ranged_cooldown = world.time + ranged_cooldown_time /mob/living/simple_animal/hostile/proc/Shoot(atom/targeted_atom) - if(targeted_atom == targets_from.loc || targeted_atom == targets_from) + if( QDELETED(targeted_atom) || targeted_atom == targets_from.loc || targeted_atom == targets_from ) return - var/turf/startloc = get_turf(targets_from) if(casingtype) var/obj/item/ammo_casing/casing = new casingtype(startloc) @@ -354,32 +416,70 @@ if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space newtonian_move(get_dir(targeted_atom, targets_from)) P.original = targeted_atom + P.preparePixelProjectile(targeted_atom, get_turf(targeted_atom), src) P.fire() return P -/mob/living/simple_animal/hostile/proc/DestroySurroundings() +/mob/living/simple_animal/hostile/proc/CanSmashTurfs(turf/T) + return iswallturf(T) || ismineralturf(T) + +/mob/living/simple_animal/hostile/Move(atom/newloc, dir , step_x , step_y) + if(dodging && approaching_target && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc)) + return dodge(newloc, dir) + else + return ..() + +/mob/living/simple_animal/hostile/proc/dodge(moving_to,move_direction) + //Assuming we move towards the target we want to swerve toward them to get closer + var/cdir = turn(move_direction, 45) + var/ccdir = turn(move_direction, -45) + dodging = FALSE + . = Move(get_step(loc,pick(cdir,ccdir))) + if(!.)//Can't dodge there so we just carry on + . = Move(moving_to,move_direction) + dodging = TRUE + +/mob/living/simple_animal/hostile/proc/DestroyObjectsInDirection(direction) + var/turf/T = get_step(targets_from, direction) + if(QDELETED(T)) + return + if(T.Adjacent(targets_from)) + if(CanSmashTurfs(T)) + T.attack_animal(src) + return + for(var/obj/O in T.contents) + if(!O.Adjacent(targets_from)) + continue + if((ismachinery(O) || isstructure(O)) && O.density && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !O.IsObscured()) + O.attack_animal(src) + return + +/mob/living/simple_animal/hostile/proc/DestroyPathToTarget() + if(environment_smash) + EscapeConfinement() + var/dir_to_target = get_dir(targets_from, target) + var/dir_list = list() + if(dir_to_target in diagonals) //it's diagonal, so we need two directions to hit + for(var/direction in cardinal) + if(direction & dir_to_target) + dir_list += direction + else + dir_list += dir_to_target + for(var/direction in dir_list) //now we hit all of the directions we got in this fashion, since it's the only directions we should actually need + DestroyObjectsInDirection(direction) + +/mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them if(environment_smash) EscapeConfinement() for(var/dir in cardinal) - var/turf/T = get_step(targets_from, dir) - if(iswallturf(T) || ismineralturf(T)) - if(T.Adjacent(targets_from)) - T.attack_animal(src) - for(var/a in T) - var/atom/A = a - if(!A.Adjacent(targets_from)) - continue - if(is_type_in_typecache(A, environment_target_typecache)) - A.attack_animal(src) - return + DestroyObjectsInDirection(dir) /mob/living/simple_animal/hostile/proc/EscapeConfinement() if(buckled) buckled.attack_animal(src) if(!isturf(targets_from.loc) && targets_from.loc != null)//Did someone put us in something? - var/atom/A = get_turf(targets_from) + var/atom/A = targets_from.loc A.attack_animal(src)//Bang on it till we get out - return /mob/living/simple_animal/hostile/proc/FindHidden() if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper) || istype(target.loc, /obj/machinery/bodyscanner) || istype(target.loc, /obj/machinery/recharge_station)) @@ -420,9 +520,23 @@ LosePatience() lose_patience_timer_id = addtimer(CALLBACK(src, .proc/LoseTarget), lose_patience_timeout, TIMER_STOPPABLE) + /mob/living/simple_animal/hostile/proc/LosePatience() deltimer(lose_patience_timer_id) + +//These two procs handle losing and regaining search_objects when attacked by a mob +/mob/living/simple_animal/hostile/proc/LoseSearchObjects() + search_objects = 0 + deltimer(search_objects_timer_id) + search_objects_timer_id = addtimer(CALLBACK(src, .proc/RegainSearchObjects), search_objects_regain_time, TIMER_STOPPABLE) + + +/mob/living/simple_animal/hostile/proc/RegainSearchObjects(value) + if(!value) + value = initial(search_objects) + search_objects = value + /mob/living/simple_animal/hostile/consider_wakeup() ..() var/list/tlist @@ -455,4 +569,4 @@ if(isturf(M.loc)) . += M else if(M.loc.type in hostile_machines) - . += M.loc + . += M.loc \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 027bbb60a14..5a36c3fffa9 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -46,7 +46,6 @@ Difficulty: Medium loot = list(/obj/item/stack/sheet/bone = 3) vision_range = 13 elimination = 1 - idle_vision_range = 13 appearance_flags = 0 mouse_opacity = MOUSE_OPACITY_ICON stat_attack = 1 // Overriden from /tg/ - otherwise Legion starts chasing its minions diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index f57559d549a..c5f11c501fc 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -18,19 +18,8 @@ damage_coeff = list(BRUTE = 1, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) minbodytemp = 0 maxbodytemp = INFINITY + vision_range = 5 aggro_vision_range = 18 - idle_vision_range = 5 - environment_target_typecache = list( - /obj/machinery/door/window, - /obj/structure/window, - /obj/structure/closet, - /obj/structure/table, - /obj/structure/grille, - /obj/structure/girder, - /obj/structure/rack, - /obj/structure/barricade, - /obj/machinery/field, - /obj/machinery/power/emitter) var/list/crusher_loot var/medal_type var/score_type = BOSS_SCORE diff --git a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm index ad8dcf44699..4dca1caeb29 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm @@ -26,8 +26,8 @@ a_intent = INTENT_HARM speak_emote = list("chitters") attack_sound = 'sound/weapons/bladeslice.ogg' + vision_range = 2 aggro_vision_range = 9 - idle_vision_range = 2 turns_per_move = 5 loot = list(/obj/item/stack/ore/diamond{layer = 4.1}, /obj/item/stack/ore/diamond{layer = 4.1}) diff --git a/code/modules/mob/living/simple_animal/hostile/mining/goldgrub.dm b/code/modules/mob/living/simple_animal/hostile/mining/goldgrub.dm index cd73868a9e7..7de8935e5ca 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/goldgrub.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/goldgrub.dm @@ -8,8 +8,8 @@ icon_dead = "Goldgrub_dead" icon_gib = "syndicate_gib" vision_range = 2 + vision_range = 2 aggro_vision_range = 9 - idle_vision_range = 2 move_to_delay = 5 friendly = "harmlessly rolls into" maxHealth = 45 @@ -77,5 +77,5 @@ return /mob/living/simple_animal/hostile/asteroid/goldgrub/adjustHealth(damage) - idle_vision_range = 9 + vision_range = 9 . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm index dae7a688cb3..84b919266d0 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm @@ -26,8 +26,8 @@ attacktext = "pulverizes" attack_sound = 'sound/weapons/punch1.ogg' throw_message = "does nothing to the rocky hide of the" + vision_range = 5 aggro_vision_range = 9 - idle_vision_range = 5 move_force = MOVE_FORCE_VERY_STRONG move_resist = MOVE_FORCE_VERY_STRONG pull_force = MOVE_FORCE_VERY_STRONG diff --git a/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm index 6e5c840a401..491e35cc621 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm @@ -38,7 +38,7 @@ animal_species = /mob/living/simple_animal/hostile/asteroid/gutlunch childtype = list(/mob/living/simple_animal/hostile/asteroid/gutlunch/gubbuck = 45, /mob/living/simple_animal/hostile/asteroid/gutlunch/guthen = 55) - wanted_objects = list(/obj/effect/decal/cleanable/blood/gibs) + wanted_objects = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/organ/internal) var/obj/item/udder/gutlunch/udder = null /mob/living/simple_animal/hostile/asteroid/gutlunch/New() diff --git a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm index 43d239c59ce..ca8644e0566 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm @@ -11,8 +11,8 @@ move_to_delay = 14 ranged = 1 vision_range = 5 + vision_range = 5 aggro_vision_range = 9 - idle_vision_range = 5 speed = 3 maxHealth = 75 health = 75 diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index 2e1e7508e1c..487ce431cdf 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -36,7 +36,6 @@ see_in_dark = 8 vision_range = 12 aggro_vision_range = 12 - idle_vision_range = 12 search_objects = 1 // So that it can see through walls diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm index 2f124e94479..8e53dc7cbbe 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm @@ -23,7 +23,7 @@ regen_points_per_hp = 2 // 50% higher regen speed stat_attack = 1 // ensures they will target people in crit, too! wander = 0 // wandering defeats the purpose of stealth - idle_vision_range = 3 // very low idle vision range + vision_range = 3 // very low idle vision range delay_web = 20 // double speed web_type = /obj/structure/spider/terrorweb/gray @@ -67,7 +67,6 @@ icon_living = "terror_gray_cloaked" if(!ckey) vision_range = 3 - idle_vision_range = 3 // Bugged, does not work yet. Also spams webs. Also doesn't look great. But... planned. move_to_delay = 15 // while invisible, slow. @@ -76,7 +75,6 @@ icon_state = "terror_gray" icon_living = "terror_gray" vision_range = 9 - idle_vision_range = 9 move_to_delay = 5 prob_ai_hides_in_vents = 10 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm index 5b82f8d35dd..dba6853bc15 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm @@ -92,7 +92,7 @@ var/global/list/ts_spiderling_list = list() var/degenerate = 0 // if 1, they slowly degen until they all die off. Used by high-level abilities only. // Vision - idle_vision_range = 10 + vision_range = 10 aggro_vision_range = 10 see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 6955633efce..630d78ef085 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -3,15 +3,17 @@ icon = 'icons/mob/animal.dmi' health = 20 maxHealth = 20 + gender = PLURAL //placeholder + universal_understand = 1 + universal_speak = 0 status_flags = CANPUSH var/icon_living = "" var/icon_dead = "" var/icon_resting = "" var/icon_gib = null //We only try to show a gibbing animation if this exists. - - var/healable = 1 + var/flip_on_death = FALSE //Flip the sprite upside down on death. Mostly here for things lacking custom dead sprites. var/list/speak = list() var/speak_chance = 0 @@ -20,7 +22,6 @@ var/turns_per_move = 1 var/turns_since_move = 0 - universal_speak = 0 var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. var/wander = 1 // Does the mob wander around when idle? var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. @@ -38,11 +39,13 @@ var/heat_damage_per_tick = 3 //amount of damage applied if animal's body temperature is higher than maxbodytemp var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp + //Healable by medical stacks? Defaults to yes. + var/healable = 1 + //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above - //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly var/melee_damage_lower = 0 var/melee_damage_upper = 0 @@ -53,7 +56,7 @@ var/attacktext = "attacks" var/attack_sound = null var/friendly = "nuzzles" //If the mob does no damage with it's attack - var/environment_smash = 0 //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls + var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster var/can_hide = 0 @@ -66,6 +69,7 @@ var/next_scan_time = 0 var/animal_species //Sorry, no spider+corgi buttbabies. + var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against var/gold_core_spawnable = CHEM_MOB_SPAWN_INVALID //if CHEM_MOB_SPAWN_HOSTILE can be spawned by plasma with gold core, CHEM_MOB_SPAWN_FRIENDLY are 'friendlies' spawned with blood var/mob/living/carbon/human/master_commander = null //holding var for determining who own/controls a sentient simple animal (for sentience potions). @@ -73,20 +77,35 @@ var/mob/living/simple_animal/hostile/spawner/nest var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions + var/list/loot = list() //list of things spawned at mob's loc when it dies var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses - var/attacked_sound = "punch" var/deathmessage = "" var/death_sound = null //The sound played on death + var/allow_movement_on_non_turfs = FALSE + + var/attacked_sound = "punch" + var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever) var/can_have_ai = TRUE //once we have become sentient, we can never go back + var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. + //domestication + var/tame = 0 -/mob/living/simple_animal/Initialize() - ..() + var/my_z // I don't want to confuse this with client registered_z + +/mob/living/simple_animal/Initialize(mapload) + . = ..() GLOB.simple_animals[AIStatus] += src + if(gender == PLURAL) + gender = pick(MALE, FEMALE) + if(!real_name) + real_name = name + if(!loc) + stack_trace("Simple animal being instantiated in nullspace") verbs -= /mob/verb/observe if(!can_hide) verbs -= /mob/living/simple_animal/verb/hide @@ -114,11 +133,10 @@ return ..() -/mob/living/simple_animal/Login() - if(src && src.client) - src.client.screen = list() - client.screen += client.void - ..() +/mob/living/simple_animal/examine(mob/user) + . = ..() + if(stat == DEAD) + to_chat(user, "Upon closer examination, [p_they()] appear[p_s()] to be dead.") /mob/living/simple_animal/updatehealth(reason = "none given") ..(reason) @@ -147,11 +165,13 @@ create_debug_log("died of damage, trigger reason: [reason]") /mob/living/simple_animal/proc/handle_automated_action() + set waitfor = FALSE return /mob/living/simple_animal/proc/handle_automated_movement() + set waitfor = FALSE if(!stop_automated_movement && wander) - if(isturf(src.loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + if((isturf(loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. turns_since_move++ if(turns_since_move >= turns_per_move) if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled @@ -159,11 +179,12 @@ if(Process_Spacemove(anydir)) Move(get_step(src,anydir), anydir) turns_since_move = 0 - return + return 1 -/mob/living/simple_animal/proc/handle_automated_speech() +/mob/living/simple_animal/proc/handle_automated_speech(override) + set waitfor = FALSE if(speak_chance) - if(rand(0,200) < speak_chance) + if(prob(speak_chance) || override) if(speak && speak.len) if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) var/length = speak.len @@ -201,7 +222,7 @@ var/areatemp = get_temperature(environment) - if(abs(areatemp - bodytemperature) > 40 && !(BREATHLESS in mutations)) + if(abs(areatemp - bodytemperature) > 5 && !(BREATHLESS in mutations)) var/diff = areatemp - bodytemperature diff = diff / 5 bodytemperature += diff @@ -242,30 +263,24 @@ atmos_suitable = 0 if(!atmos_suitable) - adjustBruteLoss(unsuitable_atmos_damage) + adjustHealth(unsuitable_atmos_damage) handle_temperature_damage() /mob/living/simple_animal/proc/handle_temperature_damage() - if(bodytemperature < minbodytemp) - adjustBruteLoss(cold_damage_per_tick) - else if(bodytemperature > maxbodytemp) - adjustBruteLoss(heat_damage_per_tick) + if((bodytemperature < minbodytemp) || (bodytemperature > maxbodytemp)) + adjustHealth(unsuitable_atmos_damage) /mob/living/simple_animal/gib() if(icon_gib) flick(icon_gib, src) if(butcher_results) + var/atom/Tsec = drop_location() for(var/path in butcher_results) - for(var/i = 1; i <= butcher_results[path];i++) - new path(src.loc) + for(var/i in 1 to butcher_results[path]) + new path(Tsec) ..() - -/mob/living/simple_animal/blob_act() - adjustBruteLoss(20) - return - /mob/living/simple_animal/emote(act, m_type = 1, message = null, force) if(stat) return @@ -279,29 +294,13 @@ ..() -/mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj) - if(!Proj) - return - if((Proj.damage_type != STAMINA)) - adjustBruteLoss(Proj.damage) - Proj.on_hit(src, 0) - return FALSE +/mob/living/simple_animal/say_quote(message) + var/verb = "says" -/mob/living/simple_animal/attackby(obj/item/O, mob/living/user) - if(can_collar && !collar && istype(O, /obj/item/clothing/accessory/petcollar)) - var/obj/item/clothing/accessory/petcollar/C = O - user.drop_item() - C.forceMove(src) - collar = C - collar.equipped(src) - regenerate_icons() - to_chat(usr, "You put \the [C] around \the [src]'s neck.") - if(C.tagname) - name = C.tagname - real_name = C.tagname - return - else - ..() + if(speak_emote.len) + verb = pick(speak_emote) + + return verb /mob/living/simple_animal/movement_delay() . = ..() @@ -312,21 +311,25 @@ /mob/living/simple_animal/Stat() ..() + if(statpanel("Status")) + stat(null, "Health: [round((health / maxHealth) * 100)]%") + return TRUE - statpanel("Status") - stat(null, "Health: [round((health / maxHealth) * 100)]%") +/mob/living/simple_animal/proc/drop_loot() + if(loot.len) + for(var/i in loot) + new i(loc) /mob/living/simple_animal/death(gibbed) // Only execute the below if we successfully died . = ..() if(!.) return FALSE + flying = FALSE if(nest) nest.spawned_mobs -= src nest = null - if(loot.len) - for(var/i in loot) - new i(loc) + drop_loot() if(!gibbed) if(death_sound) playsound(get_turf(src),death_sound, 200, 1) @@ -335,87 +338,41 @@ else if(!del_on_death) visible_message("\The [src] stops moving...") if(del_on_death) + //Prevent infinite loops if the mob Destroy() is overridden in such + //a manner as to cause a call to death() again + del_on_death = FALSE ghostize() qdel(src) else health = 0 icon_state = icon_dead + if(flip_on_death) + transform = transform.Turn(180) density = 0 - lying = 1 -/mob/living/simple_animal/ex_act(severity) - ..() - switch(severity) - if(1.0) - gib() - return - - if(2.0) - adjustBruteLoss(60) - - - if(3.0) - adjustBruteLoss(30) - -/mob/living/simple_animal/proc/adjustHealth(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE - var/oldbruteloss = bruteloss - bruteloss = Clamp(bruteloss + amount, 0, maxHealth) - if(oldbruteloss == bruteloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth() - -/mob/living/simple_animal/adjustBruteLoss(amount, updating_health = TRUE) - if(damage_coeff[BRUTE]) - return adjustHealth(amount * damage_coeff[BRUTE], updating_health) - -/mob/living/simple_animal/adjustFireLoss(amount, updating_health = TRUE) - if(damage_coeff[BURN]) - return adjustHealth(amount * damage_coeff[BURN], updating_health) - -/mob/living/simple_animal/adjustOxyLoss(amount, updating_health = TRUE) - if(damage_coeff[OXY]) - return adjustHealth(amount * damage_coeff[OXY], updating_health) - -/mob/living/simple_animal/adjustToxLoss(amount, updating_health = TRUE) - if(damage_coeff[TOX]) - return adjustHealth(amount * damage_coeff[TOX], updating_health) - -/mob/living/simple_animal/adjustCloneLoss(amount, updating_health = TRUE) - if(damage_coeff[CLONE]) - return adjustHealth(amount * damage_coeff[CLONE], updating_health) - -/mob/living/simple_animal/adjustStaminaLoss(amount, updating_health = TRUE) - if(damage_coeff[STAMINA]) - return ..(amount*damage_coeff[STAMINA], updating_health) - -/mob/living/simple_animal/proc/CanAttack(var/atom/the_target) +/mob/living/simple_animal/proc/CanAttack(atom/the_target) if(see_invisible < the_target.invisibility) return FALSE + if(ismob(the_target)) + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE if(isliving(the_target)) var/mob/living/L = the_target if(L.stat != CONSCIOUS) return FALSE - if(istype(the_target, /obj/mecha)) + if(ismecha(the_target)) var/obj/mecha/M = the_target if(M.occupant) return FALSE - if(istype(the_target,/obj/spacepod)) + if(isspacepod(the_target)) var/obj/spacepod/S = the_target if(S.pilot) return FALSE return TRUE /mob/living/simple_animal/handle_fire() - return - -/mob/living/simple_animal/update_fire() - return + return TRUE /mob/living/simple_animal/IgniteMob() return FALSE @@ -423,32 +380,20 @@ /mob/living/simple_animal/ExtinguishMob() return -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - /mob/living/simple_animal/revive() ..() health = maxHealth + icon = initial(icon) icon_state = icon_living density = initial(density) update_canmove() - - + flying = initial(flying) /mob/living/simple_animal/proc/make_babies() // <3 <3 <3 if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) return FALSE next_scan_time = world.time + 400 - + var/alone = TRUE var/mob/living/simple_animal/partner var/children = 0 @@ -472,42 +417,6 @@ if(target) return new childspawn(target) -/mob/living/simple_animal/say_quote(var/message) - var/verb = "says" - - if(speak_emote.len) - verb = pick(speak_emote) - - return verb - -/mob/living/simple_animal/update_canmove(delay_action_updates = 0) - if(paralysis || stunned || weakened || stat || resting) - drop_r_hand() - drop_l_hand() - canmove = 0 - else if(buckled) - canmove = 0 - else - canmove = 1 - update_transform() - if(!delay_action_updates) - update_action_buttons_icon() - return canmove - -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - -/* Inventory */ - /mob/living/simple_animal/show_inv(mob/user as mob) if(!can_collar) return @@ -572,50 +481,36 @@ if(collar) . |= collar.GetAccess() -/* End Inventory */ +/mob/living/simple_animal/update_canmove(delay_action_updates = 0) + if(paralysis || stunned || weakened || stat || resting) + drop_r_hand() + drop_l_hand() + canmove = 0 + else if(buckled) + canmove = 0 + else + canmove = 1 + update_transform() + if(!delay_action_updates) + update_action_buttons_icon() + return canmove + +/mob/living/simple_animal/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = FALSE + + if(resize != RESIZE_DEFAULT_SIZE) + changed = TRUE + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) /mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion toggle_ai(AI_OFF) can_have_ai = FALSE - return -/mob/living/simple_animal/can_hear() - . = TRUE - -/mob/living/simple_animal/proc/consider_wakeup() - if(pulledby || shouldwakeup) - toggle_ai(AI_ON) - -/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(!ckey && !stat)//Not unconscious - if(AIStatus == AI_IDLE) - toggle_ai(AI_ON) - -/mob/living/simple_animal/proc/toggle_ai(togglestatus) - if(!can_have_ai && (togglestatus != AI_OFF)) - return - if(AIStatus != togglestatus) - if(togglestatus > 0 && togglestatus < 5) - if(togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) - var/turf/T = get_turf(src) - if(AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - else - SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src - GLOB.simple_animals[AIStatus] -= src - GLOB.simple_animals[togglestatus] += src - AIStatus = togglestatus - else - stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") - -/mob/living/simple_animal/onTransitZ(old_z, new_z) - ..() - if(AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src - toggle_ai(initial(AIStatus)) - -// Simple animals will not be given night vision upon death, as that would result in issues when they are revived. /mob/living/simple_animal/grant_death_vision() sight |= SEE_TURFS sight |= SEE_MOBS @@ -642,4 +537,37 @@ return SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() \ No newline at end of file + sync_lighting_plane_alpha() + +/mob/living/simple_animal/proc/toggle_ai(togglestatus) + if(!can_have_ai && (togglestatus != AI_OFF)) + return + if(AIStatus != togglestatus) + if(togglestatus > 0 && togglestatus < 5) + if(togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) + var/turf/T = get_turf(src) + if(AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + else + SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src + GLOB.simple_animals[AIStatus] -= src + GLOB.simple_animals[togglestatus] += src + AIStatus = togglestatus + else + stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") + +/mob/living/simple_animal/proc/consider_wakeup() + if(pulledby || shouldwakeup) + toggle_ai(AI_ON) + +/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE) + . = ..() + if(!ckey && !stat)//Not unconscious + if(AIStatus == AI_IDLE) + toggle_ai(AI_ON) + +/mob/living/simple_animal/onTransitZ(old_z, new_z) + ..() + if(AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src + toggle_ai(initial(AIStatus)) \ No newline at end of file diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index f5f1924e395..66b4f2f9a16 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -56,9 +56,6 @@ if(abilities) client.verbs |= abilities - if(ishuman(src)) - client.screen += client.void - //HUD updates (antag hud, etc) //readd this mob's HUDs (antag, med, etc) reload_huds() diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi index 6209ce84949..78778b27d97 100644 Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ diff --git a/paradise.dme b/paradise.dme index 7e67e6dde23..83cc131a647 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1900,6 +1900,7 @@ #include "code\modules\mob\living\simple_animal\animal_defense.dm" #include "code\modules\mob\living\simple_animal\constructs.dm" #include "code\modules\mob\living\simple_animal\corpse.dm" +#include "code\modules\mob\living\simple_animal\damage_procs.dm" #include "code\modules\mob\living\simple_animal\parrot.dm" #include "code\modules\mob\living\simple_animal\posessed_object.dm" #include "code\modules\mob\living\simple_animal\powers.dm"