Maint drones now return to the drone manufacturer when shutdown (#23367)

* luv me some drones

* uhhh WIP

* ..() moment

* Gaxeer review & misc change

* henri review

* tgui

* boom, i think

* oops

* sirryan review

* final stuff

* lmaooo

* Apply suggestions from code review

Co-authored-by: Deniz <66401072+Oyu07@users.noreply.github.com>

---------

Co-authored-by: Deniz <66401072+Oyu07@users.noreply.github.com>
This commit is contained in:
Contrabang
2024-01-30 09:39:46 -05:00
committed by GitHub
parent 0aa98118b0
commit 243ff18304
13 changed files with 278 additions and 36 deletions
+128
View File
@@ -0,0 +1,128 @@
/**
* A generalized datum for pathfinding, and moving to a target.
*/
/datum/pathfinding_mover
/// Can be a simplemob bot, a drone, or even a pathfinding modsuit module (currently only implemented for drones)
VAR_PRIVATE/atom/movable/owner
/// The target turf we are after
VAR_PRIVATE/turf/target
/// List of turfs through which a mod 'steps' to reach the waypoint
VAR_PRIVATE/list/path = list()
/// max amount of tries before resetting path to null
var/max_tries = 10
/// How many times have we tried to move?
VAR_PRIVATE/tries = 0
/// How many 2-tick delays per move (5 = 1 second)
var/move_speed = 2
/// A counter for move_speed via modulo
VAR_PRIVATE/move_ticks = 0
/// Callback invoked on failure
var/datum/callback/on_set_path_null
/// Callback invoked on success
var/datum/callback/on_success
/// The delay called as part of Move()
VAR_PRIVATE/atom/movable/owner_move_delay = 0
/// Do we consider movement delay? Disable for non-mobs
var/consider_movement_delay = TRUE
/// Requires `consider_movement_delay = TRUE`, saves the last movement delay to prevent diagonal weirdness
VAR_PRIVATE/last_movement_delay
/datum/pathfinding_mover/New(_owner, _target)
target = _target
owner = _owner
if(ismob(owner))
var/mob/M = owner
owner_move_delay = M.movement_delay()
RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(signal_qdel))
/datum/pathfinding_mover/Destroy(force, ...)
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
STOP_PROCESSING(SSfastprocess, src)
return ..()
/datum/pathfinding_mover/proc/generate_path(...)
set_path(get_path_to(arglist(list(owner, target) + args)))
return (length(path) > 0)
/datum/pathfinding_mover/proc/set_target(atom/new_target)
if(!isatom(new_target))
target = null
return
target = get_turf(new_target)
/datum/pathfinding_mover/proc/set_path(list/newpath)
PRIVATE_PROC(TRUE)
if(newpath == null)
on_set_path_null?.Invoke() // This is seperate to prevent invoking the callback if calling from generate_path
path = newpath ? newpath : list()
if(!length(path)) // Because newpath could be an empty list
STOP_PROCESSING(SSfastprocess, src)
tries = 0
/**
* Start moving towards our target, returns false if the path does not lead to the target
*/
/datum/pathfinding_mover/proc/start()
if(!target || !length(path)) // Pathfinding failed or a path/destination was not set.
set_path(null)
return FALSE
var/turf/last_node = get_turf(path[length(path)]) // This is the turf at the end of the path
if(target != last_node) // The path should lead us to our given destination. If this is not true, we must stop.
set_path(null)
return FALSE
START_PROCESSING(SSfastprocess, src)
return TRUE
/**
* Using fast process, see if we should take the next step yet
*/
/datum/pathfinding_mover/process(wait) // 2 on fast process
move_ticks++
if(move_speed && (move_ticks < move_speed))
return
if(consider_movement_delay && (last_movement_delay > (move_ticks * wait)))
return
move_ticks = 0
if(tries >= max_tries)
set_path(null)
return // PROCESS_KILL called with set_path(null)
if(get_turf(owner) == target) // We have arrived, no need to move again.
on_success?.Invoke(src)
return PROCESS_KILL
generalized_step()
/**
* Take our next step in our pathfinding algorithm
*/
/datum/pathfinding_mover/proc/generalized_step() // Step, increase tries if failed
PRIVATE_PROC(TRUE)
if(!length(path))
return
var/targetted_direction = get_dir(owner, path[1])
var/delay = owner_move_delay
if(IS_DIR_DIAGONAL(targetted_direction))
delay *= SQRT_2
if(!owner.Move(path[1], targetted_direction, delay))
tries++
return
tries = 0
if(consider_movement_delay)
last_movement_delay = delay
// Increment the path
path.Cut(1, 2)
+6 -2
View File
@@ -1366,8 +1366,12 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays)
return 1
/obj/machinery/door/airlock/CanPathfindPass(obj/item/card/id/ID, to_dir, atom/movable/caller, no_id = FALSE)
//Airlock is passable if it is open (!density), bot has access, and is not bolted or welded shut)
return !density || (check_access(ID) && !locked && !welded && arePowerSystemsOn() && !no_id)
if(!density)
return TRUE
if(caller?.checkpass(PASSDOOR) && !locked)
return TRUE
//Airlock is passable if it is open (!density), bot has access, and is not bolted or welded shut)
return check_access(ID) && !locked && !welded && arePowerSystemsOn() && !no_id
/obj/machinery/door/airlock/emag_act(mob/user)
if(!operating && density && arePowerSystemsOn() && !emagged)
+9
View File
@@ -502,3 +502,12 @@
/obj/machinery/door/zap_act(power, zap_flags)
zap_flags &= ~ZAP_OBJ_DAMAGE
. = ..()
/obj/machinery/door/CanPathfindPass(obj/item/card/id/ID, to_dir, atom/movable/caller, no_id)
if(QDELETED(caller))
return ..()
if(caller.checkpass(PASSDOOR) && !locked)
return TRUE
if(caller.checkpass(PASSGLASS))
return !opacity
return ..()
+1 -1
View File
@@ -78,7 +78,7 @@
/obj/structure/plasticflaps/CanPathfindPass(obj/item/card/id/ID, to_dir, caller, no_id = FALSE)
if(isliving(caller))
if(isbot(caller))
if(isbot(caller) || isdrone(caller))
return TRUE
var/mob/living/M = caller
-3
View File
@@ -61,9 +61,6 @@
//put em at -175
adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
/mob/living/silicon/robot/drone/do_suicide()
shut_down()
/mob/living/silicon/pai/do_suicide()
if(mobility_flags & MOBILITY_MOVE)
close_up()
@@ -66,7 +66,8 @@
health = round(D.health / D.maxHealth, 0.1),
charge = round(D.cell.charge / D.cell.maxcharge, 0.1),
location = "[A] ([T.x], [T.y])",
sync_cd = D.sync_cooldown > world.time ? TRUE : FALSE
sync_cd = D.sync_cooldown > world.time ? TRUE : FALSE,
pathfinding = D.pathfinding
)
data["drones"] += list(drone_data)
return data
@@ -113,13 +114,14 @@
to_chat(usr, "<span class='notice'>You issue a law synchronization directive for the drone.</span>")
D.law_resync()
if("shutdown")
if("recall")
var/mob/living/silicon/robot/drone/D = locateUID(params["uid"])
if(D)
to_chat(usr, "<span class='warning'>You issue a kill command for the unfortunate drone.</span>")
to_chat(usr, "<span class='warning'>You issue a recall command for the unfortunate drone.</span>")
if(D != usr) // Don't need to bug admins about a suicide
message_admins("[key_name_admin(usr)] issued kill order for drone [key_name_admin(D)] from control console.")
log_game("[key_name(usr)] issued kill order for [key_name(D)] from control console.")
message_admins("[key_name_admin(usr)] issued recall order for drone [key_name_admin(D)] from control console.")
log_game("[key_name(usr)] issued recall order for [key_name(D)] from control console.")
// Yes, I know this proc is called shut_down, I've kept the name the same because emagged drones use it for death
D.shut_down()
/obj/machinery/computer/drone_control/proc/find_fab(mob/user)
@@ -87,6 +87,16 @@
var/mob/dead/observer/ghost = user
ghost.join_as_drone()
/obj/machinery/drone_fabricator/attack_hand(mob/user)
. = ..()
if(isdrone(user) && Adjacent(user))
if(alert(user, "Would you like to shut down?", null, "Yes", "No") != "Yes")
return
var/mob/living/silicon/robot/drone/D = user
if(!istype(D) || QDELETED(D))
return
D.cryo_with_dronefab(src)
/mob/dead/verb/join_as_drone()
set category = "Ghost"
set name = "Join As Drone"
@@ -45,7 +45,7 @@
)
holder_type = /obj/item/holder/drone
// var/sprite[0]
var/datum/pathfinding_mover/pathfinding
/mob/living/silicon/robot/drone/New()
@@ -125,6 +125,8 @@
overlays.Cut()
if(stat == CONSCIOUS)
overlays += "eyes-[icon_state]"
if(pathfinding)
overlays += "eyes-repairbot-pathfinding"
else
overlays -= "eyes"
@@ -279,6 +281,10 @@
to_chat(src, "<span class='warning'>You feel a system kill order percolate through your tiny brain, but it doesn't seem like a good idea to you.</span>")
return
if(!emagged && pathfind_to_dronefab())
to_chat(src, "<span class='warning'>You feel a system recall order percolate through your tiny brain, and you return to your drone fabricator.</span>")
return
to_chat(src, "<span class='warning'>You feel a system kill order percolate through your tiny brain, and you obediently destroy yourself.</span>")
death()
@@ -391,3 +397,73 @@
qdel(src)
return TRUE
return ..()
/mob/living/silicon/robot/drone/do_suicide()
ghostize(TRUE)
shut_down()
/mob/living/silicon/robot/drone/proc/pathfind_to_dronefab()
if(pathfinding)
return TRUE
if(istype(get_turf(src), /turf/space))
return FALSE // Pretty damn hard to path through space
var/turf/target
for(var/obj/machinery/drone_fabricator/DF in GLOB.machines)
if(DF.z != z)
continue
target = get_turf(DF)
target = get_step(target, EAST)
break
if(!target)
return FALSE
// Mimic having the hide-ability activated
layer = TURF_LAYER + 0.2
pass_flags |= PASSDOOR
var/datum/pathfinding_mover/pathfind = new(src, target)
// I originally only wanted to make it use an ID if it couldnt pathfind otherwise, but that means it could take multiple minutes if both searches failed
var/obj/item/card/id/temp_id = new(src)
temp_id.access = get_all_accesses()
set_pathfinding(pathfind)
var/found_path = pathfind.generate_path(150, null, temp_id)
qdel(temp_id)
if(!found_path)
set_pathfinding(null)
return FALSE
pathfind.on_set_path_null = CALLBACK(src, PROC_REF(pathfind_failed_cleanup))
pathfind.on_success = CALLBACK(src, PROC_REF(at_dronefab))
pathfind.start()
return TRUE
/mob/living/silicon/robot/drone/proc/pathfind_failed_cleanup(pathfind)
set_pathfinding(null)
death()
/mob/living/silicon/robot/drone/proc/at_dronefab(pathfind)
set_pathfinding(null)
cryo_with_dronefab()
/mob/living/silicon/robot/drone/proc/cryo_with_dronefab(obj/machinery/drone_fabricator/drone_fab)
if(!drone_fab)
drone_fab = locate() in range(1, src)
if(!drone_fab)
return FALSE
drone_fab.drone_progress = 100 // recycling!
visible_message("<span class='notice'>[src] shuts down and enters [drone_fab].</span>")
playsound(loc, 'sound/machines/twobeep.ogg', 50)
qdel(src)
return TRUE
/mob/living/silicon/robot/drone/proc/set_pathfinding(datum/pathfinding_mover/new_pathfind)
if(isnull(new_pathfind) && istype(pathfinding))
qdel(pathfinding)
pathfinding = new_pathfind
notransform = istype(new_pathfind) ? TRUE : FALSE // prevent them from moving themselves while pathfinding.
update_icons()
+4
View File
@@ -106,5 +106,9 @@
qdel(mob_to_delete)
/proc/dust_if_respawnable(mob/M)
if(isdrone(M))
var/mob/living/silicon/robot/drone/drone = M
drone.shut_down(TRUE)
return
if(HAS_TRAIT_FROM(M, TRAIT_RESPAWNABLE, GHOST_ROLE))
M.dust()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 262 KiB

