Merges AI Branch into Master

This commit is contained in:
Atermonera
2018-11-30 21:05:42 -08:00
committed by VirgoBot
parent 15017e893e
commit 2df5e9fe0e
379 changed files with 22460 additions and 4751 deletions
+13 -13
View File
@@ -280,21 +280,21 @@
if("larva") M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob )
if("nymph") M.change_mob_type( /mob/living/carbon/alien/diona , null, null, delmob )
if("human") M.change_mob_type( /mob/living/carbon/human , null, null, delmob, href_list["species"])
if("slime") M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob )
if("slime") M.change_mob_type( /mob/living/simple_mob/slime/xenobio , null, null, delmob )
if("monkey") M.change_mob_type( /mob/living/carbon/human/monkey , null, null, delmob )
if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob )
if("cat") M.change_mob_type( /mob/living/simple_animal/cat , null, null, delmob )
if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/fluff/Runtime , null, null, delmob )
if("corgi") M.change_mob_type( /mob/living/simple_animal/corgi , null, null, delmob )
if("ian") M.change_mob_type( /mob/living/simple_animal/corgi/Ian , null, null, delmob )
if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob )
if("coffee") M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob )
if("parrot") M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob )
if("polyparrot") M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob )
if("constructarmoured") M.change_mob_type( /mob/living/simple_animal/construct/armoured , null, null, delmob )
if("constructbuilder") M.change_mob_type( /mob/living/simple_animal/construct/builder , null, null, delmob )
if("constructwraith") M.change_mob_type( /mob/living/simple_animal/construct/wraith , null, null, delmob )
if("shade") M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob )
if("cat") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat , null, null, delmob )
if("runtime") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat/runtime , null, null, delmob )
if("corgi") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi , null, null, delmob )
if("ian") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi/Ian , null, null, delmob )
if("crab") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab , null, null, delmob )
if("coffee") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab/Coffee , null, null, delmob )
if("parrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot , null, null, delmob )
if("polyparrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot/poly , null, null, delmob )
if("constructarmoured") M.change_mob_type( /mob/living/simple_mob/construct/juggernaut , null, null, delmob )
if("constructbuilder") M.change_mob_type( /mob/living/simple_mob/construct/artificer , null, null, delmob )
if("constructwraith") M.change_mob_type( /mob/living/simple_mob/construct/wraith , null, null, delmob )
if("shade") M.change_mob_type( /mob/living/simple_mob/construct/shade , null, null, delmob )
/////////////////////////////////////new ban stuff
+108
View File
@@ -127,6 +127,18 @@
usr << "<span class='notice'>Right Mouse Button on turf/obj/mob = Reset glowing</span>"
usr << "<span class='notice'>Right Mouse Button on buildmode button = Change glow properties</span>"
usr << "<span class='notice'>***********************************************************</span>"
if(9) // Control mobs with ai_holders.
usr << "<span class='notice'>***********************************************************</span>"
usr << "<span class='notice'>Left Mouse Button on AI mob = Select/Deselect mob</span>"
usr << "<span class='notice'>Left Mouse Button + alt on AI mob = Toggle hostility on mob</span>"
usr << "<span class='notice'>Left Mouse Button + ctrl on AI mob = Reset target/following/movement</span>"
usr << "<span class='notice'>Right Mouse Button on enemy mob = Command selected mobs to attack mob</span>"
usr << "<span class='notice'>Right Mouse Button on allied mob = Command selected mobs to follow mob</span>"
usr << "<span class='notice'>Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction</span>"
usr << "<span class='notice'>Right Mouse Button on tile = Command selected mobs to move to tile (will cancel if enemies are seen)</span>"
usr << "<span class='notice'>Right Mouse Button + shift on tile = Command selected mobs to reposition to tile (will not be inturrupted by enemies)</span>"
usr << "<span class='notice'>Right Mouse Button + alt on obj/turfs = Command selected mobs to attack obj/turf</span>"
usr << "<span class='notice'>***********************************************************</span>"
return 1
/obj/effect/bmode/buildquit
@@ -146,6 +158,7 @@
var/obj/effect/bmode/buildmode/buildmode = null
var/obj/effect/bmode/buildquit/buildquit = null
var/atom/movable/throw_atom = null
var/list/selected_mobs = list()
/obj/effect/bmode/buildholder/Destroy()
qdel(builddir)
@@ -157,9 +170,21 @@
qdel(buildquit)
buildquit = null
throw_atom = 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"
@@ -210,6 +235,9 @@
master.cl.buildmode = 8
src.icon_state = "buildmode8"
if(8)
master.cl.buildmode = 9
src.icon_state = "buildmode9"
if(9)
master.cl.buildmode = 1
src.icon_state = "buildmode1"
@@ -416,6 +444,86 @@
if(pa.Find("right"))
if(object)
object.set_light(0, 0, "#FFFFFF")
if(9) // AI control
if(pa.Find("left"))
if(isliving(object))
var/mob/living/L = object
// Reset processes.
if(pa.Find("ctrl"))
if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
var/datum/ai_holder/AI = L.ai_holder
AI.forget_everything()
to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
// Toggle hostility
if(pa.Find("alt"))
if(!isnull(L.get_AI_stance()))
var/datum/ai_holder/AI = L.ai_holder
AI.hostile = !AI.hostile
to_chat(user, span("notice", "\The [L] is now [AI.hostile ? "hostile" : "passive"]."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
// Select/Deselect
if(!isnull(L.get_AI_stance()))
if(L in holder.selected_mobs)
holder.deselect_AI_mob(user.client, L)
to_chat(user, span("notice", "Deselected \the [L]."))
else
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)) // Force attack.
var/atom/A = object
if(pa.Find("alt"))
var/i = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
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
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)
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
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")) // 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)
var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]")
+99 -115
View File
@@ -1,115 +1,99 @@
/client/proc/admin_lightning_strike()
set name = "Lightning Strike"
set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely."
set category = "Fun"
if(!check_rights(R_FUN))
return
var/result = alert(src, "Really strike your tile with lightning?", "Confirm Badmin" , "No", "Yes (Cosmetic)", "Yes (Real)")
if(result == "No")
return
var/fake_lightning = result == "Yes (Cosmetic)"
lightning_strike(get_turf(usr), fake_lightning)
log_and_message_admins("[key_name(src)] has caused [fake_lightning ? "cosmetic":"harmful"] lightning to strike at their position ([src.mob.x], [src.mob.y], [src.mob.z]). \
(<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[src.mob.x];Y=[src.mob.y];Z=[src.mob.z]'>JMP</a>)")
#define LIGHTNING_REDIRECT_RANGE 28 // How far in tiles certain things draw lightning from.
#define LIGHTNING_ZAP_RANGE 3 // How far the tesla effect zaps, as well as the bad effects from a direct strike.
#define LIGHTNING_POWER 20000 // How much 'zap' is in a strike, used for tesla_zap().
// The real lightning proc.
// This is global until I can figure out a better place for it.
// T is the turf that is being struck. If cosmetic is true, the lightning won't actually hurt anything.
/proc/lightning_strike(turf/T, cosmetic = FALSE)
// First, visuals.
// Do a lightning flash for the whole planet, if the turf belongs to a planet.
var/datum/planet/P = null
P = SSplanets.z_to_planet[T.z]
if(P)
var/datum/weather_holder/holder = P.weather_holder
flick("lightning_flash", holder.special_visuals)
// Before we do the other visuals, we need to see if something is going to hijack our intended target.
var/obj/machinery/power/grounding_rod/ground = null // Most of the bad effects of lightning will get negated if a grounding rod is nearby.
var/obj/machinery/power/tesla_coil/coil = null // However a tesla coil has higher priority and the strike will bounce.
for(var/obj/machinery/power/thing in range(LIGHTNING_REDIRECT_RANGE, T))
if(istype(thing, /obj/machinery/power/tesla_coil))
var/turf/simulated/coil_turf = get_turf(thing)
if(istype(coil_turf) && thing.anchored && coil_turf.outdoors)
coil = thing
break
if(istype(thing, /obj/machinery/power/grounding_rod))
var/turf/simulated/rod_turf = get_turf(thing)
if(istype(rod_turf) && thing.anchored && rod_turf.outdoors)
ground = thing
if(coil) // Coil gets highest priority.
T = coil.loc
else if(ground)
T = ground.loc
// Now make the lightning strike sprite. It will fade and delete itself in a second.
new /obj/effect/temporary_effect/lightning_strike(T)
// For those close up.
playsound(T, 'sound/effects/lightningbolt.ogg', 100, 1)
// And for those far away. If the strike happens on a planet, everyone on the planet will hear it.
// Otherwise only those on the current z-level will hear it.
var/sound = get_sfx("thunder")
for(var/mob/M in player_list)
if((P && M.z in P.expected_z_levels) || M.z == T.z)
M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE)
if(cosmetic) // Everything beyond here involves potentially damaging things. If we don't want to do that, stop now.
return
if(ground) // All is well.
ground.tesla_act(LIGHTNING_POWER, FALSE)
return
else if(coil) // Otherwise lets bounce off the tesla coil.
coil.tesla_act(LIGHTNING_POWER, TRUE)
else // Striking the turf directly.
tesla_zap(T, zap_range = LIGHTNING_ZAP_RANGE, power = LIGHTNING_POWER, explosive = FALSE, stun_mobs = TRUE)
// Some extra effects.
// Some apply to those within zap range, others if they were a bit farther away.
for(var/mob/living/L in view(5, T))
if(get_dist(L, T) <= LIGHTNING_ZAP_RANGE) // They probably got zapped.
// The actual damage/electrocution is handled by tesla_zap().
L.Paralyse(5)
L.stuttering += 20
L.make_jittery(20)
L.emp_act(1)
to_chat(L, span("critical", "You've been struck by lightning!"))
// If a non-player simplemob was struck, inflict huge damage.
// If the damage is fatal, the SA is turned to ash.
if(istype(L, /mob/living/simple_animal) && !L.key)
var/mob/living/simple_animal/SA = L
SA.adjustFireLoss(200)
SA.updatehealth()
if(SA.health <= 0) // Might be best to check/give simple_mobs siemens when this gets ported to new mobs.
SA.visible_message(span("critical", "\The [SA] disintegrates into ash!"))
SA.ash()
continue // No point deafening something that wont exist.
// Deafen them.
if(L.get_ear_protection() < 2)
L.AdjustSleeping(-100)
if(iscarbon(L))
var/mob/living/carbon/C = L
C.ear_deaf += 10
to_chat(L, span("danger", "Lightning struck nearby, and the thunderclap is deafening!"))
#undef GROUNDING_ROD_RANGE
#undef LIGHTNING_ZAP_RANGE
#undef LIGHTNING_POWER
/client/proc/admin_lightning_strike()
set name = "Lightning Strike"
set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely."
set category = "Fun"
if(!check_rights(R_FUN))
return
var/result = alert(src, "Really strike your tile with lightning?", "Confirm Badmin" , "No", "Yes (Cosmetic)", "Yes (Real)")
if(result == "No")
return
var/fake_lightning = result == "Yes (Cosmetic)"
lightning_strike(get_turf(usr), fake_lightning)
log_and_message_admins("[key_name(src)] has caused [fake_lightning ? "cosmetic":"harmful"] lightning to strike at their position ([src.mob.x], [src.mob.y], [src.mob.z]). \
(<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[src.mob.x];Y=[src.mob.y];Z=[src.mob.z]'>JMP</a>)")
#define LIGHTNING_REDIRECT_RANGE 28 // How far in tiles certain things draw lightning from.
#define LIGHTNING_ZAP_RANGE 3 // How far the tesla effect zaps, as well as the bad effects from a direct strike.
#define LIGHTNING_POWER 20000 // How much 'zap' is in a strike, used for tesla_zap().
// The real lightning proc.
// This is global until I can figure out a better place for it.
// T is the turf that is being struck. If cosmetic is true, the lightning won't actually hurt anything.
/proc/lightning_strike(turf/T, cosmetic = FALSE)
// First, visuals.
// Do a lightning flash for the whole planet, if the turf belongs to a planet.
var/datum/planet/P = null
P = SSplanets.z_to_planet[T.z]
if(P)
var/datum/weather_holder/holder = P.weather_holder
flick("lightning_flash", holder.special_visuals)
// Before we do the other visuals, we need to see if something is going to hijack our intended target.
var/obj/machinery/power/grounding_rod/ground = null // Most of the bad effects of lightning will get negated if a grounding rod is nearby.
var/obj/machinery/power/tesla_coil/coil = null // However a tesla coil has higher priority and the strike will bounce.
for(var/obj/machinery/power/thing in range(LIGHTNING_REDIRECT_RANGE, T))
if(istype(thing, /obj/machinery/power/tesla_coil))
var/turf/simulated/coil_turf = get_turf(thing)
if(istype(coil_turf) && thing.anchored && coil_turf.outdoors)
coil = thing
break
if(istype(thing, /obj/machinery/power/grounding_rod))
var/turf/simulated/rod_turf = get_turf(thing)
if(istype(rod_turf) && thing.anchored && rod_turf.outdoors)
ground = thing
if(coil) // Coil gets highest priority.
T = coil.loc
else if(ground)
T = ground.loc
// Now make the lightning strike sprite. It will fade and delete itself in a second.
new /obj/effect/temporary_effect/lightning_strike(T)
// For those close up.
playsound(T, 'sound/effects/lightningbolt.ogg', 100, 1)
// And for those far away. If the strike happens on a planet, everyone on the planet will hear it.
// Otherwise only those on the current z-level will hear it.
var/sound = get_sfx("thunder")
for(var/mob/M in player_list)
if((P && M.z in P.expected_z_levels) || M.z == T.z)
M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE)
if(cosmetic) // Everything beyond here involves potentially damaging things. If we don't want to do that, stop now.
return
if(ground) // All is well.
ground.tesla_act(LIGHTNING_POWER, FALSE)
return
else if(coil) // Otherwise lets bounce off the tesla coil.
coil.tesla_act(LIGHTNING_POWER, TRUE)
else // Striking the turf directly.
tesla_zap(T, zap_range = LIGHTNING_ZAP_RANGE, power = LIGHTNING_POWER, explosive = FALSE, stun_mobs = TRUE)
// Some extra effects.
// Some apply to those within zap range, others if they were a bit farther away.
for(var/mob/living/L in view(5, T))
if(get_dist(L, T) <= LIGHTNING_ZAP_RANGE) // They probably got zapped.
L.lightning_act()
// Deafen them.
if(L.get_ear_protection() < 2)
L.AdjustSleeping(-100)
if(iscarbon(L))
var/mob/living/carbon/C = L
C.ear_deaf += 10
to_chat(L, span("danger", "Lightning struck nearby, and the thunderclap is deafening!"))
#undef GROUNDING_ROD_RANGE
#undef LIGHTNING_ZAP_RANGE
#undef LIGHTNING_POWER
+201
View File
@@ -0,0 +1,201 @@
/*
[Summary]
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]
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 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.
[Stances]
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.
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.
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.
[Interfaces]
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.
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.
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.
*/
+29
View File
@@ -0,0 +1,29 @@
// Defines for the ai_intelligence var.
// 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 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)
// Logging level defines.
#define AI_LOG_OFF 0 // Don't show anything.
#define AI_LOG_ERROR 1 // Show logs of things likely causing the mob to not be functioning correctly.
#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. 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.
// 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.
#define AI_TARGET_NOSIGHT 2 // No longer in field of view. Go STANCE_REPOSITION to their last known location if no other targets are seen.
#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.
@@ -0,0 +1,136 @@
// Base AIs for simple mobs.
// Mob-specific AIs are in their mob's file.
/datum/ai_holder/simple_mob
hostile = TRUE // The majority of simplemobs are hostile.
cooperative = TRUE
returns_home = FALSE
can_flee = FALSE
speak_chance = 1 // If the mob's saylist is empty, nothing will happen.
wander = TRUE
base_wander_delay = 4
// For non-hostile animals, and pets like Ian and Runtime.
/datum/ai_holder/simple_mob/passive
hostile = FALSE
can_flee = TRUE
violent_breakthrough = FALSE
// 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
hostile = FALSE
retaliate = FALSE
can_flee = FALSE
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
use_astar = 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
/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().
var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away.
var/moonwalk = TRUE // If true, mob turns to face the target while kiting, otherwise they turn in the direction they moved towards.
/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
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
/datum/ai_holder/simple_mob/ranged/kiting/on_engagement(atom/A)
if(get_dist(holder, A) < run_if_this_close)
holder.IMove(get_step_away(holder, A, run_if_this_close))
if(moonwalk)
holder.face_atom(A)
// Closes distance from the target even while in range.
/datum/ai_holder/simple_mob/ranged/aggressive
pointblank = TRUE
var/closest_distance = 1 // How close to get to the target. By default they will get into melee range (and then pointblank them).
/datum/ai_holder/simple_mob/ranged/aggressive/on_engagement(atom/A)
if(get_dist(holder, A) > closest_distance)
holder.IMove(get_step_towards(holder, A))
holder.face_atom(A)
// Yakkity saxes while firing at you.
/datum/ai_holder/hostile/ranged/robust/on_engagement(atom/movable/AM)
step_rand(holder)
holder.face_atom(AM)
// Switches intents based on specific criteria.
// Used for special mobs who do different things based on intents (and aren't slimes).
// Intent switching is generally done in pre_[ranged/special]_attack(), so that the mob can use the right attack for the right time.
/datum/ai_holder/simple_mob/intentional
// These try to avoid collateral damage.
/datum/ai_holder/simple_mob/restrained
violent_breakthrough = FALSE
conserve_ammo = TRUE
// 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)
// This AI hits something, then runs away for awhile.
// It will (almost) always flee if they are uncloaked, AND their target is not stunned.
/datum/ai_holder/simple_mob/melee/hit_and_run
can_flee = TRUE
// Used for the 'running' part of hit and run.
/datum/ai_holder/simple_mob/melee/hit_and_run/special_flee_check()
if(!holder.is_cloaked())
if(isliving(target))
var/mob/living/L = target
return !L.incapacitated(INCAPACITATION_DISABLED) // Don't flee if our target is stunned in some form, even if uncloaked. This is so the mob keeps attacking a stunned opponent.
return TRUE // We're out in the open, uncloaked, and our target isn't stunned, so lets flee.
return FALSE
// Simple mobs that aren't hostile, but will fight back.
/datum/ai_holder/simple_mob/retaliate
hostile = FALSE
retaliate = TRUE
// Simple mobs that retaliate and support others in their faction who get attacked.
/datum/ai_holder/simple_mob/retaliate/cooperative
cooperative = TRUE
@@ -0,0 +1,262 @@
// 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_mob/slime/xenobio
/datum/ai_holder/simple_mob/xenobio_slime
hostile = TRUE
cooperative = TRUE
firing_lanes = TRUE
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 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
obedience = 5
/datum/ai_holder/simple_mob/xenobio_slime/passive/New() // For Kendrick.
..()
pacify()
/datum/ai_holder/simple_mob/xenobio_slime/New()
..()
ASSERT(istype(holder, /mob/living/simple_mob/slime/xenobio))
// Checks if disciplining the slime would be 'justified' right now.
/datum/ai_holder/simple_mob/xenobio_slime/proc/is_justified_to_discipline()
if(rabid)
return TRUE
if(target)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(istype(H.species, /datum/species/monkey))
return FALSE // Attacking monkeys is okay.
return TRUE // Otherwise attacking other things is bad.
return FALSE // Not attacking anything.
/datum/ai_holder/simple_mob/xenobio_slime/proc/can_command(mob/living/commander)
if(rabid)
return FALSE
if(!hostile)
return SLIME_COMMAND_OBEY
// if(commander in friends)
// return SLIME_COMMAND_FRIEND
if(holder.IIsAlly(commander))
return SLIME_COMMAND_FACTION
if(discipline > resentment && obedience >= 5)
return SLIME_COMMAND_OBEY
return FALSE
/datum/ai_holder/simple_mob/xenobio_slime/proc/adjust_discipline(amount, silent)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(amount > 0)
if(rabid)
return
var/justified = is_justified_to_discipline()
lost_target() // Stop attacking.
if(justified)
obedience++
if(!silent)
holder.say(pick("Fine...", "Okay...", "Sorry...", "I yield...", "Mercy..."))
else
if(prob(resentment * 20))
enrage()
holder.say(pick("Evil...", "Kill...", "Tyrant..."))
else
if(!silent)
holder.say(pick("Why...?", "I don't understand...?", "Cruel...", "Stop...", "Nooo..."))
resentment++ // Done after check so first time will never enrage.
discipline = between(0, discipline + amount, 10)
my_slime.update_mood()
// This slime always enrages if disciplined.
/datum/ai_holder/simple_mob/xenobio_slime/red/adjust_discipline(amount, silent)
if(amount > 0 && !rabid)
holder.say("Grrr...")
holder.add_modifier(/datum/modifier/berserk, 30 SECONDS)
enrage()
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_strategical()
discipline_decay()
// Handles decay of discipline.
/datum/ai_holder/simple_mob/xenobio_slime/proc/discipline_decay()
if(discipline > 0)
if(!prob(75 + (obedience * 5)))
adjust_discipline(-1)
/datum/ai_holder/simple_mob/xenobio_slime/handle_special_tactic()
evolve_and_reproduce()
// Hit the correct verbs to keep the slime species going.
/datum/ai_holder/simple_mob/xenobio_slime/proc/evolve_and_reproduce()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.amount_grown >= 10)
// Press the correct verb when we can.
if(my_slime.is_adult)
my_slime.reproduce() // Splits into four new baby slimes.
else
my_slime.evolve() // Turns our holder into an adult slime.
// Called when pushed too far (or a red slime core was used).
/datum/ai_holder/simple_mob/xenobio_slime/proc/enrage()
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if(my_slime.harmless)
return
rabid = TRUE
my_slime.update_mood()
my_slime.visible_message(span("danger", "\The [src] enrages!"))
// Called when using a pacification agent (or it's Kendrick being initalized).
/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify()
lost_target() // So it stops trying to kill them.
rabid = FALSE
hostile = FALSE
retaliate = FALSE
cooperative = FALSE
// The holder's attack changes based on intent. This lets the AI choose what effect is desired.
/datum/ai_holder/simple_mob/xenobio_slime/pre_melee_attack(atom/A)
if(istype(A, /mob/living))
var/mob/living/L = A
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if( (!L.lying && prob(30 + (my_slime.power_charge * 7) ) || (!L.lying && always_stun) ))
my_slime.a_intent = I_DISARM // Stun them first.
else if(my_slime.can_consume(L) && L.lying)
my_slime.a_intent = I_GRAB // Then eat them.
else
my_slime.a_intent = I_HURT // Otherwise robust them.
/datum/ai_holder/simple_mob/xenobio_slime/closest_distance(atom/movable/AM)
if(istype(AM, /mob/living))
var/mob/living/L = AM
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(istype(H.species, /datum/species/monkey))
return 1 // Otherwise ranged slimes will eat a lot less often.
if(L.stat >= UNCONSCIOUS)
return 1 // Melee (eat) the target if dead/dying, don't shoot it.
return ..()
/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM)
. = ..()
if(.) // Do some additional checks because we have Special Code(tm).
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
if(istype(H.species, /datum/species/monkey)) // istype() is so they'll eat the alien monkeys too.
return TRUE // Monkeys are always food (sorry Pun Pun).
else if(H.species && H.species.name == SPECIES_PROMETHEAN)
return FALSE // Prometheans are always our friends.
if(discipline && !rabid)
return FALSE // We're a good slime.
// Commands, reactions, etc
/datum/ai_holder/simple_mob/xenobio_slime/on_hear_say(mob/living/speaker, message)
ai_log("xenobio_slime/on_hear_say([speaker], [message]) : Entered.", AI_LOG_DEBUG)
var/mob/living/simple_mob/slime/xenobio/my_slime = holder
if((findtext(message, num2text(my_slime.number)) || findtext(message, my_slime.name) || findtext(message, "slimes"))) // Talking to us.
// First, make sure it's actually a player saying something and not an AI, or else we risk infinite loops.
if(!speaker.client)
return
// Are all slimes being referred to?
// var/mass_order = FALSE
// if(findtext(message, "slimes"))
// mass_order = TRUE
// Say hello back.
if(findtext(message, "hello") || findtext(message, "hi") || findtext(message, "greetings"))
delayed_say(pick("Hello...", "Hi..."), speaker)
// Follow request.
if(findtext(message, "follow") || findtext(message, "come with me"))
if(!can_command(speaker))
delayed_say(pick("No...", "I won't follow..."), speaker)
return
delayed_say("Yes... I follow \the [speaker]...", speaker)
set_follow(speaker)
// Squish request.
if(findtext(message , "squish"))
if(!can_command(speaker))
delayed_say("No...", speaker)
return
spawn(rand(1 SECOND, 2 SECONDS))
if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime.
return
my_slime.squish()
// Stop request.
if(findtext(message, "stop") || findtext(message, "halt") || findtext(message, "cease"))
if(my_slime.victim) // We're being asked to stop eatting someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE)
my_slime.stop_consumption()
if(target) // We're being asked to stop chasing someone.
if(!can_command(speaker) || !is_justified_to_discipline())
delayed_say("No...", speaker)
return
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified.
lost_target()
if(leader) // We're being asked to stop following someone.
if(can_command(speaker) == SLIME_COMMAND_FRIEND || leader == speaker)
delayed_say("Yes... I'll stop...", speaker)
lose_follow()
else
delayed_say("No... I'll keep following \the [leader]...", speaker)
/* // Commented out since its mostly useless now due to slimes refusing to attack if it would make them naughty.
// Murder request
if(findtext(message, "harm") || findtext(message, "attack") || findtext(message, "kill") || findtext(message, "murder") || findtext(message, "eat") || findtext(message, "consume") || findtext(message, "absorb"))
if(can_command(speaker) < SLIME_COMMAND_FACTION)
delayed_say("No...", speaker)
return
for(var/mob/living/L in view(7, my_slime) - list(my_slime, speaker))
if(L == src)
continue // Don't target ourselves.
var/list/valid_names = splittext(L.name, " ") // Should output list("John", "Doe") as an example.
for(var/line in valid_names) // Check each part of someone's name.
if(findtext(message, lowertext(line))) // If part of someone's name is in the command, the slime targets them if allowed to.
if(!(mass_order && line == "slime")) //don't think random other slimes are target
if(can_attack(L))
delayed_say("Okay... I attack \the [L]...", speaker)
give_target(L)
return
else
delayed_say("No... I won't attack \the [L].", speaker)
return
// If we're here, it couldn't find anyone with that name.
delayed_say("No... I don't know who to attack...", speaker)
*/
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 ..()
+290
View File
@@ -0,0 +1,290 @@
// This is a datum-based artificial intelligence for simple mobs (and possibly others) to use.
// The neat thing with having this here instead of on the mob is that it is independant of Life(), and that different mobs
// can use a more or less complex AI by giving it a different datum.
/mob/living
var/datum/ai_holder/ai_holder = null
var/ai_holder_type = null // Which ai_holder datum to give to the mob when initialized. If null, nothing happens.
/mob/living/initialize()
if(ai_holder_type)
ai_holder = new ai_holder_type(src)
return ..()
/mob/living/Destroy()
QDEL_NULL(ai_holder)
return ..()
/datum/ai_holder
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.
/datum/ai_holder/hostile
hostile = TRUE
/datum/ai_holder/retaliate
hostile = TRUE
retaliate = TRUE
/datum/ai_holder/New(var/new_holder)
ASSERT(new_holder)
holder = new_holder
SSai.processing += src
home_turf = get_turf(holder)
..()
/datum/ai_holder/Destroy()
holder = null
SSai.processing -= src // We might've already been asleep and removed, but byond won't care if we do this again and it saves a conditional.
home_turf = null
return ..()
// Now for the actual AI stuff.
// Makes this ai holder not get processed.
// Called automatically when the host mob is killed.
// Potential future optimization would be to sleep AIs which mobs that are far away from in-round players.
/datum/ai_holder/proc/go_sleep()
if(stance == STANCE_SLEEP)
return
forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use.
set_stance(STANCE_SLEEP)
SSai.processing -= src
// Reverses the above proc.
// Revived mobs will wake their AI if they have one.
/datum/ai_holder/proc/go_wake()
if(stance != STANCE_SLEEP)
return
if(!should_wake())
return
set_stance(STANCE_IDLE)
SSai.processing += src
/datum/ai_holder/proc/should_wake()
if(holder.client && !autopilot)
return FALSE
if(holder.stat >= DEAD)
return FALSE
return TRUE
// Resets a lot of 'memory' vars.
/datum/ai_holder/proc/forget_everything()
// Some of these might be redundant, but hopefully this prevents future bugs if that changes.
lose_follow()
lose_target()
lose_target_position()
give_up_movement()
// '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_special_tactic()
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()
if(busy)
return
handle_special_strategical()
handle_stance_strategical()
// Override these for special things without polluting the main loop.
/datum/ai_holder/proc/handle_special_tactic()
/datum/ai_holder/proc/handle_special_strategical()
/*
//AI Actions
if(!ai_inactive)
//Stanceyness
handle_stance()
//Movement
if(!stop_automated_movement && wander && !anchored) //Allowed to move?
handle_wander_movement()
//Speaking
if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter?
handle_idle_speaking()
//Resisting out buckles
if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
handle_resist()
//Resisting out of closets
if(istype(loc,/obj/structure/closet))
var/obj/structure/closet/C = loc
if(C.welded)
resist()
else
C.open()
*/
// For setting the stance WITHOUT processing it
/datum/ai_holder/proc/set_stance(var/new_stance)
ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO)
stance = new_stance
if(stance_coloring) // For debugging or really weird mobs.
stance_color()
// This is called every half a second.
/datum/ai_holder/proc/handle_stance_tactical()
ai_log("========= Fast Process Beginning ==========", AI_LOG_TRACE) // This is to make it easier visually to disinguish between 'blocks' of what a tick did.
ai_log("handle_stance_tactical() : Called.", AI_LOG_TRACE)
if(stance == STANCE_SLEEP)
ai_log("handle_stance_tactical() : Going to sleep.", AI_LOG_TRACE)
go_sleep()
return
if(target && can_see_target(target))
track_target_position()
if(stance != STANCE_DISABLED && is_disabled()) // Stunned/confused/etc
ai_log("handle_stance_tactical() : Disabled.", AI_LOG_TRACE)
set_stance(STANCE_DISABLED)
return
if(stance in STANCES_COMBAT)
// Should resist? We check this before fleeing so that we can actually flee and not be trapped in a chair.
if(holder.incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
ai_log("handle_stance_tactical() : Going to handle_resist().", AI_LOG_TRACE)
handle_resist()
else if(istype(holder.loc, /obj/structure/closet))
var/obj/structure/closet/C = holder.loc
ai_log("handle_stance_tactical() : Inside a closet. Going to attempt escape.", AI_LOG_TRACE)
if(C.sealed)
holder.resist()
else
C.open()
// Should we flee?
if(should_flee())
ai_log("handle_stance_tactical() : Going to flee.", AI_LOG_TRACE)
set_stance(STANCE_FLEE)
return
switch(stance)
if(STANCE_IDLE)
if(should_go_home())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to go home.", AI_LOG_TRACE)
go_home()
else if(should_follow_leader())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to follow leader.", AI_LOG_TRACE)
set_stance(STANCE_FOLLOW)
else if(should_wander())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to wander randomly.", AI_LOG_TRACE)
handle_wander_movement()
if(STANCE_ALERT)
ai_log("handle_stance_tactical() : STANCE_ALERT, going to threaten_target().", AI_LOG_TRACE)
threaten_target()
if(STANCE_APPROACH)
ai_log("handle_stance_tactical() : STANCE_APPROACH, going to walk_to_target().", AI_LOG_TRACE)
walk_to_target()
if(STANCE_FIGHT)
ai_log("handle_stance_tactical() : STANCE_FIGHT, going to engage_target().", AI_LOG_TRACE)
engage_target()
if(STANCE_MOVE)
ai_log("handle_stance_tactical() : STANCE_MOVE, going to walk_to_destination().", AI_LOG_TRACE)
walk_to_destination()
if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order.
ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE)
walk_to_destination()
if(STANCE_FOLLOW)
ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE)
walk_to_leader()
if(STANCE_FLEE)
ai_log("handle_stance_tactical() : STANCE_FLEE, going to flee_from_target().", AI_LOG_TRACE)
flee_from_target()
if(STANCE_DISABLED)
ai_log("handle_stance_tactical() : STANCE_DISABLED.", AI_LOG_TRACE)
if(!is_disabled())
ai_log("handle_stance_tactical() : No longer disabled.", AI_LOG_TRACE)
set_stance(STANCE_IDLE)
else
handle_disabled()
ai_log("handle_stance_tactical() : Exiting.", AI_LOG_TRACE)
ai_log("========= Fast Process Ending ==========", AI_LOG_TRACE)
// This is called every two seconds.
/datum/ai_holder/proc/handle_stance_strategical()
ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE)
ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE)
switch(stance)
if(STANCE_IDLE)
if(speak_chance) // In the long loop since otherwise it wont shut up.
handle_idle_speaking()
if(hostile)
ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE)
find_target()
if(STANCE_APPROACH)
if(target)
ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE)
calculate_path(target)
if(STANCE_MOVE)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE)
if(STANCE_FOLLOW)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_FOLLOW, found target and was inturrupted.", AI_LOG_TRACE)
else if(leader)
ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE)
calculate_path(leader)
ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE)
ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE)
// 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
// Similar to above but only returns 1 or 0.
/mob/living/proc/has_AI()
return get_AI_stance() ? TRUE : FALSE
// 'Taunts' the AI into attacking the taunter.
/mob/living/proc/taunt(atom/movable/taunter, force_target_switch = FALSE)
if(ai_holder)
ai_holder.receive_taunt(taunter, force_target_switch)
+308
View File
@@ -0,0 +1,308 @@
// This file is for actual fighting. Targeting is in a seperate file.
/datum/ai_holder
var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall.
var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true.
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 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.
// This does the actual attacking.
/datum/ai_holder/proc/engage_target()
ai_log("engage_target() : Entering.", AI_LOG_DEBUG)
// Can we still see them?
// if(!target || !can_attack(target) || (!(target in list_targets())) )
if(!target || !can_attack(target))
ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE)
lose_target() // We lost them.
if(!find_target()) // If we can't get a new one, then wait for a bit and then time out.
set_stance(STANCE_IDLE)
lost_target()
ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG)
return
// if(lose_target_time + lose_target_timeout < world.time)
// ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE) // It must've been the wind.
// lost_target()
// ai_log("engage_target() : Exiting.", AI_LOG_DEBUG)
// return
// // But maybe we do one last ditch effort.
// if(!target_last_seen_turf || intelligence_level < AI_SMART)
// ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE)
// else
// ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE)
// engage_unseen_enemy()
else
ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE)
var/distance = get_dist(holder, target)
ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE)
holder.face_atom(target)
last_conflict_time = world.time
request_help() // Call our allies.
// Do a 'special' attack, if one is allowed.
// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range))
if(holder.ICheckSpecialAttack(target))
ai_log("engage_target() : Attempting a special attack.", AI_LOG_TRACE)
on_engagement(target)
if(special_attack(target)) // If this fails, then we try a regular melee/ranged attack.
ai_log("engage_target() : Successful special attack. Exiting.", AI_LOG_DEBUG)
return
// Stab them.
else if(distance <= 1 && !pointblank)
ai_log("engage_target() : Attempting a melee attack.", AI_LOG_TRACE)
on_engagement(target)
melee_attack(target)
// Shoot them.
else if(holder.ICheckRangedAttack(target) && (distance <= max_range(target)) )
on_engagement(target)
if(firing_lanes && !test_projectile_safety(target))
// Nudge them a bit, maybe they can shoot next time.
step_rand(holder)
holder.face_atom(target)
ai_log("engage_target() : Could not safely fire at target. Exiting.", AI_LOG_DEBUG)
return
ai_log("engage_target() : Attempting a ranged attack.", AI_LOG_TRACE)
ranged_attack(target)
// Run after them.
else if(!stand_ground)
ai_log("engage_target() : Target ([target]) too far away. Exiting.", AI_LOG_DEBUG)
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/A)
pre_melee_attack(A)
. = holder.IAttack(A)
if(.)
post_melee_attack(A)
// Ditto.
/datum/ai_holder/proc/ranged_attack(atom/A)
pre_ranged_attack(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)
pre_special_attack(AM)
. = holder.ISpecialAttack(AM)
if(.)
post_special_attack(AM)
// 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/A)
// Called before a ranged attack is attempted.
/datum/ai_holder/proc/pre_ranged_attack(atom/A)
// Called before a melee attack is attempted.
/datum/ai_holder/proc/pre_melee_attack(atom/A)
// Called before a 'special' attack is attempted.
/datum/ai_holder/proc/pre_special_attack(atom/A)
// Called after a successful (IE not on cooldown) ranged attack.
// Note that this is not whether the projectile actually hit, just that one was launched.
/datum/ai_holder/proc/post_ranged_attack(atom/A)
// Ditto but for melee.
/datum/ai_holder/proc/post_melee_attack(atom/A)
// And one more for special snowflake attacks.
/datum/ai_holder/proc/post_special_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)
var/mob/living/L = check_trajectory(AM, holder) // This isn't always reliable but its better than the previous method.
// world << "Checked trajectory, would hit [L]."
if(istype(L)) // Did we hit a mob?
// world << "Hit [L]."
if(holder.IIsAlly(L))
// world << "Would hit ally, canceling."
return FALSE // We would hit a friend!
// world << "Won't threaten ally, firing."
return TRUE // Otherwise we don't care, even if its not the intended target.
else
if(!isliving(AM)) // If the original target was an object, then let it happen if it doesn't threaten an ally.
// world << "Targeting object, ignoring and firing."
return TRUE
// world << "Not sure."
return !conserve_ammo // If we have infinite ammo than shooting the wall isn't so bad, but otherwise lets not.
// Test if we are within range to attempt an attack, melee or ranged.
/datum/ai_holder/proc/within_range(atom/movable/AM)
var/distance = get_dist(holder, AM)
if(distance <= 1)
return TRUE // Can melee.
else if(holder.ICheckRangedAttack(AM) && distance <= max_range(AM))
return TRUE // Can shoot.
return FALSE
// Determines how close the AI will move to its target.
/datum/ai_holder/proc/closest_distance(atom/movable/AM)
return max(max_range(AM) - 1, 1) // Max range -1 just because we don't want to constantly get kited
// Can be used to conditionally do a ranged or melee attack.
/datum/ai_holder/proc/max_range(atom/movable/AM)
return holder.ICheckRangedAttack(AM) ? 7 : 1
// Goes to the target, to attack them.
// Called when in STANCE_APPROACH.
/datum/ai_holder/proc/walk_to_target()
ai_log("walk_to_target() : Entering.", AI_LOG_DEBUG)
// Make sure we can still chase/attack them.
if(!target || !can_attack(target))
ai_log("walk_to_target() : Lost target.", AI_LOG_INFO)
if(!find_target())
lost_target()
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
else
ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO)
// Find out where we're going.
var/get_to = closest_distance(target)
var/distance = get_dist(holder, target)
ai_log("walk_to_target() : get_to is [get_to].", AI_LOG_TRACE)
// We're here!
// Special case: Our holder has a special attack that is ranged, but normally the holder uses melee.
// If that happens, we'll switch to STANCE_FIGHT so they can use it. If the special attack is limited, they'll likely switch back next tick.
if(distance <= get_to || holder.ICheckSpecialAttack(target))
ai_log("walk_to_target() : Within range.", AI_LOG_INFO)
forget_path()
set_stance(STANCE_FIGHT)
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
// Otherwise keep walking.
if(!stand_ground)
walk_path(target, get_to)
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
// Resists out of things.
// Sometimes there are times you want your mob to be buckled to something, so override this for when that is needed.
/datum/ai_holder/proc/handle_resist()
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)
// 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)
)
ai_log("breakthrough() : Starting peaceful pass.", AI_LOG_DEBUG)
var/result = FALSE
// 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
// 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, 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()
// 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)
else if(W.is_fulltile())
ai_log("destroy_surroundings() : Attacking full tile window.", AI_LOG_INFO)
return holder.IAttack(W)
// 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
@@ -0,0 +1,43 @@
// Used for fighting invisible things.
// Used when a target is out of sight or invisible.
/datum/ai_holder/proc/engage_unseen_enemy()
// Lets do some last things before giving up.
if(!ranged)
if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there.
// Go to where you last saw the enemy.
give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION.
else // We last saw them next to us, so do a blind attack on that tile.
melee_on_tile(target_last_seen_turf)
else if(!conserve_ammo)
shoot_near_turf(target_last_seen_turf)
// This shoots semi-randomly near a specific turf.
/datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf)
if(!ranged)
return // Can't shoot.
if(get_dist(holder, targeted_turf) > max_range(targeted_turf))
return // Too far to shoot.
var/turf/T = pick(RANGE_TURFS(2, targeted_turf)) // The turf we're actually gonna shoot at.
on_engagement(T)
if(firing_lanes && !test_projectile_safety(T))
step_rand(holder)
holder.face_atom(T)
return
ranged_attack(T)
// Attempts to attack something on a specific tile.
// TODO: Put on mob/living?
/datum/ai_holder/proc/melee_on_tile(turf/T)
var/mob/living/L = locate() in T
if(!L)
T.visible_message("\The [holder] attacks nothing around \the [T].")
return
if(holder.IIsAlly(L)) // Don't hurt our ally.
return
melee_attack(L)
+134
View File
@@ -0,0 +1,134 @@
// Contains code for speaking and emoting.
/datum/ai_holder
var/threaten = FALSE // If hostile and sees a valid target, gives a 'warning' to the target before beginning the attack.
var/threatening = FALSE // If the mob actually gave the warning, checked so it doesn't constantly yell every tick.
var/threaten_delay = 3 SECONDS // How long a 'threat' lasts, until actual fighting starts. If null, the mob never starts the fight but still does the threat.
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 fighting being used, in world.time.
var/last_threaten_time = null // Ditto but only for threats.
var/speak_chance = 0 // Probability that the mob talks (this is 'X in 200' chance since even 1/100 is pretty noisy)
/datum/ai_holder/proc/should_threaten()
if(!threaten)
return FALSE // We don't negotiate.
if(target in attackers)
return FALSE // They (or someone like them) attacked us before, escalate immediately.
if(!will_threaten(target))
return FALSE // Pointless to threaten an animal, a mindless drone, or an object.
if(stance in STANCES_COMBAT)
return FALSE // We're probably already fighting or recently fought if not in these stances.
if(last_threaten_time && threaten_delay && last_conflict_time + threaten_timeout > world.time)
return FALSE // We threatened someone recently, so lets show them we mean business.
return TRUE // Lets give them a chance to choose wisely and walk away.
/datum/ai_holder/proc/threaten_target()
holder.face_atom(target) // Constantly face the target.
if(!threatening) // First tick.
threatening = TRUE
last_threaten_time = world.time
if(holder.say_list)
holder.ISay(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?
var/should_escalate = FALSE
if(threaten_delay && last_threaten_time + threaten_delay < world.time) // Waited too long.
should_escalate = TRUE
else if(last_conflict_time + threaten_timeout > world.time) // We got attacked while threatening them.
should_escalate = TRUE
if(should_escalate)
threatening = FALSE
set_stance(STANCE_APPROACH)
if(holder.say_list)
holder.ISay(safepick(holder.say_list.say_escalate))
else
return // Wait a bit.
else // They left, or so we think.
threatening = FALSE
set_stance(STANCE_IDLE)
if(holder.say_list)
holder.ISay(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)
if(!isliving(the_target))
return FALSE // Turrets don't give a fuck so neither will we.
/*
// Find a nice way of doing this later.
if(istype(the_target, /mob/living/simple_mob) && istype(holder, /mob/living/simple_mob))
var/mob/living/simple_mob/us = holder
var/mob/living/simple_mob/them = target
if(them.intelligence_level < us.intelligence_level) // Todo: Bitflag these.
return FALSE // Humanoids don't care about drones/animals/etc. Drones don't care about animals, and so on.
*/
return TRUE
// Temp defines to make the below code a bit more readable.
#define COMM_SAY "say"
#define COMM_AUDIBLE_EMOTE "audible emote"
#define COMM_VISUAL_EMOTE "visual emote"
/datum/ai_holder/proc/handle_idle_speaking()
if(rand(0,200) < speak_chance)
// 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
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(holder.say_list.emote_hear.len)
comm_types += COMM_AUDIBLE_EMOTE
if(holder.say_list.emote_see.len)
comm_types += COMM_VISUAL_EMOTE
if(!comm_types.len)
return // All the relevant lists are empty, so do nothing.
switch(pick(comm_types))
if(COMM_SAY)
holder.ISay(safepick(holder.say_list.speak))
if(COMM_AUDIBLE_EMOTE)
holder.audible_emote(safepick(holder.say_list.emote_hear))
if(COMM_VISUAL_EMOTE)
holder.visible_emote(safepick(holder.say_list.emote_see))
#undef COMM_SAY
#undef COMM_AUDIBLE_EMOTE
#undef COMM_VISUAL_EMOTE
// Handles the holder hearing a mob's say()
// Does nothing by default, override this proc for special behavior.
/datum/ai_holder/proc/on_hear_say(mob/living/speaker, message)
return
// This is to make responses feel a bit more natural and not instant.
/datum/ai_holder/proc/delayed_say(var/message, var/mob/speak_to)
spawn(rand(1 SECOND, 2 SECONDS))
if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime.
return
if(speak_to)
holder.face_atom(speak_to)
holder.ISay(message)
+115
View File
@@ -0,0 +1,115 @@
// Involves cooperating with other ai_holders.
/datum/ai_holder
var/cooperative = FALSE // If true, asks allies to help when fighting something.
var/call_distance = 14 // How far away calls for help will go for.
var/last_helpask_time = 0 // world.time when a mob asked for help.
var/list/faction_friends = list() // List of all mobs inside the faction with ai_holders that have cooperate on, to call for help without using range().
// Note that this is only used for sending calls out. Receiving calls doesn't care about this list, only if the mob is in the faction.
// This means the AI could respond to a player's call for help, if a way to do so was implemented.
// These vars don't do anything currently. They did before but an optimization made them nonfunctional.
// It was probably worth it.
var/call_players = FALSE // (Currently nonfunctional) If true, players get notified of an allied mob calling for help.
var/called_player_message = "needs help!" // (Currently nonfunctional) Part of a message used when above var is true. Full message is "\The [holder] [called_player_message]"
/datum/ai_holder/New(new_holder)
..()
if(cooperative)
build_faction_friends()
/datum/ai_holder/Destroy()
if(faction_friends.len) //This list is shared amongst the faction
faction_friends -= src
return ..()
// Handles everything about that list.
// Call on initialization or if something weird happened like the mob switched factions.
/datum/ai_holder/proc/build_faction_friends()
if(faction_friends.len) // Already have a list.
// Assume we're moving to a new faction.
faction_friends -= src // Get us out of the current list shared by everyone else.
faction_friends = list() // Then make our list empty and unshared in case we become a loner.
// Find another AI-controlled mob in the same faction if possible.
var/mob/living/first_friend
for(var/mob/living/L in living_mob_list)
if(L.faction == holder.faction && L.ai_holder)
first_friend = L
break
if(first_friend) // Joining an already established faction.
faction_friends = first_friend.ai_holder.faction_friends
faction_friends |= holder
else // We're the 'founder' (first and/or only member) of this faction.
faction_friends |= holder
// Requests help in combat from other mobs possessing ai_holders.
/datum/ai_holder/proc/request_help()
ai_log("request_help() : Entering.", AI_LOG_DEBUG)
if(!cooperative || ((world.time - last_helpask_time) < 10 SECONDS))
return
ai_log("request_help() : Asking for help.", AI_LOG_INFO)
last_helpask_time = world.time
// for(var/mob/living/L in range(call_distance, holder))
for(var/mob/living/L in faction_friends)
if(L == holder) // Lets not call ourselves.
continue
if(holder.z != L.z) // On seperate z-level.
continue
if(get_dist(L, holder) > call_distance) // Too far to 'hear' the call for help.
continue
if(holder.IIsAlly(L))
// This will currently never run sadly, until faction_friends is made to accept players too.
// That might be for the best since I can imagine it getting spammy in a big fight.
if(L.client && call_players) // Dealing with a player.
ai_log("request_help() : Asking [L] (Player) for help.", AI_LOG_INFO)
to_chat(L, "<span class='critical'>\The [holder] [called_player_message]</span>")
else if(L.ai_holder) // Dealing with an AI.
ai_log("request_help() : Asking [L] (AI) for help.", AI_LOG_INFO)
L.ai_holder.help_requested(holder)
ai_log("request_help() : Exiting.", AI_LOG_DEBUG)
// What allies receive when someone else is calling for help.
/datum/ai_holder/proc/help_requested(mob/living/friend)
ai_log("help_requested() : Entering.", AI_LOG_DEBUG)
if(stance == STANCE_SLEEP)
ai_log("help_requested() : Help requested by [friend] but we are asleep.", AI_LOG_INFO)
return
if(!cooperative)
ai_log("help_requested() : Help requested by [friend] but we're not cooperative.", AI_LOG_INFO)
return
if(stance in STANCES_COMBAT)
ai_log("help_requested() : Help requested by [friend] but we are busy fighting something else.", AI_LOG_INFO)
return
if(!can_act())
ai_log("help_requested() : Help requested by [friend] but cannot act (stunned or dead).", AI_LOG_INFO)
return
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
if(get_dist(holder, friend) <= vision_range) // Within our sight.
ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO)
if(friend.ai_holder) // AI calling for help.
if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target.
last_conflict_time = world.time // So we attack immediately and not threaten.
give_target(friend.ai_holder.target) // This will set us to the appropiate stance.
ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG)
return
// Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range.
// So assuming we're AI controlled, we'll go to them and see whats wrong.
ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO)
set_follow(friend, 10 SECONDS)
ai_log("help_requested() : Exiting.", AI_LOG_DEBUG)
+89
View File
@@ -0,0 +1,89 @@
// Contains settings to make it easier to debug things.
/datum/ai_holder
var/path_display = FALSE // Displays a visual path when A* is being used.
var/path_icon = 'icons/misc/debug_group.dmi' // What icon to use for the overlay
var/path_icon_state = "red" // What state to use for the overlay
var/image/path_overlay // A reference to the overlay
var/last_turf_display = FALSE // Similar to above, but shows the target's last known turf visually.
var/last_turf_icon_state = "green" // A seperate icon_state from the previous.
var/image/last_turf_overlay // Another reference for an overlay.
var/stance_coloring = FALSE // Colors the mob depending on its stance.
var/debug_ai = AI_LOG_OFF // The level of debugging information to display to people who can see log_debug().
/datum/ai_holder/New()
..()
path_overlay = new(path_icon,path_icon_state)
last_turf_overlay = new(path_icon, last_turf_icon_state)
/datum/ai_holder/Destroy()
path_overlay = null
last_turf_overlay = null
return ..()
//For debug purposes!
/datum/ai_holder/proc/ai_log_output(var/msg = "missing message", var/ver = AI_LOG_INFO)
var/span_type
switch(ver)
if(AI_LOG_OFF)
return
if(AI_LOG_ERROR)
span_type = "debug_error"
if(AI_LOG_WARNING)
span_type = "debug_warning"
if(AI_LOG_INFO)
span_type = "debug_info"
if(AI_LOG_DEBUG)
span_type = "debug_debug" // RAS syndrome at work.
if(AI_LOG_TRACE)
span_type = "debug_trace"
if(ver <= debug_ai)
log_debug("<span class='[span_type]'>AI: ([holder]:\ref[holder] | [holder.x],[holder.y],[holder.z])(@[world.time]): [msg] </span>")
// Colors the mob based on stance, to visually tell what stance it is for debugging.
// Probably not something you want for regular use.
/datum/ai_holder/proc/stance_color()
var/new_color = null
switch(stance)
if(STANCE_SLEEP)
new_color = "#FFFFFF" // White
if(STANCE_IDLE)
new_color = "#00FF00" // Green
if(STANCE_ALERT)
new_color = "#FFFF00" // Yellow
if(STANCE_APPROACH)
new_color = "#FF9933" // Orange
if(STANCE_FIGHT)
new_color = "#FF0000" // Red
if(STANCE_MOVE)
new_color = "#0000FF" // Blue
if(STANCE_REPOSITION)
new_color = "#FF00FF" // Purple
if(STANCE_FOLLOW)
new_color = "#00FFFF" // Cyan
if(STANCE_FLEE)
new_color = "#666666" // Grey
if(STANCE_DISABLED)
new_color = "#000000" // Black
holder.color = new_color
// Turns on all the debugging stuff.
/datum/ai_holder/proc/debug()
stance_coloring = TRUE
path_display = TRUE
last_turf_display = TRUE
debug_ai = AI_LOG_INFO
/datum/ai_holder/hostile/debug
wander = FALSE
conserve_ammo = FALSE
intelligence_level = AI_SMART
stance_coloring = TRUE
path_display = TRUE
last_turf_display = TRUE
debug_ai = AI_LOG_INFO
+95
View File
@@ -0,0 +1,95 @@
// Handles AI while stunned or otherwise disabled.
/datum/ai_holder
var/respect_confusion = TRUE // If false, the mob won't wander around recklessly.
// If our holder is able to do anything.
/datum/ai_holder/proc/can_act()
if(holder.stat) // Dead or unconscious.
ai_log("can_act() : Stat was non-zero ([holder.stat]).", AI_LOG_TRACE)
return FALSE
if(holder.incapacitated(INCAPACITATION_DISABLED)) // Stunned in some form.
ai_log("can_act() : Incapacited.", AI_LOG_TRACE)
return FALSE
return TRUE
// Test if we should switch to STANCE_DISABLE.
// Currently tests for death, stuns, and confusion.
/datum/ai_holder/proc/is_disabled()
if(!can_act())
return TRUE
if(is_confused())
return TRUE
return FALSE
/datum/ai_holder/proc/is_confused()
return holder.confused > 0 && respect_confusion
// Called by the main loop.
/datum/ai_holder/proc/handle_disabled()
if(!can_act())
return // Just sit there and take it.
else if(is_confused())
dangerous_wander() // Let's bump into allies and hit them.
// Similar to normal wander, but will walk into tiles that are harmful, and attack anything they bump into, including allies.
// Occurs when confused.
/datum/ai_holder/proc/dangerous_wander()
ai_log("dangerous_wander() : Entered.", AI_LOG_DEBUG)
if(isturf(holder.loc) && can_act())
// Test if we should refrain from falling/attacking allies, if we're smart enough to realize that.
if(intelligence_level > AI_NORMAL)
var/unsafe = FALSE
tile_test:
for(var/dir_tested in cardinal)
var/turf/turf_tested = get_step(holder, dir_tested)
// Look for unsafe tiles.
if(!turf_tested.is_safe_to_enter(holder))
unsafe = TRUE
break
// Look for allies.
for(var/mob/living/L in turf_tested)
if(holder.IIsAlly(L))
unsafe = TRUE
break tile_test
if(unsafe)
ai_log("dangerous_wander() : Staying still due to risk of harm to self or allies.", AI_LOG_TRACE)
return // Just stay still.
var/moving_to = 0
moving_to = pick(cardinal)
var/turf/T = get_step(holder, moving_to)
var/mob/living/L = locate() in T
if(L)
// Attack whoever's on the tile. Even if it's an ally.
ai_log("dangerous_wander() : Going to confuse-attack [L].", AI_LOG_TRACE)
melee_attack(L)
else
// Move to the tile. Even if it's unsafe.
ai_log("dangerous_wander() : Going to confuse-walk to [T] ([T.x],[T.y],[T.z]).", AI_LOG_TRACE)
holder.IMove(T, safety = FALSE)
ai_log("dangerous_wander() : Exited.", AI_LOG_DEBUG)
/*
// Wanders randomly in cardinal directions.
/datum/ai_holder/proc/handle_wander_movement()
ai_log("handle_wander_movement() : Entered.", AI_LOG_DEBUG)
if(isturf(holder.loc) && can_act())
wander_delay--
if(wander_delay <= 0)
if(!wander_when_pulled && holder.pulledby)
ai_log("handle_wander_movement() : Being pulled and cannot wander. Exiting.", AI_LOG_DEBUG)
return
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.IMove(get_step(holder,moving_to))
wander_delay = base_wander_delay
ai_log("handle_wander_movement() : Exited.", AI_LOG_DEBUG)
*/
+45
View File
@@ -0,0 +1,45 @@
// This code handles what to do inside STANCE_FLEE.
/datum/ai_holder
var/can_flee = TRUE // If they're even allowed to flee.
var/flee_when_dying = TRUE // If they should flee when low on health.
var/dying_threshold = 0.3 // How low on health the holder needs to be before fleeing. Defaults to 30% or lower health.
var/flee_when_outmatched = FALSE // If they should flee upon reaching a specific tension threshold.
var/outmatched_threshold = 200 // The tension threshold needed for a mob to decide it should run away.
/datum/ai_holder/proc/should_flee(force = FALSE)
if(holder.has_modifier_of_type(/datum/modifier/berserk)) // Berserked mobs will never flee, even if 'forced' to.
return FALSE
if(force)
return TRUE
if(can_flee)
if(special_flee_check())
return TRUE
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!
else if(flee_when_outmatched && holder.get_tension() >= outmatched_threshold)
return TRUE // We're fighting something way way stronger then us.
return FALSE
// Override for special fleeing conditionally.
/datum/ai_holder/proc/special_flee_check()
return FALSE
/datum/ai_holder/proc/flee_from_target()
ai_log("flee_from_target() : Entering.", AI_LOG_DEBUG)
if(!target || !should_flee() || !can_attack(target)) // can_attack() is used since it checks the same things we would need to anyways.
ai_log("flee_from_target() : Lost target to flee from.", AI_LOG_INFO)
lose_target()
set_stance(STANCE_IDLE)
ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG)
return
ai_log("flee_from_target() : Stepping away.", AI_LOG_TRACE)
step_away(holder, target, vision_range)
ai_log("flee_from_target() : Exiting.", AI_LOG_DEBUG)
+68
View File
@@ -0,0 +1,68 @@
// This handles following a specific atom/movable, without violently murdering it.
/datum/ai_holder
// Following.
var/atom/movable/leader = null // The movable atom that the mob wants to follow.
var/follow_distance = 2 // How far leader must be to start moving towards them.
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_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_TRACE)
return
// Did we time out?
if(follow_until_time && world.time > follow_until_time)
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_TRACE)
return
var/get_to = follow_distance
var/distance = get_dist(holder, leader)
ai_log("walk_to_leader() : get_to is [get_to].", AI_LOG_TRACE)
// We're here!
if(distance <= get_to)
give_up_movement()
set_stance(STANCE_IDLE)
ai_log("walk_to_leader() : Within range, exiting.", AI_LOG_INFO)
return
ai_log("walk_to_leader() : Walking.", AI_LOG_TRACE)
walk_path(leader, get_to)
ai_log("walk_to_leader() : Exiting.",AI_LOG_DEBUG)
/datum/ai_holder/proc/set_follow(mob/living/L, follow_for = 0)
ai_log("set_follow() : Entered.", AI_LOG_DEBUG)
if(!L)
ai_log("set_follow() : Was told to follow a nonexistant mob.", AI_LOG_ERROR)
return FALSE
leader = L
follow_until_time = !follow_for ? 0 : world.time + follow_for
ai_log("set_follow() : Exited.", AI_LOG_DEBUG)
return TRUE
/datum/ai_holder/proc/lose_follow()
ai_log("lose_follow() : Entered.", AI_LOG_DEBUG)
ai_log("lose_follow() : Going to lose leader [leader].", AI_LOG_INFO)
leader = null
give_up_movement()
ai_log("lose_follow() : Exited.", AI_LOG_DEBUG)
/datum/ai_holder/proc/should_follow_leader()
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
+154
View File
@@ -0,0 +1,154 @@
/datum/ai_holder
// General.
var/turf/destination = null // The targeted tile the mob wants to walk to.
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. 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().
// 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.
var/wander_delay = 0 // How many ticks until the mob can move a tile in handle_wander_movement().
var/base_wander_delay = 2 // What the above var gets set to when it wanders. Note that a tick happens every half a second.
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_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_TRACE)
return
var/get_to = min_distance_to_destination
var/distance = get_dist(holder, destination)
ai_log("walk_to_destination() : get_to is [get_to].", AI_LOG_TRACE)
// We're here!
if(distance <= get_to)
give_up_movement()
set_stance(stance == STANCE_REPOSITION ? STANCE_APPROACH : STANCE_IDLE)
ai_log("walk_to_destination() : Destination reached. Exiting.", AI_LOG_INFO)
return
ai_log("walk_to_destination() : Walking.", AI_LOG_TRACE)
walk_path(destination, get_to)
ai_log("walk_to_destination() : Exiting.",AI_LOG_TRACE)
/datum/ai_holder/proc/should_go_home()
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)
ai_log("go_home() : Telling holder to go home.", AI_LOG_INFO)
lose_follow() // So they don't try to path back and forth.
give_destination(home_turf, max_home_distance)
else
ai_log("go_home() : Told to go home without home_turf.", AI_LOG_ERROR)
/datum/ai_holder/proc/give_destination(turf/new_destination, min_distance = 1, combat = FALSE)
ai_log("give_destination() : Entering.", AI_LOG_DEBUG)
destination = new_destination
min_distance_to_destination = min_distance
if(new_destination != null)
ai_log("give_destination() : Going to new destination.", AI_LOG_INFO)
set_stance(combat ? STANCE_REPOSITION : STANCE_MOVE)
return TRUE
else
ai_log("give_destination() : Given null destination.", AI_LOG_ERROR)
ai_log("give_destination() : Exiting.", AI_LOG_DEBUG)
// Walk towards whatever.
/datum/ai_holder/proc/walk_path(atom/A, get_to = 1)
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_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_INFO)
breakthrough(A) // We failed to move, time to smash things.
return
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.
ai_log("walk_path() : Too many failed_steps.", AI_LOG_DEBUG)
forget_path() // So lets try again with a new path.
failed_steps = 0
else
// 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_INFO)
breakthrough(A) // We failed to move, time to smash things.
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_TRACE)
if(!path.len)
return
if(path_display)
var/turf/T = src.path[1]
T.overlays -= path_overlay
// 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
// Wanders randomly in cardinal directions.
/datum/ai_holder/proc/handle_wander_movement()
ai_log("handle_wander_movement() : Entered.", AI_LOG_TRACE)
if(isturf(holder.loc) && can_act())
wander_delay--
if(wander_delay <= 0)
if(!wander_when_pulled && holder.pulledby)
ai_log("handle_wander_movement() : Being pulled and cannot wander. Exiting.", AI_LOG_DEBUG)
return
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.IMove(get_step(holder,moving_to))
wander_delay = base_wander_delay
ai_log("handle_wander_movement() : Exited.", AI_LOG_TRACE)
+58
View File
@@ -0,0 +1,58 @@
// This handles obtaining a (usually A*) path towards something, such as a target, destination, or leader.
// This interacts heavily with code inside ai_holder_movement.dm
/datum/ai_holder
// Pathfinding.
var/use_astar = FALSE // Do we use the more expensive A* implementation or stick with BYOND's default step_to()?
var/list/path = list() // A list of tiles that A* gave us as a solution to reach the target.
var/list/obstacles = list() // Things A* will try to avoid.
var/astar_adjacent_proc = /turf/proc/CardinalTurfsWithAccess // Proc to use when A* pathfinding. Default makes them bound to cardinals.
var/failed_steps = 0 // If move_once() fails to move the mob onto the correct tile, this increases. When it reaches 3, the path is recalc'd since they're probably stuck.
// This clears the stored A* path.
/datum/ai_holder/proc/forget_path()
ai_log("forget_path() : Entering.", AI_LOG_DEBUG)
if(path_display)
for(var/turf/T in path)
T.overlays -= path_overlay
path.Cut()
ai_log("forget_path() : Exiting.", AI_LOG_DEBUG)
/datum/ai_holder/proc/give_up_movement()
ai_log("give_up_movement() : Entering.", AI_LOG_DEBUG)
forget_path()
destination = null
ai_log("give_up_movement() : Exiting.", AI_LOG_DEBUG)
/datum/ai_holder/proc/calculate_path(atom/A, get_to = 1)
ai_log("calculate_path([A],[get_to]) : Entering.", AI_LOG_DEBUG)
if(!A)
ai_log("calculate_path() : Called without an atom. Exiting.",AI_LOG_WARNING)
return
if(!use_astar) // If we don't use A* then this is pointless.
ai_log("calculate_path() : Not using A*, Exiting.", AI_LOG_DEBUG)
return
get_path(get_turf(A), get_to)
ai_log("calculate_path() : Exiting.", AI_LOG_DEBUG)
//A* now, try to a path to a target
/datum/ai_holder/proc/get_path(var/turf/target,var/get_to = 1, var/max_distance = world.view*6)
ai_log("get_path() : Entering.",AI_LOG_DEBUG)
forget_path()
var/list/new_path = AStar(get_turf(holder.loc), target, astar_adjacent_proc, /turf/proc/Distance, min_target_dist = get_to, max_node_depth = max_distance, id = holder.IGetID(), exclude = obstacles)
if(new_path && new_path.len)
path = new_path
ai_log("get_path() : Made new path.",AI_LOG_DEBUG)
if(path_display)
for(var/turf/T in path)
T.overlays |= path_overlay
else
ai_log("get_path() : Failed to make new path. Exiting.",AI_LOG_DEBUG)
return 0
ai_log("get_path() : Exiting.", AI_LOG_DEBUG)
return path.len
+237
View File
@@ -0,0 +1,237 @@
// Used for assigning a target for attacking.
/datum/ai_holder
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/atom/movable/preferred_target = null// If set, and if given the chance, we will always prefer to target this over other options.
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.
var/vision_range = 7 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting.
var/respect_alpha = TRUE // If true, mobs with a sufficently low alpha will be treated as invisible.
var/alpha_vision_threshold = 127 // Targets with an alpha less or equal to this will be considered invisible. Requires above var to be true.
var/lose_target_time = 0 // world.time when a target was lost.
var/lose_target_timeout = 5 SECONDS // How long until a mob 'times out' and stops trying to find the mob that disappeared.
var/list/attackers = list() // List of strings of names of people who attacked us before in our life.
// This uses strings and not refs to allow for disguises, and to avoid needing to use weakrefs.
// A lot of this is based off of /TG/'s AI code.
// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
. = 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
// 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()
for(var/possible_target in possible_targets)
var/atom/A = possible_target
if(found(A)) // In case people want to override this.
. = list(A)
break
if(can_attack(A)) // Can we attack it?
. += A
continue
var/new_target = pick_target(.)
give_target(new_target)
return new_target
// 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.
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
if(preferred_target && preferred_target in targets)
chosen_target = preferred_target
else
chosen_target = pick(targets)
return chosen_target
// Step 4, give us our selected target.
/datum/ai_holder/proc/give_target(new_target)
target = new_target
if(target != null)
if(should_threaten())
set_stance(STANCE_ALERT)
else
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
if(istype(the_target, /mob/zshadow))
return FALSE // no
if(isliving(the_target))
var/mob/living/L = the_target
if(L.stat == DEAD)
return FALSE
if(holder.IIsAlly(L))
return FALSE
return TRUE
if(istype(the_target, /obj/mecha))
var/obj/mecha/M = the_target
if(M.occupant)
return can_attack(M.occupant)
if(istype(the_target, /obj/machinery/porta_turret))
var/obj/machinery/porta_turret/P = the_target
if(P.stat & BROKEN)
return FALSE // Already dead.
if(P.faction == holder.faction)
return FALSE // Don't shoot allied turrets.
if(!P.raised && !P.raising)
return FALSE // Turrets won't get hurt if they're still in their cover.
return TRUE
return TRUE
// return FALSE
// Override this for special targeting criteria.
// If it returns true, the mob will always select it as the target.
/datum/ai_holder/proc/found(atom/movable/the_target)
return FALSE
//We can't see the target, go look or attack where they were last seen.
/datum/ai_holder/proc/lose_target()
if(target)
target = null
lose_target_time = world.time
give_up_movement()
//Target is no longer valid (?)
/datum/ai_holder/proc/lost_target()
set_stance(STANCE_IDLE)
lose_target_position()
lose_target()
// Check if target is visible to us.
/datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range)
ai_log("can_see_target() : Entering.", AI_LOG_TRACE)
if(!the_target) // Nothing to target.
ai_log("can_see_target() : There is no target. Exiting.", AI_LOG_WARNING)
return FALSE
if(holder.see_invisible < the_target.invisibility) // Real invis.
ai_log("can_see_target() : Target ([the_target]) was invisible to holder. Exiting.", AI_LOG_TRACE)
return FALSE
if(respect_alpha && the_target.alpha <= alpha_vision_threshold) // Fake invis.
ai_log("can_see_target() : Target ([the_target]) was sufficently transparent to holder and is hidden. Exiting.", AI_LOG_TRACE)
return FALSE
if(get_dist(holder, the_target) > view_range) // Too far away.
ai_log("can_see_target() : Target ([the_target]) was too far from holder. Exiting.", AI_LOG_TRACE)
return FALSE
if(!can_see(holder, the_target, view_range))
ai_log("can_see_target() : Target ([the_target]) failed can_see(). Exiting.", AI_LOG_TRACE)
return FALSE
ai_log("can_see_target() : Target ([the_target]) can be seen. Exiting.", AI_LOG_TRACE)
return TRUE
// Updates the last known position of the target.
/datum/ai_holder/proc/track_target_position()
if(!target)
lose_target_position()
if(last_turf_display && target_last_seen_turf)
target_last_seen_turf.overlays -= last_turf_overlay
target_last_seen_turf = get_turf(target)
if(last_turf_display)
target_last_seen_turf.overlays += last_turf_overlay
// Resets the last known position to null.
/datum/ai_holder/proc/lose_target_position()
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
// 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)
on_attacked(attacker) // So we attack immediately and not threaten.
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)
on_attacked(attacker) // So we attack immediately and not threaten.
return give_target(attacker) // Also handles setting the appropiate stance.
// Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before.
/datum/ai_holder/proc/on_attacked(atom/movable/AM)
last_conflict_time = world.time
if(isliving(AM))
var/mob/living/L = AM
attackers |= L.name
// Causes targeting to prefer targeting the taunter if possible.
// This generally occurs if more than one option is within striking distance, including the taunter.
// Otherwise the default filter will prefer the closest target.
/datum/ai_holder/proc/receive_taunt(atom/movable/taunter, force_target_switch = FALSE)
ai_log("receive_taunt() : Was taunted by [taunter].", AI_LOG_INFO)
preferred_target = taunter
if(force_target_switch)
give_target(taunter)
/datum/ai_holder/proc/lose_taunt()
ai_log("lose_taunt() : Resetting preferred_target.", AI_LOG_INFO)
preferred_target = null
+92
View File
@@ -0,0 +1,92 @@
// 'Interfaces' are procs that the ai_holder datum uses to communicate its will to the mob its attached.
// The reason for using this proc in the middle is to ensure the AI has some form of compatibility with most mob types,
// since some actions work very differently between mob types (e.g. executing an attack as a simple animal compared to a human).
// The AI can just call holder.IAttack(target) and the mob is responsible for determining how to actually attack the target.
/mob/living/proc/IAttack(atom/A)
return FALSE
/mob/living/simple_mob/IAttack(atom/A)
if(!canClick()) // Still on cooldown from a "click".
return FALSE
return attack_target(A) // This will set click cooldown.
/mob/living/proc/IRangedAttack(atom/A)
return FALSE
/mob/living/simple_mob/IRangedAttack(atom/A)
if(!canClick()) // Still on cooldown from a "click".
return FALSE
return shoot_target(A)
// Test if the AI is allowed to attempt a ranged attack.
/mob/living/proc/ICheckRangedAttack(atom/A)
return FALSE
/mob/living/simple_mob/ICheckRangedAttack(atom/A)
if(needs_reload)
if(reload_count >= reload_max)
try_reload()
return FALSE
return projectiletype ? TRUE : FALSE
/mob/living/proc/ISpecialAttack(atom/A)
return FALSE
/mob/living/simple_mob/ISpecialAttack(atom/A)
return special_attack_target(A)
// Is the AI allowed to attempt to do it?
/mob/living/proc/ICheckSpecialAttack(atom/A)
return FALSE
/mob/living/simple_mob/ICheckSpecialAttack(atom/A)
return can_special_attack(A) && should_special_attack(A) // Just because we can doesn't mean we should.
/mob/living/proc/ISay(message)
return say(message)
/mob/living/proc/IIsAlly(mob/living/L)
return src.faction == L.faction
/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_mob/IGetID()
if(myid)
return myid.GetID()
// Respects move cooldowns as if it had a client.
// Also tries to avoid being superdumb with moving into certain tiles (unless that's desired).
/mob/living/proc/IMove(turf/newloc, safety = TRUE)
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))
// 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.
+119
View File
@@ -0,0 +1,119 @@
// 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.
var/list/say_got_target = list() // When a target is first assigned.
var/list/say_threaten = list() // When threatening someone.
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.
// Subtypes.
// 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!")
// Mercs!
/datum/say_list/merc
speak = list("When are we gonna get out of this chicken-shit outfit?",
"Wish I had better equipment...",
"I knew I should have been a line chef...",
"Fuckin' helmet keeps fogging up.",
"Anyone else smell that?")
emote_see = list("sniffs", "coughs", "taps his foot", "looks around", "checks his equipment")
say_understood = list("Understood!", "Affirmative!")
say_cannot = list("Negative!")
say_maybe_target = list("Who's there?")
say_got_target = list("Engaging!")
say_threaten = list("Get out of here!", "Hey! Private Property!")
say_stand_down = list("Good.")
say_escalate = list("Your funeral!", "Bring it!")
/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")
/datum/say_list/hivebot
speak = list(
"Resuming task: Protect area.",
"No threats found.",
"Error: No targets found."
)
emote_hear = list("hums ominously", "whirrs softly", "grinds a gear")
emote_see = list("looks around the area", "turns from side to side")
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.")
/datum/say_list/lizard
emote_hear = list("hisses")
/datum/say_list/crab
emote_hear = list("hisses")
+5 -4
View File
@@ -76,13 +76,14 @@
return
/obj/item/weapon/deadringer/proc/deathprevent()
for(var/mob/living/simple_animal/D in oviewers(7, src))
D.LoseTarget()
for(var/mob/living/simple_mob/D in oviewers(7, src))
if(!D.has_AI())
continue
D.ai_holder.lose_target()
watchowner.emote("deathgasp")
watchowner.alpha = 15
makeacorpse(watchowner)
for(var/mob/living/simple_animal/D in oviewers(7, src))
D.LoseTarget()
return
/obj/item/weapon/deadringer/proc/uncloak()
+1 -1
View File
@@ -40,7 +40,7 @@
H.UpdateDamageIcon()
H.updatehealth()
else if(ismouse(target))
var/mob/living/simple_animal/mouse/M = target
var/mob/living/simple_mob/animal/passive/mouse/M = target
visible_message("<font color='red'><b>SPLAT!</b></font>")
M.splat()
playsound(target.loc, 'sound/effects/snap.ogg', 50, 1)
+2 -4
View File
@@ -15,7 +15,7 @@
var/spore_cooldown = 8 SECONDS
/obj/structure/blob/factory/Destroy()
for(var/mob/living/simple_animal/hostile/blob/spore/spore in spores)
for(var/mob/living/simple_mob/blob/spore/spore in spores)
if(istype(spore) && spore.factory == src)
spore.factory = null
else
@@ -31,7 +31,7 @@
return
flick("blob_factory_glow", src)
spore_delay = world.time + spore_cooldown
var/mob/living/simple_animal/hostile/blob/spore/S = null
var/mob/living/simple_mob/blob/spore/S = null
if(overmind)
S = new overmind.blob_type.spore_type(src.loc, src)
S.faction = "blob"
@@ -39,10 +39,8 @@
S.overmind = overmind
overmind.blob_mobs.Add(S)
if(overmind.blob_type.ranged_spores)
S.ranged = TRUE
S.projectiletype = overmind.blob_type.spore_projectile
S.projectilesound = overmind.blob_type.spore_firesound
S.shoot_range = overmind.blob_type.spore_range
else //Other mobs don't add themselves in New. Ew.
S.nest = src
spores += S
+1 -1
View File
@@ -53,7 +53,7 @@ var/list/overminds = list()
B.update_icon() //reset anything that was ours
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
var/mob/living/simple_mob/blob/spore/BM = BLO
if(BM)
BM.overmind = null
BM.update_icons()
+9 -9
View File
@@ -32,7 +32,7 @@
var/can_build_resources = FALSE // Ditto, for resource blobs.
var/can_build_nodes = TRUE // Ditto, for nodes.
var/spore_type = /mob/living/simple_animal/hostile/blob/spore
var/spore_type = /mob/living/simple_mob/blob/spore
var/ranged_spores = FALSE // For proper spores of the type above.
var/spore_firesound = 'sound/effects/slime_squish.ogg'
var/spore_range = 7 // The range the spore can fire.
@@ -72,7 +72,7 @@
return
// Spore things
/datum/blob_type/proc/on_spore_death(mob/living/simple_animal/hostile/blob/spore/S)
/datum/blob_type/proc/on_spore_death(mob/living/simple_mob/blob/spore/S)
return
@@ -120,7 +120,7 @@
brute_multiplier = 0.25
burn_multiplier = 0.6
ai_aggressiveness = 50 //Really doesn't like you near it.
spore_type = /mob/living/simple_animal/hostile/hivebot/swarm
spore_type = /mob/living/simple_mob/mechanical/hivebot/swarm
/datum/blob_type/fabrication_swarm/on_received_damage(var/obj/structure/blob/B, damage, damage_type, mob/living/attacker)
if(istype(B, /obj/structure/blob/normal))
@@ -227,9 +227,9 @@
burn_multiplier = 3
ai_aggressiveness = 40
can_build_factories = TRUE
spore_type = /mob/living/simple_animal/hostile/blob/spore/infesting
spore_type = /mob/living/simple_mob/blob/spore/infesting
/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_animal/hostile/blob/spore/S)
/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_mob/blob/spore/S)
if(S.is_infesting)
return // Don't make blobs if they were on someone's head.
var/turf/T = get_turf(S)
@@ -258,11 +258,11 @@
brute_multiplier = 1.5
ai_aggressiveness = 30 // The spores do most of the fighting.
can_build_factories = TRUE
spore_type = /mob/living/simple_animal/hostile/blob/spore/weak
spore_type = /mob/living/simple_mob/blob/spore/weak
/datum/blob_type/fulminant_organism/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O)
if(prob(10)) // 10% chance to make a weak spore when expanding.
var/mob/living/simple_animal/hostile/blob/S = new spore_type(T)
var/mob/living/simple_mob/blob/spore/S = new spore_type(T)
if(istype(S))
S.overmind = O
O.blob_mobs.Add(S)
@@ -272,7 +272,7 @@
/datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B)
if(prob(33)) // 33% chance to make a spore when dying.
var/mob/living/simple_animal/hostile/blob/S = new spore_type(get_turf(B))
var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B))
B.visible_message("<span class='danger'>\The [S] floats free from the [name]!</span>")
if(istype(S))
S.overmind = B.overmind
@@ -614,7 +614,7 @@
attack_verb = "crashes against"
can_build_factories = TRUE
can_build_resources = TRUE
spore_type = /mob/living/simple_animal/hostile/blob/spore/weak
spore_type = /mob/living/simple_mob/blob/spore/weak
ranged_spores = TRUE
spore_range = 3
spore_projectile = /obj/item/projectile/energy/blob/splattering
+1 -1
View File
@@ -18,7 +18,7 @@
//OTHER//
/////////
var/datum/preferences/prefs = null
var/move_delay = 1
//var/move_delay = 1
var/moving = null
var/adminobs = null
var/area = null
@@ -370,4 +370,4 @@
/datum/gear/head/circuitry
display_name = "headwear, circuitry (empty)"
path = /obj/item/clothing/head/circuitry
path = /obj/item/clothing/head/circuitry
+5 -5
View File
@@ -37,15 +37,15 @@
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_animal/hostile/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()
spawn(0)
for(var/mob/living/simple_animal/hostile/C in spawned_carp)
if(!C.stat)
var/turf/T = get_turf(C)
for(var/mob/living/simple_mob/SM in spawned_carp)
if(!SM.stat)
var/turf/T = get_turf(SM)
if(istype(T, /turf/space))
if(prob(75))
qdel(C)
qdel(SM)
sleep(1)
+2 -2
View File
@@ -65,11 +65,11 @@
vermin = rand(0,2)
switch(vermin)
if(VERM_MICE)
spawn_types = list(/mob/living/simple_animal/mouse/gray, /mob/living/simple_animal/mouse/brown, /mob/living/simple_animal/mouse/white)
spawn_types = list(/mob/living/simple_mob/animal/passive/mouse/gray, /mob/living/simple_mob/animal/passive/mouse/brown, /mob/living/simple_mob/animal/passive/mouse/white)
max_number = 12
vermstring = "mice"
if(VERM_LIZARDS)
spawn_types = list(/mob/living/simple_animal/lizard)
spawn_types = list(/mob/living/simple_mob/animal/passive/lizard)
max_number = 6
vermstring = "lizards"
if(VERM_SPIDERS)
+3 -5
View File
@@ -16,10 +16,8 @@
else
num = rand(2,6)
for(var/i=0, i<num, i++)
var/mob/living/simple_animal/hostile/malf_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)
if(prob(25))
D.disabled = rand(15, 60)
/datum/event/rogue_drone/announce()
var/msg
@@ -42,12 +40,12 @@
/datum/event/rogue_drone/end()
var/num_recovered = 0
for(var/mob/living/simple_animal/hostile/malf_drone/D in drones_list)
for(var/mob/living/simple_mob/mechanical/combat_drone/D in drones_list)
var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
sparks.set_up(3, 0, D.loc)
sparks.start()
D.z = using_map.admin_levels[1]
D.has_loot = 0
D.loot_list = list()
qdel(D)
num_recovered++
+236 -236
View File
@@ -1,236 +1,236 @@
/obj/machinery/gibber
name = "gibber"
desc = "The name isn't descriptive enough?"
icon = 'icons/obj/kitchen.dmi'
icon_state = "grinder"
density = 1
anchored = 1
req_access = list(access_kitchen,access_morgue)
var/operating = 0 //Is it on?
var/dirty = 0 // Does it need cleaning?
var/mob/living/occupant // Mob who has been put inside
var/gib_time = 40 // Time from starting until meat appears
var/gib_throw_dir = WEST // Direction to spit meat and gibs in.
use_power = 1
idle_power_usage = 2
active_power_usage = 500
//auto-gibs anything that bumps into it
/obj/machinery/gibber/autogibber
var/turf/input_plate
/obj/machinery/gibber/autogibber/New()
..()
spawn(5)
for(var/i in cardinal)
var/obj/machinery/mineral/input/input_obj = locate( /obj/machinery/mineral/input, get_step(src.loc, i) )
if(input_obj)
if(isturf(input_obj.loc))
input_plate = input_obj.loc
gib_throw_dir = i
qdel(input_obj)
break
if(!input_plate)
log_misc("a [src] didn't find an input plate.")
return
/obj/machinery/gibber/autogibber/Bumped(var/atom/A)
if(!input_plate) return
if(ismob(A))
var/mob/M = A
if(M.loc == input_plate
)
M.loc = src
M.gib()
/obj/machinery/gibber/New()
..()
src.overlays += image('icons/obj/kitchen.dmi', "grjam")
/obj/machinery/gibber/update_icon()
overlays.Cut()
if (dirty)
src.overlays += image('icons/obj/kitchen.dmi', "grbloody")
if(stat & (NOPOWER|BROKEN))
return
if (!occupant)
src.overlays += image('icons/obj/kitchen.dmi', "grjam")
else if (operating)
src.overlays += image('icons/obj/kitchen.dmi', "gruse")
else
src.overlays += image('icons/obj/kitchen.dmi', "gridle")
/obj/machinery/gibber/relaymove(mob/user as mob)
src.go_out()
return
/obj/machinery/gibber/attack_hand(mob/user as mob)
if(stat & (NOPOWER|BROKEN))
return
if(operating)
user << "<span class='danger'>The gibber is locked and running, wait for it to finish.</span>"
return
else
src.startgibbing(user)
/obj/machinery/gibber/examine()
..()
usr << "The safety guard is [emagged ? "<span class='danger'>disabled</span>" : "enabled"]."
/obj/machinery/gibber/emag_act(var/remaining_charges, var/mob/user)
emagged = !emagged
user << "<span class='danger'>You [emagged ? "disable" : "enable"] the gibber safety guard.</span>"
return 1
/obj/machinery/gibber/attackby(var/obj/item/W, var/mob/user)
var/obj/item/weapon/grab/G = W
if(default_unfasten_wrench(user, W, 40))
return
if(!istype(G))
return ..()
if(G.state < 2)
user << "<span class='danger'>You need a better grip to do that!</span>"
return
move_into_gibber(user,G.affecting)
// Grab() process should clean up the grab item, no need to del it.
/obj/machinery/gibber/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.restrained())
return
move_into_gibber(user,target)
/obj/machinery/gibber/proc/move_into_gibber(var/mob/user,var/mob/living/victim)
if(src.occupant)
user << "<span class='danger'>The gibber is full, empty it first!</span>"
return
if(operating)
user << "<span class='danger'>The gibber is locked and running, wait for it to finish.</span>"
return
if(!(istype(victim, /mob/living/carbon)) && !(istype(victim, /mob/living/simple_animal)) )
user << "<span class='danger'>This is not suitable for the gibber!</span>"
return
if(istype(victim,/mob/living/carbon/human) && !emagged)
user << "<span class='danger'>The gibber safety guard is engaged!</span>"
return
if(victim.abiotic(1))
user << "<span class='danger'>Subject may not have abiotic items on.</span>"
return
user.visible_message("<span class='danger'>[user] starts to put [victim] into the gibber!</span>")
src.add_fingerprint(user)
if(do_after(user, 30) && victim.Adjacent(src) && user.Adjacent(src) && victim.Adjacent(user) && !occupant)
user.visible_message("<span class='danger'>[user] stuffs [victim] into the gibber!</span>")
if(victim.client)
victim.client.perspective = EYE_PERSPECTIVE
victim.client.eye = src
victim.loc = src
src.occupant = victim
update_icon()
/obj/machinery/gibber/verb/eject()
set category = "Object"
set name = "Empty Gibber"
set src in oview(1)
if (usr.stat != 0)
return
src.go_out()
add_fingerprint(usr)
return
/obj/machinery/gibber/proc/go_out()
if(operating || !src.occupant)
return
for(var/obj/O in src)
O.loc = src.loc
if (src.occupant.client)
src.occupant.client.eye = src.occupant.client.mob
src.occupant.client.perspective = MOB_PERSPECTIVE
src.occupant.loc = src.loc
src.occupant = null
update_icon()
return
/obj/machinery/gibber/proc/startgibbing(mob/user as mob)
if(src.operating)
return
if(!src.occupant)
visible_message("<span class='danger'>You hear a loud metallic grinding sound.</span>")
return
use_power(1000)
visible_message("<span class='danger'>You hear a loud [occupant.isSynthetic() ? "metallic" : "squelchy"] grinding sound.</span>")
src.operating = 1
update_icon()
var/slab_name = occupant.name
var/slab_count = 3
var/slab_type = /obj/item/weapon/reagent_containers/food/snacks/meat
var/slab_nutrition = src.occupant.nutrition / 15
// Some mobs have specific meat item types.
if(istype(src.occupant,/mob/living/simple_animal))
var/mob/living/simple_animal/critter = src.occupant
if(critter.meat_amount)
slab_count = critter.meat_amount
if(critter.meat_type)
slab_type = critter.meat_type
else if(istype(src.occupant,/mob/living/carbon/human))
var/mob/living/carbon/human/H = occupant
slab_name = src.occupant.real_name
slab_type = H.isSynthetic() ? /obj/item/stack/material/steel : H.species.meat_type
// Small mobs don't give as much nutrition.
if(issmall(src.occupant))
slab_nutrition *= 0.5
slab_nutrition /= slab_count
for(var/i=1 to slab_count)
var/obj/item/weapon/reagent_containers/food/snacks/meat/new_meat = new slab_type(src, rand(3,8))
if(istype(new_meat))
new_meat.name = "[slab_name] [new_meat.name]"
new_meat.reagents.add_reagent("nutriment",slab_nutrition)
if(src.occupant.reagents)
src.occupant.reagents.trans_to_obj(new_meat, round(occupant.reagents.total_volume/slab_count,1))
add_attack_logs(user,occupant,"Used [src] to gib")
src.occupant.ghostize()
spawn(gib_time)
src.operating = 0
src.occupant.gib()
qdel(src.occupant)
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1)
operating = 0
for (var/obj/item/thing in contents)
// There's a chance that the gibber will fail to destroy some evidence.
if(istype(thing,/obj/item/organ) && prob(80))
qdel(thing)
continue
thing.forceMove(get_turf(thing)) // Drop it onto the turf for throwing.
thing.throw_at(get_edge_target_turf(src,gib_throw_dir),rand(0,3),emagged ? 100 : 50) // Being pelted with bits of meat and bone would hurt.
update_icon()
/obj/machinery/gibber
name = "gibber"
desc = "The name isn't descriptive enough?"
icon = 'icons/obj/kitchen.dmi'
icon_state = "grinder"
density = 1
anchored = 1
req_access = list(access_kitchen,access_morgue)
var/operating = 0 //Is it on?
var/dirty = 0 // Does it need cleaning?
var/mob/living/occupant // Mob who has been put inside
var/gib_time = 40 // Time from starting until meat appears
var/gib_throw_dir = WEST // Direction to spit meat and gibs in.
use_power = 1
idle_power_usage = 2
active_power_usage = 500
//auto-gibs anything that bumps into it
/obj/machinery/gibber/autogibber
var/turf/input_plate
/obj/machinery/gibber/autogibber/New()
..()
spawn(5)
for(var/i in cardinal)
var/obj/machinery/mineral/input/input_obj = locate( /obj/machinery/mineral/input, get_step(src.loc, i) )
if(input_obj)
if(isturf(input_obj.loc))
input_plate = input_obj.loc
gib_throw_dir = i
qdel(input_obj)
break
if(!input_plate)
log_misc("a [src] didn't find an input plate.")
return
/obj/machinery/gibber/autogibber/Bumped(var/atom/A)
if(!input_plate) return
if(ismob(A))
var/mob/M = A
if(M.loc == input_plate
)
M.loc = src
M.gib()
/obj/machinery/gibber/New()
..()
src.overlays += image('icons/obj/kitchen.dmi', "grjam")
/obj/machinery/gibber/update_icon()
overlays.Cut()
if (dirty)
src.overlays += image('icons/obj/kitchen.dmi', "grbloody")
if(stat & (NOPOWER|BROKEN))
return
if (!occupant)
src.overlays += image('icons/obj/kitchen.dmi', "grjam")
else if (operating)
src.overlays += image('icons/obj/kitchen.dmi', "gruse")
else
src.overlays += image('icons/obj/kitchen.dmi', "gridle")
/obj/machinery/gibber/relaymove(mob/user as mob)
src.go_out()
return
/obj/machinery/gibber/attack_hand(mob/user as mob)
if(stat & (NOPOWER|BROKEN))
return
if(operating)
user << "<span class='danger'>The gibber is locked and running, wait for it to finish.</span>"
return
else
src.startgibbing(user)
/obj/machinery/gibber/examine()
..()
usr << "The safety guard is [emagged ? "<span class='danger'>disabled</span>" : "enabled"]."
/obj/machinery/gibber/emag_act(var/remaining_charges, var/mob/user)
emagged = !emagged
user << "<span class='danger'>You [emagged ? "disable" : "enable"] the gibber safety guard.</span>"
return 1
/obj/machinery/gibber/attackby(var/obj/item/W, var/mob/user)
var/obj/item/weapon/grab/G = W
if(default_unfasten_wrench(user, W, 40))
return
if(!istype(G))
return ..()
if(G.state < 2)
user << "<span class='danger'>You need a better grip to do that!</span>"
return
move_into_gibber(user,G.affecting)
// Grab() process should clean up the grab item, no need to del it.
/obj/machinery/gibber/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.restrained())
return
move_into_gibber(user,target)
/obj/machinery/gibber/proc/move_into_gibber(var/mob/user,var/mob/living/victim)
if(src.occupant)
user << "<span class='danger'>The gibber is full, empty it first!</span>"
return
if(operating)
user << "<span class='danger'>The gibber is locked and running, wait for it to finish.</span>"
return
if(!(istype(victim, /mob/living/carbon)) && !(istype(victim, /mob/living/simple_mob)) )
user << "<span class='danger'>This is not suitable for the gibber!</span>"
return
if(istype(victim,/mob/living/carbon/human) && !emagged)
user << "<span class='danger'>The gibber safety guard is engaged!</span>"
return
if(victim.abiotic(1))
user << "<span class='danger'>Subject may not have abiotic items on.</span>"
return
user.visible_message("<span class='danger'>[user] starts to put [victim] into the gibber!</span>")
src.add_fingerprint(user)
if(do_after(user, 30) && victim.Adjacent(src) && user.Adjacent(src) && victim.Adjacent(user) && !occupant)
user.visible_message("<span class='danger'>[user] stuffs [victim] into the gibber!</span>")
if(victim.client)
victim.client.perspective = EYE_PERSPECTIVE
victim.client.eye = src
victim.loc = src
src.occupant = victim
update_icon()
/obj/machinery/gibber/verb/eject()
set category = "Object"
set name = "Empty Gibber"
set src in oview(1)
if (usr.stat != 0)
return
src.go_out()
add_fingerprint(usr)
return
/obj/machinery/gibber/proc/go_out()
if(operating || !src.occupant)
return
for(var/obj/O in src)
O.loc = src.loc
if (src.occupant.client)
src.occupant.client.eye = src.occupant.client.mob
src.occupant.client.perspective = MOB_PERSPECTIVE
src.occupant.loc = src.loc
src.occupant = null
update_icon()
return
/obj/machinery/gibber/proc/startgibbing(mob/user as mob)
if(src.operating)
return
if(!src.occupant)
visible_message("<span class='danger'>You hear a loud metallic grinding sound.</span>")
return
use_power(1000)
visible_message("<span class='danger'>You hear a loud [occupant.isSynthetic() ? "metallic" : "squelchy"] grinding sound.</span>")
src.operating = 1
update_icon()
var/slab_name = occupant.name
var/slab_count = 3
var/slab_type = /obj/item/weapon/reagent_containers/food/snacks/meat
var/slab_nutrition = src.occupant.nutrition / 15
// Some mobs have specific meat item types.
if(istype(src.occupant,/mob/living/simple_mob))
var/mob/living/simple_mob/critter = src.occupant
if(critter.meat_amount)
slab_count = critter.meat_amount
if(critter.meat_type)
slab_type = critter.meat_type
else if(istype(src.occupant,/mob/living/carbon/human))
var/mob/living/carbon/human/H = occupant
slab_name = src.occupant.real_name
slab_type = H.isSynthetic() ? /obj/item/stack/material/steel : H.species.meat_type
// Small mobs don't give as much nutrition.
if(issmall(src.occupant))
slab_nutrition *= 0.5
slab_nutrition /= slab_count
for(var/i=1 to slab_count)
var/obj/item/weapon/reagent_containers/food/snacks/meat/new_meat = new slab_type(src, rand(3,8))
if(istype(new_meat))
new_meat.name = "[slab_name] [new_meat.name]"
new_meat.reagents.add_reagent("nutriment",slab_nutrition)
if(src.occupant.reagents)
src.occupant.reagents.trans_to_obj(new_meat, round(occupant.reagents.total_volume/slab_count,1))
add_attack_logs(user,occupant,"Used [src] to gib")
src.occupant.ghostize()
spawn(gib_time)
src.operating = 0
src.occupant.gib()
qdel(src.occupant)
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1)
operating = 0
for (var/obj/item/thing in contents)
// There's a chance that the gibber will fail to destroy some evidence.
if(istype(thing,/obj/item/organ) && prob(80))
qdel(thing)
continue
thing.forceMove(get_turf(thing)) // Drop it onto the turf for throwing.
thing.throw_at(get_edge_target_turf(src,gib_throw_dir),rand(0,3),emagged ? 100 : 50) // Being pelted with bits of meat and bone would hurt.
update_icon()
+392 -392
View File
@@ -1,392 +1,392 @@
/obj/machinery/microwave
name = "microwave"
icon = 'icons/obj/kitchen.dmi'
icon_state = "mw"
density = 1
anchored = 1
use_power = 1
idle_power_usage = 5
active_power_usage = 100
flags = OPENCONTAINER | NOREACT
circuit = /obj/item/weapon/circuitboard/microwave
var/operating = 0 // Is it on?
var/dirty = 0 // = {0..100} Does it need cleaning?
var/broken = 0 // ={0,1,2} How broken is it???
var/global/list/datum/recipe/available_recipes // List of the recipes you can use
var/global/list/acceptable_items // List of the items you can put in
var/global/list/acceptable_reagents // List of the reagents you can put in
var/global/max_n_of_items = 0
// see code/modules/food/recipes_microwave.dm for recipes
/*******************
* Initialising
********************/
/obj/machinery/microwave/New()
..()
reagents = new/datum/reagents(100)
reagents.my_atom = src
component_parts = list()
component_parts += new /obj/item/weapon/stock_parts/console_screen(src)
component_parts += new /obj/item/weapon/stock_parts/motor(src)
component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
if (!available_recipes)
available_recipes = new
for (var/type in (typesof(/datum/recipe)-/datum/recipe))
available_recipes+= new type
acceptable_items = new
acceptable_reagents = new
for (var/datum/recipe/recipe in available_recipes)
for (var/item in recipe.items)
acceptable_items |= item
for (var/reagent in recipe.reagents)
acceptable_reagents |= reagent
if (recipe.items)
max_n_of_items = max(max_n_of_items,recipe.items.len)
// This will do until I can think of a fun recipe to use dionaea in -
// will also allow anything using the holder item to be microwaved into
// impure carbon. ~Z
acceptable_items |= /obj/item/weapon/holder
acceptable_items |= /obj/item/weapon/reagent_containers/food/snacks/grown
RefreshParts()
/*******************
* Item Adding
********************/
/obj/machinery/microwave/attackby(var/obj/item/O as obj, var/mob/user as mob)
if(src.broken > 0)
if(src.broken == 2 && O.is_screwdriver()) // If it's broken and they're using a screwdriver
user.visible_message( \
"<span class='notice'>\The [user] starts to fix part of the microwave.</span>", \
"<span class='notice'>You start to fix part of the microwave.</span>" \
)
playsound(src, O.usesound, 50, 1)
if (do_after(user,20 * O.toolspeed))
user.visible_message( \
"<span class='notice'>\The [user] fixes part of the microwave.</span>", \
"<span class='notice'>You have fixed part of the microwave.</span>" \
)
src.broken = 1 // Fix it a bit
else if(src.broken == 1 && O.is_wrench()) // If it's broken and they're doing the wrench
user.visible_message( \
"<span class='notice'>\The [user] starts to fix part of the microwave.</span>", \
"<span class='notice'>You start to fix part of the microwave.</span>" \
)
if (do_after(user,20 * O.toolspeed))
user.visible_message( \
"<span class='notice'>\The [user] fixes the microwave.</span>", \
"<span class='notice'>You have fixed the microwave.</span>" \
)
src.icon_state = "mw"
src.broken = 0 // Fix it!
src.dirty = 0 // just to be sure
src.flags = OPENCONTAINER | NOREACT
else
to_chat(user, "<span class='warning'>It's broken!</span>")
return 1
else if(default_deconstruction_screwdriver(user, O))
return
else if(default_deconstruction_crowbar(user, O))
return
else if(default_unfasten_wrench(user, O, 10))
return
else if(src.dirty==100) // The microwave is all dirty so can't be used!
if(istype(O, /obj/item/weapon/reagent_containers/spray/cleaner) || istype(O, /obj/item/weapon/soap)) // If they're trying to clean it then let them
user.visible_message( \
"<span class='notice'>\The [user] starts to clean the microwave.</span>", \
"<span class='notice'>You start to clean the microwave.</span>" \
)
if (do_after(user,20))
user.visible_message( \
"<span class='notice'>\The [user] has cleaned the microwave.</span>", \
"<span class='notice'>You have cleaned the microwave.</span>" \
)
src.dirty = 0 // It's clean!
src.broken = 0 // just to be sure
src.icon_state = "mw"
src.flags = OPENCONTAINER | NOREACT
else //Otherwise bad luck!!
to_chat(user, "<span class='warning'>It's dirty!</span>")
return 1
else if(is_type_in_list(O,acceptable_items))
if (contents.len>=(max_n_of_items + component_parts.len + 1)) //Adds component_parts to the maximum number of items. The 1 is from the circuit
to_chat(user, "<span class='warning'>This [src] is full of ingredients, you cannot put more.</span>")
return 1
if(istype(O, /obj/item/stack) && O:get_amount() > 1) // This is bad, but I can't think of how to change it
var/obj/item/stack/S = O
new O.type (src)
S.use(1)
user.visible_message( \
"<span class='notice'>\The [user] has added one of [O] to \the [src].</span>", \
"<span class='notice'>You add one of [O] to \the [src].</span>")
return
else
// user.remove_from_mob(O) //This just causes problems so far as I can tell. -Pete
user.drop_item()
O.loc = src
user.visible_message( \
"<span class='notice'>\The [user] has added \the [O] to \the [src].</span>", \
"<span class='notice'>You add \the [O] to \the [src].</span>")
return
else if(istype(O,/obj/item/weapon/reagent_containers/glass) || \
istype(O,/obj/item/weapon/reagent_containers/food/drinks) || \
istype(O,/obj/item/weapon/reagent_containers/food/condiment) \
)
if (!O.reagents)
return 1
for (var/datum/reagent/R in O.reagents.reagent_list)
if (!(R.id in acceptable_reagents))
to_chat(user, "<span class='warning'>Your [O] contains components unsuitable for cookery.</span>")
return 1
return
else if(istype(O,/obj/item/weapon/grab))
var/obj/item/weapon/grab/G = O
to_chat(user, "<span class='warning'>This is ridiculous. You can not fit \the [G.affecting] in this [src].</span>")
return 1
else
to_chat(user, "<span class='warning'>You have no idea what you can cook with this [O].</span>")
..()
src.updateUsrDialog()
/obj/machinery/microwave/attack_ai(mob/user as mob)
if(istype(user, /mob/living/silicon/robot) && Adjacent(user))
attack_hand(user)
/obj/machinery/microwave/attack_hand(mob/user as mob)
user.set_machine(src)
interact(user)
/*******************
* Microwave Menu
********************/
/obj/machinery/microwave/interact(mob/user as mob) // The microwave Menu
var/dat = ""
if(src.broken > 0)
dat = {"<TT>Bzzzzttttt</TT>"}
else if(src.operating)
dat = {"<TT>Microwaving in progress!<BR>Please wait...!</TT>"}
else if(src.dirty==100)
dat = {"<TT>This microwave is dirty!<BR>Please clean it before use!</TT>"}
else
var/list/items_counts = new
var/list/items_measures = new
var/list/items_measures_p = new
for (var/obj/O in ((contents - component_parts) - circuit))
var/display_name = O.name
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/egg))
items_measures[display_name] = "egg"
items_measures_p[display_name] = "eggs"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/tofu))
items_measures[display_name] = "tofu chunk"
items_measures_p[display_name] = "tofu chunks"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/meat)) //any meat
items_measures[display_name] = "slab of meat"
items_measures_p[display_name] = "slabs of meat"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/donkpocket))
display_name = "Turnovers"
items_measures[display_name] = "turnover"
items_measures_p[display_name] = "turnovers"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/carpmeat))
items_measures[display_name] = "fillet of meat"
items_measures_p[display_name] = "fillets of meat"
items_counts[display_name]++
for (var/O in items_counts)
var/N = items_counts[O]
if (!(O in items_measures))
dat += {"<B>[capitalize(O)]:</B> [N] [lowertext(O)]\s<BR>"}
else
if (N==1)
dat += {"<B>[capitalize(O)]:</B> [N] [items_measures[O]]<BR>"}
else
dat += {"<B>[capitalize(O)]:</B> [N] [items_measures_p[O]]<BR>"}
for (var/datum/reagent/R in reagents.reagent_list)
var/display_name = R.name
if (R.id == "capsaicin")
display_name = "Hotsauce"
if (R.id == "frostoil")
display_name = "Coldsauce"
dat += {"<B>[display_name]:</B> [R.volume] unit\s<BR>"}
if (items_counts.len==0 && reagents.reagent_list.len==0)
dat = {"<B>The microwave is empty</B><BR>"}
else
dat = {"<b>Ingredients:</b><br>[dat]"}
dat += {"<HR><BR>\
<A href='?src=\ref[src];action=cook'>Turn on!<BR>\
<A href='?src=\ref[src];action=dispose'>Eject ingredients!<BR>\
"}
to_chat(user, browse("<HEAD><TITLE>Microwave Controls</TITLE></HEAD><TT>[dat]</TT>", "window=microwave"))
onclose(user, "microwave")
return
/***********************************
* Microwave Menu Handling/Cooking
************************************/
/obj/machinery/microwave/proc/cook()
if(stat & (NOPOWER|BROKEN))
return
start()
if (reagents.total_volume==0 && !(locate(/obj) in ((contents - component_parts) - circuit))) //dry run
if (!wzhzhzh(10))
abort()
return
stop()
return
var/datum/recipe/recipe = select_recipe(available_recipes,src)
var/obj/cooked
if (!recipe)
dirty += 1
if (prob(max(10,dirty*5)))
if (!wzhzhzh(4))
abort()
return
muck_start()
wzhzhzh(4)
muck_finish()
cooked = fail()
cooked.loc = src.loc
return
else if (has_extra_item())
if (!wzhzhzh(4))
abort()
return
broke()
cooked = fail()
cooked.loc = src.loc
return
else
if (!wzhzhzh(10))
abort()
return
stop()
cooked = fail()
cooked.loc = src.loc
return
else
var/halftime = round(recipe.time/10/2)
if (!wzhzhzh(halftime))
abort()
return
if (!wzhzhzh(halftime))
abort()
cooked = fail()
cooked.loc = src.loc
return
cooked = recipe.make_food(src)
stop()
if(cooked)
cooked.loc = src.loc
return
/obj/machinery/microwave/proc/wzhzhzh(var/seconds as num) // Whoever named this proc is fucking literally Satan. ~ Z
for (var/i=1 to seconds)
if (stat & (NOPOWER|BROKEN))
return 0
use_power(500)
sleep(10)
return 1
/obj/machinery/microwave/proc/has_extra_item()
for (var/obj/O in ((contents - component_parts) - circuit))
if ( \
!istype(O,/obj/item/weapon/reagent_containers/food) && \
!istype(O, /obj/item/weapon/grown) \
)
return 1
return 0
/obj/machinery/microwave/proc/start()
src.visible_message("<span class='notice'>The microwave turns on.</span>", "<span class='notice'>You hear a microwave.</span>")
src.operating = 1
src.icon_state = "mw1"
src.updateUsrDialog()
/obj/machinery/microwave/proc/abort()
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
/obj/machinery/microwave/proc/stop()
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
/obj/machinery/microwave/proc/dispose()
for (var/obj/O in ((contents-component_parts)-circuit))
O.loc = src.loc
if (src.reagents.total_volume)
src.dirty++
src.reagents.clear_reagents()
usr << "<span class='notice'>You dispose of the microwave contents.</span>"
src.updateUsrDialog()
/obj/machinery/microwave/proc/muck_start()
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) // Play a splat sound
src.icon_state = "mwbloody1" // Make it look dirty!!
/obj/machinery/microwave/proc/muck_finish()
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
src.visible_message("<span class='warning'>The microwave gets covered in muck!</span>")
src.dirty = 100 // Make it dirty so it can't be used util cleaned
src.flags = null //So you can't add condiments
src.icon_state = "mwbloody" // Make it look dirty too
src.operating = 0 // Turn it off again aferwards
src.updateUsrDialog()
/obj/machinery/microwave/proc/broke()
var/datum/effect/effect/system/spark_spread/s = new
s.set_up(2, 1, src)
s.start()
src.icon_state = "mwb" // Make it look all busted up and shit
src.visible_message("<span class='warning'>The microwave breaks!</span>") //Let them know they're stupid
src.broken = 2 // Make it broken so it can't be used util fixed
src.flags = null //So you can't add condiments
src.operating = 0 // Turn it off again aferwards
src.updateUsrDialog()
/obj/machinery/microwave/proc/fail()
var/obj/item/weapon/reagent_containers/food/snacks/badrecipe/ffuu = new(src)
var/amount = 0
for (var/obj/O in (((contents - ffuu) - component_parts) - circuit))
amount++
if (O.reagents)
var/id = O.reagents.get_master_reagent_id()
if (id)
amount+=O.reagents.get_reagent_amount(id)
qdel(O)
src.reagents.clear_reagents()
ffuu.reagents.add_reagent("carbon", amount)
ffuu.reagents.add_reagent("toxin", amount/10)
return ffuu
/obj/machinery/microwave/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
if(src.operating)
src.updateUsrDialog()
return
switch(href_list["action"])
if ("cook")
cook()
if ("dispose")
dispose()
return
/obj/machinery/microwave
name = "microwave"
icon = 'icons/obj/kitchen.dmi'
icon_state = "mw"
density = 1
anchored = 1
use_power = 1
idle_power_usage = 5
active_power_usage = 100
flags = OPENCONTAINER | NOREACT
circuit = /obj/item/weapon/circuitboard/microwave
var/operating = 0 // Is it on?
var/dirty = 0 // = {0..100} Does it need cleaning?
var/broken = 0 // ={0,1,2} How broken is it???
var/global/list/datum/recipe/available_recipes // List of the recipes you can use
var/global/list/acceptable_items // List of the items you can put in
var/global/list/acceptable_reagents // List of the reagents you can put in
var/global/max_n_of_items = 0
// see code/modules/food/recipes_microwave.dm for recipes
/*******************
* Initialising
********************/
/obj/machinery/microwave/New()
..()
reagents = new/datum/reagents(100)
reagents.my_atom = src
component_parts = list()
component_parts += new /obj/item/weapon/stock_parts/console_screen(src)
component_parts += new /obj/item/weapon/stock_parts/motor(src)
component_parts += new /obj/item/weapon/stock_parts/capacitor(src)
if (!available_recipes)
available_recipes = new
for (var/type in (typesof(/datum/recipe)-/datum/recipe))
available_recipes+= new type
acceptable_items = new
acceptable_reagents = new
for (var/datum/recipe/recipe in available_recipes)
for (var/item in recipe.items)
acceptable_items |= item
for (var/reagent in recipe.reagents)
acceptable_reagents |= reagent
if (recipe.items)
max_n_of_items = max(max_n_of_items,recipe.items.len)
// This will do until I can think of a fun recipe to use dionaea in -
// will also allow anything using the holder item to be microwaved into
// impure carbon. ~Z
acceptable_items |= /obj/item/weapon/holder
acceptable_items |= /obj/item/weapon/reagent_containers/food/snacks/grown
RefreshParts()
/*******************
* Item Adding
********************/
/obj/machinery/microwave/attackby(var/obj/item/O as obj, var/mob/user as mob)
if(src.broken > 0)
if(src.broken == 2 && O.is_screwdriver()) // If it's broken and they're using a screwdriver
user.visible_message( \
"<span class='notice'>\The [user] starts to fix part of the microwave.</span>", \
"<span class='notice'>You start to fix part of the microwave.</span>" \
)
playsound(src, O.usesound, 50, 1)
if (do_after(user,20 * O.toolspeed))
user.visible_message( \
"<span class='notice'>\The [user] fixes part of the microwave.</span>", \
"<span class='notice'>You have fixed part of the microwave.</span>" \
)
src.broken = 1 // Fix it a bit
else if(src.broken == 1 && O.is_wrench()) // If it's broken and they're doing the wrench
user.visible_message( \
"<span class='notice'>\The [user] starts to fix part of the microwave.</span>", \
"<span class='notice'>You start to fix part of the microwave.</span>" \
)
if (do_after(user,20 * O.toolspeed))
user.visible_message( \
"<span class='notice'>\The [user] fixes the microwave.</span>", \
"<span class='notice'>You have fixed the microwave.</span>" \
)
src.icon_state = "mw"
src.broken = 0 // Fix it!
src.dirty = 0 // just to be sure
src.flags = OPENCONTAINER | NOREACT
else
to_chat(user, "<span class='warning'>It's broken!</span>")
return 1
else if(default_deconstruction_screwdriver(user, O))
return
else if(default_deconstruction_crowbar(user, O))
return
else if(default_unfasten_wrench(user, O, 10))
return
else if(src.dirty==100) // The microwave is all dirty so can't be used!
if(istype(O, /obj/item/weapon/reagent_containers/spray/cleaner) || istype(O, /obj/item/weapon/soap)) // If they're trying to clean it then let them
user.visible_message( \
"<span class='notice'>\The [user] starts to clean the microwave.</span>", \
"<span class='notice'>You start to clean the microwave.</span>" \
)
if (do_after(user,20))
user.visible_message( \
"<span class='notice'>\The [user] has cleaned the microwave.</span>", \
"<span class='notice'>You have cleaned the microwave.</span>" \
)
src.dirty = 0 // It's clean!
src.broken = 0 // just to be sure
src.icon_state = "mw"
src.flags = OPENCONTAINER | NOREACT
else //Otherwise bad luck!!
to_chat(user, "<span class='warning'>It's dirty!</span>")
return 1
else if(is_type_in_list(O,acceptable_items))
if (contents.len>=(max_n_of_items + component_parts.len + 1)) //Adds component_parts to the maximum number of items. The 1 is from the circuit
to_chat(user, "<span class='warning'>This [src] is full of ingredients, you cannot put more.</span>")
return 1
if(istype(O, /obj/item/stack) && O:get_amount() > 1) // This is bad, but I can't think of how to change it
var/obj/item/stack/S = O
new O.type (src)
S.use(1)
user.visible_message( \
"<span class='notice'>\The [user] has added one of [O] to \the [src].</span>", \
"<span class='notice'>You add one of [O] to \the [src].</span>")
return
else
// user.remove_from_mob(O) //This just causes problems so far as I can tell. -Pete
user.drop_item()
O.loc = src
user.visible_message( \
"<span class='notice'>\The [user] has added \the [O] to \the [src].</span>", \
"<span class='notice'>You add \the [O] to \the [src].</span>")
return
else if(istype(O,/obj/item/weapon/reagent_containers/glass) || \
istype(O,/obj/item/weapon/reagent_containers/food/drinks) || \
istype(O,/obj/item/weapon/reagent_containers/food/condiment) \
)
if (!O.reagents)
return 1
for (var/datum/reagent/R in O.reagents.reagent_list)
if (!(R.id in acceptable_reagents))
to_chat(user, "<span class='warning'>Your [O] contains components unsuitable for cookery.</span>")
return 1
return
else if(istype(O,/obj/item/weapon/grab))
var/obj/item/weapon/grab/G = O
to_chat(user, "<span class='warning'>This is ridiculous. You can not fit \the [G.affecting] in this [src].</span>")
return 1
else
to_chat(user, "<span class='warning'>You have no idea what you can cook with this [O].</span>")
..()
src.updateUsrDialog()
/obj/machinery/microwave/attack_ai(mob/user as mob)
if(istype(user, /mob/living/silicon/robot) && Adjacent(user))
attack_hand(user)
/obj/machinery/microwave/attack_hand(mob/user as mob)
user.set_machine(src)
interact(user)
/*******************
* Microwave Menu
********************/
/obj/machinery/microwave/interact(mob/user as mob) // The microwave Menu
var/dat = ""
if(src.broken > 0)
dat = {"<TT>Bzzzzttttt</TT>"}
else if(src.operating)
dat = {"<TT>Microwaving in progress!<BR>Please wait...!</TT>"}
else if(src.dirty==100)
dat = {"<TT>This microwave is dirty!<BR>Please clean it before use!</TT>"}
else
var/list/items_counts = new
var/list/items_measures = new
var/list/items_measures_p = new
for (var/obj/O in ((contents - component_parts) - circuit))
var/display_name = O.name
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/egg))
items_measures[display_name] = "egg"
items_measures_p[display_name] = "eggs"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/tofu))
items_measures[display_name] = "tofu chunk"
items_measures_p[display_name] = "tofu chunks"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/meat)) //any meat
items_measures[display_name] = "slab of meat"
items_measures_p[display_name] = "slabs of meat"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/donkpocket))
display_name = "Turnovers"
items_measures[display_name] = "turnover"
items_measures_p[display_name] = "turnovers"
if (istype(O,/obj/item/weapon/reagent_containers/food/snacks/carpmeat))
items_measures[display_name] = "fillet of meat"
items_measures_p[display_name] = "fillets of meat"
items_counts[display_name]++
for (var/O in items_counts)
var/N = items_counts[O]
if (!(O in items_measures))
dat += {"<B>[capitalize(O)]:</B> [N] [lowertext(O)]\s<BR>"}
else
if (N==1)
dat += {"<B>[capitalize(O)]:</B> [N] [items_measures[O]]<BR>"}
else
dat += {"<B>[capitalize(O)]:</B> [N] [items_measures_p[O]]<BR>"}
for (var/datum/reagent/R in reagents.reagent_list)
var/display_name = R.name
if (R.id == "capsaicin")
display_name = "Hotsauce"
if (R.id == "frostoil")
display_name = "Coldsauce"
dat += {"<B>[display_name]:</B> [R.volume] unit\s<BR>"}
if (items_counts.len==0 && reagents.reagent_list.len==0)
dat = {"<B>The microwave is empty</B><BR>"}
else
dat = {"<b>Ingredients:</b><br>[dat]"}
dat += {"<HR><BR>\
<A href='?src=\ref[src];action=cook'>Turn on!<BR>\
<A href='?src=\ref[src];action=dispose'>Eject ingredients!<BR>\
"}
to_chat(user, browse("<HEAD><TITLE>Microwave Controls</TITLE></HEAD><TT>[dat]</TT>", "window=microwave"))
onclose(user, "microwave")
return
/***********************************
* Microwave Menu Handling/Cooking
************************************/
/obj/machinery/microwave/proc/cook()
if(stat & (NOPOWER|BROKEN))
return
start()
if (reagents.total_volume==0 && !(locate(/obj) in ((contents - component_parts) - circuit))) //dry run
if (!wzhzhzh(10))
abort()
return
stop()
return
var/datum/recipe/recipe = select_recipe(available_recipes,src)
var/obj/cooked
if (!recipe)
dirty += 1
if (prob(max(10,dirty*5)))
if (!wzhzhzh(4))
abort()
return
muck_start()
wzhzhzh(4)
muck_finish()
cooked = fail()
cooked.loc = src.loc
return
else if (has_extra_item())
if (!wzhzhzh(4))
abort()
return
broke()
cooked = fail()
cooked.loc = src.loc
return
else
if (!wzhzhzh(10))
abort()
return
stop()
cooked = fail()
cooked.loc = src.loc
return
else
var/halftime = round(recipe.time/10/2)
if (!wzhzhzh(halftime))
abort()
return
if (!wzhzhzh(halftime))
abort()
cooked = fail()
cooked.loc = src.loc
return
cooked = recipe.make_food(src)
stop()
if(cooked)
cooked.loc = src.loc
return
/obj/machinery/microwave/proc/wzhzhzh(var/seconds as num) // Whoever named this proc is fucking literally Satan. ~ Z
for (var/i=1 to seconds)
if (stat & (NOPOWER|BROKEN))
return 0
use_power(500)
sleep(10)
return 1
/obj/machinery/microwave/proc/has_extra_item()
for (var/obj/O in ((contents - component_parts) - circuit))
if ( \
!istype(O,/obj/item/weapon/reagent_containers/food) && \
!istype(O, /obj/item/weapon/grown) \
)
return 1
return 0
/obj/machinery/microwave/proc/start()
src.visible_message("<span class='notice'>The microwave turns on.</span>", "<span class='notice'>You hear a microwave.</span>")
src.operating = 1
src.icon_state = "mw1"
src.updateUsrDialog()
/obj/machinery/microwave/proc/abort()
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
/obj/machinery/microwave/proc/stop()
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
src.operating = 0 // Turn it off again aferwards
src.icon_state = "mw"
src.updateUsrDialog()
/obj/machinery/microwave/proc/dispose()
for (var/obj/O in ((contents-component_parts)-circuit))
O.loc = src.loc
if (src.reagents.total_volume)
src.dirty++
src.reagents.clear_reagents()
usr << "<span class='notice'>You dispose of the microwave contents.</span>"
src.updateUsrDialog()
/obj/machinery/microwave/proc/muck_start()
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) // Play a splat sound
src.icon_state = "mwbloody1" // Make it look dirty!!
/obj/machinery/microwave/proc/muck_finish()
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
src.visible_message("<span class='warning'>The microwave gets covered in muck!</span>")
src.dirty = 100 // Make it dirty so it can't be used util cleaned
src.flags = null //So you can't add condiments
src.icon_state = "mwbloody" // Make it look dirty too
src.operating = 0 // Turn it off again aferwards
src.updateUsrDialog()
/obj/machinery/microwave/proc/broke()
var/datum/effect/effect/system/spark_spread/s = new
s.set_up(2, 1, src)
s.start()
src.icon_state = "mwb" // Make it look all busted up and shit
src.visible_message("<span class='warning'>The microwave breaks!</span>") //Let them know they're stupid
src.broken = 2 // Make it broken so it can't be used util fixed
src.flags = null //So you can't add condiments
src.operating = 0 // Turn it off again aferwards
src.updateUsrDialog()
/obj/machinery/microwave/proc/fail()
var/obj/item/weapon/reagent_containers/food/snacks/badrecipe/ffuu = new(src)
var/amount = 0
for (var/obj/O in (((contents - ffuu) - component_parts) - circuit))
amount++
if (O.reagents)
var/id = O.reagents.get_master_reagent_id()
if (id)
amount+=O.reagents.get_reagent_amount(id)
qdel(O)
src.reagents.clear_reagents()
ffuu.reagents.add_reagent("carbon", amount)
ffuu.reagents.add_reagent("toxin", amount/10)
return ffuu
/obj/machinery/microwave/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
if(src.operating)
src.updateUsrDialog()
return
switch(href_list["action"])
if ("cook")
cook()
if ("dispose")
dispose()
return
+4
View File
@@ -10,7 +10,11 @@
idle_power_usage = 5
active_power_usage = 100
flags = NOREACT
<<<<<<< HEAD
var/max_n_of_items = 999 // Sorry but the BYOND infinite loop detector doesn't look things over 1000. //VOREStation Edit - Non-global
=======
var/global/max_n_of_items = 999 // Sorry but the BYOND infinite loop detector doesn't look things over 1000.
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
var/icon_on = "smartfridge"
var/icon_off = "smartfridge-off"
var/icon_panel = "smartfridge-panel"
@@ -52,12 +52,12 @@
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_animal/hostile/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.")
/datum/gm_action/carp_migration/end()
for(var/mob/living/simple_animal/hostile/carp/C in spawned_carp)
for(var/mob/living/simple_mob/animal/space/carp/C in spawned_carp)
if(!C.stat)
var/turf/T = get_turf(C)
if(istype(T, /turf/space))
@@ -47,7 +47,5 @@
spawning_turf = space
break
if(spawning_turf)
var/mob/living/simple_animal/hostile/carp/C = new(spawning_turf)
C.target_mob = victim
C.stance = STANCE_ATTACK
new /mob/living/simple_mob/animal/space/carp(spawning_turf)
number_of_carp--
+2 -2
View File
@@ -238,7 +238,7 @@
/obj/item/weapon/deck/MouseDrop(mob/user as mob) // Code from Paper bin, so you can still pick up the deck
if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
if(!istype(usr, /mob/living/simple_animal))
if(!istype(usr, /mob/living/simple_mob))
if( !usr.get_active_hand() ) //if active hand is empty
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
@@ -256,7 +256,7 @@
/obj/item/weapon/deck/verb_pickup(mob/user as mob) // Snowflaked so pick up verb work as intended
if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr))))))
if(!istype(usr, /mob/living/simple_animal))
if(!istype(usr, /mob/living/simple_mob))
if( !usr.get_active_hand() ) //if active hand is empty
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
+5 -5
View File
@@ -177,7 +177,7 @@
for(var/obj/item/weapon/holo/esword/H in linkedholodeck)
H.damtype = initial(H.damtype)
for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs)
for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs)
C.set_safety(!safety_disabled)
if (last_to_emag)
C.friends = list(last_to_emag)
@@ -210,7 +210,7 @@
derez(item, 0)
if (!safety_disabled)
for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs)
for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs)
if (get_area(C.loc) != linkedholodeck)
holographic_mobs -= C
C.derez()
@@ -306,7 +306,7 @@
for(var/item in holographic_objs)
derez(item)
for(var/mob/living/simple_animal/hostile/carp/holodeck/C in holographic_mobs)
for(var/mob/living/simple_mob/animal/space/carp/holodeck/C in holographic_mobs)
holographic_mobs -= C
C.derez()
@@ -340,11 +340,11 @@
T.temperature = 5000
T.hotspot_expose(50000,50000,1)
if(L.name=="Holocarp Spawn")
holographic_mobs += new /mob/living/simple_animal/hostile/carp/holodeck(L.loc)
holographic_mobs += new /mob/living/simple_mob/animal/space/carp/holodeck(L.loc)
if(L.name=="Holocarp Spawn Random")
if (prob(4)) //With 4 spawn points, carp should only appear 15% of the time.
holographic_mobs += new /mob/living/simple_animal/hostile/carp/holodeck(L.loc)
holographic_mobs += new /mob/living/simple_mob/animal/space/carp/holodeck(L.loc)
update_projections()
+6 -10
View File
@@ -419,7 +419,7 @@
//Holocarp
/mob/living/simple_animal/hostile/carp/holodeck
/mob/living/simple_mob/animal/space/carp/holodeck
icon = 'icons/mob/AI.dmi'
icon_state = "holo4"
icon_living = "holo4"
@@ -429,31 +429,27 @@
meat_amount = 0
meat_type = null
/mob/living/simple_animal/hostile/carp/holodeck/New()
/mob/living/simple_mob/animal/space/carp/holodeck/New()
..()
set_light(2) //hologram lighting
/mob/living/simple_animal/hostile/carp/holodeck/proc/set_safety(var/safe)
/mob/living/simple_mob/animal/space/carp/holodeck/proc/set_safety(var/safe)
if (safe)
faction = "neutral"
melee_damage_lower = 0
melee_damage_upper = 0
environment_smash = 0
destroy_surroundings = 0
else
faction = "carp"
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
environment_smash = initial(environment_smash)
destroy_surroundings = initial(destroy_surroundings)
/mob/living/simple_animal/hostile/carp/holodeck/gib()
/mob/living/simple_mob/animal/space/carp/holodeck/gib()
derez() //holograms can't gib
/mob/living/simple_animal/hostile/carp/holodeck/death()
/mob/living/simple_mob/animal/space/carp/holodeck/death()
..()
derez()
/mob/living/simple_animal/hostile/carp/holodeck/proc/derez()
/mob/living/simple_mob/animal/space/carp/holodeck/proc/derez()
visible_message("<span class='notice'>\The [src] fades away!</span>")
qdel(src)
+2 -2
View File
@@ -104,10 +104,10 @@
return
if(!istype(target))
if(istype(target, /mob/living/simple_animal/mouse))
if(istype(target, /mob/living/simple_mob/animal/passive/mouse))
new /obj/effect/decal/remains/mouse(get_turf(target))
qdel(target)
else if(istype(target, /mob/living/simple_animal/lizard))
else if(istype(target, /mob/living/simple_mob/animal/passive/lizard))
new /obj/effect/decal/remains/lizard(get_turf(target))
qdel(target)
return
+1 -1
View File
@@ -184,7 +184,7 @@
display_name = "killer tomato plant"
mutants = null
can_self_harvest = 1
has_mob_product = /mob/living/simple_animal/hostile/tomato
has_mob_product = /mob/living/simple_mob/tomato
/datum/seed/tomato/killer/New()
..()
@@ -1,84 +1,84 @@
/obj/item/device/assembly/electronic_assembly
name = "electronic device"
desc = "It's a case for building electronics with. It can be attached to other small devices."
icon_state = "setup_device"
var/opened = 0
var/obj/item/device/electronic_assembly/device/EA
/obj/item/device/assembly/electronic_assembly/New()
EA = new(src)
EA.holder = src
..()
/obj/item/device/assembly/electronic_assembly/attackby(obj/item/I as obj, mob/user as mob)
if (I.is_crowbar())
toggle_open(user)
else if (opened)
EA.attackby(I, user)
else
..()
/obj/item/device/assembly/electronic_assembly/proc/toggle_open(mob/user)
playsound(get_turf(src), 'sound/items/Crowbar.ogg', 50, 1)
opened = !opened
EA.opened = opened
to_chat(user, "<span class='notice'>You [opened ? "opened" : "closed"] \the [src].</span>")
secured = 1
update_icon()
/obj/item/device/assembly/electronic_assembly/update_icon()
if(EA)
icon_state = initial(icon_state)
else
icon_state = initial(icon_state)+"0"
if(opened)
icon_state = icon_state + "-open"
/obj/item/device/assembly/electronic_assembly/attack_self(mob/user as mob)
if(EA)
EA.attack_self(user)
/obj/item/device/assembly/electronic_assembly/pulsed(var/radio = 0) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
if(EA)
for(var/obj/item/integrated_circuit/built_in/device_input/I in EA.contents)
I.do_work()
return
/obj/item/device/assembly/electronic_assembly/examine(mob/user)
.=..(user, 1)
if(EA)
for(var/obj/item/integrated_circuit/IC in EA.contents)
IC.external_examine(user)
/obj/item/device/assembly/electronic_assembly/verb/toggle()
set src in usr
set category = "Object"
set name = "Open/Close Device Assembly"
set desc = "Open or close device assembly!"
toggle_open(usr)
/obj/item/device/electronic_assembly/device
name = "electronic device"
icon_state = "setup_device"
desc = "It's a tiny electronic device with specific use for attaching to other devices."
var/obj/item/device/assembly/electronic_assembly/holder
w_class = ITEMSIZE_TINY
max_components = IC_COMPONENTS_BASE * 3/4
max_complexity = IC_COMPLEXITY_BASE * 3/4
/obj/item/device/electronic_assembly/device/New()
..()
var/obj/item/integrated_circuit/built_in/device_input/input = new(src)
var/obj/item/integrated_circuit/built_in/device_output/output = new(src)
input.assembly = src
output.assembly = src
/obj/item/device/electronic_assembly/device/check_interactivity(mob/user)
if(!CanInteract(user, state = deep_inventory_state))
return 0
return 1
/obj/item/device/assembly/electronic_assembly
name = "electronic device"
desc = "It's a case for building electronics with. It can be attached to other small devices."
icon_state = "setup_device"
var/opened = 0
var/obj/item/device/electronic_assembly/device/EA
/obj/item/device/assembly/electronic_assembly/New()
EA = new(src)
EA.holder = src
..()
/obj/item/device/assembly/electronic_assembly/attackby(obj/item/I as obj, mob/user as mob)
if (I.is_crowbar())
toggle_open(user)
else if (opened)
EA.attackby(I, user)
else
..()
/obj/item/device/assembly/electronic_assembly/proc/toggle_open(mob/user)
playsound(get_turf(src), 'sound/items/Crowbar.ogg', 50, 1)
opened = !opened
EA.opened = opened
to_chat(user, "<span class='notice'>You [opened ? "opened" : "closed"] \the [src].</span>")
secured = 1
update_icon()
/obj/item/device/assembly/electronic_assembly/update_icon()
if(EA)
icon_state = initial(icon_state)
else
icon_state = initial(icon_state)+"0"
if(opened)
icon_state = icon_state + "-open"
/obj/item/device/assembly/electronic_assembly/attack_self(mob/user as mob)
if(EA)
EA.attack_self(user)
/obj/item/device/assembly/electronic_assembly/pulsed(var/radio = 0) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
if(EA)
for(var/obj/item/integrated_circuit/built_in/device_input/I in EA.contents)
I.do_work()
return
/obj/item/device/assembly/electronic_assembly/examine(mob/user)
.=..(user, 1)
if(EA)
for(var/obj/item/integrated_circuit/IC in EA.contents)
IC.external_examine(user)
/obj/item/device/assembly/electronic_assembly/verb/toggle()
set src in usr
set category = "Object"
set name = "Open/Close Device Assembly"
set desc = "Open or close device assembly!"
toggle_open(usr)
/obj/item/device/electronic_assembly/device
name = "electronic device"
icon_state = "setup_device"
desc = "It's a tiny electronic device with specific use for attaching to other devices."
var/obj/item/device/assembly/electronic_assembly/holder
w_class = ITEMSIZE_TINY
max_components = IC_COMPONENTS_BASE * 3/4
max_complexity = IC_COMPLEXITY_BASE * 3/4
/obj/item/device/electronic_assembly/device/New()
..()
var/obj/item/integrated_circuit/built_in/device_input/input = new(src)
var/obj/item/integrated_circuit/built_in/device_output/output = new(src)
input.assembly = src
output.assembly = src
/obj/item/device/electronic_assembly/device/check_interactivity(mob/user)
if(!CanInteract(user, state = deep_inventory_state))
return 0
return 1
+18
View File
@@ -0,0 +1,18 @@
/*
'Aura' modifiers are semi-permanent, in that they do not have a set duration, but will expire if out of range of the 'source' of the aura.
Note: The source is defined as an argument in New(), and if not specified, it is assumed the holder is the source,
making it not expire ever, which is likely not what you want.
*/
/datum/modifier/aura
var/aura_max_distance = 5 // If more than this many tiles away from the source, the modifier expires next tick.
/datum/modifier/aura/check_if_valid()
if(!origin)
expire()
var/atom/A = origin.resolve()
if(istype(A)) // Make sure we're not null.
if(get_dist(holder, A) > aura_max_distance)
expire()
else
expire() // Source got deleted or something.
+7
View File
@@ -149,6 +149,13 @@
/mob/living/proc/remove_specific_modifier(var/datum/modifier/M, var/silent = FALSE)
M.expire(silent)
// Removes one modifier of a type
/mob/living/proc/remove_a_modifier_of_type(var/modifier_type, var/silent = FALSE)
for(var/datum/modifier/M in modifiers)
if(ispath(M.type, modifier_type))
M.expire(silent)
break
// Removes all modifiers of a type
/mob/living/proc/remove_modifiers_of_type(var/modifier_type, var/silent = FALSE)
for(var/datum/modifier/M in modifiers)
+68 -1
View File
@@ -29,7 +29,7 @@ Berserk is a somewhat rare modifier to obtain freely (and for good reason), howe
- Red Slimes will berserk if they go rabid.
- Red slime core reactions will berserk slimes that can see the user in addition to making them go rabid.
- Red slime core reactions will berserk prometheans that can see the user.
- Bears will berserk when losing a fight.
- Saviks will berserk when losing a fight.
- Changelings can evolve a 2 point ability to use a changeling-specific variant of Berserk, that replaces the text with a 'we' variant.
Recursive Enhancement allows the changeling to instead used an improved variant that features less exhaustion time and less nutrition drain.
- Xenoarch artifacts may have forced berserking as one of their effects. This is especially fun if an artifact that makes hostile mobs is nearby.
@@ -180,3 +180,70 @@ the artifact triggers the rage.
accuracy = -75 // Aiming requires focus.
accuracy_dispersion = 3 // Ditto.
evasion = -45 // Too angry to dodge.
// Ignition, but confined to the modifier system.
// This makes it more predictable and thus, easier to balance.
/datum/modifier/fire
name = "on fire"
desc = "You are on fire! You will be harmed until the fire goes out or you extinguish it with water."
mob_overlay_state = "on_fire"
on_created_text = "<span class='danger'>You combust into flames!</span>"
on_expired_text = "<span class='warning'>The fire starts to fade.</span>"
stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot.
var/damage_per_tick = 5
/datum/modifier/fire/tick()
holder.inflict_heat_damage(damage_per_tick)
// Applied when near something very cold.
// Reduces mobility, attack speed.
/datum/modifier/chilled
name = "chilled"
desc = "You feel yourself freezing up. Its hard to move."
mob_overlay_state = "chilled"
on_created_text = "<span class='danger'>You feel like you're going to freeze! It's hard to move.</span>"
on_expired_text = "<span class='warning'>You feel somewhat warmer and more mobile now.</span>"
stacks = MODIFIER_STACK_EXTEND
slowdown = 2
evasion = -40
attack_speed_percent = 1.4
disable_duration_percent = 1.2
// Similar to being on fire, except poison tends to be more long term.
// Antitoxins will remove stacks over time.
// Synthetics can't receive this.
/datum/modifier/poisoned
name = "poisoned"
desc = "You have poison inside of you. It will cause harm over a long span of time if not cured."
mob_overlay_state = "poisoned"
on_created_text = "<span class='warning'>You feel sick...</span>"
on_expired_text = "<span class='notice'>You feel a bit better.</span>"
stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot.
var/damage_per_tick = 1
/datum/modifier/poisoned/weak
damage_per_tick = 0.5
/datum/modifier/poisoned/strong
damage_per_tick = 2
/datum/modifier/poisoned/tick()
if(holder.stat == DEAD)
expire(silent = TRUE)
holder.inflict_poison_damage(damage_per_tick)
/datum/modifier/poisoned/can_apply(mob/living/L)
if(L.isSynthetic())
return FALSE
if(L.get_poison_protection() >= 1)
return FALSE
return TRUE
+11 -7
View File
@@ -207,8 +207,8 @@
if(istype(thing, /obj/structure/snowman/spider)) //Snow spiders are also spooky so people can be assholes with those too.
fear_amount += 1
if(istype(thing, /mob/living/simple_animal/hostile/giant_spider)) // Actual giant spiders are the scariest of them all.
var/mob/living/simple_animal/hostile/giant_spider/S = thing
if(istype(thing, /mob/living/simple_mob/animal/giant_spider)) // Actual giant spiders are the scariest of them all.
var/mob/living/simple_mob/animal/giant_spider/S = thing
if(S.stat == DEAD) // Dead giant spiders are less scary than alive ones.
fear_amount += 4
else
@@ -425,14 +425,18 @@
if(istype(thing, /obj/item/clothing/head/collectable/slime)) // Some hats are spooky so people can be assholes with them.
fear_amount += 1
if(istype(thing, /mob/living/simple_animal/slime)) // An actual predatory specimen!
var/mob/living/simple_animal/slime/S = thing
if(istype(thing, /mob/living/simple_mob/slime)) // An actual predatory specimen!
var/mob/living/simple_mob/slime/S = thing
if(S.stat == DEAD) // Dead slimes are somewhat less spook.
fear_amount += 4
if(S.is_adult == TRUE) //big boy
fear_amount += 8
if(istype(S, /mob/living/simple_mob/slime/xenobio))
var/mob/living/simple_mob/slime/xenobio/X = S
if(X.is_adult == TRUE) //big boy
fear_amount += 8
else
fear_amount += 6
else
fear_amount += 6
fear_amount += 10 // It's huge and feral.
if(istype(thing, /mob/living/carbon/human))
var/mob/living/carbon/human/S = thing
+2 -2
View File
@@ -71,7 +71,7 @@
/datum/modifier/repair_aura/tick()
spawn()
for(var/mob/living/simple_animal/construct/T in view(4,holder))
for(var/mob/living/simple_mob/construct/T in view(4,holder))
T.adjustBruteLoss(rand(-10,-15))
T.adjustFireLoss(rand(-10,-15))
@@ -110,7 +110,7 @@
spawn()
if(isliving(holder))
var/mob/living/L = holder
if(istype(L, /mob/living/simple_animal/construct))
if(istype(L, /mob/living/simple_mob/construct))
L.adjustBruteLoss(rand(-5,-10))
L.adjustFireLoss(rand(-5,-10))
else
+26 -1
View File
@@ -180,8 +180,33 @@ note dizziness decrements automatically in the mob's Life() proc.
pixel_z = default_pixel_z
alpha = initial_alpha
/atom/movable/proc/do_attack_animation(atom/A)
// Similar to attack animations, but in reverse and is longer to act as a telegraph.
/atom/movable/proc/do_windup_animation(atom/A, windup_time)
var/pixel_x_diff = 0
var/pixel_y_diff = 0
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = -8
else if(direction & SOUTH)
pixel_y_diff = 8
if(direction & EAST)
pixel_x_diff = -8
else if(direction & WEST)
pixel_x_diff = 8
var/default_pixel_x = initial(pixel_x)
var/default_pixel_y = initial(pixel_y)
var/mob/mob = src
if(istype(mob))
default_pixel_x = mob.default_pixel_x
default_pixel_y = mob.default_pixel_y
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = windup_time - 2)
animate(pixel_x = default_pixel_x, pixel_y = default_pixel_y, time = 2)
/atom/movable/proc/do_attack_animation(atom/A)
var/pixel_x_diff = 0
var/pixel_y_diff = 0
var/direction = get_dir(src, A)
+2 -2
View File
@@ -498,7 +498,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//find a viable mouse candidate
var/mob/living/simple_animal/mouse/host
var/mob/living/simple_mob/animal/passive/mouse/host
var/obj/machinery/atmospherics/unary/vent_pump/vent_found
var/list/found_vents = list()
for(var/obj/machinery/atmospherics/unary/vent_pump/v in machines)
@@ -506,7 +506,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
found_vents.Add(v)
if(found_vents.len)
vent_found = pick(found_vents)
host = new /mob/living/simple_animal/mouse(vent_found)
host = new /mob/living/simple_mob/animal/passive/mouse(vent_found)
else
src << "<span class='warning'>Unable to find any unwelded vents to spawn mice at.</span>"
+7
View File
@@ -52,6 +52,13 @@
if(O)
O.see_emote(src, message, m_type)
// Shortcuts for above proc
/mob/proc/visible_emote(var/act_desc)
custom_emote(1, act_desc)
/mob/proc/audible_emote(var/act_desc)
custom_emote(2, act_desc)
/mob/proc/emote_dead(var/message)
if(client.prefs.muted & MUTE_DEADCHAT)
+2 -2
View File
@@ -25,10 +25,10 @@
/mob/living/silicon/seen_cult_turfs()
return list()
/mob/living/simple_animal/seen_cult_turfs()
/mob/living/simple_mob/seen_cult_turfs()
return seen_turfs_in_range(src, 1)
/mob/living/simple_animal/shade/narsie/seen_cult_turfs()
/mob/living/simple_mob/construct/shade/seen_cult_turfs()
return view(2, src)
/proc/seen_turfs_in_range(var/source, var/range)
+9 -10
View File
@@ -78,6 +78,12 @@
var/turf/source = speaker? get_turf(speaker) : get_turf(src)
src.playsound_local(source, speech_sound, sound_vol, 1)
// Done here instead of on_hear_say() since that is NOT called if the mob is clientless (which includes most AI mobs).
/mob/living/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "",var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
..()
if(has_AI()) // Won't happen if no ai_holder exists or there's a player inside w/o autopilot active.
ai_holder.on_hear_say(speaker, message)
/mob/proc/on_hear_say(var/message)
to_chat(src, message)
if(teleop)
@@ -158,17 +164,10 @@
if(!(language && (language.flags & INNATE))) // skip understanding checks for INNATE languages
if(!say_understands(speaker,language))
if(istype(speaker,/mob/living/simple_animal))
var/mob/living/simple_animal/S = speaker
if(S.speak && S.speak.len)
message = pick(S.speak)
else
return
if(language)
message = language.scramble(message)
else
if(language)
message = language.scramble(message)
else
message = stars(message)
message = stars(message)
if(hard_to_hear)
message = stars(message)
+3 -3
View File
@@ -27,16 +27,16 @@
/datum/language/corticalborer/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
var/mob/living/simple_animal/borer/B
var/mob/living/simple_mob/animal/borer/B
if(istype(speaker,/mob/living/carbon))
var/mob/living/carbon/M = speaker
B = M.has_brain_worms()
else if(istype(speaker,/mob/living/simple_animal/borer))
else if(istype(speaker,/mob/living/simple_mob/animal/borer))
B = speaker
if(B)
speaker_mask = B.truename
speaker_mask = B.true_name
..(speaker,message,speaker_mask)
/datum/language/vox
+5 -5
View File
@@ -215,7 +215,7 @@
else
if(declare_arrests)
var/action = arrest_type ? "detaining" : "arresting"
if(istype(target, /mob/living/simple_animal))
if(istype(target, /mob/living/simple_mob))
action = "fighting"
global_announcer.autosay("[src] is [action] a level [threat] [action != "fighting" ? "suspect" : "threat"] <b>[target_name(target)]</b> in <b>[get_area(src)]</b>.", "[src]", "Security")
UnarmedAttack(target)
@@ -269,8 +269,8 @@
C.handcuffed = new /obj/item/weapon/handcuffs(C)
C.update_inv_handcuffed()
busy = 0
else if(istype(M, /mob/living/simple_animal))
var/mob/living/simple_animal/S = M
else if(istype(M, /mob/living/simple_mob))
var/mob/living/simple_mob/S = M
S.Weaken(xeno_stun_strength)
S.adjustBruteLoss(xeno_harm_strength)
do_attack_animation(M)
@@ -286,8 +286,8 @@
/mob/living/bot/secbot/slime/UnarmedAttack(var/mob/living/L, var/proximity)
..()
if(istype(L, /mob/living/simple_animal/slime))
var/mob/living/simple_animal/slime/S = L
if(istype(L, /mob/living/simple_mob/slime/xenobio))
var/mob/living/simple_mob/slime/xenobio/S = L
S.adjust_discipline(2)
@@ -63,5 +63,5 @@
if(istype(M))
for(var/atom/A in M.contents)
if(istype(A,/mob/living/simple_animal/borer) || istype(A,/obj/item/weapon/holder))
if(istype(A,/mob/living/simple_mob/animal/borer) || istype(A,/obj/item/weapon/holder))
return
@@ -46,7 +46,7 @@
return 1
if (istype(other, /mob/living/carbon/human))
return 1
if (istype(other, /mob/living/simple_animal/slime))
if (istype(other, /mob/living/simple_mob/slime))
return 1
return ..()
@@ -76,7 +76,6 @@
if(ingested) ingested.metabolize()
if(bloodstr) bloodstr.metabolize()
AdjustConfused(-1)
// decrement dizziness counter, clamped to 0
if(resting)
dizziness = max(0, dizziness - 5)
@@ -5,7 +5,7 @@
set name = "Release Control"
set desc = "Release control of your host's body."
var/mob/living/simple_animal/borer/B = has_brain_worms()
var/mob/living/simple_mob/animal/borer/B = has_brain_worms()
if(B && B.host_brain)
src << "<span class='danger'>You withdraw your probosci, releasing control of [B.host_brain]</span>"
@@ -25,7 +25,7 @@
set name = "Torment host"
set desc = "Punish your host with agony."
var/mob/living/simple_animal/borer/B = has_brain_worms()
var/mob/living/simple_mob/animal/borer/B = has_brain_worms()
if(!B)
return
@@ -43,7 +43,7 @@
set name = "Reproduce"
set desc = "Spawn several young."
var/mob/living/simple_animal/borer/B = has_brain_worms()
var/mob/living/simple_mob/animal/borer/B = has_brain_worms()
if(!B)
return
@@ -55,7 +55,7 @@
B.has_reproduced = 1
vomit(1)
new /mob/living/simple_animal/borer(get_turf(src))
new /mob/living/simple_mob/animal/borer(get_turf(src))
else
src << "<span class='warning'>You do not have enough chemicals stored to reproduce.</span>"
@@ -61,11 +61,11 @@
//Handle brain slugs.
var/obj/item/organ/external/Hd = get_organ(BP_HEAD)
var/mob/living/simple_animal/borer/B
var/mob/living/simple_mob/animal/borer/B
if(Hd)
for(var/I in Hd.implants)
if(istype(I,/mob/living/simple_animal/borer))
if(istype(I,/mob/living/simple_mob/animal/borer))
B = I
if(B)
if(!B.ckey && ckey && B.controlling)
@@ -135,6 +135,27 @@ emp_act
return siemens_coefficient
// Similar to above but is for the mob's overall protection, being the average of all slots.
/mob/living/carbon/human/proc/get_siemens_coefficient_average()
var/siemens_value = 0
var/total = 0
for(var/organ_name in organs_by_name)
if(organ_name in organ_rel_size)
var/obj/item/organ/external/organ = organs_by_name[organ_name]
if(organ)
var/weight = organ_rel_size[organ_name]
siemens_value += get_siemens_coefficient_organ(organ) * weight
total += weight
if(fire_stacks < 0) // Water makes you more conductive.
siemens_value *= 1.5
return (siemens_value/max(total, 1))
// Returns a number between 0 to 1, with 1 being total protection.
/mob/living/carbon/human/get_shock_protection()
return between(0, 1-get_siemens_coefficient_average(), 1)
// Returns a list of clothing that is currently covering def_zone.
/mob/living/carbon/human/proc/get_clothing_list_organ(var/obj/item/organ/external/def_zone, var/type)
var/list/results = list()
@@ -542,6 +563,20 @@ emp_act
return perm
// This is for preventing harm by being covered in water, which only prometheans need to deal with.
// This is not actually used for now since the code for prometheans gets changed a lot.
/mob/living/carbon/human/get_water_protection()
var/protection = ..() // Todo: Replace with species var later.
if(protection == 1) // No point doing permeability checks if it won't matter.
return protection
// Wearing clothing with a low permeability_coefficient can protect from water.
var/converted_protection = 1 - protection
var/perm = reagent_permeability()
converted_protection *= perm
return 1-converted_protection
/mob/living/carbon/human/shank_attack(obj/item/W, obj/item/weapon/grab/G, mob/user, hit_zone)
if(!..())
@@ -42,6 +42,16 @@
if(cloaker.active)
cloaker.deactivate()
/mob/living/carbon/human/is_cloaked()
if(mind && mind.changeling && mind.changeling.cloaked) // Ling camo.
return TRUE
else if(istype(back, /obj/item/weapon/rig)) //Ninja cloak
var/obj/item/weapon/rig/suit = back
for(var/obj/item/rig_module/stealth_field/cloaker in suit.installed_modules)
if(cloaker.active)
return TRUE
return ..()
/mob/living/carbon/human/get_ear_protection()
var/sum = 0
if(istype(l_ear, /obj/item/clothing/ears))
+1 -3
View File
@@ -1047,8 +1047,6 @@
sleeping += 1
Paralyse(5)
confused = max(0, confused - 1)
// If you're dirty, your gloves will become dirty, too.
if(gloves && germ_level > gloves.germ_level && prob(10))
gloves.germ_level += 1
@@ -1602,7 +1600,7 @@
else if(foundVirus)
holder.icon_state = "hudill"
else if(has_brain_worms())
var/mob/living/simple_animal/borer/B = has_brain_worms()
var/mob/living/simple_mob/animal/borer/B = has_brain_worms()
if(B.controlling)
holder.icon_state = "hudbrainworm"
else
+1 -1
View File
@@ -64,7 +64,7 @@
return 1
if (istype(other, /mob/living/carbon/brain))
return 1
if (istype(other, /mob/living/simple_animal/slime))
if (istype(other, /mob/living/simple_mob/slime))
return 1
//This is already covered by mob/say_understands()
@@ -116,7 +116,7 @@ var/datum/species/shapeshifter/promethean/prometheans
/datum/species/shapeshifter/promethean/equip_survival_gear(var/mob/living/carbon/human/H)
var/boxtype = pick(typesof(/obj/item/weapon/storage/toolbox/lunchbox))
var/obj/item/weapon/storage/toolbox/lunchbox/L = new boxtype(get_turf(H))
var/mob/living/simple_animal/mouse/mouse = new (L)
var/mob/living/simple_mob/animal/passive/mouse/mouse = new (L)
var/obj/item/weapon/holder/holder = new (L)
mouse.forceMove(holder)
holder.sync(mouse)
+6
View File
@@ -1,5 +1,6 @@
/mob/living/death()
clear_fullscreens()
<<<<<<< HEAD
reveal(TRUE) //Silently reveal the mob if they were hidden.
//VOREStation Edit - Mob spawner stuff
if(source_spawner)
@@ -7,6 +8,11 @@
source_spawner = null
//VOREStation Edit End
. = ..()
=======
if(ai_holder)
ai_holder.go_sleep()
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
if(nest) //Ew.
if(istype(nest, /obj/structure/prop/nest))
+6
View File
@@ -110,6 +110,7 @@
handle_silent()
handle_drugged()
handle_slurring()
handle_confused()
/mob/living/proc/handle_stunned()
if(stunned)
@@ -146,6 +147,11 @@
AdjustParalysis(-1)
return paralysis
/mob/living/proc/handle_confused()
if(confused)
AdjustConfused(-1)
return confused
/mob/living/proc/handle_disabilities()
//Eyes
if(sdisabilities & BLIND || stat) //blindness from disability or unconsciousness doesn't get better on its own
+36 -1
View File
@@ -16,6 +16,8 @@
dsma.blend_mode = BLEND_ADD
dsoverlay.appearance = dsma
selected_image = image(icon = 'icons/mob/screen1.dmi', loc = src, icon_state = "centermarker")
/mob/living/Destroy()
dsoverlay.loc = null //I'll take my coat with me
dsoverlay = null
@@ -29,6 +31,7 @@
nest = null
if(buckled)
buckled.unbuckle_mob(src, TRUE)
qdel(selected_image)
return ..()
//mob verbs are faster than object verbs. See mob/verb/examine.
@@ -550,6 +553,36 @@ default behaviour is:
// ++++ROCKDTBEN++++ MOB PROCS //END
// Applies direct "cold" damage while checking protection against the cold.
/mob/living/proc/inflict_cold_damage(amount)
amount *= 1 - get_cold_protection(50) // Within spacesuit protection.
if(amount > 0)
adjustFireLoss(amount)
// Ditto, but for "heat".
/mob/living/proc/inflict_heat_damage(amount)
amount *= 1 - get_heat_protection(10000) // Within firesuit protection.
if(amount > 0)
adjustFireLoss(amount)
// and one for electricity because why not
/mob/living/proc/inflict_shock_damage(amount)
electrocute_act(amount, null, 1 - get_shock_protection())
// also one for water (most things resist it entirely, except for slimes)
/mob/living/proc/inflict_water_damage(amount)
amount *= 1 - get_water_protection()
if(amount > 0)
adjustToxLoss(amount)
// one for abstracted away ""poison"" (mostly because simplemobs shouldn't handle reagents)
/mob/living/proc/inflict_poison_damage(amount)
if(isSynthetic())
return
amount *= 1 - get_poison_protection()
if(amount > 0)
adjustToxLoss(amount)
/mob/proc/get_contents()
@@ -663,6 +696,8 @@ default behaviour is:
BITSET(hud_updateflag, LIFE_HUD)
ExtinguishMob()
fire_stacks = 0
if(ai_holder) // AI gets told to sleep when killed. Since they're not dead anymore, wake it up.
ai_holder.go_wake()
/mob/living/proc/rejuvenate()
if(reagents)
@@ -891,7 +926,7 @@ default behaviour is:
// Update whether or not this mob needs to pass emotes to contents.
for(var/atom/A in M.contents)
if(istype(A,/mob/living/simple_animal/borer) || istype(A,/obj/item/weapon/holder))
if(istype(A,/mob/living/simple_mob/animal/borer) || istype(A,/obj/item/weapon/holder))
return
else if(istype(H.loc,/obj/item/clothing/accessory/holster))
+33 -2
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)
@@ -267,6 +277,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
@@ -324,12 +336,13 @@
// End BS12 momentum-transfer code.
/mob/living/attack_generic(var/mob/user, var/damage, var/attack_message)
if(!damage)
return
adjustBruteLoss(damage)
add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_animal attacks
add_attack_logs(user,src,"Generic attack (probably animal)", admin_notify = FALSE) //Usually due to simple_mob 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()
@@ -412,6 +425,15 @@
/mob/living/proc/get_heat_protection()
return 0
/mob/living/proc/get_shock_protection()
return 0
/mob/living/proc/get_water_protection()
return 1 // Water won't hurt most things.
/mob/living/proc/get_poison_protection()
return 0
//Finds the effective temperature that the mob is burning at.
/mob/living/proc/fire_burn_temperature()
if (fire_stacks <= 0)
@@ -421,6 +443,15 @@
//lower limit of 700 K, same as matches and roughly the temperature of a cool flame.
return max(2.25*round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2), 700)
// Called when struck by lightning.
/mob/living/proc/lightning_act()
// The actual damage/electrocution is handled by the tesla_zap() that accompanies this.
Paralyse(5)
stuttering += 20
make_jittery(150)
emp_act(1)
to_chat(src, span("critical", "You've been struck by lightning!"))
/mob/living/proc/reagent_permeability()
return 1
return round(FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE*(fire_stacks/FIRE_MAX_FIRESUIT_STACKS)**2)
+5 -1
View File
@@ -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 = null // 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
@@ -20,6 +22,7 @@
var/list/atom/hallucinations = list() //A list of hallucinated people that try to attack the mob. See /obj/effect/fake_attacker in hallucinations.dm
var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out.
var/base_attack_cooldown = DEFAULT_ATTACK_COOLDOWN
var/t_phoron = null
var/t_oxygen = null
@@ -64,4 +67,5 @@
var/makes_dirt = TRUE //FALSE if the mob shouldn't be making dirt on the ground when it walks
var/looking_elsewhere = FALSE //If the mob's view has been relocated to somewhere else, like via a camera or with binocs
var/image/selected_image = null // Used for buildmode AI control stuff.
+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()
@@ -344,7 +344,7 @@
var/grabbed_something = 0
for(var/mob/M in T)
if(istype(M,/mob/living/simple_animal/lizard) || istype(M,/mob/living/simple_animal/mouse))
if(istype(M,/mob/living/simple_mob/animal/passive/lizard) || istype(M,/mob/living/simple_mob/animal/passive/mouse))
src.loc.visible_message("<span class='danger'>[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.</span>","<span class='danger'>It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.</span>")
new/obj/effect/decal/cleanable/blood/splatter(get_turf(src))
qdel(M)
@@ -72,7 +72,6 @@
melee_damage_lower = 13
melee_damage_upper = 28
/mob/living/simple_animal/hostile/faithless/strong/cult
faction = "cult"
supernatural = 1
@@ -1,6 +1,6 @@
// Hivebots are tuned towards how many default lasers are needed to kill them.
// As such, if laser damage is ever changed, you should change this define.
#define LASERS_TO_KILL *30
#define LASERS_TO_KILL * 40
// Default hivebot is melee, and a bit more meaty, so it can meatshield for their ranged friends.
/mob/living/simple_animal/hostile/hivebot
@@ -238,3 +238,5 @@
/obj/item/projectile/bullet/hivebot
damage = 10
damage_type = BRUTE
#undef LASERS_TO_KILL
@@ -1,3 +1,4 @@
<<<<<<< HEAD
//Cat
/mob/living/simple_animal/cat
name = "cat"
@@ -202,3 +203,203 @@
/obj/item/weapon/holder/cat/kitten
icon_state = "kitten"
w_class = ITEMSIZE_SMALL
=======
//Cat
/mob/living/simple_animal/cat
name = "cat"
desc = "A domesticated, feline pet. Has a tendency to adopt crewmembers."
tt_desc = "E Felis silvestris catus"
intelligence_level = SA_ANIMAL
icon_state = "cat2"
item_state = "cat2"
icon_living = "cat2"
icon_dead = "cat2_dead"
icon_rest = "cat2_rest"
investigates = 1
specific_targets = 1 //Only targets with Found()
run_at_them = 0 //DOMESTICATED
view_range = 5
turns_per_move = 5
see_in_dark = 6
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "kicks"
min_oxy = 16 //Require atleast 16kPA oxygen
minbodytemp = 223 //Below -50 Degrees Celcius
maxbodytemp = 323 //Above 50 Degrees Celcius
holder_type = /obj/item/weapon/holder/cat
mob_size = MOB_SMALL
has_langs = list("Cat")
speak_chance = 1
speak = list("Meow!","Esp!","Purr!","HSSSSS")
speak_emote = list("purrs", "meows")
emote_hear = list("meows","mews")
emote_see = list("shakes their head", "shivers")
say_maybe_target = list("Meow?","Mew?","Mao?")
say_got_target = list("MEOW!","HSSSS!","REEER!")
meat_amount = 1
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
var/turns_since_scan = 0
var/mob/flee_target
/mob/living/simple_animal/cat/Life()
. = ..()
if(!.) return
if(prob(2)) //spooky
var/mob/observer/dead/spook = locate() in range(src,5)
if(spook)
var/turf/T = spook.loc
var/list/visible = list()
for(var/obj/O in T.contents)
if(!O.invisibility && O.name)
visible += O
if(visible.len)
var/atom/A = pick(visible)
visible_emote("suddenly stops and stares at something unseen[istype(A) ? " near [A]":""].")
handle_flee_target()
/mob/living/simple_animal/cat/PunchTarget()
if(ismouse(target_mob))
var/mob/living/simple_mob/animal/passive/mouse/mouse = target_mob
mouse.splat()
visible_emote(pick("bites \the [mouse]!","toys with \the [mouse].","chomps on \the [mouse]!"))
return mouse
else
..()
/mob/living/simple_animal/cat/Found(var/atom/found_atom)
if(ismouse(found_atom) && SA_attackable(found_atom))
return found_atom
/mob/living/simple_animal/cat/proc/handle_flee_target()
//see if we should stop fleeing
if (flee_target && !(flee_target in ListTargets(view_range)))
flee_target = null
GiveUpMoving()
if (flee_target && !stat && !buckled)
if (resting)
lay_down()
if(prob(25)) say("HSSSSS")
stop_automated_movement = 1
walk_away(src, flee_target, 7, 2)
/mob/living/simple_animal/cat/react_to_attack(var/atom/A)
if(A == src) return
flee_target = A
turns_since_scan = 5
/mob/living/simple_animal/cat/ex_act()
. = ..()
react_to_attack(src.loc)
//Basic friend AI
/mob/living/simple_animal/cat/fluff
var/mob/living/carbon/human/friend
var/befriend_job = null
/mob/living/simple_animal/cat/fluff/Life()
. = ..()
if(!. || ai_inactive || !friend) return
var/friend_dist = get_dist(src,friend)
if (friend_dist <= 4)
if(stance == STANCE_IDLE)
if(set_follow(friend))
handle_stance(STANCE_FOLLOW)
if (friend_dist <= 1)
if (friend.stat >= DEAD || friend.health <= config.health_threshold_softcrit)
if (prob((friend.stat < DEAD)? 50 : 15))
var/verb = pick("meows", "mews", "mrowls")
audible_emote(pick("[verb] in distress.", "[verb] anxiously."))
else
if (prob(5))
visible_emote(pick("nuzzles [friend].",
"brushes against [friend].",
"rubs against [friend].",
"purrs."))
else if (friend.health <= 50)
if (prob(10))
var/verb = pick("meows", "mews", "mrowls")
audible_emote("[verb] anxiously.")
/mob/living/simple_animal/cat/fluff/verb/become_friends()
set name = "Become Friends"
set category = "IC"
set src in view(1)
if(!friend)
var/mob/living/carbon/human/H = usr
if(istype(H) && (!befriend_job || H.job == befriend_job))
friend = usr
. = 1
else if(usr == friend)
. = 1 //already friends, but show success anyways
if(.)
set_dir(get_dir(src, friend))
visible_emote(pick("nuzzles [friend].",
"brushes against [friend].",
"rubs against [friend].",
"purrs."))
else
usr << "<span class='notice'>[src] ignores you.</span>"
return
//RUNTIME IS ALIVE! SQUEEEEEEEE~
/mob/living/simple_animal/cat/fluff/Runtime
name = "Runtime"
desc = "Her fur has the look and feel of velvet, and her tail quivers occasionally."
tt_desc = "E Felis silvestris medicalis" //a hypoallergenic breed produced by NT for... medical purposes? Sure.
gender = FEMALE
icon_state = "cat"
item_state = "cat"
icon_living = "cat"
icon_dead = "cat_dead"
icon_rest = "cat_rest"
befriend_job = "Chief Medical Officer"
/mob/living/simple_animal/cat/kitten
name = "kitten"
desc = "D'aaawwww"
icon_state = "kitten"
item_state = "kitten"
icon_living = "kitten"
icon_dead = "kitten_dead"
gender = NEUTER
// Leaving this here for now.
/obj/item/weapon/holder/cat/fluff/bones
name = "Bones"
desc = "It's Bones! Meow."
gender = MALE
icon_state = "cat3"
/mob/living/simple_animal/cat/fluff/bones
name = "Bones"
desc = "That's Bones the cat. He's a laid back, black cat. Meow."
gender = MALE
icon_state = "cat3"
item_state = "cat3"
icon_living = "cat3"
icon_dead = "cat3_dead"
icon_rest = "cat3_rest"
holder_type = /obj/item/weapon/holder/cat/fluff/bones
var/friend_name = "Erstatz Vryroxes"
/mob/living/simple_animal/cat/kitten/New()
gender = pick(MALE, FEMALE)
..()
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
@@ -207,6 +207,7 @@
set_dir(i)
sleep(1)
//Technically this should be like, its own file or something or a subset of dog but whatever. Not a coder.
/mob/living/simple_animal/corgi/tamaskan
name = "tamaskan"
@@ -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
@@ -46,7 +46,7 @@
/obj/item/device/radio/borg,
/obj/item/weapon/holder,
/obj/machinery/camera,
/mob/living/simple_animal/borer,
/mob/living/simple_mob/animal/borer,
/obj/item/device/mmi,
)
@@ -1,4 +1,4 @@
/mob/living/simple_animal/borer
/mob/living/simple_mob/animal/borer
name = "cortical borer"
real_name = "cortical borer"
desc = "A small, quivering sluglike creature."
@@ -35,15 +35,15 @@
can_be_antagged = TRUE
/mob/living/simple_animal/borer/roundstart
/mob/living/simple_mob/animal/borer/roundstart
roundstart = 1
/mob/living/simple_animal/borer/Login()
/mob/living/simple_mob/animal/borer/Login()
..()
if(mind)
borers.add_antagonist(mind)
/mob/living/simple_animal/borer/New()
/mob/living/simple_mob/animal/borer/New()
..()
add_language("Cortical Link")
@@ -53,7 +53,7 @@
truename = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]"
if(!roundstart) request_player()
/mob/living/simple_animal/borer/Life()
/mob/living/simple_mob/animal/borer/Life()
..()
@@ -91,7 +91,7 @@
if(prob(host.brainloss/20))
host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]")
/mob/living/simple_animal/borer/Stat()
/mob/living/simple_mob/animal/borer/Stat()
..()
statpanel("Status")
@@ -103,7 +103,7 @@
if (client.statpanel == "Status")
stat("Chemicals", chemicals)
/mob/living/simple_animal/borer/proc/detatch()
/mob/living/simple_mob/animal/borer/proc/detatch()
if(!host || !controlling) return
@@ -154,7 +154,7 @@
qdel(host_brain)
/mob/living/simple_animal/borer/proc/leave_host()
/mob/living/simple_mob/animal/borer/proc/leave_host()
if(!host) return
@@ -172,7 +172,7 @@
return
//Procs for grabbing players.
/mob/living/simple_animal/borer/proc/request_player()
/mob/living/simple_mob/animal/borer/proc/request_player()
for(var/mob/observer/dead/O in player_list)
if(jobban_isbanned(O, "Borer"))
continue
@@ -180,7 +180,7 @@
if(O.client.prefs.be_special & BE_ALIEN)
question(O.client)
/mob/living/simple_animal/borer/proc/question(var/client/C)
/mob/living/simple_mob/animal/borer/proc/question(var/client/C)
spawn(0)
if(!C) return
var/response = alert(C, "A cortical borer needs a player. Are you interested?", "Cortical borer request", "Yes", "No", "Never for this round")
@@ -191,7 +191,7 @@
else if (response == "Never for this round")
C.prefs.be_special ^= BE_ALIEN
/mob/living/simple_animal/borer/proc/transfer_personality(var/client/candidate)
/mob/living/simple_mob/animal/borer/proc/transfer_personality(var/client/candidate)
if(!candidate || !candidate.mob || !candidate.mob.mind)
return
@@ -209,5 +209,5 @@
your host and your eventual spawn safe and warm."
src << "You can speak to your victim with <b>say</b>, to other borers with <b>say :x</b>, and use your Abilities tab to access powers."
/mob/living/simple_animal/borer/cannot_use_vents()
/mob/living/simple_mob/animal/borer/cannot_use_vents()
return
@@ -10,7 +10,7 @@
src << "<font color='red'>You cannot speak in IC (muted).</font>"
return
if(istype(src.loc,/mob/living/simple_animal/borer))
if(istype(src.loc,/mob/living/simple_mob/animal/borer))
message = sanitize(message)
if (!message)
@@ -19,7 +19,7 @@
if (stat == 2)
return say_dead(message)
var/mob/living/simple_animal/borer/B = src.loc
var/mob/living/simple_mob/animal/borer/B = src.loc
src << "You whisper silently, \"[message]\""
B.host << "The captive mind of [src] whispers, \"[message]\""
@@ -34,8 +34,8 @@
/mob/living/captive_brain/process_resist()
//Resisting control by an alien mind.
if(istype(src.loc,/mob/living/simple_animal/borer))
var/mob/living/simple_animal/borer/B = src.loc
if(istype(src.loc,/mob/living/simple_mob/animal/borer))
var/mob/living/simple_mob/animal/borer/B = src.loc
var/mob/living/captive_brain/H = src
H << "<span class='danger'>You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).</span>"
@@ -1,4 +1,4 @@
/mob/living/simple_animal/borer/verb/release_host()
/mob/living/simple_mob/animal/borer/verb/release_host()
set category = "Abilities"
set name = "Release Host"
set desc = "Slither out of your host."
@@ -38,7 +38,7 @@
detatch()
leave_host()
/mob/living/simple_animal/borer/verb/infest()
/mob/living/simple_mob/animal/borer/verb/infest()
set category = "Abilities"
set name = "Infest"
set desc = "Infest a suitable humanoid host."
@@ -126,7 +126,7 @@
return
/*
/mob/living/simple_animal/borer/verb/devour_brain()
/mob/living/simple_mob/animal/borer/verb/devour_brain()
set category = "Abilities"
set name = "Devour Brain"
set desc = "Take permanent control of a dead host."
@@ -152,7 +152,7 @@
*/
// BRAIN WORM ZOMBIES AAAAH.
/mob/living/simple_animal/borer/proc/replace_brain()
/mob/living/simple_mob/animal/borer/proc/replace_brain()
var/mob/living/carbon/human/H = host
@@ -198,7 +198,7 @@
if(!H.lastKnownIP)
H.lastKnownIP = s2h_ip
/mob/living/simple_animal/borer/verb/secrete_chemicals()
/mob/living/simple_mob/animal/borer/verb/secrete_chemicals()
set category = "Abilities"
set name = "Secrete Chemicals"
set desc = "Push some chemicals into your host's bloodstream."
@@ -226,7 +226,7 @@
host.reagents.add_reagent(chem, 10)
chemicals -= 50
/mob/living/simple_animal/borer/verb/dominate_victim()
/mob/living/simple_mob/animal/borer/verb/dominate_victim()
set category = "Abilities"
set name = "Paralyze Victim"
set desc = "Freeze the limbs of a potential host with supernatural fear."
@@ -266,7 +266,7 @@
used_dominate = world.time
/mob/living/simple_animal/borer/verb/bond_brain()
/mob/living/simple_mob/animal/borer/verb/bond_brain()
set category = "Abilities"
set name = "Assume Control"
set desc = "Fully connect to the brain of your host."
@@ -335,20 +335,3 @@
host.verbs += /mob/living/carbon/proc/spawn_larvae
return
/mob/living/carbon/human/proc/jumpstart()
set category = "Abilities"
set name = "Revive Host"
set desc = "Send a jolt of electricity through your host, reviving them."
if(stat != 2)
usr << "Your host is already alive."
return
verbs -= /mob/living/carbon/human/proc/jumpstart
visible_message("<span class='warning'>With a hideous, rattling moan, [src] shudders back to life!</span>")
rejuvenate()
restore_blood()
fixblood()
update_canmove()
@@ -1,4 +1,4 @@
/mob/living/simple_animal/borer/say(var/message)
/mob/living/simple_mob/animal/borer/say(var/message)
message = sanitize(message)
message = capitalize(message)
@@ -1,488 +0,0 @@
/mob/living/simple_animal/construct
name = "Construct"
real_name = "Construct"
var/construct_type = "shade"
desc = ""
speak_emote = list("hisses")
emote_hear = list("wails","screeches")
ui_icons = 'icons/mob/screen1_construct.dmi'
has_hands = 1
hand_form = "stone manipulators"
response_help = "thinks better of touching"
response_disarm = "flailed at"
response_harm = "punched"
intelligence_level = SA_HUMANOID // Player controlled.
hovering = TRUE
softfall = TRUE //Beings made of Hellmarble and powered by the tears of the damned are not concerned with mortal things such as 'gravity'.
parachuting = TRUE
icon_dead = "shade_dead"
var/do_glow = 1
speed = -1
a_intent = I_HURT
stop_automated_movement = 1
status_flags = CANPUSH
universal_speak = 0
universal_understand = 1
attack_sound = 'sound/weapons/spiderlunge.ogg'
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
show_stat_health = 1
faction = "cult"
supernatural = 1
see_invisible = SEE_INVISIBLE_NOLIGHTING
see_in_dark = 7
var/nullblock = 0
mob_swap_flags = HUMAN|SIMPLE_ANIMAL|SLIME|MONKEY
mob_push_flags = ALLMOBS
var/list/construct_spells = list()
can_be_antagged = TRUE
taser_kill = 0 // no
shock_resistance = 0.9 //Electricity isn't very effective on stone, especially that from hell.
armor = list(
"melee" = 10,
"bullet" = 10,
"laser" = 10,
"energy" = 10,
"bomb" = 10,
"bio" = 100,
"rad" = 100)
/mob/living/simple_animal/construct/place_spell_in_hand(var/path)
if(!path || !ispath(path))
return 0
//var/obj/item/weapon/spell/S = new path(src)
var/obj/item/weapon/spell/construct/S = new path(src)
//No hands needed for innate casts.
if(S.cast_methods & CAST_INNATE)
if(S.run_checks())
S.on_innate_cast(src)
if(l_hand && r_hand) //Make sure our hands aren't full.
if(istype(r_hand, /obj/item/weapon/spell)) //If they are full, perhaps we can still be useful.
var/obj/item/weapon/spell/r_spell = r_hand
if(r_spell.aspect == ASPECT_CHROMATIC) //Check if we can combine the new spell with one in our hands.
r_spell.on_combine_cast(S, src)
else if(istype(l_hand, /obj/item/weapon/spell))
var/obj/item/weapon/spell/l_spell = l_hand
if(l_spell.aspect == ASPECT_CHROMATIC) //Check the other hand too.
l_spell.on_combine_cast(S, src)
else //Welp
to_chat(src, "<span class='warning'>You require a free manipulator to use this power.</span>")
return 0
if(S.run_checks())
put_in_hands(S)
return 1
else
qdel(S)
return 0
/mob/living/simple_animal/construct/cultify()
return
/mob/living/simple_animal/construct/New()
..()
name = text("[initial(name)] ([rand(1, 1000)])")
real_name = name
add_language("Cult")
add_language("Occult")
for(var/spell in construct_spells)
src.add_spell(new spell, "const_spell_ready")
updateicon()
/mob/living/simple_animal/construct/updateicon()
overlays.Cut()
..()
if(do_glow)
add_glow()
/mob/living/simple_animal/construct/update_icon()
..()
if(do_glow)
add_glow()
/mob/living/simple_animal/construct/death()
new /obj/item/weapon/ectoplasm (src.loc)
..(null,"collapses in a shattered heap.")
ghostize()
qdel(src)
/mob/living/simple_animal/construct/attack_generic(var/mob/user)
if(istype(user, /mob/living/simple_animal/construct/builder))
var/mob/living/simple_animal/construct/builder/B = user
if(health < getMaxHealth())
var/repair_lower_bound = B.melee_damage_lower * -1
var/repair_upper_bound = B.melee_damage_upper * -1
adjustBruteLoss(rand(repair_lower_bound, repair_upper_bound))
adjustFireLoss(rand(repair_lower_bound, repair_upper_bound))
user.visible_message("<span class='notice'>\The [user] mends some of \the [src]'s wounds.</span>")
else
to_chat(user, "<span class='notice'>\The [src] is undamaged.</span>")
return
return ..()
/mob/living/simple_animal/construct/examine(mob/user)
..(user)
var/msg = "<span cass='info'>*---------*\nThis is \icon[src] \a <EM>[src]</EM>!\n"
if (src.health < src.getMaxHealth())
msg += "<span class='warning'>"
if (src.health >= src.getMaxHealth()/2)
msg += "It looks slightly dented.\n"
else
msg += "<B>It looks severely dented!</B>\n"
msg += "</span>"
msg += "*---------*</span>"
user << msg
/mob/living/simple_animal/construct/Process_Spacemove()
return 1 //Constructs levitate, can fall from a shuttle with no harm, and are piloted by either damned spirits or some otherworldly entity. It's not hard to believe.
/////////////////Juggernaut///////////////
/mob/living/simple_animal/construct/armoured
name = "Juggernaut"
real_name = "Juggernaut"
construct_type = "juggernaut"
desc = "A possessed suit of armour driven by the will of the restless dead"
icon = 'icons/mob/mob.dmi'
icon_state = "behemoth"
icon_living = "behemoth"
maxHealth = 300
health = 300
response_harm = "harmlessly punches"
harm_intent_damage = 0
melee_damage_lower = 30
melee_damage_upper = 40
attack_armor_pen = 60 //Being punched by a living, floating statue.
attacktext = list("smashed their armoured gauntlet into")
friendly = list("pats")
mob_size = MOB_HUGE
speed = 2 //Not super fast, but it might catch up to someone in armor who got punched once or twice.
environment_smash = 2
attack_sound = 'sound/weapons/heavysmash.ogg'
status_flags = 0
resistance = 10
construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser,
/spell/targeted/fortify,
/spell/targeted/construct_advanced/slam
)
armor = list(
"melee" = 70,
"bullet" = 30,
"laser" = 30,
"energy" = 30,
"bomb" = 10,
"bio" = 100,
"rad" = 100)
/mob/living/simple_animal/construct/armoured/Life()
weakened = 0
..()
/mob/living/simple_animal/construct/armoured/bullet_act(var/obj/item/projectile/P)
// if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) //If it's going to be slow, it's probably going to need every reflect it can get.
var/reflectchance = 80 - round(P.damage/3)
if(prob(reflectchance))
var/damage_mod = rand(2,4)
var/projectile_dam_type = P.damage_type
var/incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3)))
var/armorcheck = run_armor_check(null, P.check_armour)
var/soakedcheck = get_armor_soak(null, P.check_armour)
if(!(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)))
visible_message("<span class='danger'>The [P.name] bounces off of [src]'s shell!</span>", \
"<span class='userdanger'>The [P.name] bounces off of [src]'s shell!</span>")
new /obj/item/weapon/material/shard/shrapnel(src.loc)
if(!(P.damage_type == BRUTE || P.damage_type == BURN))
projectile_dam_type = BRUTE
incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to brute for physical projectiles, though severely decreased.
apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P)
return -1 //Doesn't reflect non-beams or non-energy projectiles. They just smack and drop with little to no effect.
else
visible_message("<span class='danger'>The [P.name] gets reflected by [src]'s shell!</span>", \
"<span class='userdanger'>The [P.name] gets reflected by [src]'s shell!</span>")
damage_mod = rand(3,5)
incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3)))
if(!(P.damage_type == BRUTE || P.damage_type == BURN))
projectile_dam_type = BURN
incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to burn for energy-type projectiles, though severely decreased.
apply_damage(incoming_damage, P.damage_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P)
// Find a turf near or on the original location to bounce to
if(P.starting)
var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/turf/curloc = get_turf(src)
// redirect the projectile
P.redirect(new_x, new_y, curloc, src)
P.reflected = 1
return -1 // complete projectile permutation
return (..(P))
////////////////////////Wraith/////////////////////////////////////////////
/mob/living/simple_animal/construct/wraith
name = "Wraith"
real_name = "Wraith"
construct_type = "wraith"
desc = "A wicked bladed shell contraption piloted by a bound spirit."
icon = 'icons/mob/mob.dmi'
icon_state = "floating"
icon_living = "floating"
maxHealth = 200
health = 200
melee_damage_lower = 25
melee_damage_upper = 30
attack_armor_pen = 15
attack_sharp = 1
attack_edge = 1
attacktext = list("slashed")
friendly = list("pinches")
speed = -1
environment_smash = 1
attack_sound = 'sound/weapons/rapidslice.ogg'
construct_spells = list(/spell/targeted/ethereal_jaunt/shift,
/spell/targeted/ambush_mode
)
/mob/living/simple_animal/construct/wraith/DoPunch(var/atom/A)
. = ..()
if(. && isliving(A))
var/mob/living/L = A
L.add_modifier(/datum/modifier/deep_wounds, 30 SECONDS)
/////////////////////////////Artificer/////////////////////////
/mob/living/simple_animal/construct/builder
name = "Artificer"
real_name = "Artificer"
construct_type = "artificer"
desc = "A bulbous construct dedicated to building and maintaining temples to their otherworldly lords."
icon = 'icons/mob/mob.dmi'
icon_state = "artificer"
icon_living = "artificer"
maxHealth = 150
health = 150
response_harm = "viciously beaten"
harm_intent_damage = 5
melee_damage_lower = 15 //It's not the strongest of the bunch, but that doesn't mean it can't hurt you.
melee_damage_upper = 20
attacktext = list("rammed")
speed = 0
environment_smash = 2
attack_sound = 'sound/weapons/rapidslice.ogg'
construct_spells = list(/spell/aoe_turf/conjure/construct/lesser,
/spell/aoe_turf/conjure/wall,
/spell/aoe_turf/conjure/floor,
/spell/aoe_turf/conjure/soulstone,
/spell/aoe_turf/conjure/pylon,
/spell/aoe_turf/conjure/door,
/spell/aoe_turf/conjure/grille,
/spell/targeted/occult_repair_aura,
/spell/targeted/construct_advanced/mend_acolyte
)
/////////////////////////////Behemoth/////////////////////////
/*
* The Behemoth. Admin-allowance only, still try to keep it in some guideline of 'Balanced', even if it means Security has to be fully geared to be so.
*/
/mob/living/simple_animal/construct/behemoth
name = "Behemoth"
real_name = "Behemoth"
construct_type = "juggernaut"
desc = "The pinnacle of occult technology, Behemoths are nothing shy of both an Immovable Object, and Unstoppable Force."
icon = 'icons/mob/mob.dmi'
icon_state = "behemoth"
icon_living = "behemoth"
maxHealth = 750
health = 750
speak_emote = list("rumbles")
response_harm = "harmlessly punched"
harm_intent_damage = 0
melee_damage_lower = 50
melee_damage_upper = 50
attacktext = list("brutally crushed")
friendly = list("pokes") //Anything nice the Behemoth would do would still Kill the Human. Leave it at poke.
speed = 5
environment_smash = 2
attack_sound = 'sound/weapons/heavysmash.ogg'
resistance = 10
icon_scale = 2
var/energy = 0
var/max_energy = 1000
armor = list(
"melee" = 60,
"bullet" = 60,
"laser" = 60,
"energy" = 30,
"bomb" = 10,
"bio" = 100,
"rad" = 100)
construct_spells = list(/spell/aoe_turf/conjure/forcewall/lesser,
/spell/targeted/fortify,
/spell/targeted/construct_advanced/slam
)
/mob/living/simple_animal/construct/behemoth/bullet_act(var/obj/item/projectile/P)
var/reflectchance = 80 - round(P.damage/3)
if(prob(reflectchance))
visible_message("<span class='danger'>The [P.name] gets reflected by [src]'s shell!</span>", \
"<span class='userdanger'>The [P.name] gets reflected by [src]'s shell!</span>")
// Find a turf near or on the original location to bounce to
if(P.starting)
var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)
var/turf/curloc = get_turf(src)
// redirect the projectile
P.redirect(new_x, new_y, curloc, src)
P.reflected = 1
return -1 // complete projectile permutation
return (..(P))
////////////////////////Harvester////////////////////////////////
/*
* Master of Spells and Ranged Abilities. Not as fragile as the Wraith, but nowhere near as maneuverable and deadly in melee.
*/
/mob/living/simple_animal/construct/harvester
name = "Harvester"
real_name = "Harvester"
construct_type = "harvester"
desc = "A tendril-laden construct piloted by a chained mind."
icon = 'icons/mob/mob.dmi'
icon_state = "harvester"
icon_living = "harvester"
maxHealth = 150
health = 150
melee_damage_lower = 20
melee_damage_upper = 25
attack_sharp = 1
attacktext = list("violently stabbed")
friendly = list("caresses")
speed = 0
environment_smash = 1
attack_sound = 'sound/weapons/pierce.ogg'
armor = list(
"melee" = 10,
"bullet" = 20,
"laser" = 20,
"energy" = 20,
"bomb" = 20,
"bio" = 100,
"rad" = 100)
construct_spells = list(
/spell/aoe_turf/knock/harvester,
/spell/targeted/construct_advanced/inversion_beam,
/spell/targeted/construct_advanced/agonizing_sphere,
/spell/rune_write
)
////////////////Glow//////////////////
/mob/living/simple_animal/construct/proc/add_glow()
var/image/eye_glow = image(icon,"glow-[icon_state]")
eye_glow.plane = PLANE_LIGHTING_ABOVE
overlays += eye_glow
set_light(2, -2, l_color = "#FFFFFF")
/mob/living/simple_animal/construct/proc/remove_glow()
overlays.Cut()
////////////////HUD//////////////////////
/mob/living/simple_animal/construct/Life()
. = ..()
if(.)
if(fire)
if(fire_alert) fire.icon_state = "fire1"
else fire.icon_state = "fire0"
if(pullin)
if(pulling) pullin.icon_state = "pull1"
else pullin.icon_state = "pull0"
if(purged)
if(purge > 0) purged.icon_state = "purge1"
else purged.icon_state = "purge0"
silence_spells(purge)
/mob/living/simple_animal/construct/updatehealth() //Special icons.
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 = "[construct_type]_health0"
if(80 to 100)
healths.icon_state = "[construct_type]_health1"
if(60 to 80)
healths.icon_state = "[construct_type]_health2"
if(40 to 60)
healths.icon_state = "[construct_type]_health3"
if(20 to 40)
healths.icon_state = "[construct_type]_health4"
if(0 to 20)
healths.icon_state = "[construct_type]_health5"
else
healths.icon_state = "[construct_type]_health6"
else
healths.icon_state = "[construct_type]_health7"
@@ -1,242 +0,0 @@
/obj/item/device/soulstone/cultify()
return
/obj/item/device/soulstone
name = "Soul Stone Shard"
icon = 'icons/obj/wizard.dmi'
icon_state = "soulstone"
item_state = "electronic"
desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artefacts power."
w_class = ITEMSIZE_SMALL
slot_flags = SLOT_BELT
origin_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 4)
var/imprinted = "empty"
var/possible_constructs = list("Juggernaut","Wraith","Artificer","Harvester")
//////////////////////////////Capturing////////////////////////////////////////////////////////
/obj/item/device/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob)
if(!istype(M, /mob/living/carbon/human))//If target is not a human.
return ..()
if(istype(M, /mob/living/carbon/human/dummy))
return..()
if(jobban_isbanned(M, "cultist"))
user << "<span class='warning'>This person's soul is too corrupt and cannot be captured!</span>"
return..()
if(M.has_brain_worms()) //Borer stuff - RR
user << "<span class='warning'>This being is corrupted by an alien intelligence and cannot be soul trapped.</span>"
return..()
add_attack_logs(user,M,"Soulstone'd with [src.name]")
transfer_soul("VICTIM", M, user)
return
///////////////////Options for using captured souls///////////////////////////////////////
/obj/item/device/soulstone/attack_self(mob/user)
if (!in_range(src, user))
return
user.set_machine(src)
var/dat = "<TT><B>Soul Stone</B><BR>"
for(var/mob/living/simple_animal/shade/A in src)
dat += "Captured Soul: [A.name]<br>"
dat += {"<A href='byond://?src=\ref[src];choice=Summon'>Summon Shade</A>"}
dat += "<br>"
dat += {"<a href='byond://?src=\ref[src];choice=Close'> Close</a>"}
user << browse(dat, "window=aicard")
onclose(user, "aicard")
return
/obj/item/device/soulstone/Topic(href, href_list)
var/mob/U = usr
if (!in_range(src, U)||U.machine!=src)
U << browse(null, "window=aicard")
U.unset_machine()
return
add_fingerprint(U)
U.set_machine(src)
switch(href_list["choice"])//Now we switch based on choice.
if ("Close")
U << browse(null, "window=aicard")
U.unset_machine()
return
if ("Summon")
for(var/mob/living/simple_animal/shade/A in src)
A.status_flags &= ~GODMODE
A.canmove = 1
A << "<b>You have been released from your prison, but you are still bound to [U.name]'s will. Help them suceed in their goals at all costs.</b>"
A.forceMove(U.loc)
A.cancel_camera()
src.icon_state = "soulstone"
attack_self(U)
///////////////////////////Transferring to constructs/////////////////////////////////////////////////////
/obj/structure/constructshell
name = "empty shell"
icon = 'icons/obj/wizard.dmi'
icon_state = "construct"
desc = "A wicked machine used by those skilled in magical arts. It is inactive."
/obj/structure/constructshell/cultify()
return
/obj/structure/constructshell/cult
icon_state = "construct-cult"
desc = "This eerie contraption looks like it would come alive if supplied with a missing ingredient."
/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob)
if(istype(O, /obj/item/device/soulstone))
var/obj/item/device/soulstone/S = O;
S.transfer_soul("CONSTRUCT",src,user)
////////////////////////////Proc for moving soul in and out off stone//////////////////////////////////////
/obj/item/device/soulstone/proc/transfer_human(var/mob/living/carbon/human/T,var/mob/U)
if(!istype(T))
return;
if(src.imprinted != "empty")
U << "<span class='danger'>Capture failed!</span>: The soul stone has already been imprinted with [src.imprinted]'s mind!"
return
if ((T.health + T.halloss) > config.health_threshold_crit && T.stat != DEAD)
U << "<span class='danger'>Capture failed!</span>: Kill or maim the victim first!"
return
if(T.client == null)
U << "<span class='danger'>Capture failed!</span>: The soul has already fled it's mortal frame."
return
if(src.contents.len)
U << "<span class='danger'>Capture failed!</span>: The soul stone is full! Use or free an existing soul to make room."
return
for(var/obj/item/W in T)
T.drop_from_inventory(W)
new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton
T.invisibility = 101
var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc )
animation.icon_state = "blank"
animation.icon = 'icons/mob/mob.dmi'
animation.master = T
flick("dust-h", animation)
qdel(animation)
var/mob/living/simple_animal/shade/S = new /mob/living/simple_animal/shade( T.loc )
S.forceMove(src) //put shade in stone
S.status_flags |= GODMODE //So they won't die inside the stone somehow
S.canmove = 0//Can't move out of the soul stone
S.name = "Shade of [T.real_name]"
S.real_name = "Shade of [T.real_name]"
S.icon = T.icon
S.icon_state = T.icon_state
S.overlays = T.overlays
S.color = rgb(254,0,0)
S.alpha = 127
if (T.client)
T.client.mob = S
S.cancel_camera()
src.icon_state = "soulstone2"
src.name = "Soul Stone: [S.real_name]"
to_chat(S, "Your soul has been captured! You are now bound to [U.name]'s will, help them suceed in their goals at all costs.")
to_chat(U, "<span class='notice'>Capture successful!</span> : [T.real_name]'s soul has been ripped from their body and stored within the soul stone.")
to_chat(U, "The soulstone has been imprinted with [S.real_name]'s mind, it will no longer react to other souls.")
src.imprinted = "[S.name]"
qdel(T)
/obj/item/device/soulstone/proc/transfer_shade(var/mob/living/simple_animal/shade/T,var/mob/U)
if(!istype(T))
return;
if (T.stat == DEAD)
to_chat(U, "<span class='danger'>Capture failed!</span>: The shade has already been banished!")
return
if(src.contents.len)
to_chat(U, "<span class='danger'>Capture failed!</span>: The soul stone is full! Use or free an existing soul to make room.")
return
if(T.name != src.imprinted)
to_chat(U, "<span class='danger'>Capture failed!</span>: The soul stone has already been imprinted with [src.imprinted]'s mind!")
return
T.forceMove(src) //put shade in stone
T.status_flags |= GODMODE
T.canmove = 0
T.health = T.getMaxHealth()
src.icon_state = "soulstone2"
to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form")
to_chat(U, "<span class='notice'>Capture successful!</span> : [T.name]'s has been recaptured and stored within the soul stone.")
/obj/item/device/soulstone/proc/transfer_construct(var/obj/structure/constructshell/T,var/mob/U)
var/mob/living/simple_animal/shade/A = locate() in src
if(!A)
to_chat(U,"<span class='danger'>Capture failed!</span>: The soul stone is empty! Go kill someone!")
return;
var/construct_class = input(U, "Please choose which type of construct you wish to create.") as null|anything in possible_constructs
switch(construct_class)
if("Juggernaut")
var/mob/living/simple_animal/construct/armoured/Z = new /mob/living/simple_animal/construct/armoured (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
qdel(T)
to_chat(Z,"<B>You are playing a Juggernaut. Though slow, you can withstand extreme punishment, and rip apart enemies and walls alike.</B>")
to_chat(Z,"<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
Z.cancel_camera()
qdel(src)
if("Wraith")
var/mob/living/simple_animal/construct/wraith/Z = new /mob/living/simple_animal/construct/wraith (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
qdel(T)
to_chat(Z,"<B>You are playing a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.</B>")
to_chat(Z,"<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
Z.cancel_camera()
qdel(src)
if("Artificer")
var/mob/living/simple_animal/construct/builder/Z = new /mob/living/simple_animal/construct/builder (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
qdel(T)
to_chat(Z,"<B>You are playing an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, repair allied constructs (by clicking on them), and even create new constructs</B>")
to_chat(Z,"<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
Z.cancel_camera()
qdel(src)
if("Harvester")
var/mob/living/simple_animal/construct/harvester/Z = new /mob/living/simple_animal/construct/harvester (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
qdel(T)
to_chat(Z,"<B>You are playing a Harvester. You are relatively weak, but your physical frailty is made up for by your ranged abilities.</B>")
to_chat(Z,"<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
Z.cancel_camera()
qdel(src)
if("Behemoth")
var/mob/living/simple_animal/construct/behemoth/Z = new /mob/living/simple_animal/construct/behemoth (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
qdel(T)
to_chat(Z,"<B>You are playing a Behemoth. You are incredibly slow, though your slowness is made up for by the fact your shell is far larger than any of your bretheren. You are the Unstoppable Force, and Immovable Object.</B>")
to_chat(Z,"<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
Z.cancel_camera()
qdel(src)
/obj/item/device/soulstone/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob)
switch(choice)
if("VICTIM")
transfer_human(target,U)
if("SHADE")
transfer_shade(target,U)
if("CONSTRUCT")
transfer_construct(target,U)
@@ -1,61 +0,0 @@
//Look Sir, free head!
/mob/living/simple_animal/head
name = "CommandBattle AI"
desc = "A standard borg shell on its chest crude marking saying CommandBattle AI MK4 : Head."
icon_state = "crab"
icon_living = "crab"
icon_dead = "crab_dead"
intelligence_level = SA_ANIMAL
wander = 0
stop_automated_movement = 1
universal_speak = 1
turns_per_move = 5
response_help = "pets"
response_disarm = "gently pushes aside"
response_harm = "punches"
speak_chance = 1
speak_emote = list("clicks")
emote_hear = list("clicks")
emote_see = list("clacks")
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
var/list/insults = list(
"Man you suck",
"You look like the most retarded douche around",
"What's up?, oh wait nevermind you are a fucking asshat",
"you are just overly retarded",
"Whiteman said what?!",)
var/list/comments = list("Man have you seen those furry cats?,I mean who in the right mind would like something like that?",
"They call me abusive,I just like the truth",
"Beeboop, im a robit",
"Gooogooooll, break ya bones",
"Crab say what?",
"Man they say we have space lizards now, man this shit is getting more wack every minute",
"The so called \"improved\" station AI is just bullshit, that thing aint fun for noone",
"The Colony Director is a traitor, he took my power core.",
"Say \"what\" again. Say \"what\" again. I dare you. I double-dare you, motherfucker. Say \"what\" one more goddamn time.",
"Ezekiel 25:17 ,The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he who in the name of charity and good will shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who attempt to poison and destroy my brothers. And you will know my name is the Lord... when I lay my vengeance upon thee.",
"Did you notice a sign out in front of my house that said \"Dead Nigger Storage\"?")
/mob/living/simple_animal/head/Life()
. = ..()
if(!. || ai_inactive) return
for(var/mob/A in viewers(world.view,src))
if(A.ckey)
say_something(A)
/mob/living/simple_animal/head/proc/say_something(mob/A)
if(prob(85))
return
if(prob(30))
var/msg = pick(insults)
msg = "Hey, [A.name].. [msg]"
src.say(msg)
else
var/msg = pick(comments)
src.say(msg)
@@ -96,7 +96,7 @@
..()
playsound(src,'sound/mecha/mechstep.ogg',40,1)
// This is a PoI mob, not the normal, floaty drones that hang out around windows
/mob/living/simple_animal/hostile/mecha/malf_drone
name = "autonomous mechanized drone"
desc = "It appears to be an exosuit, piloted by a drone intelligence. It looks scary."
@@ -591,12 +591,6 @@
if(act)
..(act, type, desc)
/mob/living/simple_animal/proc/visible_emote(var/act_desc)
custom_emote(1, act_desc)
/mob/living/simple_animal/proc/audible_emote(var/act_desc)
custom_emote(2, act_desc)
/mob/living/simple_animal/bullet_act(var/obj/item/projectile/Proj)
ai_log("bullet_act() I was shot by: [Proj.firer]",2)
@@ -710,9 +704,9 @@
//SA vs SA basically
/mob/living/simple_animal/attack_generic(var/mob/attacker)
..()
if(attacker)
react_to_attack(attacker)
return ..()
/mob/living/simple_animal/movement_delay()
var/tally = 0 //Incase I need to add stuff other than "speed" later
@@ -1280,8 +1274,11 @@
/mob/living/simple_animal/proc/PunchTarget()
if(!Adjacent(target_mob))
return
if(!client)
sleep(rand(melee_attack_minDelay, melee_attack_maxDelay))
if(!canClick())
return
setClickCooldown(get_attack_speed())
// if(!client)
// sleep(rand(melee_attack_minDelay, melee_attack_maxDelay))
if(isliving(target_mob))
var/mob/living/L = target_mob
@@ -1322,13 +1319,18 @@
//The actual top-level ranged attack proc
/mob/living/simple_animal/proc/ShootTarget()
if(!canClick())
return FALSE
setClickCooldown(get_attack_speed())
var/target = target_mob
var/tturf = get_turf(target)
if((firing_lines && !client) && !CheckFiringLine(tturf))
step_rand(src)
face_atom(tturf)
return 0
return FALSE
visible_message("<span class='danger'><b>[src]</b> fires at [target]!</span>")
if(rapid)
@@ -1349,7 +1351,7 @@
if(casingtype)
new casingtype
return 1
return TRUE
//Check firing lines for faction_friends (if we're not cooperative, we don't care)
/mob/living/simple_animal/proc/CheckFiringLine(var/turf/tturf)
@@ -1582,13 +1584,13 @@
return 0
else
return armorval
/*
// Force it to target something
/mob/living/simple_animal/proc/taunt(var/mob/living/new_target, var/forced = FALSE)
if(intelligence_level == SA_HUMANOID && !forced)
return
set_target(new_target)
*/
/mob/living/simple_animal/is_sentient()
return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC
@@ -17,8 +17,8 @@
maxHealth = 150
var/maxHealth_adult = 200
melee_damage_lower = 5
melee_damage_upper = 25
melee_damage_lower = 10
melee_damage_upper = 15
melee_miss_chance = 0
gender = NEUTER
@@ -0,0 +1,76 @@
/mob/living/simple_mob/update_icon()
. = ..()
cut_overlays()
// var/mutable_appearance/ma = new(src)
// ma.layer = layer
// ma.plane = plane
add_overlay(modifier_overlay)
if(!icon_living) // Prevent the mob from turning invisible if icon_living is null.
icon_living = initial(icon_state)
//Awake and normal
if((stat == CONSCIOUS) && (!icon_rest || !resting || !incapacitated(INCAPACITATION_DISABLED) ))
icon_state = icon_living
//Dead
else if(stat >= DEAD)
icon_state = icon_dead
//Resting or KO'd
else if(((stat == UNCONSCIOUS) || resting || incapacitated(INCAPACITATION_DISABLED) ) && icon_rest)
icon_state = icon_rest
//Backup
else
icon_state = initial(icon_state)
if(has_hands)
if(r_hand_sprite)
add_overlay(r_hand_sprite)
if(l_hand_sprite)
add_overlay(l_hand_sprite)
if(has_eye_glow)
if(icon_state != icon_living)
remove_eyes()
else
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)
cut_overlay(modifier_overlay)
modifier_overlay.cut_overlays()
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.add_overlay(I)
modifier_overlay = effects
add_overlay(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
add_overlay(eye_layer)
/mob/living/simple_mob/proc/remove_eyes()
cut_overlay(eye_layer)
/mob/living/simple_mob/gib()
..(icon_gib,1,icon) // we need to specify where the gib animation is stored
@@ -0,0 +1,236 @@
// Does a melee attack.
/mob/living/simple_mob/proc/attack_target(atom/A)
set waitfor = FALSE // For attack animations. Don't want the AI processor to get held up.
if(!A.Adjacent(src))
return FALSE
var/turf/their_T = get_turf(A)
face_atom(A)
if(melee_attack_delay)
// their_T.color = "#FF0000"
melee_pre_animation(A)
handle_attack_delay(A, melee_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
// Cooldown testing is done at click code (for players) and interface code (for AI).
setClickCooldown(get_attack_speed())
. = do_attack(A, their_T)
if(melee_attack_delay)
melee_post_animation(A)
// their_T.color = "#FFFFFF"
// This does the actual attack.
// This is a seperate proc for the purposes of attack animations.
// A is the thing getting attacked, T is the turf A is/was on when attack_target was called.
/mob/living/simple_mob/proc/do_attack(atom/A, turf/T)
face_atom(A)
var/missed = FALSE
if(!isturf(A) && !(A in T) ) // Turfs don't contain themselves so checking contents is pointless if we're targeting a turf.
missed = TRUE
else if(!T.AdjacentQuick(src))
missed = TRUE
if(missed) // Most likely we have a slow attack and they dodged it or we somehow got moved.
add_attack_logs(src, A, "Animal-attacked (dodged)", admin_notify = FALSE)
playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1)
visible_message(span("warning", "\The [src] misses their attack."))
return FALSE
var/damage_to_do = rand(melee_damage_lower, melee_damage_upper)
damage_to_do = apply_bonus_melee_damage(A, damage_to_do)
for(var/datum/modifier/M in modifiers)
if(!isnull(M.outgoing_melee_damage_percent))
damage_to_do *= M.outgoing_melee_damage_percent
if(isliving(A)) // Check defenses.
var/mob/living/L = A
if(prob(melee_miss_chance))
add_attack_logs(src, L, "Animal-attacked (miss)", admin_notify = FALSE)
do_attack_animation(src)
playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1)
return FALSE // We missed.
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(damage = damage_to_do, damage_source = src, attacker = src, def_zone = null, attack_text = "the attack"))
return FALSE // We were blocked.
if(apply_attack(A, damage_to_do))
apply_melee_effects(A)
if(attack_sound)
playsound(src, attack_sound, 75, 1)
return TRUE
// Generally used to do the regular attack.
// Override for doing special stuff with the direct result of the attack.
/mob/living/simple_mob/proc/apply_attack(atom/A, damage_to_do)
return A.attack_generic(src, damage_to_do, pick(attacktext))
// Override for special effects after a successful attack, like injecting poison or stunning the target.
/mob/living/simple_mob/proc/apply_melee_effects(atom/A)
return
// Override to modify the amount of damage the mob does conditionally.
// This must return the amount of outgoing damage.
// Note that this is done before mob modifiers scale the damage.
/mob/living/simple_mob/proc/apply_bonus_melee_damage(atom/A, damage_amount)
return damage_amount
//The actual top-level ranged attack proc
/mob/living/simple_mob/proc/shoot_target(atom/A)
set waitfor = FALSE
setClickCooldown(get_attack_speed())
face_atom(A)
if(ranged_attack_delay)
ranged_pre_animation(A)
handle_attack_delay(A, ranged_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
if(needs_reload)
if(reload_count >= reload_max)
try_reload()
return FALSE
visible_message("<span class='danger'><b>\The [src]</b> fires at \the [A]!</span>")
shoot(A, src.loc, src)
if(casingtype)
new casingtype(loc)
if(ranged_attack_delay)
ranged_post_animation(A)
return TRUE
//Shoot a bullet at someone (idk why user is an argument when src would fit???)
/mob/living/simple_mob/proc/shoot(atom/A, turf/start, mob/living/user, bullet = 0)
if(A == start)
return
face_atom(A)
var/obj/item/projectile/P = new projectiletype(user.loc)
if(!P)
return
// If the projectile has its own sound, use it.
// Otherwise default to the mob's firing sound.
playsound(user, P.fire_sound ? P.fire_sound : projectilesound, 80, 1)
P.launch(A)
if(needs_reload)
reload_count++
// if(distance >= special_attack_min_range && distance <= special_attack_max_range)
// return TRUE
/mob/living/simple_mob/proc/try_reload()
set waitfor = FALSE
set_AI_busy(TRUE)
if(do_after(src, reload_time))
if(reload_sound)
playsound(src.loc, reload_sound, 50, 1)
reload_count = 0
. = TRUE
else
. = FALSE
set_AI_busy(FALSE)
// Can we currently do a special attack?
/mob/living/simple_mob/proc/can_special_attack(atom/A)
// Validity check.
if(!istype(A))
return FALSE
// Ability check.
if(isnull(special_attack_min_range) || isnull(special_attack_max_range))
return FALSE
// Distance check.
var/distance = get_dist(src, A)
if(distance < special_attack_min_range || distance > special_attack_max_range)
return FALSE
// Cooldown check.
if(!isnull(special_attack_cooldown) && last_special_attack + special_attack_cooldown > world.time)
return FALSE
// Charge check.
if(!isnull(special_attack_charges) && special_attack_charges <= 0)
return FALSE
return TRUE
// Should we do one? Used to make the AI not waste their special attacks. Only checked for AI. Players are free to screw up on their own.
/mob/living/simple_mob/proc/should_special_attack(atom/A)
return TRUE
// Special attacks, like grenades or blinding spit or whatever.
// Don't override this, override do_special_attack() for your blinding spit/etc.
/mob/living/simple_mob/proc/special_attack_target(atom/A)
face_atom(A)
if(special_attack_delay)
special_pre_animation(A)
handle_attack_delay(A, special_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
last_special_attack = world.time
if(do_special_attack(A))
if(special_attack_charges)
special_attack_charges -= 1
. = TRUE
else
. = FALSE
if(special_attack_delay)
special_post_animation(A)
// Override this for the actual special attack.
/mob/living/simple_mob/proc/do_special_attack(atom/A)
return FALSE
// Sleeps the proc that called it for the correct amount of time.
// Also makes sure the AI doesn't do anything stupid in the middle of the delay.
/mob/living/simple_mob/proc/handle_attack_delay(atom/A, delay_amount)
set_AI_busy(TRUE)
// Click delay modifiers also affect telegraphing time.
// This means berserked enemies will leave less time to dodge.
var/true_attack_delay = delay_amount
for(var/datum/modifier/M in modifiers)
if(!isnull(M.attack_speed_percent))
true_attack_delay *= M.attack_speed_percent
setClickCooldown(true_attack_delay) // Insurance against a really long attack being longer than default click delay.
sleep(true_attack_delay)
set_AI_busy(FALSE)
// Override these four for special custom animations (like the GOLEM).
/mob/living/simple_mob/proc/melee_pre_animation(atom/A)
do_windup_animation(A, melee_attack_delay)
/mob/living/simple_mob/proc/melee_post_animation(atom/A)
/mob/living/simple_mob/proc/ranged_pre_animation(atom/A)
do_windup_animation(A, ranged_attack_delay) // Semi-placeholder.
/mob/living/simple_mob/proc/ranged_post_animation(atom/A)
/mob/living/simple_mob/proc/special_pre_animation(atom/A)
do_windup_animation(A, special_attack_delay) // Semi-placeholder.
/mob/living/simple_mob/proc/special_post_animation(atom/A)
@@ -0,0 +1,224 @@
// 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))
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 //???
. = ..()
// 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()
// Cold stuff.
/mob/living/simple_mob/get_cold_protection()
return cold_resist
// 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
/mob/living/simple_mob/get_heat_protection()
return heat_resist
// 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()
/mob/living/simple_mob/get_shock_protection()
return shock_resist
// 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))
// Water
/mob/living/simple_mob/get_water_protection()
return water_resist
// "Poison" (aka what reagents would do if we wanted to deal with those).
/mob/living/simple_mob/get_poison_protection()
return poison_resist
// 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
// Lightning
/mob/living/simple_mob/lightning_act()
..()
// If a non-player simple_mob was struck, inflict huge damage.
// If the damage is fatal, it is turned to ash.
if(!client)
inflict_shock_damage(200) // Mobs that are very beefy or resistant to shock may survive getting struck.
updatehealth()
if(health <= 0)
visible_message(span("critical", "\The [src] disintegrates into ash!"))
ash()
return // No point deafening something that wont exist.
// Injections.
/mob/living/simple_mob/can_inject(mob/user, error_msg, target_zone, ignore_thickness)
if(ignore_thickness)
return TRUE
return !thick_armor
+143
View File
@@ -0,0 +1,143 @@
// 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_hands(var/obj/item/W) // No hands.
if(has_hands)
put_in_active_hand(W)
return 1
W.forceMove(get_turf(src))
return 1
//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
/mob/living/simple_mob/proc/IsHumanoidToolUser(var/atom/tool)
if(!humanoid_hands)
var/display_name = null
if(tool)
display_name = tool
else
display_name = "object"
to_chat(src, "<span class='danger'>Your [hand_form] are not fit for use of \the [display_name].</span>")
return humanoid_hands
/mob/living/simple_mob/drop_from_inventory(var/obj/item/W, var/atom/target = null)
. = ..(W, target)
if(!target)
target = src.loc
if(.)
W.forceMove(src.loc)
+160
View File
@@ -0,0 +1,160 @@
/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() - getFireLoss() - getBruteLoss() - getToxLoss() - getOxyLoss() - getCloneLoss()
//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
adjustFireLoss(cold_damage_per_tick)
if(fire)
fire.icon_state = "fire1"
else if(bodytemperature > maxbodytemp)
fire_alert = 1
adjustFireLoss(heat_damage_per_tick)
if(fire)
fire.icon_state = "fire2"
else
fire_alert = 0
if(fire)
fire.icon_state = "fire0"
if(atmos_unsuitable)
adjustOxyLoss(unsuitable_atoms_damage)
if(oxygen)
oxygen.icon_state = "oxy1"
else if(oxygen)
if(oxygen)
oxygen.icon_state = "oxy0"
adjustOxyLoss(-unsuitable_atoms_damage)
/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,48 @@
/*
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)] \the [A]!")
if(I_HURT)
if(can_special_attack(A) && special_attack_target(A))
return
else if(melee_damage_upper == 0 && istype(A,/mob/living))
custom_emote(1,"[pick(friendly)] \the [A]!")
else
attack_target(A)
if(I_GRAB)
if(has_hands)
A.attack_hand(src)
else
attack_target(A)
if(I_DISARM)
if(has_hands)
A.attack_hand(src)
else
attack_target(A)
/mob/living/simple_mob/RangedAttack(var/atom/A)
// setClickCooldown(get_attack_speed())
if(can_special_attack(A) && special_attack_target(A))
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,290 @@
// 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
// 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
//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/humanoid_hands = 0 // Can a player in this mob use things like guns or AI cards?
var/hand_form = "hands" // Used in IsHumanoidToolUser. 'Your X are not fit-'.
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.
var/turn_sound = null // If set, plays the sound when the mob's dir changes in most cases.
var/movement_shake_radius = 0 // If set, moving will shake the camera of all living mobs within this radius slightly.
//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
// Reloading settings, part of ranged code
var/needs_reload = FALSE // If TRUE, mob needs to reload occasionally
var/reload_max = 1 // How many shots the mob gets before it has to reload, will not be used if needs_reload is FALSE
var/reload_count = 0 // A counter to keep track of how many shots the mob has fired so far. Reloads when it hits reload_max.
var/reload_time = 1 SECONDS // How long it takes for a mob to reload. This is to buy a player a bit of time to run or fight.
var/reload_sound = 'sound/weapons/flipblade.ogg' // What sound gets played when the mob successfully reloads. Defaults to the same sound as reloading guns. Can be null.
//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 = 0 // 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 = FALSE // Is the attack sharp?
var/attack_edge = FALSE // Does the attack have an edge?
var/melee_attack_delay = null // If set, the mob will do a windup animation and can miss if the target moves out of the way.
var/ranged_attack_delay = null
var/special_attack_delay = null
//Special attacks
// var/special_attack_prob = 0 // The chance to ATTEMPT a special_attack_target(). If it fails, it will do a regular attack instead.
// This is commented out to ease the AI attack logic by being (a bit more) determanistic.
// You should instead limit special attacks using the below vars instead.
var/special_attack_min_range = null // The minimum distance required for an attempt to be made.
var/special_attack_max_range = null // The maximum for an attempt.
var/special_attack_charges = null // If set, special attacks will work off of a charge system, and won't be usable if all charges are expended. Good for grenades.
var/special_attack_cooldown = null // If set, special attacks will have a cooldown between uses.
var/last_special_attack = null // world.time when a special attack occured last, for cooldown calculations.
//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
)
// Protection against heat/cold/electric/water effects.
// 0 is no protection, 1 is total protection. Negative numbers increase vulnerability.
var/heat_resist = 0.0
var/cold_resist = 0.0
var/shock_resist = 0.0
var/water_resist = 1.0
var/poison_resist = 0.0
var/thick_armor = FALSE // Stops injections and "injections".
var/purge = 0 // Cult stuff.
var/supernatural = FALSE // Ditto.
/mob/living/simple_mob/initialize()
verbs -= /mob/verb/observe
health = maxHealth
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 ..()
/mob/living/simple_mob/death()
update_icon()
..()
//Client attached
/mob/living/simple_mob/Login()
. = ..()
to_chat(src,"<b>You are \the [src].</b> [player_msg]")
/mob/living/simple_mob/emote(var/act, var/type, var/desc)
if(act)
..(act, type, desc)
/mob/living/simple_mob/SelfMove(turf/n, direct)
var/turf/old_turf = get_turf(src)
var/old_dir = dir
. = ..()
if(. && movement_shake_radius)
for(var/mob/living/L in range(movement_shake_radius, src))
shake_camera(L, 1, 1)
if(turn_sound && dir != old_dir)
playsound(src, turn_sound, 50, 1)
else if(movement_sound && old_turf != get_turf(src)) // Playing both sounds at the same time generally sounds bad.
playsound(src, movement_sound, 50, 1)
/*
/mob/living/simple_mob/set_dir(new_dir)
if(dir != new_dir)
playsound(src, turn_sound, 50, 1)
return ..()
*/
/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
// 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
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
// 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|MOB_CLASS_SLIME // Update this if needed.
/mob/living/simple_mob/get_nametag_desc(mob/user)
return "<i>[tt_desc]</i>"
@@ -0,0 +1,9 @@
/mob/living/simple_mob/animal
mob_class = MOB_CLASS_ANIMAL
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
response_help = "pets"
response_disarm = "shoos"
response_harm = "hits"
ai_holder_type = /datum/ai_holder/simple_mob/melee
@@ -0,0 +1,237 @@
// Borers are probably still going to be buggy as fuck, this is just bringing their mob defines up to the new system.
// IMO they're a relic of several ages we're long past, their code and their design showing this plainly, but removing them would
// make certain people Unhappy so here we are. They need a complete redesign but thats beyond the scope of the rewrite.
/mob/living/simple_mob/animal/borer
name = "cortical borer"
desc = "A small, quivering sluglike creature."
icon_state = "brainslug"
item_state = "brainslug"
icon_living = "brainslug"
icon_dead = "brainslug_dead"
response_help = "pokes"
response_disarm = "prods"
response_harm = "stomps on"
attacktext = list("nipped")
friendly = list("prods")
status_flags = CANPUSH
pass_flags = PASSTABLE
movement_cooldown = 5
universal_understand = TRUE
can_be_antagged = TRUE
holder_type = /obj/item/weapon/holder/borer
ai_holder_type = null // This is player-controlled, always.
var/chemicals = 10 // A resource used for reproduction and powers.
var/mob/living/carbon/human/host = null // The humanoid host for the brain worm.
var/true_name = null // String used when speaking among other worms.
var/mob/living/captive_brain/host_brain // Used for swapping control of the body back and forth.
var/controlling = FALSE // Used in human death ceck.
var/docile = FALSE // Sugar can stop borers from acting.
var/has_reproduced = FALSE
var/roundstart = FALSE // If true, spawning won't try to pull a ghost.
var/used_dominate // world.time when the dominate power was last used.
/mob/living/simple_mob/animal/borer/roundstart
roundstart = TRUE
/mob/living/simple_mob/animal/borer/Login()
..()
if(mind)
borers.add_antagonist(mind)
/mob/living/simple_mob/animal/borer/initialize()
add_language("Cortical Link")
verbs += /mob/living/proc/ventcrawl
verbs += /mob/living/proc/hide
true_name = "[pick("Primary","Secondary","Tertiary","Quaternary")] [rand(1000,9999)]"
if(!roundstart)
request_player()
return ..()
/mob/living/simple_mob/animal/borer/handle_special()
if(host && !stat && !host.stat)
// Handle docility.
if(host.reagents.has_reagent("sugar") && !docile)
var/message = "You feel the soporific flow of sugar in your host's blood, lulling you into docility."
var/target = controlling ? host : src
to_chat(target, span("warning", message))
docile = TRUE
else if(docile)
var/message = "You shake off your lethargy as the sugar leaves your host's blood."
var/target = controlling ? host : src
to_chat(target, span("notice", message))
docile = FALSE
// Chem regen.
if(chemicals < 250)
chemicals++
// Control stuff.
if(controlling)
if(docile)
to_chat(host, span("warning", "You are feeling far too docile to continue controlling your host..."))
host.release_control()
return
if(prob(5))
host.adjustBrainLoss(0.1)
if(prob(host.brainloss/20))
host.say("*[pick(list("blink","blink_r","choke","aflap","drool","twitch","twitch_v","gasp"))]")
/mob/living/simple_mob/animal/borer/Stat()
..()
if(client.statpanel == "Status")
statpanel("Status")
if(emergency_shuttle)
var/eta_status = emergency_shuttle.get_status_panel_eta()
if(eta_status)
stat(null, eta_status)
stat("Chemicals", chemicals)
/mob/living/simple_mob/animal/borer/proc/detatch()
if(!host || !controlling)
return
if(istype(host, /mob/living/carbon/human))
var/mob/living/carbon/human/H = host
var/obj/item/organ/external/head = H.get_organ(BP_HEAD)
if(head)
head.implants -= src
controlling = FALSE
host.remove_language("Cortical Link")
host.verbs -= /mob/living/carbon/proc/release_control
host.verbs -= /mob/living/carbon/proc/punish_host
host.verbs -= /mob/living/carbon/proc/spawn_larvae
if(host_brain)
// these are here so bans and multikey warnings are not triggered on the wrong people when ckey is changed.
// computer_id and IP are not updated magically on their own in offline mobs -walter0o
// This shit need to die in a phoron fire.
// host -> self
var/h2s_id = host.computer_id
var/h2s_ip= host.lastKnownIP
host.computer_id = null
host.lastKnownIP = null
src.ckey = host.ckey
if(!src.computer_id)
src.computer_id = h2s_id
if(!host_brain.lastKnownIP)
src.lastKnownIP = h2s_ip
// brain -> host
var/b2h_id = host_brain.computer_id
var/b2h_ip= host_brain.lastKnownIP
host_brain.computer_id = null
host_brain.lastKnownIP = null
host.ckey = host_brain.ckey
if(!host.computer_id)
host.computer_id = b2h_id
if(!host.lastKnownIP)
host.lastKnownIP = b2h_ip
qdel(host_brain)
/mob/living/simple_mob/animal/borer/proc/leave_host()
if(!host)
return
if(host.mind)
borers.remove_antagonist(host.mind)
forceMove(get_turf(host))
reset_view(null)
machine = null
host.reset_view(null)
host.machine = null
host = null
/mob/living/simple_mob/animal/borer/proc/request_player()
var/datum/ghost_query/Q = new /datum/ghost_query/borer()
var/list/winner = Q.query() // This will sleep the proc for awhile.
if(winner.len)
var/mob/observer/dead/D = winner[1]
transfer_personality(D)
/mob/living/simple_mob/animal/borer/proc/transfer_personality(mob/candidate)
if(!candidate || !candidate.mind)
return
src.mind = candidate.mind
candidate.mind.current = src
ckey = candidate.ckey
if(mind)
mind.assigned_role = "Cortical Borer"
mind.special_role = "Cortical Borer"
to_chat(src, span("notice", "You are a cortical borer! You are a brain slug that worms its way \
into the head of its victim. Use stealth, persuasion and your powers of mind control to keep you, \
your host and your eventual spawn safe and warm."))
to_chat(src, "You can speak to your victim with <b>say</b>, to other borers with <b>say :x</b>, and use your Abilities tab to access powers.")
/mob/living/simple_mob/animal/borer/cannot_use_vents()
return
// This is awful but its literally say code.
/mob/living/simple_mob/animal/borer/say(message)
message = sanitize(message)
message = capitalize(message)
if(!message)
return
if(stat >= DEAD)
return say_dead(message)
else if(stat)
return
if(client && client.prefs.muted & MUTE_IC)
to_chat(src, span("danger", "You cannot speak in IC (muted)."))
return
if(copytext(message, 1, 2) == "*")
return emote(copytext(message, 2))
var/datum/language/L = parse_language(message)
if(L && L.flags & HIVEMIND)
L.broadcast(src,trim(copytext(message,3)), src.true_name)
return
if(!host)
//TODO: have this pick a random mob within 3 tiles to speak for the borer.
to_chat(src, span("warning", "You have no host to speak to."))
return //No host, no audible speech.
to_chat(src, "You drop words into [host]'s mind: \"[message]\"")
to_chat(host, "Your own thoughts speak: \"[message]\"")
for(var/mob/M in player_list)
if(istype(M, /mob/new_player))
continue
else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
to_chat(M, "[src.true_name] whispers to [host], \"[message]\"")
@@ -0,0 +1,59 @@
// Straight move from the old location, with the paths corrected.
/mob/living/captive_brain
name = "host brain"
real_name = "host brain"
universal_understand = 1
/mob/living/captive_brain/say(var/message)
if (src.client)
if(client.prefs.muted & MUTE_IC)
src << "<font color='red'>You cannot speak in IC (muted).</font>"
return
if(istype(src.loc, /mob/living/simple_mob/animal/borer))
message = sanitize(message)
if (!message)
return
log_say(message,src)
if (stat == 2)
return say_dead(message)
var/mob/living/simple_mob/animal/borer/B = src.loc
src << "You whisper silently, \"[message]\""
B.host << "The captive mind of [src] whispers, \"[message]\""
for (var/mob/M in player_list)
if (istype(M, /mob/new_player))
continue
else if(M.stat == DEAD && M.is_preference_enabled(/datum/client_preference/ghost_ears))
M << "The captive mind of [src] whispers, \"[message]\""
/mob/living/captive_brain/emote(var/message)
return
/mob/living/captive_brain/process_resist()
//Resisting control by an alien mind.
if(istype(src.loc, /mob/living/simple_mob/animal/borer))
var/mob/living/simple_mob/animal/borer/B = src.loc
var/mob/living/captive_brain/H = src
H << "<span class='danger'>You begin doggedly resisting the parasite's control (this will take approximately sixty seconds).</span>"
B.host << "<span class='danger'>You feel the captive mind of [src] begin to resist your control.</span>"
spawn(rand(200,250)+B.host.brainloss)
if(!B || !B.controlling) return
B.host.adjustBrainLoss(rand(0.1,0.5))
H << "<span class='danger'>With an immense exertion of will, you regain control of your body!</span>"
B.host << "<span class='danger'>You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.</span>"
B.detatch()
verbs -= /mob/living/carbon/proc/release_control
verbs -= /mob/living/carbon/proc/punish_host
verbs -= /mob/living/carbon/proc/spawn_larvae
return
..()
@@ -0,0 +1,354 @@
/mob/living/simple_mob/animal/borer/verb/release_host()
set category = "Abilities"
set name = "Release Host"
set desc = "Slither out of your host."
if(!host)
src << "You are not inside a host body."
return
if(stat)
src << "You cannot leave your host in your current state."
if(docile)
src << "<font color='blue'>You are feeling far too docile to do that.</font>"
return
if(!host || !src) return
src << "You begin disconnecting from [host]'s synapses and prodding at their internal ear canal."
if(!host.stat)
host << "An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."
spawn(100)
if(!host || !src) return
if(src.stat)
src << "You cannot release your host in your current state."
return
src << "You wiggle out of [host]'s ear and plop to the ground."
if(host.mind)
if(!host.stat)
host << "<span class='danger'>Something slimy wiggles out of your ear and plops to the ground!</span>"
host << "<span class='danger'>As though waking from a dream, you shake off the insidious mind control of the brain worm. Your thoughts are your own again.</span>"
detatch()
leave_host()
/mob/living/simple_mob/animal/borer/verb/infest()
set category = "Abilities"
set name = "Infest"
set desc = "Infest a suitable humanoid host."
if(host)
src << "You are already within a host."
return
if(stat)
src << "You cannot infest a target in your current state."
return
var/list/choices = list()
for(var/mob/living/carbon/C in view(1,src))
if(src.Adjacent(C))
choices += C
if(!choices.len)
src << "There are no viable hosts within range..."
return
var/mob/living/carbon/M = input(src,"Who do you wish to infest?") in null|choices
if(!M || !src) return
if(!(src.Adjacent(M))) return
if(M.has_brain_worms())
src << "You cannot infest someone who is already infested!"
return
if(istype(M,/mob/living/carbon/human))
var/mob/living/carbon/human/H = M
var/obj/item/organ/external/E = H.organs_by_name[BP_HEAD]
if(!E || E.is_stump())
src << "\The [H] does not have a head!"
if(!H.should_have_organ("brain"))
src << "\The [H] does not seem to have an ear canal to breach."
return
if(H.check_head_coverage())
src << "You cannot get through that host's protective gear."
return
M << "Something slimy begins probing at the opening of your ear canal..."
src << "You slither up [M] and begin probing at their ear canal..."
if(!do_after(src,30))
src << "As [M] moves away, you are dislodged and fall to the ground."
return
if(!M || !src) return
if(src.stat)
src << "You cannot infest a target in your current state."
return
if(M in view(1, src))
src << "You wiggle into [M]'s ear."
if(!M.stat)
M << "Something disgusting and slimy wiggles into your ear!"
src.host = M
src.forceMove(M)
//Update their traitor status.
if(host.mind)
borers.add_antagonist_mind(host.mind, 1, borers.faction_role_text, borers.faction_welcome)
if(istype(M,/mob/living/carbon/human))
var/mob/living/carbon/human/H = M
var/obj/item/organ/I = H.internal_organs_by_name["brain"]
if(!I) // No brain organ, so the borer moves in and replaces it permanently.
replace_brain()
else
// If they're in normally, implant removal can get them out.
var/obj/item/organ/external/head = H.get_organ(BP_HEAD)
head.implants += src
return
else
src << "They are no longer in range!"
return
/*
/mob/living/simple_mob/animal/borer/verb/devour_brain()
set category = "Abilities"
set name = "Devour Brain"
set desc = "Take permanent control of a dead host."
if(!host)
src << "You are not inside a host body."
return
if(host.stat != 2)
src << "Your host is still alive."
return
if(stat)
src << "You cannot do that in your current state."
if(docile)
src << "<font color='blue'>You are feeling far too docile to do that.</font>"
return
src << "<span class = 'danger'>It only takes a few moments to render the dead host brain down into a nutrient-rich slurry...</span>"
replace_brain()
*/
// BRAIN WORM ZOMBIES AAAAH.
/mob/living/simple_mob/animal/borer/proc/replace_brain()
var/mob/living/carbon/human/H = host
if(!istype(host))
src << "This host does not have a suitable brain."
return
src << "<span class = 'danger'>You settle into the empty brainpan and begin to expand, fusing inextricably with the dead flesh of [H].</span>"
H.add_language("Cortical Link")
if(host.stat == 2)
H.verbs |= /mob/living/carbon/human/proc/jumpstart
H.verbs |= /mob/living/carbon/human/proc/psychic_whisper
H.verbs |= /mob/living/carbon/human/proc/tackle
H.verbs |= /mob/living/carbon/proc/spawn_larvae
if(H.client)
H.ghostize(0)
if(src.mind)
src.mind.special_role = "Borer Husk"
src.mind.transfer_to(host)
H.ChangeToHusk()
var/obj/item/organ/internal/borer/B = new(H)
H.internal_organs_by_name["brain"] = B
H.internal_organs |= B
var/obj/item/organ/external/affecting = H.get_organ(BP_HEAD)
affecting.implants -= src
var/s2h_id = src.computer_id
var/s2h_ip= src.lastKnownIP
src.computer_id = null
src.lastKnownIP = null
if(!H.computer_id)
H.computer_id = s2h_id
if(!H.lastKnownIP)
H.lastKnownIP = s2h_ip
/mob/living/simple_mob/animal/borer/verb/secrete_chemicals()
set category = "Abilities"
set name = "Secrete Chemicals"
set desc = "Push some chemicals into your host's bloodstream."
if(!host)
src << "You are not inside a host body."
return
if(stat)
src << "You cannot secrete chemicals in your current state."
if(docile)
src << "<font color='blue'>You are feeling far too docile to do that.</font>"
return
if(chemicals < 50)
src << "You don't have enough chemicals!"
var/chem = input("Select a chemical to secrete.", "Chemicals") as null|anything in list("alkysine","bicaridine","hyperzine","tramadol")
if(!chem || chemicals < 50 || !host || controlling || !src || stat) //Sanity check.
return
src << "<font color='red'><B>You squirt a measure of [chem] from your reservoirs into [host]'s bloodstream.</B></font>"
host.reagents.add_reagent(chem, 10)
chemicals -= 50
/mob/living/simple_mob/animal/borer/verb/dominate_victim()
set category = "Abilities"
set name = "Paralyze Victim"
set desc = "Freeze the limbs of a potential host with supernatural fear."
if(world.time - used_dominate < 150)
src << "You cannot use that ability again so soon."
return
if(host)
src << "You cannot do that from within a host body."
return
if(src.stat)
src << "You cannot do that in your current state."
return
var/list/choices = list()
for(var/mob/living/carbon/C in view(3,src))
if(C.stat != 2)
choices += C
if(world.time - used_dominate < 150)
src << "You cannot use that ability again so soon."
return
var/mob/living/carbon/M = input(src,"Who do you wish to dominate?") in null|choices
if(!M || !src) return
if(M.has_brain_worms())
src << "You cannot infest someone who is already infested!"
return
src << "<font color='red'>You focus your psychic lance on [M] and freeze their limbs with a wave of terrible dread.</font>"
M << "<font color='red'>You feel a creeping, horrible sense of dread come over you, freezing your limbs and setting your heart racing.</font>"
M.Weaken(10)
used_dominate = world.time
/mob/living/simple_mob/animal/borer/verb/bond_brain()
set category = "Abilities"
set name = "Assume Control"
set desc = "Fully connect to the brain of your host."
if(!host)
src << "You are not inside a host body."
return
if(src.stat)
src << "You cannot do that in your current state."
return
if(docile)
src << "<font color='blue'>You are feeling far too docile to do that.</font>"
return
src << "You begin delicately adjusting your connection to the host brain..."
spawn(100+(host.brainloss*5))
if(!host || !src || controlling)
return
else
src << "<font color='red'><B>You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.</B></font>"
host << "<font color='red'><B>You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.</B></font>"
host.add_language("Cortical Link")
// host -> brain
var/h2b_id = host.computer_id
var/h2b_ip= host.lastKnownIP
host.computer_id = null
host.lastKnownIP = null
qdel(host_brain)
host_brain = new(src)
host_brain.ckey = host.ckey
host_brain.name = host.name
if(!host_brain.computer_id)
host_brain.computer_id = h2b_id
if(!host_brain.lastKnownIP)
host_brain.lastKnownIP = h2b_ip
// self -> host
var/s2h_id = src.computer_id
var/s2h_ip= src.lastKnownIP
src.computer_id = null
src.lastKnownIP = null
host.ckey = src.ckey
if(!host.computer_id)
host.computer_id = s2h_id
if(!host.lastKnownIP)
host.lastKnownIP = s2h_ip
controlling = 1
host.verbs += /mob/living/carbon/proc/release_control
host.verbs += /mob/living/carbon/proc/punish_host
host.verbs += /mob/living/carbon/proc/spawn_larvae
return
/mob/living/carbon/human/proc/jumpstart()
set category = "Abilities"
set name = "Revive Host"
set desc = "Send a jolt of electricity through your host, reviving them."
if(stat != 2)
usr << "Your host is already alive."
return
verbs -= /mob/living/carbon/human/proc/jumpstart
visible_message("<span class='warning'>With a hideous, rattling moan, [src] shudders back to life!</span>")
rejuvenate()
restore_blood()
fixblood()
update_canmove()

Some files were not shown because too many files have changed in this diff Show More