Files
Mechoid 7dc13d532f Demolishing Mobs (#9264)
* Work on the Bluespace Worm, Again.

Space worms propagate their sprites/tooltips/meat types/segment types from the head back.
Space worms have modified organ lists, only having a brain in their head.

Bluespace worms have a unique sprite done by Sypsoti/Schnayy

Bluespace worms have more xeno-organs in addition.

Bluespace worms convert eaten things to Magmellite, instead of Phoron.

* Target through walls.

Distance traveled when not hitting a solid structure during charge is based on the length of the worm.

Bluespace Worms drop xeno hide.

* can_demolish includes trees and barricades

simplemobs can now actually attack trees

* Truncated version of my Bluespace Worm pr.
Simplemob AI can be permitted to demolish walls, mineral turfs, and other common obstructions.

Simplemob AI can be given vision through walls, to make greater use of wall demolishing.

Spaceworm now has an AI controller with x-ray and demolishing capability to tunnel through rock / into buildings, and a wind-up charge that devours impacted mobs or structures.

* Thermic spiders are demolishers, and able to detect prey through walls.

* Inert.

* Decreased chance for thermic spiders to spawn, matching carriers and webslingers.

* Adjust for continued response & thoughts.

Thermic dropped to only Demolishing by default. Still a barricade buster, not a wallhacker.
Phorogenic bumped to be Demolishing. It's big, purple, and unlikely to care there's a twig in the way of its target.

Thermic now has an xray variant for PoI or adminspawn, similar to carriers having the recursion variant.

* Spiders:
Demolishing Phorogen and Thermic moved to subtypes

AI targeting only loops over range once, typecache manually checked for machines instead of making a new list from range again.

* AI targeting dist check instead of range

== FALSE check is a ! prefix, me.

Worm no longer early returns off its overshoot charge call as it does nothing but end the proc regardless of a successful movement

* Pain in two letters.
2024-01-14 14:10:24 -08:00

486 lines
13 KiB
Plaintext

/mob/living/bot
name = "Bot"
health = 20
maxHealth = 20
icon = 'icons/obj/aibots.dmi'
layer = MOB_LAYER
universal_speak = 1
density = 0
makes_dirt = FALSE // No more dirt from Beepsky
var/obj/item/card/id/botcard = null
var/list/botcard_access = list()
var/on = 1
var/open = 0
var/locked = 1
var/emagged = 0
var/light_strength = 3
var/busy = 0
var/obj/access_scanner = null
var/list/req_access = list()
var/list/req_one_access = list()
var/atom/target = null
var/list/ignore_list = list()
var/list/patrol_path = list()
var/list/target_path = list()
var/turf/obstacle = null
var/wait_if_pulled = 0 // Only applies to moving to the target
var/will_patrol = 0 // If set to 1, will patrol, duh
var/patrol_speed = 1 // How many times per tick we move when patrolling
var/target_speed = 2 // Ditto for chasing the target
var/panic_on_alert = FALSE // Will the bot go faster when the alert level is raised?
var/min_target_dist = 1 // How close we try to get to the target
var/max_target_dist = 50 // How far we are willing to go
var/max_patrol_dist = 250
var/started_moving_along_path = 0
var/target_patience = 5
var/frustration = 0
var/max_frustration = 0
/mob/living/bot/Initialize()
. = ..()
update_icons()
default_language = GLOB.all_languages[LANGUAGE_GALCOM]
botcard = new /obj/item/card/id(src)
botcard.access = botcard_access.Copy()
access_scanner = new /obj(src)
access_scanner.req_access = req_access.Copy()
access_scanner.req_one_access = req_one_access.Copy()
if(!using_map.bot_patrolling)
will_patrol = FALSE
if(on)
turn_on() // Update lights and other stuff
/mob/living/bot/Life()
..()
if(health <= 0)
death()
return
SetWeakened(0)
SetStunned(0)
SetParalysis(0)
if(on && !client && !busy)
handleAI()
/mob/living/bot/updatehealth()
if(status_flags & GODMODE)
health = getMaxHealth()
set_stat(CONSCIOUS)
else
health = getMaxHealth() - getFireLoss() - getBruteLoss()
oxyloss = 0
toxloss = 0
cloneloss = 0
halloss = 0
/mob/living/bot/death()
explode()
/mob/living/bot/attackby(var/obj/item/O, var/mob/user)
if(O.GetID())
if(access_scanner.allowed(user) && !open)
locked = !locked
to_chat(user, "<span class='notice'>Controls are now [locked ? "locked." : "unlocked."]</span>")
attack_hand(user)
if(emagged)
to_chat(user, "<span class='warning'>ERROR! SYSTEMS COMPROMISED!</span>")
else
if(open)
to_chat(user, "<span class='warning'>Please close the access panel before locking it.</span>")
else
to_chat(user, "<span class='warning'>Access denied.</span>")
return
else if(O.is_screwdriver())
if(!locked)
open = !open
to_chat(user, "<span class='notice'>Maintenance panel is now [open ? "opened" : "closed"].</span>")
playsound(src, O.usesound, 50, 1)
else
to_chat(user, "<span class='notice'>You need to unlock the controls first.</span>")
return
else if(istype(O, /obj/item/weldingtool))
if(health < getMaxHealth())
if(open)
if(getBruteLoss() < 10)
bruteloss = 0
else
bruteloss = bruteloss - 10
if(getFireLoss() < 10)
fireloss = 0
else
fireloss = fireloss - 10
updatehealth()
user.visible_message("<span class='notice'>[user] repairs [src].</span>","<span class='notice'>You repair [src].</span>")
playsound(src, O.usesound, 50, 1)
else
to_chat(user, "<span class='notice'>Unable to repair with the maintenance panel closed.</span>")
else
to_chat(user, "<span class='notice'>[src] does not need a repair.</span>")
return
else if(istype(O, /obj/item/assembly/prox_sensor) && emagged)
if(open)
to_chat(user, "<span class='notice'>You repair the bot's systems.</span>")
emagged = 0
qdel(O)
else
to_chat(user, "<span class='notice'>Unable to repair with the maintenance panel closed.</span>")
else
..()
/mob/living/bot/attack_ai(var/mob/user)
return attack_hand(user)
/mob/living/bot/say_quote(var/message, var/datum/language/speaking = null)
return "beeps"
/mob/living/bot/speech_bubble_appearance()
return "machine"
/mob/living/bot/Bump(var/atom/A)
if(on && botcard && istype(A, /obj/machinery/door))
var/obj/machinery/door/D = A
if(!istype(D, /obj/machinery/door/firedoor) && !istype(D, /obj/machinery/door/blast) && !istype(D, /obj/machinery/door/airlock/lift) && D.check_access(botcard))
D.open()
else
..()
/mob/living/bot/emag_act(var/remaining_charges, var/mob/user)
return 0
// Some boilerplate here and below, but this is a quickfix to stop Ater nuking the mob type, so heigh ho.
/mob/living/bot/proc/stepTowardsTarget(var/delay, var/panic_speed_mod)
set waitfor = FALSE
var/started_moving_at = world.time
started_moving_along_path = started_moving_at
if(!wait_if_pulled || !pulledby)
for(var/i = 1 to (target_speed + panic_speed_mod))
sleep(delay)
if(started_moving_along_path != started_moving_at)
return // We have started another move iteration.
stepToTarget() // Calls A*, very expensive; is not necessarily safe. In a perfect world this would
if(TICK_CHECK) // not be behind a set waitfor = FALSE, but we do not live in a perfect world and
break // SSmobs fires too slowly for chases to work using A* without sleeping.
if(started_moving_along_path != started_moving_at)
return // We have started another move iteration.
if(max_frustration && frustration > max_frustration * target_speed)
handleFrustrated(1)
/mob/living/bot/proc/stepAlongPatrol(var/delay, var/panic_speed_mod)
set waitfor = FALSE
var/started_moving_at = world.time
started_moving_along_path = started_moving_at
for(var/i = 1 to (patrol_speed + panic_speed_mod))
sleep(delay)
if(started_moving_along_path != started_moving_at)
return // We have started another move iteration.
handlePatrol() // Does not call A*; is more or less safe.
if(started_moving_along_path != started_moving_at)
return // We have started another move iteration.
if(max_frustration && frustration > max_frustration * patrol_speed)
handleFrustrated(0)
/mob/living/bot/proc/handleAI()
set waitfor = FALSE
if(ignore_list.len)
for(var/atom/A in ignore_list)
if(!A || !A.loc || prob(1))
ignore_list -= A
handleRegular()
if(target && confirmTarget(target))
if(Adjacent(target))
handleAdjacentTarget()
else
handleRangedTarget()
var/panic_speed_mod = panic_on_alert ? handlePanic() : 0
stepTowardsTarget(round(20 / (target_speed + panic_speed_mod + 1)), panic_speed_mod)
return
resetTarget()
lookForTargets()
if(will_patrol && !pulledby && !target)
if(patrol_path && patrol_path.len)
stepAlongPatrol(round(20 / (patrol_speed + 1)), (panic_on_alert ? handlePanic() : 0))
return
startPatrol()
return
if((locate(/obj/machinery/door) in loc) && !pulledby) //Don't hang around blocking doors, but don't run off if someone tries to pull us through one.
var/turf/my_turf = get_turf(src)
var/list/can_go = my_turf.CardinalTurfsWithAccess(botcard)
if(!LAZYLEN(can_go) || !step_towards(src, pick(can_go)))
handleIdle()
/mob/living/bot/proc/handleRegular()
return
/mob/living/bot/proc/handleAdjacentTarget()
return
/mob/living/bot/proc/handleRangedTarget()
return
/mob/living/bot/proc/handlePanic() // Speed modification based on alert level.
. = 0
switch(get_security_level())
if("green")
. = 0
if("yellow")
. = 0
if("violet")
. = 0
if("orange")
. = 0
if("blue")
. = 1
if("red")
. = 2
if("delta")
. = 2
return .
/mob/living/bot/proc/stepToTarget()
if(!target || !target.loc)
return
if(get_dist(src, target) > min_target_dist)
if(!target_path.len || get_turf(target) != target_path[target_path.len])
calcTargetPath()
if(makeStep(target_path))
frustration = 0
else if(max_frustration)
++frustration
return
/mob/living/bot/proc/handleFrustrated(has_target)
obstacle = null
if (has_target)
if (length(target_path))
obstacle = target_path[1]
else if (length(patrol_path))
obstacle = patrol_path[1]
target_path = list()
patrol_path = list()
/mob/living/bot/proc/lookForTargets()
return
/mob/living/bot/proc/confirmTarget(var/atom/A)
if(A.invisibility >= INVISIBILITY_LEVEL_ONE)
return 0
if(A in ignore_list)
return 0
if(!A.loc)
return 0
return 1
/mob/living/bot/proc/handlePatrol()
makeStep(patrol_path)
return
/mob/living/bot/proc/startPatrol()
var/turf/T = getPatrolTurf()
if(T)
patrol_path = AStar(get_turf(loc), T, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_patrol_dist, id = botcard, exclude = obstacle)
if(!patrol_path)
patrol_path = list()
obstacle = null
return
/mob/living/bot/proc/getPatrolTurf()
var/minDist = INFINITY
var/obj/machinery/navbeacon/targ = locate() in get_turf(src)
if(!targ)
for(var/obj/machinery/navbeacon/N in navbeacons)
if(!N.codes["patrol"])
continue
if(get_dist(src, N) < minDist)
minDist = get_dist(src, N)
targ = N
if(targ && targ.codes["next_patrol"])
for(var/obj/machinery/navbeacon/N in navbeacons)
if(N.location == targ.codes["next_patrol"])
targ = N
break
if(targ)
return get_turf(targ)
return null
/mob/living/bot/proc/handleIdle()
return
/mob/living/bot/proc/calcTargetPath()
target_path = AStar(get_turf(loc), get_turf(target), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_target_dist, id = botcard, exclude = obstacle)
if(!target_path)
if(target && target.loc)
ignore_list |= target
resetTarget()
obstacle = null
return
/mob/living/bot/proc/makeStep(var/list/path)
if(!path.len)
return 0
var/turf/T = path[1]
if(get_turf(src) == T)
path -= T
return makeStep(path)
return step_towards(src, T)
/mob/living/bot/proc/resetTarget()
target = null
target_path = list()
frustration = 0
obstacle = null
/mob/living/bot/proc/turn_on()
if(stat)
return 0
on = 1
set_light(light_strength)
update_icons()
resetTarget()
patrol_path = list()
ignore_list = list()
return 1
/mob/living/bot/proc/turn_off()
on = 0
busy = 0 // If ever stuck... reboot!
set_light(0)
update_icons()
/mob/living/bot/proc/explode()
qdel(src)
/mob/living/bot/is_sentient()
return FALSE
/******************************************************************/
// Navigation procs
// Used for A-star pathfinding
// Returns the surrounding cardinal turfs with open links
// Including through doors openable with the ID
/turf/proc/CardinalTurfsWithAccess(var/obj/item/card/id/ID, flags)
var/L[] = new()
// for(var/turf/simulated/t in oview(src,1))
for(var/d in cardinal)
var/turf/T = get_step(src, d)
if(istype(T) && !T.density)
if(!LinkBlockedWithAccess(src, T, ID, flags))
L.Add(T)
return L
// Same as the above, but ignores density of turfs.
/turf/proc/CardinalTurfsWithAccessNoDensity(var/obj/item/card/id/ID)
var/L[] = new()
for(var/d in cardinal)
var/turf/T = get_step(src, d)
if(istype(T))
if(!LinkBlockedWithAccess(src, T, ID))
L.Add(T)
return L
// Similar to above but not restricted to just cardinal directions.
/turf/proc/TurfsWithAccess(var/obj/item/card/id/ID, flags)
var/L[] = new()
for(var/d in alldirs)
var/turf/T = get_step(src, d)
if(istype(T) && !T.density)
if(!LinkBlockedWithAccess(src, T, ID, flags))
L.Add(T)
return L
// Returns true if a link between A and B is blocked
// Movement through doors allowed if ID has access
/proc/LinkBlockedWithAccess(turf/A, turf/B, obj/item/card/id/ID, flags)
if(A == null || B == null) return 1
var/adir = get_dir(A,B)
var/rdir = get_dir(B,A)
if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal
var/iStep = get_step(A,adir&(NORTH|SOUTH))
if(!LinkBlockedWithAccess(A,iStep, ID, flags) && !LinkBlockedWithAccess(iStep,B,ID, flags))
return 0
var/pStep = get_step(A,adir&(EAST|WEST))
if(!LinkBlockedWithAccess(A,pStep,ID, flags) && !LinkBlockedWithAccess(pStep,B,ID, flags))
return 0
return 1
if(DirBlockedWithAccess(A,adir, ID, flags))
return 1
if(DirBlockedWithAccess(B,rdir, ID, flags))
return 1
for(var/obj/O in B)
if(O.density && !istype(O, /obj/machinery/door) && !(O.atom_flags & ATOM_HAS_TRANSITION_PRIORITY))
return 1
return 0
// Returns true if direction is blocked from loc
// Checks doors against access with given ID
/proc/DirBlockedWithAccess(turf/loc,var/dir,var/obj/item/card/id/ID, flags)
for(var/obj/structure/window/D in loc)
if (flags & ASTAR_BLOCKED_BY_WINDOWS)
if (D.is_fulltile())
return TRUE
else
if(!D.density)
continue
if(D.dir == SOUTHWEST)
return TRUE
if (D.dir & dir)
return TRUE
for(var/obj/machinery/door/D in loc)
if(!D.density) continue
if(istype(D, /obj/machinery/door/airlock))
var/obj/machinery/door/airlock/A = D
if(!A.can_open()) return 1
if(istype(D, /obj/machinery/door/window))
if( dir & D.dir ) return !D.check_access(ID)
//if((dir & SOUTH) && (D.dir & (EAST|WEST))) return !D.check_access(ID)
//if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return !D.check_access(ID)
else return !D.check_access(ID) // it's a real, air blocking door
return 0
/mob/living/bot/isSynthetic() //Robots are synthetic, no?
return 1