Merges AI Branch into Master

This commit is contained in:
Atermonera
2018-11-30 21:05:42 -08:00
committed by VirgoBot
parent 15017e893e
commit 2df5e9fe0e
379 changed files with 22460 additions and 4751 deletions

View File

@@ -22,9 +22,6 @@ mob/proc/airflow_stun()
mob/living/silicon/airflow_stun()
return
mob/living/simple_animal/slime/airflow_stun()
return
mob/living/carbon/human/airflow_stun()
if(shoes && (shoes.item_flags & NOSLIP))
to_chat(src, "<span class='notice'>Air suddenly rushes past you!</span>")

View File

@@ -58,7 +58,10 @@ What is the naming convention for planes or layers?
#define ATMOS_LAYER 2.4 // Pipe-like atmos machinery that goes on the floor, like filters.
#define ABOVE_UTILITY 2.5 // Above stuff like pipes and wires
#define TURF_PLANE -45 // Turfs themselves, most flooring
#define ABOVE_TURF_LAYER 2.1 // Snow and wallmounted/floormounted equipment
#define WATER_FLOOR_LAYER 2.0 // The 'bottom' of water tiles.
#define UNDERWATER_LAYER 2.5 // Anything on this layer will render under the water layer.
#define WATER_LAYER 3.0 // Layer for water overlays.
#define ABOVE_TURF_LAYER 3.1 // Snow and wallmounted/floormounted equipment
#define DECAL_PLANE -44 // Permanent decals
#define DIRTY_PLANE -43 // Nonpermanent decals
#define BLOOD_PLANE -42 // Blood is really dirty, but we can do special stuff if we separate it

View File

@@ -27,6 +27,9 @@
#define BORGXRAY 0x4
#define BORGMATERIAL 8
#define STANCE_ATTACK 11 // Backwards compatability
#define STANCE_ATTACKING 12 // Ditto
/*
#define STANCE_IDLE 1 // Looking for targets if hostile. Does idle wandering.
#define STANCE_ALERT 2 // Bears
#define STANCE_ATTACK 3 // Attempting to get into attack position
@@ -34,6 +37,20 @@
#define STANCE_TIRED 5 // Bears
#define STANCE_FOLLOW 6 // Following somone
#define STANCE_BUSY 7 // Do nothing on life ticks (Other code is running)
*/
#define STANCE_SLEEP 0 // Doing (almost) nothing, to save on CPU because nobody is around to notice or the mob died.
#define STANCE_IDLE 1 // The more or less default state. Wanders around, looks for baddies, and spouts one-liners.
#define STANCE_ALERT 2 // A baddie is visible but not too close, and essentially we tell them to go away or die.
#define STANCE_APPROACH 3 // Attempting to get into range to attack them.
#define STANCE_FIGHT 4 // Actually fighting, with melee or ranged.
#define STANCE_BLINDFIGHT 5 // Fighting something that cannot be seen by the mob, from invisibility or out of sight.
#define STANCE_REPOSITION 6 // Relocating to a better position while in combat. Also used when moving away from a danger like grenades.
#define STANCE_MOVE 7 // Similar to above but for out of combat. If a baddie is seen, they'll cancel and fight them.
#define STANCE_FOLLOW 8 // Following somone, without trying to murder them.
#define STANCE_FLEE 9 // Run away from the target because they're too spooky/we're dying/some other reason.
#define STANCE_DISABLED 10 // Used when the holder is afflicted with certain status effects, such as stuns or confusion.
#define STANCES_COMBAT list(STANCE_ALERT, STANCE_APPROACH, STANCE_FIGHT, STANCE_BLINDFIGHT, STANCE_REPOSITION)
#define LEFT 0x1
#define RIGHT 0x2
@@ -279,11 +296,34 @@
#define SA_ROBOTIC 3
#define SA_HUMANOID 4
// More refined version of SA_* ""intelligence"" seperators.
// Now includes bitflags, so to target two classes you just do 'MOB_CLASS_ANIMAL|MOB_CLASS_HUMANOID'
#define MOB_CLASS_NONE 0 // Default value, and used to invert for _ALL.
#define MOB_CLASS_PLANT 1 // Unused at the moment.
#define MOB_CLASS_ANIMAL 2 // Animals and beasts like spiders, saviks, and bears.
#define MOB_CLASS_HUMANOID 4 // Non-robotic humanoids, including /simple_mob and /carbon/humans and their alien variants.
#define MOB_CLASS_SYNTHETIC 8 // Silicons, mechanical simple mobs, FBPs, and anything else that would pass is_synthetic()
#define MOB_CLASS_SLIME 16 // Everyone's favorite xenobiology specimen (and maybe prometheans?).
#define MOB_CLASS_ABERRATION 32 // Weird shit.
#define MOB_CLASS_DEMONIC 64 // Cult stuff.
#define MOB_CLASS_BOSS 128 // Future megafauna hopefully someday.
#define MOB_CLASS_ILLUSION 256 // Fake mobs, e.g. Technomancer illusions.
#define MOB_CLASS_PHOTONIC 512 // Holographic mobs like holocarp, similar to _ILLUSION, but that make no attempt to hide their true nature.
#define MOB_CLASS_ALL (~MOB_CLASS_NONE)
// For slime commanding. Higher numbers allow for more actions.
#define SLIME_COMMAND_OBEY 1 // When disciplined.
#define SLIME_COMMAND_FACTION 2 // When in the same 'faction'.
#define SLIME_COMMAND_FRIEND 3 // When befriended with a slime friendship agent.
// Threshold for mobs being able to damage things like airlocks or reinforced glass windows.
// If the damage is below this, nothing will happen besides a message saying that the attack was ineffective.
// Generally, this was not a define but was commonly set to 10, however 10 may be too low now since simple_mobs now attack twice as fast,
// at half damage compared to the old mob system, meaning mobs who could hurt structures may not be able to now, so now it is 5.
#define STRUCTURE_MIN_DAMAGE_THRESHOLD 5
//Vision flags, for dealing with plane visibility
#define VIS_FULLBRIGHT 1
#define VIS_LIGHTING 2

View File

@@ -60,6 +60,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_OVERLAY -6
#define INIT_ORDER_XENOARCH -20
#define INIT_ORDER_CIRCUIT -21
#define INIT_ORDER_AI -22
// Subsystem fire priority, from lowest to highest priority
@@ -67,6 +68,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_SHUTTLES 5
#define FIRE_PRIORITY_ORBIT 8
#define FIRE_PRIORITY_VOTE 9
#define FIRE_PRIORITY_AI 10
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_AIRFLOW 30
#define FIRE_PRIORITY_AIR 35

View File

@@ -501,7 +501,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
moblist.Add(M)
for(var/mob/new_player/M in sortmob)
moblist.Add(M)
for(var/mob/living/simple_animal/M in sortmob)
for(var/mob/living/simple_mob/M in sortmob)
moblist.Add(M)
// for(var/mob/living/silicon/hivebot/M in sortmob)
// mob_list.Add(M)

View File

@@ -10,7 +10,7 @@
#define isalien(A) istype(A, /mob/living/carbon/alien)
#define isanimal(A) istype(A, /mob/living/simple_animal)
#define isanimal(A) istype(A, /mob/living/simple_mob)
#define isairlock(A) istype(A, /obj/machinery/door/airlock)
@@ -18,7 +18,7 @@
#define iscarbon(A) istype(A, /mob/living/carbon)
#define iscorgi(A) istype(A, /mob/living/simple_animal/corgi)
#define iscorgi(A) istype(A, /mob/living/simple_mob/animal/passive/dog/corgi)
#define isEye(A) istype(A, /mob/observer/eye)
@@ -26,7 +26,7 @@
#define isliving(A) istype(A, /mob/living)
#define ismouse(A) istype(A, /mob/living/simple_animal/mouse)
#define ismouse(A) istype(A, /mob/living/simple_mob/animal/passive/mouse)
#define isnewplayer(A) istype(A, /mob/new_player)
@@ -42,11 +42,11 @@
#define isvoice(A) istype(A, /mob/living/voice)
#define isslime(A) istype(A, /mob/living/simple_animal/slime)
#define isslime(A) istype(A, /mob/living/simple_mob/slime)
#define isbot(A) istype(A, /mob/living/bot)
#define isxeno(A) istype(A, /mob/living/simple_animal/xeno)
#define isxeno(A) istype(A, /mob/living/simple_mob/animal/space/alien)
#define isopenspace(A) istype(A, /turf/simulated/open)

View File

@@ -98,7 +98,7 @@
/obj/screen/fullscreen/flash
icon = 'icons/mob/screen1.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "flash"
icon_state = "flash_static"
/obj/screen/fullscreen/flash/noise
icon_state = "noise"

View File

@@ -316,8 +316,6 @@ datum/hud/New(mob/owner)
mymob.instantiate_hud(src)
else if(isalien(mymob))
larva_hud()
else if(isslime(mymob))
slime_hud()
else if(isAI(mymob))
ai_hud()
else if(isobserver(mymob))

View File

@@ -23,7 +23,7 @@
mymob.client.screen += list(blobpwrdisplay, blobhealthdisplay)
mymob.client.screen += mymob.client.void
/*
/datum/hud/proc/slime_hud(ui_style = 'icons/mob/screen1_Midnight.dmi')
src.adding = list()
@@ -92,10 +92,7 @@
mymob.client.screen += mymob.client.void
return
/mob/living/simple_animal/construct/instantiate_hud(var/datum/hud/HUD)
..(HUD)
*/
// HUD.construct_hud() //Archaic.
/*

View File

@@ -60,7 +60,7 @@ avoid code duplication. This includes items that may sometimes act as a standard
// Same as above but actually does useful things.
// W is the item being used in the attack, if any. modifier is if the attack should be longer or shorter than usual, for whatever reason.
/mob/living/get_attack_speed(var/obj/item/W)
var/speed = DEFAULT_ATTACK_COOLDOWN
var/speed = base_attack_cooldown
if(W && istype(W))
speed = W.attackspeed
for(var/datum/modifier/M in modifiers)

View File

@@ -68,58 +68,3 @@
*/
/mob/new_player/ClickOn()
return
/*
Animals
*/
/mob/living/simple_animal/UnarmedAttack(var/atom/A, var/proximity)
if(!(. = ..()))
return
setClickCooldown(get_attack_speed())
if(has_hands && istype(A,/obj) && a_intent != I_HURT)
var/obj/O = A
return O.attack_hand(src)
switch(a_intent)
if(I_HELP)
if(isliving(A))
custom_emote(1,"[pick(friendly)] [A]!")
if(I_HURT)
if(prob(spattack_prob))
if(spattack_min_range <= 1)
SpecialAtkTarget()
else if(melee_damage_upper == 0 && istype(A,/mob/living))
custom_emote(1,"[pick(friendly)] [A]!")
else
DoPunch(A)
if(I_GRAB)
if(has_hands)
A.attack_hand(src)
if(I_DISARM)
if(has_hands)
A.attack_hand(src)
/mob/living/simple_animal/RangedAttack(var/atom/A)
setClickCooldown(get_attack_speed())
var/distance = get_dist(src, A)
if(prob(spattack_prob) && (distance >= spattack_min_range) && (distance <= spattack_max_range))
target_mob = A
SpecialAtkTarget()
target_mob = null
return
if(ranged && distance <= shoot_range)
target_mob = A
ShootTarget(A)
target_mob = null

View File

@@ -0,0 +1,36 @@
SUBSYSTEM_DEF(ai)
name = "AI"
init_order = INIT_ORDER_AI
priority = FIRE_PRIORITY_AI
wait = 5 // This gets run twice a second, however this is technically two loops in one, with the second loop being run every four iterations.
flags = SS_NO_INIT|SS_TICKER
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/processing = list()
var/list/currentrun = list()
/datum/controller/subsystem/ai/stat_entry(msg_prefix)
var/list/msg = list(msg_prefix)
msg += "P:[processing.len]"
..(msg.Join())
/datum/controller/subsystem/ai/fire(resumed = 0)
if (!resumed)
src.currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
// var/mob/living/L = currentrun[currentrun.len]
var/datum/ai_holder/A = currentrun[currentrun.len]
--currentrun.len
if(!A || QDELETED(A)) // Doesn't exist or won't exist soon.
continue
if(times_fired % 4 == 0 && A.holder.stat != DEAD)
A.handle_strategicals()
if(A.holder.stat != DEAD) // The /TG/ version checks stat twice, presumably in-case processing somehow got the mob killed in that instant.
A.handle_tactics()
if(MC_TICK_CHECK)
return

View File

@@ -106,6 +106,13 @@
check_bans = list("AI", "Cyborg", "Syndicate")
cutoff_number = 1
/datum/ghost_query/borer
role_name = "Cortical Borer"
question = "A cortical borer has just been created on the facility. Would you like to play as them?"
be_special_flag = BE_ALIEN
check_bans = list("Syndicate", "Borer")
cutoff_number = 1
// Surface stuff.
/datum/ghost_query/lost_drone
role_name = "Lost Drone"

View File

@@ -502,7 +502,7 @@
if(!mind.assigned_role) mind.assigned_role = USELESS_JOB //defualt //VOREStation Edit - Visitor not Assistant
//slime
/mob/living/simple_animal/slime/mind_initialize()
/mob/living/simple_mob/slime/mind_initialize()
. = ..()
mind.assigned_role = "slime"
@@ -527,29 +527,30 @@
mind.special_role = ""
//Animals
/mob/living/simple_animal/mind_initialize()
/mob/living/simple_mob/mind_initialize()
. = ..()
mind.assigned_role = "Animal"
mind.assigned_role = "Simple Mob"
/mob/living/simple_animal/corgi/mind_initialize()
/mob/living/simple_mob/animal/passive/dog/corgi/mind_initialize()
. = ..()
mind.assigned_role = "Corgi"
/mob/living/simple_animal/shade/mind_initialize()
/mob/living/simple_mob/construct/shade/mind_initialize()
. = ..()
mind.assigned_role = "Shade"
mind.special_role = "Cultist"
/mob/living/simple_animal/construct/builder/mind_initialize()
/mob/living/simple_mob/construct/artificer/mind_initialize()
. = ..()
mind.assigned_role = "Artificer"
mind.special_role = "Cultist"
/mob/living/simple_animal/construct/wraith/mind_initialize()
/mob/living/simple_mob/construct/wraith/mind_initialize()
. = ..()
mind.assigned_role = "Wraith"
mind.special_role = "Cultist"
/mob/living/simple_animal/construct/armoured/mind_initialize()
/mob/living/simple_mob/construct/juggernaut/mind_initialize()
. = ..()
mind.assigned_role = "Juggernaut"
mind.special_role = "Cultist"

View File

@@ -5,7 +5,7 @@ var/datum/antagonist/borer/borers
role_type = BE_ALIEN
role_text = "Cortical Borer"
role_text_plural = "Cortical Borers"
mob_path = /mob/living/simple_animal/borer
mob_path = /mob/living/simple_mob/animal/borer
bantype = "Borer"
welcome_text = "Use your Infest power to crawl into the ear of a host and fuse with their brain. You can only take control temporarily, and at risk of hurting your host, so be clever and careful; your host is encouraged to help you however they can. Talk to your fellow borers with :x."
antag_indicator = "brainworm"
@@ -40,7 +40,7 @@ var/datum/antagonist/borer/borers
player.objectives += new /datum/objective/escape()
/datum/antagonist/borer/place_mob(var/mob/living/mob)
var/mob/living/simple_animal/borer/borer = mob
var/mob/living/simple_mob/animal/borer/borer = mob
if(istype(borer))
var/mob/living/carbon/human/host
for(var/mob/living/carbon/human/H in mob_list)

View File

@@ -111,12 +111,12 @@ var/datum/antagonist/cultist/cult
. = ..()
if(.)
player << "You catch a glimpse of the Realm of Nar-Sie, the Geometer of Blood. You now see how flimsy the world is, you see that it should be open to the knowledge of That Which Waits. Assist your new compatriots in their dark dealings. Their goals are yours, and yours are theirs. You serve the Dark One above all else. Bring It back."
if(player.current && !istype(player.current, /mob/living/simple_animal/construct))
if(player.current && !istype(player.current, /mob/living/simple_mob/construct))
player.current.add_language(LANGUAGE_CULT)
/datum/antagonist/cultist/remove_antagonist(var/datum/mind/player, var/show_message, var/implanted)
. = ..()
if(. && player.current && !istype(player.current, /mob/living/simple_animal/construct))
if(. && player.current && !istype(player.current, /mob/living/simple_mob/construct))
player.current.remove_language(LANGUAGE_CULT)
/datum/antagonist/cultist/can_become_antag(var/datum/mind/player)

View File

@@ -476,14 +476,14 @@ proc/findNullRod(var/atom/target)
/obj/item/weapon/spell/construct/run_checks()
if(owner)
if((iscultist(owner) || istype(owner, /mob/living/simple_animal/construct)) && (world.time >= (last_castcheck + cooldown))) //Are they a cultist or a construct, and has the cooldown time passed?
if((iscultist(owner) || istype(owner, /mob/living/simple_mob/construct)) && (world.time >= (last_castcheck + cooldown))) //Are they a cultist or a construct, and has the cooldown time passed?
last_castcheck = world.time
return 1
return 0
/obj/item/weapon/spell/construct/pay_energy(var/amount)
if(owner)
if(istype(owner, /mob/living/simple_animal/construct))
if(istype(owner, /mob/living/simple_mob/construct))
return 1
if(iscultist(owner) && pay_blood(amount))
return 1
@@ -607,7 +607,7 @@ proc/findNullRod(var/atom/target)
name = "sphere of agony"
desc = "Call forth a portal to a dimension of naught but pain at your target."
spawner_type = /obj/effect/temporary_effect/pulsar/agonizing_sphere
spawner_type = /obj/effect/temporary_effect/pulse/agonizing_sphere
/obj/item/weapon/spell/construct/spawner/agonizing_sphere/on_ranged_cast(atom/hit_atom, mob/user)
if(within_range(hit_atom) && pay_energy(10))
@@ -619,7 +619,7 @@ proc/findNullRod(var/atom/target)
var/mob/living/L = hit_atom
L.add_modifier(/datum/modifier/agonize, 10 SECONDS)
/obj/effect/temporary_effect/pulsar/agonizing_sphere
/obj/effect/temporary_effect/pulse/agonizing_sphere
name = "agonizing sphere"
desc = "A portal to some hellish place. Its screams wrack your body with pain.."
icon_state = "red_static_sphere"
@@ -628,19 +628,15 @@ proc/findNullRod(var/atom/target)
light_power = 5
light_color = "#FF0000"
pulses_remaining = 10
pulse_delay = 1 SECOND
/obj/effect/temporary_effect/pulsar/agonizing_sphere/pulse_loop()
while(pulses_remaining)
sleep(1 SECONDS)
spawn()
/obj/effect/temporary_effect/pulse/agonizing_sphere/on_pulse()
for(var/mob/living/L in view(4,src))
if(!iscultist(L) && !istype(L, /mob/living/simple_animal/construct))
if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct))
L.add_modifier(/datum/modifier/agonize, 2 SECONDS)
if(L.isSynthetic())
to_chat(L, "<span class='cult'>Your chassis warps as the [src] pulses!</span>")
L.adjustFireLoss(4)
pulses_remaining--
qdel(src)
//Artificer Heal
@@ -659,7 +655,7 @@ proc/findNullRod(var/atom/target)
L.add_modifier(/datum/modifier/mend_occult, 150)
qdel(src)
//Juggernaut + Behemoth Slam
//Juggernaut Slam
/obj/item/weapon/spell/construct/slam
name = "slam"
desc = "Empower your FIST, to send an opponent flying."
@@ -672,8 +668,8 @@ proc/findNullRod(var/atom/target)
/obj/item/weapon/spell/construct/slam/on_melee_cast(atom/hit_atom, mob/living/user, def_zone)
var/attack_message = "slams"
if(istype(user, /mob/living/simple_animal))
var/mob/living/simple_animal/S = user
if(istype(user, /mob/living/simple_mob))
var/mob/living/simple_mob/S = user
attack_message = pick(S.attacktext)
if(isliving(hit_atom))
var/mob/living/L = hit_atom

