/mob/living/bot
name = "Bot"
health = 20
maxHealth = 20
icon = 'icons/obj/aibots.dmi'
layer = MOB_LAYER
universal_speak = 1
density = FALSE
makes_dirt = FALSE // No more dirt from Beepsky
var/obj/item/weapon/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/item/device/paicard/paicard = null
var/obj/access_scanner = null
var/list/req_access = list()
var/list/req_one_access = list()
var/atom/target = null
var/list/ignore_past = list() //CHOMPStation edit
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/target_patience = 5
var/frustration = 0
var/max_frustration = 0
/mob/living/bot/New()
..()
update_icons()
default_language = GLOB.all_languages[LANGUAGE_GALCOM]
botcard = new /obj/item/weapon/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
// Make sure mapped in units start turned on.
/mob/living/bot/Initialize()
. = ..()
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 && !paicard)
spawn(0)
handleAI()
/*
/mob/living/bot/examine(mob/user)
. = ..()
if(health < maxHealth)
if(health > maxHealth/3)
. += "[src]'s parts look loose."
else
. += "[src]'s parts look very loose!"
else
. += "[src] is in pristine condition."
. += span_notice("Its maintenance panel is [open ? "open" : "closed"].")
. += span_info("You can use a screwdriver to [open ? "close" : "open"] it.")
. += span_notice("Its control panel is [locked ? "locked" : "unlocked"].")
if(paicard)
. += span_notice("It has a pAI device installed.")
if(open)
. += span_info("You can use a crowbar to remove it.")
*/
/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, "Controls are now [locked ? "locked." : "unlocked."]")
attack_hand(user)
if(emagged)
to_chat(user, "ERROR! SYSTEMS COMPROMISED!")
else
if(open)
to_chat(user, "Please close the access panel before locking it.")
else
to_chat(user, "Access denied.")
return
else if(O.is_screwdriver())
if(!locked)
open = !open
to_chat(user, "Maintenance panel is now [open ? "opened" : "closed"].")
playsound(src, O.usesound, 50, 1)
else
to_chat(user, "You need to unlock the controls first.")
return
else if(istype(O, /obj/item/weapon/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("[user] repairs [src].","You repair [src].")
playsound(src, O.usesound, 50, 1)
else
to_chat(user, "Unable to repair with the maintenance panel closed.")
else
to_chat(user, "[src] does not need a repair.")
return
else if(istype(O, /obj/item/device/assembly/prox_sensor) && emagged)
if(open)
to_chat(user, "You repair the bot's systems.")
emagged = 0
qdel(O)
else
to_chat(user, "Unable to repair with the maintenance panel closed.")
else if(istype(O, /obj/item/device/paicard))
if(open)
insertpai(user, O)
to_chat(user, span_notice("You slot the card into \the [initial(src.name)]."))
else
to_chat(user, span_notice("You must open the panel first!"))
else if(O.is_crowbar())
if(open && paicard)
to_chat(user, span_notice("You are attempting to remove the pAI.."))
if(do_after(user,10 * O.toolspeed))
ejectpai(user)
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
/mob/living/bot/proc/handleAI()
if(ignore_list.len)
for(var/atom/A in ignore_list)
if(!A || !A.loc || prob(1))
//CHOMPEdit Begin
if(A in ignore_past)
if(prob(10/ignore_past[A]) || !A || !A.loc)
ignore_past[A]++
ignore_list -= A
else
ignore_past[A] = 1
ignore_list -= A
//CHOMPEdit End
handleRegular()
var/panic_speed_mod = 0
if(panic_on_alert)
panic_speed_mod = handlePanic()
if(target && confirmTarget(target))
if(Adjacent(target))
handleAdjacentTarget()
else
handleRangedTarget()
if(!wait_if_pulled || !pulledby)
for(var/i = 1 to (target_speed + panic_speed_mod))
sleep(20 / (target_speed + panic_speed_mod + 1))
stepToTarget()
if(max_frustration && frustration > max_frustration * target_speed)
handleFrustrated(1)
else
resetTarget()
lookForTargets()
if(will_patrol && !pulledby && !target)
if(patrol_path && patrol_path.len)
for(var/i = 1 to (patrol_speed + panic_speed_mod))
sleep(20 / (patrol_speed + 1))
handlePatrol()
if(max_frustration && frustration > max_frustration * patrol_speed)
handleFrustrated(0)
else
startPatrol()
else
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))
if(step_towards(src, pick(can_go)))
return
for(var/mob in loc)
if(istype(mob, /mob/living/bot) && mob != src) // Same as above, but we also don't want to have bots ontop of bots. Cleanbots shouldn't stack >:(
var/turf/my_turf = get_turf(src)
var/list/can_go = my_turf.CardinalTurfsWithAccess(botcard)
if(LAZYLEN(can_go))
if(step_towards(src, pick(can_go)))
return
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++ //CHOMPEdit
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()
//CHOMPEdit Begin
if(makeStep(patrol_path))
frustration = 0
else if(max_frustration)
frustration++
//CHOMPEdit End
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
else if(target in ignore_past) //CHOMPEdit
ignore_past.Remove(target) //CHOMPEdit
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()
//CHOMPEdit frustration = 0
//CHOMPEdit 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()
update_canmove()
return 1
/mob/living/bot/proc/turn_off()
on = 0
busy = 0 // If ever stuck... reboot!
set_light(0)
update_icons()
update_canmove()
/mob/living/bot/proc/explode()
if(paicard)
ejectpai()
release_vore_contents()
qdel(src)
/mob/living/bot/is_sentient()
if(paicard)
return TRUE
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/weapon/card/id/ID)
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))
L.Add(T)
return L
// Similar to above but not restricted to just cardinal directions.
/turf/proc/TurfsWithAccess(var/obj/item/weapon/card/id/ID)
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))
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/weapon/card/id/ID)
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) && !LinkBlockedWithAccess(iStep,B,ID))
return 0
var/pStep = get_step(A,adir&(EAST|WEST))
if(!LinkBlockedWithAccess(A,pStep,ID) && !LinkBlockedWithAccess(pStep,B,ID))
return 0
return 1
if(DirBlockedWithAccess(A,adir, ID))
return 1
if(DirBlockedWithAccess(B,rdir, ID))
return 1
for(var/obj/O in B)
if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER))
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/weapon/card/id/ID)
for(var/obj/structure/window/D in loc)
if(!D.density) continue
if(D.dir == SOUTHWEST) return 1
if(D.dir == dir) return 1
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
/mob/living/bot/update_canmove()
..()
canmove = on
return canmove
/mob/living/bot/proc/insertpai(mob/user, obj/item/device/paicard/card)
//var/obj/item/paicard/card = I
var/mob/living/silicon/pai/AI = card.pai
if(paicard)
to_chat(user, span_notice("This bot is already under PAI Control!"))
return
if(!istype(card)) // TODO: Add sleevecard support.
return
if(client)
to_chat(user, span_notice("Higher levels of processing are already present!"))
return
if(!card.pai)
to_chat(user, span_notice("This card does not currently have a personality!"))
return
paicard = card
user.unEquip(card)
card.forceMove(src)
src.ckey = AI.ckey
name = AI.name
ooc_notes = AI.ooc_notes
to_chat(src, span_notice("You feel a tingle in your circuits as your systems interface with \the [initial(src.name)]."))
if(AI.idcard.access)
botcard.access |= AI.idcard.access
/mob/living/bot/proc/ejectpai(mob/user)
if(paicard)
var/mob/living/silicon/pai/AI = paicard.pai
AI.ckey = src.ckey
AI.ooc_notes = ooc_notes
paicard.forceMove(src.loc)
paicard = null
name = initial(name)
botcard.access = botcard_access.Copy()
to_chat(AI, span_notice("You feel a tad claustrophobic as your mind closes back into your card, ejecting from \the [initial(src.name)]."))
if(user)
to_chat(user, span_notice("You eject the card from \the [initial(src.name)]."))
/mob/living/bot/verb/bot_nom(var/mob/living/T in oview(1))
set name = "Bot Nom"
set category = "Bot Commands"
set desc = "Allows you to eat someone. Yum."
if (stat != CONSCIOUS)
return
return feed_grabbed_to_self(src,T)
/mob/living/bot/verb/ejectself()
set name = "Eject pAI"
set category = "Bot Commands"
set desc = "Eject your card, return to smole."
return ejectpai()
/mob/living/bot/Login()
no_vore = FALSE // ROBOT VORE
init_vore() // ROBOT VORE
verbs |= /mob/living/proc/insidePanel
return ..()
/mob/living/bot/Logout()
no_vore = TRUE // ROBOT VORE
release_vore_contents()
init_vore() // ROBOT VORE
verbs -= /mob/living/proc/insidePanel
no_vore = TRUE
devourable = FALSE
feeding = FALSE
can_be_drop_pred = FALSE
return ..()