mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-07-14 09:43:14 +01:00
7dc13d532f
* 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.
486 lines
13 KiB
Plaintext
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
|