View File

@@ -14,7 +14,7 @@
return
/obj/item/weapon/melee/cultblade/attack(mob/living/M, mob/living/user, var/target_zone)
if(iscultist(user) && !istype(user, /mob/living/simple_animal/construct))
if(iscultist(user) && !istype(user, /mob/living/simple_mob/construct))
return ..()
var/zone = (user.hand ? "l_arm":"r_arm")
@@ -25,7 +25,7 @@
//random amount of damage between half of the blade's force and the full force of the blade.
user.apply_damage(rand(force/2, force), BRUTE, zone, 0, sharp=1, edge=1)
user.Weaken(5)
else if(!istype(user, /mob/living/simple_animal/construct))
else if(!istype(user, /mob/living/simple_mob/construct))
to_chat(user, "<span class='danger'>An inexplicable force rips through you, tearing the sword from your grasp!</span>")
else
to_chat(user, "<span class='critical'>The blade hisses, forcing itself from your manipulators. \The [src] will only allow mortals to wield it against foes, not kin.</span>")
@@ -39,10 +39,10 @@
return 1
/obj/item/weapon/melee/cultblade/pickup(mob/living/user as mob)
if(!iscultist(user) && !istype(user, /mob/living/simple_animal/construct))
if(!iscultist(user) && !istype(user, /mob/living/simple_mob/construct))
to_chat(user, "<span class='warning'>An overwhelming feeling of dread comes over you as you pick up the cultist's sword. It would be wise to be rid of this blade quickly.</span>")
user.make_dizzy(120)
if(istype(user, /mob/living/simple_animal/construct))
if(istype(user, /mob/living/simple_mob/construct))
to_chat(user, "<span class='warning'>\The [src] hisses, as it is discontent with your acquisition of it. It would be wise to return it to a worthy mortal quickly.</span>")
/obj/item/clothing/head/culthood

View File

@@ -108,18 +108,25 @@
light_range=5
light_color="#ff0000"
spawnable=list(
<<<<<<< HEAD
/mob/living/simple_animal/hostile/scarybat,
/mob/living/simple_animal/hostile/creature/vore,
/mob/living/simple_animal/hostile/faithless
) // Vorestation Edit
=======
/mob/living/simple_mob/animal/space/bats,
/mob/living/simple_mob/creature,
/mob/living/simple_mob/faithless
)
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
/obj/effect/gateway/active/cult
light_range=5
light_color="#ff0000"
spawnable=list(
/mob/living/simple_animal/hostile/scarybat/cult,
/mob/living/simple_animal/hostile/creature/cult,
/mob/living/simple_animal/hostile/faithless/cult
/mob/living/simple_mob/animal/space/bats/cult,
/mob/living/simple_mob/creature/cult,
/mob/living/simple_mob/faithless/cult
)
/obj/effect/gateway/active/cult/cultify()

View File

@@ -16,7 +16,7 @@
/mob/living/cultify()
if(iscultist(src) && client)
var/mob/living/simple_animal/construct/harvester/C = new(get_turf(src))
var/mob/living/simple_mob/construct/harvester/C = new(get_turf(src))
mind.transfer_to(C)
C << "<span class='sinister'>The Geometer of Blood is overjoyed to be reunited with its followers, and accepts your body in sacrifice. As reward, you have been gifted with the shell of an Harvester.<br>Your tendrils can use and draw runes without need for a tome, your eyes can see beings through walls, and your mind can open any door. Use these assets to serve Nar-Sie and bring him any remaining living human in the world.<br>You can teleport yourself back to Nar-Sie along with any being under yourself at any time using your \"Harvest\" spell.</span>"
dust()

View File

@@ -1,5 +1,6 @@
/obj/item/device/soulstone/cultify()
return
/////////////////////////
// Soulstone
/////////////////////////
/obj/item/device/soulstone
name = "Soul Stone Shard"
@@ -13,6 +14,9 @@
var/imprinted = "empty"
var/possible_constructs = list("Juggernaut","Wraith","Artificer","Harvester")
/obj/item/device/soulstone/cultify()
return
//////////////////////////////Capturing////////////////////////////////////////////////////////
/obj/item/device/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob)
@@ -40,7 +44,7 @@
return
user.set_machine(src)
var/dat = "<TT><B>Soul Stone</B><BR>"
for(var/mob/living/simple_animal/shade/A in src)
for(var/mob/living/simple_mob/construct/shade/A in src)
dat += "Captured Soul: [A.name]<br>"
dat += {"<A href='byond://?src=\ref[src];choice=Summon'>Summon Shade</A>"}
dat += "<br>"
@@ -69,7 +73,7 @@
return
if ("Summon")
for(var/mob/living/simple_animal/shade/A in src)
for(var/mob/living/simple_mob/construct/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>"
@@ -128,7 +132,7 @@
flick("dust-h", animation)
qdel(animation)
var/mob/living/simple_animal/shade/S = new /mob/living/simple_animal/shade( T.loc )
var/mob/living/simple_mob/construct/shade/S = new /mob/living/simple_mob/construct/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
@@ -152,7 +156,7 @@
src.imprinted = "[S.name]"
qdel(T)
/obj/item/device/soulstone/proc/transfer_shade(var/mob/living/simple_animal/shade/T,var/mob/U)
/obj/item/device/soulstone/proc/transfer_shade(var/mob/living/simple_mob/construct/shade/T,var/mob/U)
if(!istype(T))
return;
if (T.stat == DEAD)
@@ -175,14 +179,14 @@
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
var/mob/living/simple_mob/construct/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))
var/mob/living/simple_mob/construct/juggernaut/Z = new /mob/living/simple_mob/construct/juggernaut (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
@@ -192,7 +196,7 @@
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))
var/mob/living/simple_mob/construct/wraith/Z = new /mob/living/simple_mob/construct/wraith (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
@@ -202,7 +206,7 @@
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))
var/mob/living/simple_mob/construct/artificer/Z = new /mob/living/simple_mob/construct/artificer (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
@@ -212,7 +216,7 @@
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))
var/mob/living/simple_mob/construct/harvester/Z = new /mob/living/simple_mob/construct/harvester (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)
@@ -222,7 +226,7 @@
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))
var/mob/living/simple_mob/construct/juggernaut/behemoth/Z = new /mob/living/simple_mob/construct/juggernaut/behemoth (get_turf(T.loc))
Z.key = A.key
if(iscultist(U))
cult.add_antagonist(Z.mind)

View File

@@ -206,7 +206,7 @@ var/hadevent = 0
/proc/carp_migration() // -- Darem
for(var/obj/effect/landmark/C in landmarks_list)
if(C.name == "carpspawn")
new /mob/living/simple_animal/hostile/carp(C.loc)
new /mob/living/simple_mob/animal/space/carp(C.loc)
//sleep(100)
spawn(rand(300, 600)) //Delayed announcements to keep the crew on their toes.
command_announcement.Announce("Unknown biological entities have been detected near \the [station_name()], please stand-by.", "Lifesign Alert", new_sound = 'sound/AI/commandreport.ogg')

View File

@@ -9,7 +9,7 @@
/proc/ChristmasEvent()
for(var/obj/structure/flora/tree/pine/xmas in world)
var/mob/living/simple_animal/hostile/tree/evil_tree = new /mob/living/simple_animal/hostile/tree(xmas.loc)
var/mob/living/simple_mob/animal/space/tree/evil_tree = new /mob/living/simple_mob/animal/space/tree(xmas.loc)
evil_tree.icon_state = xmas.icon_state
evil_tree.icon_living = evil_tree.icon_state
evil_tree.icon_dead = evil_tree.icon_state

View File

@@ -808,7 +808,7 @@ datum/objective/heist/salvage
/datum/objective/borer_survive/check_completion()
if(owner)
var/mob/living/simple_animal/borer/B = owner
var/mob/living/simple_mob/animal/borer/B = owner
if(istype(B) && B.stat < 2 && B.host && B.host.stat < 2) return 1
return 0
@@ -817,7 +817,7 @@ datum/objective/heist/salvage
/datum/objective/borer_reproduce/check_completion()
if(owner && owner.current)
var/mob/living/simple_animal/borer/B = owner.current
var/mob/living/simple_mob/animal/borer/B = owner.current
if(istype(B) && B.has_reproduced) return 1
return 0

View File

@@ -1,258 +0,0 @@
//An AI-controlled 'companion' for the Technomancer. It's tough, strong, and can also use spells.
/mob/living/simple_animal/technomancer_golem
name = "G.O.L.E.M."
desc = "A rather unusual looking synthetic."
icon = 'icons/mob/mob.dmi'
icon_state = "technomancer_golem"
health = 250
maxHealth = 250
stop_automated_movement = 1
wander = 0
response_help = "pets"
response_disarm = "pushes away"
response_harm = "punches"
harm_intent_damage = 3
heat_damage_per_tick = 0
cold_damage_per_tick = 0
min_oxy = 0
max_oxy = 0
min_tox = 0
max_tox = 0
min_co2 = 0
max_co2 = 0
min_n2 = 0
max_n2 = 0
unsuitable_atoms_damage = 0
speed = 0
melee_damage_lower = 30 // It has a built in esword.
melee_damage_upper = 30
attack_sound = 'sound/weapons/blade1.ogg'
attacktext = list("slashed")
friendly = "hugs"
resistance = 0
melee_miss_chance = 0
var/obj/item/weapon/technomancer_core/golem/core = null
var/obj/item/weapon/spell/active_spell = null // Shield and ranged spells
var/mob/living/master = null
var/list/known_spells = list(
"beam" = /obj/item/weapon/spell/projectile/beam,
"chain lightning" = /obj/item/weapon/spell/projectile/chain_lightning,
"force missile" = /obj/item/weapon/spell/projectile/force_missile,
"ionic bolt" = /obj/item/weapon/spell/projectile/ionic_bolt,
"lightning" = /obj/item/weapon/spell/projectile/lightning,
"blink" = /obj/item/weapon/spell/blink,
"dispel" = /obj/item/weapon/spell/dispel,
"oxygenate" = /obj/item/weapon/spell/oxygenate,
"mend life" = /obj/item/weapon/spell/modifier/mend_life,
"mend synthetic" = /obj/item/weapon/spell/modifier/mend_synthetic,
"mend organs" = /obj/item/weapon/spell/mend_organs,
"purify" = /obj/item/weapon/spell/modifier/purify,
"resurrect" = /obj/item/weapon/spell/resurrect,
"passwall" = /obj/item/weapon/spell/passwall,
"repel missiles" = /obj/item/weapon/spell/modifier/repel_missiles,
"corona" = /obj/item/weapon/spell/modifier/corona,
"haste" = /obj/item/weapon/spell/modifier/haste
)
// Holds the overlays, when idle or attacking.
var/image/sword_image = null
var/image/spell_image = null
// These contain icon_states for each frame of an attack animation, which is swapped in and out manually, because BYOND.
// They are assoc lists, to hold the frame duration and the frame icon_state in one list.
var/list/spell_pre_attack_states = list(
"golem_spell_attack_1" = 1,
"golem_spell_attack_2" = 2,
"golem_spell_attack_3" = 2
)
var/list/spell_post_attack_states = list(
"golem_spell_attack_4" = 2,
"golem_spell_attack_5" = 3,
"golem_spell_attack_6" = 3
)
var/list/sword_pre_attack_states = list(
"golem_sword_attack_1" = 1,
"golem_sword_attack_2" = 5
)
var/list/sword_post_attack_states = list(
"golem_sword_attack_3" = 1,
"golem_sword_attack_4" = 3
)
/mob/living/simple_animal/technomancer_golem/New()
..()
core = new(src)
sword_image = image(icon, src, "golem_sword")
spell_image = image(icon, src, "golem_spell")
update_icon()
/mob/living/simple_animal/technomancer_golem/Destroy()
qdel(core)
qdel(sword_image)
qdel(spell_image)
return ..()
/mob/living/simple_animal/technomancer_golem/unref_spell()
active_spell = null
return ..()
/mob/living/simple_animal/hostile/hivebot/death()
..()
visible_message("\The [src] disintegrates!")
new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc)
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
s.set_up(3, 1, src)
s.start()
qdel(src)
/mob/living/simple_animal/technomancer_golem/update_icon()
overlays.Cut()
overlays += sword_image
overlays += spell_image
update_modifier_visuals()
// Unfortunately, BYOND does not let you flick() images or other overlays, so we need to do this in a terrible way.
/atom/proc/manual_flick(var/list/frames, var/image/I, var/reset_to = null)
// Swap in and out each frame manually.
for(var/frame in frames)
overlays -= I
I.icon_state = frame
overlays += I
sleep(frames[frame])
if(reset_to)
// One more time to reset it to what it was before.
overlays -= I
I.icon_state = reset_to
overlays += I
/mob/living/simple_animal/technomancer_golem/proc/spellcast_pre_animation()
setClickCooldown(5)
manual_flick(spell_pre_attack_states, spell_image, reset_to = "golem_spell_attack_3")
/mob/living/simple_animal/technomancer_golem/proc/spellcast_post_animation()
setClickCooldown(8)
manual_flick(spell_post_attack_states, spell_image, reset_to = "golem_spell")
/mob/living/simple_animal/technomancer_golem/proc/sword_pre_animation()
setClickCooldown(6)
manual_flick(sword_pre_attack_states, sword_image)
/mob/living/simple_animal/technomancer_golem/proc/sword_post_animation()
setClickCooldown(3)
manual_flick(sword_post_attack_states, sword_image, reset_to = "golem_sword")
/mob/living/simple_animal/technomancer_golem/DoPunch(var/atom/A)
sword_pre_animation()
. = ..() // This does the actual attack and will check adjacency again.
sword_post_animation()
/mob/living/simple_animal/technomancer_golem/isSynthetic()
return TRUE // So Mend Synthetic will work on them.
/mob/living/simple_animal/technomancer_golem/speech_bubble_appearance()
return "synthetic_evil"
/mob/living/simple_animal/technomancer_golem/place_spell_in_hand(var/path)
if(!path || !ispath(path))
return 0
if(active_spell)
qdel(active_spell) // Get rid of our old spell.
var/obj/item/weapon/spell/S = new path(src)
active_spell = S
/mob/living/simple_animal/technomancer_golem/verb/test_giving_spells()
var/choice = input(usr, "What spell?", "Give spell") as null|anything in known_spells
if(choice)
place_spell_in_hand(known_spells[choice])
else
qdel(active_spell)
// Used to cast spells.
/mob/living/simple_animal/technomancer_golem/RangedAttack(var/atom/A, var/params)
if(active_spell)
spellcast_pre_animation()
if(active_spell.cast_methods & CAST_RANGED)
active_spell.on_ranged_cast(A, src)
spellcast_post_animation()
/mob/living/simple_animal/technomancer_golem/UnarmedAttack(var/atom/A, var/proximity)
if(proximity)
if(active_spell)
spellcast_pre_animation()
if(!Adjacent(A)) // Need to check again since they might've moved while 'warming up'.
spellcast_post_animation()
return
var/effective_cooldown = round(active_spell.cooldown * core.cooldown_modifier, 5)
if(active_spell.cast_methods & CAST_MELEE)
active_spell.on_melee_cast(A, src)
else if(active_spell.cast_methods & CAST_RANGED)
active_spell.on_ranged_cast(A, src)
spellcast_post_animation()
src.setClickCooldown(effective_cooldown)
else
..()
/mob/living/simple_animal/technomancer_golem/get_technomancer_core()
return core
/mob/living/simple_animal/technomancer_golem/proc/bind_to_mob(mob/user)
if(!user || master)
return
master = user
name = "[master]'s [initial(name)]"
/mob/living/simple_animal/technomancer_golem/examine(mob/user)
..()
if(user.mind && technomancers.is_antagonist(user.mind))
user << "Your pride and joy. It's a very special synthetic robot, capable of using functions similar to you, and you built it \
yourself! It'll always stand by your side, ready to help you out. You have no idea what GOLEM stands for, however..."
/mob/living/simple_animal/technomancer_golem/Life()
..()
handle_ai()
// This is where the real spaghetti begins.
/mob/living/simple_animal/technomancer_golem/proc/handle_ai()
if(!master)
return
if(get_dist(src, master) > 6 || src.z != master.z)
targeted_blink(master)
// Give our allies buffs and heals.
for(var/mob/living/L in view(src))
if(L in friends)
support_friend(L)
return
/mob/living/simple_animal/technomancer_golem/proc/support_friend(var/mob/living/L)
if(L.getBruteLoss() >= 10 || L.getFireLoss() >= 10)
if(L.isSynthetic() && !L.has_modifier_of_type(/datum/modifier/technomancer/mend_synthetic))
place_spell_in_hand(known_spells["mend synthetic"])
targeted_blink(L)
UnarmedAttack(L, 1)
else if(!L.has_modifier_of_type(/datum/modifier/technomancer/mend_life))
place_spell_in_hand(known_spells["mend life"])
targeted_blink(L)
UnarmedAttack(L, 1)
return
// Give them repel missiles if they lack it.
if(!L.has_modifier_of_type(/datum/modifier/technomancer/repel_missiles))
place_spell_in_hand(known_spells["repel missiles"])
RangedAttack(L)
return
/mob/living/simple_animal/technomancer_golem/proc/targeted_blink(var/atom/target)
var/datum/effect/effect/system/spark_spread/spark_system = new()
spark_system.set_up(5, 0, get_turf(src))
spark_system.start()
src.visible_message("<span class='notice'>\The [src] vanishes!</span>")
src.forceMove(get_turf(target))
return

View File

@@ -130,7 +130,7 @@
for(var/mob/living/L in summoned_mobs)
summoned_mobs -= L
qdel(L)
for(var/mob/living/simple_animal/ward/ward in wards_in_use)
for(var/mob/living/ward in wards_in_use)
wards_in_use -= ward
qdel(ward)

View File

