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