mirror of
https://github.com/goonstation/goonstation-2020.git
synced 2026-08-27 14:06:13 +01:00
1065 lines
31 KiB
Plaintext
1065 lines
31 KiB
Plaintext
// p much straight up copied from secbot code =I
|
|
|
|
/obj/critter/
|
|
name = "critter"
|
|
desc = "you shouldnt be able to see this"
|
|
icon = 'icons/misc/critter.dmi'
|
|
var/living_state = null
|
|
var/dead_state = null
|
|
layer = 5.0
|
|
density = 1
|
|
anchored = 0
|
|
flags = FPRINT | CONDUCT | USEDELAY | FLUID_SUBMERGE
|
|
event_handler_flags = USE_PROXIMITY | USE_FLUID_ENTER | USE_CANPASS
|
|
var/is_template = 0
|
|
var/alive = 1
|
|
var/health = 10
|
|
|
|
// "sleeping" is a special state that sleeps for 10 cycles, wakes up, sleeps again unless someone is found
|
|
// "hibernating" is another special state where it does nothing unless explicitly woken up
|
|
var/task = "thinking"
|
|
|
|
var/list/followed_path = null
|
|
var/followed_path_retries = 0
|
|
var/followed_path_retry_target = null
|
|
var/follow_path_blindly = 0
|
|
|
|
var/report_state = 0
|
|
var/quality_name = null
|
|
var/mobile = 1
|
|
var/aggressive = 0
|
|
var/defensive = 0
|
|
var/wanderer = 1
|
|
var/opensdoors = 0
|
|
var/frustration = 0
|
|
var/last_found = null
|
|
var/target = null
|
|
var/oldtarget_name = null
|
|
var/target_lastloc = null
|
|
var/atkcarbon = 0
|
|
var/atksilicon = 0
|
|
var/atcritter = 0
|
|
var/atkintangible = 0
|
|
var/attack = 0
|
|
var/attacking = 0
|
|
var/atk_delay = 25 // how long before a critter will attack again
|
|
var/crit_chance = 5
|
|
var/atk_diseases = null // can be a path to a disease or a list (lists will be picked from)
|
|
var/atk_disease_prob = 0 // if atk_diseases isn't null, this is how likely it is that a disease will be contracted
|
|
var/atk_brute_amt = 1 // how much brute to do per attack
|
|
var/atk_burn_amt = 0 // how much burn to do per attack
|
|
var/crit_brute_amt = 2
|
|
var/crit_burn_amt = 0
|
|
var/twitching = 0
|
|
var/steps = 0
|
|
var/firevuln = 1
|
|
var/brutevuln = 1
|
|
var/miscvuln = 0.2
|
|
var/attack_range = 1 // how many tiles away it will attack from
|
|
var/seekrange = 7 // how many tiles away it will look for a target
|
|
var/list/friends = list() // used for tracking hydro-grown monsters's creator
|
|
var/attacker = null // used for defensive tracking
|
|
var/angertext = "charges at" // comes between critter name and target name
|
|
var/pet_text = "pets"
|
|
var/death_text = "%src% dies!"
|
|
var/atk_text = "bites"
|
|
var/chase_text = "leaps on"
|
|
var/crit_text = "savagely bites"
|
|
var/hitsound = null
|
|
var/flying = 0
|
|
//flags = OPENCONTAINER
|
|
var/sleeping = 0 //countdown, when hits 0 does a wake check
|
|
var/sleep_check = 10 //countdown, when hits 0 does a sleep check
|
|
var/hibernate_check = 2
|
|
var/wander_check = 0
|
|
var/sleeping_icon_state = null
|
|
var/mob/living/wrangler = null
|
|
|
|
var/butcherable = 0
|
|
var/meat_type = /obj/item/reagent_containers/food/snacks/ingredient/meat/mysterymeat
|
|
var/name_the_meat = 1
|
|
|
|
var/generic = 1 // if yes, critter can be randomized a bit
|
|
var/max_quality = 100
|
|
var/min_quality = -100
|
|
|
|
var/can_revive = 1 // resurrectable with strange reagent
|
|
|
|
var/skinresult = null //type path of hide/leather item from skinning
|
|
var/max_skins = 1 //How many skins you can get at most from this critter. It's 1 to the max amound defined here. random.
|
|
var/muted = 0 // shut UP
|
|
|
|
var/chases_food = 0
|
|
var/health_gain_from_food = 0
|
|
var/obj/item/reagent_containers/food/snacks/food_target = null
|
|
var/eat_text = "nibbles at"
|
|
var/feed_text = null
|
|
|
|
var/scavenger = 0
|
|
var/mob/living/corpse_target = null
|
|
|
|
var/area/registered_area = null //the area this critter is registered in
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// INTRUDER VARS AND PROCS
|
|
var/selected = 0
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
proc/tokenized_message(var/message, var/target)
|
|
if (!message || !length(message))
|
|
return
|
|
var/msg = replacetext(message, "%src%", "<b>[src]</b>")
|
|
msg = replacetext(msg, "%target%", "[target]")
|
|
src.visible_message("<span style='color:red'>[msg]</span>")
|
|
|
|
proc/report_spawn()
|
|
if (!report_state)
|
|
report_state = 1
|
|
if (src in gauntlet_controller.gauntlet)
|
|
gauntlet_controller.increaseCritters(src)
|
|
if (src in colosseum_controller.colosseum)
|
|
colosseum_controller.increaseCritters(src)
|
|
|
|
proc/report_death()
|
|
if (report_state == 1)
|
|
report_state = 0
|
|
if (src in gauntlet_controller.gauntlet)
|
|
gauntlet_controller.decreaseCritters(src)
|
|
if (src in colosseum_controller.colosseum)
|
|
colosseum_controller.decreaseCritters(src)
|
|
|
|
serialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
|
|
..()
|
|
F["[path].aggressive"] << src.aggressive
|
|
F["[path].atkcarbon"] << src.atkcarbon
|
|
F["[path].atksilicon"] << src.atksilicon
|
|
F["[path].health"] << src.health
|
|
F["[path].opensdoors"] << src.opensdoors
|
|
F["[path].wanderer"] << src.wanderer
|
|
F["[path].mobile"] << src.mobile
|
|
F["[path].brutevuln"] << src.brutevuln
|
|
F["[path].firevuln"] << src.firevuln
|
|
|
|
deserialize(var/savefile/F, var/path, var/datum/sandbox/sandbox)
|
|
. = ..()
|
|
F["[path].aggressive"] >> src.aggressive
|
|
F["[path].atkcarbon"] >> src.atkcarbon
|
|
F["[path].atksilicon"] >> src.atksilicon
|
|
F["[path].health"] >> src.health
|
|
F["[path].opensdoors"] >> src.opensdoors
|
|
F["[path].wanderer"] >> src.wanderer
|
|
F["[path].mobile"] >> src.mobile
|
|
F["[path].brutevuln"] >> src.brutevuln
|
|
F["[path].firevuln"] >> src.firevuln
|
|
|
|
clone()
|
|
var/obj/critter/C = ..()
|
|
C.mobile = mobile
|
|
C.aggressive = aggressive
|
|
C.defensive = defensive
|
|
C.atkcarbon = atkcarbon
|
|
C.atksilicon = atksilicon
|
|
C.health = health
|
|
C.wanderer = wanderer
|
|
C.brutevuln = brutevuln
|
|
C.firevuln = firevuln
|
|
return C
|
|
|
|
proc/wake_from_hibernation()
|
|
if(task != "hibernating") return
|
|
//DEBUG_MESSAGE("[src] woke from hibernation at [showCoords(src.x, src.y, src.z)] in [registered_area ? registered_area.name : "nowhere"] due to [usr ? usr : "some mysterious fucking reason"]")
|
|
//Ok, now we look to see if we should get murdlin'
|
|
task = "sleeping"
|
|
hibernate_check = 20 //20 sleep_checks
|
|
do_wake_check(1)
|
|
|
|
if(registered_area)
|
|
registered_area.registered_critters -= src
|
|
registered_area = null
|
|
|
|
anchored = initial(anchored)
|
|
//critters |= src //Resume processing this critter
|
|
|
|
|
|
proc/hibernate()
|
|
registered_area = get_area(src)
|
|
hibernate_check = 20 //Reset this counter in case of failure
|
|
if(registered_area)
|
|
task = "hibernating"
|
|
registered_area.registered_critters |= src
|
|
anchored = 1
|
|
//DEBUG_MESSAGE("[src] started hibernating at [showCoords(src.x, src.y, src.z)] in [registered_area ? registered_area.name : "nowhere"].")
|
|
//critters -= src //Stop processing this critter
|
|
|
|
|
|
HasProximity(atom/movable/AM as mob|obj)
|
|
if(task == "hibernating" && ismob(AM))
|
|
var/mob/living/M = AM
|
|
if(M.client) wake_from_hibernation()
|
|
|
|
..()
|
|
|
|
set_loc(var/atom/newloc)
|
|
..()
|
|
wake_from_hibernation() //Critters hibernate lightly enough to wake up when moved
|
|
|
|
proc/on_revive()
|
|
return
|
|
|
|
proc/on_sleep()
|
|
|
|
proc/on_wake()
|
|
var/area/A = get_area(src)
|
|
if(A) A.wake_critters() //HLEP!
|
|
|
|
proc/on_grump()
|
|
|
|
attackby(obj/item/W as obj, mob/living/user as mob)
|
|
..()
|
|
if (!src.alive)
|
|
if (src.skinresult && max_skins)
|
|
if (istype(W, /obj/item/circular_saw) || istype(W, /obj/item/kitchen/utensil/knife) || istype(W, /obj/item/scalpel) || istype(W, /obj/item/raw_material/shard) || istype(W, /obj/item/sword) || istype(W, /obj/item/saw) || issnippingtool(W))
|
|
|
|
for(var/i, i<rand(1, max_skins), i++)
|
|
new src.skinresult (src.loc)
|
|
|
|
src.skinresult = null
|
|
|
|
user.visible_message("<span style=\"color:red\">[user] skins [src].</span>","You skin [src].")
|
|
|
|
if (src.butcherable && (istype(W, /obj/item/kitchen/utensil/knife) || istype(W, /obj/item/knife_butcher)))
|
|
user.visible_message("<span style=\"color:red\">[user] butchers [src].[src.butcherable == 2 ? "<b>WHAT A MONSTER</b>" : null]","You butcher [src].</span>")
|
|
|
|
var/i = rand(2,4)
|
|
var/transfer = src.reagents.total_volume / i
|
|
|
|
while (i-- > 0)
|
|
var/obj/item/reagent_containers/food/newmeat = new meat_type
|
|
newmeat.set_loc(src.loc)
|
|
src.reagents.trans_to(newmeat, transfer)
|
|
if (name_the_meat)
|
|
newmeat.name = "[src.name] meat"
|
|
newmeat.real_name = newmeat.name
|
|
qdel (src)
|
|
return
|
|
..()
|
|
return
|
|
|
|
if (src.health_gain_from_food && (istype(W, /obj/item/reagent_containers/food/snacks) || istype(W, /obj/item/seed)))
|
|
user.visible_message("<b>[user]</b> feeds [W] to [src]!","You feed [W] to [src].")
|
|
if (src.feed_text)
|
|
src.visible_message("[src] [src.feed_text]")
|
|
src.health += src.health_gain_from_food
|
|
qdel(W)
|
|
return
|
|
|
|
user.lastattacked = src
|
|
attack_particle(user,src)
|
|
|
|
var/attack_force = 0
|
|
var/damage_type = "brute"
|
|
if (istype(W, /obj/item/artifact/melee_weapon))
|
|
var/datum/artifact/melee/ME = W.artifact
|
|
attack_force = ME.dmg_amount
|
|
damage_type = ME.damtype
|
|
else
|
|
attack_force = W.force
|
|
damage_type = W.damtype
|
|
|
|
//Simplified weapon properties for critters. Fuck this shit.
|
|
if(W.getProperty("searing"))
|
|
damage_type = "fire"
|
|
attack_force += W.getProperty("searing")
|
|
|
|
if(W.hasProperty("vorpal"))
|
|
attack_force += W.getProperty("vorpal")
|
|
|
|
if(W.hasProperty("unstable"))
|
|
attack_force = rand(attack_force, round(attack_force * W.getProperty("unstable")))
|
|
|
|
if(W.hasProperty("frenzy"))
|
|
SPAWN_DBG(0)
|
|
var/frenzy = W.getProperty("frenzy")
|
|
W.click_delay -= frenzy
|
|
sleep(30)
|
|
W.click_delay += frenzy
|
|
///////////////////////////
|
|
|
|
if (!attack_force)
|
|
return
|
|
|
|
if (src.sleeping)
|
|
sleeping = 0
|
|
on_wake()
|
|
|
|
switch(damage_type)
|
|
if("fire")
|
|
src.health -= attack_force * (src.firevuln + W.getProperty("piercing")) //Extremely half assed piercing for critters
|
|
if("brute")
|
|
src.health -= attack_force * (src.brutevuln + W.getProperty("piercing"))
|
|
else
|
|
src.health -= attack_force * (src.miscvuln + W.getProperty("piercing"))
|
|
|
|
if (src.alive && src.health <= 0) src.CritterDeath()
|
|
|
|
if (hitsound)
|
|
playsound(src, hitsound, 50, 1)
|
|
if (W && W.hitsound)
|
|
playsound(src,W.hitsound,50,1)
|
|
|
|
if (src.alive)
|
|
on_damaged(user)
|
|
|
|
if(W.hasProperty("impact"))
|
|
var/turf/T = get_edge_target_turf(src, get_dir(user, src))
|
|
src.throw_at(T, 2, W.getProperty("impact"))
|
|
|
|
if (src.defensive)
|
|
if (src.target == user && src.task == "attacking")
|
|
if (prob(50 - attack_force))
|
|
return
|
|
else
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> flinches!</span>")
|
|
src.target = user
|
|
src.oldtarget_name = user.name
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> [src.angertext] [user.name]!</span>")
|
|
src.task = "chasing"
|
|
on_grump()
|
|
|
|
|
|
proc/on_damaged(mob/user)
|
|
if(registered_area) //In case some butt fiddles with a hibernating critter
|
|
registered_area.wake_critters()
|
|
return
|
|
|
|
proc/on_pet()
|
|
if(registered_area) //In case some nice person fiddles with a hibernating critter
|
|
registered_area.wake_critters()
|
|
return
|
|
|
|
attack_hand(var/mob/user as mob)
|
|
..()
|
|
if (!src.alive)
|
|
..()
|
|
return
|
|
|
|
if (src.sleeping)
|
|
sleeping = 0
|
|
on_wake()
|
|
|
|
user.lastattacked = src
|
|
attack_particle(user,src)
|
|
|
|
if (user.a_intent == INTENT_HARM)
|
|
src.health -= rand(1,2) * src.brutevuln
|
|
src.visible_message("<span style=\"color:red\"><b>[user]</b> punches [src]!</span>")
|
|
playsound(src.loc, pick('sound/impact_sounds/Generic_Punch_2.ogg','sound/impact_sounds/Generic_Punch_3.ogg','sound/impact_sounds/Generic_Punch_4.ogg','sound/impact_sounds/Generic_Punch_5.ogg'), 100, 1)
|
|
attack_twitch(user)
|
|
hit_twitch(src)
|
|
if (hitsound)
|
|
playsound(src, hitsound, 50, 1)
|
|
if (src.alive && src.health <= 0) src.CritterDeath()
|
|
if (src.alive)
|
|
on_damaged(user)
|
|
if (src.defensive)
|
|
if (src.target == user && src.task == "attacking")
|
|
if (prob(50))
|
|
return
|
|
else
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> flinches!</span>")
|
|
src.target = user
|
|
src.oldtarget_name = user.name
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> [src.angertext] [user.name]!</span>")
|
|
src.task = "chasing"
|
|
on_grump()
|
|
else
|
|
var/pet_verb = islist(src.pet_text) ? pick(src.pet_text) : src.pet_text
|
|
src.visible_message("<span style=\"color:blue\"><b>[user]</b> [pet_verb] [src]!</span>", 1)
|
|
on_pet()
|
|
|
|
proc/patrol_step()
|
|
if (!mobile)
|
|
return
|
|
var/turf/moveto = locate(src.x + rand(-1,1),src.y + rand(-1, 1),src.z)
|
|
if(isturf(moveto) && !moveto.density) patrol_to(moveto)
|
|
if(src.aggressive) seek_target()
|
|
steps += 1
|
|
//if (steps == rand(5,20)) src.task = "thinking" // what the fuck is wrong with you
|
|
if (steps == wander_check)
|
|
src.task = "thinking"
|
|
wander_check = rand(5,20)
|
|
|
|
proc/patrol_to(var/towhat)
|
|
step_to(src, towhat)
|
|
|
|
Bump(M as mob|obj)
|
|
SPAWN_DBG(0)
|
|
if (istype(M, /obj/machinery/door/))
|
|
var/obj/machinery/door/D = M
|
|
D.Bumped(src) // Doesn't call that automatically for some inexplicable reason.
|
|
else if ((isliving(M)) && (!src.anchored))
|
|
src.set_loc(M:loc)
|
|
src.frustration = 0
|
|
return
|
|
return
|
|
|
|
bullet_act(var/obj/projectile/P)
|
|
var/damage = 0
|
|
damage = round((P.power*P.proj_data.ks_ratio), 1.0)
|
|
|
|
if (src.sleeping)
|
|
sleeping = 0
|
|
on_wake()
|
|
|
|
if(src.material) src.material.triggerOnBullet(src, src, P)
|
|
|
|
switch(P.proj_data.damage_type)
|
|
if(D_KINETIC,D_PIERCING,D_SLASHING)
|
|
src.health -= (damage*brutevuln)
|
|
if(D_ENERGY)
|
|
src.health -= damage
|
|
if(D_BURNING)
|
|
src.health -= (damage*firevuln)
|
|
if(D_RADIOACTIVE)
|
|
src.health -= 1
|
|
if(D_TOXIC)
|
|
src.health -= 1
|
|
|
|
on_damaged(usr)
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
|
|
ex_act(severity)
|
|
if (src.sleeping)
|
|
sleeping = 0
|
|
on_wake()
|
|
|
|
on_damaged()
|
|
|
|
switch(severity)
|
|
if(1.0)
|
|
src.health -= 200
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
return
|
|
if(2.0)
|
|
src.health -= 75
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
return
|
|
else
|
|
src.health -= 25
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
return
|
|
return
|
|
|
|
meteorhit()
|
|
src.health -= 150 // no more instakill
|
|
on_damaged()
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
return
|
|
|
|
proc/check_health()
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
|
|
blob_act(var/power)
|
|
src.health -= power
|
|
on_damaged()
|
|
if (src.health <= 0)
|
|
src.CritterDeath()
|
|
return
|
|
|
|
proc/follow_path()
|
|
if (!mobile)
|
|
task = "thinking"
|
|
return
|
|
if (src.loc == followed_path_retry_target)
|
|
logTheThing("debug", null, null, "<B>Marquesas/Critter Astar:</b> Critter arrived at target location.")
|
|
task = "thinking"
|
|
followed_path = null
|
|
followed_path_retries = 0
|
|
followed_path_retry_target = null
|
|
else if (!followed_path)
|
|
logTheThing("debug", null, null, "<B>Marquesas/Critter Astar:</b> Critter following empty path.")
|
|
task = "thinking"
|
|
else if (!followed_path.len)
|
|
logTheThing("debug", null, null, "<B>Marquesas/Critter Astar:</b> Critter path ran out.")
|
|
task = "thinking"
|
|
else
|
|
var/turf/nextturf = followed_path[1]
|
|
var/retry = 0
|
|
if (nextturf.density)
|
|
retry = 1
|
|
if (!retry)
|
|
for (var/obj/O in nextturf)
|
|
if (O.density)
|
|
retry = 1
|
|
break
|
|
if (retry)
|
|
if (!followed_path_retry_target)
|
|
task = "thinking"
|
|
else if (followed_path_retries > 10)
|
|
logTheThing("debug", null, null, "<B>Marquesas/Critter Astar:</b> Critter out of retries.")
|
|
task = "thinking"
|
|
else
|
|
logTheThing("debug", null, null, "<B>Marquesas/Critter Astar:</b> Hit a wall, retrying.")
|
|
followed_path = findPath(src.loc, followed_path_retry_target)
|
|
return
|
|
else
|
|
step_to(src, nextturf)
|
|
followed_path -= nextturf
|
|
if (!follow_path_blindly)
|
|
seek_target()
|
|
|
|
proc/do_wake_check(var/force = 0)
|
|
if(!force && sleeping-- > 0) return
|
|
|
|
var/waking = 0
|
|
|
|
for (var/client/C)
|
|
var/mob/M = C.mob
|
|
if (M && src.z == M.z && get_dist(src,M) <= 10)
|
|
if (isliving(M))
|
|
waking = 1
|
|
break
|
|
|
|
//for(var/mob/living/M in range(10, src))
|
|
// if(M.client)
|
|
// waking = 1
|
|
// break
|
|
|
|
if (!waking)
|
|
if (get_area(src) == colosseum_controller.colosseum)
|
|
waking = 1
|
|
|
|
if(waking)
|
|
hibernate_check = 20
|
|
sleeping = 0
|
|
task = "thinking"
|
|
on_wake()
|
|
if (sleeping_icon_state)
|
|
src.icon_state = initial(src.icon_state)
|
|
return 1
|
|
else
|
|
sleeping = 10
|
|
if(--hibernate_check <= 0)
|
|
src.hibernate()
|
|
return 0
|
|
|
|
proc/do_sleep_check(var/force = 0)
|
|
if(!force && sleep_check-- > 0) return
|
|
|
|
var/stay_awake = 0
|
|
|
|
|
|
for (var/client/C)
|
|
var/mob/M = C.mob
|
|
if (M && src.z == M.z && get_dist(src,M) <= 10)
|
|
if (isliving(M))
|
|
stay_awake = 1
|
|
break
|
|
|
|
//for(var/mob/living/M in range(10, src))
|
|
// if(M.client)
|
|
// stay_awake = 1
|
|
// break
|
|
|
|
if(!stay_awake)
|
|
sleeping = 10
|
|
on_sleep()
|
|
if (sleeping_icon_state)
|
|
src.icon_state = sleeping_icon_state
|
|
task = "sleeping"
|
|
return 0
|
|
else
|
|
sleep_check = 10
|
|
|
|
proc/process()
|
|
if (is_template || task == "hibernating")
|
|
return 0
|
|
if (!src.alive) return 0
|
|
|
|
if(sleeping > 0)
|
|
sleeping--
|
|
return 0
|
|
|
|
check_health()
|
|
|
|
if(task == "following path")
|
|
follow_path()
|
|
SPAWN_DBG(1 SECOND)
|
|
follow_path()
|
|
else if(task == "sleeping")
|
|
do_wake_check()
|
|
else
|
|
do_sleep_check()
|
|
|
|
return ai_think()
|
|
|
|
proc/ai_think()
|
|
switch(task)
|
|
if ("thinking")
|
|
src.attack = 0
|
|
src.target = null
|
|
|
|
walk_to(src,0)
|
|
|
|
if (src.aggressive) seek_target()
|
|
if (src.wanderer && src.mobile && !src.target) src.task = "wandering"
|
|
|
|
if ("chasing")
|
|
if (src.frustration >= 8)
|
|
src.target = null
|
|
src.food_target = null
|
|
src.corpse_target = null
|
|
src.last_found = world.time
|
|
src.frustration = 0
|
|
src.task = "thinking"
|
|
if (mobile)
|
|
walk_to(src,0)
|
|
|
|
var/atom/current_target
|
|
if (src.target)
|
|
current_target = src.target
|
|
else if (src.scavenger && src.corpse_target)
|
|
current_target = src.corpse_target
|
|
else if (src.chases_food && src.food_target)
|
|
current_target = src.food_target
|
|
|
|
if (current_target)
|
|
if (get_dist(src, current_target) <= src.attack_range)
|
|
if (current_target == src.corpse_target)
|
|
src.task = "scavenging"
|
|
else if (current_target == src.food_target)
|
|
src.task = "eating"
|
|
else
|
|
var/turf/t = get_turf(current_target)
|
|
if( !t.loc:sanctuary || !istype(current_target, /mob) )
|
|
ChaseAttack(current_target)
|
|
src.task = "attacking"
|
|
src.anchored = 1
|
|
src.target_lastloc = current_target.loc
|
|
else
|
|
if (mobile)
|
|
var/turf/olddist = get_dist(src, current_target)
|
|
walk_to(src, current_target,1,4)
|
|
if ((get_dist(src, current_target)) >= (olddist))
|
|
src.frustration++
|
|
else
|
|
src.frustration = 0
|
|
else
|
|
if (get_dist(src, current_target) > attack_range)
|
|
src.frustration++
|
|
else
|
|
src.frustration = 0
|
|
else
|
|
src.task = "thinking"
|
|
|
|
if ("chasing food")
|
|
if (!src.chases_food || src.food_target == null)
|
|
src.task = "thinking"
|
|
else if (get_dist(src, src.food_target) <= src.attack_range)
|
|
src.task = "eating"
|
|
else if (src.mobile)
|
|
walk_to(src, src.food_target,1,4)
|
|
|
|
if ("eating")
|
|
if (get_dist(src, src.food_target) > src.attack_range)
|
|
src.task = "chasing"// food"
|
|
else
|
|
src.task = "eating2"
|
|
|
|
if ("eating2")
|
|
if (get_dist(src, src.food_target) > src.attack_range)
|
|
src.task = "chasing"// food"
|
|
else
|
|
src.visible_message("<b>[src]</b> [src.eat_text] [src.food_target].")
|
|
playsound(src.loc,"sound/items/eatfood.ogg", rand(10,50), 1)
|
|
if (food_target)
|
|
if (food_target.amount) src.food_target.amount-- //ZeWaka: Fix for null. amount
|
|
if (food_target.reagents && food_target.reagents.total_volume > 0 && src.reagents.total_volume < 30)
|
|
food_target.reagents.trans_to(src, 5)
|
|
if (src.food_target != null && src.food_target.amount <= 0)
|
|
src.food_target.loc = null
|
|
SPAWN_DBG(1 SECOND)
|
|
qdel(src.food_target)
|
|
src.task = "thinking"
|
|
src.food_target = null
|
|
src.health += src.health_gain_from_food
|
|
|
|
if ("chasing corpse")
|
|
if (!src.scavenger || src.corpse_target == null)
|
|
src.task = "thinking"
|
|
else if (get_dist(src, src.corpse_target) <= src.attack_range)
|
|
src.task = "scavenging"
|
|
else if (src.mobile)
|
|
walk_to(src, src.corpse_target,1,4)
|
|
|
|
if ("scavenging")
|
|
if (!src.scavenger || src.corpse_target == null)
|
|
src.task = "thinking"
|
|
if (get_dist(src, src.corpse_target) > src.attack_range)
|
|
src.task = "chasing"// corpse"
|
|
var/mob/living/carbon/human/C = src.corpse_target
|
|
src.visible_message("<b>[src]</b> gnaws some meat off [src.corpse_target]'s body!")
|
|
playsound(src.loc,"sound/items/eatfood.ogg", rand(10,50), 1)
|
|
sleep(rand(20,30))
|
|
if (prob(20))
|
|
C.decomp_stage += 1
|
|
C.update_body()
|
|
C.update_face()
|
|
switch (C.decomp_stage)
|
|
if (4)
|
|
src.visible_message("<span class='combat'><b>[src]</b> tears the last piece of meat off [src.corpse_target]!</span>")
|
|
src.task = "thinking"
|
|
src.corpse_target = null
|
|
if (3)
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> has eaten most of the flesh from [src.corpse_target]'s bones!")
|
|
if (2)
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> has eaten enough of [src.corpse_target] that their bones are showing!")
|
|
|
|
if ("attacking")
|
|
// see if he got away
|
|
if ((get_dist(src, src.target) > src.attack_range) || ((src.target:loc != src.target_lastloc)))
|
|
src.anchored = initial(src.anchored)
|
|
src.task = "chasing"
|
|
else
|
|
if (get_dist(src, src.target) <= src.attack_range)
|
|
var/mob/living/carbon/M = src.target
|
|
if (!src.attacking)
|
|
var/turf/t = get_turf(src.target)
|
|
if( !t.loc:sanctuary || !istype(M) )
|
|
CritterAttack(src.target)
|
|
if (src)
|
|
attack_twitch(src)
|
|
if (src.target)
|
|
hit_twitch(src.target)
|
|
if (!src.aggressive)
|
|
src.task = "thinking"
|
|
src.target = null
|
|
src.anchored = initial(src.anchored)
|
|
src.last_found = world.time
|
|
src.frustration = 0
|
|
src.attacking = 0
|
|
else
|
|
if(M!=null)
|
|
if (M.health < 0)
|
|
src.task = "thinking"
|
|
src.target = null
|
|
src.anchored = initial(src.anchored)
|
|
src.last_found = world.time
|
|
src.frustration = 0
|
|
src.attacking = 0
|
|
else
|
|
src.anchored = initial(src.anchored)
|
|
src.attacking = 0
|
|
src.task = "chasing"
|
|
if ("wandering")
|
|
patrol_step()
|
|
return 1
|
|
|
|
|
|
New()
|
|
if(!src.reagents) src.create_reagents(100)
|
|
wander_check = rand(5,20)
|
|
critters += src
|
|
report_spawn()
|
|
if(generic)
|
|
src.quality = rand(min_quality,max_quality)
|
|
var/nickname = getCritterQuality(src.quality)
|
|
if(nickname)
|
|
src.quality_name = nickname
|
|
src.name = "[nickname] [src.name]"
|
|
..()
|
|
|
|
disposing()
|
|
critters -= src
|
|
if(registered_area)
|
|
registered_area.registered_critters -= src
|
|
..()
|
|
|
|
proc/seek_target()
|
|
src.anchored = initial(src.anchored)
|
|
if (src.target)
|
|
src.task = "chasing"
|
|
return
|
|
|
|
if (src.scavenger)
|
|
if (src.corpse_target)
|
|
src.task = "chasing"// corpse"
|
|
return
|
|
var/list/visible = new()
|
|
for (var/mob/living/carbon/human/H in view (src.seekrange,src))
|
|
if (isdead(H) && H.decomp_stage <= 3 && !H:bioHolder.HasEffect("husk")) //is dead, isn't a skeleton, isn't a grody husk
|
|
visible.Add(H)
|
|
else continue
|
|
if (src.corpse_target && visible.Find(src.corpse_target))
|
|
src.task = "chasing"// corpse"
|
|
return
|
|
else
|
|
src.task = "thinking"
|
|
if (visible.len)
|
|
src.corpse_target = visible[1]
|
|
src.visible_message("<span style=\"color:red\"><b>[src]</b> eyes [src.corpse_target.name] hungrily!</span>")
|
|
src.task = "chasing"// corpse"
|
|
|
|
if (src.chases_food)
|
|
var/list/visible = new()
|
|
for (var/obj/item/reagent_containers/food/snacks/S in view(src.seekrange,src))
|
|
visible.Add(S)
|
|
if (src.food_target && visible.Find(src.food_target))
|
|
src.task = "chasing"// food"
|
|
return
|
|
else
|
|
src.task = "thinking"
|
|
if (visible.len)
|
|
src.food_target = visible[1]
|
|
src.task = "chasing"// food"
|
|
|
|
for (var/mob/living/C in hearers(src.seekrange,src))
|
|
//if (src.target)
|
|
// src.task = "chasing"
|
|
// break
|
|
if ((C.name == src.oldtarget_name) && (world.time < src.last_found + 100)) continue
|
|
if (iscarbon(C) && !src.atkcarbon) continue
|
|
if (issilicon(C) && !src.atksilicon) continue
|
|
if (isintangible(C) && !src.atkintangible) continue
|
|
if (C.health < 0) continue
|
|
if(!filter_target(C)) continue
|
|
if (C in src.friends) continue
|
|
if (ishuman(C))
|
|
if (C:bioHolder && C:bioHolder.HasEffect("revenant") || C:bioHolder && C:bioHolder.HasEffect("husk"))
|
|
continue
|
|
if (C.name == src.attacker) src.attack = 1
|
|
if (iscarbon(C) && src.atkcarbon) src.attack = 1
|
|
if (issilicon(C) && src.atksilicon) src.attack = 1
|
|
|
|
if (src.attack)
|
|
src.target = C
|
|
src.oldtarget_name = C.name
|
|
src.visible_message("<span class='combat'><b>[src]</b> [src.angertext] [C.name]!</span>")
|
|
src.task = "chasing"
|
|
on_grump()
|
|
break
|
|
else
|
|
continue
|
|
|
|
proc/filter_target(var/mob/living/M) //Better to have specialized filters in it's own proc rather than overriding the main seek_target thing imo
|
|
return 1
|
|
|
|
proc/CritterDeath()
|
|
if (!src.alive) return
|
|
if (!dead_state)
|
|
src.icon_state = "[initial(src.icon_state)]-dead"
|
|
else
|
|
src.icon_state = dead_state
|
|
src.alive = 0
|
|
src.anchored = 0
|
|
src.set_density(0)
|
|
walk_to(src,0)
|
|
report_death()
|
|
src.tokenized_message(death_text)
|
|
|
|
proc/ChaseAttack(mob/M)
|
|
src.visible_message("<span class='combat'><B>[src]</B> [src.chase_text] [src.target]!</span>")
|
|
if (ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
H.was_harmed(src)
|
|
//playsound(src.loc, "sound/impact_sounds/Generic_Hit_1.ogg", 50, 1, -1)
|
|
|
|
proc/CritterAttack(mob/M)
|
|
src.attacking = 1
|
|
if (prob(src.crit_chance))
|
|
CritAttack(M)
|
|
else
|
|
src.visible_message("<span class='combat'><B>[src]</B> [src.atk_text] [src.target]!</span>")
|
|
random_brute_damage(src.target, src.atk_brute_amt)
|
|
random_burn_damage(src.target, src.atk_burn_amt)
|
|
if (ishuman(M))
|
|
var/mob/living/carbon/human/H = M
|
|
H.was_harmed(src)
|
|
SPAWN_DBG(src.atk_delay)
|
|
src.attacking = 0
|
|
if (iscarbon(M) && src.atk_diseases && prob(src.atk_disease_prob))
|
|
var/mob/living/carbon/C = M
|
|
if (islist(src.atk_diseases))
|
|
C.contract_disease(pick(src.atk_diseases), null, null, 1) // path, name, strain, bypass resist
|
|
else if (ispath(src.atk_diseases))
|
|
C.contract_disease(src.atk_diseases, null, null, 1) // path, name, strain, bypass resist
|
|
|
|
proc/CritAttack(mob/M)
|
|
src.visible_message("<span class='combat'><B>[src]</B> [src.crit_text] [src.target]!</span>")
|
|
random_brute_damage(src.target, src.crit_brute_amt)
|
|
random_burn_damage(src.target, src.crit_burn_amt)
|
|
|
|
proc/getCritterQuality(var/quality)
|
|
switch(quality)
|
|
if(-INFINITY to -100)
|
|
return "abysmal"
|
|
if(-100 to -99)
|
|
return "worst"
|
|
if(-98 to -91)
|
|
return pick("shameful", "hideous", "grotesque", "vile", "misshapen", "garbage", "illegal", "dreadful", "god-awful")
|
|
if(-90 to -75)
|
|
return pick("ugly", "grody", "stinky", "awful", "diseased", "filthy", "lousy", "overweight", "broken", "unfortunate", "unacceptable", "sad", "slipshod", "crappy", "faulty", "fraudulent")
|
|
if(-74 to -50)
|
|
return pick("shabby", "mangy", "dented", "dusty", "sub-par", "slightly less nice", "weird", "crummy", "busted", "funky", "bad news", "deficient", "cruddy", "icky", "not good")
|
|
if(-49 to 50)
|
|
return ""
|
|
if(51 to 64)
|
|
return pick("nice", "cute", "healthy", "buff", "strong")
|
|
if(65 to 74)
|
|
return pick("suave", "buff", "robust", "handsome", "fine", "slightly nicer", "pretty good")
|
|
if(75 to 85)
|
|
return pick("high-class", "great", "burly", "superb", "excellent", "admirable")
|
|
if(86 to 90)
|
|
return pick("majestic", "fantastic", "high-quality", "marvelous", "deluxe")
|
|
if(91 to 94)
|
|
return pick("show-quality", "finest", "superb")
|
|
if(95 to 97)
|
|
return "champion"
|
|
if(98 to 99)
|
|
src.setMaterial(getMaterial("gold"), appearance = 0, setname = 0)
|
|
return "best"
|
|
if(100 to INFINITY)
|
|
src.setMaterial(getMaterial("gold"), appearance = 0, setname = 0)
|
|
return "mystical"
|
|
else
|
|
return "odd"
|
|
return
|
|
|
|
proc/Shoot(var/target, var/start, var/user, var/bullet = 0)
|
|
if(target == start)
|
|
return
|
|
// playsound(user, "mp5gunshot.ogg", 100, 1)
|
|
/* if(bullet == 0)
|
|
A = new /obj/bullet/mpbullet( user:loc )
|
|
else if(bullet == 1)
|
|
playsound(user, "sound/weapons/shotgunshot.ogg", 100, 1)
|
|
A = new /obj/bullet/slug( user:loc )
|
|
else if(bullet == 2)
|
|
playsound(user, "fivegunshot.ogg", 100, 1)
|
|
A = new /obj/bullet/medbullet( user:loc )*/
|
|
if (!isturf(target))
|
|
return
|
|
// FUCK YOU WHOEVER IS USING THIS
|
|
// FUCK YOU
|
|
shoot_projectile_ST(src, new/datum/projectile/bullet/revolver_38(), target)
|
|
return
|
|
|
|
|
|
/obj/item/reagent_containers/food/snacks/ingredient/egg/critter
|
|
name = "egg"
|
|
desc = "Looks like this could hatch into something."
|
|
icon_state = "critter_egg"
|
|
var/critter_name = null
|
|
var/hatched = 0
|
|
var/critter_type = null
|
|
var/warm_count = 10 // how many times you gotta warm it before it hatches
|
|
var/critter_reagent = null
|
|
rand_pos = 1
|
|
|
|
New()
|
|
..()
|
|
var/amt_to_mod = round(src.warm_count / 10, 1)
|
|
src.warm_count += rand(-amt_to_mod,amt_to_mod)
|
|
src.color = random_saturated_hex_color(1)
|
|
if (src.reagents && src.critter_reagent)
|
|
src.reagents.add_reagent(src.critter_reagent, 10)
|
|
|
|
attack_hand(mob/user as mob)
|
|
if (src.anchored)
|
|
return
|
|
else
|
|
..()
|
|
|
|
attackby(obj/item/W as obj, mob/user as mob)
|
|
if (istype(W, /obj/item/pen))
|
|
var/t = input(user, "Enter new name", src.name, src.critter_name) as null|text
|
|
logTheThing("debug", user, null, "names a critter egg \"[t]\"")
|
|
if (!t)
|
|
return
|
|
t = strip_html(replacetext(t, "'",""))
|
|
t = copytext(t, 1, 65)
|
|
if (!t)
|
|
return
|
|
if (!in_range(src, usr) && src.loc != usr)
|
|
return
|
|
|
|
src.critter_name = t
|
|
|
|
else if ((istype(W, /obj/item/weldingtool) && W:welding) || (istype(W, /obj/item/clothing/head/cakehat) && W:on) || istype(W, /obj/item/device/igniter) || ((istype(W, /obj/item/device/light/zippo) || istype(W, /obj/item/match) || istype(W, /obj/item/device/light/candle)) && W:on) || W.burning || W.hit_type == DAMAGE_BURN) // jesus motherfucking christ
|
|
user.visible_message("<span style=\"color:red\"><b>[user]</b> warms [src] with [W].</span>",\
|
|
"<span style=\"color:red\">You warm [src] with [W].</span>")
|
|
src.warm_count -= 2
|
|
src.warm_count = max(src.warm_count, 0)
|
|
src.hatch_check(0, user)
|
|
else
|
|
return ..()
|
|
|
|
attack_self(mob/user as mob)
|
|
if (src.anchored)
|
|
return
|
|
user.visible_message("[user] warms [src] with [his_or_her(user)] hands.",\
|
|
"You warm [src] with your hands.")
|
|
src.warm_count --
|
|
src.warm_count = max(src.warm_count, 0)
|
|
src.hatch_check(0, user)
|
|
|
|
throw_impact(var/turf/T)
|
|
//..() <- Fuck off mom, I'm 25 and I do what I want =I
|
|
src.hatch_check(1, null, T)
|
|
|
|
proc/hatch_check(var/shouldThrow = 0, var/mob/user, var/turf/T)
|
|
if (hatched || anchored)
|
|
return
|
|
if (src.warm_count <= 0 || shouldThrow)
|
|
if (shouldThrow && T)
|
|
make_cleanable( /obj/decal/cleanable/eggsplat,T)
|
|
src.set_loc(T)
|
|
else
|
|
src.anchored = 1
|
|
src.layer = initial(src.layer)
|
|
if (user)
|
|
user.u_equip(src)
|
|
src.set_loc(get_turf(src))
|
|
|
|
SPAWN_DBG(0)
|
|
if (shouldThrow && T)
|
|
src.visible_message("<span style=\"color:red\">[src] splats onto the floor messily!</span>")
|
|
playsound(T, "sound/impact_sounds/Slimy_Splat_1.ogg", 100, 1)
|
|
else
|
|
var/hatch_wiggle_counter = rand(3,8)
|
|
while (hatch_wiggle_counter-- > 0)
|
|
src.pixel_x++
|
|
sleep(2)
|
|
src.pixel_x--
|
|
sleep(10)
|
|
src.visible_message("[src] hatches!")
|
|
|
|
if (!ispath(critter_type))
|
|
if (istext(critter_type))
|
|
critter_type = text2path(critter_type)
|
|
else
|
|
logTheThing("debug", null, null, "EGG: [src] has invalid critter path!")
|
|
src.visible_message("Looks like there wasn't anything inside of [src]!")
|
|
qdel(src)
|
|
return
|
|
|
|
var/obj/critter/newCritter = new critter_type(T ? T : get_turf(src))
|
|
|
|
if (critter_name)
|
|
newCritter.name = critter_name
|
|
|
|
if (shouldThrow && T)
|
|
newCritter.throw_at(get_edge_target_turf(src, src.dir), 2, 1)
|
|
|
|
sleep(1)
|
|
qdel(src)
|
|
return
|
|
else
|
|
return
|