mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-08-23 05:06:50 +01:00
Begins replacing simple_animal with simple_mob.
This commit is contained in:
@@ -14,6 +14,12 @@
|
||||
#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution.
|
||||
#define AI_LOG_TRACE 5 // Even more detailed than the last.
|
||||
|
||||
// Results of pre-movement checks.
|
||||
// Todo: Move outside AI code?
|
||||
#define MOVEMENT_ON_COOLDOWN -1 // Recently moved and needs to try again soon.
|
||||
#define MOVEMENT_FAILED 0 // Move() returned false for whatever reason and the mob didn't move.
|
||||
#define MOVEMENT_SUCCESSFUL 1 // Move() returned true and the mob hopefully moved.
|
||||
|
||||
// Reasons for targets to not be valid. Based on why, the AI responds differently.
|
||||
#define AI_TARGET_VALID 0 // We can fight them.
|
||||
#define AI_TARGET_INVIS 1 // They were in field of view but became invisible. Switch to STANCE_BLINDFIGHT if no other viable targets exist.
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// AIs for simple mobs.
|
||||
|
||||
/datum/ai_holder/simple_mob
|
||||
hostile = TRUE // The majority of simplemobs are hostile.
|
||||
cooperative = TRUE
|
||||
returns_home = TRUE
|
||||
can_flee = FALSE
|
||||
speak_chance = 1 // If the mob's saylist is empty, nothing will happen.
|
||||
|
||||
// For animals.
|
||||
/datum/ai_holder/simple_mob/passive
|
||||
hostile = FALSE
|
||||
wander = TRUE
|
||||
can_flee = TRUE
|
||||
|
||||
// Ranged mobs.
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged
|
||||
ranged = TRUE
|
||||
|
||||
// Tries to not waste ammo.
|
||||
/datum/ai_holder/simple_mob/ranged/careful
|
||||
conserve_ammo = TRUE
|
||||
|
||||
// Runs away from its target if within a certain distance.
|
||||
/datum/ai_holder/simple_mob/ranged/kiting
|
||||
pointblank = TRUE // So we don't need to copypaste post_melee_attack().
|
||||
var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away.
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/threatening
|
||||
threaten = TRUE
|
||||
threaten_delay = 1 SECOND // Less of a threat and more of pre-attack notice.
|
||||
threaten_timeout = 30 SECONDS
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/post_ranged_attack(atom/A)
|
||||
if(get_dist(holder, A) < run_if_this_close)
|
||||
holder.IMove(get_step_away(holder, A, run_if_this_close))
|
||||
holder.face_atom(A)
|
||||
|
||||
|
||||
// Melee mobs.
|
||||
|
||||
/datum/ai_holder/simple_mob/melee
|
||||
|
||||
// Dances around the enemy its fighting, making it harder to fight back.
|
||||
/datum/ai_holder/simple_mob/melee/evasive
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/evasive/post_melee_attack(atom/A)
|
||||
if(holder.Adjacent(A))
|
||||
holder.IMove(get_step(holder, pick(alldirs)))
|
||||
holder.face_atom(A)
|
||||
|
||||
// The AI for hooligan crabs. Follows people for awhile.
|
||||
/datum/ai_holder/simple_mob/melee/hooligan
|
||||
hostile = FALSE
|
||||
retaliate = TRUE
|
||||
max_home_distance = 12
|
||||
var/random_follow = TRUE // Turn off if you want to bus with crabs.
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/hooligan/handle_stance_strategical()
|
||||
..()
|
||||
if(random_follow && stance == STANCE_IDLE && !leader)
|
||||
if(prob(10))
|
||||
for(var/mob/living/L in hearers(holder))
|
||||
if(!istype(L, holder)) // Don't follow other hooligan crabs.
|
||||
holder.visible_message("<span class='notice'>\The [holder] starts to follow \the [L].</span>")
|
||||
set_follow(L, rand(20 SECONDS, 40 SECONDS))
|
||||
|
||||
|
||||
// The AI for nurse spiders. Wraps things in webs by 'attacking' them.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider
|
||||
wander = TRUE
|
||||
base_wander_delay = 8
|
||||
|
||||
// Get us unachored objects as an option as well.
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets()
|
||||
. = ..()
|
||||
|
||||
var/static/alternative_targets = typecacheof(list(/obj/item, /obj/structure))
|
||||
|
||||
for(var/AT in typecache_filter_list(range(vision_range, holder), alternative_targets))
|
||||
var/obj/O = AT
|
||||
if(can_see(holder, O, vision_range) && !O.anchored)
|
||||
. += O
|
||||
|
||||
// Select an obj if no mobs are around.
|
||||
/datum/ai_holder/melee/nurse_spider/pick_target(list/targets)
|
||||
var/mobs_only = locate(/mob/living) in targets // If a mob is in the list of targets, then ignore objects.
|
||||
if(mobs_only)
|
||||
for(var/A in targets)
|
||||
if(!isliving(A))
|
||||
targets -= A
|
||||
|
||||
return ..(targets)
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/can_attack(atom/movable/the_target)
|
||||
. = ..()
|
||||
if(!.) // Parent returned FALSE.
|
||||
if(istype(the_target, /obj))
|
||||
var/obj/O = the_target
|
||||
if(!O.anchored)
|
||||
return TRUE
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets()
|
||||
var/list/targets = ..()
|
||||
|
||||
if(targets.len) // Do regular targeting if there's actual enemies.
|
||||
world << "Returned early."
|
||||
world << "targets was [english_list(targets)]."
|
||||
return targets
|
||||
|
||||
// Otherwise lets target objects to web them.
|
||||
var/static/webbable_types = typecacheof(list(/obj/machinery, /obj/item/, /obj/structure))
|
||||
for(var/WT in typecache_filter_list(range(vision_range, holder), webbable_types))
|
||||
var/obj/O = WT
|
||||
if(!O.anchored && can_see(holder, O, vision_range))
|
||||
targets += WT
|
||||
|
||||
world << "targets was [english_list(targets)]."
|
||||
return targets
|
||||
*/
|
||||
/*
|
||||
/datum/ai_holder/simple_mob/melee/nurse_spider/can_attack(atom/movable/the_target)
|
||||
. = ..()
|
||||
if(!.) // Parent returned FALSE.
|
||||
if(istype(the_target, /obj))
|
||||
var/obj/O = the_target
|
||||
if(!O.anchored)
|
||||
return TRUE
|
||||
*/
|
||||
|
||||
/*
|
||||
. = hearers(vision_range, holder) - src // Remove ourselves to prevent suicidal decisions.
|
||||
|
||||
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
|
||||
|
||||
for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines))
|
||||
if(can_see(holder, HM, vision_range))
|
||||
. += HM
|
||||
*/
|
||||
@@ -0,0 +1,102 @@
|
||||
// Specialized AI for slime simplemobs.
|
||||
// Unlike the parent AI code, this will probably break a lot of things if you put it on something that isn't /mob/living/simple_animal/slime
|
||||
|
||||
/datum/ai_holder/normal/slime
|
||||
|
||||
/datum/ai_holder/normal/slime/New()
|
||||
..()
|
||||
ASSERT(istype(holder, /mob/living/simple_animal/slime))
|
||||
|
||||
/datum/ai_holder/normal/slime/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
|
||||
var/mob/living/simple_animal/slime/me = holder
|
||||
if(me.victim) // Don't worry about finding another target if we're eatting someone.
|
||||
return
|
||||
if(leader && me.can_command(leader)) // If following someone, don't attack until the leader says so, something hits you, or the leader is no longer worthy.
|
||||
return
|
||||
..()
|
||||
|
||||
/datum/ai_holder/normal/slime/found(mob/living/L)
|
||||
var/mob/living/simple_animal/slime/me = holder
|
||||
if(isliving(L))
|
||||
if(can_attack(L))
|
||||
if(L.faction == me.faction && !attack_same_faction)
|
||||
if(ishuman(L))
|
||||
var/mob/living/carbon/human/H = L
|
||||
if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too.
|
||||
return H // Monkeys are always food.
|
||||
else
|
||||
return
|
||||
|
||||
// if(L in friends)
|
||||
// return
|
||||
|
||||
if(istype(L, /mob/living/simple_animal/slime))
|
||||
var/mob/living/simple_animal/slime/buddy = L
|
||||
if(buddy.slime_color == me.slime_color || me.discipline || me.unity || buddy.unity)
|
||||
return // Don't hurt same colored slimes.
|
||||
else
|
||||
return buddy //do hurt others
|
||||
|
||||
if(ishuman(L))
|
||||
var/mob/living/carbon/human/H = L
|
||||
if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too.
|
||||
return H // Monkeys are always food.
|
||||
|
||||
if(issilicon(L) || isbot(L))
|
||||
if(me.discipline && !me.rabid)
|
||||
return // We're a good slime. For now at least.
|
||||
return
|
||||
return
|
||||
|
||||
/datum/ai_holder/normal/slime/closest_distance()
|
||||
if(isliving(target))
|
||||
var/mob/living/L = target
|
||||
if(L.stat)
|
||||
return 1 // Melee (eat) the target if dying, don't shoot it.
|
||||
return ..()
|
||||
|
||||
|
||||
/datum/ai_holder/normal/slime/help_requested(var/mob/living/simple_animal/slime/buddy)
|
||||
var/mob/living/simple_animal/slime/me = holder
|
||||
if(istype(buddy))
|
||||
if(buddy.slime_color != me.slime_color && (!me.unity || !buddy.unity)) // We only help slimes of the same color, if it's another slime calling for help.
|
||||
// ai_log("HelpRequested() by [buddy] but they are a [buddy.slime_color] while we are a [src.slime_color].",2)
|
||||
ai_log("help_requested() : Help was requested by [buddy] but they are a [buddy.slime_color] while we are a [me.slime_color].", AI_LOG_INFO)
|
||||
return
|
||||
..()
|
||||
|
||||
|
||||
/datum/ai_holder/normal/slime/handle_resist()
|
||||
var/mob/living/simple_animal/slime/me = holder
|
||||
if(me.buckled && me.victim && isliving(me.buckled) && me.victim == me.buckled) // If it's buckled to a living thing it's probably eating it.
|
||||
return
|
||||
else
|
||||
..()
|
||||
|
||||
|
||||
/datum/ai_holder/normal/slime/melee_attack(atom/A)
|
||||
var/mob/living/simple_animal/slime/me = holder
|
||||
if(isliving(A))
|
||||
var/mob/living/L = A
|
||||
if( (!L.lying && prob(60 + (me.power_charge * 4) ) || (!L.lying && me.optimal_combat) )) // "Smart" slimes always stun first.
|
||||
me.a_intent = I_DISARM // Stun them first.
|
||||
else if(me.can_consume(L) && L.lying)
|
||||
me.a_intent = I_GRAB // Then eat them.
|
||||
else
|
||||
me.a_intent = I_HURT // Otherwise robust them.
|
||||
..(A)
|
||||
|
||||
/*
|
||||
/mob/living/simple_animal/slime/PunchTarget()
|
||||
if(victim)
|
||||
return // Already eatting someone.
|
||||
if(!client) // AI controlled.
|
||||
if( (!target_mob.lying && prob(60 + (power_charge * 4) ) || (!target_mob.lying && optimal_combat) )) // "Smart" slimes always stun first.
|
||||
a_intent = I_DISARM // Stun them first.
|
||||
else if(can_consume(target_mob) && target_mob.lying)
|
||||
a_intent = I_GRAB // Then eat them.
|
||||
else
|
||||
a_intent = I_HURT // Otherwise robust them.
|
||||
ai_log("PunchTarget() will [a_intent] [target_mob]",2)
|
||||
..()
|
||||
*/
|
||||
@@ -19,6 +19,10 @@
|
||||
var/mob/living/holder = null // The mob this datum is going to control.
|
||||
var/stance = STANCE_IDLE // Determines if the mob should be doing a specific thing, e.g. attacking, following, standing around, etc.
|
||||
var/intelligence_level = AI_NORMAL // Adjust to make the AI be intentionally dumber, or make it more robust (e.g. dodging grenades).
|
||||
var/autopilot = FALSE // If true, the AI won't be deactivated if a client gets attached to the AI's mob.
|
||||
var/busy = FALSE // If true, the ticker will skip processing this mob until this is false. Good for if you need the
|
||||
// mob to stay still (e.g. delayed attacking). If you need the mob to be inactive for an extended period of time,
|
||||
// consider sleeping the AI instead.
|
||||
|
||||
|
||||
|
||||
@@ -73,11 +77,14 @@
|
||||
|
||||
// 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly.
|
||||
/datum/ai_holder/proc/handle_tactics()
|
||||
if(busy)
|
||||
return
|
||||
handle_stance_tactical()
|
||||
|
||||
// 'Strategical' processes that are more expensive on the CPU and so don't get run as often as the above proc, such as A* pathfinding or robust targeting.
|
||||
/datum/ai_holder/proc/handle_strategicals()
|
||||
// world << "[holder.name] Strategicals!"
|
||||
if(busy)
|
||||
return
|
||||
handle_stance_strategical()
|
||||
|
||||
/*
|
||||
@@ -301,3 +308,23 @@
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
// Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code.
|
||||
/mob/living/proc/set_AI_busy(value)
|
||||
if(ai_holder)
|
||||
ai_holder.busy = value
|
||||
|
||||
/mob/living/proc/is_AI_busy()
|
||||
if(!ai_holder)
|
||||
return FALSE
|
||||
return ai_holder.busy
|
||||
|
||||
// Helper proc to check for the AI's stance.
|
||||
// Returns null if there's no AI holder, or the mob has a player and autopilot is not on.
|
||||
// Otherwise returns the stance.
|
||||
/mob/living/proc/get_AI_stance()
|
||||
if(!ai_holder)
|
||||
return null
|
||||
if(client && !ai_holder.autopilot)
|
||||
return null
|
||||
return ai_holder.stance
|
||||
@@ -86,12 +86,16 @@
|
||||
set_stance(STANCE_APPROACH)
|
||||
|
||||
// We're not entirely sure how holder will do melee attacks since any /mob/living could be holder, but we don't have to care because Interfaces.
|
||||
/datum/ai_holder/proc/melee_attack(atom/movable/AM)
|
||||
return holder.IAttack(AM)
|
||||
/datum/ai_holder/proc/melee_attack(atom/A)
|
||||
. = holder.IAttack(A)
|
||||
if(.)
|
||||
post_melee_attack(A)
|
||||
|
||||
// Ditto.
|
||||
/datum/ai_holder/proc/ranged_attack(atom/movable/AM)
|
||||
return holder.IRangedAttack(AM)
|
||||
/datum/ai_holder/proc/ranged_attack(atom/A)
|
||||
. = holder.IRangedAttack(A)
|
||||
if(.)
|
||||
post_ranged_attack(A)
|
||||
|
||||
// Most mobs probably won't have this defined but we don't care.
|
||||
/datum/ai_holder/proc/special_attack(atom/movable/AM)
|
||||
@@ -100,7 +104,13 @@
|
||||
// Called when within striking/shooting distance, however cooldown is not considered.
|
||||
// Override to do things like move in a random step for evasiveness.
|
||||
// Note that this is called BEFORE the attack.
|
||||
/datum/ai_holder/proc/on_engagement(atom/movable/AM)
|
||||
/datum/ai_holder/proc/on_engagement(atom/A)
|
||||
|
||||
// Called after a successful (IE not on cooldown) ranged attack.
|
||||
/datum/ai_holder/proc/post_ranged_attack(atom/A)
|
||||
|
||||
// Ditto but for melee.
|
||||
/datum/ai_holder/proc/post_melee_attack(atom/A)
|
||||
|
||||
// Used to make sure projectiles will probably hit the target and not the wall or a friend.
|
||||
/datum/ai_holder/proc/test_projectile_safety(atom/movable/AM)
|
||||
|
||||
@@ -7,17 +7,9 @@
|
||||
var/threaten_timeout = 1 MINUTE // If the mob threatens someone, they leave, and then come back before this timeout period, the mob escalates to fighting immediately.
|
||||
var/last_conflict_time = null // Last occurance of threatening or fighting being used, in world.time.
|
||||
|
||||
var/threaten_sound = null // Sound file played when the mob calls threaten_target() for the first time.
|
||||
var/stand_down_sound = null // Sound file played when the mob loses sight of the threatened target.
|
||||
|
||||
var/speak_chance = 0 // Probability that the mob talks (this is 'X in 200' chance since even 1/100 is pretty noisy)
|
||||
var/reacts = 0 // Reacts to some things being said.
|
||||
var/datum/say_list/say_list = null // Datum containing all of our lines.
|
||||
var/say_list_type = /datum/say_list // Type to give us on initialization. Default has empty lists, so the mob will be silent.
|
||||
|
||||
/datum/ai_holder/New()
|
||||
..()
|
||||
say_list = new say_list_type()
|
||||
|
||||
/datum/ai_holder/proc/should_threaten()
|
||||
if(!threaten)
|
||||
@@ -37,25 +29,28 @@
|
||||
threatening = TRUE
|
||||
last_conflict_time = world.time
|
||||
|
||||
holder.say(safepick(say_list.say_threaten))
|
||||
playsound(holder.loc, threaten_sound, 75, 1) // We do this twice to make the sound -very- noticable to the target.
|
||||
playsound(target.loc, threaten_sound, 75, 1) // Actual aim-mode also does that so at least it's consistant.
|
||||
if(holder.say_list)
|
||||
holder.say(safepick(holder.say_list.say_threaten))
|
||||
playsound(holder.loc, holder.say_list.threaten_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
|
||||
playsound(target.loc, holder.say_list.threaten_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
|
||||
else // Otherwise we are waiting for them to go away or to wait long enough for escalate.
|
||||
if(target in list_targets()) // Are they still visible?
|
||||
|
||||
if(threaten_delay && last_conflict_time + threaten_delay < world.time) // Waited too long.
|
||||
threatening = FALSE
|
||||
set_stance(STANCE_APPROACH)
|
||||
holder.say(safepick(say_list.say_escalate))
|
||||
if(holder.say_list)
|
||||
holder.say(safepick(holder.say_list.say_escalate))
|
||||
else
|
||||
return // Wait a bit.
|
||||
|
||||
else // They left, or so we think.
|
||||
threatening = FALSE
|
||||
set_stance(STANCE_IDLE)
|
||||
holder.say(safepick(say_list.say_stand_down))
|
||||
playsound(holder.loc, stand_down_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
|
||||
playsound(target.loc, stand_down_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
|
||||
if(holder.say_list)
|
||||
holder.say(safepick(holder.say_list.say_stand_down))
|
||||
playsound(holder.loc, holder.say_list.stand_down_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
|
||||
playsound(target.loc, holder.say_list.stand_down_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
|
||||
|
||||
// Determines what is deserving of a warning when STANCE_ALERT is active.
|
||||
/datum/ai_holder/proc/will_threaten(mob/living/the_target)
|
||||
@@ -75,13 +70,25 @@
|
||||
|
||||
/datum/ai_holder/proc/handle_idle_speaking()
|
||||
if(rand(0,200) < speak_chance)
|
||||
var/list/comm_types = list() // What kinds of things can we do?
|
||||
// Check if anyone is around to 'appreciate' what we say.
|
||||
var/alone = TRUE
|
||||
for(var/m in viewers(holder))
|
||||
var/mob/M = m
|
||||
if(M.client)
|
||||
alone = FALSE
|
||||
break
|
||||
if(alone) // Forever alone. No point doing anything else.
|
||||
return
|
||||
|
||||
if(say_list.speak.len)
|
||||
var/list/comm_types = list() // What kinds of things can we do?
|
||||
if(!holder.say_list)
|
||||
return
|
||||
|
||||
if(holder.say_list.speak.len)
|
||||
comm_types += COMM_SAY
|
||||
if(say_list.emote_hear.len)
|
||||
if(holder.say_list.emote_hear.len)
|
||||
comm_types += COMM_AUDIBLE_EMOTE
|
||||
if(say_list.emote_see.len)
|
||||
if(holder.say_list.emote_see.len)
|
||||
comm_types += COMM_VISUAL_EMOTE
|
||||
|
||||
if(!comm_types.len)
|
||||
@@ -89,11 +96,11 @@
|
||||
|
||||
switch(pick(comm_types))
|
||||
if(COMM_SAY)
|
||||
holder.say(safepick(say_list.speak))
|
||||
holder.say(safepick(holder.say_list.speak))
|
||||
if(COMM_AUDIBLE_EMOTE)
|
||||
holder.audible_emote(safepick(say_list.emote_hear))
|
||||
holder.audible_emote(safepick(holder.say_list.emote_hear))
|
||||
if(COMM_VISUAL_EMOTE)
|
||||
holder.visible_emote(safepick(say_list.emote_see))
|
||||
holder.visible_emote(safepick(holder.say_list.emote_see))
|
||||
|
||||
#undef COMM_SAY
|
||||
#undef COMM_AUDIBLE_EMOTE
|
||||
|
||||
@@ -102,9 +102,6 @@
|
||||
threaten = TRUE
|
||||
wander = TRUE
|
||||
|
||||
threaten_sound = 'sound/weapons/TargetOn.ogg'
|
||||
stand_down_sound = 'sound/weapons/TargetOff.ogg'
|
||||
|
||||
/datum/ai_holder/hostile/ranged/debug
|
||||
wander = FALSE
|
||||
conserve_ammo = FALSE
|
||||
@@ -115,8 +112,6 @@
|
||||
last_turf_display = TRUE
|
||||
debug_ai = AI_LOG_INFO
|
||||
|
||||
say_list_type = /datum/say_list/pirate
|
||||
|
||||
/mob/living/simple_animal/hostile/pirate
|
||||
hostile = FALSE
|
||||
ai_inactive = TRUE
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
return TRUE
|
||||
|
||||
if(can_flee)
|
||||
if(!hostile)
|
||||
if(!hostile && !retaliate)
|
||||
return TRUE // We're not hostile and someone attacked us first.
|
||||
if(flee_when_dying && (holder.health / holder.getMaxHealth()) <= dying_threshold)
|
||||
return TRUE // We're gonna die!
|
||||
|
||||
@@ -57,4 +57,12 @@
|
||||
ai_log("lose_follow() : Exited.", AI_LOG_DEBUG)
|
||||
|
||||
/datum/ai_holder/proc/should_follow_leader()
|
||||
return leader && get_dist(holder, leader) > follow_distance
|
||||
if(!leader)
|
||||
return FALSE
|
||||
if(follow_until_time && world.time > follow_until_time)
|
||||
lose_follow()
|
||||
set_stance(STANCE_IDLE)
|
||||
return FALSE
|
||||
if(get_dist(holder, leader) > follow_distance)
|
||||
return TRUE
|
||||
return FALSE
|
||||
@@ -6,7 +6,9 @@
|
||||
// Home.
|
||||
var/turf/home_turf = null // The mob's 'home' turf. It will try to stay near it if told to do so.
|
||||
var/returns_home = FALSE // If true, makes the mob go to its 'home' if it strays too far.
|
||||
var/home_low_priority = FALSE // If true, the mob will not go home unless it has nothing better to do, e.g. its following someone.
|
||||
var/max_home_distance = 3 // How far the mob can go away from its home before being told to go_home().
|
||||
// Note that there is a 'BYOND cap' of 14 due to limitations of get_/step_to().
|
||||
|
||||
// Wandering.
|
||||
var/wander = FALSE // If true, the mob will randomly move in the four cardinal directions when idle.
|
||||
@@ -15,7 +17,6 @@
|
||||
var/wander_when_pulled = FALSE // If the mob will refrain from wandering if someone is pulling it.
|
||||
|
||||
|
||||
|
||||
/datum/ai_holder/proc/walk_to_destination()
|
||||
ai_log("walk_to_destination() : Entering.",AI_LOG_DEBUG)
|
||||
if(!destination)
|
||||
@@ -41,7 +42,15 @@
|
||||
ai_log("walk_to_destination() : Exiting.",AI_LOG_DEBUG)
|
||||
|
||||
/datum/ai_holder/proc/should_go_home()
|
||||
return (returns_home && home_turf) && (get_dist(holder, home_turf) > max_home_distance)
|
||||
if(!returns_home || !home_turf)
|
||||
return FALSE
|
||||
if(get_dist(holder, home_turf) > max_home_distance)
|
||||
if(!home_low_priority)
|
||||
return TRUE
|
||||
else if(!leader && !target)
|
||||
return TRUE
|
||||
return FALSE
|
||||
// return (returns_home && home_turf) && (get_dist(holder, home_turf) > max_home_distance)
|
||||
|
||||
/datum/ai_holder/proc/go_home()
|
||||
if(home_turf)
|
||||
@@ -70,7 +79,6 @@
|
||||
// Walk towards whatever.
|
||||
/datum/ai_holder/proc/walk_path(atom/A, get_to = 1)
|
||||
ai_log("walk_path() : Entered.", AI_LOG_DEBUG)
|
||||
var/turf/pre_step_turf = get_turf(holder)
|
||||
|
||||
if(use_astar)
|
||||
if(!path.len) // If we're missing a path, make a new one.
|
||||
@@ -78,13 +86,14 @@
|
||||
calculate_path(A, get_to)
|
||||
|
||||
if(!path.len) // If we still don't have one, then the target's probably somewhere inaccessible to us. Get as close as we can.
|
||||
ai_log("walk_path() : Failed to obtain path to target. Using step_to() instead.", AI_LOG_INFO)
|
||||
step_to(holder, A)
|
||||
if(get_turf(holder) == pre_step_turf)
|
||||
ai_log("walk_path() : Failed to obtain path to target. Using get_step_to() instead.", AI_LOG_INFO)
|
||||
// step_to(holder, A)
|
||||
if(holder.IMove(get_step_to(holder, A)) == MOVEMENT_FAILED)
|
||||
ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_TRACE)
|
||||
breakthrough(A) // We failed to move, time to smash things.
|
||||
return
|
||||
|
||||
if(!move_once()) // Start walking the path.
|
||||
if(move_once() == FALSE) // Start walking the path.
|
||||
ai_log("walk_path() : Failed to step.", AI_LOG_TRACE)
|
||||
++failed_steps
|
||||
if(failed_steps > 3) // We're probably stuck.
|
||||
@@ -93,8 +102,10 @@
|
||||
failed_steps = 0
|
||||
|
||||
else
|
||||
step_to(holder, A)
|
||||
if(get_turf(holder) == pre_step_turf)
|
||||
// step_to(holder, A)
|
||||
ai_log("walk_path() : Going to IMove().", AI_LOG_TRACE)
|
||||
if(holder.IMove(get_step_to(holder, A)) == MOVEMENT_FAILED )
|
||||
ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_TRACE)
|
||||
breakthrough(A) // We failed to move, time to smash things.
|
||||
|
||||
ai_log("walk_path() : Exited.", AI_LOG_DEBUG)
|
||||
@@ -110,14 +121,17 @@
|
||||
var/turf/T = src.path[1]
|
||||
T.overlays -= path_overlay
|
||||
|
||||
step_towards(holder, src.path[1])
|
||||
if(holder.loc != src.path[1])
|
||||
ai_log("move_once() : Failed step. Exiting.", AI_LOG_TRACE)
|
||||
return FALSE
|
||||
else
|
||||
path -= src.path[1]
|
||||
ai_log("move_once() : Successful step. Exiting.", AI_LOG_TRACE)
|
||||
return TRUE
|
||||
// step_towards(holder, src.path[1])
|
||||
if(holder.IMove(get_step_towards(holder, src.path[1])) != MOVEMENT_ON_COOLDOWN)
|
||||
if(holder.loc != src.path[1])
|
||||
ai_log("move_once() : Failed step. Exiting.", AI_LOG_TRACE)
|
||||
return MOVEMENT_FAILED
|
||||
else
|
||||
path -= src.path[1]
|
||||
ai_log("move_once() : Successful step. Exiting.", AI_LOG_TRACE)
|
||||
return MOVEMENT_SUCCESSFUL
|
||||
ai_log("move_once() : Mob movement on cooldown. Exiting.", AI_LOG_TRACE)
|
||||
return MOVEMENT_ON_COOLDOWN
|
||||
|
||||
/datum/ai_holder/proc/should_wander()
|
||||
return wander && !leader
|
||||
@@ -135,6 +149,6 @@
|
||||
var/moving_to = 0 // Apparently this is required or it always picks 4, according to the previous developer for simplemob AI.
|
||||
moving_to = pick(cardinal)
|
||||
holder.set_dir(moving_to)
|
||||
holder.Move(get_step(holder,moving_to))
|
||||
holder.IMove(get_step(holder,moving_to))
|
||||
wander_delay = base_wander_delay
|
||||
ai_log("handle_wander_movement() : Exited.", AI_LOG_DEBUG)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Used for assigning a target for attacking.
|
||||
|
||||
/datum/ai_holder
|
||||
var/hostile = FALSE // Do we try to hurt others? Setting to false disables most combat processing.
|
||||
var/retaliate = FALSE // Attacks whatever struck it first.
|
||||
var/hostile = FALSE // Do we try to hurt others?
|
||||
var/retaliate = FALSE // Attacks whatever struck it first. Mobs will still attack back if this is false but hostile is true.
|
||||
|
||||
var/atom/movable/target = null // The thing (mob or object) we're trying to kill.
|
||||
var/turf/target_last_seen_turf = null // Where the mob last observed the target being, used if the target disappears but the mob wants to keep fighting.
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
// Step 2, filter down possible targets to things we actually care about.
|
||||
/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
|
||||
if(!hostile) // So retaliating mobs only attack the thing that hit it.
|
||||
return null
|
||||
. = list()
|
||||
if(!has_targets_list)
|
||||
possible_targets = list_targets()
|
||||
@@ -45,12 +47,13 @@
|
||||
// Step 3, pick among the possible, attackable targets.
|
||||
/datum/ai_holder/proc/pick_target(list/targets)
|
||||
if(target != null) // If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets.
|
||||
for(var/possible_target in targets)
|
||||
var/atom/A = possible_target
|
||||
var/target_dist = get_dist(holder, target)
|
||||
var/possible_target_distance = get_dist(holder, A)
|
||||
if(target_dist < possible_target_distance)
|
||||
targets -= A
|
||||
targets = target_filter_distance(targets)
|
||||
// for(var/possible_target in targets)
|
||||
// var/atom/A = possible_target
|
||||
// var/target_dist = get_dist(holder, target)
|
||||
// var/possible_target_distance = get_dist(holder, A)
|
||||
// if(target_dist < possible_target_distance)
|
||||
// targets -= A
|
||||
if(!targets.len) // We found nothing.
|
||||
return
|
||||
var/chosen_target = pick(targets)
|
||||
@@ -66,6 +69,18 @@
|
||||
set_stance(STANCE_APPROACH)
|
||||
return TRUE
|
||||
|
||||
// Filters return one or more 'preferred' targets.
|
||||
|
||||
// This one is for closest targets.
|
||||
/datum/ai_holder/proc/target_filter_distance(list/targets)
|
||||
for(var/possible_target in targets)
|
||||
var/atom/A = possible_target
|
||||
var/target_dist = get_dist(holder, target)
|
||||
var/possible_target_distance = get_dist(holder, A)
|
||||
if(target_dist < possible_target_distance)
|
||||
targets -= A
|
||||
return targets
|
||||
|
||||
/datum/ai_holder/proc/can_attack(atom/movable/the_target)
|
||||
if(!can_see_target(the_target))
|
||||
return FALSE
|
||||
@@ -156,4 +171,31 @@
|
||||
if(last_turf_display && target_last_seen_turf)
|
||||
target_last_seen_turf.overlays -= last_turf_overlay
|
||||
ai_log("lose_target_position() : Last position is being reset.", AI_LOG_INFO)
|
||||
target_last_seen_turf = null
|
||||
target_last_seen_turf = null
|
||||
|
||||
// Responds to a hostile action against its mob.
|
||||
/datum/ai_holder/proc/react_to_attack(atom/movable/attacker)
|
||||
if(holder.stat) // We're dead.
|
||||
ai_log("react_to_attack() : Was attacked by [attacker], but we are dead/unconscious.", AI_LOG_TRACE)
|
||||
return FALSE
|
||||
if(!hostile && !retaliate) // Not allowed to defend ourselves.
|
||||
ai_log("react_to_attack() : Was attacked by [attacker], but we are not allowed to attack back.", AI_LOG_TRACE)
|
||||
return FALSE
|
||||
if(holder.IIsAlly(attacker)) // I'll overlook it THIS time...
|
||||
ai_log("react_to_attack() : Was attacked by [attacker], but they were an ally.", AI_LOG_TRACE)
|
||||
return FALSE
|
||||
if(target) // Already fighting someone. Switching every time we get hit would impact our combat performance.
|
||||
ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE)
|
||||
return FALSE
|
||||
|
||||
if(stance == STANCE_SLEEP) // If we're asleep, try waking up if someone's wailing on us.
|
||||
ai_log("react_to_attack() : AI is asleep. Waking up.", AI_LOG_TRACE)
|
||||
go_wake()
|
||||
|
||||
ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO)
|
||||
return give_target(attacker) // Also handles setting the appropiate stance.
|
||||
|
||||
/*
|
||||
if(ai_inactive || stat || M == target_mob) return //Not if we're dead or already hitting them
|
||||
if(M in friends || M.faction == faction) return //I'll overlook it THIS time...
|
||||
*/
|
||||
@@ -6,20 +6,20 @@
|
||||
/mob/living/proc/IAttack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_animal/IAttack(atom/A)
|
||||
/mob/living/simple_mob/IAttack(atom/A)
|
||||
return attack_target(A)
|
||||
|
||||
/mob/living/proc/IRangedAttack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_animal/IRangedAttack(atom/A)
|
||||
/mob/living/simple_mob/IRangedAttack(atom/A)
|
||||
return shoot_target(A)
|
||||
|
||||
/mob/living/proc/ISpecialAttack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_animal/ISpecialAttack(atom/A)
|
||||
return special_attack_target()
|
||||
/mob/living/simple_mob/ISpecialAttack(atom/A)
|
||||
return special_attack_target(A)
|
||||
|
||||
/mob/living/proc/ISay(message)
|
||||
|
||||
@@ -31,8 +31,32 @@
|
||||
if(!.) // Outside the faction, try to see if they're friends.
|
||||
return L in friends
|
||||
|
||||
/mob/living/simple_mob/IIsAlly(mob/living/L)
|
||||
. = ..()
|
||||
if(!.) // Outside the faction, try to see if they're friends.
|
||||
return L in friends
|
||||
|
||||
/mob/living/proc/IGetID()
|
||||
|
||||
/mob/living/simple_animal/IGetID()
|
||||
if(myid)
|
||||
return myid.GetID()
|
||||
return myid.GetID()
|
||||
|
||||
// Respects move cooldowns as if it had a client.
|
||||
/mob/living/proc/IMove(newloc)
|
||||
if(check_move_cooldown())
|
||||
// if(!newdir)
|
||||
// newdir = get_dir(get_turf(src), newloc)
|
||||
|
||||
// Move()ing to another tile successfully returns 32 because BYOND. Would rather deal with TRUE/FALSE-esque terms.
|
||||
// Note that moving to the same tile will be 'successful'.
|
||||
var/turf/old_T = get_turf(src)
|
||||
. = SelfMove(newloc) ? MOVEMENT_SUCCESSFUL : MOVEMENT_FAILED
|
||||
if(. == MOVEMENT_SUCCESSFUL)
|
||||
set_dir(get_dir(old_T, newloc))
|
||||
// Apply movement delay.
|
||||
// Player movement has more factors but its all in the client and fixing that would be its own project.
|
||||
setMoveCooldown(movement_delay())
|
||||
return
|
||||
|
||||
. = MOVEMENT_ON_COOLDOWN // To avoid superfast mobs that aren't meant to be superfast. Is actually -1.
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
// A simple datum that just holds many lists of lines for AI mobs to pick from.
|
||||
// A simple datum that just holds many lists of lines for mobs to pick from.
|
||||
// This is its own datum in order to be able to have different types of mobs be able to use the same lines if desired,
|
||||
// even when inheritence wouldn't be able to do so.
|
||||
|
||||
// Also note this also contains emotes, despite its name.
|
||||
// and now sounds because its probably better that way.
|
||||
|
||||
/mob/living
|
||||
var/datum/say_list/say_list = null
|
||||
var/say_list_type = /datum/say_list // Type to give us on initialization. Default has empty lists, so the mob will be silent.
|
||||
|
||||
/mob/living/initialize()
|
||||
if(say_list_type)
|
||||
say_list = new say_list_type(src)
|
||||
return ..()
|
||||
|
||||
/mob/living/Destroy()
|
||||
qdel_null(say_list)
|
||||
return ..()
|
||||
|
||||
|
||||
/datum/say_list
|
||||
var/list/speak = list() // Things the mob might say if it talks while idle.
|
||||
var/list/emote_hear = list() // Hearable emotes it might perform
|
||||
var/list/emote_see = list() // Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps
|
||||
|
||||
var/list/say_understood = list() // When accepting an order.
|
||||
var/list/say_cannot = list() // When they cannot comply.
|
||||
var/list/say_maybe_target = list() // When they briefly see something.
|
||||
@@ -16,6 +32,11 @@
|
||||
var/list/say_stand_down = list() // When the threatened thing goes away.
|
||||
var/list/say_escalate = list() // When the threatened thing doesn't go away.
|
||||
|
||||
var/threaten_sound = null // Sound file played when the mob's AI calls threaten_target() for the first time.
|
||||
var/stand_down_sound = null // Sound file played when the mob's AI loses sight of the threatened target.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,10 +46,38 @@
|
||||
// This one's pretty dumb, but pirates are dumb anyways and it makes for a good test.
|
||||
/datum/say_list/pirate
|
||||
speak = list("Yarr!")
|
||||
|
||||
say_understood = list("Alright, matey.")
|
||||
say_cannot = list("No, matey.")
|
||||
say_maybe_target = list("Eh?")
|
||||
say_got_target = list("Yarrrr!")
|
||||
say_threaten = list("You best leave, this booty is mine.", "No plank to walk on, just walk away.")
|
||||
say_stand_down = list("Good.")
|
||||
say_escalate = list("Yarr! The booty is mine!")
|
||||
say_escalate = list("Yarr! The booty is mine!")
|
||||
|
||||
/datum/say_list/malf_drone
|
||||
speak = list("ALERT.","Hostile-ile-ile entities dee-twhoooo-wected.","Threat parameterszzzz- szzet.","Bring sub-sub-sub-systems uuuup to combat alert alpha-a-a.")
|
||||
emote_see = list("beeps menacingly","whirrs threateningly","scans its immediate vicinity")
|
||||
|
||||
say_understood = list("Affirmative.", "Positive.")
|
||||
say_cannot = list("Denied.", "Negative.")
|
||||
say_maybe_target = list("Possible threat detected. Investigating.", "Motion detected.", "Investigating.")
|
||||
say_got_target = list("Threat detected.", "New task: Remove threat.", "Threat removal engaged.", "Engaging target.")
|
||||
say_threaten = list("Motion detected, judging target...")
|
||||
say_stand_down = list("Visual lost.", "Error: Target not found.")
|
||||
say_escalate = list("Viable target found. Removing.", "Engaging target.", "Target judgement complete. Removal required.")
|
||||
|
||||
threaten_sound = 'sound/effects/turret/move1.wav'
|
||||
stand_down_sound = 'sound/effects/turret/move2.wav'
|
||||
|
||||
/datum/say_list/mercenary
|
||||
threaten_sound = 'sound/weapons/TargetOn.ogg'
|
||||
stand_down_sound = 'sound/weapons/TargetOff.ogg'
|
||||
|
||||
|
||||
/datum/say_list/crab
|
||||
emote_hear = list("clicks")
|
||||
emote_see = list("clacks")
|
||||
|
||||
/datum/say_list/spider
|
||||
emote_hear = list("chitters")
|
||||
@@ -1,3 +1,7 @@
|
||||
/mob/living/death()
|
||||
clear_fullscreens()
|
||||
|
||||
if(ai_holder)
|
||||
ai_holder.go_sleep()
|
||||
|
||||
. = ..()
|
||||
@@ -92,6 +92,13 @@
|
||||
/mob/living/proc/getsoak(var/def_zone, var/type)
|
||||
return 0
|
||||
|
||||
// Clicking with an empty hand
|
||||
/mob/living/attack_hand(mob/living/L)
|
||||
..()
|
||||
if(istype(L) && L.a_intent != I_HELP)
|
||||
if(ai_holder) // Using disarm, grab, or harm intent is considered a hostile action to the mob's AI.
|
||||
ai_holder.react_to_attack(L)
|
||||
|
||||
/mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone)
|
||||
|
||||
//Being hit while using a deadman switch
|
||||
@@ -102,6 +109,9 @@
|
||||
src.visible_message("<font color='red'>[src] triggers their deadman's switch!</font>")
|
||||
signaler.signal()
|
||||
|
||||
if(ai_holder && P.firer)
|
||||
ai_holder.react_to_attack(P.firer)
|
||||
|
||||
//Armor
|
||||
var/soaked = get_armor_soak(def_zone, P.check_armour, P.armor_penetration)
|
||||
var/absorb = run_armor_check(def_zone, P.check_armour, P.armor_penetration)
|
||||
@@ -272,6 +282,8 @@
|
||||
var/client/assailant = M.client
|
||||
if(assailant)
|
||||
add_attack_logs(M,src,"Hit by thrown [O.name]")
|
||||
if(ai_holder)
|
||||
ai_holder.react_to_attack(O.thrower)
|
||||
|
||||
// Begin BS12 momentum-transfer code.
|
||||
var/mass = 1.5
|
||||
@@ -335,6 +347,8 @@
|
||||
|
||||
adjustBruteLoss(damage)
|
||||
add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_animal attacks
|
||||
if(ai_holder)
|
||||
ai_holder.react_to_attack(user)
|
||||
src.visible_message("<span class='danger'>[user] has [attack_message] [src]!</span>")
|
||||
user.do_attack_animation(src)
|
||||
spawn(1) updatehealth()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
var/maxHealth = 100 //Maximum health that should be possible. Avoid adjusting this if you can, and instead use modifiers datums.
|
||||
var/health = 100 //A mob's health
|
||||
|
||||
var/mob_class = MOB_CLASS_ANIMAL // A mob's "class", e.g. human, mechanical, animal, etc. Used for certain projectile effects. See __defines/mob.dm for available classes.
|
||||
|
||||
var/hud_updateflag = 0
|
||||
|
||||
//Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS
|
||||
|
||||
@@ -8,4 +8,9 @@
|
||||
update_antag_icons(mind)
|
||||
client.screen |= global_hud.darksight
|
||||
client.images |= dsoverlay
|
||||
|
||||
if(ai_holder && !ai_holder.autopilot)
|
||||
ai_holder.go_sleep()
|
||||
to_chat(src,"<span class='notice'>Mob AI disabled while you are controlling the mob.</span>")
|
||||
|
||||
return .
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/mob/living/Logout()
|
||||
..()
|
||||
if (mind)
|
||||
if (mind)
|
||||
//Per BYOND docs key remains set if the player DCs, becomes null if switching bodies.
|
||||
if(!key) //key and mind have become seperated.
|
||||
if(!key) //key and mind have become seperated.
|
||||
mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body.
|
||||
|
||||
spawn(15 SECONDS) //15 seconds to get back into the mob before it goes wild
|
||||
if(src && !src.client)
|
||||
if(ai_holder)
|
||||
ai_holder.go_wake()
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
see_in_dark = 6
|
||||
universal_understand = 1
|
||||
|
||||
mob_size = MOB_MINISCULE
|
||||
mob_size = MOB_SMALL
|
||||
pass_flags = PASSTABLE
|
||||
can_pull_size = ITEMSIZE_TINY
|
||||
can_pull_mobs = MOB_PULL_NONE
|
||||
// can_pull_size = ITEMSIZE_TINY
|
||||
// can_pull_mobs = MOB_PULL_NONE
|
||||
layer = MOB_LAYER
|
||||
density = 0
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/mob/living/simple_mob/update_icon()
|
||||
. = ..()
|
||||
var/mutable_appearance/ma = new(src)
|
||||
ma.layer = layer
|
||||
ma.plane = plane
|
||||
|
||||
ma.overlays = list(modifier_overlay)
|
||||
|
||||
//Awake and normal
|
||||
if((stat == CONSCIOUS) && (!icon_rest || !resting || !incapacitated(INCAPACITATION_DISABLED) ))
|
||||
ma.icon_state = icon_living
|
||||
|
||||
//Dead
|
||||
else if(stat >= DEAD)
|
||||
ma.icon_state = icon_dead
|
||||
|
||||
//Resting or KO'd
|
||||
else if(((stat == UNCONSCIOUS) || resting || incapacitated(INCAPACITATION_DISABLED) ) && icon_rest)
|
||||
ma.icon_state = icon_rest
|
||||
|
||||
//Backup
|
||||
else
|
||||
ma.icon_state = initial(icon_state)
|
||||
|
||||
if(has_hands)
|
||||
if(r_hand_sprite)
|
||||
ma.overlays += r_hand_sprite
|
||||
if(l_hand_sprite)
|
||||
ma.overlays += l_hand_sprite
|
||||
|
||||
if(has_eye_glow)
|
||||
add_eyes()
|
||||
|
||||
appearance = ma
|
||||
|
||||
|
||||
// If your simple mob's update_icon() call calls overlays.Cut(), this needs to be called after this, or manually apply modifier_overly to overlays.
|
||||
/mob/living/simple_mob/update_modifier_visuals()
|
||||
var/image/effects = null
|
||||
if(modifier_overlay)
|
||||
overlays -= modifier_overlay
|
||||
modifier_overlay.overlays.Cut()
|
||||
effects = modifier_overlay
|
||||
else
|
||||
effects = new()
|
||||
|
||||
for(var/datum/modifier/M in modifiers)
|
||||
if(M.mob_overlay_state)
|
||||
var/image/I = image("icon" = 'icons/mob/modifier_effects.dmi', "icon_state" = M.mob_overlay_state)
|
||||
I.appearance_flags = RESET_COLOR // So colored mobs don't affect the overlay.
|
||||
effects.overlays += I
|
||||
|
||||
modifier_overlay = effects
|
||||
overlays += modifier_overlay
|
||||
|
||||
|
||||
/mob/living/simple_mob/proc/add_eyes()
|
||||
if(!eye_layer)
|
||||
eye_layer = image(icon, "[icon_state]-eyes")
|
||||
eye_layer.plane = PLANE_LIGHTING_ABOVE
|
||||
|
||||
overlays += eye_layer
|
||||
|
||||
/mob/living/simple_mob/proc/remove_eyes()
|
||||
overlays -= eye_layer
|
||||
|
||||
|
||||
/mob/living/simple_mob/gib()
|
||||
..(icon_gib,1,icon) // we need to specify where the gib animation is stored
|
||||
+15
-12
@@ -1,8 +1,5 @@
|
||||
// Reorganized and somewhat cleaned up.
|
||||
// AI code has been made into a datum, inside the AI module folder.
|
||||
|
||||
// Does a melee attack.
|
||||
/mob/living/simple_animal/proc/attack_target(atom/A)
|
||||
/mob/living/simple_mob/proc/attack_target(atom/A)
|
||||
set waitfor = FALSE // For attack animations, if they're ever added. Don't want the AI processor to get held up.
|
||||
|
||||
if(!A.Adjacent(src))
|
||||
@@ -13,8 +10,10 @@
|
||||
|
||||
return do_attack(A)
|
||||
|
||||
|
||||
// This does the actual attack.
|
||||
/mob/living/simple_animal/proc/do_attack(atom/A)
|
||||
// This is a seperate proc for the purposes of attack animations.
|
||||
/mob/living/simple_mob/proc/do_attack(atom/A)
|
||||
if(!A.Adjacent(src)) // They could've moved in the meantime.
|
||||
return FALSE
|
||||
|
||||
@@ -27,10 +26,8 @@
|
||||
if(isliving(A)) // Check defenses.
|
||||
var/mob/living/L = A
|
||||
|
||||
if(prob(melee_miss_chance)) // This is stupid (The logging part).
|
||||
src.attack_log += text("\[[time_stamp()]\] <font color='red'>attacked [L.name] ([L.ckey])</font>")
|
||||
L.attack_log += text("\[[time_stamp()]\] <font color='orange'>was attacked by [src.name] ([src.ckey])</font>")
|
||||
visible_message("<span class='danger'>[src] misses [L]!</span>")
|
||||
if(prob(melee_miss_chance))
|
||||
add_attack_logs(src, L, "Animal-attacked (miss)", admin_notify = FALSE)
|
||||
do_attack_animation(src)
|
||||
return FALSE // We missed.
|
||||
|
||||
@@ -40,12 +37,16 @@
|
||||
return FALSE // We were blocked.
|
||||
|
||||
if(A.attack_generic(src, damage_to_do, pick(attacktext)) && attack_sound)
|
||||
apply_melee_effects(A)
|
||||
playsound(src, attack_sound, 75, 1)
|
||||
|
||||
return TRUE
|
||||
|
||||
// Override for special effects after a successful attack.
|
||||
/mob/living/simple_mob/proc/apply_melee_effects(atom/A)
|
||||
|
||||
//The actual top-level ranged attack proc
|
||||
/mob/living/simple_animal/proc/shoot_target(atom/A)
|
||||
/mob/living/simple_mob/proc/shoot_target(atom/A)
|
||||
if(!canClick())
|
||||
return FALSE
|
||||
|
||||
@@ -58,8 +59,9 @@
|
||||
|
||||
return TRUE
|
||||
|
||||
|
||||
//Shoot a bullet at someone (idk why user is an argument when src would fit???)
|
||||
/mob/living/simple_animal/proc/shoot(atom/A, turf/start, mob/living/user, bullet = 0)
|
||||
/mob/living/simple_mob/proc/shoot(atom/A, turf/start, mob/living/user, bullet = 0)
|
||||
if(A == start)
|
||||
return
|
||||
|
||||
@@ -69,6 +71,7 @@
|
||||
return
|
||||
P.launch(A)
|
||||
|
||||
|
||||
//Special attacks, like grenades or blinding spit or whatever
|
||||
/mob/living/simple_animal/proc/special_attack_target(atom/A)
|
||||
/mob/living/simple_mob/proc/special_attack_target(atom/A)
|
||||
return FALSE
|
||||
@@ -0,0 +1,192 @@
|
||||
// Hit by a projectile.
|
||||
/mob/living/simple_mob/bullet_act(var/obj/item/projectile/P)
|
||||
//Projectiles with bonus SA damage
|
||||
if(!P.nodamage)
|
||||
// if(!P.SA_vulnerability || P.SA_vulnerability == intelligence_level)
|
||||
if(P.SA_vulnerability & mob_class)
|
||||
P.damage += P.SA_bonus_damage
|
||||
|
||||
. = ..()
|
||||
|
||||
|
||||
// When someone clicks us with an empty hand
|
||||
/mob/living/simple_mob/attack_hand(mob/living/L)
|
||||
..()
|
||||
|
||||
switch(L.a_intent)
|
||||
if(I_HELP)
|
||||
if(health > 0)
|
||||
L.visible_message("<span class='notice'>\The [L] [response_help] \the [src].</span>")
|
||||
|
||||
if(I_DISARM)
|
||||
L.visible_message("<span class='notice'>\The [L] [response_disarm] \the [src].</span>")
|
||||
L.do_attack_animation(src)
|
||||
//TODO: Push the mob away or something
|
||||
|
||||
if(I_GRAB)
|
||||
if (L == src)
|
||||
return
|
||||
if (!(status_flags & CANPUSH))
|
||||
return
|
||||
if(!incapacitated(INCAPACITATION_ALL) && prob(grab_resist))
|
||||
L.visible_message("<span class='warning'>\The [L] tries to grab \the [src] but fails!</span>")
|
||||
return
|
||||
|
||||
var/obj/item/weapon/grab/G = new /obj/item/weapon/grab(L, src)
|
||||
|
||||
L.put_in_active_hand(G)
|
||||
|
||||
G.synch()
|
||||
G.affecting = src
|
||||
LAssailant = L
|
||||
|
||||
L.visible_message("<span class='warning'>\The [L] has grabbed [src] passively!</span>")
|
||||
L.do_attack_animation(src)
|
||||
|
||||
if(I_HURT)
|
||||
var/armor = run_armor_check(def_zone = null, attack_flag = "melee")
|
||||
apply_damage(damage = harm_intent_damage, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE)
|
||||
L.visible_message("<span class='warning'>\The [L] [response_harm] \the [src]!</span>")
|
||||
L.do_attack_animation(src)
|
||||
|
||||
return
|
||||
|
||||
|
||||
// When somoene clicks us with an item in hand
|
||||
/mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user)
|
||||
if(istype(O, /obj/item/stack/medical))
|
||||
if(stat != DEAD)
|
||||
// This could be done better.
|
||||
var/obj/item/stack/medical/MED = O
|
||||
if(health < getMaxHealth())
|
||||
if(MED.amount >= 1)
|
||||
adjustBruteLoss(-MED.heal_brute)
|
||||
MED.amount -= 1
|
||||
if(MED.amount <= 0)
|
||||
qdel(MED)
|
||||
visible_message("<span class='notice'>\The [user] applies the [MED] on [src].</span>")
|
||||
else
|
||||
var/datum/gender/T = gender_datums[src.get_visible_gender()]
|
||||
to_chat(user, "<span class='notice'>\The [src] is dead, medical items won't bring [T.him] back to life.</span>") // the gender lookup is somewhat overkill, but it functions identically to the obsolete gender macros and future-proofs this code
|
||||
if(meat_type && (stat == DEAD)) //if the animal has a meat, and if it is dead.
|
||||
if(istype(O, /obj/item/weapon/material/knife) || istype(O, /obj/item/weapon/material/knife/butch))
|
||||
harvest(user)
|
||||
|
||||
return ..()
|
||||
|
||||
|
||||
// Handles the actual harming by a melee weapon.
|
||||
/mob/living/simple_mob/hit_with_weapon(obj/item/O, mob/living/user, var/effective_force, var/hit_zone)
|
||||
effective_force = O.force
|
||||
|
||||
//Animals can't be stunned(?)
|
||||
if(O.damtype == HALLOSS)
|
||||
effective_force = 0
|
||||
if(supernatural && istype(O,/obj/item/weapon/nullrod))
|
||||
effective_force *= 2
|
||||
purge = 3
|
||||
if(O.force <= resistance)
|
||||
to_chat(user,"<span class='danger'>This weapon is ineffective, it does no damage.</span>")
|
||||
return 2 //???
|
||||
|
||||
// react_to_attack(user)
|
||||
|
||||
. = ..()
|
||||
|
||||
|
||||
// Exploding.
|
||||
/mob/living/simple_mob/ex_act(severity)
|
||||
if(!blinded)
|
||||
flash_eyes()
|
||||
var/armor = run_armor_check(def_zone = null, attack_flag = "bomb")
|
||||
var/bombdam = 500
|
||||
switch (severity)
|
||||
if (1.0)
|
||||
bombdam = 500
|
||||
if (2.0)
|
||||
bombdam = 60
|
||||
if (3.0)
|
||||
bombdam = 30
|
||||
|
||||
apply_damage(damage = bombdam, damagetype = BRUTE, def_zone = null, blocked = armor, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE)
|
||||
|
||||
if(bombdam > maxHealth)
|
||||
gib()
|
||||
|
||||
|
||||
// Fire stuff. Not really exciting at the moment.
|
||||
/mob/living/simple_mob/handle_fire()
|
||||
return
|
||||
/mob/living/simple_mob/update_fire()
|
||||
return
|
||||
/mob/living/simple_mob/IgniteMob()
|
||||
return
|
||||
/mob/living/simple_mob/ExtinguishMob()
|
||||
return
|
||||
|
||||
|
||||
// Electricity
|
||||
/mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null)
|
||||
shock_damage *= siemens_coeff
|
||||
if(shock_damage < 1)
|
||||
return 0
|
||||
|
||||
apply_damage(damage = shock_damage, damagetype = BURN, def_zone = null, blocked = null, blocked = resistance, used_weapon = null, sharp = FALSE, edge = FALSE)
|
||||
playsound(loc, "sparks", 50, 1, -1)
|
||||
|
||||
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
|
||||
s.set_up(5, 1, loc)
|
||||
s.start()
|
||||
|
||||
|
||||
// Shot with taser/stunvolver
|
||||
/mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null)
|
||||
if(taser_kill)
|
||||
var/stunDam = 0
|
||||
var/agonyDam = 0
|
||||
var/armor = run_armor_check(def_zone = null, attack_flag = "energy")
|
||||
|
||||
if(stun_amount)
|
||||
stunDam += stun_amount * 0.5
|
||||
apply_damage(damage = stunDam, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = used_weapon, sharp = FALSE, edge = FALSE)
|
||||
|
||||
if(agony_amount)
|
||||
agonyDam += agony_amount * 0.5
|
||||
apply_damage(damage = agonyDam, damagetype = BURN, def_zone = null, blocked = armor, blocked = resistance, used_weapon = used_weapon, sharp = FALSE, edge = FALSE)
|
||||
|
||||
|
||||
// Electromagnetism
|
||||
/mob/living/simple_mob/emp_act(severity)
|
||||
..() // To emp_act() its contents.
|
||||
if(!isSynthetic())
|
||||
return
|
||||
switch(severity)
|
||||
if(1)
|
||||
// adjustFireLoss(rand(15, 25))
|
||||
adjustFireLoss(min(60, getMaxHealth()*0.5)) // Weak mobs will always take two direct EMP hits to kill. Stronger ones might take more.
|
||||
if(2)
|
||||
adjustFireLoss(min(30, getMaxHealth()*0.25))
|
||||
// adjustFireLoss(rand(10, 18))
|
||||
if(3)
|
||||
adjustFireLoss(min(15, getMaxHealth()*0.125))
|
||||
// adjustFireLoss(rand(5, 12))
|
||||
if(4)
|
||||
adjustFireLoss(min(7, getMaxHealth()*0.0625))
|
||||
// adjustFireLoss(rand(1, 6))
|
||||
|
||||
|
||||
// Armor
|
||||
/mob/living/simple_mob/getarmor(def_zone, attack_flag)
|
||||
var/armorval = armor[attack_flag]
|
||||
if(!armorval)
|
||||
return 0
|
||||
else
|
||||
return armorval
|
||||
|
||||
/mob/living/simple_mob/getsoak(def_zone, attack_flag)
|
||||
var/armorval = armor_soak[attack_flag]
|
||||
if(!armorval)
|
||||
return 0
|
||||
else
|
||||
return armorval
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Hand procs for player-controlled SA's
|
||||
/mob/living/simple_mob/swap_hand()
|
||||
src.hand = !( src.hand )
|
||||
if(hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
|
||||
if(hand) //This being 1 means the left hand is in use
|
||||
hud_used.l_hand_hud_object.icon_state = "l_hand_active"
|
||||
hud_used.r_hand_hud_object.icon_state = "r_hand_inactive"
|
||||
else
|
||||
hud_used.l_hand_hud_object.icon_state = "l_hand_inactive"
|
||||
hud_used.r_hand_hud_object.icon_state = "r_hand_active"
|
||||
return
|
||||
|
||||
/mob/living/simple_mob/put_in_active_hand(var/obj/item/I)
|
||||
if(!has_hands || !istype(I))
|
||||
return
|
||||
|
||||
//Puts the item into our active hand if possible. returns 1 on success.
|
||||
/mob/living/simple_mob/put_in_active_hand(var/obj/item/W)
|
||||
if(!has_hands)
|
||||
return FALSE
|
||||
return (hand ? put_in_l_hand(W) : put_in_r_hand(W))
|
||||
|
||||
/mob/living/simple_mob/put_in_l_hand(var/obj/item/W)
|
||||
if(!..() || l_hand)
|
||||
return 0
|
||||
W.forceMove(src)
|
||||
l_hand = W
|
||||
W.equipped(src,slot_l_hand)
|
||||
W.add_fingerprint(src)
|
||||
update_inv_l_hand()
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_mob/put_in_r_hand(var/obj/item/W)
|
||||
if(!..() || r_hand)
|
||||
return 0
|
||||
W.forceMove(src)
|
||||
r_hand = W
|
||||
W.equipped(src,slot_r_hand)
|
||||
W.add_fingerprint(src)
|
||||
update_inv_r_hand()
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_mob/update_inv_r_hand()
|
||||
if(QDESTROYING(src))
|
||||
return
|
||||
|
||||
if(r_hand)
|
||||
r_hand.screen_loc = ui_rhand //TODO
|
||||
|
||||
//determine icon state to use
|
||||
var/t_state
|
||||
if(r_hand.item_state_slots && r_hand.item_state_slots[slot_r_hand_str])
|
||||
t_state = r_hand.item_state_slots[slot_r_hand_str]
|
||||
else if(r_hand.item_state)
|
||||
t_state = r_hand.item_state
|
||||
else
|
||||
t_state = r_hand.icon_state
|
||||
|
||||
//determine icon to use
|
||||
var/icon/t_icon
|
||||
if(r_hand.item_icons && (slot_r_hand_str in r_hand.item_icons))
|
||||
t_icon = r_hand.item_icons[slot_r_hand_str]
|
||||
else if(r_hand.icon_override)
|
||||
t_state += "_r"
|
||||
t_icon = r_hand.icon_override
|
||||
else
|
||||
t_icon = INV_R_HAND_DEF_ICON
|
||||
|
||||
//apply color
|
||||
var/image/standing = image(icon = t_icon, icon_state = t_state)
|
||||
standing.color = r_hand.color
|
||||
|
||||
r_hand_sprite = standing
|
||||
|
||||
else
|
||||
r_hand_sprite = null
|
||||
|
||||
update_icon()
|
||||
|
||||
/mob/living/simple_mob/update_inv_l_hand()
|
||||
if(QDESTROYING(src))
|
||||
return
|
||||
|
||||
if(l_hand)
|
||||
l_hand.screen_loc = ui_lhand //TODO
|
||||
|
||||
//determine icon state to use
|
||||
var/t_state
|
||||
if(l_hand.item_state_slots && l_hand.item_state_slots[slot_l_hand_str])
|
||||
t_state = l_hand.item_state_slots[slot_l_hand_str]
|
||||
else if(l_hand.item_state)
|
||||
t_state = l_hand.item_state
|
||||
else
|
||||
t_state = l_hand.icon_state
|
||||
|
||||
//determine icon to use
|
||||
var/icon/t_icon
|
||||
if(l_hand.item_icons && (slot_l_hand_str in l_hand.item_icons))
|
||||
t_icon = l_hand.item_icons[slot_l_hand_str]
|
||||
else if(l_hand.icon_override)
|
||||
t_state += "_l"
|
||||
t_icon = l_hand.icon_override
|
||||
else
|
||||
t_icon = INV_L_HAND_DEF_ICON
|
||||
|
||||
//apply color
|
||||
var/image/standing = image(icon = t_icon, icon_state = t_state)
|
||||
standing.color = l_hand.color
|
||||
|
||||
l_hand_sprite = standing
|
||||
|
||||
else
|
||||
l_hand_sprite = null
|
||||
|
||||
update_icon()
|
||||
|
||||
//Can insert extra huds into the hud holder here.
|
||||
/mob/living/simple_mob/proc/extra_huds(var/datum/hud/hud,var/icon/ui_style,var/list/hud_elements)
|
||||
return
|
||||
|
||||
//If they can or cannot use tools/machines/etc
|
||||
/mob/living/simple_mob/IsAdvancedToolUser()
|
||||
return has_hands
|
||||
@@ -0,0 +1,159 @@
|
||||
/mob/living/simple_mob/Life()
|
||||
..()
|
||||
|
||||
//Health
|
||||
updatehealth()
|
||||
if(stat >= DEAD)
|
||||
return FALSE
|
||||
|
||||
handle_stunned()
|
||||
handle_weakened()
|
||||
handle_paralysed()
|
||||
handle_supernatural()
|
||||
handle_atmos()
|
||||
|
||||
handle_special()
|
||||
|
||||
return TRUE
|
||||
|
||||
|
||||
//Should we be dead?
|
||||
/mob/living/simple_mob/updatehealth()
|
||||
health = getMaxHealth() - getToxLoss() - getFireLoss() - getBruteLoss()
|
||||
|
||||
//Alive, becoming dead
|
||||
if((stat < DEAD) && (health <= 0))
|
||||
death()
|
||||
|
||||
//Overhealth
|
||||
if(health > getMaxHealth())
|
||||
health = getMaxHealth()
|
||||
|
||||
//Update our hud if we have one
|
||||
if(healths)
|
||||
if(stat != DEAD)
|
||||
var/heal_per = (health / getMaxHealth()) * 100
|
||||
switch(heal_per)
|
||||
if(100 to INFINITY)
|
||||
healths.icon_state = "health0"
|
||||
if(80 to 100)
|
||||
healths.icon_state = "health1"
|
||||
if(60 to 80)
|
||||
healths.icon_state = "health2"
|
||||
if(40 to 60)
|
||||
healths.icon_state = "health3"
|
||||
if(20 to 40)
|
||||
healths.icon_state = "health4"
|
||||
if(0 to 20)
|
||||
healths.icon_state = "health5"
|
||||
else
|
||||
healths.icon_state = "health6"
|
||||
else
|
||||
healths.icon_state = "health7"
|
||||
|
||||
//Updates the nutrition while we're here
|
||||
if(nutrition_icon)
|
||||
var/food_per = (nutrition / initial(nutrition)) * 100
|
||||
switch(food_per)
|
||||
if(90 to INFINITY)
|
||||
nutrition_icon.icon_state = "nutrition0"
|
||||
if(75 to 90)
|
||||
nutrition_icon.icon_state = "nutrition1"
|
||||
if(50 to 75)
|
||||
nutrition_icon.icon_state = "nutrition2"
|
||||
if(25 to 50)
|
||||
nutrition_icon.icon_state = "nutrition3"
|
||||
if(0 to 25)
|
||||
nutrition_icon.icon_state = "nutrition4"
|
||||
|
||||
// Override for special bullshit.
|
||||
/mob/living/simple_mob/proc/handle_special()
|
||||
return
|
||||
|
||||
|
||||
// Handle interacting with and taking damage from atmos
|
||||
// TODO - Refactor this to use handle_environment() like a good /mob/living
|
||||
/mob/living/simple_mob/proc/handle_atmos()
|
||||
var/atmos_unsuitable = 0
|
||||
|
||||
var/atom/A = src.loc
|
||||
|
||||
if(istype(A,/turf))
|
||||
var/turf/T = A
|
||||
|
||||
var/datum/gas_mixture/Environment = T.return_air()
|
||||
|
||||
if(Environment)
|
||||
|
||||
if( abs(Environment.temperature - bodytemperature) > 40 )
|
||||
bodytemperature += ((Environment.temperature - bodytemperature) / 5)
|
||||
|
||||
if(min_oxy)
|
||||
if(Environment.gas["oxygen"] < min_oxy)
|
||||
atmos_unsuitable = 1
|
||||
if(max_oxy)
|
||||
if(Environment.gas["oxygen"] > max_oxy)
|
||||
atmos_unsuitable = 1
|
||||
if(min_tox)
|
||||
if(Environment.gas["phoron"] < min_tox)
|
||||
atmos_unsuitable = 2
|
||||
if(max_tox)
|
||||
if(Environment.gas["phoron"] > max_tox)
|
||||
atmos_unsuitable = 2
|
||||
if(min_n2)
|
||||
if(Environment.gas["nitrogen"] < min_n2)
|
||||
atmos_unsuitable = 1
|
||||
if(max_n2)
|
||||
if(Environment.gas["nitrogen"] > max_n2)
|
||||
atmos_unsuitable = 1
|
||||
if(min_co2)
|
||||
if(Environment.gas["carbon_dioxide"] < min_co2)
|
||||
atmos_unsuitable = 1
|
||||
if(max_co2)
|
||||
if(Environment.gas["carbon_dioxide"] > max_co2)
|
||||
atmos_unsuitable = 1
|
||||
|
||||
//Atmos effect
|
||||
if(bodytemperature < minbodytemp)
|
||||
fire_alert = 2
|
||||
adjustBruteLoss(cold_damage_per_tick)
|
||||
if(fire)
|
||||
fire.icon_state = "fire1"
|
||||
else if(bodytemperature > maxbodytemp)
|
||||
fire_alert = 1
|
||||
adjustBruteLoss(heat_damage_per_tick)
|
||||
if(fire)
|
||||
fire.icon_state = "fire2"
|
||||
else
|
||||
fire_alert = 0
|
||||
if(fire)
|
||||
fire.icon_state = "fire0"
|
||||
|
||||
if(atmos_unsuitable)
|
||||
adjustBruteLoss(unsuitable_atoms_damage)
|
||||
if(oxygen)
|
||||
oxygen.icon_state = "oxy1"
|
||||
else if(oxygen)
|
||||
if(oxygen)
|
||||
oxygen.icon_state = "oxy0"
|
||||
|
||||
|
||||
/mob/living/simple_mob/proc/handle_supernatural()
|
||||
if(purge)
|
||||
purge -= 1
|
||||
|
||||
/mob/living/simple_mob/death(gibbed, deathmessage = "dies!")
|
||||
density = 0 //We don't block even if we did before
|
||||
|
||||
if(has_eye_glow)
|
||||
remove_eyes()
|
||||
|
||||
if(loot_list.len) //Drop any loot
|
||||
for(var/path in loot_list)
|
||||
if(prob(loot_list[path]))
|
||||
new path(get_turf(src))
|
||||
|
||||
spawn(3) //We'll update our icon in a sec
|
||||
update_icon()
|
||||
|
||||
return ..(gibbed,deathmessage)
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Animals
|
||||
*/
|
||||
/mob/living/simple_mob/UnarmedAttack(var/atom/A, var/proximity)
|
||||
if(!(. = ..()))
|
||||
return
|
||||
|
||||
// setClickCooldown(get_attack_speed())
|
||||
|
||||
if(has_hands && istype(A,/obj) && a_intent != I_HURT)
|
||||
var/obj/O = A
|
||||
return O.attack_hand(src)
|
||||
|
||||
switch(a_intent)
|
||||
if(I_HELP)
|
||||
if(isliving(A))
|
||||
custom_emote(1,"[pick(friendly)] [A]!")
|
||||
|
||||
if(I_HURT)
|
||||
if(prob(special_attack_prob))
|
||||
if(special_attack_min_range <= 1)
|
||||
special_attack_target(A)
|
||||
|
||||
else if(melee_damage_upper == 0 && istype(A,/mob/living))
|
||||
custom_emote(1,"[pick(friendly)] [A]!")
|
||||
|
||||
else
|
||||
attack_target(A)
|
||||
|
||||
if(I_GRAB)
|
||||
if(has_hands)
|
||||
A.attack_hand(src)
|
||||
|
||||
if(I_DISARM)
|
||||
if(has_hands)
|
||||
A.attack_hand(src)
|
||||
|
||||
/mob/living/simple_mob/RangedAttack(var/atom/A)
|
||||
// setClickCooldown(get_attack_speed())
|
||||
var/distance = get_dist(src, A)
|
||||
|
||||
if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range))
|
||||
special_attack_target()
|
||||
return
|
||||
|
||||
if(projectiletype)
|
||||
shoot_target(A)
|
||||
@@ -0,0 +1,311 @@
|
||||
/mob/living/simple_mob/instantiate_hud(var/datum/hud/hud)
|
||||
if(!client)
|
||||
return //Why bother.
|
||||
|
||||
var/ui_style = 'icons/mob/screen1_animal.dmi'
|
||||
if(ui_icons)
|
||||
ui_style = ui_icons
|
||||
|
||||
var/ui_color = "#ffffff"
|
||||
var/ui_alpha = 255
|
||||
|
||||
var/list/adding = list()
|
||||
var/list/other = list()
|
||||
var/list/hotkeybuttons = list()
|
||||
var/list/slot_info = list()
|
||||
|
||||
hud.adding = adding
|
||||
hud.other = other
|
||||
hud.hotkeybuttons = hotkeybuttons
|
||||
|
||||
var/list/hud_elements = list()
|
||||
var/obj/screen/using
|
||||
var/obj/screen/inventory/inv_box
|
||||
|
||||
var/has_hidden_gear
|
||||
if(LAZYLEN(hud_gears))
|
||||
for(var/gear_slot in hud_gears)
|
||||
inv_box = new /obj/screen/inventory()
|
||||
inv_box.icon = ui_style
|
||||
inv_box.color = ui_color
|
||||
inv_box.alpha = ui_alpha
|
||||
|
||||
var/list/slot_data = hud_gears[gear_slot]
|
||||
inv_box.name = gear_slot
|
||||
inv_box.screen_loc = slot_data["loc"]
|
||||
inv_box.slot_id = slot_data["slot"]
|
||||
inv_box.icon_state = slot_data["state"]
|
||||
slot_info["[inv_box.slot_id]"] = inv_box.screen_loc
|
||||
|
||||
if(slot_data["dir"])
|
||||
inv_box.set_dir(slot_data["dir"])
|
||||
|
||||
if(slot_data["toggle"])
|
||||
other += inv_box
|
||||
has_hidden_gear = 1
|
||||
else
|
||||
adding += inv_box
|
||||
|
||||
if(has_hidden_gear)
|
||||
using = new /obj/screen()
|
||||
using.name = "toggle"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "other"
|
||||
using.screen_loc = ui_inventory
|
||||
using.hud_layerise()
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
adding += using
|
||||
|
||||
//Intent Backdrop
|
||||
using = new /obj/screen()
|
||||
using.name = "act_intent"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "intent_"+a_intent
|
||||
using.screen_loc = ui_acti
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.adding += using
|
||||
hud.action_intent = using
|
||||
|
||||
hud_elements |= using
|
||||
|
||||
//Small intent quarters
|
||||
var/icon/ico
|
||||
|
||||
ico = new(ui_style, "black")
|
||||
ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
|
||||
ico.DrawBox(rgb(255,255,255,1),1,ico.Height()/2,ico.Width()/2,ico.Height())
|
||||
using = new /obj/screen()
|
||||
using.name = I_HELP
|
||||
using.icon = ico
|
||||
using.screen_loc = ui_acti
|
||||
using.alpha = ui_alpha
|
||||
using.layer = LAYER_HUD_ITEM //These sit on the intent box
|
||||
hud.adding += using
|
||||
hud.help_intent = using
|
||||
|
||||
ico = new(ui_style, "black")
|
||||
ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
|
||||
ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,ico.Height()/2,ico.Width(),ico.Height())
|
||||
using = new /obj/screen()
|
||||
using.name = I_DISARM
|
||||
using.icon = ico
|
||||
using.screen_loc = ui_acti
|
||||
using.alpha = ui_alpha
|
||||
using.layer = LAYER_HUD_ITEM
|
||||
hud.adding += using
|
||||
hud.disarm_intent = using
|
||||
|
||||
ico = new(ui_style, "black")
|
||||
ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
|
||||
ico.DrawBox(rgb(255,255,255,1),ico.Width()/2,1,ico.Width(),ico.Height()/2)
|
||||
using = new /obj/screen()
|
||||
using.name = I_GRAB
|
||||
using.icon = ico
|
||||
using.screen_loc = ui_acti
|
||||
using.alpha = ui_alpha
|
||||
using.layer = LAYER_HUD_ITEM
|
||||
hud.adding += using
|
||||
hud.grab_intent = using
|
||||
|
||||
ico = new(ui_style, "black")
|
||||
ico.MapColors(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, -1,-1,-1,-1)
|
||||
ico.DrawBox(rgb(255,255,255,1),1,1,ico.Width()/2,ico.Height()/2)
|
||||
using = new /obj/screen()
|
||||
using.name = I_HURT
|
||||
using.icon = ico
|
||||
using.screen_loc = ui_acti
|
||||
using.alpha = ui_alpha
|
||||
using.layer = LAYER_HUD_ITEM
|
||||
hud.adding += using
|
||||
hud.hurt_intent = using
|
||||
|
||||
//Move intent (walk/run)
|
||||
using = new /obj/screen()
|
||||
using.name = "mov_intent"
|
||||
using.icon = ui_style
|
||||
using.icon_state = (m_intent == "run" ? "running" : "walking")
|
||||
using.screen_loc = ui_movi
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.adding += using
|
||||
hud.move_intent = using
|
||||
|
||||
//Resist button
|
||||
using = new /obj/screen()
|
||||
using.name = "resist"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "act_resist"
|
||||
using.screen_loc = ui_pull_resist
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.hotkeybuttons += using
|
||||
|
||||
//Pull button
|
||||
pullin = new /obj/screen()
|
||||
pullin.icon = ui_style
|
||||
pullin.icon_state = "pull0"
|
||||
pullin.name = "pull"
|
||||
pullin.screen_loc = ui_pull_resist
|
||||
hud.hotkeybuttons += pullin
|
||||
hud_elements |= pullin
|
||||
|
||||
//Health status
|
||||
healths = new /obj/screen()
|
||||
healths.icon = ui_style
|
||||
healths.icon_state = "health0"
|
||||
healths.name = "health"
|
||||
healths.screen_loc = ui_health
|
||||
hud_elements |= healths
|
||||
|
||||
//Oxygen dep icon
|
||||
oxygen = new /obj/screen()
|
||||
oxygen.icon = ui_style
|
||||
oxygen.icon_state = "oxy0"
|
||||
oxygen.name = "oxygen"
|
||||
oxygen.screen_loc = ui_oxygen
|
||||
hud_elements |= oxygen
|
||||
|
||||
//Toxins present icon
|
||||
toxin = new /obj/screen()
|
||||
toxin.icon = ui_style
|
||||
toxin.icon_state = "tox0"
|
||||
toxin.name = "toxin"
|
||||
toxin.screen_loc = ui_toxin
|
||||
hud_elements |= toxin
|
||||
|
||||
//Fire warning
|
||||
fire = new /obj/screen()
|
||||
fire.icon = ui_style
|
||||
fire.icon_state = "fire0"
|
||||
fire.name = "fire"
|
||||
fire.screen_loc = ui_fire
|
||||
hud_elements |= fire
|
||||
|
||||
//Pressure warning
|
||||
pressure = new /obj/screen()
|
||||
pressure.icon = ui_style
|
||||
pressure.icon_state = "pressure0"
|
||||
pressure.name = "pressure"
|
||||
pressure.screen_loc = ui_pressure
|
||||
hud_elements |= pressure
|
||||
|
||||
//Body temp warning
|
||||
bodytemp = new /obj/screen()
|
||||
bodytemp.icon = ui_style
|
||||
bodytemp.icon_state = "temp0"
|
||||
bodytemp.name = "body temperature"
|
||||
bodytemp.screen_loc = ui_temp
|
||||
hud_elements |= bodytemp
|
||||
|
||||
//Nutrition status
|
||||
nutrition_icon = new /obj/screen()
|
||||
nutrition_icon.icon = ui_style
|
||||
nutrition_icon.icon_state = "nutrition0"
|
||||
nutrition_icon.name = "nutrition"
|
||||
nutrition_icon.screen_loc = ui_nutrition
|
||||
hud_elements |= nutrition_icon
|
||||
|
||||
pain = new /obj/screen( null )
|
||||
|
||||
zone_sel = new /obj/screen/zone_sel( null )
|
||||
zone_sel.icon = ui_style
|
||||
zone_sel.color = ui_color
|
||||
zone_sel.alpha = ui_alpha
|
||||
zone_sel.overlays.Cut()
|
||||
zone_sel.overlays += image('icons/mob/zone_sel.dmi', "[zone_sel.selecting]")
|
||||
hud_elements |= zone_sel
|
||||
|
||||
//Hand things
|
||||
if(has_hands)
|
||||
//Drop button
|
||||
using = new /obj/screen()
|
||||
using.name = "drop"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "act_drop"
|
||||
using.screen_loc = ui_drop_throw
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.hotkeybuttons += using
|
||||
|
||||
//Equip detail
|
||||
using = new /obj/screen()
|
||||
using.name = "equip"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "act_equip"
|
||||
using.screen_loc = ui_equip
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.adding += using
|
||||
|
||||
//Hand slots themselves
|
||||
inv_box = new /obj/screen/inventory/hand()
|
||||
inv_box.hud = src
|
||||
inv_box.name = "r_hand"
|
||||
inv_box.icon = ui_style
|
||||
inv_box.icon_state = "r_hand_inactive"
|
||||
if(!hand) //This being 0 or null means the right hand is in use
|
||||
inv_box.icon_state = "r_hand_active"
|
||||
inv_box.screen_loc = ui_rhand
|
||||
inv_box.slot_id = slot_r_hand
|
||||
inv_box.color = ui_color
|
||||
inv_box.alpha = ui_alpha
|
||||
hud.r_hand_hud_object = inv_box
|
||||
hud.adding += inv_box
|
||||
slot_info["[slot_r_hand]"] = inv_box.screen_loc
|
||||
|
||||
inv_box = new /obj/screen/inventory/hand()
|
||||
inv_box.hud = src
|
||||
inv_box.name = "l_hand"
|
||||
inv_box.icon = ui_style
|
||||
inv_box.icon_state = "l_hand_inactive"
|
||||
if(hand) //This being 1 means the left hand is in use
|
||||
inv_box.icon_state = "l_hand_active"
|
||||
inv_box.screen_loc = ui_lhand
|
||||
inv_box.slot_id = slot_l_hand
|
||||
inv_box.color = ui_color
|
||||
inv_box.alpha = ui_alpha
|
||||
hud.l_hand_hud_object = inv_box
|
||||
hud.adding += inv_box
|
||||
slot_info["[slot_l_hand]"] = inv_box.screen_loc
|
||||
|
||||
//Swaphand titlebar
|
||||
using = new /obj/screen/inventory()
|
||||
using.name = "hand"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "hand1"
|
||||
using.screen_loc = ui_swaphand1
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.adding += using
|
||||
|
||||
using = new /obj/screen/inventory()
|
||||
using.name = "hand"
|
||||
using.icon = ui_style
|
||||
using.icon_state = "hand2"
|
||||
using.screen_loc = ui_swaphand2
|
||||
using.color = ui_color
|
||||
using.alpha = ui_alpha
|
||||
hud.adding += using
|
||||
|
||||
//Throw button
|
||||
throw_icon = new /obj/screen()
|
||||
throw_icon.icon = ui_style
|
||||
throw_icon.icon_state = "act_throw_off"
|
||||
throw_icon.name = "throw"
|
||||
throw_icon.screen_loc = ui_drop_throw
|
||||
throw_icon.color = ui_color
|
||||
throw_icon.alpha = ui_alpha
|
||||
hud.hotkeybuttons += throw_icon
|
||||
hud_elements |= throw_icon
|
||||
|
||||
extra_huds(hud,ui_style,hud_elements)
|
||||
|
||||
client.screen = list()
|
||||
|
||||
client.screen += hud_elements
|
||||
client.screen += adding + hotkeybuttons
|
||||
client.screen += client.void
|
||||
|
||||
return
|
||||
@@ -0,0 +1,252 @@
|
||||
// Reorganized and somewhat cleaned up.
|
||||
// AI code has been made into a datum, inside the AI module folder.
|
||||
|
||||
/mob/living/simple_mob
|
||||
name = "animal"
|
||||
desc = ""
|
||||
icon = 'icons/mob/animal.dmi'
|
||||
health = 20
|
||||
maxHealth = 20
|
||||
|
||||
mob_bump_flag = SIMPLE_ANIMAL
|
||||
mob_swap_flags = MONKEY|SLIME|HUMAN
|
||||
mob_push_flags = MONKEY|SLIME|HUMAN
|
||||
|
||||
var/tt_desc = "Uncataloged Life Form" //Tooltip description
|
||||
|
||||
//Settings for played mobs
|
||||
var/show_stat_health = 1 // Does the percentage health show in the stat panel for the mob
|
||||
var/has_hands = 0 // Set to 1 to enable the use of hands and the hands hud
|
||||
var/list/hud_gears // Slots to show on the hud (typically none)
|
||||
var/ui_icons // Icon file path to use for the HUD, otherwise generic icons are used
|
||||
var/r_hand_sprite // If they have hands,
|
||||
var/l_hand_sprite // they could use some icons.
|
||||
var/player_msg // Message to print to players about 'how' to play this mob on login.
|
||||
|
||||
//Mob icon/appearance settings
|
||||
var/icon_living = "" // The iconstate if we're alive, required
|
||||
var/icon_dead = "" // The iconstate if we're dead, required
|
||||
var/icon_gib = "generic_gib" // The iconstate for being gibbed, optional. Defaults to a generic gib animation.
|
||||
var/icon_rest = null // The iconstate for resting, optional
|
||||
var/image/modifier_overlay = null // Holds overlays from modifiers.
|
||||
var/image/eye_layer = null // Holds the eye overlay.
|
||||
var/has_eye_glow = FALSE // If true, adds an overlay over the lighting plane for [icon_state]-eyes.
|
||||
attack_icon = 'icons/effects/effects.dmi' //Just the default, played like the weapon attack anim
|
||||
attack_icon_state = "slash" //Just the default
|
||||
|
||||
//Mob talking settings
|
||||
universal_speak = 0 // Can all mobs in the entire universe understand this one?
|
||||
var/has_langs = list(LANGUAGE_GALCOM)// Text name of their language if they speak something other than galcom. They speak the first one.
|
||||
|
||||
//Movement things.
|
||||
var/movement_cooldown = 5 // Lower is faster.
|
||||
var/movement_sound = null // If set, will play this sound when it moves on its own will.
|
||||
|
||||
//Mob interaction
|
||||
var/response_help = "tries to help" // If clicked on help intent
|
||||
var/response_disarm = "tries to disarm" // If clicked on disarm intent
|
||||
var/response_harm = "tries to hurt" // If clicked on harm intent
|
||||
var/list/friends = list() // Mobs on this list wont get attacked regardless of faction status.
|
||||
var/harm_intent_damage = 3 // How much an unarmed harm click does to this mob.
|
||||
var/meat_amount = 0 // How much meat to drop from this mob when butchered
|
||||
var/obj/meat_type // The meat object to drop
|
||||
var/list/loot_list = list() // The list of lootable objects to drop, with "/path = prob%" structure
|
||||
var/obj/item/weapon/card/id/myid// An ID card if they have one to give them access to stuff.
|
||||
|
||||
//Mob environment settings
|
||||
var/minbodytemp = 250 // Minimum "okay" temperature in kelvin
|
||||
var/maxbodytemp = 350 // Maximum of above
|
||||
var/heat_damage_per_tick = 3 // Amount of damage applied if animal's body temperature is higher than maxbodytemp
|
||||
var/cold_damage_per_tick = 2 // Same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
|
||||
var/fire_alert = 0 // 0 = fine, 1 = hot, 2 = cold
|
||||
|
||||
var/min_oxy = 5 // Oxygen in moles, minimum, 0 is 'no minimum'
|
||||
var/max_oxy = 0 // Oxygen in moles, maximum, 0 is 'no maximum'
|
||||
var/min_tox = 0 // Phoron min
|
||||
var/max_tox = 1 // Phoron max
|
||||
var/min_co2 = 0 // CO2 min
|
||||
var/max_co2 = 5 // CO2 max
|
||||
var/min_n2 = 0 // N2 min
|
||||
var/max_n2 = 0 // N2 max
|
||||
var/unsuitable_atoms_damage = 2 // This damage is taken when atmos doesn't fit all the requirements above
|
||||
|
||||
//Hostility settings
|
||||
var/taser_kill = 1 // Is the mob weak to tasers
|
||||
|
||||
//Attack ranged settings
|
||||
var/projectiletype // The projectiles I shoot
|
||||
var/projectilesound // The sound I make when I do it
|
||||
var/casingtype // What to make the hugely laggy casings pile out of
|
||||
|
||||
//Mob melee settings
|
||||
var/melee_damage_lower = 2 // Lower bound of randomized melee damage
|
||||
var/melee_damage_upper = 6 // Upper bound of randomized melee damage
|
||||
var/list/attacktext = list("attacked") // "You are [attacktext] by the mob!"
|
||||
var/list/friendly = list("nuzzles") // "The mob [friendly] the person."
|
||||
var/attack_sound = null // Sound to play when I attack
|
||||
var/melee_miss_chance = 15 // percent chance to miss a melee attack.
|
||||
var/attack_armor_type = "melee" // What armor does this check?
|
||||
var/attack_armor_pen = 0 // How much armor pen this attack has.
|
||||
var/attack_sharp = 0 // Is the attack sharp?
|
||||
var/attack_edge = 0 // Does the attack have an edge?
|
||||
|
||||
//Special attacks
|
||||
var/special_attack_prob = 0 // Chance of the mob doing a special attack (0 for never)
|
||||
var/special_attack_min_range = 0 // Min range to perform the special attacks from
|
||||
var/special_attack_max_range = 0 // Max range to perform special attacks from
|
||||
|
||||
//Damage resistances
|
||||
var/grab_resist = 0 // Chance for a grab attempt to fail. Note that this is not a true resist and is just a prob() of failure.
|
||||
var/resistance = 0 // Damage reduction for all types
|
||||
var/list/armor = list( // Values for normal getarmor() checks
|
||||
"melee" = 0,
|
||||
"bullet" = 0,
|
||||
"laser" = 0,
|
||||
"energy" = 0,
|
||||
"bomb" = 0,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
var/list/armor_soak = list( // Values for getsoak() checks.
|
||||
"melee" = 0,
|
||||
"bullet" = 0,
|
||||
"laser" = 0,
|
||||
"energy" = 0,
|
||||
"bomb" = 0,
|
||||
"bio" = 0,
|
||||
"rad" = 0
|
||||
)
|
||||
var/purge = 0 // Cult stuff.
|
||||
var/supernatural = FALSE // Ditto.
|
||||
|
||||
|
||||
/mob/living/simple_mob/initialize()
|
||||
verbs -= /mob/verb/observe
|
||||
maxHealth = health
|
||||
|
||||
for(var/L in has_langs)
|
||||
languages |= all_languages[L]
|
||||
if(languages.len)
|
||||
default_language = languages[1]
|
||||
|
||||
if(has_eye_glow)
|
||||
add_eyes()
|
||||
return ..()
|
||||
|
||||
|
||||
/mob/living/simple_mob/Destroy()
|
||||
default_language = null
|
||||
if(myid)
|
||||
qdel(myid)
|
||||
myid = null
|
||||
|
||||
friends.Cut()
|
||||
languages.Cut()
|
||||
|
||||
if(has_eye_glow)
|
||||
remove_eyes()
|
||||
return ..()
|
||||
|
||||
|
||||
//Client attached
|
||||
/mob/living/simple_mob/Login()
|
||||
. = ..()
|
||||
to_chat(src,"<b>You are \the [src]. [player_msg]</b>")
|
||||
|
||||
|
||||
/mob/living/simple_mob/emote(var/act, var/type, var/desc)
|
||||
if(act)
|
||||
..(act, type, desc)
|
||||
|
||||
|
||||
/mob/living/simple_mob/SelfMove()
|
||||
. = ..()
|
||||
if(movement_sound)
|
||||
playsound(src, movement_sound, 50, 1)
|
||||
|
||||
/mob/living/simple_mob/movement_delay()
|
||||
var/tally = 0 //Incase I need to add stuff other than "speed" later
|
||||
|
||||
tally = movement_cooldown
|
||||
|
||||
if(force_max_speed)
|
||||
return -3
|
||||
|
||||
for(var/datum/modifier/M in modifiers)
|
||||
if(!isnull(M.haste) && M.haste == TRUE)
|
||||
return -3
|
||||
if(!isnull(M.slowdown))
|
||||
tally += M.slowdown
|
||||
|
||||
if(purge)//Purged creatures will move more slowly. The more time before their purge stops, the slower they'll move.
|
||||
if(tally <= 0)
|
||||
tally = 1
|
||||
tally *= purge
|
||||
|
||||
if(m_intent == "walk")
|
||||
tally *= 1.5
|
||||
|
||||
return tally+config.animal_delay
|
||||
|
||||
|
||||
/mob/living/simple_mob/Stat()
|
||||
..()
|
||||
if(statpanel("Status") && show_stat_health)
|
||||
stat(null, "Health: [round((health / getMaxHealth()) * 100)]%")
|
||||
|
||||
/mob/living/simple_mob/lay_down()
|
||||
..()
|
||||
if(resting && icon_rest)
|
||||
icon_state = icon_rest
|
||||
else
|
||||
icon_state = icon_living
|
||||
update_icon()
|
||||
|
||||
|
||||
/mob/living/simple_mob/say(var/message,var/datum/language/language)
|
||||
var/verb = "says"
|
||||
if(speak_emote.len)
|
||||
verb = pick(speak_emote)
|
||||
|
||||
message = sanitize(message)
|
||||
|
||||
..(message, null, verb)
|
||||
|
||||
/mob/living/simple_mob/get_speech_ending(verb, var/ending)
|
||||
return verb
|
||||
|
||||
|
||||
/mob/living/simple_mob/put_in_hands(var/obj/item/W) // No hands.
|
||||
W.forceMove(get_turf(src))
|
||||
return 1
|
||||
|
||||
// Harvest an animal's delicious byproducts
|
||||
/mob/living/simple_mob/proc/harvest(var/mob/user)
|
||||
var/actual_meat_amount = max(1,(meat_amount/2))
|
||||
if(meat_type && actual_meat_amount>0 && (stat == DEAD))
|
||||
for(var/i=0;i<actual_meat_amount;i++)
|
||||
var/obj/item/meat = new meat_type(get_turf(src))
|
||||
meat.name = "[src.name] [meat.name]"
|
||||
if(issmall(src))
|
||||
user.visible_message("<span class='danger'>[user] chops up \the [src]!</span>")
|
||||
new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
|
||||
qdel(src)
|
||||
else
|
||||
user.visible_message("<span class='danger'>[user] butchers \the [src] messily!</span>")
|
||||
gib()
|
||||
|
||||
|
||||
/mob/living/simple_mob/is_sentient()
|
||||
return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL // Update this if needed.
|
||||
// return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC
|
||||
|
||||
//Just some subpaths for easy searching
|
||||
/mob/living/simple_mob/hostile
|
||||
faction = "not yours"
|
||||
// ai_holder_type = /datum/ai_holder/regular/hostile
|
||||
|
||||
/mob/living/simple_mob/retaliate
|
||||
// ai_holder_type = /datum/ai_holder/regular/retaliate
|
||||
|
||||
/mob/living/simple_mob/get_nametag_desc(mob/user)
|
||||
return "<i>[tt_desc]</i>"
|
||||
@@ -0,0 +1,2 @@
|
||||
/mob/living/simple_mob/animal
|
||||
mob_class = MOB_CLASS_ANIMAL
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Spiders come in various types, and are a fairly common enemy both inside and outside the station.
|
||||
Their attacks can inject reagents, which can cause harm long after the spider is killed.
|
||||
Thick material will prevent injections, similar to other means of injections.
|
||||
*/
|
||||
|
||||
// The base spider, in the 'walking tank' family.
|
||||
/mob/living/simple_mob/animal/giant_spider
|
||||
name = "giant spider"
|
||||
desc = "Furry and brown, it makes you shudder to look at it. This one has deep red eyes."
|
||||
tt_desc = "Atrax robustus gigantus"
|
||||
icon_state = "guard"
|
||||
icon_living = "guard"
|
||||
icon_dead = "guard_dead"
|
||||
has_eye_glow = TRUE
|
||||
|
||||
faction = "spiders"
|
||||
maxHealth = 200
|
||||
health = 200
|
||||
pass_flags = PASSTABLE
|
||||
movement_cooldown = 10
|
||||
|
||||
see_in_dark = 10
|
||||
|
||||
response_help = "pets"
|
||||
response_disarm = "gently pushes aside"
|
||||
response_harm = "punches"
|
||||
|
||||
melee_damage_lower = 18
|
||||
melee_damage_upper = 30
|
||||
attack_sharp = 1
|
||||
attack_edge = 1
|
||||
|
||||
heat_damage_per_tick = 20
|
||||
cold_damage_per_tick = 20
|
||||
|
||||
speak_emote = list("chitters")
|
||||
|
||||
meat_type = /obj/item/weapon/reagent_containers/food/snacks/xenomeat/spidermeat
|
||||
|
||||
say_list_type = /datum/say_list/spider
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee
|
||||
|
||||
var/poison_type = "spidertoxin" // The reagent that gets injected when it attacks.
|
||||
var/poison_chance = 10 // Chance for injection to occur.
|
||||
var/poison_per_bite = 5 // Amount added per injection.
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/apply_melee_effects(var/atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/L = A
|
||||
if(L.reagents)
|
||||
var/target_zone = pick(BP_TORSO,BP_TORSO,BP_TORSO,BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_HEAD)
|
||||
if(L.can_inject(src, null, target_zone))
|
||||
inject_poison(L, target_zone)
|
||||
|
||||
// Does actual poison injection, after all checks passed.
|
||||
/mob/living/simple_mob/animal/giant_spider/proc/inject_poison(mob/living/L, target_zone)
|
||||
if(prob(poison_chance))
|
||||
to_chat(L, "<span class='warning'>You feel a tiny prick.</span>")
|
||||
L.reagents.add_reagent(poison_type, poison_per_bite)
|
||||
|
||||
// Subtype
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// Nurses, they create webs and eggs.
|
||||
// They're fragile but their attacks can cause horrifying consequences.
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse
|
||||
desc = "Furry and beige, it makes you shudder to look at it. This one has brilliant green eyes."
|
||||
icon_state = "nurse"
|
||||
icon_living = "nurse"
|
||||
icon_dead = "nurse_dead"
|
||||
|
||||
maxHealth = 40
|
||||
health = 40
|
||||
|
||||
movement_cooldown = 5 // A bit faster so that they can inject the eggs easier.
|
||||
|
||||
melee_damage_lower = 5 // Doesn't do a lot of damage, since the goal is to make more spiders with egg attacks.
|
||||
melee_damage_upper = 10
|
||||
poison_per_bite = 5
|
||||
poison_type = "stoxin"
|
||||
|
||||
player_msg = "You can spin webs on an adjacent tile, or cocoon an object by clicking on it.<br>\
|
||||
You can also cocoon a dying or dead entity by clicking on them, and you will gain charges for egg-laying.<br>\
|
||||
To lay eggs, click a nearby tile. Laying eggs will deplete a charge."
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee/nurse_spider
|
||||
|
||||
var/fed = 0 // Counter for how many egg laying 'charges' the spider has.
|
||||
var/egg_inject_chance = 25 // One in four chance to get eggs.
|
||||
var/egg_type = /obj/effect/spider/eggcluster/small
|
||||
var/web_type = /obj/effect/spider/stickyweb/dark
|
||||
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/inject_poison(mob/living/L, target_zone)
|
||||
..() // Inject the stoxin here.
|
||||
if(ishuman(L) && prob(egg_inject_chance))
|
||||
var/mob/living/carbon/human/H = L
|
||||
var/obj/item/organ/external/O = H.get_organ(target_zone)
|
||||
if(O)
|
||||
var/eggcount = 0
|
||||
for(var/obj/effect/spider/eggcluster/E in O.implants)
|
||||
eggcount++
|
||||
if(!eggcount)
|
||||
var/eggs = new egg_type(O, src)
|
||||
O.implants += eggs
|
||||
to_chat(H, span("critical", "\The [src] injects something into your [O.name]!") ) // Oh god its laying eggs in me!
|
||||
|
||||
// Webs target in a web if able to.
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/attack_target(atom/A)
|
||||
if(isturf(A))
|
||||
if(fed)
|
||||
return lay_eggs(A)
|
||||
return web_tile(A)
|
||||
|
||||
if(isliving(A))
|
||||
var/mob/living/L = A
|
||||
if(!L.stat)
|
||||
return ..()
|
||||
|
||||
if(!istype(A, /atom/movable))
|
||||
return
|
||||
var/atom/movable/AM = A
|
||||
|
||||
if(AM.anchored)
|
||||
return ..()
|
||||
|
||||
return spin_cocoon(AM)
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/proc/spin_cocoon(atom/movable/AM)
|
||||
if(!istype(AM))
|
||||
return FALSE // We can't cocoon walls sadly.
|
||||
visible_message(span("notice", "\The [src] begins to secrete a sticky substance around \the [AM].") )
|
||||
|
||||
// Get our AI to stay still.
|
||||
set_AI_busy(TRUE)
|
||||
|
||||
if(!do_mob(src, AM, 5 SECONDS))
|
||||
set_AI_busy(FALSE)
|
||||
to_chat(src, span("warning", "You need to stay still to spin a web around \the [AM]."))
|
||||
return FALSE
|
||||
|
||||
set_AI_busy(FALSE)
|
||||
|
||||
if(!AM) // Make sure it didn't get deleted for whatever reason.
|
||||
to_chat(src, span("warning", "Whatever you were spinning a web for, its no longer there..."))
|
||||
return FALSE
|
||||
|
||||
if(!isturf(AM.loc))
|
||||
to_chat(src, span("warning", "You can't spin \the [AM] in a web while it is inside \the [AM.loc]."))
|
||||
return FALSE
|
||||
|
||||
if(!Adjacent(AM))
|
||||
to_chat(src, span("warning", "You need to be next to \the [AM] to spin it into a web."))
|
||||
return FALSE
|
||||
|
||||
// Finally done with the checks.
|
||||
var/obj/effect/spider/cocoon/C = new(AM.loc)
|
||||
var/large_cocoon = FALSE
|
||||
for(var/mob/living/L in C.loc)
|
||||
if(istype(L, /mob/living/simple_mob/animal/giant_spider)) // Cannibalism is bad.
|
||||
continue
|
||||
fed++
|
||||
visible_message(span("warning","\The [src] sticks a proboscis into \the [L], and sucks a viscous substance out."))
|
||||
to_chat(src, span("notice", "You've fed upon \the [L], and can now lay [fed] cluster\s of eggs."))
|
||||
L.forceMove(C)
|
||||
large_cocoon = TRUE
|
||||
break
|
||||
|
||||
// This part's pretty stupid.
|
||||
for(var/obj/O in C.loc)
|
||||
if(!O.anchored)
|
||||
O.forceMove(C)
|
||||
|
||||
// Todo: Put this code on the cocoon object itself?
|
||||
if(large_cocoon)
|
||||
C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3")
|
||||
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/handle_special()
|
||||
set waitfor = FALSE
|
||||
if(get_AI_stance() == STANCE_IDLE && !is_AI_busy() && isturf(loc))
|
||||
if(fed)
|
||||
lay_eggs(loc)
|
||||
else
|
||||
web_tile(loc)
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/proc/web_tile(turf/T)
|
||||
if(!istype(T))
|
||||
return FALSE
|
||||
|
||||
var/obj/effect/spider/stickyweb/W = locate() in T
|
||||
if(W)
|
||||
return FALSE // Already got webs here.
|
||||
|
||||
visible_message(span("notice", "\The [src] begins to secrete a sticky substance.") )
|
||||
// Get our AI to stay still.
|
||||
set_AI_busy(TRUE)
|
||||
|
||||
if(!do_mob(src, T, 5 SECONDS))
|
||||
set_AI_busy(FALSE)
|
||||
to_chat(src, span("warning", "You need to stay still to spin a web on \the [T]."))
|
||||
return FALSE
|
||||
|
||||
W = locate() in T
|
||||
if(W)
|
||||
return FALSE // Spamclick protection.
|
||||
|
||||
set_AI_busy(FALSE)
|
||||
new web_type(T)
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/living/simple_mob/animal/giant_spider/nurse/proc/lay_eggs(turf/T)
|
||||
if(!istype(T))
|
||||
return FALSE
|
||||
|
||||
if(!fed)
|
||||
return FALSE
|
||||
|
||||
var/obj/effect/spider/eggcluster/E = locate() in T
|
||||
if(E)
|
||||
return FALSE // Already got eggs here.
|
||||
|
||||
visible_message(span("notice", "\The [src] begins to lay a cluster of eggs.") )
|
||||
// Get our AI to stay still.
|
||||
set_AI_busy(TRUE)
|
||||
|
||||
if(!do_mob(src, T, 5 SECONDS))
|
||||
set_AI_busy(FALSE)
|
||||
to_chat(src, span("warning", "You need to stay still to lay eggs on \the [T]."))
|
||||
return FALSE
|
||||
|
||||
E = locate() in T
|
||||
if(E)
|
||||
return FALSE // Spamclick protection.
|
||||
|
||||
set_AI_busy(FALSE)
|
||||
new egg_type(T)
|
||||
fed--
|
||||
return TRUE
|
||||
|
||||
|
||||
// Variant that 'blocks' light (by being a negative light source).
|
||||
// This is done to make webbed rooms scary and allow for spiders on the other side of webs to see prey.
|
||||
/obj/effect/spider/stickyweb/dark
|
||||
name = "dense web"
|
||||
desc = "It's sticky, and blocks a lot of light."
|
||||
light_color = "#FFFFFF"
|
||||
light_range = 2
|
||||
light_power = -3
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Hooligan Crabs are called so because they are rather curious and tend to follow people,
|
||||
whether the people want them to or not, and sometimes causing vandalism by accident.
|
||||
They're pretty strong and have strong melee armor, but won't attack first.
|
||||
They unknowingly play a role in keeping the shoreline fairly safe, by killing whatever would attack other people.
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/animal/sif/hooligan_crab
|
||||
name = "hooligan crab"
|
||||
desc = "A large, hard-shelled crustacean. This one is mostly grey."
|
||||
icon_state = "sif_crab"
|
||||
icon_living = "sif_crab"
|
||||
icon_dead = "sif_crab_dead"
|
||||
icon_scale = 1.5
|
||||
|
||||
faction = "crabs"
|
||||
|
||||
maxHealth = 200
|
||||
health = 200
|
||||
movement_cooldown = 10
|
||||
movement_sound = 'sound/weapons/heavysmash.ogg'
|
||||
armor = list(
|
||||
"melee" = 40,
|
||||
"bullet" = 20,
|
||||
"laser" = 10,
|
||||
"energy" = 0,
|
||||
"bomb" = 0,
|
||||
"bio" = 0,
|
||||
"rad" = 0
|
||||
)
|
||||
armor_soak = list(
|
||||
"melee" = 10,
|
||||
"bullet" = 5,
|
||||
"laser" = 0,
|
||||
"energy" = 0,
|
||||
"bomb" = 0,
|
||||
"bio" = 0,
|
||||
"rad" = 0
|
||||
)
|
||||
|
||||
mob_size = MOB_LARGE
|
||||
|
||||
melee_damage_lower = 22
|
||||
melee_damage_upper = 35
|
||||
attack_armor_pen = 35
|
||||
attack_sharp = 1
|
||||
attack_edge = 1
|
||||
|
||||
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
|
||||
response_help = "pets"
|
||||
response_disarm = "gently pushes aside"
|
||||
response_harm = "kicks"
|
||||
friendly = "pinches"
|
||||
attacktext = list("clawed", "pinched", "crushed")
|
||||
speak_emote = list("clicks")
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee/hooligan
|
||||
say_list_type = /datum/say_list/crab
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Mobs intended to be on Sif. As such, they won't die to the cold.
|
||||
/mob/living/simple_mob/animal/sif
|
||||
minbodytemp = 175
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Combat drones have a rapid ranged attack, and have a projectile shield.
|
||||
They are rather slow, but attempt to 'kite' its target.
|
||||
A solid hit with an EMP grenade will kill the shield instantly.
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/combat_drone
|
||||
name = "combat drone"
|
||||
desc = "An automated combat drone armed with state of the art weaponry and shielding."
|
||||
icon_state = "drone"
|
||||
icon_living = "drone"
|
||||
icon_dead = "drone_dead"
|
||||
has_eye_glow = TRUE
|
||||
|
||||
faction = "malf_drone"
|
||||
|
||||
maxHealth = 50 // Shield has 150 for total of 200.
|
||||
health = 50
|
||||
movement_cooldown = 5
|
||||
hovering = TRUE
|
||||
|
||||
base_attack_cooldown = 5
|
||||
projectiletype = /obj/item/projectile/beam/drone
|
||||
projectilesound = 'sound/weapons/laser3.ogg'
|
||||
|
||||
response_help = "pokes"
|
||||
response_disarm = "gently pushes aside"
|
||||
response_harm = "hits"
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening
|
||||
say_list_type = /datum/say_list/malf_drone
|
||||
|
||||
var/datum/effect/effect/system/ion_trail_follow/ion_trail = null
|
||||
var/obj/item/shield_projector/shields = null
|
||||
|
||||
/mob/living/simple_mob/mechanical/combat_drone/initialize()
|
||||
ion_trail = new
|
||||
ion_trail.set_up(src)
|
||||
ion_trail.start()
|
||||
|
||||
shields = new /obj/item/shield_projector/rectangle/automatic/drone(src)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/combat_drone/Destroy()
|
||||
qdel_null(ion_trail)
|
||||
qdel_null(shields)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/combat_drone/death()
|
||||
..(null,"suddenly breaks apart.")
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_mob/mechanical/combat_drone/Process_Spacemove(var/check_drift = 0)
|
||||
return TRUE
|
||||
|
||||
/obj/item/projectile/beam/drone
|
||||
damage = 10
|
||||
|
||||
/obj/item/shield_projector/rectangle/automatic/drone
|
||||
shield_health = 150
|
||||
max_shield_health = 150
|
||||
shield_regen_delay = 10 SECONDS
|
||||
shield_regen_amount = 10
|
||||
size_x = 1
|
||||
size_y = 1
|
||||
|
||||
// A slightly easier drone, for POIs.
|
||||
// Difference is that it should not be faster than you.
|
||||
/mob/living/simple_mob/mechanical/combat_drone/lesser
|
||||
desc = "An automated combat drone with an aged apperance."
|
||||
movement_cooldown = 10
|
||||
@@ -0,0 +1,20 @@
|
||||
/mob/living/simple_mob/mechanical
|
||||
mob_class = MOB_CLASS_CONSTRUCT
|
||||
|
||||
min_oxy = 0
|
||||
max_oxy = 0
|
||||
min_tox = 0
|
||||
max_tox = 0
|
||||
min_co2 = 0
|
||||
max_co2 = 0
|
||||
min_n2 = 0
|
||||
max_n2 = 0
|
||||
minbodytemp = 0
|
||||
|
||||
taser_kill = FALSE
|
||||
|
||||
/mob/living/simple_mob/mechanical/isSynthetic()
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_mob/mechanical/speech_bubble_appearance()
|
||||
return faction != "neutral" ? "synthetic_evil" : "machine"
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Viscerators are fragile and don't hit very hard, but fast, evasive, and rarely come alone.
|
||||
They also tend to dodge while in melee range.
|
||||
A weapon that can cleave is very effective against them.
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/viscerator
|
||||
name = "viscerator"
|
||||
desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations."
|
||||
icon = 'icons/mob/critter.dmi'
|
||||
icon_state = "viscerator_attack"
|
||||
icon_living = "viscerator_attack"
|
||||
hovering = TRUE // Won't trigger landmines.
|
||||
|
||||
faction = "syndicate"
|
||||
maxHealth = 15
|
||||
health = 15
|
||||
movement_cooldown = 0
|
||||
|
||||
pass_flags = PASSTABLE
|
||||
mob_swap_flags = 0
|
||||
mob_push_flags = 0
|
||||
|
||||
melee_damage_lower = 4 // Approx 8 DPS.
|
||||
melee_damage_upper = 4
|
||||
base_attack_cooldown = 5 // Two attacks a second or so.
|
||||
attack_sharp = 1
|
||||
attack_edge = 1
|
||||
attack_sound = 'sound/weapons/bladeslice.ogg'
|
||||
attacktext = list("cut", "sliced")
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee/evasive
|
||||
|
||||
/mob/living/simple_mob/mechanical/viscerator/death()
|
||||
..(null,"is smashed into pieces!")
|
||||
qdel(src)
|
||||
@@ -13,6 +13,11 @@
|
||||
/mob/proc/setMoveCooldown(var/timeout)
|
||||
move_delay = max(world.time + timeout, move_delay)
|
||||
|
||||
/mob/proc/check_move_cooldown()
|
||||
if(world.time < src.move_delay)
|
||||
return FALSE // Need to wait more.
|
||||
return TRUE
|
||||
|
||||
/client/North()
|
||||
..()
|
||||
|
||||
@@ -198,7 +203,8 @@
|
||||
|
||||
if(moving) return 0
|
||||
|
||||
if(world.time < mob.move_delay) return
|
||||
if(!mob.check_move_cooldown())
|
||||
return
|
||||
|
||||
if(locate(/obj/effect/stop/, mob.loc))
|
||||
for(var/obj/effect/stop/S in mob.loc)
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
but allow those projectiles to leave the shield from the inside. Blocking too many damaging projectiles will cause the shield to fail."
|
||||
icon = 'icons/obj/device.dmi'
|
||||
icon_state = "signmaker_sec"
|
||||
light_range = 4
|
||||
light_power = 4
|
||||
var/active = FALSE // If it's on.
|
||||
var/shield_health = 400 // How much damage the shield blocks before breaking. This is a shared health pool for all shields attached to this projector.
|
||||
var/max_shield_health = 400 // Ditto. This is fairly high, but shields are really big, you can't miss them, and laser carbines pump out so much hurt.
|
||||
@@ -170,6 +172,8 @@
|
||||
|
||||
var/new_color = rgb(new_r, new_g, new_b)
|
||||
|
||||
set_light(light_range, light_power, new_color)
|
||||
|
||||
// Now deploy the new color to all the shields.
|
||||
for(var/obj/effect/directional_shield/S in active_shields)
|
||||
S.update_color(new_color)
|
||||
|
||||
Reference in New Issue
Block a user