Fixes loads of AI bugs.

This commit is contained in:
Neerti
2018-10-31 17:43:10 -04:00
parent 1e492e8c44
commit 508b051716
52 changed files with 531 additions and 188 deletions
+42 -21
View File
@@ -170,10 +170,21 @@
qdel(buildquit)
buildquit = null
throw_atom = null
cl = null
for(var/mob/living/unit in selected_mobs)
deselect_AI_mob(cl, unit)
selected_mobs.Cut()
cl = null
return ..()
/obj/effect/bmode/buildholder/proc/select_AI_mob(client/C, mob/living/unit)
selected_mobs += unit
C.images += unit.selected_image
/obj/effect/bmode/buildholder/proc/deselect_AI_mob(client/C, mob/living/unit)
selected_mobs -= unit
C.images -= unit.selected_image
/obj/effect/bmode/buildmode
icon_state = "buildmode1"
screen_loc = "NORTH,WEST+2"
@@ -460,48 +471,58 @@
// Select/Deselect
if(!isnull(L.get_AI_stance()))
if(L in holder.selected_mobs)
// Todo: Select graphic only the admin can see?
holder.selected_mobs -= L
user.client.images -= L.selected_image
holder.deselect_AI_mob(user.client, L)
to_chat(user, span("notice", "Deselected \the [L]."))
else
holder.selected_mobs += L
user.client.images += L.selected_image
holder.select_AI_mob(user.client, L)
to_chat(user, span("notice", "Selected \the [L]."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
if(pa.Find("right"))
if(istype(object, /atom/movable)) // Force attack.
var/atom/movable/AM = object
if(istype(object, /atom)) // Force attack.
var/atom/A = object
if(pa.Find("alt"))
for(var/thing in holder.selected_mobs)
var/mob/living/unit = thing
var/i = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
AI.give_target(AM)
to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to attack \the [AM]."))
AI.give_target(A)
i++
to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A]."))
return
if(isliving(object)) // Follow or attack.
var/mob/living/L = object
for(var/thing in holder.selected_mobs)
var/mob/living/unit = thing
var/i = 0 // Attacking mobs.
var/j = 0 // Following mobs.
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
if(L.IIsAlly(unit) || !AI.hostile || pa.Find("shift"))
AI.set_follow(L)
j++
else
AI.give_target(L)
to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to attack or follow \the [L]."))
i++
var/message = "Commanded "
if(i)
message += "[i] mob\s to attack \the [L]"
if(j)
message += ", and "
else
message += "."
if(j)
message += "[j] mob\s to follow \the [L]."
to_chat(user, span("notice", message))
if(isturf(object)) // Move or reposition.
var/turf/T = object
for(var/thing in holder.selected_mobs)
var/mob/living/unit = thing
var/i = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
AI.give_destination(T, 1, pa.Find("shift"))
to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to move to \the [T]."))
AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy.
i++
to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T]."))
/obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path)
+178 -34
View File
@@ -1,57 +1,201 @@
/*
[Summary]
This module contains an AI implementation designed to be (mostly) mobtype-agnostic, by being held inside a datum instead of being on the mob directly.
This module contains an AI implementation designed to be (at the base level) mobtype-agnostic,
by being held inside a datum instead of being written into the mob directly. More specialized
subtypes of the base AI may be designed with a specific mob type in mind, but the base system
should be compatible with most types of mobs which have the needed Interfaces in place to
support them.
When designing a new mob, all that is needed to give a mob an AI is to set
its 'ai_holder_type' variable to the path of the AI that is desired.
Seperation:
[Seperation]
The ai_holder datum is designed to be fairly distant from its mob holder, in terms of coupling.
This presents some advantages.
* Not being tied to the mob's Life() cycle allows for a different tick rate.
* Being seperate from the mob simplifies mob code greatly.
* It allows for better encapsulation and seperation of duties from the mob.
* It is more logical to think that the mob is the 'body', where as its ai_holder is the 'mind'.
* It can be made mobtype-independant with the use of Interfaces.
In previous iterations of AI systems, the AI is generally written into the mob's code directly,
which has some advantages, but often makes the code rigid, and also tied the speed of the AI
to the mob's own ticker, meaning it could only decide every two seconds.
Instead, this version has the code for the AI held inside an /datum/ai_holder object,
which is carried by the mob it controls. This gives some advantages;
All /mob/living mobs can potentially have an AI applied to them, and utilize the
same base code while adding specialized code on top.
Interfaces allow the base AI code to not need to know what particular mode it's controlling.
The processing of the AI is independant of the mob's Life() cycle, which allows for a
different clock rate.
Seperating the AI from the mob simplies the mob's code greatly.
It is more logical to think that a mob is the 'body', where as its ai_holder is
the 'mind'.
AIs can be applied or disabled on the fly by instantiating or deleting the
ai_holder, if needed.
The datum is held by the mob that it should control, called the holder, and is not processed by the mob itself (as Life() did so in previous implementations).
Instead, each instance of /datum/ai_holder is processed by the 'AI' master controller subsystem. The datum itself has two seperate process tracks instead of one,
as most other objects ( process() ) or mobs ( Life() ) do.
The current implementation also has some disadvantages, but they can perhaps be resolved
in the future.
AI-driven mob movement and attack speed is tied to half-second delays due to the
AI subsystem ticking at that rate. Porting the timer subsystem and integrating
callbacks into basic AI actions (moving, attacking) can potentially resolve that.
It can be difficult to modify AI variables at mob instantiation without an ugly
delay, as the ai_holder might not exist yet.
[Flow of Processing]
Terrible visual representation here;
AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)...
-> Every 2.0s -> /datum/ai_holder/handle_strategicals() -> switch(stance)...
The AI datum is not processed by the mob itself, but instead it is directly processed
by a new AI subsystem. The AI subsystem contains a list of all active ai_holder
objects, which is iterated every tick to process each individual ai_holder
object attached to a mob.
Each ai_holder actually has two 'tracks' for processing, a 'fast' track
and a 'slow' track.
The fast track is named handle_tactics(), and is called every 0.5 seconds.
The slow track is named handle_strategicals(), and is called every 2 seconds.
When an ai_holder is iterated on inside the AI subsystem's list, it first
calls that ai_holder's handle_tactics(). It will then call that ai_holder's
handle_strategicals() every fourth tick, effectively doing so every two seconds.
Both functions do different things depending on which 'stance' the
ai_holder is in. See the Stances section for more information.
The fast track is for 'cheap' processing that needs to happen fast, such as
walking along a path, initiating an attack, or firing a gun. The rate that
it is called allows for the ai_holder to interact with the world through
its mob very often, giving a more convincing appearance of intelligence,
allowing for faster reaction times to certain events, and allowing for
variable attack speeds that would not be possible when bound to a
two second Life() cycle.
The slow track, on the other hand, is for 'expensive' processing that might
be too demanding on the CPU to do every half a second, such as
re/calculating an A* path (if the mob uses A*), or running a complicated
tension assessment to determine how brave the mob is feeling. This is the
same delay used for certain tasks in the old implementation, but it is less
noticable due to the mob appearing to do things inbetween those two seconds.
The purpose of having two tracks is to allow for 'fast' and 'slow' actions
to be more easily encapsulated, and ensures that all ai_holders are syncronized
with each other, as opposed to having individual tick counters inside all of
the ai_holder instances. It should be noted that handle_tactics() is always
called first, before handle_strategicals() every two seconds.
[Process Skipping]
An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state,
in order to avoid processing.
When busy, the AI subsystem will skip over the ai_holder until it is no
longer busy. The busy state is intended to be short-term, and is usually
toggled by the mob when doing something with a delay, so that the ai_holder
does not accidentally do something to inturrupt something important, like
a special attack.
The longer term alternative to the busy state is the sleep state. Unlike
being busy, an ai_holder set to sleep will remove itself from the
AI subsystem's list, meaning it will no longer process until something
else 'wakes' it. This is usually done when the mob dies or a client
logs into an AI-controlled mob (and the AI is not set to ignore that,
with the autopilot variable). If the mob is revived, the AI will be
awakened automatically.
The ai_holder functions, and mob functions that are called by the
ai_holder, should not be sleep()ed, as it will block the AI Subsystem
from processing the other ai_holders until the sleep() finishes.
Delays on the mob typically have set waitfor = FALSE, or spawn() is used.
Flow of Execution:
[Stances]
AI Subsystem
- > Every 0.5s - > /datum/ai_holder/handle_tactics() - > /datum/ai_holder/handle_stance_tactical() - > switch(stance)...
- > Every 2.0s - > /datum/ai_holder/handle_strategicals() - > /datum/ai_holder/handle_stance_strategical() - > switch(stance)...
The AI has a large number of states that it can be in, called stances.
The AI will act in a specific way depending on which stance it is in,
and only one stance can be active at a time. This effectively creates
a state pattern.
The datum is not driven by its mob, as previous implementations did, meaning Life() is not involved. Instead, it is processed by a specific Master Controller Subsystem
titled 'AI', which by default ticks every half a second. Each instance of the ai_holder datum that is not 'asleep' is part of a list, containing ai_holders that are awake.
When the subsystem runs, each ai_holder instance inside the list is iterated on, and calls one or two procs on it.
Every tick, each instance has handle_tactics() called on it, and every four ticks, handle_strategicals(), meaning every half a second and very two seconds, respectively.
This means that the ai_holder datum has two seperate types of processing, for the purposes of efficency.
To change the stance, set_stance() is used, with the new stance as
the first argument. It should be noted that the change is not immediate,
and it will react to the change next tick instead of immediately switching
to the new stance and acting on that in the same tick. This is done to help
avoid infinite loops (I.E. Stance A switches to Stance B, which then
switches to Stance A, and so on...), and the delay is very short so
it should not be an issue.
handle_tactics() is used for 'cheap' processing that needs to happen fast, such as walking along a path, initiating an attack, or firing a gun.
The rate that it is called allows for the ai_holder to interact with the world through its mob very often, giving a more convincing appearance of intelligence,
allowing for faster reaction times to certain events, and allowing for variable attack speeds that would not be possible when bound to a two second Life() cycle.
See code/__defines/mob.dm for a list of stance defines, and descriptions
about their purpose. Generally, each stance has its own file in the AI
module folder and are mostly self contained, however some files instead
deal with general things that other stances may require, such as targeting
or movement.
handle_strategicals() on the other hand, is for 'expensive' processing that might be too demanding on the CPU to do every half a second, such as re/calculating an A* path, or
running a complicated tension assessment to determine how brave the mob is feeling. This is the same delay used for certain tasks in the old implementation.
[Interfaces]
Having two seperate process procs allows for 'fast' and 'slow' actions to be more easily encapsulated, and ensures that all ai_holders are syncronized with each other,
as opposed to having individual tick counters inside all of the ai_holder instances. It should be noted that handle_tactics() is called first,
before handle_strategicals() every two seconds.
Interfaces are a concept that is used to help bridge the gap between
the ai_holder, and its mob. Because the (base) ai_holder is explicitly
designed to not be specific to any type of mob, all that it knows is
that it is controlling a /mob/living mob. Some mobs work very differently,
between mob types such as /mob/living/simple_mob, /mob/living/silicon/robot,
/mob/living/carbon/human, and more.
Both process procs work in a similar fashion, using a large amount of 'stances' to act in a specific way, effectively creating a state pattern.
There are 10 stances implemented, and more can easily be added by defining them and switching to them with the set_stance() proc.
This is similar to the old implementation, except with a vastly larger amount of options for the AI to make use of.
See code/__defines/mob.dm for the stance defines and descriptions about their purpose.
The solution to the vast differences between mob types is to have the
mob itself deal with how to handle a specific task, such as attacking
something, talking, moving, etc. Interfaces exist to do this.
Each stance is evaluated once per tick, meaning that switching the stance takes effect on the next tick.
Interfaces are applied on the mob-side, and are generally specific to
that mob type. This lets the ai_holder not have to worry about specific
implementations and instead just tell the Interface that it wants to attack
something, or move into a tile. The AI does not need to know if the mob its
controlling has hands, instead that is the mob's responsibility.
Interface functions have an uppercase I at the start of the function name,
and then the function they are bridging between the AI and the mob
(if it exists), e.g. IMove(), IAttack(), ISay().
Interfaces are also used for the AI to ask its mob if it can do certain
things, without having to actually know what type of mob it is attached to.
For example, ICheckRangedAttack() tells the AI if it is possible to do a
ranged attack. For simple_mobs, they can if a ranged projectile type was set,
where as for a human mob, it could check if a gun is in a hand. For a borg,
it could check if a gun is inside their current module.
[Say List]
A /datum/say_list is a very light datum that holds a list of strings for the
AI to have their mob say based on certain conditions, such as when threatening
to kill another mob. Despite the name, a say_list also can contain emotes
and some sounds.
The reason that it is in a seperate datum is to allow for multiple mob types
to have the same text, even when inheritence cannot do that, such as
mercenaries and fake piloted mecha mobs.
The say_list datum is applied to the mob itself and not held inside the AI datum.
[Subtypes]
Some subtypes of ai_holder are more specialized, but remain compatible with
most mob types. There are many different subtypes that make the AI act different
by overriding a function, such as kiting their target, moving up close while
using ranged attacks, or running away if not cloaked.
Other subtypes are very specific about what kind of mob it controls, and trying
to apply them to a different type of mob will likely result in a lot of bugs
or ASSERT() failures. The xenobio slime AI is an example of the latter.
To use a specific subtype on a mob, all that is needed is setting the mob's
ai_holder_type to the subtype desired, and it will create that subtype when
the mob is initialize()d. Switching to a subtype 'live' will require additional
effort on the coder.
*/
+4 -6
View File
@@ -2,7 +2,7 @@
// Controls if the mob will do 'advanced tactics' like running from grenades.
#define AI_DUMB 1 // Be dumber than usual.
#define AI_NORMAL 2 // Default level.
#define AI_SMART 3 // Will do more processing to perceive threats such as active grenades. Use sparingly.
#define AI_SMART 3 // Will do more processing to be a little smarter, like not walking while confused if it could risk stepping randomly onto a bad tile.
#define ai_log(M,V) if(debug_ai) ai_log_output(M,V)
@@ -12,13 +12,13 @@
#define AI_LOG_WARNING 2 // Show less serious but still helpful to know issues that might be causing things to work incorrectly.
#define AI_LOG_INFO 3 // Important regular events, like selecting a target or switching stances.
#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution.
#define AI_LOG_TRACE 5 // Even more detailed than the last.
#define AI_LOG_TRACE 5 // Even more detailed than the last. Will absolutely flood your chatlog.
// 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.
#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.
@@ -27,5 +27,3 @@
#define AI_TARGET_ALLY 3 // They are an ally. Find a new target.
#define AI_TARGET_DEAD 4 // They're dead. Find a new target.
#define AI_TARGET_INVINCIBLE 5 // Target is currently unable to receive damage for whatever reason. Find a new target or wait.
//TRACE DEBUG INFO WARN ERROR OFF
@@ -13,6 +13,7 @@
/datum/ai_holder/simple_mob/passive
hostile = FALSE
can_flee = TRUE
violent_breakthrough = FALSE
// For parrots like Poly.
// They modify their say_list datum based on what their mob hears.
@@ -21,10 +22,14 @@
base_wander_delay = 8
/datum/ai_holder/simple_mob/passive/parrot/on_hear_say(mob/living/speaker, message)
if(holder.stat || !holder.say_list || !message)
if(holder.stat || !holder.say_list || !message || speaker == holder)
return
var/datum/say_list/S = holder.say_list
S.speak += message
S.speak |= message
// Won't wander away, ideal for event-spawned mobs like carp or drones.
/datum/ai_holder/simple_mob/event
wander = FALSE
// Doesn't really act until told to by something on the outside.
/datum/ai_holder/simple_mob/inert
@@ -34,6 +39,7 @@
wander = FALSE
speak_chance = 0
cooperative = FALSE
violent_breakthrough = FALSE // So it can open doors but not attack windows and shatter the literal illusion.
// Used for technomancer illusions, to resemble player movement better.
/datum/ai_holder/simple_mob/inert/astar
@@ -63,6 +69,9 @@
/datum/ai_holder/simple_mob/ranged/careful
conserve_ammo = TRUE
/datum/ai_holder/simple_mob/ranged/pointblank
pointblank = 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().
@@ -73,6 +82,11 @@
threaten = TRUE
threaten_delay = 1 SECOND // Less of a threat and more of pre-attack notice.
threaten_timeout = 30 SECONDS
conserve_ammo = TRUE
// For event-spawned malf drones.
/datum/ai_holder/simple_mob/ranged/kiting/threatening/event
wander = FALSE
/datum/ai_holder/simple_mob/ranged/kiting/no_moonwalk
moonwalk = FALSE
@@ -151,12 +165,11 @@
// If we're surrounded, Electric Defense will quickly fix that.
var/tally = 0
for(var/mob/living/L in hearers(electric_defense_radius, holder))
if(holder == L)
var/list/potential_targets = list_targets() // Returns list of mobs and certain objects like mechs and turrets.
for(var/atom/movable/AM in potential_targets)
if(get_dist(holder, AM) > electric_defense_radius)
continue
if(L.IIsAlly(holder))
continue
if(L.stat)
if(!can_attack(AM))
continue
tally++
@@ -168,14 +181,15 @@
// Otherwise they're a fair distance away and we're not getting mobbed up close.
// See if we should use missiles or microsingulo.
tally = 0 // Let's recycle the var.
for(var/mob/living/L in hearers(microsingulo_radius, target))
if(holder == L)
for(var/atom/movable/AM in potential_targets)
if(get_dist(target, AM) > microsingulo_radius) // Deliberately tests distance between target and nearby targets and not the holder.
continue
if(L.IIsAlly(holder))
if(!can_attack(AM))
continue
if(L.stat)
continue
tally++
if(AM.anchored) // Microsingulo doesn't do anything to anchored things.
tally--
else
tally++
// Lots of people means minisingulo would be more useful.
if(tally >= microsingulo_threshold)
@@ -187,6 +201,10 @@
holder.a_intent = I_HURT // Fire rockets if it's an obj/turf.
// These try to avoid collateral damage.
/datum/ai_holder/simple_mob/restrained
violent_breakthrough = FALSE
conserve_ammo = TRUE
// Melee mobs.
@@ -222,6 +240,7 @@
/datum/ai_holder/simple_mob/melee/nurse_spider
wander = TRUE
base_wander_delay = 8
cooperative = FALSE // So we don't ask our spider friends to attack things we're webbing. This might also make them stay at the base if their friends find tasty explorers.
// Get us unachored objects as an option as well.
/datum/ai_holder/simple_mob/melee/nurse_spider/list_targets()
@@ -333,6 +352,7 @@
*/
/datum/ai_holder/simple_mob/hivebot
pointblank = TRUE
conserve_ammo = TRUE
firing_lanes = TRUE
can_flee = FALSE // Fearless dumb machines.
@@ -8,12 +8,13 @@
var/rabid = FALSE // Will attack regardless of discipline.
var/discipline = 0 // Beating slimes makes them less likely to lash out. In theory.
var/resentment = 0 // 'Unjustified' beatings make this go up, and makes it more likely for abused slimes to go rabid.
var/obedience = 0 // Conversely, 'justified' beatings make this go up, and makes discipline decay slowly, potentially making it not decay at all.
var/obedience = 0 // Conversely, 'justified' beatings make this go up, and makes discipline decay slower, potentially making it not decay at all.
var/always_stun = FALSE // If true, the slime will elect to attempt to permastun the target.
/datum/ai_holder/simple_mob/xenobio_slime/sapphire
always_stun = TRUE // They know that stuns are godly.
intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death.
/datum/ai_holder/simple_mob/xenobio_slime/light_pink
discipline = 5
@@ -255,3 +256,7 @@
*/
ai_log("xenobio_slime/on_hear_say() : Exited.", AI_LOG_DEBUG)
/datum/ai_holder/simple_mob/xenobio_slime/can_violently_breakthrough()
if(discipline && !rabid) // Good slimes don't shatter the windows because their buddy in an adjacent cell decided to piss off Slimesky.
return FALSE
return ..()
+81 -30
View File
@@ -7,7 +7,8 @@
var/shoot_range = 5 // How close the mob needs to be to attempt to shoot at the enemy, if the mob is capable of ranged attacks.
var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack.
var/can_breakthrough = TRUE // If false, the AI will not try to break things like windows or other structures in the way.
var/can_breakthrough = TRUE // If false, the AI will not try to open a path to its goal, like opening doors.
var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true.
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
@@ -196,7 +197,8 @@
// Otherwise keep walking.
walk_path(target, get_to)
if(!stand_ground)
walk_path(target, get_to)
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
@@ -206,53 +208,102 @@
holder.resist()
// Used to break through windows and barriers to a target on the other side.
// This does two passes, so that if its just a public access door, the windows nearby don't need to be smashed.
/datum/ai_holder/proc/breakthrough(atom/target_atom)
ai_log("breakthrough() : Entering", AI_LOG_TRACE)
if(!can_breakthrough)
ai_log("breakthrough() : Not allowed to breakthrough. Exiting.", AI_LOG_TRACE)
return FALSE
if(!isturf(holder.loc))
ai_log("breakthrough() : Trapped inside \the [holder.loc]. Exiting.", AI_LOG_TRACE)
return FALSE
var/dir_to_target = get_dir(holder, target_atom)
holder.face_atom(target_atom)
ai_log("breakthrough() : Exiting", AI_LOG_DEBUG)
// First, try to break things directly in front of us.
var/result = destroy_surroundings(dir_to_target)
// Sometimes the mob will try to hit something diagonally, and generally this fails.
// So instead we will try two more times with some adjustments if the attack fails.
var/list/directions_to_try = list(
dir_to_target,
turn(dir_to_target, 45),
turn(dir_to_target, -45)
)
// If that doesn't work, we might be trying to attack something diagonally.
// If so, we can try again with some adjustments to avoid invalid diagonal directions.
if(!result)
result = destroy_surroundings(turn(dir_to_target, 45))
ai_log("breakthrough() : Starting peaceful pass.", AI_LOG_DEBUG)
// One last time, going the other way.
if(!result)
result = destroy_surroundings(turn(dir_to_target, -45))
var/result = FALSE
// Welp.
// First, we will try to peacefully make a path, I.E opening a door we have access to.
for(var/direction in directions_to_try)
result = destroy_surroundings(direction, violent = FALSE)
if(result)
break
// Alright, lets smash some shit instead, if it didn't work and we're allowed to be violent.
if(!result && can_violently_breakthrough())
ai_log("breakthrough() : Starting violent pass.", AI_LOG_DEBUG)
for(var/direction in directions_to_try)
result = destroy_surroundings(direction, violent = TRUE)
if(result)
break
ai_log("breakthrough() : Exiting with [result].", AI_LOG_TRACE)
return result
/datum/ai_holder/proc/destroy_surroundings(direction)
// Despite the name, this can also be used to help clear a path without any destruction.
/datum/ai_holder/proc/destroy_surroundings(direction, violent = TRUE)
ai_log("destroy_surroundings() : Entering.", AI_LOG_TRACE)
if(!direction)
direction = pick(cardinal) // FLAIL WILDLY
ai_log("destroy_surroundings() : No direction given, picked [direction] randomly.", AI_LOG_DEBUG)
var/turf/problem_turf = get_step(holder, direction)
// First, kill windows in the way.
for(var/obj/structure/window/W in problem_turf)
if(W.dir == reverse_dir[holder.dir]) // So that windows get smashed in the right order
ai_log("destroy_surroundings() : Attacking diagonal window.", AI_LOG_INFO)
return holder.IAttack(W)
// First, give peace a chance.
if(!violent)
ai_log("destroy_surroundings() : Going to try to peacefully clear [problem_turf].", AI_LOG_DEBUG)
for(var/obj/machinery/door/D in problem_turf)
if(D.density && holder.Adjacent(D) && D.allowed(holder) && D.operable())
// First, try to open the door if possible without smashing it. We might have access.
ai_log("destroy_surroundings() : Opening closed door.", AI_LOG_INFO)
return D.open()
else if(W.is_fulltile())
ai_log("destroy_surroundings() : Attacking full tile window.", AI_LOG_INFO)
return holder.IAttack(W)
// Peace has failed us, can we just smash the things in the way?
else
ai_log("destroy_surroundings() : Going to try to violently clear [problem_turf].", AI_LOG_DEBUG)
// First, kill windows in the way.
for(var/obj/structure/window/W in problem_turf)
if(W.dir == reverse_dir[holder.dir]) // So that windows get smashed in the right order
ai_log("destroy_surroundings() : Attacking side window.", AI_LOG_INFO)
return holder.IAttack(W)
var/obj/structure/obstacle = locate(/obj/structure, problem_turf)
if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))
ai_log("destroy_surroundings() : Attacking generic structure.", AI_LOG_INFO)
return holder.IAttack(obstacle)
else if(W.is_fulltile())
ai_log("destroy_surroundings() : Attacking full tile window.", AI_LOG_INFO)
return holder.IAttack(W)
for(var/obj/machinery/door/D in problem_turf) // Required since firelocks take up the same turf.
if(D.density)
ai_log("destroy_surroundings() : Attacking closed door.", AI_LOG_INFO)
return holder.IAttack(D)
// Kill hull shields in the way.
for(var/obj/effect/energy_field/shield in problem_turf)
if(shield.density) // Don't attack shields that are already down.
ai_log("destroy_surroundings() : Attacking hull shield.", AI_LOG_INFO)
return holder.IAttack(shield)
// Kill common obstacle in the way like tables.
var/obj/structure/obstacle = locate(/obj/structure, problem_turf)
if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))
ai_log("destroy_surroundings() : Attacking generic structure.", AI_LOG_INFO)
return holder.IAttack(obstacle)
for(var/obj/machinery/door/D in problem_turf) // Required since firelocks take up the same turf.
if(D.density)
ai_log("destroy_surroundings() : Attacking closed door.", AI_LOG_INFO)
return holder.IAttack(D)
ai_log("destroy_surroundings() : Exiting due to nothing to attack.", AI_LOG_INFO)
return FALSE // Nothing to attack.
// Override for special behaviour.
/datum/ai_holder/proc/can_violently_breakthrough()
return violent_breakthrough
+3
View File
@@ -92,6 +92,9 @@
if(!holder.IIsAlly(friend)) // Extra sanity.
ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO)
return
if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target))
ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO)
return
if(get_dist(holder, friend) <= follow_distance)
ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO)
return
+3 -3
View File
@@ -7,12 +7,12 @@
var/follow_until_time = 0 // world.time when the mob will stop following leader. 0 means it won't time out.
/datum/ai_holder/proc/walk_to_leader()
ai_log("walk_to_leader() : Entering.",AI_LOG_DEBUG)
ai_log("walk_to_leader() : Entering.",AI_LOG_TRACE)
if(!leader)
ai_log("walk_to_leader() : No leader.", AI_LOG_WARNING)
forget_path()
set_stance(STANCE_IDLE)
ai_log("walk_to_leader() : Exiting.", AI_LOG_DEBUG)
ai_log("walk_to_leader() : Exiting.", AI_LOG_TRACE)
return
// Did we time out?
@@ -20,7 +20,7 @@
ai_log("walk_to_leader() : Follow timed out, losing leader.", AI_LOG_INFO)
lose_follow()
set_stance(STANCE_IDLE)
ai_log("walk_to_leader() : Exiting.", AI_LOG_DEBUG)
ai_log("walk_to_leader() : Exiting.", AI_LOG_TRACE)
return
var/get_to = follow_distance
+14 -14
View File
@@ -4,7 +4,7 @@
var/min_distance_to_destination = 1 // Holds how close the mob should go to destination until they're done.
// Home.
var/turf/home_turf = null // The mob's 'home' turf. It will try to stay near it if told to do so.
var/turf/home_turf = null // The mob's 'home' turf. It will try to stay near it if told to do so. This is the turf the AI was initialized on by default.
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().
@@ -18,12 +18,12 @@
/datum/ai_holder/proc/walk_to_destination()
ai_log("walk_to_destination() : Entering.",AI_LOG_DEBUG)
ai_log("walk_to_destination() : Entering.",AI_LOG_TRACE)
if(!destination)
ai_log("walk_to_destination() : No destination.", AI_LOG_WARNING)
forget_path()
set_stance(stance == STANCE_REPOSITION ? STANCE_APPROACH : STANCE_IDLE)
ai_log("walk_to_destination() : Exiting.", AI_LOG_DEBUG)
ai_log("walk_to_destination() : Exiting.", AI_LOG_TRACE)
return
var/get_to = min_distance_to_destination
@@ -39,7 +39,7 @@
ai_log("walk_to_destination() : Walking.", AI_LOG_TRACE)
walk_path(destination, get_to)
ai_log("walk_to_destination() : Exiting.",AI_LOG_DEBUG)
ai_log("walk_to_destination() : Exiting.",AI_LOG_TRACE)
/datum/ai_holder/proc/should_go_home()
if(!returns_home || !home_turf)
@@ -67,7 +67,7 @@
min_distance_to_destination = min_distance
if(new_destination != null)
ai_log("give_destination() : Going to new destination.", AI_LOG_TRACE)
ai_log("give_destination() : Going to new destination.", AI_LOG_INFO)
set_stance(combat ? STANCE_REPOSITION : STANCE_MOVE)
return TRUE
else
@@ -78,18 +78,18 @@
// Walk towards whatever.
/datum/ai_holder/proc/walk_path(atom/A, get_to = 1)
ai_log("walk_path() : Entered.", AI_LOG_DEBUG)
ai_log("walk_path() : Entered.", AI_LOG_TRACE)
if(use_astar)
if(!path.len) // If we're missing a path, make a new one.
ai_log("walk_path() : No path. Attempting to calculate path.", AI_LOG_TRACE)
ai_log("walk_path() : No path. Attempting to calculate path.", AI_LOG_DEBUG)
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 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)
ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_INFO)
breakthrough(A) // We failed to move, time to smash things.
return
@@ -97,7 +97,7 @@
ai_log("walk_path() : Failed to step.", AI_LOG_TRACE)
++failed_steps
if(failed_steps > 3) // We're probably stuck.
ai_log("walk_path() : Too many failed_steps.", AI_LOG_INFO)
ai_log("walk_path() : Too many failed_steps.", AI_LOG_DEBUG)
forget_path() // So lets try again with a new path.
failed_steps = 0
@@ -105,15 +105,15 @@
// 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)
ai_log("walk_path() : Failed to move, attempting breakthrough.", AI_LOG_INFO)
breakthrough(A) // We failed to move, time to smash things.
ai_log("walk_path() : Exited.", AI_LOG_DEBUG)
ai_log("walk_path() : Exited.", AI_LOG_TRACE)
//Take one step along a path
/datum/ai_holder/proc/move_once()
ai_log("move_once() : Entered.", AI_LOG_DEBUG)
ai_log("move_once() : Entered.", AI_LOG_TRACE)
if(!path.len)
return
@@ -138,7 +138,7 @@
// Wanders randomly in cardinal directions.
/datum/ai_holder/proc/handle_wander_movement()
ai_log("handle_wander_movement() : Entered.", AI_LOG_DEBUG)
ai_log("handle_wander_movement() : Entered.", AI_LOG_TRACE)
if(isturf(holder.loc) && can_act())
wander_delay--
if(wander_delay <= 0)
@@ -151,4 +151,4 @@
holder.set_dir(moving_to)
holder.IMove(get_step(holder,moving_to))
wander_delay = base_wander_delay
ai_log("handle_wander_movement() : Exited.", AI_LOG_DEBUG)
ai_log("handle_wander_movement() : Exited.", AI_LOG_TRACE)
+8
View File
@@ -66,13 +66,21 @@
if(check_move_cooldown())
// if(!newdir)
// newdir = get_dir(get_turf(src), newloc)
// Check to make sure moving to newloc won't actually kill us. e.g. we're a slime and trying to walk onto water.
if(istype(newloc))
if(safety && !newloc.is_safe_to_enter(src))
return MOVEMENT_FAILED
// 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)
// An adjacency check to avoid mobs phasing diagonally past windows.
// This might be better in general movement code but I'm too scared to add it, and most things don't move diagonally anyways.
if(!old_T.Adjacent(newloc))
return MOVEMENT_FAILED
. = SelfMove(newloc) ? MOVEMENT_SUCCESSFUL : MOVEMENT_FAILED
if(. == MOVEMENT_SUCCESSFUL)
set_dir(get_dir(old_T, newloc))
+1 -1
View File
@@ -37,7 +37,7 @@
while (i <= num_groups)
var/group_size = rand(group_size_min, group_size_max)
for (var/j = 1, j <= group_size, j++)
spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp(spawn_locations[i]))
spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp/event(spawn_locations[i]))
i++
/datum/event/carp_migration/end()
+1 -1
View File
@@ -16,7 +16,7 @@
else
num = rand(2,6)
for(var/i=0, i<num, i++)
var/mob/living/simple_mob/mechanical/combat_drone/D = new(get_turf(pick(possible_spawns)))
var/mob/living/simple_mob/mechanical/combat_drone/event/D = new(get_turf(pick(possible_spawns)))
drones_list.Add(D)
/datum/event/rogue_drone/announce()
@@ -52,7 +52,7 @@
while (i <= carp_amount)
var/group_size = rand(group_size_min, group_size_max)
for (var/j = 1, j <= group_size, j++)
spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp(spawn_locations[i]))
spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp/event(spawn_locations[i]))
i++
message_admins("[spawned_carp.len] carp spawned by event.")
@@ -8,9 +8,11 @@
health = 20
maxHealth = 20
mob_bump_flag = SIMPLE_ANIMAL
mob_swap_flags = MONKEY|SLIME|HUMAN
mob_push_flags = MONKEY|SLIME|HUMAN
// Generally we don't want simple_mobs to get displaced when bumped into due to it trivializing combat with windup attacks.
// Some subtypes allow displacement, like passive animals.
mob_bump_flag = HEAVY
mob_swap_flags = ~HEAVY
mob_push_flags = ~HEAVY
var/tt_desc = "Uncataloged Life Form" //Tooltip description
@@ -222,6 +224,11 @@
if(!isnull(M.slowdown))
tally += M.slowdown
// Turf related slowdown
var/turf/T = get_turf(src)
if(T && T.movement_cost && !hovering) // Flying mobs ignore turf-based slowdown.
tally += T.movement_cost
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
@@ -12,6 +12,8 @@
health = 120
poison_per_bite = 5
melee_damage_lower = 9
melee_damage_upper = 15
movement_cooldown = 0 // Hunters are FAST.
@@ -19,7 +21,8 @@
player_msg = "You are very fast, and <b>can perform a leaping attack</b> by clicking on someone from a short distance away.<br>\
If the leap succeeds, the target will be knocked down briefly and you will be on top of them.<br>\
Note that there is a short delay before you leap!"
Note that there is a short delay before you leap!<br>\
In addition, you will do more damage to incapacitated opponents."
// Leaping is a special attack, so these values determine when leap can happen.
// Leaping won't occur if its on cooldown.
@@ -28,6 +31,16 @@
special_attack_cooldown = 10 SECONDS
var/leap_warmup = 1 SECOND // How long the leap telegraphing is.
var/leap_sound = 'sound/weapons/spiderlunge.ogg'
// Multiplies damage if the victim is stunned in some form, including a successful leap.
/mob/living/simple_mob/animal/giant_spider/hunter/apply_bonus_melee_damage(atom/A, damage_amount)
if(isliving(A))
var/mob/living/L = A
if(L.incapacitated(INCAPACITATION_DISABLED))
return damage_amount * 1.5
return ..()
// The actual leaping attack.
/mob/living/simple_mob/animal/giant_spider/hunter/do_special_attack(atom/A)
@@ -42,7 +55,7 @@
status_flags |= LEAPING // Lets us pass over everything.
visible_message(span("danger","\The [src] leaps at \the [A]!"))
throw_at(get_step(get_turf(A), get_turf(src)), special_attack_max_range+1, 1, src)
playsound(src, 'sound/weapons/spiderlunge.ogg', 75, 1)
playsound(src, leap_sound, 75, 1)
sleep(5) // For the throw to complete. It won't hold up the AI ticker due to waitfor being false.
@@ -11,7 +11,7 @@
maxHealth = 5
health = 5
mob_size = MOB_SMALL
mob_size = MOB_MINISCULE
pass_flags = PASSTABLE
// can_pull_size = ITEMSIZE_TINY
// can_pull_mobs = MOB_PULL_NONE
@@ -1,3 +1,5 @@
// Passive mobs can't attack things, and will run away instead.
// They can also be displaced by all mobs.
/mob/living/simple_mob/animal/passive
ai_holder_type = /datum/ai_holder/simple_mob/passive
ai_holder_type = /datum/ai_holder/simple_mob/passive
mob_bump_flag = 0
@@ -57,6 +57,7 @@
icon_state = "commonblackbird"
icon_dead = "commonblackbird-dead"
tt_desc = "E Turdus merula"
icon_scale = 0.5
/mob/living/simple_mob/animal/passive/bird/azure_tit
name = "azure tit"
@@ -64,6 +65,7 @@
icon_state = "azuretit"
icon_dead = "azuretit-dead"
tt_desc = "E Cyanistes cyanus"
icon_scale = 0.5
/mob/living/simple_mob/animal/passive/bird/european_robin
name = "european robin"
@@ -71,6 +73,7 @@
icon_state = "europeanrobin"
icon_dead = "europeanrobin-dead"
tt_desc = "E Erithacus rubecula"
icon_scale = 0.5
/mob/living/simple_mob/animal/passive/bird/goldcrest
name = "goldcrest"
@@ -79,6 +82,7 @@
icon_state = "goldcrest"
icon_dead = "goldcrest-dead"
tt_desc = "E Regulus regulus"
icon_scale = 0.5
/mob/living/simple_mob/animal/passive/bird/ringneck_dove
name = "ringneck dove"
@@ -86,3 +90,4 @@
icon_state = "ringneckdove"
icon_dead = "ringneckdove-dead"
tt_desc = "E Streptopelia risoria" // This is actually disputed IRL but since we can't tell the future it'll stay the same for 500+ years.
icon_scale = 0.5
@@ -2,7 +2,7 @@
/mob/living/simple_mob/animal/passive/bird/parrot
name = "parrot"
description_info = "You can give it a headset by clicking on it with a headset. \
To remove it, click-drag the bird to you while adjacent to them."
To remove it, click the bird while on grab intent."
has_langs = list("Galactic Common", "Bird")
ai_holder_type = /datum/ai_holder/simple_mob/passive/parrot
@@ -25,7 +25,7 @@
"Meteors have been detected on a collision course with the station!"
)
// Let's the AI use headsets.
// Lets the AI use headsets.
// Player-controlled parrots will need to do it manually.
/mob/living/simple_mob/animal/passive/bird/parrot/ISay(message)
if(my_headset && prob(50))
@@ -24,6 +24,8 @@
health = 200
movement_cooldown = 10
movement_sound = 'sound/weapons/heavysmash.ogg'
movement_shake_radius = 5
taser_kill = FALSE
armor = list(
"melee" = 40,
@@ -49,8 +51,8 @@
melee_damage_lower = 22
melee_damage_upper = 35
attack_armor_pen = 35
attack_sharp = 1
attack_edge = 1
attack_sharp = TRUE
attack_edge = TRUE
melee_attack_delay = 1 SECOND
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
@@ -41,6 +41,12 @@
L.visible_message(span("danger", "\The [src] knocks down \the [L]!"))
// Subtypes.
// Won't wander away.
/mob/living/simple_mob/animal/space/carp/event
ai_holder_type = /datum/ai_holder/simple_mob/event
/mob/living/simple_mob/animal/space/carp/large
name = "elder carp"
desc = "An older, more matured carp. Few survive to this age due to their aggressiveness."
@@ -55,7 +61,7 @@
mob_size = MOB_LARGE
pixel_x = -16
old_x = -16
default_pixel_x = -16
meat_amount = 3
@@ -75,8 +81,8 @@
melee_damage_lower = 15 // About 20 DPS.
melee_damage_upper = 25
old_y = -16
pixel_y = -16
default_pixel_x = -16
meat_amount = 10
@@ -139,8 +139,8 @@
continue
if(!IIsAlly(S)) // Only friendly spores make us stronger.
continue
// Friendly spores contribute half of their averaged attack power to our attack.
damage_to_do += ((S.melee_damage_lower + S.melee_damage_upper) / 2) / 2
// Friendly spores contribute 1/4th of their averaged attack power to our attack.
damage_to_do += ((S.melee_damage_lower + S.melee_damage_upper) / 2) / 4
helpers++
if(helpers)
@@ -11,6 +11,7 @@
poison_resist = 1
movement_cooldown = 0
mob_bump_flag = 0 // If the illusion can't be swapped it will be obvious.
response_help = "pushes a hand through"
response_disarm = "tried to disarm"
@@ -31,6 +32,7 @@
return FALSE
appearance = thing_to_copy.appearance
copying = thing_to_copy
density = thing_to_copy.density // So you can't bump into objects that aren't supposed to be dense.
return TRUE
// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but
@@ -68,4 +68,10 @@
// 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
movement_cooldown = 10
// This one is the type spawned by the random event.
// It won't wander away from its spawn point
/mob/living/simple_mob/mechanical/combat_drone/event
ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening/event
@@ -8,7 +8,6 @@
icon = 'icons/mob/hivebot.dmi'
icon_state = "basic"
icon_living = "basic"
icon_dead = "basic"
faction = "hivebot"
@@ -32,6 +32,9 @@
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion
name = "ionic hivebot"
desc = "A robot with an electromagnetic pulse projector."
icon_state = "yellow"
icon_living = "yellow"
projectiletype = /obj/item/projectile/ion
projectilesound = 'sound/weapons/Laser.ogg'
player_msg = "You have a <b>ranged ion attack</b>, which is very strong against other synthetics.<br>\
@@ -58,6 +61,9 @@
/mob/living/simple_mob/mechanical/hivebot/ranged_damage/dot
name = "ember hivebot"
desc = "A robot that appears to utilize fire to cook their enemies."
icon_state = "red"
icon_living = "red"
projectiletype = /obj/item/projectile/fire
heat_resist = 1
player_msg = "Your attacks inflict a <b>damage over time</b> effect, that will \
@@ -100,6 +106,9 @@
desc = "A large robot capable of delivering long range bombardment."
projectiletype = /obj/item/projectile/arc/test
icon_scale = 2
icon_state = "red"
icon_living = "red"
player_msg = "You are capable of firing <b>very long range bombardment attacks</b>.<br>\
To use, click on a tile or enemy at a long range. Note that the projectile arcs in the air, \
so it will fly over everything inbetween you and the target.<br>\
@@ -1,6 +1,8 @@
// These hivebots help their team in various ways, and can be very powerful with allies, but are otherwise very weak when alone.
/mob/living/simple_mob/mechanical/hivebot/support
icon_state = "white"
icon_living = "white"
attacktext = list("prodded")
movement_cooldown = 5
melee_damage_lower = 2
@@ -29,6 +29,7 @@
special_attack_max_range = 7
special_attack_cooldown = 10 SECONDS
projectiletype = /obj/item/projectile/force_missile
projectilesound = 'sound/weapons/wave.ogg'
var/obj/effect/overlay/energy_ball/energy_ball = null
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/Destroy()
@@ -71,14 +72,23 @@
for(var/i = 1 to 10)
energy_ball.adjust_scale(0.5 + (i/10))
energy_ball.set_light(i/2, i/2, "#0000FF")
for(var/mob/living/L in range(3, src))
if(L == src)
continue
if(L.stat)
continue // Otherwise it can get pretty laggy if there's loads of corpses around.
L.inflict_shock_damage(i * 2)
if(L && L.has_AI()) // Some mobs delete themselves when dying.
L.ai_holder.react_to_attack(src)
for(var/thing in range(3, src))
// This is stupid because mechs are stupid and not mobs.
if(isliving(thing))
var/mob/living/L = thing
if(L == src)
continue
if(L.stat)
continue // Otherwise it can get pretty laggy if there's loads of corpses around.
L.inflict_shock_damage(i * 2)
if(L && L.has_AI()) // Some mobs delete themselves when dying.
L.ai_holder.react_to_attack(src)
else if(istype(thing, /obj/mecha))
var/obj/mecha/M = thing
M.take_damage(i * 2, "energy") // Mechs don't have a concept for siemens so energy armor check is the best alternative.
sleep(1 SECOND)
// Shoot a tesla bolt, and flashes people who are looking at the mecha without sufficent eye protection.
@@ -107,7 +117,7 @@
set waitfor = FALSE
// Telegraph our next move.
Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS)
Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS, maxdistance = INFINITY)
visible_message(span("warning", "\The [src] deploys a missile rack!"))
playsound(src, 'sound/effects/turret/move1.wav', 50, 1)
sleep(0.5 SECONDS)
@@ -7,6 +7,7 @@
/mob/living/simple_mob/mechanical/ward/monitor
desc = "It's a little flying drone. This one seems to be watching you..."
icon_state = "ward"
glow_color = "#00FF00"
see_invisible = SEE_INVISIBLE_LEVEL_TWO
has_eye_glow = TRUE
@@ -20,9 +20,7 @@
movement_cooldown = 0
hovering = TRUE
pass_flags = PASSTABLE
mob_swap_flags = 0
mob_push_flags = 0
mob_bump_flag = 0
melee_damage_lower = 0
melee_damage_upper = 0
@@ -22,6 +22,8 @@
movement_cooldown = 10
melee_attack_delay = 0.5 SECONDS
ai_holder_type = /datum/ai_holder/simple_mob/ranged/pointblank
// Slimebatoning/xenotasing it just makes it mad at you (which can be good if you're heavily armored and your friends aren't).
/mob/living/simple_mob/slime/feral/slimebatoned(mob/living/user, amount)
@@ -46,9 +48,10 @@
cold_damage_per_tick = 0
projectiletype = /obj/item/projectile/icicle
base_attack_cooldown = 3 SECONDS
base_attack_cooldown = 2 SECONDS
ranged_attack_delay = 1 SECOND
player_msg = "You can fire an icicle projectile every three seconds. It hits hard, and armor has a hard time resisting it.<br>\
player_msg = "You can fire an icicle projectile every two seconds. It hits hard, and armor has a hard time resisting it.<br>\
You are also immune to the cold, and you cause enemies around you to suffer periodic harm from the cold, if unprotected.<br>\
Unprotected enemies are also Chilled, making them slower, less evasive, and to suffer disabling effects for longer."
@@ -68,7 +71,7 @@
return ..()
/obj/item/projectile/icicle/get_structure_damage()
return 0 // They're really deadly against mobs, but not walls.
return damage / 2 // They're really deadly against mobs, but less effective against solid things.
/mob/living/simple_mob/slime/feral/dark_blue/handle_special()
if(stat != DEAD)
@@ -97,24 +97,22 @@
/mob/living/simple_mob/slime/update_icon()
..() // Do the regular stuff first.
var/mutable_appearance/MA = new(src)
if(stat != DEAD)
// General slime shine.
var/image/I = image(icon, src, "slime light")
I.appearance_flags = RESET_COLOR
MA.overlays += I
add_overlay(I)
// 'Shiny' overlay, for gemstone-slimes.
if(shiny)
I = image(icon, src, "slime shiny")
I.appearance_flags = RESET_COLOR
MA.overlays += I
add_overlay(I)
// Mood overlay.
I = image(icon, src, "aslime-[mood]")
I.appearance_flags = RESET_COLOR
MA.overlays += I
add_overlay(I)
// Hat simulator.
if(hat)
@@ -122,9 +120,7 @@
var/image/I = image('icons/mob/head.dmi', src, hat_state)
I.pixel_y = -7 // Slimes are small.
I.appearance_flags = RESET_COLOR
MA.overlays += I
appearance = MA
add_overlay(I)
// Controls the 'mood' overlay. Overrided in subtypes for specific behaviour.
/mob/living/simple_mob/slime/proc/update_mood()
@@ -133,7 +133,7 @@
shock_resist = 1
projectiletype = /obj/item/projectile/beam/lightning/slime
projectilesound = 'sound/weapons/gauss_shoot.ogg' // Closest thing to a 'thunderstrike' sound we have.
projectilesound = 'sound/effects/lightningbolt.ogg'
glow_toggle = TRUE
description_info = "In addition to being immune to electrical shocks, this slime will fire ranged lightning attacks at \
@@ -165,6 +165,7 @@
/obj/item/projectile/beam/lightning/slime
power = 10
fire_sound = 'sound/effects/lightningbolt.ogg'
/mob/living/simple_mob/slime/xenobio/dark_purple
+3
View File
@@ -60,6 +60,9 @@
..() //extend the zap
explode()
/obj/mecha/tesla_act(power)
..()
take_damage(power / 200, "energy") // A surface lightning strike will do 100 damage.
+6
View File
@@ -53,6 +53,12 @@
user.setClickCooldown(user.get_attack_speed(W))
..()
/obj/effect/energy_field/attack_generic(mob/user, damage)
if(damage)
adjust_strength(-damage / 20)
user.do_attack_animation(src)
user.setClickCooldown(user.get_attack_speed())
/obj/effect/energy_field/attack_hand(var/mob/living/user)
impact_effect(3) // Harmless, but still produces the 'impact' effect.
..()