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