Merge pull request #1133 from Kelenius/coolBots

Overhauls bot AI
This commit is contained in:
Neerti
2016-03-30 02:23:28 -04:00
10 changed files with 468 additions and 840 deletions
+144 -1
View File
@@ -6,6 +6,7 @@
layer = MOB_LAYER
universal_speak = 1
density = 0
var/obj/item/weapon/card/id/botcard = null
var/list/botcard_access = list()
var/on = 1
@@ -13,11 +14,30 @@
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 // Not a setting - whether or no this type of bots patrols at all
var/patrol_speed = 1 // How many times per tick we move when patrolling
var/target_speed = 2 // Ditto for chasing the target
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()
@@ -40,6 +60,9 @@
stunned = 0
paralysis = 0
if(on && !client && !busy)
handleAI()
/mob/living/bot/updatehealth()
if(status_flags & GODMODE)
health = maxHealth
@@ -109,6 +132,123 @@
/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))
ignore_list -= A
handleRegular()
if(target && confirmTarget(target))
if(Adjacent(target))
handleAdjacentTarget()
else
handleRangedTarget()
if(!wait_if_pulled || !pulledby)
for(var/i = 1 to target_speed)
stepToTarget()
if(i < target_speed)
sleep(20 / target_speed)
if(max_frustration && frustration > max_frustration * target_speed)
handleFrustrated(1)
else
resetTarget()
lookForTargets()
if(will_patrol && !pulledby && !target)
if(patrol_path.len)
for(var/i = 1 to patrol_speed)
handlePatrol()
if(i < patrol_speed)
sleep(20 / patrol_speed)
if(max_frustration && frustration > max_frustration * patrol_speed)
handleFrustrated(0)
else
startPatrol()
else
handleIdle()
/mob/living/bot/proc/handleRegular()
return
/mob/living/bot/proc/handleAdjacentTarget()
return
/mob/living/bot/proc/handleRangedTarget()
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(var/targ)
obstacle = targ ? target_path[1] : patrol_path[1]
target_path = list()
patrol_path = list()
return
/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()
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
@@ -121,6 +261,9 @@
on = 0
set_light(0)
update_icons()
resetTarget()
patrol_path = list()
ignore_list = list()
/mob/living/bot/proc/explode()
qdel(src)
@@ -141,7 +284,7 @@
// for(var/turf/simulated/t in oview(src,1))
for(var/d in cardinal)
var/turf/simulated/T = get_step(src, d)
var/turf/T = get_step(src, d)
if(istype(T) && !T.density)
if(!LinkBlockedWithAccess(src, T, ID))
L.Add(T)
+23 -140
View File
@@ -6,18 +6,8 @@
botcard_access = list(access_janitor, access_maint_tunnels)
locked = 0 // Start unlocked so roboticist can set them to patrol.
var/obj/effect/decal/cleanable/target
var/list/path = list()
var/list/patrol_path = list()
var/list/ignorelist = list()
var/obj/cleanbot_listener/listener = null
var/beacon_freq = 1445 // navigation beacon frequency
var/signal_sent = 0
var/closest_dist
var/next_dest
var/next_dest_loc
wait_if_pulled = 1
min_target_dist = 0
var/cleaning = 0
var/screwloose = 0
@@ -26,48 +16,11 @@
var/blood = 1
var/list/target_types = list()
var/maximum_search_range = 7
/mob/living/bot/cleanbot/New()
..()
get_targets()
listener = new /obj/cleanbot_listener(src)
listener.cleanbot = src
if(radio_controller)
radio_controller.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS)
/mob/living/bot/cleanbot/proc/handle_target()
if(loc == target.loc)
if(!cleaning)
UnarmedAttack(target)
return 1
if(!path.len)
// spawn(0)
path = AStar(loc, target.loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
custom_emote(2, "[src] can't reach the target and is giving up.")
target = null
path = list()
return
if(path.len)
step_to(src, path[1])
path -= path[1]
return 1
return
/mob/living/bot/cleanbot/Life()
..()
if(!on)
return
if(client)
return
if(cleaning)
return
/mob/living/bot/cleanbot/handleIdle()
if(!screwloose && !oddbutton && prob(5))
custom_emote(2, "makes an excited beeping booping sound!")
@@ -79,68 +32,27 @@
if(oddbutton && prob(5)) // Make a big mess
visible_message("Something flies out of [src]. He seems to be acting oddly.")
var/obj/effect/decal/cleanable/blood/gibs/gib = new /obj/effect/decal/cleanable/blood/gibs(loc)
ignorelist += gib
ignore_list += gib
spawn(600)
ignorelist -= gib
ignore_list -= gib
// Find a target
if(pulledby) // Don't wiggle if someone pulls you
patrol_path = list()
return
var/found_spot
search_loop:
for(var/i=0, i <= maximum_search_range, i++)
for(var/obj/effect/decal/cleanable/D in view(i, src))
if(D in ignorelist)
continue
for(var/T in target_types)
if(istype(D, T))
patrol_path = list()
target = D
found_spot = handle_target()
if (found_spot)
break search_loop
else
target = null
continue // no need to check the other types
if(!found_spot && !target) // No targets in range
if(!patrol_path || !patrol_path.len)
if(!signal_sent || signal_sent > world.time + 200) // Waited enough or didn't send yet
var/datum/radio_frequency/frequency = radio_controller.return_frequency(beacon_freq)
if(!frequency)
return
closest_dist = 9999
next_dest = null
next_dest_loc = null
var/datum/signal/signal = new()
signal.source = src
signal.transmission_method = 1
signal.data = list("findbeakon" = "patrol")
frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS)
signal_sent = world.time
else
if(next_dest)
next_dest_loc = listener.memorized[next_dest]
if(next_dest_loc)
patrol_path = AStar(loc, next_dest_loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id = botcard, exclude = null)
signal_sent = 0
else
if(pulledby) // Don't wiggle if someone pulls you
patrol_path = list()
return
if(patrol_path[1] == loc)
patrol_path -= patrol_path[1]
var/moved = step_towards(src, patrol_path[1])
if(moved)
patrol_path -= patrol_path[1]
/mob/living/bot/cleanbot/lookForTargets()
for(var/obj/effect/decal/cleanable/D in view(world.view, src)) // There was some odd code to make it start with nearest decals, it's unnecessary, this works
if(confirmTarget(D))
target = D
return
/mob/living/bot/cleanbot/confirmTarget(var/obj/effect/decal/cleanable/D)
if(!..())
return 0
for(var/T in target_types)
if(istype(D, T))
return 1
return 0
/mob/living/bot/cleanbot/handleAdjacentTarget()
if(get_turf(target) == src.loc)
UnarmedAttack(target)
/mob/living/bot/cleanbot/UnarmedAttack(var/obj/effect/decal/cleanable/D, var/proximity)
if(!..())
@@ -152,7 +64,7 @@
if(D.loc != loc)
return
cleaning = 1
busy = 1
custom_emote(2, "begins to clean up \the [D]")
update_icons()
var/cleantime = istype(D, /obj/effect/decal/cleanable/dirt) ? 10 : 50
@@ -165,7 +77,7 @@
qdel(D)
if(D == target)
target = null
cleaning = 0
busy = 0
update_icons()
/mob/living/bot/cleanbot/explode()
@@ -185,17 +97,11 @@
return
/mob/living/bot/cleanbot/update_icons()
if(cleaning)
if(busy)
icon_state = "cleanbot-c"
else
icon_state = "cleanbot[on]"
/mob/living/bot/cleanbot/turn_off()
..()
target = null
path = list()
patrol_path = list()
/mob/living/bot/cleanbot/attack_hand(var/mob/user)
var/dat
dat += "<TT><B>Automatic Station Cleaner v1.0</B></TT><BR><BR>"
@@ -230,10 +136,6 @@
if("patrol")
should_patrol = !should_patrol
patrol_path = null
if("freq")
var/freq = text2num(input("Select frequency for navigation beacons", "Frequnecy", num2text(beacon_freq / 10))) * 10
if (freq > 0)
beacon_freq = freq
if("screw")
screwloose = !screwloose
usr << "<span class='notice'>You twiddle the screw.</span>"
@@ -264,25 +166,6 @@
if(blood)
target_types += /obj/effect/decal/cleanable/blood
/* Radio object that listens to signals */
/obj/cleanbot_listener
var/mob/living/bot/cleanbot/cleanbot = null
var/list/memorized = list()
/obj/cleanbot_listener/receive_signal(var/datum/signal/signal)
var/recv = signal.data["beacon"]
var/valid = signal.data["patrol"]
if(!recv || !valid || !cleanbot)
return
var/dist = get_dist(cleanbot, signal.source.loc)
memorized[recv] = signal.source.loc
if(dist < cleanbot.closest_dist) // We check all signals, choosing the closest beakon; then we move to the NEXT one after the closest one
cleanbot.closest_dist = dist
cleanbot.next_dest = signal.data["next_patrol"]
/* Assembly */
/obj/item/weapon/bucket_sensor
+4 -2
View File
@@ -7,7 +7,6 @@
health = 100
maxHealth = 100
bot_version = "2.5"
is_ranged = 1
preparing_arrest_sounds = new()
@@ -20,7 +19,7 @@
var/last_shot = 0
/mob/living/bot/secbot/ed209/update_icons()
if(on && is_attacking)
if(on && busy)
icon_state = "ed209-c"
else
icon_state = "ed209[on]"
@@ -50,6 +49,9 @@
new /obj/effect/decal/cleanable/blood/oil(Tsec)
qdel(src)
/mob/living/bot/secbot/ed209/handleRangedTarget()
RangedAttack(target)
/mob/living/bot/secbot/ed209/RangedAttack(var/atom/A)
if(last_shot + shot_delay > world.time)
src << "You are not ready to fire yet!"
+82 -83
View File
@@ -22,17 +22,12 @@
var/obj/structure/reagent_dispensers/watertank/tank
var/attacking = 0
var/list/path = list()
var/atom/target
var/frustration = 0
/mob/living/bot/farmbot/New()
..()
spawn(5)
tank = locate() in contents
if(!tank)
tank = new /obj/structure/reagent_dispensers/watertank(src)
/mob/living/bot/farmbot/New(var/newloc, var/newTank)
..(newloc)
if(!newTank)
newTank = new /obj/structure/reagent_dispensers/watertank(src)
tank = newTank
tank.forceMove(src)
/mob/living/bot/farmbot/attack_hand(var/mob/user as mob)
. = ..()
@@ -110,63 +105,55 @@
else
icon_state = "farmbot[on]"
/mob/living/bot/farmbot/Life()
..()
if(!on)
return
/mob/living/bot/farmbot/handleRegular()
if(emagged && prob(1))
flick("farmbot_broke", src)
if(client)
return
if(target)
if(Adjacent(target))
UnarmedAttack(target)
path = list()
target = null
else
if(path.len && frustration < 5)
if(path[1] == loc)
path -= path[1]
var/t = step_towards(src, path[1])
if(t)
path -= path[1]
else
++frustration
else
path = list()
target = null
/mob/living/bot/farmbot/handleAdjacentTarget()
UnarmedAttack(target)
/mob/living/bot/farmbot/lookForTargets()
if(emagged)
for(var/mob/living/carbon/human/H in view(7, src))
target = H
return
else
if(emagged)
for(var/mob/living/carbon/human/H in view(7, src))
target = H
break
else
for(var/obj/machinery/portable_atmospherics/hydroponics/tray in view(7, src))
if(process_tray(tray))
target = tray
frustration = 0
break
if(!target && refills_water && tank && tank.reagents.total_volume < tank.reagents.maximum_volume)
for(var/obj/structure/sink/source in view(7, src))
target = source
frustration = 0
break
if(target)
var/t = get_dir(target, src) // Turf with the tray is impassable, so a* can't navigate directly to it
path = AStar(loc, get_step(target, t), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
path = list()
for(var/obj/machinery/portable_atmospherics/hydroponics/tray in view(7, src))
if(confirmTarget(tray))
target = tray
return
if(!target && refills_water && tank && tank.reagents.total_volume < tank.reagents.maximum_volume)
for(var/obj/structure/sink/source in view(7, src))
target = source
return
/mob/living/bot/farmbot/calcTargetPath() // We need to land NEXT to the tray, because the tray itself is impassable
for(var/trayDir in list(NORTH, SOUTH, EAST, WEST))
target_path = AStar(get_turf(loc), get_step(get_turf(target), trayDir), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_target_dist, id = botcard)
if(target_path)
break
if(!target_path)
ignore_list |= target
target = null
target_path = list()
return
/mob/living/bot/farmbot/stepToTarget() // Same reason
var/turf/T = get_turf(target)
if(!target_path.len || !T.Adjacent(target_path[target_path.len]))
calcTargetPath()
makeStep(target_path)
return
/mob/living/bot/farmbot/UnarmedAttack(var/atom/A, var/proximity)
if(!..())
return
if(attacking)
if(busy)
return
if(istype(A, /obj/machinery/portable_atmospherics/hydroponics))
var/obj/machinery/portable_atmospherics/hydroponics/T = A
var/t = process_tray(T)
var/t = confirmTarget(T)
switch(t)
if(0)
return
@@ -174,7 +161,7 @@
action = "water" // Needs a better one
update_icons()
visible_message("<span class='notice'>[src] starts [T.dead? "removing the plant from" : "harvesting"] \the [A].</span>")
attacking = 1
busy = 1
if(do_after(src, 30))
visible_message("<span class='notice'>[src] [T.dead? "removes the plant from" : "harvests"] \the [A].</span>")
T.attack_hand(src)
@@ -182,7 +169,7 @@
action = "water"
update_icons()
visible_message("<span class='notice'>[src] starts watering \the [A].</span>")
attacking = 1
busy = 1
if(do_after(src, 30))
playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
visible_message("<span class='notice'>[src] waters \the [A].</span>")
@@ -191,7 +178,7 @@
action = "hoe"
update_icons()
visible_message("<span class='notice'>[src] starts uprooting the weeds in \the [A].</span>")
attacking = 1
busy = 1
if(do_after(src, 30))
visible_message("<span class='notice'>[src] uproots the weeds in \the [A].</span>")
T.weedlevel = 0
@@ -199,11 +186,11 @@
action = "fertile"
update_icons()
visible_message("<span class='notice'>[src] starts fertilizing \the [A].</span>")
attacking = 1
busy = 1
if(do_after(src, 30))
visible_message("<span class='notice'>[src] waters \the [A].</span>")
visible_message("<span class='notice'>[src] fertilizes \the [A].</span>")
T.reagents.add_reagent("ammonia", 10)
attacking = 0
busy = 0
action = ""
update_icons()
T.update_icon()
@@ -213,20 +200,20 @@
action = "water"
update_icons()
visible_message("<span class='notice'>[src] starts refilling its tank from \the [A].</span>")
attacking = 1
busy = 1
while(do_after(src, 10) && tank.reagents.total_volume < tank.reagents.maximum_volume)
tank.reagents.add_reagent("water", 10)
if(prob(5))
playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
attacking = 0
busy = 0
action = ""
update_icons()
visible_message("<span class='notice'>[src] finishes refilling its tank.</span>")
else if(emagged && ishuman(A))
var/action = pick("weed", "water")
attacking = 1
busy = 1
spawn(50) // Some delay
attacking = 0
busy = 0
switch(action)
if("weed")
flick("farmbot_hoe", src)
@@ -238,7 +225,8 @@
A.attack_generic(src, 5, t)
if("water")
flick("farmbot_water", src)
visible_message("<span class='danger'>[src] splashes [A] with water!</span>") // That's it. RP effect.
visible_message("<span class='danger'>[src] splashes [A] with water!</span>")
tank.reagents.splash(A, 100)
/mob/living/bot/farmbot/explode()
visible_message("<span class='danger'>[src] blows apart!</span>")
@@ -261,8 +249,22 @@
qdel(src)
return
/mob/living/bot/farmbot/proc/process_tray(var/obj/machinery/portable_atmospherics/hydroponics/tray)
if(!tray || !istype(tray))
/mob/living/bot/farmbot/confirmTarget(var/atom/targ)
if(!..())
return 0
if(emagged && ishuman(targ))
if(targ in view(world.view, src))
return 1
return 0
if(istype(targ, /obj/structure/sink))
if(!tank || tank.reagents.total_volume >= tank.reagents.maximum_volume)
return 0
return 1
var/obj/machinery/portable_atmospherics/hydroponics/tray = targ
if(!istype(tray))
return 0
if(tray.closed_system || !tray.seed)
@@ -291,28 +293,28 @@
icon_state = "water_arm"
var/build_step = 0
var/created_name = "Farmbot"
var/obj/tank
w_class = 3.0
New()
..()
spawn(4) // If an admin spawned it, it won't have a watertank it, so lets make one for em!
var tank = locate(/obj/structure/reagent_dispensers/watertank) in contents
if(!tank)
new /obj/structure/reagent_dispensers/watertank(src)
/obj/item/weapon/farmbot_arm_assembly/New(var/newloc, var/theTank)
..(newloc)
if(!theTank) // If an admin spawned it, it won't have a watertank it, so lets make one for em!
tank = new /obj/structure/reagent_dispensers/watertank(src)
else
tank = theTank
tank.forceMove(src)
/obj/structure/reagent_dispensers/watertank/attackby(var/obj/item/robot_parts/S, mob/user as mob)
if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm)))
..()
return
var/obj/item/weapon/farmbot_arm_assembly/A = new /obj/item/weapon/farmbot_arm_assembly(loc)
user << "You add the robot arm to [src]."
loc = A //Place the water tank into the assembly, it will be needed for the finished bot
user.drop_from_inventory(S)
qdel(S)
new /obj/item/weapon/farmbot_arm_assembly(loc, src)
/obj/item/weapon/farmbot_arm_assembly/attackby(obj/item/weapon/W as obj, mob/user as mob)
..()
if((istype(W, /obj/item/device/analyzer/plant_analyzer)) && (build_step == 0))
@@ -339,10 +341,7 @@
else if((isprox(W)) && (build_step == 3))
build_step++
user << "You complete the Farmbot! Beep boop."
var/mob/living/bot/farmbot/S = new /mob/living/bot/farmbot(get_turf(src))
for(var/obj/structure/reagent_dispensers/watertank/wTank in contents)
wTank.loc = S
S.tank = wTank
var/mob/living/bot/farmbot/S = new /mob/living/bot/farmbot(get_turf(src), tank)
S.name = created_name
user.remove_from_mob(W)
qdel(W)
+84 -96
View File
@@ -3,22 +3,20 @@
desc = "A little floor repairing robot, he looks so excited!"
icon_state = "floorbot0"
req_access = list(access_construction)
wait_if_pulled = 1
min_target_dist = 0
var/amount = 10 // 1 for tile, 2 for lattice
var/maxAmount = 60
var/tilemake = 0 // When it reaches 100, bot makes a tile
var/repairing = 0
var/improvefloors = 0
var/eattiles = 0
var/maketiles = 0
var/targetdirection = null
var/list/path = list()
var/list/ignorelist = list()
var/turf/target
var/floor_build_type = /decl/flooring/tiling // Basic steel floor.
/mob/living/bot/floorbot/update_icons()
if(repairing)
if(busy)
icon_state = "floorbot-c"
else if(amount > 0)
icon_state = "floorbot[on]"
@@ -31,7 +29,7 @@
dat += "<TT><B>Automatic Station Floor Repairer v1.0</B></TT><BR><BR>"
dat += "Status: <A href='?src=\ref[src];operation=start'>[src.on ? "On" : "Off"]</A><BR>"
dat += "Maintenance panel is [open ? "opened" : "closed"]<BR>"
//dat += "Tiles left: [amount]<BR>"
dat += "Tiles left: [amount]<BR>"
dat += "Behvaiour controls are [locked ? "locked" : "unlocked"]<BR>"
if(!locked || issilicon(user))
dat += "Improves floors: <A href='?src=\ref[src];operation=improve'>[improvefloors ? "Yes" : "No"]</A><BR>"
@@ -89,100 +87,90 @@
targetdirection = null
attack_hand(usr)
/mob/living/bot/floorbot/turn_off()
..()
target = null
path = list()
ignorelist = list()
/mob/living/bot/floorbot/Life()
..()
if(!on)
return
/mob/living/bot/floorbot/handleRegular()
++tilemake
if(tilemake >= 100)
tilemake = 0
addTiles(1)
if(client)
return
if(prob(5))
if(prob(1))
custom_emote(2, "makes an excited booping beeping sound!")
if(ignorelist.len) // Don't stick forever
for(var/T in ignorelist)
if(prob(1))
ignorelist -= T
if(amount && !emagged)
if(!target && targetdirection) // Building a bridge
var/turf/T = get_step(src, targetdirection)
while(T in range(src))
if(istype(T, /turf/space))
target = T
break
T = get_step(T, targetdirection)
if(!target) // Fixing floors
for(var/turf/T in view(src))
if(T.loc.name == "Space")
continue
if(T in ignorelist)
continue
if(istype(T, /turf/space))
if(get_turf(T) == loc || prob(40)) // So they target the same tile all the time
target = T
if(improvefloors && istype(T, /turf/simulated/floor))
var/turf/simulated/floor/F = T
if(!F.flooring && (get_turf(T) == loc || prob(40)))
target = T
if(emagged) // Time to griff
for(var/turf/simulated/floor/D in view(src))
if(D.loc.name == "Space")
continue
if(D in ignorelist)
continue
target = D
break
if(!target && amount < maxAmount && eattiles || maketiles) // Eat tiles
if(eattiles)
for(var/obj/item/stack/tile/floor/T in view(src))
if(T in ignorelist)
continue
target = T
break
if(maketiles && !target)
for(var/obj/item/stack/material/steel/T in view(src))
if(T in ignorelist)
continue
target = T
break
if(target && get_turf(target) == loc)
/mob/living/bot/floorbot/handleAdjacentTarget()
if(get_turf(target) == src.loc)
UnarmedAttack(target)
if(target && get_turf(target) != loc && !path.len)
spawn(0)
path = AStar(loc, get_turf(target), /turf/proc/AdjacentTurfsSpace, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
path = list()
ignorelist += target
target = null
/mob/living/bot/floorbot/lookForTargets()
if(emagged) // Time to griff
for(var/turf/simulated/floor/D in view(src))
if(confirmTarget(D))
target = D
return
if(path.len)
step_to(src, path[1])
path -= path[1]
else if(amount)
if(targetdirection) // Building a bridge
var/turf/T = get_step(src, targetdirection)
while(T in range(world.view, src))
if(confirmTarget(T))
target = T
return
T = get_step(T, targetdirection)
else // Fixing floors
for(var/turf/space/T in view(src)) // Breaches are of higher priority
if(confirmTarget(T))
target = T
return
for(var/turf/simulated/mineral/floor/T in view(src)) // Asteroids are of smaller priority
if(confirmTarget(T))
target = T
return
if(improvefloors)
for(var/turf/simulated/floor/T in view(src))
if(confirmTarget(T))
target = T
return
if(amount < maxAmount && (eattiles || maketiles))
for(var/obj/item/stack/S in view(src))
if(confirmTarget(S))
target = S
return
/mob/living/bot/floorbot/confirmTarget(var/atom/A) // The fact that we do some checks twice may seem confusing but remember that the bot's settings may be toggled while it's moving and we want them to stop in that case
if(!..())
return 0
if(istype(A, /obj/item/stack/tile/floor))
return (amount < maxAmount && eattiles)
if(istype(A, /obj/item/stack/material/steel))
return (amount < maxAmount && maketiles)
if(A.loc.name == "Space")
return 0
if(emagged)
return (istype(A, /turf/simulated/floor))
if(!amount)
return 0
if(istype(A, /turf/space))
return 1
if(istype(A, /turf/simulated/mineral/floor))
return 1
var/turf/simulated/floor/T = A
return (istype(T) && improvefloors && !T.flooring && (get_turf(T) == loc || prob(40)))
/mob/living/bot/floorbot/UnarmedAttack(var/atom/A, var/proximity)
if(!..())
return
if(repairing)
if(busy)
return
if(get_turf(A) != loc)
@@ -190,9 +178,9 @@
if(emagged && istype(A, /turf/simulated/floor))
var/turf/simulated/floor/F = A
repairing = 1
busy = 1
update_icons()
if(F.is_plating())
if(F.flooring)
visible_message("<span class='warning'>[src] begins to tear the floor tile from the floor!</span>")
if(do_after(src, 50))
F.break_tile_to_plating()
@@ -203,15 +191,15 @@
F.ReplaceWithLattice()
addTiles(1)
target = null
repairing = 0
busy = 0
update_icons()
else if(istype(A, /turf/space))
else if(istype(A, /turf/space) || istype(A, /turf/simulated/mineral/floor))
var/building = 2
if(locate(/obj/structure/lattice, A))
building = 1
if(amount < building)
return
repairing = 1
busy = 1
update_icons()
visible_message("<span class='notice'>[src] begins to repair the hole.</span>")
if(do_after(src, 50))
@@ -223,12 +211,12 @@
I = PoolOrNew(/obj/item/stack/rods, src)
A.attackby(I, src)
target = null
repairing = 0
busy = 0
update_icons()
else if(istype(A, /turf/simulated/floor))
var/turf/simulated/floor/F = A
if(!F.flooring && amount)
repairing = 1
busy = 1
update_icons()
visible_message("<span class='notice'>[src] begins to improve the floor.</span>")
if(do_after(src, 50))
@@ -236,12 +224,12 @@
F.set_flooring(get_flooring_data(floor_build_type))
addTiles(-1)
target = null
repairing = 0
busy = 0
update_icons()
else if(istype(A, /obj/item/stack/tile/floor) && amount < maxAmount)
var/obj/item/stack/tile/floor/T = A
visible_message("<span class='notice'>[src] begins to collect tiles.</span>")
repairing = 1
busy = 1
update_icons()
if(do_after(src, 20))
if(T)
@@ -249,13 +237,13 @@
T.use(eaten)
addTiles(eaten)
target = null
repairing = 0
busy = 0
update_icons()
else if(istype(A, /obj/item/stack/material) && amount + 4 <= maxAmount)
var/obj/item/stack/material/M = A
if(M.get_material_name() == DEFAULT_WALL_MATERIAL)
visible_message("<span class='notice'>[src] begins to make tiles.</span>")
repairing = 1
busy = 1
update_icons()
if(do_after(50))
if(M)
+37 -63
View File
@@ -3,21 +3,16 @@
desc = "A little medical robot. He looks somewhat underwhelmed."
icon_state = "medibot0"
req_access = list(access_medical)
var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits.
botcard_access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics)
var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits.
//AI vars
var/frustration = 0
var/list/path = list()
var/mob/living/carbon/human/patient = null
var/mob/ignored = list() // Used by emag
var/last_newpatient_speak = 0
var/vocal = 1
//Healing vars
var/obj/item/weapon/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents.
var/currently_healing = 0
var/injection_amount = 15 //How much reagent do we inject at a time?
var/heal_threshold = 10 //Start healing when they have this much damage in a category
var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents.
@@ -29,49 +24,26 @@
var/treatment_emag = "toxin"
var/declare_treatment = 0 //When attempting to treat a patient, should it notify everyone wearing medhuds?
/mob/living/bot/medbot/Life()
..()
/mob/living/bot/medbot/handleIdle()
if(vocal && prob(1))
var/message = pick("Radar, put a mask on!", "There's always a catch, and it's the best there is.", "I knew it, I should've been a plastic surgeon.", "What kind of medbay is this? Everyone's dropping like dead flies.", "Delicious!")
say(message)
if(!on)
return
/mob/living/bot/medbot/handleAdjacentTarget()
UnarmedAttack(target)
if(!client)
/mob/living/bot/medbot/lookForTargets()
for(var/mob/living/carbon/human/H in view(7, src)) // Time to find a patient!
if(confirmTarget(H))
target = H
if(last_newpatient_speak + 300 < world.time)
var/message = pick("Hey, [H.name]! Hold on, I'm coming.", "Wait [H.name]! I want to help!", "[H.name], you appear to be injured!")
say(message)
custom_emote(1, "points at [H.name].")
last_newpatient_speak = world.time
break
if(vocal && prob(1))
var/message = pick("Radar, put a mask on!", "There's always a catch, and it's the best there is.", "I knew it, I should've been a plastic surgeon.", "What kind of medbay is this? Everyone's dropping like dead flies.", "Delicious!")
say(message)
if(patient)
if(Adjacent(patient))
if(!currently_healing)
UnarmedAttack(patient)
else
if(path.len && (get_dist(patient, path[path.len]) > 2)) // We have a path, but it's off
path = list()
if(!path.len && (get_dist(src, patient) > 1))
spawn(0)
path = AStar(loc, get_turf(patient), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
path = list()
if(path.len)
step_to(src, path[1])
path -= path[1]
++frustration
if(get_dist(src, patient) > 7 || frustration > 8)
patient = null
else
for(var/mob/living/carbon/human/H in view(7, src)) // Time to find a patient!
if(valid_healing_target(H))
patient = H
frustration = 0
if(last_newpatient_speak + 300 < world.time)
var/message = pick("Hey, [H.name]! Hold on, I'm coming.", "Wait [H.name]! I want to help!", "[H.name], you appear to be injured!")
say(message)
custom_emote(1, "points at [H.name].")
last_newpatient_speak = world.time
break
/mob/living/bot/medbot/UnarmedAttack(var/mob/living/carbon/human/H, var/proximity)
/mob/living/bot/medbot/UnarmedAttack(var/mob/living/carbon/human/H)
if(!..())
return
@@ -81,25 +53,27 @@
if(!istype(H))
return
if(busy)
return
if(H.stat == DEAD)
var/death_message = pick("No! NO!", "Live, damnit! LIVE!", "I... I've never lost a patient before. Not today, I mean.")
say(death_message)
patient = null
target = null
return
var/t = valid_healing_target(H)
var/t = confirmTarget(H)
if(!t)
var/message = pick("All patched up!", "An apple a day keeps me away.", "Feel better soon!")
say(message)
patient = null
target = null
return
icon_state = "medibots"
visible_message("<span class='warning'>[src] is trying to inject [H]!</span>")
if(declare_treatment)
var/area/location = get_area(src)
broadcast_medical_hud_message("[src] is treating <b>[H]</b> in <b>[location]</b>", src)
currently_healing = 1
busy = 1
update_icons()
if(do_mob(src, H, 30))
if(t == 1)
@@ -107,14 +81,14 @@
else
H.reagents.add_reagent(t, injection_amount)
visible_message("<span class='warning'>[src] injects [H] with the syringe!</span>")
currently_healing = 0
busy = 0
update_icons()
/mob/living/bot/medbot/update_icons()
overlays.Cut()
if(skin)
overlays += image('icons/obj/aibots.dmi', "medskin_[skin]")
if(currently_healing)
if(busy)
icon_state = "medibots"
else
icon_state = "medibot[on]"
@@ -226,13 +200,13 @@
user << "<span class='warning'>You short out [src]'s reagent synthesis circuits.</span>"
visible_message("<span class='warning'>[src] buzzes oddly!</span>")
flick("medibot_spark", src)
patient = null
currently_healing = 0
target = null
busy = 0
emagged = 1
on = 1
update_icons()
. = 1
ignored |= user
ignore_list |= user
/mob/living/bot/medbot/explode()
on = 0
@@ -255,15 +229,15 @@
qdel(src)
return
/mob/living/bot/medbot/proc/valid_healing_target(var/mob/living/carbon/human/H)
/mob/living/bot/medbot/confirmTarget(var/mob/living/carbon/human/H)
if(!..())
return 0
if(H.stat == DEAD) // He's dead, Jim
return null
return 0
if(H.suiciding)
return null
if(H in ignored)
return null
return 0
if(emagged)
return treatment_emag
+43 -137
View File
@@ -17,23 +17,22 @@
maxHealth = 150
mob_bump_flag = HEAVY
min_target_dist = 0
max_target_dist = 250
target_speed = 3
max_frustration = 5
botcard_access = list(access_maint_tunnels, access_mailsorting, access_cargo, access_cargo_bot, access_qm, access_mining, access_mining_station)
var/busy = 0
var/mode = MULE_IDLE
var/atom/movable/load
var/paused = 0
var/crates_only = 1
var/auto_return = 1
var/safety = 1
var/list/path = list()
var/frustration = 0
var/turf/target
var/targetName
var/turf/home
var/homeName
var/turf/obstacle
var/global/amount = 0
@@ -71,19 +70,6 @@
dat += "Power: [on ? "On" : "Off"]<BR>"
if(!open)
dat += "Status: "
switch(mode)
if(MULE_IDLE)
dat += "Ready"
if(MULE_MOVING, MULE_UNLOAD, MULE_PATH_DONE)
dat += "Navigating"
if(MULE_UNLOAD)
dat += "Unloading"
if(MULE_LOST)
dat += "Processing commands"
if(MULE_CALC_MIN to MULE_CALC_MAX)
dat += "Calculating navigation path"
dat += "<BR>Current Load: [load ? load.name : "<i>none</i>"]<BR>"
if(locked)
@@ -177,10 +163,9 @@
/mob/living/bot/mulebot/proc/obeyCommand(var/command)
switch(command)
if("Home")
mode = MULE_IDLE
resetTarget()
target = home
targetName = "Home"
mode = MULE_LOST
if("SetD")
var/new_dest
var/list/beaconlist = new()
@@ -192,13 +177,13 @@
else
alert("No destination beacons available.")
if(new_dest)
resetTarget()
target = get_turf(beaconlist[new_dest])
targetName = new_dest
if("GoTD")
if(mode == MULE_IDLE)
mode = MULE_LOST
paused = 0
if("Stop")
mode = MULE_IDLE
paused = 1
/mob/living/bot/mulebot/emag_act(var/remaining_charges, var/user)
locked = !locked
@@ -211,89 +196,49 @@
if(open)
icon_state = "mulebot-hatch"
return
if(mode == MULE_MOVING || mode == MULE_UNLOAD)
if(target_path.len && !paused)
icon_state = "mulebot1"
return
icon_state = "mulebot0"
/mob/living/bot/mulebot/Life()
..()
if(busy)
return
/mob/living/bot/mulebot/handleRegular()
if(!safety && prob(1))
flick("mulebot-emagged", src)
update_icons()
switch(mode)
if(MULE_IDLE) // Idle
return
if(MULE_MOVING) // Moving to target
if(!target) // Return home
if(auto_return && home)
target = home
targetName = "Home"
mode = MULE_LOST
else
mode = MULE_IDLE
update_icons()
return
if(loc == target) // Unload or stop
custom_emote(2, "makes a chiming sound.")
playsound(loc, 'sound/machines/chime.ogg', 50, 0)
mode = MULE_UNLOAD
update_icons()
return
if(path.len) // Move
makeStep()
sleep(10)
if(path.len)
makeStep()
else
mode = MULE_LOST
/mob/living/bot/mulebot/handleFrustrated()
custom_emote(2, "makes a sighing buzz.")
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
..()
update_icons()
return
if(MULE_UNLOAD)
unload(dir)
/mob/living/bot/mulebot/handleAdjacentTarget()
if(target == src.loc)
custom_emote(2, "makes a chiming sound.")
playsound(loc, 'sound/machines/chime.ogg', 50, 0)
UnarmedAttack(target)
resetTarget()
if(auto_return && home && (loc != home))
target = home
targetName = "Home"
if(auto_return && home && (loc != home))
target = home
targetName = "Home"
mode = MULE_LOST
else
mode = MULE_IDLE
update_icons()
return
if(MULE_LOST) // Lost my way
if(target)
spawn(0)
calc_path(obstacle)
mode = MULE_CALC_MIN
else
mode = MULE_IDLE
update_icons()
return
if(MULE_CALC_MIN to MULE_CALC_MAX) // Calcing path
if(path.len)
mode = MULE_PATH_DONE
update_icons()
else
++mode
return
if(MULE_PATH_DONE) // Done with path
obstacle = null
if(path.len)
frustration = 0
mode = MULE_MOVING
else
if(home)
target = home
targetName = "Home"
mode = MULE_LOST
else
mode = MULE_IDLE
update_icons()
/mob/living/bot/mulebot/confirmTarget()
return 1
/mob/living/bot/mulebot/calcTargetPath()
..()
if(!target_path.len && target != home) // I presume that target is not null
resetTarget()
target = home
targetName = "Home"
/mob/living/bot/mulebot/stepToTarget()
if(paused)
return
..()
/mob/living/bot/mulebot/UnarmedAttack(var/turf/T)
if(T == src.loc)
unload(dir)
/mob/living/bot/mulebot/Bump(var/mob/living/M)
if(!safety && istype(M))
@@ -302,28 +247,6 @@
M.Weaken(5)
..()
/mob/living/bot/mulebot/proc/makeStep()
var/turf/next = path[1]
if(next == loc)
path -= next
return
var/moved = step_towards(src, next)
if(moved)
frustration = 0
path -= next
else if(frustration < 6)
if(frustration == 3)
custom_emote(2, "makes an annoyed buzzing sound")
playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0)
++frustration
else
custom_emote(2, "makes a sighing buzz.")
playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
obstacle = next
mode = MULE_LOST
/mob/living/bot/mulebot/proc/runOver(var/mob/living/carbon/human/H)
if(istype(H)) // No safety checks - WILL run over lying humans. Stop ERPing in the maint!
visible_message("<span class='warning'>[src] drives over [H]!</span>")
@@ -362,11 +285,6 @@
new /obj/effect/decal/cleanable/blood/oil(Tsec)
..()
/mob/living/bot/mulebot/proc/calc_path(var/turf/avoid = null)
path = AStar(loc, target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 250, id = botcard, exclude = avoid)
if(!path)
path = list()
/mob/living/bot/mulebot/proc/load(var/atom/movable/C)
if(busy || load || get_dist(C, src) > 1 || !isturf(C.loc))
return
@@ -384,10 +302,6 @@
if(istype(crate))
crate.close()
//I'm sure someone will come along and ask why this is here... well people were dragging screen items onto the mule, and that was not cool.
//So this is a simple fix that only allows a selection of item types to be considered. Further narrowing-down is below.
//if(!istype(C,/obj/item) && !istype(C,/obj/machinery) && !istype(C,/obj/structure) && !ismob(C))
// return
busy = 1
C.loc = loc
@@ -428,11 +342,3 @@
AM.layer = initial(AM.layer)
AM.pixel_y = initial(AM.pixel_y)
busy = 0
#undef MULE_IDLE
#undef MULE_MOVING
#undef MULE_UNLOAD
#undef MULE_LOST
#undef MULE_CALC_MIN
#undef MULE_CALC_MAX
#undef MULE_PATH_DONE
+45 -308
View File
@@ -6,8 +6,8 @@
health = 50
req_one_access = list(access_security, access_forensics_lockers)
botcard_access = list(access_security, access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels)
var/mob/target
patrol_speed = 2
target_speed = 3
var/idcheck = 0 // If true, arrests for having weapons without authorization.
var/check_records = 0 // If true, arrests people without a record.
@@ -16,30 +16,9 @@
var/declare_arrests = 0 // If true, announces arrests over sechuds.
var/auto_patrol = 0 // If true, patrols on its own
var/mode = 0
#define SECBOT_IDLE 0 // idle
#define SECBOT_HUNT 1 // found target, hunting
#define SECBOT_ARREST 2 // arresting target
#define SECBOT_START_PATROL 3 // start patrol
#define SECBOT_WAIT_PATROL 4 // waiting for signals
#define SECBOT_PATROL 5 // patrolling
#define SECBOT_SUMMON 6 // summoned by PDA
var/is_attacking = 0
var/is_ranged = 0
var/awaiting_surrender = 0
var/obj/secbot_listener/listener = null
var/beacon_freq = 1445 // Navigation beacon frequency
var/control_freq = BOT_FREQ // Bot control frequency
var/list/path = list()
var/frustration = 0
var/turf/patrol_target = null // This is where we are headed
var/closest_dist // Used to find the closest beakon
var/destination = "__nearest__" // This is the current beacon's ID
var/next_destination = "__nearest__" // This is the next beacon's ID
var/nearest_beacon // Tag of the beakon that we assume to be the closest one
var/bot_version = 1.3
var/list/threat_found_sounds = new('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg')
var/list/preparing_arrest_sounds = new('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/binsult.ogg', 'sound/voice/bcreep.ogg')
@@ -48,24 +27,8 @@
desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey."
auto_patrol = 1
/mob/living/bot/secbot/New()
..()
listener = new /obj/secbot_listener(src)
listener.secbot = src
spawn(5) // Since beepsky is made on the start... this delay is necessary
if(radio_controller)
radio_controller.add_object(listener, control_freq, filter = RADIO_SECBOT)
radio_controller.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS)
/mob/living/bot/secbot/turn_off()
..()
target = null
frustration = 0
mode = SECBOT_IDLE
/mob/living/bot/secbot/update_icons()
if(on && is_attacking)
if(on && busy)
icon_state = "secbot-c"
else
icon_state = "secbot[on]"
@@ -78,7 +41,7 @@
/mob/living/bot/secbot/attack_hand(var/mob/user)
user.set_machine(src)
var/dat
dat += "<TT><B>Automatic Security Unit v[bot_version]</B></TT><BR><BR>"
dat += "<TT><B>Automatic Security Unit</B></TT><BR><BR>"
dat += "Status: <A href='?src=\ref[src];power=1'>[on ? "On" : "Off"]</A><BR>"
dat += "Behaviour controls are [locked ? "locked" : "unlocked"]<BR>"
dat += "Maintenance panel is [open ? "opened" : "closed"]"
@@ -89,7 +52,7 @@
dat += "Operating Mode: <A href='?src=\ref[src];operation=switchmode'>[arrest_type ? "Detain" : "Arrest"]</A><BR>"
dat += "Report Arrests: <A href='?src=\ref[src];operation=declarearrests'>[declare_arrests ? "Yes" : "No"]</A><BR>"
dat += "Auto Patrol: <A href='?src=\ref[src];operation=patrol'>[auto_patrol ? "On" : "Off"]</A>"
user << browse("<HEAD><TITLE>Securitron v[bot_version] controls</TITLE></HEAD>[dat]", "window=autosec")
user << browse("<HEAD><TITLE>Securitron controls</TITLE></HEAD>[dat]", "window=autosec")
onclose(user, "autosec")
return
@@ -118,7 +81,6 @@
arrest_type = !arrest_type
if("patrol")
auto_patrol = !auto_patrol
mode = SECBOT_IDLE
if("declarearrests")
declare_arrests = !declare_arrests
attack_hand(usr)
@@ -129,127 +91,47 @@
if(health < curhealth)
target = user
awaiting_surrender = 5
mode = SECBOT_HUNT
/mob/living/bot/secbot/Life()
/mob/living/bot/secbot/startPatrol()
if(!locked) // Stop running away when we set you up
return
..()
if(!on)
return
if(client)
return
if(!target)
scan_view()
/mob/living/bot/secbot/confirmTarget(var/atom/A)
if(!..())
return 0
if(!locked && (mode == SECBOT_START_PATROL || mode == SECBOT_PATROL)) // Stop running away when we set you up
mode = SECBOT_IDLE
return (check_threat(A) > 3)
switch(mode)
if(SECBOT_IDLE)
if(auto_patrol && locked)
mode = SECBOT_START_PATROL
/mob/living/bot/secbot/lookForTargets()
for(var/mob/living/M in view(src))
if(M.stat == DEAD)
continue
if(confirmTarget(M))
var/threat = check_threat(M)
target = M
awaiting_surrender = -1
say("Level [threat] infraction alert!")
custom_emote(1, "points at [M.name]!")
return
if(SECBOT_HUNT) // Target is in the view or has been recently - chase it
if(frustration > 7)
target = null
frustration = 0
awaiting_surrender = 0
mode = SECBOT_IDLE
return
if(target)
var/threat = check_threat(target)
if(threat < 4) // Re-evaluate in case they dropped the weapon or something
target = null
frustration = 0
awaiting_surrender = 0
mode = SECBOT_IDLE
return
if(!(target in view(7, src)))
++frustration
if(Adjacent(target))
mode = SECBOT_ARREST
return
else
if(is_ranged)
RangedAttack(target)
else
step_towards(src, target) // Melee bots chase a bit faster
spawn(8)
if(!Adjacent(target))
step_towards(src, target)
spawn(16)
if(!Adjacent(target))
step_towards(src, target)
/mob/living/bot/secbot/calcTargetPath()
..()
if(awaiting_surrender != -1)
awaiting_surrender = 5 // This implies that a) we have already approached the target and b) it has moved after the warning
if(SECBOT_ARREST) // Target is next to us - attack it
if(!target)
mode = SECBOT_IDLE
if(!Adjacent(target))
awaiting_surrender = 5 // I'm done playing nice
mode = SECBOT_HUNT
return
var/threat = check_threat(target)
if(threat < 4)
target = null
awaiting_surrender = 0
frustration = 0
mode = SECBOT_IDLE
return
if(awaiting_surrender < 5 && ishuman(target) && !target.lying)
if(awaiting_surrender == 0)
say("Down on the floor, [target]! You have five seconds to comply.")
++awaiting_surrender
else
UnarmedAttack(target)
if(ishuman(target) && declare_arrests)
var/area/location = get_area(src)
broadcast_security_hud_message("[src] is [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect <b>[target]</b> in <b>[location]</b>.", src)
return
if(SECBOT_START_PATROL)
if(path.len && patrol_target)
mode = SECBOT_PATROL
return
else if(patrol_target)
spawn(0)
calc_path()
if(!path.len)
patrol_target = null
mode = SECBOT_IDLE
else
mode = SECBOT_PATROL
if(!patrol_target)
if(next_destination)
find_next_target()
else
find_patrol_target()
say("Engaging patrol mode.")
mode = SECBOT_WAIT_PATROL
return
if(SECBOT_WAIT_PATROL)
if(patrol_target)
mode = SECBOT_START_PATROL
else
++frustration
if(frustration > 120)
frustration = 0
mode = SECBOT_IDLE
if(SECBOT_PATROL)
patrol_step()
spawn(10)
patrol_step()
return
if(SECBOT_SUMMON)
patrol_step()
spawn(8)
patrol_step()
spawn(16)
patrol_step()
return
/mob/living/bot/secbot/handleAdjacentTarget()
if(awaiting_surrender < 5 && ishuman(target) && !target:lying)
if(awaiting_surrender == -1)
say("Down on the floor, [target]! You have five seconds to comply.")
++awaiting_surrender
else
UnarmedAttack(target)
if(ishuman(target) && declare_arrests)
var/area/location = get_area(src)
broadcast_security_hud_message("[src] is [arrest_type ? "detaining" : "arresting"] a level [check_threat(target)] suspect <b>[target]</b> in <b>[location]</b>.", src)
// say("Engaging patrol mode.")
/mob/living/bot/secbot/UnarmedAttack(var/mob/M, var/proximity)
if(!..())
@@ -271,31 +153,33 @@
C.stun_effect_act(0, 60, null)
playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
do_attack_animation(C)
is_attacking = 1
busy = 1
update_icons()
spawn(2)
is_attacking = 0
busy = 0
update_icons()
visible_message("<span class='warning'>[C] was prodded by [src] with a stun baton!</span>")
else
playsound(loc, 'sound/weapons/handcuffs.ogg', 30, 1, -2)
visible_message("<span class='warning'>[src] is trying to put handcuffs on [C]!</span>")
busy = 1
if(do_mob(src, C, 60))
if(!C.handcuffed)
C.handcuffed = new /obj/item/weapon/handcuffs(C)
C.update_inv_handcuffed()
if(preparing_arrest_sounds.len)
playsound(loc, pick(preparing_arrest_sounds), 50, 0)
busy = 0
else if(istype(M, /mob/living/simple_animal))
var/mob/living/simple_animal/S = M
S.AdjustStunned(10)
S.adjustBruteLoss(15)
do_attack_animation(M)
playsound(loc, "swing_hit", 50, 1, -1)
is_attacking = 1
busy = 1
update_icons()
spawn(2)
is_attacking = 0
busy = 0
update_icons()
visible_message("<span class='warning'>[M] was beaten by [src] with a stun baton!</span>")
@@ -319,30 +203,8 @@
new /obj/effect/decal/cleanable/blood/oil(Tsec)
qdel(src)
/mob/living/bot/secbot/proc/scan_view()
for(var/mob/living/M in view(7, src))
if(M.invisibility >= INVISIBILITY_LEVEL_ONE)
continue
if(M.stat)
continue
var/threat = check_threat(M)
if(threat >= 4)
target = M
say("Level [threat] infraction alert!")
custom_emote(1, "points at [M.name]!")
mode = SECBOT_HUNT
break
return
/mob/living/bot/secbot/proc/calc_path(var/turf/avoid = null)
path = AStar(loc, patrol_target, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 120, id=botcard, exclude=avoid)
if(!path)
path = list()
/mob/living/bot/secbot/proc/check_threat(var/mob/living/M)
if(!M || !istype(M) || M.stat || src == M)
if(!M || !istype(M) || M.stat == DEAD || src == M)
return 0
if(emagged)
@@ -350,131 +212,6 @@
return M.assess_perp(access_scanner, 0, idcheck, check_records, check_arrest)
/mob/living/bot/secbot/proc/patrol_step()
if(loc == patrol_target)
patrol_target = null
path = list()
mode = SECBOT_IDLE
return
if(path.len && patrol_target)
var/turf/next = path[1]
if(loc == next)
path -= next
return
var/moved = step_towards(src, next)
if(moved)
path -= next
frustration = 0
else
++frustration
if(frustration > 5) // Make a new path
mode = SECBOT_START_PATROL
return
else
mode = SECBOT_START_PATROL
/mob/living/bot/secbot/proc/find_patrol_target()
send_status()
nearest_beacon = null
next_destination = "__nearest__"
listener.post_signal(beacon_freq, "findbeacon", "patrol")
/mob/living/bot/secbot/proc/find_next_target()
send_status()
nearest_beacon = null
listener.post_signal(beacon_freq, "findbeacon", "patrol")
/mob/living/bot/secbot/proc/send_status()
var/list/kv = list(
"type" = "secbot",
"name" = name,
"loca" = get_area(loc),
"mode" = mode
)
listener.post_signal_multiple(control_freq, kv)
/obj/secbot_listener
var/mob/living/bot/secbot/secbot = null
/obj/secbot_listener/proc/post_signal(var/freq, var/key, var/value) // send a radio signal with a single data key/value pair
post_signal_multiple(freq, list("[key]" = value))
/obj/secbot_listener/proc/post_signal_multiple(var/freq, var/list/keyval) // send a radio signal with multiple data key/values
var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq)
if(!frequency)
return
var/datum/signal/signal = new()
signal.source = secbot
signal.transmission_method = 1
signal.data = keyval.Copy()
if(signal.data["findbeacon"])
frequency.post_signal(secbot, signal, filter = RADIO_NAVBEACONS)
else if(signal.data["type"] == "secbot")
frequency.post_signal(secbot, signal, filter = RADIO_SECBOT)
else
frequency.post_signal(secbot, signal)
/obj/secbot_listener/receive_signal(datum/signal/signal)
if(!secbot || !secbot.on)
return
var/recv = signal.data["command"]
if(recv == "bot_status")
secbot.send_status()
return
if(signal.data["active"] == secbot)
switch(recv)
if("stop")
secbot.mode = SECBOT_IDLE
secbot.auto_patrol = 0
return
if("go")
secbot.mode = SECBOT_IDLE
secbot.auto_patrol = 1
return
if("summon")
secbot.patrol_target = signal.data["target"]
secbot.next_destination = secbot.destination
secbot.destination = null
//secbot.awaiting_beacon = 0
secbot.mode = SECBOT_SUMMON
secbot.calc_path()
secbot.say("Responding.")
return
recv = signal.data["beacon"]
var/valid = signal.data["patrol"]
if(!recv || !valid)
return
if(recv == secbot.next_destination) // This beacon is our target
secbot.destination = secbot.next_destination
secbot.patrol_target = signal.source.loc
secbot.next_destination = signal.data["next_patrol"]
else if(secbot.next_destination == "__nearest__")
var/dist = get_dist(secbot, signal.source.loc)
if(dist <= 1)
return
if(secbot.nearest_beacon)
if(dist < secbot.closest_dist)
secbot.nearest_beacon = recv
secbot.patrol_target = secbot.nearest_beacon
secbot.next_destination = signal.data["next_patrol"]
secbot.closest_dist = dist
return
else
secbot.nearest_beacon = recv
secbot.patrol_target = secbot.nearest_beacon
secbot.next_destination = signal.data["next_patrol"]
secbot.closest_dist = dist
//Secbot Construction
/obj/item/clothing/head/helmet/attackby(var/obj/item/device/assembly/signaler/S, mob/user as mob)