Begins replacing simple_animal with simple_mob.

This commit is contained in:
Neerti
2018-04-22 17:25:46 -04:00
parent bac28e72de
commit 5772ad84ac
41 changed files with 2196 additions and 96 deletions
+6
View File
@@ -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)
..()
*/
+28 -1
View File
@@ -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
+15 -5
View File
@@ -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)
+29 -22
View File
@@ -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
-5
View File
@@ -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
+1 -1
View File
@@ -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!
+9 -1
View File
@@ -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
+32 -18
View File
@@ -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)
+51 -9
View File
@@ -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...
*/
+29 -5
View File
@@ -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.
+51 -2
View File
@@ -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")
+4
View File
@@ -1,3 +1,7 @@
/mob/living/death()
clear_fullscreens()
if(ai_holder)
ai_holder.go_sleep()
. = ..()
+14
View File
@@ -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
+5
View File
@@ -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 .
+7 -2
View File
@@ -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
@@ -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
+123
View File
@@ -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
+159
View File
@@ -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)
+7 -1
View File
@@ -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)