Pathfinder Module no longer pathfinds (#94261)

## About The Pull Request


![dreamseeker_9pYVx5xTE0](https://github.com/user-attachments/assets/86695b34-deb1-4307-83c8-ecd81b1b6b28)

Removes the "pathfinding through the halls" function of the MODsuit
pathfinder module in favour of having it "fly" to your location in the
same manner as cargo pods (by getting inside an invisible cargo pod).

If it's inside a locked crate or locker when this happens it will break
the door on the way out, so be careful where you leave it.

## Why It's Good For The Game

Mostly because nobody uses the pathfinder module.
Pathfinding through the corridors is _cool_ on a conceptual level but
it's not very practical, _especially_ because it means that it has a
good chance of not functioning at all if you are on a station with Z
levels.
This method means that it will always arrive in the same amount of time
(quite fast, but not fast enough that it will arrive before someone can
shoot you) regardless of where you are when you suddenly realise that
you need spaceproofing rather than drip.

Also Fikou asked me to do it.

## Changelog

🆑
balance: The Captain's MODSuit pathfinder module now flies it directly
to their location rather than physically traversing the halls of the
station.
/🆑
This commit is contained in:
Jacquerel
2025-12-03 13:37:35 -07:00
committed by GitHub
parent a9bbc6304c
commit 7a994fffba
8 changed files with 105 additions and 117 deletions
-2
View File
@@ -176,8 +176,6 @@
#define BB_MOD_TARGET "BB_mod_target"
///The module the AI was created from
#define BB_MOD_MODULE "BB_mod_module"
///Range for a MOD AI controller.
#define MOD_AI_RANGE 200
///Only target mobs with these traits
#define BB_TARGET_ONLY_WITH_TRAITS "BB_target_only_with_traits"
@@ -226,6 +226,8 @@
#define COMSIG_MULTITOOL_REMOVE_BUFFER "multitool_remove_buffer"
///from [/obj/effect/mine/proc/triggermine]:
#define COMSIG_MINE_TRIGGERED "minegoboom"
///from [/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting]:
#define COMSIG_SUPPLYPOD_RETURNING "supplypodgohome"
///from [/obj/structure/closet/supplypod/proc/preOpen]:
#define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom"
@@ -53,6 +53,3 @@
/datum/ai_movement/jps/bot/travel_to_beacon
maximum_length = AI_BOT_PATH_LENGTH
max_pathing_attempts = 10
/datum/ai_movement/jps/modsuit
maximum_length = MOD_AI_RANGE
-48
View File
@@ -1,48 +0,0 @@
/// An AI controller for the MODsuit pathfinder module. It's activated by module and attaches itself to the user.
/datum/ai_controller/mod
blackboard = list(
BB_MOD_TARGET,
BB_MOD_MODULE,
)
can_idle = FALSE
max_target_distance = MOD_AI_RANGE //a little spicy but its one specific item that summons it, and it doesn't run otherwise
ai_movement = /datum/ai_movement/jps/modsuit
///ID card generated from the suit's required access. Used for pathing.
var/obj/item/card/id/advanced/id_card
/datum/ai_controller/mod/TryPossessPawn(atom/new_pawn)
if(!istype(new_pawn, /obj/item/mod/control))
return AI_CONTROLLER_INCOMPATIBLE
var/obj/item/mod/control/mod = new_pawn
id_card = new /obj/item/card/id/advanced/simple_bot()
if(length(mod.req_access))
id_card.set_access(mod.req_access)
return ..() //Run parent at end
/datum/ai_controller/mod/UnpossessPawn(destroy)
QDEL_NULL(id_card)
return ..() //Run parent at end
/datum/ai_controller/mod/SelectBehaviors(seconds_per_tick)
current_behaviors = list()
if(blackboard[BB_MOD_TARGET] && blackboard[BB_MOD_MODULE])
queue_behavior(/datum/ai_behavior/mod_attach)
/datum/ai_controller/mod/get_access()
return id_card.GetAccess()
/datum/ai_behavior/mod_attach
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_MOVE_AND_PERFORM
/datum/ai_behavior/mod_attach/perform(seconds_per_tick, datum/ai_controller/controller)
if(!controller.pawn.Adjacent(controller.blackboard[BB_MOD_TARGET]))
return AI_BEHAVIOR_DELAY
var/obj/item/mod/module/pathfinder/module = controller.blackboard[BB_MOD_MODULE]
module.attach(controller.blackboard[BB_MOD_TARGET])
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/mod_attach/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.clear_blackboard_key(BB_MOD_TARGET)
var/obj/item/mod/module/pathfinder/module = controller.blackboard[BB_MOD_MODULE]
module.end_recall(succeeded)
+2 -11
View File
@@ -48,7 +48,7 @@
launched_bot = WEAKREF(possible_bot)
podspawn(list(
"target" = get_turf(src),
"path" = /obj/structure/closet/supplypod/botpod,
"path" = /obj/structure/closet/supplypod/transport/botpod,
"style" = /datum/pod_style/seethrough,
"reverse_dropoff_coords" = list(reverse_turf.x, reverse_turf.y, reverse_turf.z)
))
@@ -66,15 +66,6 @@
var/mob/living/simple_animal/bot/simple_bot = our_bot
simple_bot.call_bot(src, get_turf(src))
/obj/structure/closet/supplypod/botpod
style = /datum/pod_style/seethrough
explosionSize = list(0,0,0,0)
reversing = TRUE
/obj/structure/closet/supplypod/transport/botpod
reverse_option_list = list("Mobs"=TRUE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE,"Mecha"=FALSE)
delays = list(POD_TRANSIT = 0, POD_FALLING = 0, POD_OPENING = 0, POD_LEAVING = 0)
reverse_delays = list(POD_TRANSIT = 15, POD_FALLING = 10, POD_OPENING = 0, POD_LEAVING = 0)
custom_rev_delay = TRUE
effectQuiet = TRUE
leavingSound = 'sound/vehicles/rocketlaunch.ogg'
close_sound = null
pod_flags = FIRST_SOUNDS
+30 -6
View File
@@ -71,6 +71,19 @@
bluespace = TRUE
explosionSize = list(0,0,0,0)
/// Quick setup for if you want a pod that transports a specific object somewhere and makes it look like it is flying away
/obj/structure/closet/supplypod/transport
style = /datum/pod_style/seethrough
specialised = TRUE
explosionSize = list(0,0,0,0)
reversing = TRUE
delays = list(POD_TRANSIT = 0, POD_FALLING = 0, POD_OPENING = 0, POD_LEAVING = 0)
reverse_delays = list(POD_TRANSIT = 15, POD_FALLING = 10, POD_OPENING = 0, POD_LEAVING = 0)
custom_rev_delay = TRUE
effectQuiet = TRUE
close_sound = null
pod_flags = FIRST_SOUNDS
/obj/structure/closet/supplypod/podspawn/deathmatch
desc = "A blood-red styled drop pod."
specialised = TRUE
@@ -300,6 +313,7 @@
if (custom_rev_delay)
delays = reverse_delays
backToNonReverseIcon()
SEND_SIGNAL(src, COMSIG_SUPPLYPOD_RETURNING)
var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3])
new /obj/effect/pod_landingzone(return_turf, src)
@@ -308,9 +322,9 @@
var/list/boom = explosionSize
resistance_flags = initial(resistance_flags)
set_density(TRUE) //Density is originally false so the pod doesn't block anything while it's still falling through the air
AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude)
if(effectShrapnel)
SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED)
AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude)
SEND_SIGNAL(src, COMSIG_SUPPLYPOD_LANDED)
for (var/mob/living/target_living in turf_underneath)
if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people):
if (effectLimb)
@@ -364,6 +378,7 @@
moveToNullspace()
addtimer(CALLBACK(src, PROC_REF(open_pod), benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob
else if (ispath(style, /datum/pod_style/seethrough))
transform = matrix()
open_pod(src)
else
addtimer(CALLBACK(src, PROC_REF(open_pod), src), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplypod, while referencing this supplypod's contents
@@ -415,7 +430,8 @@
if (!holder)
return
take_contents(holder)
playsound(holder, close_sound, soundVolume*0.75, TRUE, -3)
if (close_sound)
playsound(holder, close_sound, soundVolume*0.75, TRUE, -3)
holder.setClosed()
addtimer(CALLBACK(src, PROC_REF(preReturn), holder), delays[POD_LEAVING] * 0.2) //Start to leave a bit after closing for cinematic effect
@@ -658,7 +674,11 @@
stack_trace("Pod landingzone effect created with no pod")
return INITIALIZE_HINT_QDEL
transform = matrix() * 1.5
animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING])
var/arrival_time = pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING]
if (arrival_time > 0)
animate(src, transform = matrix()*0.01, time = arrival_time)
else
alpha = 0
/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to its location
name = "Landing Zone Indicator"
@@ -685,7 +705,11 @@
if (!pod.effectStealth)
helper = new (drop_location(), pod)
alpha = 255
animate(src, transform = matrix().Turn(90), time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING])
var/arrival_time = pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING]
if (arrival_time > 0)
animate(src, transform = matrix().Turn(90), time = arrival_time)
else
alpha = 0
if (single_order)
if (istype(single_order, /datum/supply_order))
var/datum/supply_order/SO = single_order
@@ -701,7 +725,7 @@
if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them
for (var/mob/living/target_living in get_turf(src))
target_living.Stun(pod.delays[POD_TRANSIT]+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid.
if (pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] < pod.fallingSoundLength)
if (arrival_time < pod.fallingSoundLength)
pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound
pod.fallingSound = 'sound/items/weapons/mortar_whistle.ogg'
var/soundStartTime = pod.delays[POD_TRANSIT] - pod.fallingSoundLength + pod.delays[POD_FALLING]
+71 -46
View File
@@ -1,3 +1,5 @@
#define PATHFINDER_PRE_ANIMATE_TIME (2 SECONDS)
///Pathfinder - Can fly the suit from a long distance to an implant installed in someone.
/obj/item/mod/module/pathfinder
name = "MOD pathfinder module"
@@ -24,6 +26,8 @@
var/image/jet_icon
/// Allow suit activation - Lets this module be recalled from the MOD.
var/allow_suit_activation = FALSE // I'm not here to argue about balance
/// Are we currently travelling?
var/in_transit = FALSE
/obj/item/mod/module/pathfinder/Initialize(mapload)
@@ -88,7 +92,7 @@
return
balloon_alert(activator, "implanted")
if(!(activator == mod.wearer)) // someone else implanted you
balloon_alert(mod.wearer, "pathfinder MOD tracker implanted!")
balloon_alert(mod.wearer, "tracker implanted!")
playsound(src, 'sound/effects/spray.ogg', 30, TRUE, -6)
/obj/item/mod/module/pathfinder/proc/attach(mob/living/user)
@@ -101,64 +105,81 @@
return
mod.quick_deploy(user)
human_user.update_action_buttons(TRUE)
balloon_alert(human_user, "[mod] attached")
playsound(mod, 'sound/machines/ping.ogg', 50, TRUE)
drain_power(use_energy_cost)
/obj/item/mod/module/pathfinder/proc/recall(mob/recaller)
if(!implant)
balloon_alert(recaller, "no target implant!")
balloon_alert(recaller, "no implant!")
return FALSE
if(recaller != implant.imp_in && !allow_suit_activation) // No pAI recalling
balloon_alert(recaller, "sector safety regulations prevent MOD-side recalling!")
balloon_alert(recaller, "invalid user!")
return FALSE
if(mod.open)
balloon_alert(recaller, "cover open!")
return FALSE
if(mod.ai_controller)
balloon_alert(recaller, "already moving!")
if(in_transit)
balloon_alert(recaller, "suit in transit!")
return FALSE
if(ismob(get_atom_on_turf(mod)))
balloon_alert(recaller, "already on someone!")
balloon_alert(recaller, "already worn!")
return FALSE
if(mod.z != implant.imp_in.z || get_dist(implant.imp_in, mod) > MOD_AI_RANGE)
balloon_alert(recaller, "too far!")
return FALSE
var/datum/ai_controller/mod_ai = new /datum/ai_controller/mod(mod)
mod.ai_controller = mod_ai
mod_ai.set_movement_target(type, implant.imp_in)
mod_ai.set_blackboard_key(BB_MOD_TARGET, implant.imp_in)
mod_ai.set_blackboard_key(BB_MOD_MODULE, src)
mod.interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP
mod.AddElement(/datum/element/movetype_handler)
ADD_TRAIT(mod, TRAIT_MOVE_FLYING, MOD_TRAIT)
animate(mod, 0.2 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y)
mod.add_overlay(jet_icon)
RegisterSignal(mod, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
in_transit = TRUE
animate(mod, 0.5 SECONDS, pixel_x = base_pixel_y, pixel_y = base_pixel_y)
mod.Shake(pixelshiftx = 1, pixelshifty = 1, duration = PATHFINDER_PRE_ANIMATE_TIME)
addtimer(CALLBACK(src, PROC_REF(do_recall), recaller), PATHFINDER_PRE_ANIMATE_TIME, TIMER_DELETE_ME)
balloon_alert(recaller, "suit recalled")
if(!(recaller == mod.wearer))
balloon_alert(mod.wearer, "suit recalled")
return TRUE
/obj/item/mod/module/pathfinder/proc/on_move(atom/movable/source, atom/old_loc, dir, forced)
SIGNAL_HANDLER
var/matrix/mod_matrix = matrix()
mod_matrix.Turn(get_angle(source, implant.imp_in))
source.transform = mod_matrix
/obj/item/mod/module/pathfinder/proc/end_recall(successful = TRUE)
if(!mod)
/// Pod-transport the suit to its owner
/obj/item/mod/module/pathfinder/proc/do_recall(mob/recaller)
var/container = get_atom_on_turf(mod)
if(ismob(container))
balloon_alert(recaller, "launch interrupted!")
return
QDEL_NULL(mod.ai_controller)
mod.interaction_flags_item |= INTERACT_ITEM_ATTACK_HAND_PICKUP
REMOVE_TRAIT(mod, TRAIT_MOVE_FLYING, MOD_TRAIT)
mod.RemoveElement(/datum/element/movetype_handler)
if(iscloset(container))
var/obj/structure/closet/closet = container
if (!closet.opened)
if (!closet.open())
playsound(closet, 'sound/effects/bang.ogg', vol = 50, vary = TRUE)
closet.bust_open()
mod.add_overlay(jet_icon)
playsound(mod, 'sound/vehicles/rocketlaunch.ogg', vol = 80, vary = FALSE)
var/turf/land_target = get_turf(implant.imp_in)
var/obj/structure/closet/supplypod/pod = podspawn(list(
"target" = get_turf(mod),
"path" = /obj/structure/closet/supplypod/transport/module_pathfinder,
"reverse_dropoff_coords" = list(land_target.x, land_target.y, land_target.z),
))
pod.insert(mod, pod)
RegisterSignal(pod, COMSIG_SUPPLYPOD_RETURNING, PROC_REF(pod_takeoff))
if (istype(container, /obj/machinery/suit_storage_unit))
var/obj/machinery/suit_storage_unit/storage = container
storage.locked = FALSE
storage.open_machine()
/// Track when pod has taken off so we don't falsely report the initial landing
/obj/item/mod/module/pathfinder/proc/pod_takeoff(datum/pod)
SIGNAL_HANDLER
RegisterSignal(pod, COMSIG_SUPPLYPOD_LANDED, PROC_REF(pod_landed))
/// When the pod landed, we can recall again
/obj/item/mod/module/pathfinder/proc/pod_landed()
SIGNAL_HANDLER
in_transit = FALSE
mod.cut_overlay(jet_icon)
mod.transform = matrix()
UnregisterSignal(mod, COMSIG_MOVABLE_MOVED)
if(!successful)
balloon_alert(implant.imp_in, "suit lost connection!")
playsound(mod, 'sound/items/handling/toolbox/toolbox_drop.ogg', vol = 80, vary = FALSE)
if (implant?.imp_in?.Adjacent(src))
INVOKE_ASYNC(src, PROC_REF(attach), implant.imp_in)
// ###########
// THE INPLANT
@@ -173,8 +194,6 @@
/// The pathfinder module we are linked to.
var/obj/item/mod/module/pathfinder/module
/obj/item/implant/mod/Initialize(mapload)
. = ..()
if(!istype(loc, /obj/item/mod/module/pathfinder))
@@ -182,8 +201,6 @@
module = loc
/obj/item/implant/mod/Destroy()
if(module?.mod?.ai_controller)
module.end_recall(successful = FALSE)
module = null
return ..()
@@ -192,7 +209,6 @@
<b>Name:</b> Nakamura Engineering Pathfinder Implant<BR> \
<b>Implant Details:</b> Allows for the recall of a Modular Outerwear Device by the implant owner at any time.<BR>"
/datum/action/item_action/mod_recall
name = "Recall MOD"
desc = "Recall a MODsuit anyplace, anytime."
@@ -205,7 +221,7 @@
COOLDOWN_DECLARE(recall_cooldown)
/datum/action/item_action/mod_recall/New(Target)
..()
. = ..()
if(!istype(Target, /obj/item/implant/mod))
qdel(src)
return
@@ -215,5 +231,14 @@
if(!COOLDOWN_FINISHED(src, recall_cooldown))
implant.balloon_alert(owner, "on cooldown!")
return
if(implant.module.recall(owner)) // change this
COOLDOWN_START(src, recall_cooldown, 15 SECONDS)
if(implant.module.recall(owner))
implant.balloon_alert(owner, "suit incoming...")
COOLDOWN_START(src, recall_cooldown, 5 SECONDS)
/// Special pod subtype we use just to make insertion check easy
/obj/structure/closet/supplypod/transport/module_pathfinder
/obj/structure/closet/supplypod/transport/module_pathfinder/insertion_allowed(atom/to_insert)
return istype(to_insert, /obj/item/mod/control)
#undef PATHFINDER_PRE_ANIMATE_TIME
-1
View File
@@ -1041,7 +1041,6 @@
#include "code\datums\ai\movement\ai_movement_complete_stop.dm"
#include "code\datums\ai\movement\ai_movement_dumb.dm"
#include "code\datums\ai\movement\ai_movement_jps.dm"
#include "code\datums\ai\objects\mod.dm"
#include "code\datums\ai\objects\vending_machines\vending_machine_behaviors.dm"
#include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm"
#include "code\datums\ai\robot_customer\robot_customer_behaviors.dm"