@@ -1,12 +1,17 @@
//Returns 1 if the turf is dense, or if there's dense objects on it, unless told to ignore them.
/turf/proc/check_density(var/ignore_objs = 0)
//Returns 1 if the turf is dense, or if there's dense objects/mobs on it, unless told to ignore them.
/turf/proc/check_density(var/ignore_objs = FALSE, var/ignore_mobs = FALSE)
if(density)
return 1
if(!ignore_objs)
return TRUE
if(!ignore_objs || !ignore_mobs)
for(var/atom/movable/stuff in contents)
if(stuff.density)
return 1
return 0
if(ignore_objs && isobj(stuff))
continue
else if(ignore_mobs && isliving(stuff)) // Ghosts aren't dense but keeping this limited to living type will probably save headaches in the future.
continue
else
return TRUE
return FALSE
// Used to distinguish friend from foe.
/obj/item/weapon/spell/proc/is_ally(var/mob/living/L)
@@ -14,9 +19,9 @@
return 1
if(L.mind && technomancers.is_antagonist(L.mind)) // This should be done better since we might want opposing technomancers later.
return 1
if(istype(L, /mob/living/simple_animal/hostile)) // Mind controlled simple mobs count as allies too.
var/mob/living/simple_animal/SA = L
if(owner in SA.friends)
if(istype(L, /mob/living/simple_mob)) // Mind controlled simple mobs count as allies too.
var/mob/living/simple_mob/SM = L
if(owner in SM.friends)
return 1
return 0

View File

@@ -16,12 +16,12 @@
/obj/item/weapon/spell/abjuration/on_ranged_cast(atom/hit_atom, mob/user)
if(istype(hit_atom, /mob/living) && pay_energy(500) && within_range(hit_atom))
var/mob/living/L = hit_atom
var/mob/living/simple_animal/SA = null
var/mob/living/simple_mob/SM = null
//Bit of a roundabout typecheck, in order to test for two variables from two different mob types in one line.
if(istype(L, /mob/living/simple_animal))
SA = L
if(L.summoned || (SA && SA.supernatural) )
if(istype(L, /mob/living/simple_mob))
SM = L
if(L.summoned || (SM && SM.supernatural) )
if(L.client) // Player-controlled mobs are immune to being killed by this.
user << "<span class='warning'>\The [L] resists your attempt to banish it!</span>"
L << "<span class='warning'>\The [user] tried to teleport you far away, but failed.</span>"
@@ -29,8 +29,8 @@
else
visible_message("<span class='notice'>\The [L] vanishes!</span>")
qdel(L)
else if(istype(L, /mob/living/simple_animal/construct))
var/mob/living/simple_animal/construct/evil = L
else if(istype(L, /mob/living/simple_mob/construct))
var/mob/living/simple_mob/construct/evil = L
evil << "<span class='danger'>\The [user]'s abjuration purges your form!</span>"
evil.purge = 3
adjust_instability(5)

View File

@@ -21,6 +21,7 @@
aspect = ASPECT_BIOMED //Not sure if this should be something else.
var/image/control_overlay = null
var/list/controlled_mobs = list()
<<<<<<< HEAD
var/list/allowed_mobs = list(
/mob/living/bot,
/mob/living/simple_animal/cat,
@@ -45,67 +46,70 @@
/mob/living/simple_animal/hostile/savik,
/mob/living/simple_animal/hostile/shantak
)
=======
var/allowed_mob_classes = MOB_CLASS_ANIMAL|MOB_CLASS_SYNTHETIC
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
//This unfortunately is gonna be rather messy due to the various mobtypes involved.
/obj/item/weapon/spell/control/proc/select(var/mob/living/L)
if(!(is_type_in_list(L, allowed_mobs)))
return 0
if(!(L.mob_class & allowed_mob_classes))
return FALSE
if(istype(L, /mob/living/simple_animal))
var/mob/living/simple_animal/SA = L
SA.ai_inactive = 1
SA.friends |= src.owner
SA.stance = STANCE_IDLE
if(!L.has_AI())
return FALSE
L.overlays |= control_overlay
var/datum/ai_holder/AI = L.ai_holder
AI.hostile = FALSE // The Technomancer chooses the target, not the AI.
AI.retaliate = TRUE
AI.wander = FALSE
AI.forget_everything()
if(istype(L, /mob/living/simple_mob))
var/mob/living/simple_mob/SM = L
SM.friends |= src.owner
L.add_overlay(control_overlay, TRUE)
controlled_mobs |= L
/obj/item/weapon/spell/control/proc/deselect(var/mob/living/L)
if(!(L in controlled_mobs))
return 0
return FALSE
if(istype(L, /mob/living/simple_animal))
var/mob/living/simple_animal/SA = L
SA.ai_inactive = 1
if(istype(SA, /mob/living/simple_animal/hostile))
var/mob/living/simple_animal/hostile/SAH = SA
SAH.friends.Remove(owner)
if(L.has_AI())
var/datum/ai_holder/AI = L.ai_holder
AI.hostile = initial(AI.hostile)
AI.retaliate = initial(AI.retaliate)
AI.wander = initial(AI.wander)
AI.forget_everything()
L.overlays.Remove(control_overlay)
if(istype(L, /mob/living/simple_mob))
var/mob/living/simple_mob/SM = L
SM.friends -= owner
L.cut_overlay(control_overlay, TRUE)
controlled_mobs.Remove(L)
/obj/item/weapon/spell/control/proc/move_all(turf/T)
for(var/mob/living/living in controlled_mobs)
if(living.stat)
deselect(living)
for(var/mob/living/L in controlled_mobs)
if(!L.has_AI() || L.stat)
deselect(L)
continue
if(istype(living, /mob/living/simple_animal))
var/mob/living/simple_animal/SA = living
SA.target_mob = null
SA.stance = STANCE_IDLE
walk_towards(SA,T,SA.speed)
else
walk_towards(living,T,5)
L.ai_holder.give_destination(T, 0, TRUE)
/obj/item/weapon/spell/control/proc/attack_all(mob/target)
for(var/mob/living/L in controlled_mobs)
if(L.stat)
if(!L.has_AI() || L.stat)
deselect(L)
continue
if(istype(L, /mob/living/simple_animal/hostile))
var/mob/living/simple_animal/hostile/SAH
SAH.target_mob = target
else if(istype(L, /mob/living/bot))
var/mob/living/bot/B = L
B.UnarmedAttack(L)
L.ai_holder.give_target(target)
/obj/item/weapon/spell/control/New()
/obj/item/weapon/spell/control/initialize()
control_overlay = image('icons/obj/spells.dmi',"controlled")
..()
return ..()
/obj/item/weapon/spell/control/Destroy()
for(var/mob/living/simple_animal/hostile/SM in controlled_mobs)
deselect(SM)
for(var/mob/living/L in controlled_mobs)
deselect(L)
controlled_mobs = list()
return ..()
@@ -127,11 +131,14 @@
trying to use it on yourself, perhaps you're an exception? Regardless, nothing happens.</span>"
return 0
if(is_type_in_list(L, allowed_mobs))
if(L.mob_class & allowed_mob_classes)
if(!(L in controlled_mobs)) //Selecting
if(L.client)
user << "<span class='danger'>\The [L] seems to resist you!</span>"
return 0
if(!L.has_AI())
to_chat(user, span("warning", "\The [L] seems too dim for this to work on them."))
return FALSE
if(pay_energy(500))
select(L)
user << "<span class='notice'>\The [L] is now under your (limited) control.</span>"

View File

@@ -75,9 +75,9 @@
for(var/mob/living/L in view(owner))
// Spiders, carp... bears.
if(istype(L, /mob/living/simple_animal))
var/mob/living/simple_animal/SM = L
if(!is_ally(SM) && SM.hostile)
if(istype(L, /mob/living/simple_mob))
var/mob/living/simple_mob/SM = L
if(!is_ally(SM) && SM.has_AI() && SM.ai_holder.hostile)
hostile_mobs++
if(SM.summoned || SM.supernatural) // Our creations might be trying to kill us.
potential_spells |= /obj/item/weapon/spell/abjuration

View File

@@ -14,7 +14,7 @@
aspect = ASPECT_LIGHT
cast_methods = CAST_RANGED | CAST_USE
var/atom/movable/copied = null
var/mob/living/simple_animal/illusion/illusion = null
var/mob/living/simple_mob/illusion/illusion = null
/obj/item/weapon/spell/illusion/on_ranged_cast(atom/hit_atom, mob/user)
if(istype(hit_atom, /atom/movable))
@@ -33,17 +33,13 @@
if(pay_energy(500))
illusion = new(T)
illusion.copy_appearance(copied)
if(ishuman(copied))
var/mob/living/carbon/human/H = copied
// This is to try to have the illusion move at the same rate the real mob world.
illusion.step_delay = max(H.movement_delay() + 4, 3)
user << "<span class='notice'>An illusion of \the [copied] is made on \the [T].</span>"
user << 'sound/effects/pop.ogg'
return 1
else
if(pay_energy(100))
spawn(1)
illusion.walk_loop(T)
var/datum/ai_holder/AI = illusion.ai_holder
AI.give_destination(T)
/obj/item/weapon/spell/illusion/on_use_cast(mob/user)
if(illusion)
@@ -76,116 +72,3 @@
temp_image.transform = M
// temp_image.pixel_y = 8
src.overlays.Add(temp_image)
/mob/living/simple_animal/illusion
name = "illusion" // gets overwritten
desc = "If you can read me, the game broke. Please report this to a coder."
resistance = 1000 // holograms are tough
wander = 0
response_help = "pushes a hand through"
response_disarm = "tried to disarm"
response_harm = "tried to punch"
var/atom/movable/copying = null
universal_speak = 1
var/realistic = 0
var/list/path = list() //Used for AStar pathfinding.
var/walking = 0
var/step_delay = 10
/mob/living/simple_animal/illusion/update_icon() // We don't want the appearance changing AT ALL unless by copy_appearance().
return
/mob/living/simple_animal/illusion/proc/copy_appearance(var/atom/movable/thing_to_copy)
if(!thing_to_copy)
return 0
name = thing_to_copy.name
desc = thing_to_copy.desc
gender = thing_to_copy.gender
appearance = thing_to_copy.appearance
copying = thing_to_copy
return 1
// We use special movement code for illusions, because BYOND's default pathfinding will use diagonal movement if it results
// in the shortest path. As players are incapable of moving in diagonals, we must do this or else illusions will not be convincing.
/mob/living/simple_animal/illusion/proc/calculate_path(var/turf/targeted_loc)
if(!path.len || !path)
spawn(0)
path = AStar(loc, targeted_loc, /turf/proc/CardinalTurfs, /turf/proc/Distance, 0, 10, id = null)
if(!path)
path = list()
return
/mob/living/simple_animal/illusion/proc/walk_path(var/turf/targeted_loc)
if(path && path.len)
step_to(src, path[1])
path -= path[1]
return
else
if(targeted_loc)
calculate_path(targeted_loc)
/mob/living/simple_animal/illusion/proc/walk_loop(var/turf/targeted_loc)
if(walking) //Already busy moving somewhere else.
return 0
walking = 1
calculate_path(targeted_loc)
if(!targeted_loc)
walking = 0
return 0
if(path.len == 0)
calculate_path(targeted_loc)
while(loc != targeted_loc)
walk_path(targeted_loc)
sleep(step_delay)
walking = 0
return 1
// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but
// this is to prevent easy checks from the opposing force.
/mob/living/simple_animal/illusion/examine(mob/user)
if(copying)
copying.examine(user)
return
..()
/mob/living/simple_animal/illusion/bullet_act(var/obj/item/projectile/P)
if(!P)
return
if(realistic)
return ..()
return PROJECTILE_FORCE_MISS
/mob/living/simple_animal/illusion/attack_hand(mob/living/carbon/human/M)
if(!realistic)
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
visible_message("<span class='warning'>[M]'s hand goes through \the [src]!</span>")
return
else
switch(M.a_intent)
if(I_HELP)
var/datum/gender/T = gender_datums[src.get_visible_gender()]
M.visible_message("<span class='notice'>[M] hugs [src] to make [T.him] feel better!</span>", \
"<span class='notice'>You hug [src] to make [T.him] feel better!</span>") // slightly redundant as at the moment most mobs still use the normal gender var, but it works and future-proofs it
playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
if(I_DISARM)
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
visible_message("<span class='danger'>[M] attempted to disarm [src]!</span>")
M.do_attack_animation(src)
if(I_GRAB)
..()
if(I_HURT)
adjustBruteLoss(harm_intent_damage)
M.visible_message("<font color='red'>[M] [response_harm] \the [src]</font>")
M.do_attack_animation(src)
return
/mob/living/simple_animal/illusion/ex_act()
return

View File

@@ -15,11 +15,13 @@
/obj/item/weapon/spell/modifier/on_melee_cast(atom/hit_atom, mob/user)
if(istype(hit_atom, /mob/living))
on_add_modifier(hit_atom)
return on_add_modifier(hit_atom)
return FALSE
/obj/item/weapon/spell/modifier/on_ranged_cast(atom/hit_atom, mob/user)
if(istype(hit_atom, /mob/living))
on_add_modifier(hit_atom)
return on_add_modifier(hit_atom)
return FALSE
/obj/item/weapon/spell/modifier/proc/on_add_modifier(var/mob/living/L)
@@ -32,6 +34,7 @@
MT.spell_power = calculate_spell_power(1)
log_and_message_admins("has casted [src] on [L].")
qdel(src)
return TRUE
// Technomancer specific subtype which keeps track of spell power and gets targeted specificially by Dispel.
/datum/modifier/technomancer

View File

@@ -30,13 +30,13 @@
this point.</span>"
return 0
user << "<span class='notice'>You stab \the [L] with a hidden integrated hypo, attempting to bring them back...</span>"
if(istype(L, /mob/living/simple_animal))
var/mob/living/simple_animal/SM = L
if(istype(L, /mob/living/simple_mob))
var/mob/living/simple_mob/SM = L
SM.health = SM.getMaxHealth() / 3
SM.stat = CONSCIOUS
dead_mob_list -= SM
living_mob_list += SM
SM.icon_state = SM.icon_living
SM.update_icon()
adjust_instability(15)
else if(ishuman(L))
var/mob/living/carbon/human/H = L

View File

@@ -11,7 +11,7 @@
icon_state = "radiance"
cast_methods = CAST_RANGED | CAST_THROW
aspect = ASPECT_EMP
spawner_type = /obj/effect/temporary_effect/pulsar
spawner_type = /obj/effect/temporary_effect/pulse/pulsar
/obj/item/weapon/spell/spawner/pulsar/New()
..()
@@ -25,7 +25,29 @@
/obj/item/weapon/spell/spawner/pulsar/on_throw_cast(atom/hit_atom, mob/user)
empulse(hit_atom, 1, 1, 1, 1, log=1)
/obj/effect/temporary_effect/pulsar
// Does something every so often. Deletes itself when pulses_remaining hits zero.
/obj/effect/temporary_effect/pulse
var/pulses_remaining = 3
var/pulse_delay = 2 SECONDS
/obj/effect/temporary_effect/pulse/initialize()
spawn(0)
pulse_loop()
return ..()
/obj/effect/temporary_effect/pulse/proc/pulse_loop()
while(pulses_remaining)
sleep(pulse_delay)
on_pulse()
pulses_remaining--
qdel(src)
// Override for specific effects.
/obj/effect/temporary_effect/pulse/proc/on_pulse()
/obj/effect/temporary_effect/pulse/pulsar
name = "pulsar"
desc = "Not a real pulsar, but still emits loads of EMP."
icon_state = "shield2"
@@ -33,17 +55,14 @@
light_range = 4
light_power = 5
light_color = "#2ECCFA"
var/pulses_remaining = 3
pulses_remaining = 3
/obj/effect/temporary_effect/pulsar/New()
..()
spawn(0)
pulse_loop()
/obj/effect/temporary_effect/pulsar/proc/pulse_loop()
while(pulses_remaining)
sleep(2 SECONDS)
/obj/effect/temporary_effect/pulse/pulsar/on_pulse()
empulse(src, 1, 1, 2, 2, log = 1)
pulses_remaining--
qdel(src)

View File

@@ -16,6 +16,7 @@
desc = "Chitter chitter."
summoned_mob_type = null
summon_options = list(
<<<<<<< HEAD
"Mouse" = /mob/living/simple_animal/mouse,
"Lizard" = /mob/living/simple_animal/lizard,
"Chicken" = /mob/living/simple_animal/chicken,
@@ -34,16 +35,34 @@
"CARP" = /mob/living/simple_animal/hostile/carp,
"BEAR" = /mob/living/simple_animal/hostile/bear
) // Vorestation edits to add vore versions.
=======
"Mouse" = /mob/living/simple_mob/animal/passive/mouse,
"Lizard" = /mob/living/simple_mob/animal/passive/lizard,
"Chicken" = /mob/living/simple_mob/animal/passive/chicken,
"Chick" = /mob/living/simple_mob/animal/passive/chick,
"Crab" = /mob/living/simple_mob/animal/passive/crab,
"Parrot" = /mob/living/simple_mob/animal/passive/bird/parrot,
"Goat" = /mob/living/simple_mob/animal/goat,
"Cat" = /mob/living/simple_mob/animal/passive/cat,
"Kitten" = /mob/living/simple_mob/animal/passive/cat/kitten,
"Corgi" = /mob/living/simple_mob/animal/passive/dog/corgi,
"Corgi Pup" = /mob/living/simple_mob/animal/passive/dog/corgi/puppy,
"BAT" = /mob/living/simple_mob/animal/space/bats,
"SPIDER" = /mob/living/simple_mob/animal/giant_spider,
"SPIDER HUNTER" = /mob/living/simple_mob/animal/giant_spider/hunter,
"SPIDER NURSE" = /mob/living/simple_mob/animal/giant_spider/nurse,
"CARP" = /mob/living/simple_mob/animal/space/carp,
"BEAR" = /mob/living/simple_mob/animal/space/bear
)
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
cooldown = 30
instability_cost = 10
energy_cost = 1000
/obj/item/weapon/spell/summon/summon_creature/on_summon(var/mob/living/simple_animal/summoned)
/obj/item/weapon/spell/summon/summon_creature/on_summon(var/mob/living/simple_mob/summoned)
if(check_for_scepter())
// summoned.faction = "technomancer"
if(istype(summoned, /mob/living/simple_animal/hostile))
var/mob/living/simple_animal/SA = summoned
SA.friends.Add(owner)
summoned.friends += owner
// Makes their new pal big and strong, if they have spell power.
summoned.maxHealth = calculate_spell_power(summoned.maxHealth)
@@ -51,15 +70,12 @@
summoned.melee_damage_lower = calculate_spell_power(summoned.melee_damage_lower)
summoned.melee_damage_upper = calculate_spell_power(summoned.melee_damage_upper)
// This makes the summon slower, so the crew has a chance to flee from massive monsters.
summoned.move_to_delay = calculate_spell_power(round(summoned.move_to_delay))
summoned.movement_cooldown = calculate_spell_power(round(summoned.movement_cooldown))
var/new_size = calculate_spell_power(1)
if(new_size != 1)
var/matrix/M = matrix()
M.Scale(new_size)
M.Translate(0, 16*(new_size-1))
summoned.transform = M
adjust_scale(new_size)
// Now we hurt their new pal, because being forcefully abducted by teleportation can't be healthy.
summoned.health = round(summoned.getMaxHealth() * 0.7)
summoned.adjustBruteLoss(summoned.getMaxHealth() * 0.3) // Lose 30% of max health on arrival (but could be healed back up).

View File

