diff --git a/baystation12.dme b/baystation12.dme index 8abe15560d3..e46880cadf2 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1012,6 +1012,7 @@ #include "code\modules\economy\Events_Mundane.dm" #include "code\modules\economy\TradeDestinations.dm" #include "code\modules\events\apc_damage.dm" +#include "code\modules\events\bear_attack.dm" #include "code\modules\events\blob.dm" #include "code\modules\events\brand_intelligence.dm" #include "code\modules\events\camera_damage.dm" diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index dc6f99ff9cf..6ecd67658ad 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -289,3 +289,41 @@ var/list/mob/living/forced_ambiance_list = new H.AdjustStunned(1) H.AdjustWeakened(1) mob << "The sudden appearance of gravity makes you fall to the floor!" + + +//A useful proc for events. +//This returns a random area of the station which is meaningful. Ie, a room somewhere + +/proc/random_station_area() + var/list/possible = list() + for(var/Y in the_station_areas) + for(var/areapath in typesof(Y)) + var/area/A = locate(areapath) + if(!A) + continue + if(!(A.z in config.station_levels)) + continue + if (istype(A, /area/shuttle)) + continue + if (istype(A, /area/solar) || findtext(A.name, "solar")) + continue + if (istype(A, /area/constructionsite)) + continue + + //Although hostile mobs instadying to turrets is fun + //If there's no AI they'll just be hit with stunbeams all day and spam the attack logs. + if (istype(A, /area/turret_protected)) + continue + + possible.Add(A) + + return pick(possible) + + +/area/proc/random_space() + var/list/turfs = list() + for(var/turf/simulated/floor/F in src.contents) + if(turf_clear(F)) + turfs += F + + return pick(turfs) \ No newline at end of file diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm index a90bc689047..15cb3f83c5e 100644 --- a/code/game/objects/items/weapons/traps.dm +++ b/code/game/objects/items/weapons/traps.dm @@ -77,11 +77,11 @@ //armour var/blocked = L.run_armor_check(target_zone, "melee") - if(blocked >= 2) return - if(!L.apply_damage(30, BRUTE, target_zone, blocked, used_weapon=src)) + var/success = L.apply_damage(30, BRUTE, target_zone, blocked, src) + if(!success) return 0 //trap the victim in place @@ -92,6 +92,12 @@ L << "The steel jaws of \the [src] bite into you, trapping you in place!" deployed = 0 can_buckle = initial(can_buckle) + playsound(src, 'sound/weapons/beartrap_shut.ogg', 100, 1)//Really loud snapping sound + + if (istype(L, /mob/living/simple_animal/hostile/bear)) + var/mob/living/simple_animal/hostile/bear/bear = L + bear.anger += 15//Beartraps make bears really angry + bear.instant_aggro() /obj/item/weapon/beartrap/Crossed(AM as mob|obj) if(deployed && isliving(AM)) diff --git a/code/modules/events/bear_attack.dm b/code/modules/events/bear_attack.dm new file mode 100644 index 00000000000..fd1dcc098ff --- /dev/null +++ b/code/modules/events/bear_attack.dm @@ -0,0 +1,33 @@ +/datum/event/bear_attack + startWhen = 2 + announceWhen = 20 + + var/list/possible_turfs = list() + var/spawn_type + var/spawn_number + +/datum/event/bear_attack/setup() + for(var/areapath in typesof(/area/maintenance)) + var/area/A = locate(areapath) + for(var/turf/simulated/floor/F in A.contents) + if(turf_clear(F)) + possible_turfs |= F + + if (severity <= EVENT_LEVEL_MODERATE)//Moderate spacebear event disabled by head developer veto + spawn_type = /mob/living/simple_animal/hostile/bear + spawn_number = rand(8,16) + else + spawn_type = /mob/living/simple_animal/hostile/bear/spatial + spawn_number = rand(4,6) + + +/datum/event/bear_attack/start() + var/i + for (i = 0,i < spawn_number, i++) + new spawn_type(pick(possible_turfs)) + +/datum/event/bear_attack/announce() + if (severity <= EVENT_LEVEL_MODERATE) + command_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') + else + command_announcement.Announce("Highly dangerous bioweapons have escaped from a nearby research facility and boarded [station_name()]. Station security is advised to be on high alert.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index ea9d0435a34..d908d4808a1 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -165,6 +165,7 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT new /datum/event_meta(EVENT_LEVEL_MODERATE, "Spider Infestation", /datum/event/spider_infestation, 50, list(ASSIGNMENT_SECURITY = 25)), new /datum/event_meta(EVENT_LEVEL_MODERATE, "Viral Infection", /datum/event/viral_infection, 0, list(ASSIGNMENT_MEDICAL = 12), 1), new /datum/event_meta(EVENT_LEVEL_MODERATE, "Major Vermin Infestation", /datum/event/infestation, 60, list(ASSIGNMENT_JANITOR = 15, ASSIGNMENT_SECURITY = 15)), + ) /datum/event_container/major @@ -176,6 +177,8 @@ var/global/list/severity_to_string = list(EVENT_LEVEL_MUNDANE = "Mundane", EVENT new /datum/event_meta(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 40, list(ASSIGNMENT_ENGINEER = 10),1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Space Vines", /datum/event/spacevine, 50, list(ASSIGNMENT_ENGINEER = 10, ASSIGNMENT_GARDENER = 20), 1), new /datum/event_meta(EVENT_LEVEL_MAJOR, "Viral Infection", /datum/event/viral_infection, 20, list(ASSIGNMENT_MEDICAL = 13), 1), + new /datum/event_meta(EVENT_LEVEL_MAJOR, "Bluespace Bears", /datum/event/bear_attack, 60, list(ASSIGNMENT_SECURITY = 10), 1), + ) //NOTE: Re added nothing option, but with fairly low weight diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index b88dcb56af7..557ccec9367 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -6,7 +6,7 @@ var/list/antibodies = list() var/last_eating = 0 //Not sure what this does... I found it hidden in food.dm - var/life_tick = 0 // The amount of life ticks that have processed on this mob. + var/analgesic = 0 // when this is set, the mob isn't affected by shock or pain // life should decrease this by 1 every tick // total amount of wounds on mob, used to spread out healing and the like over all wounds diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 26209ddac7d..d393dc8cd0d 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -189,14 +189,14 @@ // End BS12 momentum-transfer code. /mob/living/attack_generic(var/mob/user, var/damage, var/attack_message) - if(!damage) return adjustBruteLoss(damage) user.attack_log += text("\[[time_stamp()]\] attacked [src.name] ([src.ckey])") src.attack_log += text("\[[time_stamp()]\] was attacked by [user.name] ([user.ckey])") - src.visible_message("[user] has [attack_message] [src]!") + if (attack_message) + src.visible_message("[user] has [attack_message] [src]!") user.do_attack_animation(src) spawn(1) updatehealth() return 1 diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index eddf2f4c2d3..edd441b07c4 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -46,4 +46,5 @@ var/mouth_size = 2//how large of a creature it can swallow at once, and how big of a bite it can take out of larger things var/eat_types = 0//This is a bitfield which must be initialised in New(). The valid values for it are in devour.dm var/datum/reagents/metabolism/ingested = null - var/underdoor //Used for mobs that can walk through maintenance hatches - drones, pais, and spiderbots \ No newline at end of file + var/underdoor //Used for mobs that can walk through maintenance hatches - drones, pais, and spiderbots + var/life_tick = 0 // The amount of life ticks that have processed on this mob. \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index 0da4cf45571..1203cb6e046 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -1,7 +1,10 @@ +#define BEARMODE_INDOORS 1 +#define BEARMODE_SPACE 2 + //Space bears! /mob/living/simple_animal/hostile/bear name = "space bear" - desc = "RawrRawr!!" + desc = "You should walk away, quickly!" icon_state = "bear" icon_living = "bear" icon_dead = "bear_dead" @@ -10,19 +13,42 @@ speak_emote = list("growls", "roars") emote_hear = list("rawrs","grumbles","grawls") emote_see = list("stares ferociously", "stomps") - speak_chance = 1 - turns_per_move = 5 + speak_chance = 10 + turns_per_move = 7 see_in_dark = 6 meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat response_help = "pets" response_disarm = "gently pushes aside" response_harm = "pokes" stop_automated_movement_when_pulled = 0 - maxHealth = 60 - health = 60 - melee_damage_lower = 20 - melee_damage_upper = 30 + maxHealth = 80 + melee_damage_lower = 16 + melee_damage_upper = 24 + break_stuff_probability = 80 mob_size = 17 + var/turns_since_hit = 0//If the bear chases someone too long without hitting them, it will try to change to another nearby target instead + + attacktext = null//This allows custom attacking emotes + + var/quiet_sounds = list('sound/effects/creatures/bear_quiet_1.ogg', + 'sound/effects/creatures/bear_quiet_2.ogg', + 'sound/effects/creatures/bear_quiet_3.ogg', + 'sound/effects/creatures/bear_quiet_4.ogg', + 'sound/effects/creatures/bear_quiet_5.ogg', + 'sound/effects/creatures/bear_quiet_6.ogg') + var/loud_sounds = list('sound/effects/creatures/bear_loud_1.ogg', + 'sound/effects/creatures/bear_loud_2.ogg', + 'sound/effects/creatures/bear_loud_3.ogg', + 'sound/effects/creatures/bear_loud_4.ogg') + var/bearmode + //Bears in space, or other low-pressure environments are stronger. Higher health, faster movement, longer aggression before being tired + + var/anger + //An angry bear will immediately attack anyone it sees without warning + //Anger decreases at 1 point per minute + //Any amount of anger causes instant aggro, quantity of it is only a duration + + var/health_last_tick = 0 //Space bears aren't affected by atmos. min_oxy = 0 @@ -38,6 +64,9 @@ faction = "russian" + + + //SPACE BEARS! SQUEEEEEEEE~ OW! FUCK! IT BIT MY HAND OFF!! /mob/living/simple_animal/hostile/bear/Hudson name = "Hudson" @@ -46,128 +75,466 @@ response_disarm = "gently pushes aside" response_harm = "pokes" +/mob/living/simple_animal/hostile/bear/New() + ..() + update_bearmode() + +/mob/living/simple_animal/hostile/bear/proc/set_stance(var/input) + var/previous = stance + stance = input + if (stance != previous) + stance_step = 0 + /mob/living/simple_animal/hostile/bear/Life() . =..() - if(!.) + if (stat != CONSCIOUS) return - if(loc && istype(loc,/turf/space)) - icon_state = "bear" - else - icon_state = "bearfloor" + if (life_tick % 5 == 0) + update_bearmode() + + + //This covers cases where the bear is damaged by things it can't detect. + //Most notably, security bots, beepsky kept murdering them without resistance + if (health < health_last_tick && stance != HOSTILE_STANCE_TIRED) + anger++ + instant_aggro() + + if (life_tick % 30 == 0)//A point of anger wears off per minute + anger = max(0, anger-1) switch(stance) if(HOSTILE_STANCE_TIRED) stop_automated_movement = 1 stance_step++ - if(stance_step >= 10) //rests for 10 ticks + if(stance_step >= 15) //rests for 10 ticks if(target_mob && target_mob in ListTargets(10)) - stance = HOSTILE_STANCE_ATTACK //If the mob he was chasing is still nearby, resume the attack, otherwise go idle. + set_stance(HOSTILE_STANCE_ATTACK) //If the mob he was chasing is still nearby, resume the attack, otherwise go idle. else - stance = HOSTILE_STANCE_IDLE + set_stance(HOSTILE_STANCE_IDLE) if(HOSTILE_STANCE_ALERT) stop_automated_movement = 1 var/found_mob = 0 - if(target_mob && target_mob in ListTargets(10)) - if(!(SA_attackable(target_mob))) - stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again. - stance_step++ - found_mob = 1 - src.set_dir(get_dir(src,target_mob)) //Keep staring at the mob + if(target_mob && (target_mob in ListTargets(10)) && !(SA_attackable(target_mob))) + found_mob = 1 + else + LoseTarget() - if(stance_step in list(1,4,7)) //every 3 ticks - var/action = pick( list( "growls at [target_mob]", "stares angrily at [target_mob]", "prepares to attack [target_mob]", "closely watches [target_mob]" ) ) - if(action) - custom_emote(1,action) - if(!found_mob) + if(target_mob) + found_mob = 1 + + + if (found_mob) + stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again. + stance_step++ + set_dir(get_dir(src,target_mob)) //Keep staring at the mob + if(stance_step in list(1,4,7)) //every 3 ticks + var/action = pick( list( "growls at [target_mob]", "stares angrily at [target_mob]", "prepares to attack [target_mob]", "closely watches [target_mob]" ) ) + if(action) + custom_emote(1,action) + speak_audio() + else stance_step-- - if(stance_step <= -20) //If we have not found a mob for 20-ish ticks, revert to idle mode - stance = HOSTILE_STANCE_IDLE - if(stance_step >= 7) //If we have been staring at a mob for 7 ticks, - stance = HOSTILE_STANCE_ATTACK + + + + if (anger && found_mob) + instant_aggro()//If we're angry and someone is nearby, skip waiting and charge them + + if(stance_step <= -20*bearmode) //If we have not found a mob for 20-ish ticks, revert to idle mode + set_stance(HOSTILE_STANCE_IDLE) + stop_automated_movement = 0 + if(stance_step >= 12) //If this mob just refuses to get out of our territory, then we attack + anger += 3 + set_stance(HOSTILE_STANCE_ATTACK) + growl_loud() if(HOSTILE_STANCE_ATTACKING) - if(stance_step >= 20) //attacks for 20 ticks, then it gets tired and needs to rest - custom_emote(1, "is worn out and needs to rest." ) - stance = HOSTILE_STANCE_TIRED - stance_step = 0 - walk(src, 0) //This stops the bear's walking + turns_since_hit++ + stance_step++ + if(stance_step >= (16+(anger*2))*bearmode) //attacks a while, then it gets tired and needs to rest. An angry bear will chase longer + tire_out() return + //If we're having no luck attacking our current target + if (target_mob && !Adjacent(target_mob) && turns_since_hit > 3) + LoseTarget() + else + set_dir(get_dir(src,target_mob)) + + health_last_tick = health + + +//Causes the bear to find and start attacking the nearest target. +//This will overwrite any existing target if a different one is closer +//If there are no other suitable targets, targeting will not be changed +/mob/living/simple_animal/hostile/bear/FindTarget() + + var/turf/here = get_turf(src) + var/mob/nearest_target = null + var/nearest_dist = 99999 + var/mob/nearest_downed_target = null + var/nearest_downed_dist = 99999 + + for(var/atom/A in ListTargets(10)) + + if(A == src || A == target_mob)//We're only interested in alternatives to our current target + continue + + + var/dist = get_dist(here, get_turf(A)) < nearest_dist + if(isliving(A)) + var/mob/living/L = A + if(L.faction == src.faction && !attack_same) + continue + else if(L in friends) + continue + else + + if(!L.stat == CONSCIOUS) + if (dist < nearest_dist) + nearest_target = L + nearest_dist = dist + + else if (L.stat != DEAD)//Unconscious people are lower priority, only targeted if nobody nearby is standing + if (dist < nearest_downed_dist) + nearest_downed_target = L + nearest_downed_dist = dist + + else if(istype(A, /obj/mecha)) + var/obj/mecha/M = A + if (M.occupant) + if (dist < nearest_dist) + nearest_target = M + nearest_dist = dist + + if(istype(A, /obj/machinery/bot)) + var/obj/machinery/bot/B = A + if (B.health > 0) + if (dist < nearest_dist) + nearest_target = B + nearest_dist = dist + + if (nearest_target) + target_mob = nearest_target + FoundTarget() + return nearest_target + else if (nearest_downed_target) + target_mob = nearest_downed_target + FoundTarget() + return nearest_downed_target + + return target_mob + //If neither of the above is true, dont change the target + + +/mob/living/simple_animal/hostile/bear/proc/tire_out() + custom_emote(1, "is worn out and needs to rest." ) + set_stance(HOSTILE_STANCE_TIRED) + speak_audio() + stance_step = 0 + walk(src, 0) //This stops the bear's walking + +/mob/living/simple_animal/hostile/bear/spatial/tire_out() + ..() + spawn(5) + teleport()//Bluespace bears teleport away to rest /mob/living/simple_animal/hostile/bear/attackby(var/obj/item/O as obj, var/mob/user as mob) - if(stance != HOSTILE_STANCE_ATTACK && stance != HOSTILE_STANCE_ATTACKING) - stance = HOSTILE_STANCE_ALERT - stance_step = 6 - target_mob = user + var/healthbefore = health ..() + spawn(1) + if (health < healthbefore)//Hurting the bear makes it mad + if(stance != HOSTILE_STANCE_ATTACK && stance != HOSTILE_STANCE_ATTACKING) + target_mob = user + anger++ + instant_aggro() + /mob/living/simple_animal/hostile/bear/attack_hand(mob/living/carbon/human/M as mob) - if(stance != HOSTILE_STANCE_ATTACK && stance != HOSTILE_STANCE_ATTACKING) - stance = HOSTILE_STANCE_ALERT - stance_step = 6 - target_mob = M + var/healthbefore = health ..() + spawn(1) + if (health < healthbefore)//Hurting the bear makes it mad + if(stance != HOSTILE_STANCE_ATTACK && stance != HOSTILE_STANCE_ATTACKING) + target_mob = M + anger++ + instant_aggro() + + +/mob/living/simple_animal/hostile/bear/bullet_act(obj/item/projectile/P, def_zone)//Teleport around when shot, so its harder to burst it down with a carbine + var/healthbefore = health + ..() + spawn(1) + if (health < healthbefore) + instant_aggro() + +/mob/living/simple_animal/hostile/bear/ex_act() + var/healthbefore = health + ..() + spawn(1) + if (health < healthbefore) + instant_aggro() + /mob/living/simple_animal/hostile/bear/Process_Spacemove(var/check_drift = 0) - return //No drifting in space for space bears! + inertia_dir = 0 + return 1 //No drifting in space for space bears! + //Fixed this, it wasnt working -/mob/living/simple_animal/hostile/bear/FindTarget() - . = ..() - if(.) - custom_emote(1,"stares alertly at [.]") - stance = HOSTILE_STANCE_ALERT +/mob/living/simple_animal/hostile/bear/FoundTarget() + if(target_mob) + turns_since_hit = 0 + custom_emote(1,"stares alertly at [target_mob]") + speak_audio() + + //If we're idle, move up to alert. + //But don't drop down to alert if we're already in combat + if (stance == HOSTILE_STANCE_IDLE) + set_stance(HOSTILE_STANCE_ALERT) + else if (stance == HOSTILE_STANCE_ATTACKING) + MoveToTarget() /mob/living/simple_animal/hostile/bear/LoseTarget() - ..(5) + //If we're still angry, try to find someone else to attack + if (anger) + FindTarget() + + if (!anger || !target_mob) + //If the anger has subsided, then give up the chase and let them go + //But we'll get mad again soon if they don't go. + if (stance > HOSTILE_STANCE_ALERT)//If we're currently above alert + set_stance(HOSTILE_STANCE_ALERT)//Drop to alert and cease attacking + target_mob = null + walk(src, 0) /mob/living/simple_animal/hostile/bear/AttackingTarget() - if(!Adjacent(target_mob)) + var/targetname = target_mob.name + if(..()) + turns_since_hit = 0 + custom_emote(1, pick( list("crushes [targetname] in its arms","slashes at [targetname]", "bites [targetname]", "mauls [targetname]", "tears into [targetname]", "rends [targetname]") ) ) + if (prob(15)) + growl_loud() + else if (prob(10)) + growl_soft() + + +/mob/living/simple_animal/hostile/bear/proc/update_bearmode() + turns_per_move = initial(turns_per_move) + if (anger >= 3) + turns_per_move -= 1 + + + var/former = bearmode + bearmode = BEARMODE_INDOORS + if(loc && istype(loc,/turf/space)) + bearmode = BEARMODE_SPACE + else + if(istype(loc,/turf)) + var/turf/T = loc + var/datum/gas_mixture/environment = T.return_air() + if (environment.return_pressure() <= 80) + bearmode = BEARMODE_SPACE + + if (bearmode != former) + var/healthpercent + if (bearmode == BEARMODE_SPACE) + custom_emote(1, "looks bright, energised and aggressive!" ) + healthpercent = health / maxHealth + maxHealth = initial(maxHealth) * 1.5 + health = maxHealth * healthpercent + melee_damage_lower = initial(melee_damage_lower)*1.2 + melee_damage_upper = initial(melee_damage_upper)*1.2 + turns_per_move -= 2 + growl_loud() + else + custom_emote(1, "looks darker and more subdued." ) + healthpercent = health / maxHealth + maxHealth = initial(maxHealth) + health = maxHealth * healthpercent + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + growl_soft() + + update_icons() + + +/mob/living/simple_animal/hostile/bear/update_icons() + if (stat == DEAD) + icon_state = "bear_dead" + else if (bearmode == BEARMODE_INDOORS) + icon_state = "bearfloor" + else + icon_state = "bear" + + +/mob/living/simple_animal/hostile/bear/speak_audio() + if (anger > 5 || (anger && prob(25))) + growl_loud() + else + growl_soft() + + +//Plays a random selection of six sounds, at a low volume +//This is triggered randomly periodically by the bear +/mob/living/simple_animal/hostile/bear/proc/growl_soft() + var/sound = pick(quiet_sounds) + playsound(src, sound, 50, 1,3, usepressure = 0) + + + +//Plays a loud sound from a selection of four +//Played when bear is attacking or dies +/mob/living/simple_animal/hostile/bear/proc/growl_loud() + var/sound = pick(loud_sounds) + playsound(src, sound, 85, 1, 5, usepressure = 0) + + + + + + +//A special bear subclass which is more powerful and has the ability to teleport around to seek out prey. +//It dislikes other bears and refuses to cooperate with them. If two of them see each other, one or both will teleport away +//Therefore the crew never has to fight more than one at a time +/mob/living/simple_animal/hostile/bear/spatial + name = "bluespace bear" + desc = "*bzzt*..Rawr!!" + maxHealth = 130 + turns_per_move = 7 + break_stuff_probability = 100//Constantly smashing everything nearby + speak_chance = 15 + var/idletime + var/focus_time//How long we've focused on this target + var/teleport_delay = 60 + var/tactical_delay = 3//Procs between shortrange teleports + + + +//Called when we want to bypass ticks and attack immediately. For example in response to being shot +//This calls several procs and some duplicated code from the parent class to immediately put us in an assault state and lash out +/mob/living/simple_animal/hostile/bear/proc/instant_aggro() + if (stance < HOSTILE_STANCE_ATTACK) + growl_loud() + if(!target_mob) + FindTarget() + + if(target_mob) + growl_loud() + if(destroy_surroundings) + DestroySurroundings() + MoveToTarget() + AttackTarget()//When first aggroed, this causes the bear to instantly lash out and counter any melee attacker nearby + else + set_stance(HOSTILE_STANCE_ALERT) + + + + + + +//These bears are always in spacemode +/mob/living/simple_animal/hostile/bear/spatial/update_bearmode() + turns_per_move = initial(turns_per_move) + if (anger >= 3) + turns_per_move -= 1 + + + bearmode = BEARMODE_SPACE + + var/healthpercent = health / maxHealth + maxHealth = initial(maxHealth) * 1.5 + health = maxHealth * healthpercent + melee_damage_lower = initial(melee_damage_lower)*1.2 + melee_damage_upper = initial(melee_damage_upper)*1.2 + turns_per_move -= 2 + + update_icons() + +/mob/living/simple_animal/hostile/bear/spatial/Life() + ..() + if (stat != CONSCIOUS) return - custom_emote(1, pick( list("slashes at [target_mob]", "bites [target_mob]") ) ) - - var/damage = rand(20,30) - - if(ishuman(target_mob)) - var/mob/living/carbon/human/H = target_mob - var/dam_zone = pick("chest", "l_hand", "r_hand", "l_leg", "r_leg") - var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) - H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp=1, edge=1) - return H - else if(isliving(target_mob)) - var/mob/living/L = target_mob - L.adjustBruteLoss(damage) - return L - //else if(istype(target_mob,/obj/mecha)) - //var/obj/mecha/M = target_mob - //M.attack_animal(src) - //return M - - - - - - - - - - - + var/time_to_go = 0 + //If they've sat around too long without finding a mob to target, then they'll teleport elsewhere to seek prey + if (stance == HOSTILE_STANCE_IDLE) + idletime++ + if (idletime >= teleport_delay) + time_to_go = 1 + else + idletime = 0 + + //Randomly make shortrange teleports in the vicinity of the target + if ((stance == HOSTILE_STANCE_ATTACKING)&& target_mob) + focus_time++ + if (focus_time % tactical_delay == 0) + teleport_tactical(target_mob) + + + //Teleport away from other bears + for (var/mob/living/simple_animal/hostile/bear/bear in view(world.view, get_turf(src))) + if (bear != src && bear.stat != DEAD) + time_to_go = 1 + + if (time_to_go) + idletime = 0 + stance = HOSTILE_STANCE_IDLE + teleport() + growl_soft() +//Used to move to a new part of the station when it sees another bear, or it hasnt found any prey +/mob/living/simple_animal/hostile/bear/spatial/proc/teleport() + if (stat == CONSCIOUS) + var/area/A = random_station_area() + var/turf/target = A.random_space() + + sparks() + forceMove(target) + sparks() +//Used, with some luck, to reposition near the target. Hiding behind glass is a bad idea +//Picks a random tile in the target's area and teleports there. Might be closer, might be farther away +//Who knows, it's unpredictable. But definitely dangerous. +//This allows the target to escape as often as it allows the bear to attack +/mob/living/simple_animal/hostile/bear/spatial/proc/teleport_tactical() + if (target_mob && stat == CONSCIOUS) + var/area/A = get_area(target_mob) + if (A) + var/turf/target = A.random_space() + sparks() + forceMove(target) + sparks() + return 1 + return 0 +/mob/living/simple_animal/hostile/bear/spatial/bullet_act(obj/item/projectile/P, def_zone)//Teleport around when shot, so its harder to burst it down with a carbine + ..(P, def_zone) + if (prob(P.damage*1.5))//Bear has a good chance of teleporting when shot, making it harder to burst down + teleport_tactical() +/mob/living/simple_animal/hostile/bear/spatial/proc/sparks() + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(10, 1, get_turf(src)) + s.start() + spawn(2) + qdel(s) + +/mob/living/simple_animal/hostile/bear/spatial/FoundTarget() + ..() + focus_time = 0 +#undef BEARMODE_INDOORS +#undef BEARMODE_SPACE \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 4931e5d1eae..d3f7cb29b27 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -56,16 +56,24 @@ stance = HOSTILE_STANCE_ATTACK T = B break + + if (T != target_mob) + target_mob = T + FoundTarget() return T +//This proc is called after a target is acquired +/mob/living/simple_animal/hostile/proc/FoundTarget() + return + /mob/living/simple_animal/hostile/proc/Found(var/atom/A) return /mob/living/simple_animal/hostile/proc/MoveToTarget() stop_automated_movement = 1 if(!target_mob || SA_attackable(target_mob)) - stance = HOSTILE_STANCE_IDLE + LoseTarget() if(target_mob in ListTargets(10)) if(ranged) if(get_dist(src, target_mob) <= 6) @@ -83,7 +91,7 @@ LoseTarget() return 0 if(!(target_mob in ListTargets(10))) - LostTarget() + LoseTarget() return 0 if(get_dist(src, target_mob) <= 1) //Attacking AttackingTarget() @@ -103,15 +111,16 @@ if(istype(target_mob,/obj/machinery/bot)) var/obj/machinery/bot/B = target_mob B.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext) + return B /mob/living/simple_animal/hostile/proc/LoseTarget() stance = HOSTILE_STANCE_IDLE target_mob = null walk(src, 0) + LostTarget() /mob/living/simple_animal/hostile/proc/LostTarget() - stance = HOSTILE_STANCE_IDLE - walk(src, 0) + return /mob/living/simple_animal/hostile/proc/ListTargets(var/dist = 7) @@ -147,9 +156,9 @@ MoveToTarget() if(HOSTILE_STANCE_ATTACKING) - if(destroy_surroundings) + if(!AttackTarget() && destroy_surroundings)//hit a window OR a mob, not both at once DestroySurroundings() - AttackTarget() + /mob/living/simple_animal/hostile/proc/OpenFire(target_mob) var/target = target_mob @@ -197,10 +206,12 @@ for(var/obj/structure/window/obstacle in get_step(src, dir)) if(obstacle.dir == reverse_dir[dir]) // So that windows get smashed in the right order obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext) - return + return 1 var/obj/structure/obstacle = locate(/obj/structure, get_step(src, dir)) if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille)) obstacle.attack_generic(src,rand(melee_damage_lower,melee_damage_upper),attacktext) + return 1 + return 0 /mob/living/simple_animal/hostile/proc/check_horde() @@ -234,7 +245,7 @@ else if(istype(A, /obj/structure/window) || istype(A, /obj/structure/closet) || istype(A, /obj/structure/table) || istype(A, /obj/structure/grille)) A.attack_generic(src, rand(melee_damage_lower, melee_damage_upper)) Move(T) - FindTarget() + target_mob = FindTarget() if(!target_mob || enroute) spawn(10) if(!src.stat) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 7461d96ef8a..1c7981794f5 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -103,6 +103,7 @@ ..() seek_move_delay = (1 / seek_speed) / (world.tick_lag / 10)//number of ticks between moves turns_since_scan = rand(min_scan_interval, max_scan_interval)//Randomise this at the start so animals don't sync up + health = maxHealth verbs -= /mob/verb/observe health = maxHealth if (mob_size) @@ -144,7 +145,7 @@ /mob/living/simple_animal/Life() ..() - + life_tick++ //Health updatehealth() diff --git a/html/changelogs/Nanako-Bears.yml b/html/changelogs/Nanako-Bears.yml new file mode 100644 index 00000000000..e7f810e6b02 --- /dev/null +++ b/html/changelogs/Nanako-Bears.yml @@ -0,0 +1,40 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Nanako + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Space bears can now control their movement in space." + - rscadd: "Bears are now stronger in space or low pressure, weaker in pressurised environments" + - soundadd: "Rawr! Kthunk!" + - rscadd: "Added more bears and events!" + \ No newline at end of file diff --git a/sound/effects/creatures/bear_loud_1.ogg b/sound/effects/creatures/bear_loud_1.ogg new file mode 100644 index 00000000000..f12071809e8 Binary files /dev/null and b/sound/effects/creatures/bear_loud_1.ogg differ diff --git a/sound/effects/creatures/bear_loud_2.ogg b/sound/effects/creatures/bear_loud_2.ogg new file mode 100644 index 00000000000..599a41b44e5 Binary files /dev/null and b/sound/effects/creatures/bear_loud_2.ogg differ diff --git a/sound/effects/creatures/bear_loud_3.ogg b/sound/effects/creatures/bear_loud_3.ogg new file mode 100644 index 00000000000..bf31276f018 Binary files /dev/null and b/sound/effects/creatures/bear_loud_3.ogg differ diff --git a/sound/effects/creatures/bear_loud_4.ogg b/sound/effects/creatures/bear_loud_4.ogg new file mode 100644 index 00000000000..55af6e9dd7c Binary files /dev/null and b/sound/effects/creatures/bear_loud_4.ogg differ diff --git a/sound/effects/creatures/bear_quiet_1.ogg b/sound/effects/creatures/bear_quiet_1.ogg new file mode 100644 index 00000000000..3727916a0ba Binary files /dev/null and b/sound/effects/creatures/bear_quiet_1.ogg differ diff --git a/sound/effects/creatures/bear_quiet_2.ogg b/sound/effects/creatures/bear_quiet_2.ogg new file mode 100644 index 00000000000..3fea1faac9a Binary files /dev/null and b/sound/effects/creatures/bear_quiet_2.ogg differ diff --git a/sound/effects/creatures/bear_quiet_3.ogg b/sound/effects/creatures/bear_quiet_3.ogg new file mode 100644 index 00000000000..0c5c80f9beb Binary files /dev/null and b/sound/effects/creatures/bear_quiet_3.ogg differ diff --git a/sound/effects/creatures/bear_quiet_4.ogg b/sound/effects/creatures/bear_quiet_4.ogg new file mode 100644 index 00000000000..a738dd4d9af Binary files /dev/null and b/sound/effects/creatures/bear_quiet_4.ogg differ diff --git a/sound/effects/creatures/bear_quiet_5.ogg b/sound/effects/creatures/bear_quiet_5.ogg new file mode 100644 index 00000000000..d18da0a7d0b Binary files /dev/null and b/sound/effects/creatures/bear_quiet_5.ogg differ diff --git a/sound/effects/creatures/bear_quiet_6.ogg b/sound/effects/creatures/bear_quiet_6.ogg new file mode 100644 index 00000000000..c415de11c06 Binary files /dev/null and b/sound/effects/creatures/bear_quiet_6.ogg differ diff --git a/sound/weapons/beartrap_shut.ogg b/sound/weapons/beartrap_shut.ogg new file mode 100644 index 00000000000..a8230a838b5 Binary files /dev/null and b/sound/weapons/beartrap_shut.ogg differ