+1
View File
@@ -349,6 +349,7 @@
#include "code\datums\mixed.dm"
#include "code\datums\movement_detector.dm"
#include "code\datums\mutable_appearance.dm"
#include "code\datums\pathfinding_mover.dm"
#include "code\datums\periodic_news.dm"
#include "code\datums\pipe_datums.dm"
#include "code\datums\position_point_vector.dm"
+34 -23
View File
@@ -11,6 +11,7 @@ import {
NoticeBox,
ProgressBar,
Section,
Stack,
} from '../components';
import { Window } from '../layouts';
@@ -154,29 +155,39 @@ const DroneList = (props, context) => {
key={drone.name}
title={toTitleCase(drone.name)}
buttons={
<Flex>
<Button
icon="sync"
content="Resync"
disabled={drone.stat === 2 || drone.sync_cd}
onClick={() =>
act('resync', {
uid: drone.uid,
})
}
/>
<Button.Confirm
icon="power-off"
content="Shutdown"
disabled={drone.stat === 2}
color="bad"
onClick={() =>
act('shutdown', {
uid: drone.uid,
})
}
/>
</Flex>
<Stack>
<Stack.Item>
<Button
icon="sync"
content="Resync"
disabled={drone.stat === 2 || drone.sync_cd}
onClick={() =>
act('resync', {
uid: drone.uid,
})
}
/>
</Stack.Item>
<Stack.Item>
<Button.Confirm
icon="power-off"
content="Recall"
disabled={drone.stat === 2 || drone.pathfinding}
tooltip={
drone.pathfinding
? 'This drone is currently pathfinding, please wait.'
: null
}
tooltipPosition="left"
color="bad"
onClick={() =>
act('recall', {
uid: drone.uid,
})
}
/>
</Stack.Item>
</Stack>
}
>
<LabeledList>
File diff suppressed because one or more lines are too long