diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm
index 397651dce7..0169cd3f00 100644
--- a/code/game/gamemodes/events.dm
+++ b/code/game/gamemodes/events.dm
@@ -350,6 +350,11 @@ Would like to add a law like "Law x is _______" where x = a number, and _____ is
M << "
"
M.add_ion_law("THE STATION IS [who2pref] [who2]")
+ if(botEmagChance)
+ for(var/mob/living/bot/bot in machines)
+ if(prob(botEmagChance))
+ bot.emag_act(1)
+
/*
var/apcnum = 0
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index fe5cac9c44..8700caf785 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -1,52 +1,45 @@
// Navigation beacon for AI robots
// Functions as a transponder: looks for incoming signal matching
-var/global/list/navbeacons // no I don't like putting this in, but it will do for now
+var/global/list/navbeacons = list() // no I don't like putting this in, but it will do for now
/obj/machinery/navbeacon
icon = 'icons/obj/objects.dmi'
icon_state = "navbeacon0-f"
name = "navigation beacon"
- desc = "A radio beacon used for bot navigation."
+ desc = "A beacon used for bot navigation."
level = 1 // underfloor
layer = 2.5
anchored = 1
var/open = 0 // true if cover is open
var/locked = 1 // true if controls are locked
- var/freq = 1445 // radio frequency
+ var/freq = null // DEPRECATED we don't use radios anymore!
var/location = "" // location response text
- var/list/codes // assoc. list of transponder codes
- var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value"
+ var/codes_txt // DEPRECATED codes as set on map: "tag1;tag2" or "tag1=value;tag2=value"
+ var/list/codes = list() // assoc. list of transponder codes
req_access = list(access_engine)
/obj/machinery/navbeacon/New()
..()
-
- set_codes()
+ set_codes_from_txt(codes_txt)
+ if(freq)
+ warning("[src] at [x],[y],[z] has deprecated var freq=[freq]. Replace it with proper type.")
var/turf/T = loc
hide(!T.is_plating())
-
- // add beacon to MULE bot beacon list
- if(freq == 1400)
- if(!navbeacons)
- navbeacons = new()
- navbeacons += src
-
-
- spawn(5) // must wait for map loading to finish
- if(radio_controller)
- radio_controller.add_object(src, freq, RADIO_NAVBEACONS)
+ navbeacons += src
// set the transponder codes assoc list from codes_txt
-/obj/machinery/navbeacon/proc/set_codes()
+// DEPRECATED - This is kept only for compatibilty with old map files! Do not use this!
+// Instead, you should replace the map instance with one of the appropriate navbeacon subtypes.
+// See the bottom of this file for a list of subtypes, make your own examples if your map needs more
+/obj/machinery/navbeacon/proc/set_codes_from_txt()
if(!codes_txt)
return
+ warning("[src] at [x],[y],[z] in [get_area(src)] is using the deprecated 'codes_txt' mapping method. Replace it with proper type.")
- codes = new()
-
+ codes = list()
var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons
-
for(var/e in entries)
var/index = findtext(e, "=") // format is "key=value"
if(index)
@@ -56,6 +49,8 @@ var/global/list/navbeacons // no I don't like putting this in, but it will do
else
codes[e] = "1"
+/obj/machinery/navbeacon/hides_under_flooring()
+ return 1
// called when turf state changes
// hide the object if turf is intact
@@ -73,38 +68,6 @@ var/global/list/navbeacons // no I don't like putting this in, but it will do
else
icon_state = "[state]"
-
-// look for a signal of the form "findbeacon=X"
-// where X is any
-// or the location
-// or one of the set transponder keys
-// if found, return a signal
-/obj/machinery/navbeacon/receive_signal(datum/signal/signal)
-
- var/request = signal.data["findbeacon"]
- if(request && ((request in codes) || request == "any" || request == location))
- spawn(1)
- post_signal()
-
-// return a signal giving location and transponder codes
-
-/obj/machinery/navbeacon/proc/post_signal()
-
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq)
-
- if(!frequency) return
-
- var/datum/signal/signal = new()
- signal.source = src
- signal.transmission_method = 1
- signal.data["beacon"] = location
-
- for(var/key in codes)
- signal.data[key] = codes[key]
-
- frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS)
-
-
/obj/machinery/navbeacon/attackby(var/obj/item/I, var/mob/user)
var/turf/T = loc
if(!T.is_plating())
@@ -117,7 +80,7 @@ var/global/list/navbeacons // no I don't like putting this in, but it will do
updateicon()
- else if(istype(I, /obj/item/weapon/card/id)||istype(I, /obj/item/device/pda))
+ else if(I.GetID())
if(open)
if(allowed(user))
locked = !locked
@@ -153,8 +116,7 @@ var/global/list/navbeacons // no I don't like putting this in, but it will do
if(locked && !ai)
t = {"Navigation Beacon
-(swipe card to unlock controls)
-Frequency: [format_frequency(freq)]
+(swipe card to unlock controls)
Location: [location ? location : "(none)"]
Transponder Codes:"}
@@ -165,14 +127,7 @@ Transponder Codes:"}
else
t = {"Navigation Beacon
-(swipe card to lock controls)
-Frequency:
--
--
-[format_frequency(freq)]
-+
-+
-
+(swipe card to lock controls)
Location: [location ? location : "(none)"]
Transponder Codes:"}
@@ -195,11 +150,7 @@ Transponder Codes:"}
if(open && !locked)
usr.set_machine(src)
- if(href_list["freq"])
- freq = sanitize_frequency(freq + text2num(href_list["freq"]))
- updateDialog()
-
- else if(href_list["locedit"])
+ if(href_list["locedit"])
var/newloc = sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null)
if(newloc)
location = newloc
@@ -248,6 +199,41 @@ Transponder Codes:"}
/obj/machinery/navbeacon/Destroy()
navbeacons.Remove(src)
- if(radio_controller)
- radio_controller.remove_object(src, freq)
+ ..()
+
+
+//
+// Nav Beacon Mapping
+// These subtypes are what you should actually put into maps! they will make your life much easier.
+//
+// Developer Note: navbeacons do not HAVE to use these subtypes. They are purely for mapping convenience.
+// You can feel free to construct them in-game as just /obj/machinery/navbeacon and they will work just
+// fine, and you can define your own specific types for every instance on map if you want (BayStation does)
+// This design is a compromise that means you can do mapping without every single one being its own type
+// but with it still being easy to map ~ Leshana
+//
+
+// Mulebot delivery destinations
+
+/obj/machinery/navbeacon/delivery/north
+ codes = list("delivery" = 1, "dir" = NORTH)
+
+/obj/machinery/navbeacon/delivery/south
+ codes = list("delivery" = 1, "dir" = SOUTH)
+
+/obj/machinery/navbeacon/delivery/east
+ codes = list("delivery" = 1, "dir" = EAST)
+
+/obj/machinery/navbeacon/delivery/west
+ codes = list("delivery" = 1, "dir" = WEST)
+
+
+// For part of the patrol route
+// You MUST set "location"
+// You MUST set "next_patrol"
+/obj/machinery/navbeacon/patrol
+ var/next_patrol
+
+/obj/machinery/navbeacon/patrol/New()
+ codes = list("patrol" = 1, "next_patrol" = next_patrol)
..()
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index 1e22826d0b..4c702038a9 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -86,6 +86,12 @@
"admin","ponies","heresy","meow","Pun Pun","monkey","Ian","moron","pizza","message","spam",\
"director", "Hello", "Hi!"," ","nuke","crate","dwarf","xeno")
+/datum/event/ionstorm/tick()
+ if(botEmagChance)
+ for(var/mob/living/bot/bot in world)
+ if(prob(botEmagChance))
+ bot.emag_act(1)
+
/datum/event/ionstorm/end()
spawn(rand(5000,8000))
if(prob(50))
diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm
index 0ad82ae384..cb73171c7e 100644
--- a/code/modules/mob/living/bot/bot.dm
+++ b/code/modules/mob/living/bot/bot.dm
@@ -27,7 +27,7 @@
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/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/min_target_dist = 1 // How close we try to get to the target
@@ -49,7 +49,11 @@
access_scanner.req_access = req_access.Copy()
access_scanner.req_one_access = req_one_access.Copy()
- turn_on()
+// 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()
..()
@@ -61,7 +65,8 @@
paralysis = 0
if(on && !client && !busy)
- handleAI()
+ spawn(0)
+ handleAI()
/mob/living/bot/updatehealth()
if(status_flags & GODMODE)
@@ -145,20 +150,18 @@
handleRangedTarget()
if(!wait_if_pulled || !pulledby)
for(var/i = 1 to target_speed)
+ sleep(20 / (target_speed + 1))
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)
+ if(patrol_path && patrol_path.len)
for(var/i = 1 to patrol_speed)
+ sleep(20 / (patrol_speed + 1))
handlePatrol()
- if(i < patrol_speed)
- sleep(20 / patrol_speed)
if(max_frustration && frustration > max_frustration * patrol_speed)
handleFrustrated(0)
else
@@ -219,6 +222,25 @@
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()
@@ -255,15 +277,16 @@
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()
- resetTarget()
- patrol_path = list()
- ignore_list = list()
/mob/living/bot/proc/explode()
qdel(src)
diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm
index 779d912cf3..d4cb723861 100644
--- a/code/modules/mob/living/bot/cleanbot.dm
+++ b/code/modules/mob/living/bot/cleanbot.dm
@@ -12,7 +12,6 @@
var/cleaning = 0
var/screwloose = 0
var/oddbutton = 0
- var/should_patrol = 0
var/blood = 1
var/list/target_types = list()
@@ -32,9 +31,11 @@
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)
- ignore_list += gib
+ // TODO - I have a feeling weakrefs will not work in ignore_list, verify this ~Leshana
+ var/weakref/g = weakref(gib)
+ ignore_list += g
spawn(600)
- ignore_list -= gib
+ ignore_list -= g
/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
@@ -110,7 +111,7 @@
dat += "Maintenance panel is [open ? "opened" : "closed"]"
if(!locked || issilicon(user))
dat += "
Cleans Blood: [blood ? "Yes" : "No"]
"
- dat += "
Patrol station: [should_patrol ? "Yes" : "No"]
"
+ dat += "
Patrol station: [will_patrol ? "Yes" : "No"]
"
if(open && !locked)
dat += "Odd looking screw twiddled: [screwloose ? "Yes" : "No"]
"
dat += "Weird button pressed: [oddbutton ? "Yes" : "No"]"
@@ -134,7 +135,7 @@
blood = !blood
get_targets()
if("patrol")
- should_patrol = !should_patrol
+ will_patrol = !will_patrol
patrol_path = null
if("screw")
screwloose = !screwloose
diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm
index 09320fd323..1e23af341c 100644
--- a/code/modules/mob/living/bot/farmbot.dm
+++ b/code/modules/mob/living/bot/farmbot.dm
@@ -170,7 +170,7 @@
visible_message("[src] starts [T.dead? "removing the plant from" : "harvesting"] \the [A].")
busy = 1
- if(do_after(src, 30))
+ if(do_after(src, 30, A))
visible_message("[src] [T.dead? "removes the plant from" : "harvests"] \the [A].")
T.attack_hand(src)
if(FARMBOT_WATER)
@@ -179,7 +179,7 @@
visible_message("[src] starts watering \the [A].")
busy = 1
- if(do_after(src, 30))
+ if(do_after(src, 30, A))
playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
visible_message("[src] waters \the [A].")
tank.reagents.trans_to(T, 100 - T.waterlevel)
@@ -198,7 +198,7 @@
visible_message("[src] starts fertilizing \the [A].")
busy = 1
- if(do_after(src, 30))
+ if(do_after(src, 30, A))
visible_message("[src] fertilizes \the [A].")
T.reagents.add_reagent("ammonia", 10)
diff --git a/code/modules/mob/living/bot/floorbot.dm b/code/modules/mob/living/bot/floorbot.dm
index 2259515941..323d2909fb 100644
--- a/code/modules/mob/living/bot/floorbot.dm
+++ b/code/modules/mob/living/bot/floorbot.dm
@@ -1,3 +1,9 @@
+// Configure whether or not floorbot will fix hull breaches.
+// This can be a problem if it tries to pave over space or shuttles. That should be fixed but...
+// If it can see space outside windows, it can be laggy since it keeps wondering if it should fix them.
+// Therefore that functionality is disabled for now. But it can be turned on by uncommenting this.
+// #define FLOORBOT_PATCHES_HOLES 1
+
/mob/living/bot/floorbot
name = "Floorbot"
desc = "A little floor repairing robot, he looks so excited!"
@@ -25,7 +31,7 @@
/mob/living/bot/floorbot/attack_hand(var/mob/user)
user.set_machine(src)
- var/dat
+ var/list/dat = list()
dat += "Automatic Station Floor Repairer v1.0
"
dat += "Status: [src.on ? "On" : "Off"]
"
dat += "Maintenance panel is [open ? "opened" : "closed"]
"
@@ -41,9 +47,9 @@
else
bmode = "Disabled"
dat += "
Bridge Mode : [bmode]
"
-
- user << browse("Repairbot v1.0 controls[dat]", "window=autorepair")
- onclose(user, "autorepair")
+ var/datum/browser/popup = new(user, "autorepair", "Repairbot v1.1 controls")
+ popup.set_content(jointext(dat,null))
+ popup.open()
return
/mob/living/bot/floorbot/emag_act(var/remaining_charges, var/mob/user)
@@ -115,23 +121,24 @@
target = T
return
T = get_step(T, targetdirection)
+ return // In bridge mode we don't want to step off that line even to eat plates!
- else // Fixing floors
- for(var/turf/space/T in view(src)) // Breaches are of higher priority
- if(confirmTarget(T))
- target = T
- return
+#ifdef FLOORBOT_PATCHES_HOLES
+ 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
+ for(var/turf/simulated/mineral/floor/T in view(src)) // Asteroids are of smaller priority
+ if(confirmTarget(T))
+ target = T
+ return
+#endif
+ // Look for broken floors even if we aren't 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))
@@ -148,7 +155,11 @@
if(istype(A, /obj/item/stack/material/steel))
return (amount < maxAmount && maketiles)
- if(A.loc.name == "Space")
+ // Don't pave over all of space, build there only if in bridge mode
+ if(!targetdirection && istype(A.loc, /area/space)) // Note name == "Space" does not work!
+ return 0
+
+ if(istype(A.loc, /area/shuttle)) // Do NOT mess with shuttle drop zones
return 0
if(emagged)
@@ -157,14 +168,16 @@
if(!amount)
return 0
+#ifdef FLOORBOT_PATCHES_HOLES
if(istype(A, /turf/space))
return 1
if(istype(A, /turf/simulated/mineral/floor))
return 1
+#endif
var/turf/simulated/floor/T = A
- return (istype(T) && improvefloors && !T.flooring && (get_turf(T) == loc || prob(40)))
+ return (istype(T) && (T.broken || T.burnt || (improvefloors && !T.flooring)) && (get_turf(T) == loc || prob(40)))
/mob/living/bot/floorbot/UnarmedAttack(var/atom/A, var/proximity)
if(!..())
@@ -181,12 +194,12 @@
busy = 1
update_icons()
if(F.flooring)
- visible_message("[src] begins to tear the floor tile from the floor!")
+ visible_message("\The [src] begins to tear the floor tile from the floor!")
if(do_after(src, 50))
F.break_tile_to_plating()
addTiles(1)
else
- visible_message("[src] begins to tear through the floor!")
+ visible_message("\The [src] begins to tear through the floor!")
if(do_after(src, 150)) // Extra time because this can and will kill.
F.ReplaceWithLattice()
addTiles(1)
@@ -201,7 +214,7 @@
return
busy = 1
update_icons()
- visible_message("[src] begins to repair the hole.")
+ visible_message("\The [src] begins to repair the hole.")
if(do_after(src, 50))
if(A && (locate(/obj/structure/lattice, A) && building == 1 || !locate(/obj/structure/lattice, A) && building == 2)) // Make sure that it still needs repairs
var/obj/item/I
@@ -215,10 +228,20 @@
update_icons()
else if(istype(A, /turf/simulated/floor))
var/turf/simulated/floor/F = A
- if(!F.flooring && amount)
+ if(F.broken || F.burnt)
busy = 1
update_icons()
- visible_message("[src] begins to improve the floor.")
+ visible_message("\The [src] begins to remove the broken floor.")
+ if(do_after(src, 50, F))
+ if(F.broken || F.burnt)
+ F.make_plating()
+ target = null
+ busy = 0
+ update_icons()
+ else if(!F.flooring && amount)
+ busy = 1
+ update_icons()
+ visible_message("\The [src] begins to improve the floor.")
if(do_after(src, 50))
if(!F.flooring)
F.set_flooring(get_flooring_data(floor_build_type))
@@ -228,7 +251,7 @@
update_icons()
else if(istype(A, /obj/item/stack/tile/floor) && amount < maxAmount)
var/obj/item/stack/tile/floor/T = A
- visible_message("[src] begins to collect tiles.")
+ visible_message("\The [src] begins to collect tiles.")
busy = 1
update_icons()
if(do_after(src, 20))
@@ -242,7 +265,7 @@
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("[src] begins to make tiles.")
+ visible_message("\The [src] begins to make tiles.")
busy = 1
update_icons()
if(do_after(50))
@@ -252,7 +275,7 @@
/mob/living/bot/floorbot/explode()
turn_off()
- visible_message("[src] blows apart!")
+ visible_message("\The [src] blows apart!")
var/turf/Tsec = get_turf(src)
var/obj/item/weapon/storage/toolbox/mechanical/N = new /obj/item/weapon/storage/toolbox/mechanical(Tsec)
@@ -353,4 +376,4 @@
return
if(!in_range(src, user) && loc != user)
return
- created_name = t
\ No newline at end of file
+ created_name = t
diff --git a/code/modules/mob/living/bot/mulebot.dm b/code/modules/mob/living/bot/mulebot.dm
index c330e0efc0..2079f77a35 100644
--- a/code/modules/mob/living/bot/mulebot.dm
+++ b/code/modules/mob/living/bot/mulebot.dm
@@ -130,10 +130,7 @@
if("sethome")
var/new_dest
- var/list/beaconlist = new()
- for(var/obj/machinery/navbeacon/N in navbeacons)
- beaconlist.Add(N.location)
- beaconlist[N.location] = N
+ var/list/beaconlist = GetBeaconList()
if(beaconlist.len)
new_dest = input("Select new home tag", "Mulebot [suffix ? "([suffix])" : ""]", null) in null|beaconlist
else
@@ -168,10 +165,7 @@
targetName = "Home"
if("SetD")
var/new_dest
- var/list/beaconlist = new()
- for(var/obj/machinery/navbeacon/N in navbeacons)
- beaconlist.Add(N.location)
- beaconlist[N.location] = N
+ var/list/beaconlist = GetBeaconList()
if(beaconlist.len)
new_dest = input("Select new destination tag", "Mulebot [suffix ? "([suffix])" : ""]") in null|beaconlist
else
@@ -285,6 +279,15 @@
new /obj/effect/decal/cleanable/blood/oil(Tsec)
..()
+/mob/living/bot/mulebot/proc/GetBeaconList()
+ var/list/beaconlist = list()
+ for(var/obj/machinery/navbeacon/N in navbeacons)
+ if(!N.codes["delivery"])
+ continue
+ beaconlist.Add(N.location)
+ beaconlist[N.location] = N
+ return beaconlist
+
/mob/living/bot/mulebot/proc/load(var/atom/movable/C)
if(busy || load || get_dist(C, src) > 1 || !isturf(C.loc))
return
@@ -304,11 +307,11 @@
busy = 1
- C.loc = loc
+ C.forceMove(loc)
sleep(2)
if(C.loc != loc) //To prevent you from going onto more than one bot.
return
- C.loc = src
+ C.forceMove(src)
load = C
C.pixel_y += 9
@@ -325,7 +328,7 @@
busy = 1
overlays.Cut()
- load.loc = loc
+ load.forceMove(loc)
load.pixel_y -= 9
load.layer = initial(load.layer)
@@ -338,7 +341,7 @@
if(AM == botcard || AM == access_scanner)
continue
- AM.loc = loc
+ AM.forceMove(loc)
AM.layer = initial(AM.layer)
AM.pixel_y = initial(AM.pixel_y)
busy = 0
diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm
index f7556e0ece..d6a014c450 100644
--- a/code/modules/mob/living/bot/secbot.dm
+++ b/code/modules/mob/living/bot/secbot.dm
@@ -1,10 +1,14 @@
+#define SECBOT_WAIT_TIME 5 //number of in-game seconds to wait for someone to surrender
+#define SECBOT_THREAT_ARREST 4 //threat level at which we decide to arrest someone
+#define SECBOT_THREAT_ATTACK 8 //threat level at which was assume immediate danger and attack right away
+
/mob/living/bot/secbot
name = "Securitron"
desc = "A little security robot. He looks less than thrilled."
icon_state = "secbot0"
maxHealth = 100
health = 100
- req_one_access = list(access_robotics, access_security, access_forensics_lockers)
+ 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)
patrol_speed = 2
target_speed = 3
@@ -14,18 +18,17 @@
var/check_arrest = 1 // If true, arrests people who are set to arrest.
var/arrest_type = 0 // If true, doesn't handcuff. You monster.
var/declare_arrests = 0 // If true, announces arrests over sechuds.
- var/auto_patrol = 0 // If true, patrols on its own
var/is_ranged = 0
var/awaiting_surrender = 0
- 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')
+ var/list/threat_found_sounds = list('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg')
+ var/list/preparing_arrest_sounds = list('sound/voice/bgod.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bsecureday.ogg', 'sound/voice/bradio.ogg', 'sound/voice/bcreep.ogg')
/mob/living/bot/secbot/beepsky
name = "Officer Beepsky"
desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey."
- auto_patrol = 1
+ will_patrol = 1
/mob/living/bot/secbot/update_icons()
if(on && busy)
@@ -40,7 +43,7 @@
/mob/living/bot/secbot/attack_hand(var/mob/user)
user.set_machine(src)
- var/dat
+ var/list/dat = list()
dat += "Automatic Security Unit
"
dat += "Status: [on ? "On" : "Off"]
"
dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
"
@@ -51,10 +54,10 @@
dat += "Check Arrest Status: [check_arrest ? "Yes" : "No"]
"
dat += "Operating Mode: [arrest_type ? "Detain" : "Arrest"]
"
dat += "Report Arrests: [declare_arrests ? "Yes" : "No"]
"
- dat += "Auto Patrol: [auto_patrol ? "On" : "Off"]"
- user << browse("Securitron controls[dat]", "window=autosec")
- onclose(user, "autosec")
- return
+ dat += "Auto Patrol: [will_patrol ? "On" : "Off"]"
+ var/datum/browser/popup = new(user, "autosec", "Securitron controls")
+ popup.set_content(jointext(dat,null))
+ popup.open()
/mob/living/bot/secbot/Topic(href, href_list)
if(..())
@@ -80,7 +83,7 @@
if("switchmode")
arrest_type = !arrest_type
if("patrol")
- auto_patrol = !auto_patrol
+ will_patrol = !will_patrol
if("declarearrests")
declare_arrests = !declare_arrests
attack_hand(usr)
@@ -99,10 +102,46 @@
/mob/living/bot/secbot/attackby(var/obj/item/O, var/mob/user)
var/curhealth = health
- ..()
+ . = ..()
if(health < curhealth)
- target = user
- awaiting_surrender = 5
+ react_to_attack(user)
+
+/mob/living/bot/secbot/bullet_act(var/obj/item/projectile/P)
+ var/curhealth = health
+ var/mob/shooter = P.firer
+ . = ..()
+ //if we already have a target just ignore to avoid lots of checking
+ if(!target && health < curhealth && shooter && (shooter in view(world.view, src)))
+ react_to_attack(shooter)
+
+/mob/living/bot/secbot/proc/react_to_attack(mob/attacker)
+ if(!target)
+ playsound(src.loc, pick(threat_found_sounds), 50)
+ broadcast_security_hud_message("[src] was attacked by a hostile [target_name(attacker)] in [get_area(src)].", src)
+ target = attacker
+ awaiting_surrender = INFINITY // Don't try and wait for surrender
+
+// Say "freeze!" and demand surrender
+/mob/living/bot/secbot/proc/demand_surrender(mob/target, var/threat)
+ var/suspect_name = target_name(target)
+ if(declare_arrests)
+ broadcast_security_hud_message("[src] is [arrest_type ? "detaining" : "arresting"] a level [threat] suspect [suspect_name] in [get_area(src)].", src)
+ say("Down on the floor, [suspect_name]! You have [SECBOT_WAIT_TIME] seconds to comply.")
+ playsound(src.loc, pick(preparing_arrest_sounds), 50)
+ // Register to be told when the target moves
+ moved_event.register(target, src, /mob/living/bot/secbot/proc/target_moved)
+
+// Callback invoked if the registered target moves
+/mob/living/bot/secbot/proc/target_moved(atom/movable/moving_instance, atom/old_loc, atom/new_loc)
+ if(get_dist(get_turf(src), get_turf(target)) >= 1)
+ awaiting_surrender = INFINITY // Done waiting!
+ moved_event.unregister(moving_instance, src)
+
+/mob/living/bot/secbot/resetTarget()
+ ..()
+ moved_event.unregister(target, src)
+ awaiting_surrender = -1
+ walk_to(src, 0)
/mob/living/bot/secbot/startPatrol()
if(!locked) // Stop running away when we set you up
@@ -112,8 +151,7 @@
/mob/living/bot/secbot/confirmTarget(var/atom/A)
if(!..())
return 0
-
- return (check_threat(A) > 3)
+ return (check_threat(A) >= SECBOT_THREAT_ARREST)
/mob/living/bot/secbot/lookForTargets()
for(var/mob/living/M in view(src))
@@ -127,23 +165,17 @@
custom_emote(1, "points at [M.name]!")
return
-/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
-
/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.")
+ var/mob/living/carbon/human/H = target
+ var/threat = check_threat(target)
+ if(awaiting_surrender < SECBOT_WAIT_TIME && istype(H) && !H.lying && threat < SECBOT_THREAT_ATTACK)
+ if(awaiting_surrender == -1) // On first tick of awaiting...
+ demand_surrender(target, threat)
++awaiting_surrender
else
+ if(declare_arrests)
+ broadcast_security_hud_message("[src] is [arrest_type ? "detaining" : "arresting"] a level [threat] suspect [target_name(target)] in [get_area(src)].", src)
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 [target] in [location].", src)
-
- // say("Engaging patrol mode.")
/mob/living/bot/secbot/UnarmedAttack(var/mob/M, var/proximity)
if(!..())
@@ -170,17 +202,15 @@
spawn(2)
busy = 0
update_icons()
- visible_message("[C] was prodded by [src] with a stun baton!")
+ visible_message("\The [C] was prodded by \the [src] with a stun baton!")
else
playsound(loc, 'sound/weapons/handcuffs.ogg', 30, 1, -2)
- visible_message("[src] is trying to put handcuffs on [C]!")
+ visible_message("\The [src] is trying to put handcuffs on \the [C]!")
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
@@ -193,7 +223,8 @@
spawn(2)
busy = 0
update_icons()
- visible_message("[M] was beaten by [src] with a stun baton!")
+ visible_message("\The [M] was beaten by \the [src] with a stun baton!")
+
/mob/living/bot/secbot/explode()
visible_message("[src] blows apart!")
@@ -215,11 +246,17 @@
new /obj/effect/decal/cleanable/blood/oil(Tsec)
qdel(src)
+/mob/living/bot/secbot/proc/target_name(mob/living/T)
+ if(ishuman(T))
+ var/mob/living/carbon/human/H = T
+ return H.get_id_name("unidentified person")
+ return "unidentified lifeform"
+
/mob/living/bot/secbot/proc/check_threat(var/mob/living/M)
if(!M || !istype(M) || M.stat == DEAD || src == M)
return 0
- if(emagged)
+ if(emagged && !M.incapacitated()) //check incapacitated so emagged secbots don't keep attacking the same target forever
return 10
return M.assess_perp(access_scanner, 0, idcheck, check_records, check_arrest)
@@ -297,4 +334,4 @@
return
if(!in_range(src, usr) && loc != usr)
return
- created_name = t
\ No newline at end of file
+ created_name = t