@@ -1,9 +1,8 @@
/datum/technomancer/spell/summon_ward
name = "Summon Ward"
desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you and your allies when it sees entities \
moving around it, or when it is attacked. They can see for up to five meters."
enhancement_desc = "Wards can detect invisibile entities, and are more specific in relaying information about what it sees. \
Invisible entities that are spotted by it will be decloaked."
name = "Summon Monitor Ward"
desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you when it sees entities \
moving around it, or when it is attacked. They can see for up to five meters. It can also see invisible entities, and \
forcefully decloak them if close enough."
cost = 25
obj_path = /obj/item/weapon/spell/summon/summon_ward
category = UTILITY_SPELLS
@@ -12,116 +11,10 @@
name = "summon ward"
desc = "Finally, someone you can depend on to watch your back."
cast_methods = CAST_RANGED
summoned_mob_type = /mob/living/simple_animal/ward
summoned_mob_type = /mob/living/simple_mob/mechanical/ward/monitor
cooldown = 10
instability_cost = 5
energy_cost = 500
/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_animal/ward/ward)
ward.creator = owner
if(check_for_scepter())
ward.true_sight = 1
ward.see_invisible = SEE_INVISIBLE_LEVEL_TWO
/mob/living/simple_animal/ward
name = "ward"
desc = "It's a little flying drone that seems to be watching you..."
icon = 'icons/mob/critter.dmi'
icon_state = "ward"
resistance = 5
wander = 0
response_help = "pets the"
response_disarm = "swats away"
response_harm = "punches"
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
maxbodytemp = 0
unsuitable_atoms_damage = 0
heat_damage_per_tick = 0
cold_damage_per_tick = 0
var/true_sight = 0 // If true, detects more than what the Technomancer normally can't.
var/mob/living/carbon/human/creator = null
var/list/seen_mobs = list()
/mob/living/simple_animal/ward/death()
if(creator)
creator << "<span class='danger'>Your ward inside [get_area(src)] was killed!</span>"
..()
qdel(src)
/mob/living/simple_animal/ward/proc/expire()
if(creator && src)
creator << "<span class='warning'>Your ward inside [get_area(src)] expired.</span>"
qdel(src)
/mob/living/simple_animal/ward/Life()
..()
detect_mobs()
update_icon()
/mob/living/simple_animal/ward/proc/detect_mobs()
var/list/things_in_sight = view(5,src)
var/list/newly_seen_mobs = list()
for(var/mob/living/L in things_in_sight)
if(L == creator) // I really wish is_ally() was usable here.
continue
if(istype(L, /mob/living/simple_animal/ward))
continue
if(istype(L, /mob/living/simple_animal))
var/mob/living/simple_animal/SA = L
if(creator in SA.friends)
continue
if(!true_sight)
var/turf/T = get_turf(L)
var/light_amount = T.get_lumcount()
if(light_amount <= 0.5)
continue // Too dark to see.
if(L.alpha <= 127)
continue // Too transparent, as a mercy to camo lings.
else
L.break_cloak()
// Warn the Technomancer when it sees a new mob.
if(!(L in seen_mobs))
seen_mobs.Add(L)
newly_seen_mobs.Add(L)
if(creator)
if(true_sight)
creator << "<span class='notice'>Your ward at [get_area(src)] detected [english_list(newly_seen_mobs)].</span>"
else
creator << "<span class='notice'>Your ward at [get_area(src)] detected something.</span>"
// Now get rid of old mobs that left vision.
for(var/mob/living/L in seen_mobs)
if(!(L in things_in_sight))
seen_mobs.Remove(L)
/mob/living/simple_animal/ward/update_icon()
if(seen_mobs.len)
icon_state = "ward_spotted"
set_light(3, 3, l_color = "FF0000")
else
icon_state = "ward"
set_light(3, 3, l_color = "00FF00")
if(true_sight)
overlays.Cut()
var/image/I = image('icons/mob/critter.dmi',"ward_truesight")
overlays.Add(I)
/mob/living/simple_animal/ward/invisible_detect
true_sight = 1
see_invisible = SEE_INVISIBLE_LEVEL_TWO
/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_mob/mechanical/ward/monitor/my_ward)
my_ward.owner = owner

View File

@@ -49,9 +49,8 @@
if(occupant)
to_chat(user, "<span class='notice'>\The [src] is already occupied!</span>")
return
for(var/mob/living/simple_animal/slime/M in range(1, H.affecting))
if(M.victim == H.affecting)
to_chat(user, "<span class='danger'>[H.affecting.name] has a slime attached to them, deal with that first.</span>")
if(H.affecting.has_buckled_mobs())
to_chat(user, span("warning", "\The [H.affecting] has other entities attached to it. Remove them first."))
return
var/mob/M = H.affecting
if(M.abiotic())
@@ -86,10 +85,9 @@
if(O.abiotic())
to_chat(user, "<span class='notice'>Subject cannot have abiotic items on.</span>")
return 0
for(var/mob/living/simple_animal/slime/M in range(1, O))
if(M.victim == O)
to_chat(user, "<span class='danger'>[O] has a slime attached to them, deal with that first.</span>")
return 0
if(O.has_buckled_mobs())
to_chat(user, span("warning", "\The [O] has other entities attached to it. Remove them first."))
return
if(O == user)
visible_message("[user] climbs into \the [src].")

View File

@@ -143,7 +143,7 @@
/obj/machinery/camera/attack_generic(mob/user as mob)
if(isanimal(user))
var/mob/living/simple_animal/S = user
var/mob/living/simple_mob/S = user
set_status(0)
S.do_attack_animation(src)
S.setClickCooldown(user.get_attack_speed())

View File

@@ -201,7 +201,7 @@ var/prison_shuttle_timeleft = 0
for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area...
bug.gib()
for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug...
for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug...
pest.gib()
start_location.move_contents_to(end_location)

View File

@@ -81,7 +81,7 @@ var/specops_shuttle_timeleft = 0
for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area...
bug.gib()
for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug...
for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug...
pest.gib()
start_location.move_contents_to(end_location)

View File

@@ -166,7 +166,7 @@ var/syndicate_elite_shuttle_timeleft = 0
for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area...
bug.gib()
for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug...
for(var/mob/living/simple_mob/pest in end_location) // And for the other kind of bug...
pest.gib()
start_location.move_contents_to(end_location)

View File

@@ -207,7 +207,7 @@
for(var/obj/effect/decal/cleanable/blood/B in linkedholodeck)
qdel(B)
for(var/mob/living/simple_animal/hostile/carp/C in linkedholodeck)
for(var/mob/living/simple_mob/animal/space/carp/C in linkedholodeck)
qdel(C)
holographic_items = A.copy_contents_to(linkedholodeck , 1)
@@ -228,7 +228,7 @@
T.temperature = 5000
T.hotspot_expose(50000,50000,1)
if(L.name=="Holocarp Spawn")
new /mob/living/simple_animal/hostile/carp(L.loc)
new /mob/living/simple_mob/animal/space/carp(L.loc)
/datum/file/program/holodeck/proc/emergencyShutdown()

View File

@@ -199,9 +199,8 @@
return
if(occupant)
to_chat(user,"<span class='warning'>\The [src] is already occupied by [occupant].</span>")
for(var/mob/living/simple_animal/slime/M in range(1,grab.affecting))
if(M.victim == grab.affecting)
to_chat(usr, "[grab.affecting.name] will not fit into the cryo because they have a slime latched onto their head.")
if(grab.affecting.has_buckled_mobs())
to_chat(user, span("warning", "\The [grab.affecting] has other entities attached to it. Remove them first."))
return
var/mob/M = grab.affecting
qdel(grab)
@@ -349,14 +348,14 @@
set name = "Move Inside"
set category = "Object"
set src in oview(1)
for(var/mob/living/simple_animal/slime/M in range(1,usr))
if(M.victim == usr)
to_chat(usr, "You're too busy getting your life sucked out of you.")
if(isliving(usr))
var/mob/living/L = usr
if(L.has_buckled_mobs())
to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first."))
return
if(usr.stat != 0)
return
put_mob(usr)
if(L.stat != CONSCIOUS)
return
put_mob(L)
/atom/proc/return_air_for_internal_lifeform(var/mob/living/lifeform)
return return_air()

View File

@@ -567,9 +567,10 @@
to_chat(usr, "<span class='notice'><B>\The [src] is in use.</B></span>")
return
for(var/mob/living/simple_animal/slime/M in range(1,usr))
if(M.victim == usr)
to_chat(usr, "You're too busy getting your life sucked out of you.")
if(isliving(usr))
var/mob/living/L = usr
if(L.has_buckled_mobs())
to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first."))
return
visible_message("[usr] [on_enter_visible_message] [src].", 3)

View File

@@ -46,11 +46,12 @@
var/bolt_up_sound = 'sound/machines/boltsup.ogg'
var/bolt_down_sound = 'sound/machines/boltsdown.ogg'
/obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage)
/obj/machinery/door/airlock/attack_generic(var/mob/living/user, var/damage)
if(stat & (BROKEN|NOPOWER))
if(damage >= 10)
if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
if(src.locked || src.welded)
visible_message("<span class='danger'>\The [user] begins breaking into \the [src] internals!</span>")
user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
if(do_after(user,10 SECONDS,src))
src.locked = 0
src.welded = 0
@@ -58,6 +59,7 @@
open(1)
if(prob(25))
src.shock(user, 100)
user.set_AI_busy(FALSE)
else if(src.density)
visible_message("<span class='danger'>\The [user] forces \the [src] open!</span>")
open(1)
@@ -508,9 +510,6 @@ About the new airlock wires panel:
return
..(user)
/obj/machinery/door/airlock/bumpopen(mob/living/simple_animal/user as mob)
..(user)
/obj/machinery/door/airlock/proc/isElectrified()
if(src.electrified_until != 0)
return 1

View File

@@ -39,8 +39,8 @@
/obj/machinery/door/attack_generic(var/mob/user, var/damage)
if(isanimal(user))
var/mob/living/simple_animal/S = user
if(damage >= 10)
var/mob/living/simple_mob/S = user
if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
visible_message("<span class='danger'>\The [user] smashes into the [src]!</span>")
playsound(src, S.attack_sound, 75, 1)
take_damage(damage)

View File

@@ -216,22 +216,26 @@
return
..()
/obj/machinery/door/firedoor/attack_generic(var/mob/user, var/damage)
/obj/machinery/door/firedoor/attack_generic(var/mob/living/user, var/damage)
if(stat & (BROKEN|NOPOWER))
if(damage >= 10)
if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
var/time_to_force = (2 + (2 * blocked)) * 5
if(src.density)
visible_message("<span class='danger'>\The [user] starts forcing \the [src] open!</span>")
user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
if(do_after(user, time_to_force, src))
visible_message("<span class='danger'>\The [user] forces \the [src] open!</span>")
src.blocked = 0
open(1)
user.set_AI_busy(FALSE)
else
time_to_force = (time_to_force / 2)
visible_message("<span class='danger'>\The [user] starts forcing \the [src] closed!</span>")
user.set_AI_busy(TRUE) // If the mob doesn't have an AI attached, this won't do anything.
if(do_after(user, time_to_force, src))
visible_message("<span class='danger'>\The [user] forces \the [src] closed!</span>")
close(1)
user.set_AI_busy(FALSE)
else
visible_message("<span class='notice'>\The [user] strains fruitlessly to force \the [src] [density ? "open" : "closed"].</span>")
return

View File

@@ -406,16 +406,16 @@ var/list/turret_icons
attacked = 0
..()
/obj/machinery/porta_turret/attack_generic(mob/user as mob, var/damage)
if(isanimal(user))
var/mob/living/simple_animal/S = user
if(damage >= 10)
/obj/machinery/porta_turret/attack_generic(mob/living/L, damage)
if(isanimal(L))
var/mob/living/simple_mob/S = L
if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
var/incoming_damage = round(damage - (damage / 5)) //Turrets are slightly armored, assumedly.
visible_message("<span class='danger'>\The [S] [pick(S.attacktext)] \the [src]!</span>")
take_damage(incoming_damage)
S.do_attack_animation(src)
return 1
visible_message("<span class='notice'>\The [user] bonks \the [src]'s casing!</span>")
visible_message("<span class='notice'>\The [L] bonks \the [src]'s casing!</span>")
return ..()
/obj/machinery/porta_turret/emag_act(var/remaining_charges, var/mob/user)

View File

@@ -69,7 +69,7 @@
for(var/obj/machinery/teleport/hub/H in range(1))
var/amount = rand(2,5)
for(var/i=0;i<amount;i++)
new /mob/living/simple_animal/hostile/carp(get_turf(H))
new /mob/living/simple_mob/animal/space/carp(get_turf(H))
//
else
for(var/mob/O in hearers(src, null))

View File

@@ -40,9 +40,8 @@
if(occupant)
occupant_message("The sleeper is already occupied")
return
for(var/mob/living/simple_animal/slime/M in range(1,target))
if(M.victim == target)
occupant_message("[target] will not fit into the sleeper because they have a slime latched onto their head.")
if(target.has_buckled_mobs())
occupant_message(span("warning", "\The [target] has other entities attached to it. Remove them first."))
return
occupant_message("You start putting [target] into [src].")
chassis.visible_message("[chassis] starts putting [target] into the [src].")

View File

@@ -1203,9 +1203,10 @@
usr << "<span class='danger'>Kinda hard to climb in while handcuffed don't you think?</span>"
return
for(var/mob/living/simple_animal/slime/M in range(1,usr))
if(M.victim == usr)
usr << "<span class='danger'>You're too busy getting your life sucked out of you.</span>"
if(isliving(usr))
var/mob/living/L = usr
if(L.has_buckled_mobs())
to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first."))
return
//search for a valid passenger compartment

View File

@@ -1142,10 +1142,12 @@
to_chat(usr,"<span class='warning'>Access denied</span>")
src.log_append_to_last("Permission denied.")
return
for(var/mob/living/simple_animal/slime/M in range(1,usr))
if(M.victim == usr)
to_chat(usr,"You're too busy getting your life sucked out of you.")
if(isliving(usr))
var/mob/living/L = usr
if(L.has_buckled_mobs())
to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first."))
return
// usr << "You start climbing into [src.name]"
visible_message("<span class='notice'>\The [usr] starts to climb into [src.name]</span>")

View File

@@ -99,6 +99,14 @@
name = "Dark Gygax wreckage"
icon_state = "darkgygax-broken"
/obj/effect/decal/mecha_wreckage/gygax/adv
name = "Advanced Dark Gygax wreckage"
icon_state = "darkgygax_adv-broken"
/obj/effect/decal/mecha_wreckage/gygax/medgax
name = "Medgax wreckage"
icon_state = "medgax-broken"
/obj/effect/decal/mecha_wreckage/marauder
name = "Marauder wreckage"
icon_state = "marauder-broken"
@@ -198,6 +206,9 @@
parts -= part
return
/obj/effect/decal/mecha_wreckage/odysseus/murdysseus
icon_state = "murdysseus-broken"
/obj/effect/decal/mecha_wreckage/hoverpod
name = "Hover pod wreckage"
icon_state = "engineering_pod-broken"

View File

@@ -109,7 +109,7 @@
else if(foundVirus)
holder.icon_state = "hudill"
else if(patient.has_brain_worms())
var/mob/living/simple_animal/borer/B = patient.has_brain_worms()
var/mob/living/simple_mob/animal/borer/B = patient.has_brain_worms()
if(B.controlling)
holder.icon_state = "hudbrainworm"
else

View File

@@ -99,3 +99,13 @@
mouse_opacity = FALSE
anchored = TRUE
plane = ABOVE_PLANE
// Similar to the tesla ball but doesn't actually do anything and is purely visual.
/obj/effect/overlay/energy_ball
name = "energy ball"
desc = "An energy ball."
icon = 'icons/obj/tesla_engine/energy_ball.dmi'
icon_state = "energy_ball"
plane = PLANE_LIGHTING_ABOVE
pixel_x = -32
pixel_y = -32

View File

@@ -56,13 +56,15 @@
/obj/effect/spider/stickyweb
icon_state = "stickyweb1"
New()
/obj/effect/spider/stickyweb/initialize()
if(prob(50))
icon_state = "stickyweb2"
return ..()
/obj/effect/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group || (height==0)) return 1
if(istype(mover, /mob/living/simple_animal/hostile/giant_spider))
if(istype(mover, /mob/living/simple_mob/animal/giant_spider))
return 1
else if(istype(mover, /mob/living))
if(prob(50))
@@ -80,10 +82,12 @@
var/spiders_min = 6
var/spiders_max = 24
var/spider_type = /obj/effect/spider/spiderling
New()
/obj/effect/spider/eggcluster/initialize()
pixel_x = rand(3,-3)
pixel_y = rand(3,-3)
processing_objects |= src
return ..()
/obj/effect/spider/eggcluster/New(var/location, var/atom/parent)
get_light_and_color(parent)
@@ -129,10 +133,10 @@
var/amount_grown = -1
var/obj/machinery/atmospherics/unary/vent_pump/entry_vent
var/travelling_in_vent = 0
var/list/grow_as = list(/mob/living/simple_animal/hostile/giant_spider, /mob/living/simple_animal/hostile/giant_spider/nurse, /mob/living/simple_animal/hostile/giant_spider/hunter)
var/list/grow_as = list(/mob/living/simple_mob/animal/giant_spider, /mob/living/simple_mob/animal/giant_spider/nurse, /mob/living/simple_mob/animal/giant_spider/hunter)
/obj/effect/spider/spiderling/frost
grow_as = list(/mob/living/simple_animal/hostile/giant_spider/frost)
grow_as = list(/mob/living/simple_mob/animal/giant_spider/frost)
/obj/effect/spider/spiderling/New(var/location, var/atom/parent)
pixel_x = rand(6,-6)

View File

@@ -107,7 +107,7 @@
return 0
if(!user.IsAdvancedToolUser() && isanimal(user))
var/mob/living/simple_animal/S = user
var/mob/living/simple_mob/S = user
if(!S.IsHumanoidToolUser(src))
return 0

View File

@@ -166,7 +166,7 @@
outmsg = "<span class='info'>You missed the lens of [C] with [src].</span>"
//cats!
for(var/mob/living/simple_animal/cat/C in viewers(1,targloc))
for(var/mob/living/simple_mob/animal/passive/cat/C in viewers(1,targloc))
if (!(C.stat || C.buckled))
if(prob(50) && !(C.client))
C.visible_message("<span class='warning'>[C] pounces on the light!</span>", "<span class='warning'>You pounce on the light!</span>")

View File

@@ -451,15 +451,15 @@ HALOGEN COUNTER - Radcount on mobs
matter = list(DEFAULT_WALL_MATERIAL = 30,"glass" = 20)
/obj/item/device/slime_scanner/attack(mob/living/M as mob, mob/living/user as mob)
if(!isslime(M))
to_chat(user, "<B>This device can only scan slimes!</B>")
if(!istype(M, /mob/living/simple_mob/slime/xenobio))
to_chat(user, "<B>This device can only scan lab-grown slimes!</B>")
return
var/mob/living/simple_animal/slime/S = M
var/mob/living/simple_mob/slime/xenobio/S = M
user.show_message("Slime scan results:<br>[S.slime_color] [S.is_adult ? "adult" : "baby"] slime<br>Health: [S.health]<br>Mutation Probability: [S.mutation_chance]")
var/list/mutations = list()
for(var/potential_color in S.slime_mutation)
var/mob/living/simple_animal/slime/slime = potential_color
var/mob/living/simple_mob/slime/xenobio/slime = potential_color
mutations.Add(initial(slime.slime_color))
user.show_message("Potental to mutate into [english_list(mutations)] colors.<br>Extract potential: [S.cores]<br>Nutrition: [S.nutrition]/[S.get_max_nutrition()]")
@@ -469,12 +469,14 @@ HALOGEN COUNTER - Radcount on mobs
user.show_message("<span class='warning'>Warning: Subject is hungry.</span>")
user.show_message("Electric change strength: [S.power_charge]")
if(S.resentment)
if(S.has_AI())
var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder
if(AI.resentment)
user.show_message("<span class='warning'>Warning: Subject is harboring resentment.</span>")
if(S.docile)
user.show_message("Subject has been pacified.")
if(S.rabid)
if(AI.rabid)
user.show_message("<span class='danger'>Subject is enraged and extremely dangerous!</span>")
if(S.harmless)
user.show_message("Subject has been pacified.")
if(S.unity)
user.show_message("Subject is friendly to other slime colors.")

