Files
Batrachophreno 4ecb0bc21c Repath obj/machinery to obj/structure/machinery [MDB Ignore] (#22500)
Repaths obj/machinery to obj/structure/machinery. **Note for
reviewers:** the only meaningful changed code exists within
**code/game/objects/structures.dm** and
**code/game/objects/structures/_machinery.dm**, largely concerning
damage procs. With the exception of moving airlock defines to their own
file, ALL OTHER CHANGES ARE STRICTLY PATH CHANGES.

Objects, _categorically_, are largely divided between those you can hold
in your hand/inventory and those you can't. Machinery objects are
already subtypes of Structures behaviorally, this PR just makes their
pathing reflect that, and allows for future work (tool actions, more
health/destruction functionality) to be developed without unnecessary
code duplication.

I have tested this PR by loading up the Horizon and dismantling various
machines and structures with tools, shooting guns of various types
throughout the ship, and detonating a bunch of explosions throughout the
ship.
2026-05-26 19:35:48 +00:00

516 lines
15 KiB
Plaintext

// Updated by Nadrew, bits and pieces taken from Baycode, but fairly heavily modified to function here (and because a few bits of the baycode was ehh)
// The main issue in the old code was the Life() loop and the fact that it could go infinite really easily.
// The fix involved labeling the various loops involved so they could be continued and broken properly.
// It also decreases the amount of calls to AStar() and handle_target()
///A list of *types* that cleanbots will look for
GLOBAL_LIST_INIT_TYPED(cleanbot_types, /obj/effect/decal/cleanable, typesof(/obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/vomit, /obj/effect/decal/cleanable/flour, \
/obj/effect/decal/cleanable/crayon, /obj/effect/decal/cleanable/liquid_fuel, /obj/effect/decal/cleanable/mucus, /obj/effect/decal/cleanable/dirt))
/obj/effect/decal/cleanable
var/being_cleaned = FALSE
///A reference to a `/mob/living/bot/cleanbot` that wants to clean this turf, or null
var/datum/weakref/clean_marked = null
/mob/living/bot/cleanbot
name = "Cleanbot"
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 = FALSE // Start unlocked so roboticist can set them to patrol.
///The target we're going to clean up, an `/obj/effect/decal/cleanable` weakref
var/datum/weakref/cleaning_target = null
var/list/path = list()
var/list/patrol_path = list()
//A list of `/datum/weakref` that resolve to `/obj/effect/decal/cleanable`, those are the objects to ignore
var/list/datum/weakref/ignorelist = list()
var/obj/cleanbot_listener/listener
///Used for patrol pathing, navbeacons have this by default
var/beacon_freq = BEACONS_FREQ
///The time at which a "findbeacon" signal was broadcasted; it's used for us to find the list of available beacons in patrol mode, to know where to go
var/signal_sent = 0
var/closest_dist = 9999
var/next_dest
var/next_dest_loc
var/screw_loose = FALSE
var/odd_button = FALSE
var/should_patrol = FALSE
var/cleans_blood = TRUE
///A list of `/obj/effect/decal/cleanable` *types* that this borg can target for cleaning
var/list/target_types = list()
var/maximum_search_range = 7
///Boolean, if it's cleaning something *right now* and waiting for the timer to say it's done
var/cleaning = FALSE
///The turf we got the last movement failure on, since doors have to be bumped to be opened
var/turf/last_movement_failure_turf
/mob/living/bot/cleanbot/Cross(atom/movable/crossed)
if(crossed)
if(istype(crossed, /mob/living/bot/cleanbot))
return FALSE
return ..()
/mob/living/bot/cleanbot/Initialize()
. = ..()
get_targets()
//Do not start to patrol until you're told to, also save processing
set_patrol_mode(FALSE)
listener = new /obj/cleanbot_listener(src)
listener.cleanbot = src
if(is_station_turf(get_turf(src)))
GLOB.janitorial_supplies |= src
SSradio.add_object(listener, beacon_freq, filter = RADIO_NAVBEACONS)
/mob/living/bot/cleanbot/Destroy()
path = null
patrol_path = null
cleaning_target = null
ignorelist = null
next_dest_loc = null
QDEL_NULL(listener)
SSradio.remove_object(listener, beacon_freq)
if(src in GLOB.janitorial_supplies)
GLOB.janitorial_supplies -= src
return ..()
/mob/living/bot/cleanbot/proc/handle_target()
//Get the actual cleanable decal to target
var/obj/effect/decal/cleanable/cleaning_target_cache = cleaning_target?.resolve()
var/mob/living/bot/cleanbot/turf_targeting_cleanbot = cleaning_target_cache?.clean_marked?.resolve()
//If already marked by another borg, ignore it
if(turf_targeting_cleanbot != src)
cleaning_target = null
path = list()
ignorelist |= cleaning_target
return
//If we are over it, clean it up
if(get_turf(src) == get_turf(cleaning_target_cache))
if(!cleaning)
UnarmedAttack(cleaning_target_cache)
return TRUE
//Try to get a path to the location if you don't have one
if(!length(path))
path = get_path_to(src, cleaning_target_cache, 250, 0, botcard.GetAccess(), diagonal_handling=DIAGONAL_REMOVE_ALL)
//No length means there's no path to reach it, add it to the exclusions
if(!length(path))
ignorelist |= cleaning_target
cleaning_target_cache.clean_marked = null
cleaning_target = null
path = list()
if(length(path) && !cleaning)
var/successfully_moved = step_to(src, path[1])
if(successfully_moved)
path -= path[1]
return TRUE
//Something blocked us, look for a different target, we might come back to this in a while
else
for(var/obj/structure/machinery/door/a_door in path[1])
if(last_movement_failure_turf != path[1])
last_movement_failure_turf = path[1]
return
//This is the second failure, invalidate the target
else
ignorelist |= cleaning_target
cleaning_target_cache.clean_marked = null
cleaning_target = null
path = list()
break
/mob/living/bot/cleanbot/proc/remove_from_ignore(datum/weakref/thing_to_unignore)
ignorelist -= thing_to_unignore
/mob/living/bot/cleanbot/Life(seconds_per_tick, times_fired)
if(!..())
return FALSE
if(!on)
ignorelist = list()
return
if(length(ignorelist) && prob(2))
ignorelist -= pick(ignorelist)
if(client)
return
if(cleaning)
return
if(!screw_loose && !odd_button && prob(2) && world.time > last_emote + 2 MINUTES)
custom_emote(AUDIBLE_MESSAGE, "makes an excited beeping booping sound!")
last_emote = world.time
if(screw_loose && prob(5)) // Make a mess
if(istype(loc, /turf/simulated))
var/turf/simulated/T = loc
T.wet_floor()
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_REF(remove_from_ignore), gib), 600)
return TRUE
/mob/living/bot/cleanbot/think()
if(pAI) // no AI if we have a pAI installed
return
..()
if(!on)
return
if(pulledby) // Don't wiggle if someone pulls you
patrol_path?.Cut()
return
//We have a path to a target already, follow it
if(length(path))
handle_target()
//Otherwise, look around for a target, or patrol
else
//If we could a spot to clean or not
var/found_spot
for(var/obj/effect/decal/cleanable/D in view(maximum_search_range, src))
var/datum/weakref/cleanable_weakref = WEAKREF(D)
var/mob/living/bot/cleanbot/turf_targeting_cleanbot = D.clean_marked?.resolve()
//Someone already wants this cleanable and it's not us, keep looking
if(!isnull(turf_targeting_cleanbot) && turf_targeting_cleanbot != src)
continue
var/mob/living/bot/cleanbot/other_bot = locate() in get_turf(D)
if(other_bot && other_bot.cleaning && other_bot != src)
continue
// If the object has been slated to be ignored we continue the loop.
if((cleanable_weakref in ignorelist))
continue
// A matching /cleanable was found, now we want to path trace to it and see if we can reach it.
if((D.type in target_types))
patrol_path = list()
cleaning_target = cleanable_weakref
D.clean_marked = WEAKREF(src)
found_spot = handle_target()
if(found_spot)
break // If the target location is found and pathed properly, break the search loop.
else
cleaning_target = null // Otherwise we want to try the next cleanable in view, if any.
D.clean_marked = null
if(!found_spot && !cleaning_target) // No targets in range
if(!patrol_path || !patrol_path.len)
if(!signal_sent || signal_sent > world.time + 200) // Waited enough or didn't send yet
var/datum/radio_frequency/frequency = SSradio.return_frequency(beacon_freq)
if(!frequency)
return
closest_dist = 9999
next_dest = null
next_dest_loc = null
var/datum/signal/signal = new()
signal.source = src
signal.transmission_method = TRANSMISSION_RADIO
signal.data = list("findbeacon" = "patrol")
frequency.post_signal(src, signal, filter = RADIO_NAVBEACONS)
signal_sent = world.time
else
if(next_dest)
next_dest_loc = listener.memorized[next_dest]
if(next_dest_loc)
patrol_path = get_path_to(loc, next_dest_loc, 120, 0, botcard.GetAccess(), diagonal_handling=DIAGONAL_REMOVE_ALL)
signal_sent = 0
else if(should_patrol)
if(patrol_path[1] == loc)
patrol_path -= patrol_path[1]
return
var/moved = step_towards(src, patrol_path[1])
if(moved)
patrol_path -= patrol_path[1]
/mob/living/bot/cleanbot/UnarmedAttack(var/obj/effect/decal/cleanable/D, var/proximity)
. = ..()
if(!.)
return
if(isturf(D))
D = locate(/obj/effect/decal/cleanable) in D
if(!istype(D))
return
if(!src.Adjacent(D))
return
cleaning = TRUE
D.being_cleaned = TRUE
update_icon()
var/clean_time = istype(D, /obj/effect/decal/cleanable/dirt) ? 10 : 50
addtimer(CALLBACK(src, PROC_REF(do_clean), D), clean_time)
/mob/living/bot/cleanbot/proc/do_clean(var/obj/effect/decal/cleanable/D)
if(D)
if(istype(D.loc, /turf/simulated))
var/turf/simulated/f = loc
f.dirt = 0
if(!D)
return
D.clean_marked = null
D.being_cleaned = FALSE
cleaning_target = null
qdel(D)
cleaning = FALSE
update_icon()
/mob/living/bot/cleanbot/explode()
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/assembly/prox_sensor(T)
if(prob(50))
new /obj/item/robot_parts/l_arm(T)
spark(src, 3, GLOB.alldirs)
qdel(src)
return
/mob/living/bot/cleanbot/update_icon()
if(cleaning)
icon_state = "cleanbot-c"
else
icon_state = "cleanbot[on]"
/mob/living/bot/cleanbot/attack_hand(mob/user)
ui_interact(user)
/mob/living/bot/cleanbot/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CleanBot")
ui.open()
/mob/living/bot/cleanbot/ui_data(mob/user)
var/list/data = list()
data["status"] = on
data["locked"] = locked
data["maintenance_panel"] = open
data["cleans_blood"] = cleans_blood
data["should_patrol"] = should_patrol
data["screw_loose"] = screw_loose
data["odd_button"] = odd_button
data["beacon_freq"] = beacon_freq
return data
/mob/living/bot/cleanbot/ui_static_data(mob/user)
var/list/data = list()
var/list/cleanables_names = list()
for(var/obj/effect/decal/cleanable/cleanable_type as anything in target_types)
cleanables_names |= capitalize_first_letters(initial(cleanable_type.name))
data["cleanable_types"] = cleanables_names
return data
/mob/living/bot/cleanbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("toggle_status")
if(on)
turn_off()
else
turn_on()
if("toggle_cleans_blood")
cleans_blood = !cleans_blood
get_targets()
if("toggle_patrol_mode")
set_patrol_mode(!should_patrol)
if("set_frequency")
var/new_frequency = tgui_input_number(usr, "Select frequency for navigation beacons", "Frequnecy", (beacon_freq/10), round_value = FALSE)
if(new_frequency > 0)
SSradio.remove_object(listener, beacon_freq)
beacon_freq = new_frequency*10
SSradio.add_object(listener, beacon_freq, RADIO_NAVBEACONS)
if("toggle_screw")
screw_loose = !screw_loose
to_chat(usr, SPAN_NOTICE("You twiddle the screw."))
if("toggle_odd_button")
odd_button = !odd_button
to_chat(usr, SPAN_NOTICE("You press the weird button."))
/mob/living/bot/cleanbot/Topic(href, href_list)
if(..())
return
add_fingerprint(usr)
attack_hand(usr)
/mob/living/bot/cleanbot/attackby(obj/item/attacking_item, mob/user)
. = ..()
//To refresh the lock/unlock from ID hitting etc.
SStgui.try_update_ui(user, src)
/mob/living/bot/cleanbot/turn_on()
. = ..()
MOB_START_THINKING(src)
/mob/living/bot/cleanbot/turn_off()
. = ..()
cleaning_target = null
path = list()
patrol_path = list()
ignorelist = list()
MOB_STOP_THINKING(src)
/**
* Handles the turn on / off of patrol mode
*
* * state - A Boolean, `TRUE` to turn patrol mode on, `FALSE` to turn it off
*/
/mob/living/bot/cleanbot/proc/set_patrol_mode(state)
closest_dist = initial(closest_dist)
patrol_path.Cut()
if(state)
should_patrol = TRUE
patrol_path = list()
signal_sent = 0
else
should_patrol = FALSE
/mob/living/bot/cleanbot/emag_act(var/remaining_uses, var/mob/user)
. = ..()
if(!screw_loose || !odd_button)
if(user)
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()
target_types = GLOB.cleanbot_types
if(!cleans_blood)
//Well now there's a point, this doesn't clean blood or oil
target_types = target_types.Copy()
target_types -= typesof(/obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/blood/oil)
/* Radio object that listens to signals */
/obj/cleanbot_listener
var/mob/living/bot/cleanbot/cleanbot = null
var/list/memorized = list()
/obj/cleanbot_listener/receive_signal(var/datum/signal/signal)
var/recv = signal.data["beacon"]
var/valid = signal.data["patrol"]
if(!recv || !valid || !cleanbot)
return
var/turf/signal_sender_turf = get_turf(signal.source)
var/turf/our_cleanbot_turf = get_turf(cleanbot)
//Nullspace, some other shit, either way, abort
if(!istype(signal_sender_turf) || !istype(our_cleanbot_turf))
return
//At the moment, the path system does not understand zlevels, so this only works on the same zlevel, deal with it, then you can just turn this into:
// if(!AreConnectedZLevels(signal.source.loc?.z, cleanbot.loc?.z))
if(signal_sender_turf.z != our_cleanbot_turf.z)
return
var/dist = get_dist(cleanbot, signal_sender_turf)
memorized[recv] = signal_sender_turf
if(dist < cleanbot.closest_dist) // We check all signals, choosing the closest beacon; then we move to the NEXT one after the closest one
cleanbot.closest_dist = dist
cleanbot.next_dest = signal.data["next_patrol"]
/obj/cleanbot_listener/Destroy()
cleanbot = null
SSradio.remove_object_all(src)
return ..()
/* Assembly */
/obj/item/bucket_sensor
name = "proxy bucket"
desc = "It's a bucket. With a sensor attached."
icon = 'icons/mob/npc/aibots.dmi'
icon_state = "bucket_proxy"
force = 3
throwforce = 10
throw_speed = 2
throw_range = 5
var/created_name = "Cleanbot"
/obj/item/bucket_sensor/attackby(obj/item/attacking_item, mob/user)
..()
if(istype(attacking_item, /obj/item/robot_parts/l_arm) || istype(attacking_item, /obj/item/robot_parts/r_arm))
user.drop_from_inventory(attacking_item, get_turf(src))
qdel(attacking_item)
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, SPAN_NOTICE("You add the robot arm to the bucket and sensor assembly. Beep boop!"))
qdel(src)
else if(attacking_item.tool_behaviour == TOOL_PEN)
var/t = sanitizeSafe( tgui_input_text(user, "Enter new robot name", name, created_name, MAX_NAME_LEN), MAX_NAME_LEN )
if(!t)
return
if(!in_range(src, usr) && src.loc != usr)
return
created_name = t