mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-19 19:15:11 +01:00
Merges AI Branch into Master
This commit is contained in:
+13
-13
@@ -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
|
||||
|
||||
@@ -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]")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
*/
|
||||
@@ -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 ..()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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--
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,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.
|
||||
@@ -8,4 +8,9 @@
|
||||
update_antag_icons(mind)
|
||||
client.screen |= global_hud.darksight
|
||||
client.images |= dsoverlay
|
||||
|
||||
if(ai_holder && !ai_holder.autopilot)
|
||||
ai_holder.go_sleep()
|
||||
to_chat(src,"<span class='notice'>Mob AI disabled while you are controlling the mob.</span>")
|
||||
|
||||
return .
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/mob/living/Logout()
|
||||
..()
|
||||
if (mind)
|
||||
if (mind)
|
||||
//Per BYOND docs key remains set if the player DCs, becomes null if switching bodies.
|
||||
if(!key) //key and mind have become seperated.
|
||||
if(!key) //key and mind have become seperated.
|
||||
mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body.
|
||||
|
||||
spawn(15 SECONDS) //15 seconds to get back into the mob before it goes wild
|
||||
if(src && !src.client)
|
||||
if(ai_holder)
|
||||
ai_holder.go_wake()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user