View File

@@ -6,7 +6,7 @@
w_class = ITEMSIZE_SMALL
matter = list("glass" = 200)
flags = NOBLUDGEON
var/list/accept_mobs = list(/mob/living/simple_animal/lizard, /mob/living/simple_animal/mouse)
var/list/accept_mobs = list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/mouse)
var/contains = 0 // 0 = nothing, 1 = money, 2 = animal, 3 = spiderling
/obj/item/glass_jar/New()

View File

@@ -47,14 +47,3 @@
processing_objects -= src
return ..()
//Crashed Cargo Shuttle PoI
/obj/structure/largecrate/animal/crashedshuttle
name = "SCP"
/obj/structure/largecrate/animal/crashedshuttle/initialize()
starts_with = list(pick(/mob/living/simple_animal/hostile/statue, /obj/item/cursed_marble, /obj/item/weapon/deadringer)) // Starts_with has to be a list
name = pick("Spicy Crust Pizzeria", "Soap and Care Products", "Sally's Computer Parts", "Steve's Chocolate Pastries", "Smith & Christian's Plastics","Standard Containers & Packaging Co.", "Sanitary Chemical Purgation (LTD)")
name += " delivery crate"
return ..()

View File

@@ -275,13 +275,6 @@
add_flashes(W,user)
else
add_flashes(W,user)
else if(istype(W, /obj/item/weapon/stock_parts/manipulator))
to_chat(user, "<span class='notice'>You install some manipulators and modify the head, creating a functional spider-bot!</span>")
new /mob/living/simple_animal/spiderbot(get_turf(loc))
user.drop_item()
qdel(W)
qdel(src)
return
return
/obj/item/robot_parts/head/proc/add_flashes(obj/item/W as obj, mob/user as mob) //Made into a seperate proc to avoid copypasta

2
code/game/objects/items/weapons/AI_modules.dm Executable file → Normal file
View File

@@ -23,7 +23,7 @@ AI MODULES
/obj/item/weapon/aiModule/proc/install(var/atom/movable/AM, var/mob/living/user)
if(!user.IsAdvancedToolUser() && isanimal(user))
var/mob/living/simple_animal/S = user
var/mob/living/simple_mob/S = user
if(!S.IsHumanoidToolUser(src))
return 0

View File

@@ -31,19 +31,25 @@
/obj/item/weapon/grenade/spawnergrenade/manhacks
name = "manhack delivery grenade"
spawner_type = /mob/living/simple_animal/hostile/viscerator
spawner_type = /mob/living/simple_mob/mechanical/viscerator
deliveryamt = 5
origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4)
/obj/item/weapon/grenade/spawnergrenade/manhacks/mercenary
spawner_type = /mob/living/simple_mob/mechanical/viscerator/mercenary
/obj/item/weapon/grenade/spawnergrenade/manhacks/raider
spawner_type = /mob/living/simple_mob/mechanical/viscerator/raider
/obj/item/weapon/grenade/spawnergrenade/spesscarp
name = "carp delivery grenade"
spawner_type = /mob/living/simple_animal/hostile/carp
spawner_type = /mob/living/simple_mob/animal/space/carp
deliveryamt = 5
origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4)
/obj/item/weapon/grenade/spawnergrenade/spider
name = "spider delivery grenade"
spawner_type = /mob/living/simple_animal/hostile/giant_spider/hunter
spawner_type = /mob/living/simple_mob/animal/giant_spider/hunter
deliveryamt = 3
origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4)

View File

@@ -79,9 +79,8 @@
var/obj/item/weapon/grab/grab = G
if(!ismob(grab.affecting))
return
for(var/mob/living/simple_animal/slime/M in range(1,grab.affecting))
if(M.victim == grab.affecting)
usr << "[grab.affecting.name] will not fit into the [src.name] because they have a slime latched onto their head."
if(grab.affecting.has_buckled_mobs())
to_chat(user, span("warning", "\The [grab.affecting] has other entities attached to them. Remove them first."))
return
var/mob/M = grab.affecting
if(put_mob(M))

View File

