diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index 6f1f9c60b2f..882c6dd6179 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -124,9 +124,14 @@
turfs += T
return turfs
-
-
-//var/debug_mob = 0
+// Will recursively loop through an atom's locs until it finds the atom loc above a turf
+/proc/recursive_loc_turf_check(var/atom/O, var/recursion_limit = 3)
+ if(recursion_limit <= 0 || isturf(O.loc))
+ return O
+ else
+ O = O.loc
+ recursion_limit--
+ return recursive_loc_turf_check(O, recursion_limit)
// Will recursively loop through an atom's contents and check for mobs, then it will loop through every atom in that atom's contents.
// It will keep doing this until it checks every content possible. This will fix any problems with mobs, that are inside objects,
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 3bd7d86890b..4f33d72737b 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -74,6 +74,14 @@
var/mob/living/heavy_vehicle/M = loc
return M.ClickOn(A, params, src)
+ // pAI handling
+ if(istype(loc.loc, /mob/living/bot))
+ var/mob/living/bot/B = loc.loc
+ if(!B.on)
+ to_chat(src, SPAN_WARNING("\The [B] isn't turned on!"))
+ return
+ return B.ClickOn(A, params)
+
if(client && client.buildmode)
build_click(src, client.buildmode, params, A)
return
@@ -150,7 +158,6 @@
sdepth = A.storage_depth_turf()
if(isturf(A) || isturf(A.loc) || (sdepth != -1 && sdepth <= 1))
if(A.Adjacent(src) || (W && W.attack_can_reach(src, A, W.reach)) ) // see adjacent.dm
-
if(W)
// Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example)
var/resolved = W.resolve_attackby(A,src, params)
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 59d86aba70b..906a02a0609 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -9,6 +9,7 @@
var/obj/item/device/radio/radio
var/looking_for_personality = 0
var/mob/living/silicon/pai/pai
+ var/move_delay = 0
light_power = 1
light_range = 1
@@ -17,6 +18,15 @@
/obj/item/device/paicard/relaymove(var/mob/user, var/direction)
if(user.stat || user.stunned)
return
+ if(istype(loc, /mob/living/bot))
+ if(world.time < move_delay)
+ return
+ var/mob/living/bot/B = loc
+ move_delay = world.time + 1 SECOND
+ if(!B.on)
+ to_chat(pai, SPAN_WARNING("\The [B] isn't turned on!"))
+ return
+ step_towards(B, get_step(B, direction))
var/obj/item/rig/rig = src.get_rig()
if(istype(rig))
rig.forced_move(direction, user)
diff --git a/code/modules/mob/floating_messages.dm b/code/modules/mob/floating_messages.dm
index 350e1e3efba..fd366a781f8 100644
--- a/code/modules/mob/floating_messages.dm
+++ b/code/modules/mob/floating_messages.dm
@@ -43,8 +43,8 @@ var/list/floating_chat_colors = list()
else
C.images += gibberish
-/proc/generate_floating_text(atom/movable/holder, message, style, size, duration, show_to)
- var/image/I = image(null, holder)
+/proc/generate_floating_text(atom/movable/holder, message, style, size, duration, show_to)
+ var/image/I = image(null, recursive_loc_turf_check(holder))
I.layer = FLY_LAYER
I.alpha = 0
I.maptext_width = 80
@@ -68,4 +68,4 @@ var/list/floating_chat_colors = list()
/proc/remove_floating_text(atom/movable/holder, image/I)
animate(I, 2, pixel_y = I.pixel_y + 10, alpha = 0)
- LAZYREMOVE(holder.stored_chat_text, I)
+ LAZYREMOVE(holder.stored_chat_text, I)
\ No newline at end of file
diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm
index 3013235a69e..3359a23ad53 100644
--- a/code/modules/mob/living/bot/bot.dm
+++ b/code/modules/mob/living/bot/bot.dm
@@ -4,21 +4,27 @@
maxHealth = 20
icon = 'icons/obj/aibots.dmi'
layer = MOB_LAYER
- universal_speak = 1
- density = 0
- var/obj/item/card/id/botcard = null
+ universal_speak = TRUE
+ density = FALSE
+ var/obj/item/card/id/botcard
var/list/botcard_access = list()
- var/on = 1
- var/open = 0
- var/locked = 1
- var/emagged = 0
+ var/on = TRUE
+ var/open = FALSE
+ var/locked = TRUE
+ var/emagged = FALSE
var/light_strength = 3
- var/obj/access_scanner = null
+ var/obj/access_scanner
var/list/req_access = list()
var/list/req_one_access = list()
var/master_access = access_robotics
+ var/last_emote = 0 // timer for emotes
+
+ var/can_take_pai = TRUE
+ var/obj/item/device/paicard/pAI
+ var/old_name
+
/mob/living/bot/Initialize()
. = ..()
update_icons()
@@ -31,10 +37,22 @@
access_scanner.req_one_access = req_one_access.Copy()
/mob/living/bot/Destroy()
+ if(pAI)
+ if(isturf(loc))
+ drop_from_inventory(pAI, get_turf(src))
+ pAI.throw_at_random(FALSE, 3, 1)
+ else
+ drop_from_inventory(pAI, loc)
+ pAI = null
QDEL_NULL(botcard)
QDEL_NULL(access_scanner)
return ..()
+/mob/living/bot/examine(mob/user, distance, infix, suffix)
+ . = ..()
+ if(pAI)
+ to_chat(user, FONT_SMALL(SPAN_NOTICE("It has a pAI piloting it.")))
+
/mob/living/bot/Life()
..()
if(health <= 0)
@@ -44,6 +62,9 @@
stunned = 0
paralysis = 0
+/mob/living/bot/movement_delay()
+ return 3
+
/mob/living/bot/updatehealth()
if(status_flags & GODMODE)
health = maxHealth
@@ -56,54 +77,82 @@
/mob/living/bot/proc/has_master_access(var/obj/item/I)
var/list/L = I.GetAccess()
- if (master_access in L)
- return 1
+ if(master_access in L)
+ return TRUE
else
- return 0
+ return FALSE
/mob/living/bot/proc/has_ui_access(mob/user)
- if (access_scanner.allowed(user))
- return 1
- if (!locked)
- return 1
- if (isAI(user))
- return 1
- return 0
+ if(access_scanner.allowed(user))
+ return TRUE
+ if(!locked)
+ return TRUE
+ if(isAI(user))
+ return TRUE
+ return FALSE
/mob/living/bot/attackby(var/obj/item/O, var/mob/user)
if(O.GetID())
if((has_master_access(O) || access_scanner.allowed(user)) && !open && !emagged)
locked = !locked
- to_chat(user, "Controls are now [locked ? "locked." : "unlocked."]")
+ to_chat(user, SPAN_NOTICE("You [locked ? "lock" : "unlock"] the controls."))
else
if(emagged)
- to_chat(user, "ERROR")
+ to_chat(user, SPAN_WARNING("As you swipe your ID, it reads: \"Interface error!\""))
if(open)
- to_chat(user, "Please close the access panel before locking it.")
+ to_chat(user, SPAN_WARNING("You have to close the access panel before locking it."))
else
- to_chat(user, "Access denied.")
+ to_chat(user, SPAN_WARNING("As you swipe your ID, it reads: \"Access denied.\""))
return
else if(O.isscrewdriver())
if(!locked)
open = !open
- to_chat(user, "Maintenance panel is now [open ? "opened" : "closed"].")
+ to_chat(user, SPAN_NOTICE("You [open ? "open" : "close"] the maintenance panel."))
else
- to_chat(user, "You need to unlock the controls first.")
+ to_chat(user, SPAN_WARNING("You need to unlock the controls first."))
return
else if(O.iswelder())
if(health < maxHealth)
if(open)
health = min(maxHealth, health + 10)
- user.visible_message("[user] repairs [src].","You repair [src].")
+ user.visible_message(SPAN_NOTICE("\The [user] repairs [src]."), SPAN_NOTICE("You repair [src]."))
else
- to_chat(user, "Unable to repair with the maintenance panel closed.")
+ to_chat(user, SPAN_WARNING("You are unable to repair [src] with the maintenance panel closed."))
else
- to_chat(user, "[src] does not need a repair.")
+ to_chat(user, SPAN_WARNING("[src] does not need a repair."))
return
+ else if(O.iscrowbar())
+ if(!pAI)
+ to_chat(user, SPAN_WARNING("\The [src] does not have a pAI installed!"))
+ return
+ if(!old_name)
+ old_name = initial(name)
+ name = old_name
+ user.put_in_hands(pAI)
+ user.visible_message(SPAN_NOTICE("\The [user] pries \the [pAI.pai] out of \the [src]."), SPAN_NOTICE("You pry \the [pAI.pai] out of \the [src]."))
+ pAI = null
+ else if(istype(O, /obj/item/device/paicard))
+ if(!can_take_pai)
+ to_chat(user, SPAN_WARNING("\The [src] cannot take a pAI!"))
+ return
+ if(pAI)
+ to_chat(user, SPAN_WARNING("\The [src] already has a pAI installed!"))
+ return
+ var/obj/item/device/paicard/P = O
+ P.pai.open_up(FALSE)
+ P.pai.close_up()
+ user.drop_from_inventory(P, src)
+ pAI = P
+ user.visible_message(SPAN_NOTICE("\The [user] places \the [pAI.pai] into \the [src]."), SPAN_NOTICE("You place \the [O] into \the [src]."))
+ old_name = src.name
+ name = pAI.pai.name
else
..()
-/mob/living/bot/attack_ai(var/mob/user)
+/mob/living/bot/attack_ai(mob/user)
+ if(pAI)
+ to_chat(user, SPAN_WARNING("\The [src] contains a pAI and cannot be remotely controlled."))
+ return
return attack_hand(user)
/mob/living/bot/say(var/message)
@@ -121,8 +170,13 @@
else
. = ..()
-/mob/living/bot/emag_act(var/remaining_charges, var/mob/user)
- return 0
+/mob/living/bot/cleanbot/think()
+ if(pAI) // no AI if we have a pAI installed
+ return
+ ..()
+
+/mob/living/bot/emag_act()
+ return FALSE
/mob/living/bot/emp_act(severity)
switch(severity)
@@ -134,14 +188,14 @@
/mob/living/bot/proc/turn_on()
if(stat)
- return 0
- on = 1
+ return FALSE
+ on = TRUE
set_light(light_strength)
update_icons()
- return 1
+ return TRUE
/mob/living/bot/proc/turn_off()
- on = 0
+ on = FALSE
set_light(0)
update_icons()
diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm
index a952a6c1e63..4f44eccadb1 100644
--- a/code/modules/mob/living/bot/cleanbot.dm
+++ b/code/modules/mob/living/bot/cleanbot.dm
@@ -12,37 +12,38 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
/mob/living/bot/cleanbot
name = "Cleanbot"
- desc = "A little cleaning robot, he looks so excited!"
+ desc = "A little cleaning robot, consisting of a bucket, a proximity sensor, and a prosthetic arm. It looks excited to clean!"
icon_state = "cleanbot0"
req_one_access = list(access_janitor, access_robotics)
botcard_access = list(access_janitor, access_maint_tunnels)
- locked = 0 // Start unlocked so roboticist can set them to patrol.
+ locked = FALSE // 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/obj/cleanbot_listener/listener
var/beacon_freq = 1445 // navigation beacon frequency
var/signal_sent = 0
var/closest_dist
var/next_dest
var/next_dest_loc
- var/cleaning = 0
- var/screwloose = 0
- var/oddbutton = 0
- var/should_patrol = 0
- var/blood = 1
+ var/cleaning = FALSE
+ var/screw_loose = FALSE
+ var/odd_button = FALSE
+ var/should_patrol = FALSE
+ var/cleans_blood = TRUE
var/list/target_types = list()
var/maximum_search_range = 7
/mob/living/bot/cleanbot/Cross(atom/movable/crossed)
if(crossed)
- if(istype(crossed,/mob/living/bot/cleanbot)) return 0
+ if(istype(crossed, /mob/living/bot/cleanbot))
+ return FALSE
return ..()
/mob/living/bot/cleanbot/Initialize()
@@ -57,13 +58,13 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
SSradio.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS)
/mob/living/bot/cleanbot/Destroy()
- . = ..()
path = null
patrol_path = null
target = null
ignorelist = null
QDEL_NULL(listener)
global.janitorial_supplies -= src
+ return ..()
/mob/living/bot/cleanbot/proc/handle_target()
if(target.clean_marked && target.clean_marked != src)
@@ -71,24 +72,22 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
path = list()
ignorelist |= target
return
- if(loc == target.loc)
+ if(get_turf(src) == get_turf(target))
if(!cleaning)
UnarmedAttack(target)
- return 1
+ return TRUE
if(!path.len)
- path = AStar(loc, target.loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
+ path = AStar(get_turf(src), get_turf(target), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id = botcard)
if(!path)
- //log_debug("[src] can't reach [target.name] ([target.x], [target.y])")
ignorelist |= target
target.clean_marked = null
target = null
path = list()
return
- if(path.len)
+ else
step_to(src, path[1])
path -= path[1]
- return 1
- return
+ return TRUE
/mob/living/bot/cleanbot/proc/remove_from_ignore(path)
ignorelist -= path
@@ -107,23 +106,24 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
if(cleaning)
return
- if(!screwloose && !oddbutton && prob(2))
+ if(!screw_loose && !odd_button && prob(2) && world.time > last_emote + 2 MINUTES)
custom_emote(2, "makes an excited beeping booping sound!")
+ last_emote = world.time
- if(screwloose && prob(5)) // Make a mess
+ if(screw_loose && prob(5)) // Make a mess
if(istype(loc, /turf/simulated))
var/turf/simulated/T = loc
T.wet_floor()
- 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)
+ if(odd_button && prob(5)) // Make a big mess
+ visible_message(SPAN_WARNING("Some bloody gibs fall out of [src]..."))
+ var/obj/effect/decal/cleanable/blood/gibs/gib = new /obj/effect/decal/cleanable/blood/gibs(get_turf(src))
ignorelist += gib
addtimer(CALLBACK(src, .proc/remove_from_ignore, gib), 600)
/mob/living/bot/cleanbot/think()
..()
- if (!on)
+ if(!on)
return
if(pulledby) // Don't wiggle if someone pulls you
@@ -137,11 +137,12 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
// This loop will progressively search outwards for /cleanables in view(), gradually to prevent excessively large view() calls when none are needed.
search_for: // We use the label so we can break out of this loop from within the next loop.
// Not breaking out of these loops properly is where the infinite loop was coming from before.
- for(var/i=0, i <= maximum_search_range, i++)
+ for(var/i = 0, i <= maximum_search_range, i++)
clean_for: // This one isn't really needed in this context, but it's good to have in case we expand later.
for(var/obj/effect/decal/cleanable/D in view(i, src))
- if(D.clean_marked && D.clean_marked != src) continue clean_for
- var/mob/living/bot/cleanbot/other_bot = locate() in D.loc
+ if(D.clean_marked && D.clean_marked != src)
+ continue clean_for
+ var/mob/living/bot/cleanbot/other_bot = locate() in get_turf(D)
if(other_bot && other_bot.cleaning && other_bot != src)
continue clean_for
if((D in ignorelist))
@@ -153,9 +154,8 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
target = D
D.clean_marked = src
found_spot = handle_target()
- if (found_spot)
+ if(found_spot)
break search_for // If the target location is found and pathed properly, break the search loop.
-
else
target = null // Otherwise we want to try the next cleanable in view, if any.
D.clean_marked = null
@@ -197,44 +197,47 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
patrol_path -= patrol_path[1]
/mob/living/bot/cleanbot/UnarmedAttack(var/obj/effect/decal/cleanable/D, var/proximity)
- if(!..())
+ . = ..()
+ if(!.)
return
+ if(isturf(D))
+ D = locate(/obj/effect/decal/cleanable) in D
if(!istype(D))
return
- if(D.loc != loc)
+ if(!src.Adjacent(D))
return
- cleaning = 1
- target.being_cleaned = 1
+ cleaning = TRUE
+ D.being_cleaned = TRUE
+ update_icons()
+ var/clean_time = istype(D, /obj/effect/decal/cleanable/dirt) ? 10 : 50
+ INVOKE_ASYNC(src, .proc/do_clean, D, clean_time)
+
+/mob/living/bot/cleanbot/proc/do_clean(var/obj/effect/decal/cleanable/D, var/clean_time)
+ if(D && do_after(src, clean_time))
+ if(istype(D.loc, /turf/simulated))
+ var/turf/simulated/f = loc
+ f.dirt = 0
+ if(!D)
+ return
+ D.clean_marked = null
+ if(D == target)
+ target.being_cleaned = FALSE
+ target = null
+ qdel(D)
+ cleaning = FALSE
update_icons()
- var/cleantime = istype(D, /obj/effect/decal/cleanable/dirt) ? 10 : 50
- spawn(1)
- if(do_after(src, cleantime))
- if(istype(loc, /turf/simulated))
- var/turf/simulated/f = loc
- f.dirt = 0
- if(!D)
- return
- D.clean_marked = null
- if(D == target)
- target.being_cleaned = 0
- target = null
- qdel(D)
- cleaning = 0
- update_icons()
/mob/living/bot/cleanbot/explode()
- on = 0
- visible_message("[src] blows apart!")
- var/turf/Tsec = get_turf(src)
-
- new /obj/item/reagent_containers/glass/bucket(Tsec)
- new /obj/item/device/assembly/prox_sensor(Tsec)
+ on = FALSE // the first thing i do when i explode is turn off, tbh - geeves
+ visible_message(SPAN_WARNING("[src] blows apart!"))
+ var/turf/T = get_turf(src)
+ new /obj/item/reagent_containers/glass/bucket(T)
+ new /obj/item/device/assembly/prox_sensor(T)
if(prob(50))
- new /obj/item/robot_parts/l_arm(Tsec)
-
+ new /obj/item/robot_parts/l_arm(T)
spark(src, 3, alldirs)
qdel(src)
return
@@ -252,8 +255,8 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
patrol_path = list()
/mob/living/bot/cleanbot/attack_hand(var/mob/user)
- if (!has_ui_access(user) && !emagged)
- to_chat(user, "The unit's interface refuses to unlock!")
+ if(!has_ui_access(user) && !emagged)
+ to_chat(user, SPAN_WARNING("The unit's interface refuses to unlock!"))
return
var/dat
@@ -262,11 +265,11 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
"
dat += "Maintenance panel is [open ? "opened" : "closed"]"
if(!locked || issilicon(user))
- dat += "
Cleans Blood: [blood ? "Yes" : "No"]
"
+ dat += "
Cleans Blood: [cleans_blood ? "Yes" : "No"]
"
dat += "
Patrol station: [should_patrol ? "Yes" : "No"]
"
if(open && !locked)
- dat += "Odd looking screw twiddled: [screwloose ? "Yes" : "No"]
"
- dat += "Weird button pressed: [oddbutton ? "Yes" : "No"]"
+ dat += "Odd looking screw twiddled: [screw_loose ? "Yes" : "No"]
"
+ dat += "Weird button pressed: [odd_button ? "Yes" : "No"]"
user << browse("
Cleaner v1.1 controls[dat]", "window=autocleaner")
onclose(user, "autocleaner")
@@ -278,8 +281,8 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
usr.set_machine(src)
add_fingerprint(usr)
- if (!has_ui_access(usr) && !emagged)
- to_chat(usr, "Insufficient permissions.")
+ if(!has_ui_access(usr) && !emagged)
+ to_chat(usr, SPAN_WARNING("Insufficient permissions."))
return
switch(href_list["operation"])
@@ -289,31 +292,31 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
else
turn_on()
if("blood")
- blood = !blood
+ cleans_blood = !cleans_blood
get_targets()
if("patrol")
should_patrol = !should_patrol
patrol_path = list()
if("freq")
- var/freq = text2num(input("Select frequency for navigation beacons", "Frequnecy", num2text(beacon_freq / 10))) * 10
- if (freq > 0)
+ 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
- to_chat(usr, "You twiddle the screw.")
- if("oddbutton")
- oddbutton = !oddbutton
- to_chat(usr, "You press the weird button.")
+ screw_loose = !screw_loose
+ to_chat(usr, SPAN_NOTICE("You twiddle the screw."))
+ if("odd_button")
+ odd_button = !odd_button
+ to_chat(usr, SPAN_NOTICE("You press the weird button."))
attack_hand(usr)
/mob/living/bot/cleanbot/emag_act(var/remaining_uses, var/mob/user)
. = ..()
- if(!screwloose || !oddbutton)
+ if(!screw_loose || !odd_button)
if(user)
- to_chat(user, "The [src] buzzes and beeps.")
- oddbutton = 1
- screwloose = 1
- return 1
+ to_chat(user, SPAN_NOTICE("The [src] buzzes and beeps."))
+ odd_button = TRUE
+ screw_loose = TRUE
+ return TRUE
/mob/living/bot/cleanbot/proc/get_targets()
// To avoid excessive loops, instead of storing a list of top-level types, we're going to store a list of all cleanables.
@@ -327,19 +330,8 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
/obj/effect/decal/cleanable/crayon,/obj/effect/decal/cleanable/liquid_fuel,/obj/effect/decal/cleanable/mucus,/obj/effect/decal/cleanable/dirt)
// I honestly forgot you could pass multiple types to typesof() until I accidentally did it here.
target_types = cleanbot_types.Copy()
- if(!blood)
+ if(!cleans_blood)
target_types -= typesof(/obj/effect/decal/cleanable/blood)-typesof(/obj/effect/decal/cleanable/blood/oil)
-/* target_types = list()
-
- target_types += /obj/effect/decal/cleanable/blood/oil
- target_types += /obj/effect/decal/cleanable/vomit
- target_types += /obj/effect/decal/cleanable/crayon
- target_types += /obj/effect/decal/cleanable/liquid_fuel
- target_types += /obj/effect/decal/cleanable/mucus
- target_types += /obj/effect/decal/cleanable/dirt
-
- if(blood)
- target_types += /obj/effect/decal/cleanable/blood*/
/* Radio object that listens to signals */
@@ -367,28 +359,26 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th
/* Assembly */
/obj/item/bucket_sensor
- desc = "It's a bucket. With a sensor attached."
name = "proxy bucket"
+ desc = "It's a bucket. With a sensor attached."
icon = 'icons/obj/aibots.dmi'
icon_state = "bucket_proxy"
- force = 3.0
- throwforce = 10.0
+ force = 3
+ throwforce = 10
throw_speed = 2
throw_range = 5
- w_class = 3.0
var/created_name = "Cleanbot"
/obj/item/bucket_sensor/attackby(var/obj/item/O, var/mob/user)
..()
if(istype(O, /obj/item/robot_parts/l_arm) || istype(O, /obj/item/robot_parts/r_arm))
- user.drop_from_inventory(O,get_turf(src))
+ user.drop_from_inventory(O, get_turf(src))
qdel(O)
- var/turf/T = get_turf(loc)
+ var/turf/T = get_turf(src)
var/mob/living/bot/cleanbot/A = new /mob/living/bot/cleanbot(T)
A.name = created_name
- to_chat(user, "You add the robot arm to the bucket and sensor assembly. Beep boop!")
+ to_chat(user, SPAN_NOTICE("You add the robot arm to the bucket and sensor assembly. Beep boop!"))
qdel(src)
-
else if(O.ispen())
var/t = sanitizeSafe(input(user, "Enter new robot name", name, created_name), MAX_NAME_LEN)
if(!t)
diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm
index a8aa5e70ec9..0b1256b6ec3 100644
--- a/code/modules/mob/living/bot/farmbot.dm
+++ b/code/modules/mob/living/bot/farmbot.dm
@@ -6,7 +6,7 @@
/mob/living/bot/farmbot
name = "Farmbot"
- desc = "The botanist's best friend."
+ desc = "The botanist's best friend. Various farming equipment seems haphazardly attached to it."
icon = 'icons/obj/aibots.dmi'
icon_state = "farmbot0"
health = 50
@@ -14,17 +14,17 @@
req_one_access = list(access_hydroponics, access_robotics)
var/action = "" // Used to update icon
- var/waters_trays = 1
- var/refills_water = 1
- var/uproots_weeds = 1
- var/replaces_nutriment = 0
- var/collects_produce = 0
- var/removes_dead = 0
- var/eliminates_pests = 0
+ var/waters_trays = TRUE
+ var/refills_water = TRUE
+ var/uproots_weeds = TRUE
+ var/replaces_nutriment = FALSE
+ var/collects_produce = FALSE
+ var/removes_dead = FALSE
+ var/eliminates_pests = FALSE
var/obj/structure/reagent_dispensers/watertank/tank
- var/attacking = 0
+ var/attacking = FALSE
var/list/path = list()
var/atom/target
var/frustration = 0
@@ -45,7 +45,7 @@
return
if (!has_ui_access(user))
- to_chat(user, "The unit's interface refuses to unlock!")
+ to_chat(user, SPAN_WARNING("The unit's interface refuses to unlock!"))
return
var/dat = ""
@@ -79,11 +79,11 @@
. = ..()
if(!emagged)
if(user)
- to_chat(user, "You short out [src]'s plant identifier circuits.")
+ to_chat(user, SPAN_NOTICE("You short out [src]'s plant identifier circuits."))
spawn(rand(30, 50))
- visible_message("[src] buzzes oddly.")
- emagged = 1
- return 1
+ visible_message(SPAN_WARNING("[src] buzzes oddly."))
+ emagged = TRUE
+ return TRUE
/mob/living/bot/farmbot/Topic(href, href_list)
if(..())
@@ -91,8 +91,8 @@
usr.machine = src
add_fingerprint(usr)
- if (!has_ui_access(usr))
- to_chat(usr, "Insufficient permissions.")
+ if(!has_ui_access(usr))
+ to_chat(usr, SPAN_WARNING("Insufficient permissions."))
return
if(href_list["power"])
@@ -180,7 +180,8 @@
path = list()
/mob/living/bot/farmbot/UnarmedAttack(var/atom/A, var/proximity)
- if(!..())
+ . = ..()
+ if(!.)
return
if(attacking)
return
@@ -194,48 +195,48 @@
if(FARMBOT_COLLECT)
action = "collect"
update_icons()
- visible_message("[src] starts [T.dead? "removing the plant from" : "harvesting"] \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts [T.dead? "removing the plant from" : "harvesting"] \the [A]."))
+ attacking = TRUE
if(do_after(src, 30))
- visible_message("[src] [T.dead? "removes the plant from" : "harvests"] \the [A].")
+ visible_message(SPAN_NOTICE("[src] [T.dead? "removes the plant from" : "harvests"] \the [A]."))
T.attack_hand(src)
if(FARMBOT_WATER)
action = "water"
update_icons()
- visible_message("[src] starts watering \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts watering \the [A]."))
+ attacking = TRUE
if(do_after(src, 30))
- playsound(loc, 'sound/effects/slosh.ogg', 25, 1)
- visible_message("[src] waters \the [A].")
+ playsound(get_turf(src), 'sound/effects/slosh.ogg', 25, TRUE)
+ visible_message(SPAN_NOTICE("[src] waters \the [A]."))
tank.reagents.trans_to(T, 100 - T.waterlevel)
if(FARMBOT_UPROOT)
action = "hoe"
update_icons()
- visible_message("[src] starts uprooting the weeds in \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts uprooting the weeds in \the [A]."))
+ attacking = TRUE
if(do_after(src, 30))
- visible_message("[src] uproots the weeds in \the [A].")
+ visible_message(SPAN_NOTICE("[src] uproots the weeds in \the [A]."))
T.weedlevel = 0
T.update_icon()
if(FARMBOT_PESTKILL)
action = "hoe"
update_icons()
- visible_message("[src] starts eliminating the pests in \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts eliminating the pests in \the [A]."))
+ attacking = TRUE
if(do_after(src, 30))
- visible_message("[src] decimates the pests in \the [A].")
+ visible_message(SPAN_NOTICE("[src] eliminates the pests in \the [A]."))
T.pestlevel = 0
T.reagents.add_reagent("nutriment", 0.5)
T.update_icon()
if(FARMBOT_NUTRIMENT)
action = "fertile"
update_icons()
- visible_message("[src] starts fertilizing \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts fertilizing \the [A]."))
+ attacking = TRUE
if(do_after(src, 30))
- visible_message("[src] waters \the [A].")
+ visible_message(SPAN_NOTICE("[src] waters \the [A]."))
T.reagents.add_reagent("ammonia", 10)
- attacking = 0
+ attacking = FALSE
action = ""
update_icons()
T.update_icon()
@@ -244,48 +245,46 @@
return
action = "water"
update_icons()
- visible_message("[src] starts refilling its tank from \the [A].")
- attacking = 1
+ visible_message(SPAN_NOTICE("[src] starts refilling its tank from \the [A]."))
+ attacking = TRUE
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
+ playsound(get_turf(src), 'sound/effects/slosh.ogg', 25, TRUE)
+ attacking = FALSE
action = ""
update_icons()
- visible_message("[src] finishes refilling its tank.")
+ visible_message(SPAN_NOTICE("[src] finishes refilling its tank."))
else if(emagged && ishuman(A))
var/action = pick("weed", "water")
- attacking = 1
+ attacking = TRUE
spawn(50) // Some delay
- attacking = 0
+ attacking = FALSE
switch(action)
if("weed")
flick("farmbot_hoe", src)
do_attack_animation(A)
if(prob(50))
- visible_message("[src] swings wildly at [A] with a minihoe, missing completely!")
+ visible_message(SPAN_DANGER("[src] swings wildly at [A] with a minihoe, missing completely!"))
return
var/t = pick("slashed", "sliced", "cut", "clawed")
A.attack_generic(src, 5, t)
if("water")
flick("farmbot_water", src)
- visible_message("[src] splashes [A] with water!") // That's it. RP effect.
+ visible_message(SPAN_DANGER("[src] splashes [A] with water!")) // That's it. RP effect.
/mob/living/bot/farmbot/explode()
- visible_message("[src] blows apart!")
- var/turf/Tsec = get_turf(src)
-
- new /obj/item/material/minihoe(Tsec)
- new /obj/item/reagent_containers/glass/bucket(Tsec)
- new /obj/item/device/assembly/prox_sensor(Tsec)
- new /obj/item/device/analyzer/plant_analyzer(Tsec)
+ visible_message(SPAN_DANGER("[src] blows apart!"))
+ var/turf/T = get_turf(src)
+ new /obj/item/material/minihoe(T)
+ new /obj/item/reagent_containers/glass/bucket(T)
+ new /obj/item/device/assembly/prox_sensor(T)
+ new /obj/item/device/analyzer/plant_analyzer(T)
if(tank)
- tank.forceMove(Tsec)
-
+ tank.forceMove(T)
if(prob(50))
- new /obj/item/robot_parts/l_arm(Tsec)
+ new /obj/item/robot_parts/l_arm(T)
spark(src, 3, alldirs)
qdel(src)
@@ -293,34 +292,27 @@
/mob/living/bot/farmbot/proc/process_tray(var/obj/machinery/portable_atmospherics/hydroponics/tray)
if(!tray || !istype(tray))
- return 0
-
+ return FALSE
if(tray.closed_system || !tray.seed)
- return 0
-
+ return FALSE
if(tray.dead && removes_dead || tray.harvest && collects_produce)
return FARMBOT_COLLECT
-
else if(waters_trays && tray.waterlevel < 10 && !tray.reagents.has_reagent("water"))
return FARMBOT_WATER
-
else if(uproots_weeds && tray.weedlevel >= 5)
return FARMBOT_UPROOT
-
else if(eliminates_pests && tray.pestlevel >= 3)
return FARMBOT_PESTKILL
-
else if(replaces_nutriment && tray.nutrilevel < 2 && tray.reagents.total_volume < 1)
return FARMBOT_NUTRIMENT
-
- return 0
+ return FALSE
// Assembly
/mob/living/bot/farmbot/proc/check_tank()
- if(!tank)
- return FALSE
- return ((!target && refills_water && tank.reagents.total_volume < tank.reagents.maximum_volume) || ((tank.reagents.total_volume ) / tank.reagents.maximum_volume) <= 0.3)
+ if(!tank)
+ return FALSE
+ return ((!target && refills_water && tank.reagents.total_volume < tank.reagents.maximum_volume) || ((tank.reagents.total_volume ) / tank.reagents.maximum_volume) <= 0.3)
/obj/item/farmbot_arm_assembly
name = "water tank/robot arm assembly"
@@ -329,46 +321,42 @@
icon_state = "water_arm"
var/build_step = 0
var/created_name = "Farmbot"
- w_class = 3.0
-/obj/structure/reagent_dispensers/watertank/attackby(var/obj/item/robot_parts/S, mob/user as mob)
+/obj/structure/reagent_dispensers/watertank/attackby(obj/item/robot_parts/S, mob/user)
if ((!istype(S, /obj/item/robot_parts/l_arm)) && (!istype(S, /obj/item/robot_parts/r_arm)))
..()
return
var/obj/item/farmbot_arm_assembly/A = new /obj/item/farmbot_arm_assembly(loc)
- to_chat(user, "You add the robot arm to [src].")
+ to_chat(user, SPAN_NOTICE("You add \the [S] to \the [src]."))
loc = A //Place the water tank into the assembly, it will be needed for the finished bot
qdel(S)
-/obj/item/farmbot_arm_assembly/attackby(obj/item/W as obj, mob/user as mob)
+/obj/item/farmbot_arm_assembly/attackby(obj/item/W, mob/user)
..()
- if((istype(W, /obj/item/device/analyzer/plant_analyzer)) && (build_step == 0))
+ if(istype(W, /obj/item/device/analyzer/plant_analyzer) && build_step == 0)
build_step++
- to_chat(user, "You add the plant analyzer to [src].")
+ to_chat(user, SPAN_NOTICE("You add the plant analyzer to [src]."))
name = "farmbot assembly"
qdel(W)
- return 1
-
- else if((istype(W, /obj/item/reagent_containers/glass/bucket)) && (build_step == 1))
+ return TRUE
+ else if(istype(W, /obj/item/reagent_containers/glass/bucket) && build_step == 1)
build_step++
- to_chat(user, "You add a bucket to [src].")
+ to_chat(user, SPAN_NOTICE("You add a bucket to [src]."))
name = "farmbot assembly with bucket"
qdel(W)
- return 1//Prevents the object's afterattack from executing and causing runtime errors
-
- else if((istype(W, /obj/item/material/minihoe)) && (build_step == 2))
+ return TRUE //Prevents the object's afterattack from executing and causing runtime errors
+ else if(istype(W, /obj/item/material/minihoe) && build_step == 2)
build_step++
- to_chat(user, "You add a minihoe to [src].")
+ to_chat(user, SPAN_NOTICE("You add a minihoe to [src]."))
name = "farmbot assembly with bucket and minihoe"
user.remove_from_mob(W)
qdel(W)
- return 1
-
- else if((isprox(W)) && (build_step == 3))
+ return TRUE
+ else if(isprox(W) && build_step == 3)
build_step++
- to_chat(user, "You complete the Farmbot! Beep boop.")
+ to_chat(user, SPAN_NOTICE("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)
qdel(S.tank)
@@ -378,8 +366,7 @@
user.remove_from_mob(W)
qdel(W)
qdel(src)
- return 1
-
+ return TRUE
else if(W.ispen())
var/t = input(user, "Enter new robot name", name, created_name) as text
t = sanitize(t, MAX_NAME_LEN)
@@ -387,8 +374,7 @@
return
if(!in_range(src, usr) && loc != usr)
return
-
created_name = t
-/obj/item/farmbot_arm_assembly/attack_hand(mob/user as mob)
- return //it's a converted watertank, no you cannot pick it up and put it in your backpack
+/obj/item/farmbot_arm_assembly/attack_hand(mob/user)
+ return //it's a converted watertank, no you cannot pick it up and put it in your backpack
\ No newline at end of file
diff --git a/code/modules/mob/living/bot/floorbot.dm b/code/modules/mob/living/bot/floorbot.dm
index 6213ab77855..d2973d47cb6 100644
--- a/code/modules/mob/living/bot/floorbot.dm
+++ b/code/modules/mob/living/bot/floorbot.dm
@@ -194,13 +194,14 @@
target = null
/mob/living/bot/floorbot/UnarmedAttack(var/atom/A, var/proximity)
- if(!..())
+ . = ..()
+ if(!.)
return
if(repairing)
return
- if(get_turf(A) != loc)
+ if(!src.Adjacent(A))
return
if(emagged && istype(A, /turf/simulated/floor))
diff --git a/code/modules/mob/living/bot/medbot.dm b/code/modules/mob/living/bot/medbot.dm
index 383da0f0962..d0b09c0d152 100644
--- a/code/modules/mob/living/bot/medbot.dm
+++ b/code/modules/mob/living/bot/medbot.dm
@@ -74,7 +74,8 @@
break
/mob/living/bot/medbot/UnarmedAttack(var/mob/living/carbon/human/H, var/proximity)
- if(!..())
+ . = ..()
+ if(!.)
return
if(!on)
@@ -84,6 +85,9 @@
return
if(H.stat == DEAD)
+ if(pAI)
+ to_chat(pAI.pai, SPAN_WARNING("\The [H] is dead, you cannot help them now."))
+ return
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
diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm
index da35e4c0539..55f0f743096 100644
--- a/code/modules/mob/living/bot/secbot.dm
+++ b/code/modules/mob/living/bot/secbot.dm
@@ -58,6 +58,7 @@
var/datum/callback/patrol_callback // this is here so we don't constantly recreate this datum, it being identical each time.
var/move_to_delay = 4 //delay for the automated movement.
+ can_take_pai = FALSE
/mob/living/bot/secbot/beepsky
name = "Officer Beepsky"
diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm
index 66849131c99..b99761c25e2 100644
--- a/code/modules/mob/living/silicon/pai/life.dm
+++ b/code/modules/mob/living/silicon/pai/life.dm
@@ -35,3 +35,18 @@
stat = CONSCIOUS
else
health = 100 - getBruteLoss() - getFireLoss()
+
+/mob/living/silicon/pai/Stat()
+ ..()
+
+ if(istype(card.loc, /mob/living/bot))
+ var/mob/living/bot/B = card.loc
+ stat(null, "Piloting: [B.name]")
+ stat(null, "Bot Status: [B.on ? "Active" : "Inactive"]")
+ stat(null, "Maintenance Hatch: [B.open ? "Open" : "Closed"]")
+ stat(null, "Maintenance Lock: [B.locked ? "Locked" : "Unlocked"]")
+ if(B.emagged)
+ stat(null, "Bot M#$FUN90: MALFUNC--")
+ if(istype(card.loc, /mob/living/bot/floorbot))
+ var/mob/living/bot/floorbot/F = card.loc
+ stat(null, "Metal Count: [F.amount]/[F.maxAmount]")
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 402004c694d..53ea697f61d 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -209,7 +209,6 @@
greet()
..()
-
/mob/living/silicon/pai/proc/greet()
if (!greeted)
@@ -346,12 +345,18 @@
return
last_special = world.time + 20
+ open_up()
+
+/mob/living/silicon/pai/proc/open_up(var/loud = TRUE)
+ if(istype(card.loc, /mob/living/bot))
+ to_chat(src, SPAN_WARNING("You cannot unfold while inside the bot!"))
+ return FALSE
//I'm not sure how much of this is necessary, but I would rather avoid issues.
if(istype(card.loc,/obj/item/rig_module))
- to_chat(src, "There is no room to unfold inside this rig module. You're good and stuck.")
- return 0
- else if(istype(card.loc,/mob))
+ to_chat(src, SPAN_WARNING("There is no room to unfold inside this rig module. You're good and stuck."))
+ return FALSE
+ else if(istype(card.loc, /mob))
var/mob/holder = card.loc
if(ishuman(holder))
var/mob/living/carbon/human/H = holder
@@ -359,7 +364,8 @@
if(card in affecting.implants)
affecting.take_damage(rand(30,50))
affecting.implants -= card
- H.visible_message("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!")
+ if(loud)
+ H.visible_message(SPAN_DANGER("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!"))
break
holder.drop_from_inventory(card)
else if(istype(card.loc,/obj/item/device/pda))
@@ -374,9 +380,11 @@
card.screen_loc = null
var/turf/T = get_turf(src)
- if(istype(T)) T.visible_message("[src] folds outwards, expanding into a mobile form.")
- canmove = 1
- resting = 0
+ if(loud && istype(T))
+ T.visible_message(SPAN_NOTICE("[src] folds outwards, expanding into a mobile form."))
+ canmove = TRUE
+ resting = FALSE
+
/mob/living/silicon/pai/verb/fold_up()
set category = "pAI Commands"
@@ -417,8 +425,8 @@
set name = "Check location"
set desc = "Find out where on their person, someone is holding you."
- if (!get_holding_mob())
- to_chat(src, "Nobody is holding you!")
+ if(!get_holding_mob())
+ to_chat(src, SPAN_WARNING("Nobody is holding you!"))
return
card.report_onmob_location(0, card.get_equip_slot(), src)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 1a058067f5f..f75db5f3389 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -766,6 +766,9 @@ proc/is_blind(A)
*/
/obj/proc/report_onmob_location(var/justmoved, var/slot = null, var/mob/reportto)
+ if(istype(reportto.loc, /mob/living/bot))
+ to_chat(reportto, SPAN_NOTICE("You are currently housed within \the [reportto.loc]."))
+ return
var/mob/living/carbon/human/H//The person who the item is on
var/newlocation
var/preposition= ""
diff --git a/html/changelogs/geeves-player_bots.yml b/html/changelogs/geeves-player_bots.yml
new file mode 100644
index 00000000000..123589900a5
--- /dev/null
+++ b/html/changelogs/geeves-player_bots.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "pAI can now pilot farmbots, cleanbots, floorbots and medbots."
+ - rscadd: "Floating messages now appear even if you're inside an object or another mob."
\ No newline at end of file