diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm index babeb620afa..5f87af8fd8e 100644 --- a/code/__DEFINES/cooldowns.dm +++ b/code/__DEFINES/cooldowns.dm @@ -46,6 +46,8 @@ #define COOLDOWN_CIRCUIT_SOUNDEMITTER "circuit_soundemitter" #define COOLDOWN_CIRCUIT_SPEECH "circuit_speech" +#define COOLDOWN_CIRCUIT_PATHFIND_SAME "circuit_pathfind_same" +#define COOLDOWN_CIRCUIT_PATHFIND_DIF "circuit_pathfind_different" //TIMER COOLDOWN MACROS diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index 83062de2eeb..b7734cfd4c5 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -218,6 +218,11 @@ id = "comp_select_query" build_path = /obj/item/circuit_component/select +/datum/design/component/pathfind + name = "Pathfinder" + id = "comp_pathfind" + build_path = /obj/item/circuit_component/pathfind + /datum/design/component/tempsensor name = "Temperature Sensor Component" id = "comp_tempsensor" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 79741b69aa2..f65f531a6e4 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -700,6 +700,7 @@ description = "Grants access to movable shells." prereq_ids = list("adv_shells", "robotics") design_ids = list( + "comp_pathfind", "comp_pull", "drone_shell", ) diff --git a/code/modules/wiremod/components/action/pathfind.dm b/code/modules/wiremod/components/action/pathfind.dm new file mode 100644 index 00000000000..4b009bbc525 --- /dev/null +++ b/code/modules/wiremod/components/action/pathfind.dm @@ -0,0 +1,129 @@ +/** + * # Pathfinding component + * + * Calcualtes a path, returns a list of entities. Each entity is the next step in the path. Can be used with the direction component to move. + */ +/obj/item/circuit_component/pathfind + display_name = "Pathfinder" + display_desc = "When triggered, the next step to the target's location as an entity. This can be used with the direction component and the drone shell to make it move on its own. The Id Card input port is for considering ID access when pathing, it does not give the shell actual access." + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + var/datum/port/input/input_X + var/datum/port/input/input_Y + var/datum/port/input/id_card + + var/datum/port/output/output + var/datum/port/output/finished + var/datum/port/output/failed + var/datum/port/output/reason_failed + + var/list/path + var/turf/old_dest + var/turf/next_turf + + // Cooldown to limit how frequently we can path to the same location. + var/same_path_cooldown = 5 SECONDS + var/different_path_cooldown = 30 SECONDS + + var/max_range = 60 + +/obj/item/circuit_component/pathfind/get_ui_notices() + . = ..() + // Not necessary to show the same path cooldown, since it doesn't change much for the player + . += create_ui_notice("Pathfinding Cooldown: [DisplayTimeText(different_path_cooldown)]", "orange", "stopwatch") + . += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info") + +/obj/item/circuit_component/pathfind/Initialize() + . = ..() + input_X = add_input_port("Target X", PORT_TYPE_NUMBER, FALSE) + input_Y = add_input_port("Target Y", PORT_TYPE_NUMBER, FALSE) + id_card = add_input_port("ID Card", PORT_TYPE_ATOM, FALSE) + + output = add_output_port("Next step", PORT_TYPE_ATOM) + finished = add_output_port("Arrived to destination", PORT_TYPE_SIGNAL) + failed = add_output_port("Failed", PORT_TYPE_SIGNAL) + reason_failed = add_output_port("Fail reason", PORT_TYPE_STRING) + +/obj/item/circuit_component/pathfind/Destroy() + input_X = null + input_Y = null + id_card = null + + output = null + finished = null + failed = null + reason_failed = null + + path = null + old_dest = null + next_turf = null + return ..() + +/obj/item/circuit_component/pathfind/input_received(datum/port/input/port) + . = ..() + if(.) + return + + var/target_X = input_X.input_value + if(isnull(target_X)) + return + + var/target_Y = input_Y.input_value + if(isnull(target_Y)) + return + + var/atom/path_id = id_card.input_value + if(path_id && !istype(path_id, /obj/item/card/id)) + path_id = null + failed.set_output(COMPONENT_SIGNAL) + reason_failed.set_output("Object marked is not an ID! Using no ID instead.") + + // Get both the current turf and the destination's turf + var/turf/current_turf = get_turf(src) + var/turf/destination = locate(target_X, target_Y, current_turf?.z) + + // We're already here! No need to do anything. + if(current_turf == destination) + finished.set_output(COMPONENT_SIGNAL) + old_dest = null + TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME) + next_turf = null + return + + // If we're going to the same place and the cooldown hasn't subsided, we're probably on the same path as before + if (destination == old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME)) + + // Check if the current turf is the same as the current turf we're supposed to be in. If so, then we set the next step as the next turf on the list + if(current_turf == next_turf) + popleft(path) + next_turf = get_turf(path[1]) + output.set_output(next_turf) + + // Restart the cooldown since we don't need a new path ( TIMER_COOLDOWN_START might restart the timer by itself and i dont need to call TIMER_COOLDOWN_END, but better safe than sorry ) + TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME) + TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown) + + + else // Either we're not going to the same place or the cooldown is over. Either way, we need a new path + + if(destination != old_dest && TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF)) + failed.set_output(COMPONENT_SIGNAL) + reason_failed.set_output("Cooldown still active!") + return + + TIMER_COOLDOWN_END(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME) + + old_dest = destination + path = get_path_to(src, destination, max_range, id=path_id) + if(length(path) == 0 || !path)// Check if we can even path there + next_turf = null + failed.set_output(COMPONENT_SIGNAL) + reason_failed.set_output("Can't go there!") + return + else + TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_DIF, different_path_cooldown) + popleft(path) // The first step is literally where we are right now, so we dont need it + next_turf = get_turf(path[1]) + output.set_output(next_turf) + TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_PATHFIND_SAME, same_path_cooldown) + diff --git a/tgstation.dme b/tgstation.dme index 149ba3a85d7..16fd9857646 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3637,6 +3637,7 @@ #include "code\modules\wiremod\components\abstract\module.dm" #include "code\modules\wiremod\components\action\light.dm" #include "code\modules\wiremod\components\action\mmi.dm" +#include "code\modules\wiremod\components\action\pathfind.dm" #include "code\modules\wiremod\components\action\pull.dm" #include "code\modules\wiremod\components\action\radio.dm" #include "code\modules\wiremod\components\action\soundemitter.dm"