mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-14 16:44:33 +01:00
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:
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 |
@@ -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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user