@@ -233,11 +233,10 @@
playsound(get_turf(target), 'sound/weapons/blade1.ogg', 100, 1)
// Make lesser robots really mad at us.
if(istype(target, /mob/living/simple_animal))
var/mob/living/simple_animal/SA = target
if(SA.intelligence_level == SA_ROBOTIC)
SA.taunt(user)
SA.adjustFireLoss(force * 6) // 30 Burn, for 50 total.
if(target.mob_class & MOB_CLASS_SYNTHETIC)
if(target.has_AI())
target.taunt(user)
target.adjustFireLoss(force * 6) // 30 Burn, for 50 total.
/*
*Energy Blade

View File

@@ -292,7 +292,7 @@ var/list/tape_roll_applications = list()
add_fingerprint(M)
if (!allowed(M)) //only select few learn art of not crumpling the tape
M << "<span class='warning'>You are not supposed to go past [src]...</span>"
if(M.a_intent == I_HELP && !(istype(M, /mob/living/simple_animal)))
if(M.a_intent == I_HELP && !(istype(M, /mob/living/simple_mob)))
return 0
crumple()
return ..(mover)

View File

@@ -269,9 +269,8 @@
/obj/item/weapon/melee/baton/shocker/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
..(target, user, hit_zone)
if(istype(target, /mob/living/simple_animal) && status)
var/mob/living/simple_animal/SA = target
SA.taunt(user)
if(status && target.has_AI())
target.taunt(user)
// Borg version, for the lost module.
/obj/item/weapon/melee/baton/shocker/robot

View File

@@ -1,3 +1,7 @@
<<<<<<< HEAD
=======
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
#define WELDER_FUEL_BURN_INTERVAL 13
/*
* Welding Tool
@@ -317,7 +321,10 @@
var/obj/item/organ/internal/eyes/E = H.internal_organs_by_name[O_EYES]
if(!E)
return
<<<<<<< HEAD
if(H.nif && H.nif.flag_check(NIF_V_UVFILTER,NIF_FLAGS_VISION)) return //VOREStation Add - NIF
=======
>>>>>>> 3155d58... Merge pull request #5735 from Neerti/hopefully_last_master_sync
switch(safety)
if(1)
to_chat(usr, "<span class='warning'>Your eyes sting a little.</span>")

View File

@@ -18,44 +18,47 @@
var/mob_retaliate = 0
/obj/random/mob/item_to_spawn()
return pick(prob(10);/mob/living/simple_animal/lizard,
prob(6);/mob/living/simple_animal/retaliate/diyaab,
prob(10);/mob/living/simple_animal/cat/fluff,
prob(6);/mob/living/simple_animal/cat/kitten,
prob(10);/mob/living/simple_animal/corgi,
prob(6);/mob/living/simple_animal/corgi/puppy,
prob(10);/mob/living/simple_animal/crab,
prob(10);/mob/living/simple_animal/chicken,
prob(6);/mob/living/simple_animal/chick,
prob(10);/mob/living/simple_animal/cow,
prob(6);/mob/living/simple_animal/retaliate/goat,
prob(10);/mob/living/simple_animal/penguin,
prob(10);/mob/living/simple_animal/mouse,
prob(10);/mob/living/simple_animal/yithian,
prob(10);/mob/living/simple_animal/tindalos,
prob(10);/mob/living/simple_animal/corgi/tamaskan,
prob(3);/mob/living/simple_animal/parrot,
prob(1);/mob/living/simple_animal/giant_crab)
return pick(prob(10);/mob/living/simple_mob/animal/passive/lizard,
prob(6);/mob/living/simple_mob/animal/sif/diyaab,
prob(10);/mob/living/simple_mob/animal/passive/cat,
prob(6);/mob/living/simple_mob/animal/passive/cat,
prob(10);/mob/living/simple_mob/animal/passive/dog/corgi,
prob(6);/mob/living/simple_mob/animal/passive/dog/corgi/puppy,
prob(10);/mob/living/simple_mob/animal/passive/crab,
prob(10);/mob/living/simple_mob/animal/passive/chicken,
prob(6);/mob/living/simple_mob/animal/passive/chick,
prob(10);/mob/living/simple_mob/animal/passive/cow,
prob(6);/mob/living/simple_mob/animal/goat,
prob(10);/mob/living/simple_mob/animal/passive/penguin,
prob(10);/mob/living/simple_mob/animal/passive/mouse,
prob(10);/mob/living/simple_mob/animal/passive/yithian,
prob(10);/mob/living/simple_mob/animal/passive/tindalos,
prob(10);/mob/living/simple_mob/animal/passive/dog/tamaskan,
prob(3);/mob/living/simple_mob/animal/passive/bird/parrot,
prob(1);/mob/living/simple_mob/animal/passive/crab)
/obj/random/mob/spawn_item() //These should only ever have simple mobs.
var/build_path = item_to_spawn()
var/mob/living/simple_animal/M = new build_path(src.loc)
M.ai_inactive = 1 //Don't fight eachother while we're still setting up!
if(mob_faction)
M.faction = mob_faction
M.returns_home = mob_returns_home
M.wander = mob_wander
M.wander_distance = mob_wander_distance
var/mob/living/simple_mob/M = new build_path(src.loc)
if(!istype(M))
return
if(M.has_AI())
var/datum/ai_holder/AI = M.ai_holder
AI.go_sleep() //Don't fight eachother while we're still setting up!
AI.returns_home = mob_returns_home
AI.wander = mob_wander
AI.max_home_distance = mob_wander_distance
if(overwrite_hostility)
M.hostile = mob_hostile
M.retaliate = mob_retaliate
M.ai_inactive = 0 //Now you can kill eachother if your faction didn't override.
AI.hostile = mob_hostile
AI.retaliate = mob_retaliate
AI.go_wake() //Now you can kill eachother if your faction didn't override.
if(pixel_x || pixel_y)
M.pixel_x = pixel_x
M.pixel_y = pixel_y
/obj/random/mob/sif
name = "Random Sif Animal"
desc = "This is a random cold weather animal."
@@ -65,14 +68,14 @@
mob_wander_distance = 10
/obj/random/mob/sif/item_to_spawn()
return pick(prob(30);/mob/living/simple_animal/retaliate/diyaab,
prob(15);/mob/living/simple_animal/crab,
prob(15);/mob/living/simple_animal/penguin,
prob(15);/mob/living/simple_animal/mouse,
prob(15);/mob/living/simple_animal/corgi/tamaskan,
prob(2);/mob/living/simple_animal/hostile/giant_spider/frost,
prob(1);/mob/living/simple_animal/hostile/goose,
prob(20);/mob/living/simple_animal/giant_crab)
return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab,
prob(15);/mob/living/simple_mob/animal/passive/crab,
prob(15);/mob/living/simple_mob/animal/passive/penguin,
prob(15);/mob/living/simple_mob/animal/passive/mouse,
prob(15);/mob/living/simple_mob/animal/passive/dog/tamaskan,
prob(2);/mob/living/simple_mob/animal/giant_spider/frost,
prob(1);/mob/living/simple_mob/animal/space/goose,
prob(20);/mob/living/simple_mob/animal/passive/crab)
/obj/random/mob/sif/peaceful
@@ -84,12 +87,12 @@
mob_wander_distance = 12
/obj/random/mob/sif/peaceful/item_to_spawn()
return pick(prob(30);/mob/living/simple_animal/retaliate/diyaab,
prob(15);/mob/living/simple_animal/crab,
prob(15);/mob/living/simple_animal/penguin,
prob(15);/mob/living/simple_animal/mouse,
prob(15);/mob/living/simple_animal/corgi/tamaskan,
prob(20);/mob/living/simple_animal/giant_crab)
return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab,
prob(15);/mob/living/simple_mob/animal/passive/crab,
prob(15);/mob/living/simple_mob/animal/passive/penguin,
prob(15);/mob/living/simple_mob/animal/passive/mouse,
prob(15);/mob/living/simple_mob/animal/passive/dog/tamaskan,
prob(20);/mob/living/simple_mob/animal/sif/hooligan_crab)
/obj/random/mob/sif/hostile
name = "Random Hostile Sif Animal"
@@ -97,9 +100,9 @@
icon_state = "frost"
/obj/random/mob/sif/hostile/item_to_spawn()
return pick(prob(22);/mob/living/simple_animal/hostile/savik,
prob(33);/mob/living/simple_animal/hostile/giant_spider/frost,
prob(45);/mob/living/simple_animal/hostile/shantak)
return pick(prob(22);/mob/living/simple_mob/animal/sif/savik,
prob(33);/mob/living/simple_mob/animal/giant_spider/frost,
prob(45);/mob/living/simple_mob/animal/sif/shantak)
/obj/random/mob/spider
name = "Random Spider" //Spiders should patrol where they spawn.
@@ -110,9 +113,9 @@
mob_wander_distance = 4
/obj/random/mob/spider/item_to_spawn()
return pick(prob(22);/mob/living/simple_animal/hostile/giant_spider/nurse,
prob(33);/mob/living/simple_animal/hostile/giant_spider/hunter,
prob(45);/mob/living/simple_animal/hostile/giant_spider)
return pick(prob(22);/mob/living/simple_mob/animal/giant_spider/nurse,
prob(33);/mob/living/simple_mob/animal/giant_spider/hunter,
prob(45);/mob/living/simple_mob/animal/giant_spider)
/obj/random/mob/spider/nurse
name = "Random Nurse Spider"
@@ -123,8 +126,8 @@
mob_wander_distance = 4
/obj/random/mob/spider/nurse/item_to_spawn()
return pick(prob(22);/mob/living/simple_animal/hostile/giant_spider/nurse/hat,
prob(45);/mob/living/simple_animal/hostile/giant_spider/nurse)
return pick(prob(22);/mob/living/simple_mob/animal/giant_spider/nurse/hat,
prob(45);/mob/living/simple_mob/animal/giant_spider/nurse)
/obj/random/mob/spider/mutant
name = "Random Mutant Spider"
@@ -133,15 +136,15 @@
/obj/random/mob/spider/mutant/item_to_spawn()
return pick(prob(5);/obj/random/mob/spider,
prob(10);/mob/living/simple_animal/hostile/giant_spider/webslinger,
prob(10);/mob/living/simple_animal/hostile/giant_spider/carrier,
prob(33);/mob/living/simple_animal/hostile/giant_spider/lurker,
prob(33);/mob/living/simple_animal/hostile/giant_spider/tunneler,
prob(40);/mob/living/simple_animal/hostile/giant_spider/pepper,
prob(20);/mob/living/simple_animal/hostile/giant_spider/thermic,
prob(40);/mob/living/simple_animal/hostile/giant_spider/electric,
prob(1);/mob/living/simple_animal/hostile/giant_spider/phorogenic,
prob(40);/mob/living/simple_animal/hostile/giant_spider/frost)
prob(10);/mob/living/simple_mob/animal/giant_spider/webslinger,
prob(10);/mob/living/simple_mob/animal/giant_spider/carrier,
prob(33);/mob/living/simple_mob/animal/giant_spider/lurker,
prob(33);/mob/living/simple_mob/animal/giant_spider/tunneler,
prob(40);/mob/living/simple_mob/animal/giant_spider/pepper,
prob(20);/mob/living/simple_mob/animal/giant_spider/thermic,
prob(40);/mob/living/simple_mob/animal/giant_spider/electric,
prob(1);/mob/living/simple_mob/animal/giant_spider/phorogenic,
prob(40);/mob/living/simple_mob/animal/giant_spider/frost)
/obj/random/mob/robotic
name = "Random Robot Mob"
@@ -158,17 +161,18 @@
mob_retaliate = 1
/obj/random/mob/robotic/item_to_spawn() //Hivebots have a total number of 'lots' equal to the lesser drone, at 60.
return pick(prob(60);/mob/living/simple_animal/hostile/malf_drone/lesser,
prob(50);/mob/living/simple_animal/hostile/malf_drone,
prob(15);/mob/living/simple_animal/hostile/mecha/malf_drone,
prob(10);/mob/living/simple_animal/hostile/hivebot,
prob(15);/mob/living/simple_animal/hostile/hivebot/swarm,
prob(10);/mob/living/simple_animal/hostile/hivebot/range,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/rapid,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/ion,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/laser,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/strong,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/guard)
return pick(prob(60);/mob/living/simple_mob/mechanical/combat_drone/lesser,
prob(50);/mob/living/simple_mob/mechanical/combat_drone,
prob(15);/mob/living/simple_mob/mechanical/mecha/ripley,
prob(15);/mob/living/simple_mob/mechanical/mecha/odysseus,
prob(10);/mob/living/simple_mob/mechanical/hivebot,
prob(15);/mob/living/simple_mob/mechanical/hivebot/swarm,
prob(10);/mob/living/simple_mob/mechanical/hivebot/ranged_damage,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard)
/obj/random/mob/robotic/hivebot
name = "Random Hivebot"
@@ -178,14 +182,14 @@
mob_faction = "hivebot"
/obj/random/mob/robotic/hivebot/item_to_spawn()
return pick(prob(10);/mob/living/simple_animal/hostile/hivebot,
prob(15);/mob/living/simple_animal/hostile/hivebot/swarm,
prob(10);/mob/living/simple_animal/hostile/hivebot/range,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/rapid,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/ion,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/laser,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/strong,
prob(5);/mob/living/simple_animal/hostile/hivebot/range/guard)
return pick(prob(10);/mob/living/simple_mob/mechanical/hivebot,
prob(15);/mob/living/simple_mob/mechanical/hivebot/swarm,
prob(10);/mob/living/simple_mob/mechanical/hivebot/ranged_damage,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong,
prob(5);/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard)
//Mice
@@ -195,7 +199,7 @@
icon_state = "mouse_gray"
/obj/random/mob/mouse/item_to_spawn()
return pick(prob(15);/mob/living/simple_animal/mouse/white,
prob(30);/mob/living/simple_animal/mouse/brown,
prob(30);/mob/living/simple_animal/mouse/gray,
return pick(prob(15);/mob/living/simple_mob/animal/passive/mouse/white,
prob(30);/mob/living/simple_mob/animal/passive/mouse/brown,
prob(30);/mob/living/simple_mob/animal/passive/mouse/gray,
prob(25);/obj/random/mouseremains) //because figuring out how to come up with it picking nothing is beyond my coding ability.

View File

@@ -176,8 +176,8 @@
return 0
return 1
/obj/structure/attack_generic(var/mob/user, var/damage, var/attack_verb, var/wallbreaker)
if(!breakable || damage < 10 || !wallbreaker)
/obj/structure/attack_generic(var/mob/user, var/damage, var/attack_verb)
if(!breakable || damage < STRUCTURE_MIN_DAMAGE_THRESHOLD)
return 0
visible_message("<span class='danger'>[user] [attack_verb] the [src] apart!</span>")
user.do_attack_animation(src)

View File

@@ -381,8 +381,8 @@
else
icon_state = icon_opened
/obj/structure/closet/attack_generic(var/mob/user, var/damage, var/attack_message = "destroys", var/wallbreaker)
if(damage < 10 || !wallbreaker)
/obj/structure/closet/attack_generic(var/mob/user, var/damage, var/attack_message = "destroys")
if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD)
return
user.do_attack_animation(src)
visible_message("<span class='danger'>[user] [attack_message] the [src]!</span>")

View File

@@ -90,23 +90,23 @@
/obj/structure/largecrate/animal/corgi
name = "corgi carrier"
starts_with = list(/mob/living/simple_animal/corgi)
starts_with = list(/mob/living/simple_mob/animal/passive/dog/corgi)
/obj/structure/largecrate/animal/cow
name = "cow crate"
starts_with = list(/mob/living/simple_animal/cow)
starts_with = list(/mob/living/simple_mob/animal/passive/cow)
/obj/structure/largecrate/animal/goat
name = "goat crate"
starts_with = list(/mob/living/simple_animal/retaliate/goat)
starts_with = list(/mob/living/simple_mob/animal/goat)
/obj/structure/largecrate/animal/cat
name = "cat carrier"
starts_with = list(/mob/living/simple_animal/cat)
starts_with = list(/mob/living/simple_mob/animal/passive/cat)
/obj/structure/largecrate/animal/cat/bones
starts_with = list(/mob/living/simple_animal/cat/fluff/bones)
starts_with = list(/mob/living/simple_mob/animal/passive/cat/bones)
/obj/structure/largecrate/animal/chick
name = "chicken crate"
starts_with = list(/mob/living/simple_animal/chick = 5)
starts_with = list(/mob/living/simple_mob/animal/passive/chick = 5)

View File

@@ -15,7 +15,7 @@
/obj/structure/ghost_pod/manual/corgi/create_occupant(var/mob/M)
lightning_strike(get_turf(src), cosmetic = TRUE)
density = FALSE
var/mob/living/simple_animal/corgi/R = new(get_turf(src))
var/mob/living/simple_mob/animal/passive/dog/corgi/R = new(get_turf(src))
if(M.mind)
M.mind.transfer_to(R)
to_chat(M, "<span class='notice'>You are a <b>Corgi</b>! Woof!</span>")

View File

@@ -83,8 +83,8 @@
health = (displaced_health - round(current_damage / 4))
cover = 25
/obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart", var/wallbreaker)
if(!damage || !wallbreaker)
/obj/structure/girder/attack_generic(var/mob/user, var/damage, var/attack_message = "smashes apart")
if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD)
return 0
user.do_attack_animation(src)
visible_message("<span class='danger'>[user] [attack_message] the [src]!</span>")

View File

@@ -126,7 +126,7 @@
src.set_dir(turn(src.dir, 90))
return
else
if(istype(usr,/mob/living/simple_animal/mouse))
if(ismouse(usr))
return
if(!usr || !isturf(usr.loc))
return

View File

@@ -574,6 +574,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
icon = 'icons/mecha/mecha.dmi'
icon_state = "engineering_pod-broken"
density = TRUE
anchored = FALSE // In case a dead mecha-mob dies in a bad spot.
chance_uncommon = 20
chance_rare = 10
@@ -615,7 +616,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/structure/loot_pile/mecha/ripley
name = "ripley wreckage"
desc = "The ruins of some unfortunate ripley. Perhaps something is salvageable."
icon_states_to_use = list("ripley-broken", "firefighter-broken", "ripley-broken-old")
icon_state = "ripley-broken"
common_loot = list(
/obj/random/tool,
@@ -649,6 +650,12 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/mecha_parts/mecha_equipment/weapon/energy/flamer/rigged
)
/obj/structure/loot_pile/mecha/ripley/firefighter
icon_state = "firefighter-broken"
/obj/structure/loot_pile/mecha/ripley/random_sprite
icon_states_to_use = list("ripley-broken", "firefighter-broken", "ripley-broken-old")
//Death-Ripley, same common, but more combat-exosuit-based
/obj/structure/loot_pile/mecha/deathripley
name = "strange ripley wreckage"
@@ -719,6 +726,14 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/mecha_parts/mecha_equipment/shocker
)
/obj/structure/loot_pile/mecha/odysseus/murdysseus
icon_state = "murdysseus-broken"
/obj/structure/loot_pile/mecha/hoverpod
name = "hoverpod wreckage"
desc = "The ruins of some unfortunate hoverpod. Perhaps something is salvageable."
icon_state = "engineering_pod"
/obj/structure/loot_pile/mecha/gygax
name = "gygax wreckage"
desc = "The ruins of some unfortunate gygax. Perhaps something is salvageable."
@@ -759,6 +774,18 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy
)
/obj/structure/loot_pile/mecha/gygax/dark
icon_state = "darkgygax-broken"
// Todo: Better loot.
/obj/structure/loot_pile/mecha/gygax/dark/adv
icon_state = "darkgygax_adv-broken"
icon_scale = 1.5
pixel_y = 8
/obj/structure/loot_pile/mecha/gygax/medgax
icon_state = "medgax-broken"
/obj/structure/loot_pile/mecha/durand
name = "durand wreckage"
desc = "The ruins of some unfortunate durand. Perhaps something is salvageable."
@@ -799,6 +826,22 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy
)
/obj/structure/loot_pile/mecha/marauder
name = "marauder wreckage"
desc = "The ruins of some unfortunate marauder. Perhaps something is salvagable."
icon_state = "marauder-broken"
// Todo: Better loot.
/obj/structure/loot_pile/mecha/marauder/seraph
name = "seraph wreckage"
desc = "The ruins of some unfortunate seraph. Perhaps something is salvagable."
icon_state = "seraph-broken"
/obj/structure/loot_pile/mecha/marauder/mauler
name = "mauler wreckage"
desc = "The ruins of some unfortunate mauler. Perhaps something is salvagable."
icon_state = "mauler-broken"
/obj/structure/loot_pile/mecha/phazon
name = "phazon wreckage"
desc = "The ruins of some unfortunate phazon. Perhaps something is salvageable."

View File

@@ -321,7 +321,7 @@
set category = "Object"
set src in oview(1)
if(istype(usr,/mob/living/simple_animal/mouse))
if(istype(usr,/mob/living/simple_mob/animal/passive/mouse))
return
else if(!usr || !isturf(usr.loc))
return

View File

@@ -10,8 +10,8 @@
explosion_resistance = 5
var/list/mobs_can_pass = list(
/mob/living/bot,
/mob/living/simple_animal/slime,
/mob/living/simple_animal/mouse,
/mob/living/simple_mob/slime/xenobio,
/mob/living/simple_mob/animal/passive/mouse,
/mob/living/silicon/robot/drone
)

View File

@@ -11,7 +11,7 @@
var/last_spawn
var/spawn_delay = 150
var/randomize_spawning = FALSE
var/creature_types = list(/mob/living/simple_animal/retaliate/diyaab)
var/creature_types = list(/mob/living/simple_mob/animal/sif/diyaab)
var/list/den_mobs
var/den_faction //The faction of any spawned creatures.
var/max_creatures = 3 //Maximum number of living creatures this nest can have at one time.

View File

@@ -206,6 +206,6 @@
/obj/structure/simple_door/cult/TryToSwitchState(atom/user)
if(isliving(user))
var/mob/living/L = user
if(!iscultist(L) && !istype(L, /mob/living/simple_animal/construct))
if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct))
return
..()

View File

@@ -77,7 +77,7 @@
src.set_dir(turn(src.dir, 90))
return
else
if(istype(usr,/mob/living/simple_animal/mouse))
if(istype(usr,/mob/living/simple_mob/animal/passive/mouse))
return
if(!usr || !isturf(usr.loc))
return

View File

@@ -206,7 +206,7 @@
user.setClickCooldown(user.get_attack_speed())
if(!damage)
return
if(damage >= 10)
if(damage >= STRUCTURE_MIN_DAMAGE_THRESHOLD)
visible_message("<span class='danger'>[user] smashes into [src]!</span>")
if(reinf)
damage = damage / 2

View File

@@ -27,16 +27,16 @@
cleaving = TRUE
var/hit_mobs = 0
for(var/mob/living/simple_animal/SA in range(get_turf(target), 1))
if(SA.stat == DEAD) // Don't beat a dead horse.
for(var/mob/living/simple_mob/SM in range(get_turf(target), 1))
if(SM.stat == DEAD) // Don't beat a dead horse.
continue
if(SA == user) // Don't hit ourselves. Simple mobs shouldn't be able to do this but that might change later to be able to hit all mob/living-s.
if(SM == user) // Don't hit ourselves. Simple mobs shouldn't be able to do this but that might change later to be able to hit all mob/living-s.
continue
if(SA == target) // We (presumably) already hit the target before cleave() was called. orange() should prevent this but just to be safe...
if(SM == target) // We (presumably) already hit the target before cleave() was called. orange() should prevent this but just to be safe...
continue
if(!SA.Adjacent(user) || !SA.Adjacent(target)) // Cleaving only hits mobs near the target mob and user.
if(!SM.Adjacent(user) || !SM.Adjacent(target)) // Cleaving only hits mobs near the target mob and user.
continue
if(resolve_attackby(SA, user, attack_modifier = 0.5)) // Hit them with the weapon. This won't cause recursive cleaving due to the cleaving variable being set to true.
if(resolve_attackby(SM, user, attack_modifier = 0.5)) // Hit them with the weapon. This won't cause recursive cleaving due to the cleaving variable being set to true.
hit_mobs++
cleave_visual(user, target)

View File

@@ -13,6 +13,9 @@
/turf/simulated/floor/outdoors/snow/Entered(atom/A)
if(isliving(A))
var/mob/living/L = A
if(L.hovering) // Flying things shouldn't make footprints.
return ..()
var/mdir = "[A.dir]"
crossed_dirs[mdir] = 1
update_icon()

View File

@@ -60,9 +60,9 @@
var/damage_lower = 25
var/damage_upper = 75
if(isanimal(user))
var/mob/living/simple_animal/S = user
var/mob/living/simple_mob/S = user
playsound(src, S.attack_sound, 75, 1)
if(!(S.melee_damage_upper >= 10))
if(!(S.melee_damage_upper >= STRUCTURE_MIN_DAMAGE_THRESHOLD * 2))
to_chat(user, "<span class='notice'>You bounce against the wall.</span>")
return FALSE
damage_lower = S.melee_damage_lower
@@ -75,7 +75,7 @@
to_chat(user, "<span class='danger'>You smash through the wall!</span>")
user.do_attack_animation(src)
if(isanimal(user))
var/mob/living/simple_animal/S = user
var/mob/living/simple_mob/S = user
playsound(src, S.attack_sound, 75, 1)
spawn(1)
dismantle_wall(1)
@@ -115,12 +115,12 @@
try_touch(user, rotting)
/turf/simulated/wall/attack_generic(var/mob/user, var/damage, var/attack_message, var/wallbreaker)
/turf/simulated/wall/attack_generic(var/mob/user, var/damage, var/attack_message)
radiate()
user.setClickCooldown(user.get_attack_speed())
var/rotting = (locate(/obj/effect/overlay/wallrot) in src)
if(!damage || !wallbreaker)
if(damage < STRUCTURE_MIN_DAMAGE_THRESHOLD * 2)
try_touch(user, rotting)
return
@@ -128,7 +128,7 @@
return success_smash(user)
if(reinf_material)
if((wallbreaker == 2) || (damage >= max(material.hardness,reinf_material.hardness)))
if(damage >= max(material.hardness, reinf_material.hardness) )
return success_smash(user)
else if(damage >= material.hardness)
return success_smash(user)

View File

@@ -9,6 +9,9 @@
edge_blending_priority = -1
movement_cost = 4
outdoors = TRUE
layer = WATER_FLOOR_LAYER
can_dirty = FALSE // It's water
var/depth = 1 // Higher numbers indicates deeper water.
@@ -19,10 +22,11 @@
/turf/simulated/floor/water/update_icon()
..() // To get the edges.
icon_state = water_state
var/image/floorbed_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = under_state)
underlays.Cut() // To clear the old underlay, so the list doesn't expand infinitely
underlays.Add(floorbed_sprite)
icon_state = under_state // This isn't set at compile time in order for it to show as water in the map editor.
var/image/water_sprite = image(icon = 'icons/turf/outdoors.dmi', icon_state = water_state, layer = WATER_LAYER)
add_overlay(water_sprite)
update_icon_edge()
/turf/simulated/floor/water/get_edge_icon_state()
@@ -102,6 +106,8 @@
/mob/living/proc/check_submerged()
if(buckled)
return 0
if(hovering)
return 0
var/turf/simulated/floor/water/T = loc
if(istype(T))
return T.depth
@@ -115,6 +121,8 @@
adjust_fire_stacks(-amount * 5)
for(var/atom/movable/AM in contents)
AM.water_act(amount)
remove_modifiers_of_type(/datum/modifier/fire)
inflict_water_damage(20 * amount) // Only things vulnerable to water will actually be harmed (slimes/prommies).
var/list/shoreline_icon_cache = list()
@@ -140,7 +148,13 @@ var/list/shoreline_icon_cache = list()
var/icon/shoreline_water = icon(src.icon, "shoreline_water", src.dir)
var/icon/shoreline_subtract = icon(src.icon, "[initial(icon_state)]_subtract", src.dir)
shoreline_water.Blend(shoreline_subtract,ICON_SUBTRACT)
var/image/final = image(shoreline_water)
final.layer = WATER_LAYER
shoreline_icon_cache[cache_string] = shoreline_water
shoreline_icon_cache[cache_string] = final
add_overlay(shoreline_icon_cache[cache_string])
/turf/simulated/floor/water/is_safe_to_enter(mob/living/L)
if(L.get_water_protection() < 1)
return FALSE
return ..()

View File

@@ -323,6 +323,10 @@ var/const/enterloopsanity = 100
/turf/AllowDrop()
return TRUE
// Returns false if stepping into a tile would cause harm (e.g. open space while unable to fly, water tile while a slime, lava, etc).
/turf/proc/is_safe_to_enter(mob/living/L)
return TRUE
// This is all the way up here since its the common ancestor for things that need to get replaced with a floor when an RCD is used on them.
// More specialized turfs like walls should instead override this.
// The code for applying lattices/floor tiles onto lattices could also utilize something similar in the future.

View File

@@ -28,7 +28,7 @@
if(N == /turf/space)
var/turf/below = GetBelow(src)
if(istype(below) && (air_master.has_valid_zone(below) || air_master.has_valid_zone(src)))
if(istype(below) && !istype(below,/turf/space))
N = /turf/simulated/open
var/obj/fire/old_fire = fire

View File

@@ -280,21 +280,21 @@
if("larva") M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob )
if("nymph") M.change_mob_type( /mob/living/carbon/alien/diona , null, null, delmob )
if("human") M.change_mob_type( /mob/living/carbon/human , null, null, delmob, href_list["species"])
if("slime") M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob )
if("slime") M.change_mob_type( /mob/living/simple_mob/slime/xenobio , null, null, delmob )
if("monkey") M.change_mob_type( /mob/living/carbon/human/monkey , null, null, delmob )
if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob )
if("cat") M.change_mob_type( /mob/living/simple_animal/cat , null, null, delmob )
if("runtime") M.change_mob_type( /mob/living/simple_animal/cat/fluff/Runtime , null, null, delmob )
if("corgi") M.change_mob_type( /mob/living/simple_animal/corgi , null, null, delmob )
if("ian") M.change_mob_type( /mob/living/simple_animal/corgi/Ian , null, null, delmob )
if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob )
if("coffee") M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob )
if("parrot") M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob )
if("polyparrot") M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob )
if("constructarmoured") M.change_mob_type( /mob/living/simple_animal/construct/armoured , null, null, delmob )
if("constructbuilder") M.change_mob_type( /mob/living/simple_animal/construct/builder , null, null, delmob )
if("constructwraith") M.change_mob_type( /mob/living/simple_animal/construct/wraith , null, null, delmob )
if("shade") M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob )
if("cat") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat , null, null, delmob )
if("runtime") M.change_mob_type( /mob/living/simple_mob/animal/passive/cat/runtime , null, null, delmob )
if("corgi") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi , null, null, delmob )
if("ian") M.change_mob_type( /mob/living/simple_mob/animal/passive/dog/corgi/Ian , null, null, delmob )
if("crab") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab , null, null, delmob )
if("coffee") M.change_mob_type( /mob/living/simple_mob/animal/passive/crab/Coffee , null, null, delmob )
if("parrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot , null, null, delmob )
if("polyparrot") M.change_mob_type( /mob/living/simple_mob/animal/passive/bird/parrot/poly , null, null, delmob )
if("constructarmoured") M.change_mob_type( /mob/living/simple_mob/construct/juggernaut , null, null, delmob )
if("constructbuilder") M.change_mob_type( /mob/living/simple_mob/construct/artificer , null, null, delmob )
if("constructwraith") M.change_mob_type( /mob/living/simple_mob/construct/wraith , null, null, delmob )
if("shade") M.change_mob_type( /mob/living/simple_mob/construct/shade , null, null, delmob )
/////////////////////////////////////new ban stuff

View File

@@ -127,6 +127,18 @@
usr << "<span class='notice'>Right Mouse Button on turf/obj/mob = Reset glowing</span>"
usr << "<span class='notice'>Right Mouse Button on buildmode button = Change glow properties</span>"
usr << "<span class='notice'>***********************************************************</span>"
if(9) // Control mobs with ai_holders.
usr << "<span class='notice'>***********************************************************</span>"
usr << "<span class='notice'>Left Mouse Button on AI mob = Select/Deselect mob</span>"
usr << "<span class='notice'>Left Mouse Button + alt on AI mob = Toggle hostility on mob</span>"
usr << "<span class='notice'>Left Mouse Button + ctrl on AI mob = Reset target/following/movement</span>"
usr << "<span class='notice'>Right Mouse Button on enemy mob = Command selected mobs to attack mob</span>"
usr << "<span class='notice'>Right Mouse Button on allied mob = Command selected mobs to follow mob</span>"
usr << "<span class='notice'>Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction</span>"
usr << "<span class='notice'>Right Mouse Button on tile = Command selected mobs to move to tile (will cancel if enemies are seen)</span>"
usr << "<span class='notice'>Right Mouse Button + shift on tile = Command selected mobs to reposition to tile (will not be inturrupted by enemies)</span>"
usr << "<span class='notice'>Right Mouse Button + alt on obj/turfs = Command selected mobs to attack obj/turf</span>"
usr << "<span class='notice'>***********************************************************</span>"
return 1
/obj/effect/bmode/buildquit
@@ -146,6 +158,7 @@
var/obj/effect/bmode/buildmode/buildmode = null
var/obj/effect/bmode/buildquit/buildquit = null
var/atom/movable/throw_atom = null
var/list/selected_mobs = list()
/obj/effect/bmode/buildholder/Destroy()
qdel(builddir)
@@ -157,9 +170,21 @@
qdel(buildquit)
buildquit = null
throw_atom = null
for(var/mob/living/unit in selected_mobs)
deselect_AI_mob(cl, unit)
selected_mobs.Cut()
cl = null
return ..()
/obj/effect/bmode/buildholder/proc/select_AI_mob(client/C, mob/living/unit)
selected_mobs += unit
C.images += unit.selected_image
/obj/effect/bmode/buildholder/proc/deselect_AI_mob(client/C, mob/living/unit)
selected_mobs -= unit
C.images -= unit.selected_image
/obj/effect/bmode/buildmode
icon_state = "buildmode1"
screen_loc = "NORTH,WEST+2"
@@ -210,6 +235,9 @@
master.cl.buildmode = 8
src.icon_state = "buildmode8"
if(8)
master.cl.buildmode = 9
src.icon_state = "buildmode9"
if(9)
master.cl.buildmode = 1
src.icon_state = "buildmode1"
@@ -416,6 +444,86 @@
if(pa.Find("right"))
if(object)
object.set_light(0, 0, "#FFFFFF")
if(9) // AI control
if(pa.Find("left"))
if(isliving(object))
var/mob/living/L = object
// Reset processes.
if(pa.Find("ctrl"))
if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
var/datum/ai_holder/AI = L.ai_holder
AI.forget_everything()
to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
// Toggle hostility
if(pa.Find("alt"))
if(!isnull(L.get_AI_stance()))
var/datum/ai_holder/AI = L.ai_holder
AI.hostile = !AI.hostile
to_chat(user, span("notice", "\The [L] is now [AI.hostile ? "hostile" : "passive"]."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
// Select/Deselect
if(!isnull(L.get_AI_stance()))
if(L in holder.selected_mobs)
holder.deselect_AI_mob(user.client, L)
to_chat(user, span("notice", "Deselected \the [L]."))
else
holder.select_AI_mob(user.client, L)
to_chat(user, span("notice", "Selected \the [L]."))
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
if(pa.Find("right"))
if(istype(object, /atom)) // Force attack.
var/atom/A = object
if(pa.Find("alt"))
var/i = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
AI.give_target(A)
i++
to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A]."))
return
if(isliving(object)) // Follow or attack.
var/mob/living/L = object
var/i = 0 // Attacking mobs.
var/j = 0 // Following mobs.
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
if(L.IIsAlly(unit) || !AI.hostile || pa.Find("shift"))
AI.set_follow(L)
j++
else
AI.give_target(L)
i++
var/message = "Commanded "
if(i)
message += "[i] mob\s to attack \the [L]"
if(j)
message += ", and "
else
message += "."
if(j)
message += "[j] mob\s to follow \the [L]."
to_chat(user, span("notice", message))
if(isturf(object)) // Move or reposition.
var/turf/T = object
var/i = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy.
i++
to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T]."))
/obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path)
var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]")

View File

@@ -84,23 +84,7 @@
// 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.
L.lightning_act()
// Deafen them.
if(L.get_ear_protection() < 2)

201
code/modules/ai/__readme.dm Normal file
View File

@@ -0,0 +1,201 @@
/*
[Summary]
This module contains an AI implementation designed to be (at the base level) mobtype-agnostic,
by being held inside a datum instead of being written into the mob directly. More specialized
subtypes of the base AI may be designed with a specific mob type in mind, but the base system
should be compatible with most types of mobs which have the needed Interfaces in place to
support them.
When designing a new mob, all that is needed to give a mob an AI is to set
its 'ai_holder_type' variable to the path of the AI that is desired.
[Seperation]
In previous iterations of AI systems, the AI is generally written into the mob's code directly,
which has some advantages, but often makes the code rigid, and also tied the speed of the AI
to the mob's own ticker, meaning it could only decide every two seconds.
Instead, this version has the code for the AI held inside an /datum/ai_holder object,
which is carried by the mob it controls. This gives some advantages;
All /mob/living mobs can potentially have an AI applied to them, and utilize the
same base code while adding specialized code on top.
Interfaces allow the base AI code to not need to know what particular mode it's controlling.
The processing of the AI is independant of the mob's Life() cycle, which allows for a
different clock rate.
Seperating the AI from the mob simplies the mob's code greatly.
It is more logical to think that a mob is the 'body', where as its ai_holder is
the 'mind'.
AIs can be applied or disabled on the fly by instantiating or deleting the
ai_holder, if needed.
The current implementation also has some disadvantages, but they can perhaps be resolved
in the future.
AI-driven mob movement and attack speed is tied to half-second delays due to the
AI subsystem ticking at that rate. Porting the timer subsystem and integrating
callbacks into basic AI actions (moving, attacking) can potentially resolve that.
It can be difficult to modify AI variables at mob instantiation without an ugly
delay, as the ai_holder might not exist yet.
[Flow of Processing]
Terrible visual representation here;
AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)...
-> Every 2.0s -> /datum/ai_holder/handle_strategicals() -> switch(stance)...
The AI datum is not processed by the mob itself, but instead it is directly processed
by a new AI subsystem. The AI subsystem contains a list of all active ai_holder
objects, which is iterated every tick to process each individual ai_holder
object attached to a mob.
Each ai_holder actually has two 'tracks' for processing, a 'fast' track
and a 'slow' track.
The fast track is named handle_tactics(), and is called every 0.5 seconds.
The slow track is named handle_strategicals(), and is called every 2 seconds.
When an ai_holder is iterated on inside the AI subsystem's list, it first
calls that ai_holder's handle_tactics(). It will then call that ai_holder's
handle_strategicals() every fourth tick, effectively doing so every two seconds.
Both functions do different things depending on which 'stance' the
ai_holder is in. See the Stances section for more information.
The fast track is for 'cheap' processing that needs to happen fast, such as
walking along a path, initiating an attack, or firing a gun. The rate that
it is called allows for the ai_holder to interact with the world through
its mob very often, giving a more convincing appearance of intelligence,
allowing for faster reaction times to certain events, and allowing for
variable attack speeds that would not be possible when bound to a
two second Life() cycle.
The slow track, on the other hand, is for 'expensive' processing that might
be too demanding on the CPU to do every half a second, such as
re/calculating an A* path (if the mob uses A*), or running a complicated
tension assessment to determine how brave the mob is feeling. This is the
same delay used for certain tasks in the old implementation, but it is less
noticable due to the mob appearing to do things inbetween those two seconds.
The purpose of having two tracks is to allow for 'fast' and 'slow' actions
to be more easily encapsulated, and ensures that all ai_holders are syncronized
with each other, as opposed to having individual tick counters inside all of
the ai_holder instances. It should be noted that handle_tactics() is always
called first, before handle_strategicals() every two seconds.
[Process Skipping]
An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state,
in order to avoid processing.
When busy, the AI subsystem will skip over the ai_holder until it is no
longer busy. The busy state is intended to be short-term, and is usually
toggled by the mob when doing something with a delay, so that the ai_holder
does not accidentally do something to inturrupt something important, like
a special attack.
The longer term alternative to the busy state is the sleep state. Unlike
being busy, an ai_holder set to sleep will remove itself from the
AI subsystem's list, meaning it will no longer process until something
else 'wakes' it. This is usually done when the mob dies or a client
logs into an AI-controlled mob (and the AI is not set to ignore that,
with the autopilot variable). If the mob is revived, the AI will be
awakened automatically.
The ai_holder functions, and mob functions that are called by the
ai_holder, should not be sleep()ed, as it will block the AI Subsystem
from processing the other ai_holders until the sleep() finishes.
Delays on the mob typically have set waitfor = FALSE, or spawn() is used.
[Stances]
The AI has a large number of states that it can be in, called stances.
The AI will act in a specific way depending on which stance it is in,
and only one stance can be active at a time. This effectively creates
a state pattern.
To change the stance, set_stance() is used, with the new stance as
the first argument. It should be noted that the change is not immediate,
and it will react to the change next tick instead of immediately switching
to the new stance and acting on that in the same tick. This is done to help
avoid infinite loops (I.E. Stance A switches to Stance B, which then
switches to Stance A, and so on...), and the delay is very short so
it should not be an issue.
See code/__defines/mob.dm for a list of stance defines, and descriptions
about their purpose. Generally, each stance has its own file in the AI
module folder and are mostly self contained, however some files instead
deal with general things that other stances may require, such as targeting
or movement.
[Interfaces]
Interfaces are a concept that is used to help bridge the gap between
the ai_holder, and its mob. Because the (base) ai_holder is explicitly
designed to not be specific to any type of mob, all that it knows is
that it is controlling a /mob/living mob. Some mobs work very differently,
between mob types such as /mob/living/simple_mob, /mob/living/silicon/robot,
/mob/living/carbon/human, and more.
The solution to the vast differences between mob types is to have the
mob itself deal with how to handle a specific task, such as attacking
something, talking, moving, etc. Interfaces exist to do this.
Interfaces are applied on the mob-side, and are generally specific to
that mob type. This lets the ai_holder not have to worry about specific
implementations and instead just tell the Interface that it wants to attack
something, or move into a tile. The AI does not need to know if the mob its
controlling has hands, instead that is the mob's responsibility.
Interface functions have an uppercase I at the start of the function name,
and then the function they are bridging between the AI and the mob
(if it exists), e.g. IMove(), IAttack(), ISay().
Interfaces are also used for the AI to ask its mob if it can do certain
things, without having to actually know what type of mob it is attached to.
For example, ICheckRangedAttack() tells the AI if it is possible to do a
ranged attack. For simple_mobs, they can if a ranged projectile type was set,
where as for a human mob, it could check if a gun is in a hand. For a borg,
it could check if a gun is inside their current module.
[Say List]
A /datum/say_list is a very light datum that holds a list of strings for the
AI to have their mob say based on certain conditions, such as when threatening
to kill another mob. Despite the name, a say_list also can contain emotes
and some sounds.
The reason that it is in a seperate datum is to allow for multiple mob types
to have the same text, even when inheritence cannot do that, such as
mercenaries and fake piloted mecha mobs.
The say_list datum is applied to the mob itself and not held inside the AI datum.
[Subtypes]
Some subtypes of ai_holder are more specialized, but remain compatible with
most mob types. There are many different subtypes that make the AI act different
by overriding a function, such as kiting their target, moving up close while
using ranged attacks, or running away if not cloaked.
Other subtypes are very specific about what kind of mob it controls, and trying
to apply them to a different type of mob will likely result in a lot of bugs
or ASSERT() failures. The xenobio slime AI is an example of the latter.
To use a specific subtype on a mob, all that is needed is setting the mob's
ai_holder_type to the subtype desired, and it will create that subtype when
the mob is initialize()d. Switching to a subtype 'live' will require additional
effort on the coder.
*/

View File

@@ -0,0 +1,29 @@
// Defines for the ai_intelligence var.
// Controls if the mob will do 'advanced tactics' like running from grenades.
#define AI_DUMB 1 // Be dumber than usual.
#define AI_NORMAL 2 // Default level.
#define AI_SMART 3 // Will do more processing to be a little smarter, like not walking while confused if it could risk stepping randomly onto a bad tile.
#define ai_log(M,V) if(debug_ai) ai_log_output(M,V)
// Logging level defines.
#define AI_LOG_OFF 0 // Don't show anything.
#define AI_LOG_ERROR 1 // Show logs of things likely causing the mob to not be functioning correctly.
#define AI_LOG_WARNING 2 // Show less serious but still helpful to know issues that might be causing things to work incorrectly.
#define AI_LOG_INFO 3 // Important regular events, like selecting a target or switching stances.
#define AI_LOG_DEBUG 4 // More detailed information about the flow of execution.
#define AI_LOG_TRACE 5 // Even more detailed than the last. Will absolutely flood your chatlog.
// Results of pre-movement checks.
// Todo: Move outside AI code?
#define MOVEMENT_ON_COOLDOWN -1 // Recently moved and needs to try again soon.
#define MOVEMENT_FAILED 0 // Move() returned false for whatever reason and the mob didn't move.
#define MOVEMENT_SUCCESSFUL 1 // Move() returned true and the mob hopefully moved.
// Reasons for targets to not be valid. Based on why, the AI responds differently.
#define AI_TARGET_VALID 0 // We can fight them.
#define AI_TARGET_INVIS 1 // They were in field of view but became invisible. Switch to STANCE_BLINDFIGHT if no other viable targets exist.
#define AI_TARGET_NOSIGHT 2 // No longer in field of view. Go STANCE_REPOSITION to their last known location if no other targets are seen.
#define AI_TARGET_ALLY 3 // They are an ally. Find a new target.
#define AI_TARGET_DEAD 4 // They're dead. Find a new target.
#define AI_TARGET_INVINCIBLE 5 // Target is currently unable to receive damage for whatever reason. Find a new target or wait.

View File

@@ -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

View File

@@ -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 ..()

View File

@@ -0,0 +1,290 @@
// This is a datum-based artificial intelligence for simple mobs (and possibly others) to use.
// The neat thing with having this here instead of on the mob is that it is independant of Life(), and that different mobs
// can use a more or less complex AI by giving it a different datum.
/mob/living
var/datum/ai_holder/ai_holder = null
var/ai_holder_type = null // Which ai_holder datum to give to the mob when initialized. If null, nothing happens.
/mob/living/initialize()
if(ai_holder_type)
ai_holder = new ai_holder_type(src)
return ..()
/mob/living/Destroy()
QDEL_NULL(ai_holder)
return ..()
/datum/ai_holder
var/mob/living/holder = null // The mob this datum is going to control.
var/stance = STANCE_IDLE // Determines if the mob should be doing a specific thing, e.g. attacking, following, standing around, etc.
var/intelligence_level = AI_NORMAL // Adjust to make the AI be intentionally dumber, or make it more robust (e.g. dodging grenades).
var/autopilot = FALSE // If true, the AI won't be deactivated if a client gets attached to the AI's mob.
var/busy = FALSE // If true, the ticker will skip processing this mob until this is false. Good for if you need the
// mob to stay still (e.g. delayed attacking). If you need the mob to be inactive for an extended period of time,
// consider sleeping the AI instead.
/datum/ai_holder/hostile
hostile = TRUE
/datum/ai_holder/retaliate
hostile = TRUE
retaliate = TRUE
/datum/ai_holder/New(var/new_holder)
ASSERT(new_holder)
holder = new_holder
SSai.processing += src
home_turf = get_turf(holder)
..()
/datum/ai_holder/Destroy()
holder = null
SSai.processing -= src // We might've already been asleep and removed, but byond won't care if we do this again and it saves a conditional.
home_turf = null
return ..()
// Now for the actual AI stuff.
// Makes this ai holder not get processed.
// Called automatically when the host mob is killed.
// Potential future optimization would be to sleep AIs which mobs that are far away from in-round players.
/datum/ai_holder/proc/go_sleep()
if(stance == STANCE_SLEEP)
return
forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use.
set_stance(STANCE_SLEEP)
SSai.processing -= src
// Reverses the above proc.
// Revived mobs will wake their AI if they have one.
/datum/ai_holder/proc/go_wake()
if(stance != STANCE_SLEEP)
return
if(!should_wake())
return
set_stance(STANCE_IDLE)
SSai.processing += src
/datum/ai_holder/proc/should_wake()
if(holder.client && !autopilot)
return FALSE
if(holder.stat >= DEAD)
return FALSE
return TRUE
// Resets a lot of 'memory' vars.
/datum/ai_holder/proc/forget_everything()
// Some of these might be redundant, but hopefully this prevents future bugs if that changes.
lose_follow()
lose_target()
lose_target_position()
give_up_movement()
// 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly.
/datum/ai_holder/proc/handle_tactics()
if(busy)
return
handle_special_tactic()
handle_stance_tactical()
// 'Strategical' processes that are more expensive on the CPU and so don't get run as often as the above proc, such as A* pathfinding or robust targeting.
/datum/ai_holder/proc/handle_strategicals()
if(busy)
return
handle_special_strategical()
handle_stance_strategical()
// Override these for special things without polluting the main loop.
/datum/ai_holder/proc/handle_special_tactic()
/datum/ai_holder/proc/handle_special_strategical()
/*
//AI Actions
if(!ai_inactive)
//Stanceyness
handle_stance()
//Movement
if(!stop_automated_movement && wander && !anchored) //Allowed to move?
handle_wander_movement()
//Speaking
if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter?
handle_idle_speaking()
//Resisting out buckles
if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
handle_resist()
//Resisting out of closets
if(istype(loc,/obj/structure/closet))
var/obj/structure/closet/C = loc
if(C.welded)
resist()
else
C.open()
*/
// For setting the stance WITHOUT processing it
/datum/ai_holder/proc/set_stance(var/new_stance)
ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO)
stance = new_stance
if(stance_coloring) // For debugging or really weird mobs.
stance_color()
// This is called every half a second.
/datum/ai_holder/proc/handle_stance_tactical()
ai_log("========= Fast Process Beginning ==========", AI_LOG_TRACE) // This is to make it easier visually to disinguish between 'blocks' of what a tick did.
ai_log("handle_stance_tactical() : Called.", AI_LOG_TRACE)
if(stance == STANCE_SLEEP)
ai_log("handle_stance_tactical() : Going to sleep.", AI_LOG_TRACE)
go_sleep()
return
if(target && can_see_target(target))
track_target_position()
if(stance != STANCE_DISABLED && is_disabled()) // Stunned/confused/etc
ai_log("handle_stance_tactical() : Disabled.", AI_LOG_TRACE)
set_stance(STANCE_DISABLED)
return
if(stance in STANCES_COMBAT)
// Should resist? We check this before fleeing so that we can actually flee and not be trapped in a chair.
if(holder.incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
ai_log("handle_stance_tactical() : Going to handle_resist().", AI_LOG_TRACE)
handle_resist()
else if(istype(holder.loc, /obj/structure/closet))
var/obj/structure/closet/C = holder.loc
ai_log("handle_stance_tactical() : Inside a closet. Going to attempt escape.", AI_LOG_TRACE)
if(C.sealed)
holder.resist()
else
C.open()
// Should we flee?
if(should_flee())
ai_log("handle_stance_tactical() : Going to flee.", AI_LOG_TRACE)
set_stance(STANCE_FLEE)
return
switch(stance)
if(STANCE_IDLE)
if(should_go_home())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to go home.", AI_LOG_TRACE)
go_home()
else if(should_follow_leader())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to follow leader.", AI_LOG_TRACE)
set_stance(STANCE_FOLLOW)
else if(should_wander())
ai_log("handle_stance_tactical() : STANCE_IDLE, going to wander randomly.", AI_LOG_TRACE)
handle_wander_movement()
if(STANCE_ALERT)
ai_log("handle_stance_tactical() : STANCE_ALERT, going to threaten_target().", AI_LOG_TRACE)
threaten_target()
if(STANCE_APPROACH)
ai_log("handle_stance_tactical() : STANCE_APPROACH, going to walk_to_target().", AI_LOG_TRACE)
walk_to_target()
if(STANCE_FIGHT)
ai_log("handle_stance_tactical() : STANCE_FIGHT, going to engage_target().", AI_LOG_TRACE)
engage_target()
if(STANCE_MOVE)
ai_log("handle_stance_tactical() : STANCE_MOVE, going to walk_to_destination().", AI_LOG_TRACE)
walk_to_destination()
if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order.
ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE)
walk_to_destination()
if(STANCE_FOLLOW)
ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE)
walk_to_leader()
if(STANCE_FLEE)
ai_log("handle_stance_tactical() : STANCE_FLEE, going to flee_from_target().", AI_LOG_TRACE)
flee_from_target()
if(STANCE_DISABLED)
ai_log("handle_stance_tactical() : STANCE_DISABLED.", AI_LOG_TRACE)
if(!is_disabled())
ai_log("handle_stance_tactical() : No longer disabled.", AI_LOG_TRACE)
set_stance(STANCE_IDLE)
else
handle_disabled()
ai_log("handle_stance_tactical() : Exiting.", AI_LOG_TRACE)
ai_log("========= Fast Process Ending ==========", AI_LOG_TRACE)
// This is called every two seconds.
/datum/ai_holder/proc/handle_stance_strategical()
ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE)
ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE)
switch(stance)
if(STANCE_IDLE)
if(speak_chance) // In the long loop since otherwise it wont shut up.
handle_idle_speaking()
if(hostile)
ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE)
find_target()
if(STANCE_APPROACH)
if(target)
ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE)
calculate_path(target)
if(STANCE_MOVE)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE)
if(STANCE_FOLLOW)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_FOLLOW, found target and was inturrupted.", AI_LOG_TRACE)
else if(leader)
ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE)
calculate_path(leader)
ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE)
ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE)
// Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code.
/mob/living/proc/set_AI_busy(value)
if(ai_holder)
ai_holder.busy = value
/mob/living/proc/is_AI_busy()
if(!ai_holder)
return FALSE
return ai_holder.busy
// Helper proc to check for the AI's stance.
// Returns null if there's no AI holder, or the mob has a player and autopilot is not on.
// Otherwise returns the stance.
/mob/living/proc/get_AI_stance()
if(!ai_holder)
return null
if(client && !ai_holder.autopilot)
return null
return ai_holder.stance
// Similar to above but only returns 1 or 0.
/mob/living/proc/has_AI()
return get_AI_stance() ? TRUE : FALSE
// 'Taunts' the AI into attacking the taunter.
/mob/living/proc/taunt(atom/movable/taunter, force_target_switch = FALSE)
if(ai_holder)
ai_holder.receive_taunt(taunter, force_target_switch)

View File

@@ -0,0 +1,308 @@
// This file is for actual fighting. Targeting is in a seperate file.
/datum/ai_holder
var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall.
var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true.
var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack.
var/can_breakthrough = TRUE // If false, the AI will not try to open a path to its goal, like opening doors.
var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true.
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
// This does the actual attacking.
/datum/ai_holder/proc/engage_target()
ai_log("engage_target() : Entering.", AI_LOG_DEBUG)
// Can we still see them?
// if(!target || !can_attack(target) || (!(target in list_targets())) )
if(!target || !can_attack(target))
ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE)
lose_target() // We lost them.
if(!find_target()) // If we can't get a new one, then wait for a bit and then time out.
set_stance(STANCE_IDLE)
lost_target()
ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG)
return
// if(lose_target_time + lose_target_timeout < world.time)
// ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE) // It must've been the wind.
// lost_target()
// ai_log("engage_target() : Exiting.", AI_LOG_DEBUG)
// return
// // But maybe we do one last ditch effort.
// if(!target_last_seen_turf || intelligence_level < AI_SMART)
// ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE)
// else
// ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE)
// engage_unseen_enemy()
else
ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE)
var/distance = get_dist(holder, target)
ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE)
holder.face_atom(target)
last_conflict_time = world.time
request_help() // Call our allies.
// Do a 'special' attack, if one is allowed.
// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range))
if(holder.ICheckSpecialAttack(target))
ai_log("engage_target() : Attempting a special attack.", AI_LOG_TRACE)
on_engagement(target)
if(special_attack(target)) // If this fails, then we try a regular melee/ranged attack.
ai_log("engage_target() : Successful special attack. Exiting.", AI_LOG_DEBUG)
return
// Stab them.
else if(distance <= 1 && !pointblank)
ai_log("engage_target() : Attempting a melee attack.", AI_LOG_TRACE)
on_engagement(target)
melee_attack(target)
// Shoot them.
else if(holder.ICheckRangedAttack(target) && (distance <= max_range(target)) )
on_engagement(target)
if(firing_lanes && !test_projectile_safety(target))
// Nudge them a bit, maybe they can shoot next time.
step_rand(holder)
holder.face_atom(target)
ai_log("engage_target() : Could not safely fire at target. Exiting.", AI_LOG_DEBUG)
return
ai_log("engage_target() : Attempting a ranged attack.", AI_LOG_TRACE)
ranged_attack(target)
// Run after them.
else if(!stand_ground)
ai_log("engage_target() : Target ([target]) too far away. Exiting.", AI_LOG_DEBUG)
set_stance(STANCE_APPROACH)
// We're not entirely sure how holder will do melee attacks since any /mob/living could be holder, but we don't have to care because Interfaces.
/datum/ai_holder/proc/melee_attack(atom/A)
pre_melee_attack(A)
. = holder.IAttack(A)
if(.)
post_melee_attack(A)
// Ditto.
/datum/ai_holder/proc/ranged_attack(atom/A)
pre_ranged_attack(A)
. = holder.IRangedAttack(A)
if(.)
post_ranged_attack(A)
// Most mobs probably won't have this defined but we don't care.
/datum/ai_holder/proc/special_attack(atom/movable/AM)
pre_special_attack(AM)
. = holder.ISpecialAttack(AM)
if(.)
post_special_attack(AM)
// Called when within striking/shooting distance, however cooldown is not considered.
// Override to do things like move in a random step for evasiveness.
// Note that this is called BEFORE the attack.
/datum/ai_holder/proc/on_engagement(atom/A)
// Called before a ranged attack is attempted.
/datum/ai_holder/proc/pre_ranged_attack(atom/A)
// Called before a melee attack is attempted.
/datum/ai_holder/proc/pre_melee_attack(atom/A)
// Called before a 'special' attack is attempted.
/datum/ai_holder/proc/pre_special_attack(atom/A)
// Called after a successful (IE not on cooldown) ranged attack.
// Note that this is not whether the projectile actually hit, just that one was launched.
/datum/ai_holder/proc/post_ranged_attack(atom/A)
// Ditto but for melee.
/datum/ai_holder/proc/post_melee_attack(atom/A)
// And one more for special snowflake attacks.
/datum/ai_holder/proc/post_special_attack(atom/A)
// Used to make sure projectiles will probably hit the target and not the wall or a friend.
/datum/ai_holder/proc/test_projectile_safety(atom/movable/AM)
var/mob/living/L = check_trajectory(AM, holder) // This isn't always reliable but its better than the previous method.
// world << "Checked trajectory, would hit [L]."
if(istype(L)) // Did we hit a mob?
// world << "Hit [L]."
if(holder.IIsAlly(L))
// world << "Would hit ally, canceling."
return FALSE // We would hit a friend!
// world << "Won't threaten ally, firing."
return TRUE // Otherwise we don't care, even if its not the intended target.
else
if(!isliving(AM)) // If the original target was an object, then let it happen if it doesn't threaten an ally.
// world << "Targeting object, ignoring and firing."
return TRUE
// world << "Not sure."
return !conserve_ammo // If we have infinite ammo than shooting the wall isn't so bad, but otherwise lets not.
// Test if we are within range to attempt an attack, melee or ranged.
/datum/ai_holder/proc/within_range(atom/movable/AM)
var/distance = get_dist(holder, AM)
if(distance <= 1)
return TRUE // Can melee.
else if(holder.ICheckRangedAttack(AM) && distance <= max_range(AM))
return TRUE // Can shoot.
return FALSE
// Determines how close the AI will move to its target.
/datum/ai_holder/proc/closest_distance(atom/movable/AM)
return max(max_range(AM) - 1, 1) // Max range -1 just because we don't want to constantly get kited
// Can be used to conditionally do a ranged or melee attack.
/datum/ai_holder/proc/max_range(atom/movable/AM)
return holder.ICheckRangedAttack(AM) ? 7 : 1
// Goes to the target, to attack them.
// Called when in STANCE_APPROACH.
/datum/ai_holder/proc/walk_to_target()
ai_log("walk_to_target() : Entering.", AI_LOG_DEBUG)
// Make sure we can still chase/attack them.
if(!target || !can_attack(target))
ai_log("walk_to_target() : Lost target.", AI_LOG_INFO)
if(!find_target())
lost_target()
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
else
ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO)
// Find out where we're going.
var/get_to = closest_distance(target)
var/distance = get_dist(holder, target)
ai_log("walk_to_target() : get_to is [get_to].", AI_LOG_TRACE)
// We're here!
// Special case: Our holder has a special attack that is ranged, but normally the holder uses melee.
// If that happens, we'll switch to STANCE_FIGHT so they can use it. If the special attack is limited, they'll likely switch back next tick.
if(distance <= get_to || holder.ICheckSpecialAttack(target))
ai_log("walk_to_target() : Within range.", AI_LOG_INFO)
forget_path()
set_stance(STANCE_FIGHT)
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
// Otherwise keep walking.
if(!stand_ground)
walk_path(target, get_to)
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
// Resists out of things.
// Sometimes there are times you want your mob to be buckled to something, so override this for when that is needed.
/datum/ai_holder/proc/handle_resist()
holder.resist()
// Used to break through windows and barriers to a target on the other side.
// This does two passes, so that if its just a public access door, the windows nearby don't need to be smashed.
/datum/ai_holder/proc/breakthrough(atom/target_atom)
ai_log("breakthrough() : Entering", AI_LOG_TRACE)
if(!can_breakthrough)
ai_log("breakthrough() : Not allowed to breakthrough. Exiting.", AI_LOG_TRACE)
return FALSE
if(!isturf(holder.loc))
ai_log("breakthrough() : Trapped inside \the [holder.loc]. Exiting.", AI_LOG_TRACE)
return FALSE
var/dir_to_target = get_dir(holder, target_atom)
holder.face_atom(target_atom)
ai_log("breakthrough() : Exiting", AI_LOG_DEBUG)
// Sometimes the mob will try to hit something diagonally, and generally this fails.
// So instead we will try two more times with some adjustments if the attack fails.
var/list/directions_to_try = list(
dir_to_target,
turn(dir_to_target, 45),
turn(dir_to_target, -45)
)
ai_log("breakthrough() : Starting peaceful pass.", AI_LOG_DEBUG)
var/result = FALSE
// First, we will try to peacefully make a path, I.E opening a door we have access to.
for(var/direction in directions_to_try)
result = destroy_surroundings(direction, violent = FALSE)
if(result)
break
// Alright, lets smash some shit instead, if it didn't work and we're allowed to be violent.
if(!result && can_violently_breakthrough())
ai_log("breakthrough() : Starting violent pass.", AI_LOG_DEBUG)
for(var/direction in directions_to_try)
result = destroy_surroundings(direction, violent = TRUE)
if(result)
break
ai_log("breakthrough() : Exiting with [result].", AI_LOG_TRACE)
return result
// Despite the name, this can also be used to help clear a path without any destruction.
/datum/ai_holder/proc/destroy_surroundings(direction, violent = TRUE)
ai_log("destroy_surroundings() : Entering.", AI_LOG_TRACE)
if(!direction)
direction = pick(cardinal) // FLAIL WILDLY
ai_log("destroy_surroundings() : No direction given, picked [direction] randomly.", AI_LOG_DEBUG)
var/turf/problem_turf = get_step(holder, direction)
// First, give peace a chance.
if(!violent)
ai_log("destroy_surroundings() : Going to try to peacefully clear [problem_turf].", AI_LOG_DEBUG)
for(var/obj/machinery/door/D in problem_turf)
if(D.density && holder.Adjacent(D) && D.allowed(holder) && D.operable())
// First, try to open the door if possible without smashing it. We might have access.
ai_log("destroy_surroundings() : Opening closed door.", AI_LOG_INFO)
return D.open()
// Peace has failed us, can we just smash the things in the way?
else
ai_log("destroy_surroundings() : Going to try to violently clear [problem_turf].", AI_LOG_DEBUG)
// First, kill windows in the way.
for(var/obj/structure/window/W in problem_turf)
if(W.dir == reverse_dir[holder.dir]) // So that windows get smashed in the right order
ai_log("destroy_surroundings() : Attacking side window.", AI_LOG_INFO)
return holder.IAttack(W)
else if(W.is_fulltile())
ai_log("destroy_surroundings() : Attacking full tile window.", AI_LOG_INFO)
return holder.IAttack(W)
// Kill hull shields in the way.
for(var/obj/effect/energy_field/shield in problem_turf)
if(shield.density) // Don't attack shields that are already down.
ai_log("destroy_surroundings() : Attacking hull shield.", AI_LOG_INFO)
return holder.IAttack(shield)
// Kill common obstacle in the way like tables.
var/obj/structure/obstacle = locate(/obj/structure, problem_turf)
if(istype(obstacle, /obj/structure/window) || istype(obstacle, /obj/structure/closet) || istype(obstacle, /obj/structure/table) || istype(obstacle, /obj/structure/grille))
ai_log("destroy_surroundings() : Attacking generic structure.", AI_LOG_INFO)
return holder.IAttack(obstacle)
for(var/obj/machinery/door/D in problem_turf) // Required since firelocks take up the same turf.
if(D.density)
ai_log("destroy_surroundings() : Attacking closed door.", AI_LOG_INFO)
return holder.IAttack(D)
ai_log("destroy_surroundings() : Exiting due to nothing to attack.", AI_LOG_INFO)
return FALSE // Nothing to attack.
// Override for special behaviour.
/datum/ai_holder/proc/can_violently_breakthrough()
return violent_breakthrough

View File

@@ -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)

View File

@@ -0,0 +1,134 @@
// Contains code for speaking and emoting.
/datum/ai_holder
var/threaten = FALSE // If hostile and sees a valid target, gives a 'warning' to the target before beginning the attack.
var/threatening = FALSE // If the mob actually gave the warning, checked so it doesn't constantly yell every tick.
var/threaten_delay = 3 SECONDS // How long a 'threat' lasts, until actual fighting starts. If null, the mob never starts the fight but still does the threat.
var/threaten_timeout = 1 MINUTE // If the mob threatens someone, they leave, and then come back before this timeout period, the mob escalates to fighting immediately.
var/last_conflict_time = null // Last occurance of fighting being used, in world.time.
var/last_threaten_time = null // Ditto but only for threats.
var/speak_chance = 0 // Probability that the mob talks (this is 'X in 200' chance since even 1/100 is pretty noisy)
/datum/ai_holder/proc/should_threaten()
if(!threaten)
return FALSE // We don't negotiate.
if(target in attackers)
return FALSE // They (or someone like them) attacked us before, escalate immediately.
if(!will_threaten(target))
return FALSE // Pointless to threaten an animal, a mindless drone, or an object.
if(stance in STANCES_COMBAT)
return FALSE // We're probably already fighting or recently fought if not in these stances.
if(last_threaten_time && threaten_delay && last_conflict_time + threaten_timeout > world.time)
return FALSE // We threatened someone recently, so lets show them we mean business.
return TRUE // Lets give them a chance to choose wisely and walk away.
/datum/ai_holder/proc/threaten_target()
holder.face_atom(target) // Constantly face the target.
if(!threatening) // First tick.
threatening = TRUE
last_threaten_time = world.time
if(holder.say_list)
holder.ISay(safepick(holder.say_list.say_threaten))
playsound(holder.loc, holder.say_list.threaten_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
playsound(target.loc, holder.say_list.threaten_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
else // Otherwise we are waiting for them to go away or to wait long enough for escalate.
if(target in list_targets()) // Are they still visible?
var/should_escalate = FALSE
if(threaten_delay && last_threaten_time + threaten_delay < world.time) // Waited too long.
should_escalate = TRUE
else if(last_conflict_time + threaten_timeout > world.time) // We got attacked while threatening them.
should_escalate = TRUE
if(should_escalate)
threatening = FALSE
set_stance(STANCE_APPROACH)
if(holder.say_list)
holder.ISay(safepick(holder.say_list.say_escalate))
else
return // Wait a bit.
else // They left, or so we think.
threatening = FALSE
set_stance(STANCE_IDLE)
if(holder.say_list)
holder.ISay(safepick(holder.say_list.say_stand_down))
playsound(holder.loc, holder.say_list.stand_down_sound, 50, 1) // We do this twice to make the sound -very- noticable to the target.
playsound(target.loc, holder.say_list.stand_down_sound, 50, 1) // Actual aim-mode also does that so at least it's consistant.
// Determines what is deserving of a warning when STANCE_ALERT is active.
/datum/ai_holder/proc/will_threaten(mob/living/the_target)
if(!isliving(the_target))
return FALSE // Turrets don't give a fuck so neither will we.
/*
// Find a nice way of doing this later.
if(istype(the_target, /mob/living/simple_mob) && istype(holder, /mob/living/simple_mob))
var/mob/living/simple_mob/us = holder
var/mob/living/simple_mob/them = target
if(them.intelligence_level < us.intelligence_level) // Todo: Bitflag these.
return FALSE // Humanoids don't care about drones/animals/etc. Drones don't care about animals, and so on.
*/
return TRUE
// Temp defines to make the below code a bit more readable.
#define COMM_SAY "say"
#define COMM_AUDIBLE_EMOTE "audible emote"
#define COMM_VISUAL_EMOTE "visual emote"
/datum/ai_holder/proc/handle_idle_speaking()
if(rand(0,200) < speak_chance)
// Check if anyone is around to 'appreciate' what we say.
var/alone = TRUE
for(var/m in viewers(holder))
var/mob/M = m
if(M.client)
alone = FALSE
break
if(alone) // Forever alone. No point doing anything else.
return
var/list/comm_types = list() // What kinds of things can we do?
if(!holder.say_list)
return
if(holder.say_list.speak.len)
comm_types += COMM_SAY
if(holder.say_list.emote_hear.len)
comm_types += COMM_AUDIBLE_EMOTE
if(holder.say_list.emote_see.len)
comm_types += COMM_VISUAL_EMOTE
if(!comm_types.len)
return // All the relevant lists are empty, so do nothing.
switch(pick(comm_types))
if(COMM_SAY)
holder.ISay(safepick(holder.say_list.speak))
if(COMM_AUDIBLE_EMOTE)
holder.audible_emote(safepick(holder.say_list.emote_hear))
if(COMM_VISUAL_EMOTE)
holder.visible_emote(safepick(holder.say_list.emote_see))
#undef COMM_SAY
#undef COMM_AUDIBLE_EMOTE
#undef COMM_VISUAL_EMOTE
// Handles the holder hearing a mob's say()
// Does nothing by default, override this proc for special behavior.
/datum/ai_holder/proc/on_hear_say(mob/living/speaker, message)
return
// This is to make responses feel a bit more natural and not instant.
/datum/ai_holder/proc/delayed_say(var/message, var/mob/speak_to)
spawn(rand(1 SECOND, 2 SECONDS))
if(!src || !holder || !can_act()) // We might've died/got deleted/etc in the meantime.
return
if(speak_to)
holder.face_atom(speak_to)
holder.ISay(message)

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