diff --git a/.gitignore b/.gitignore index 9d2b3ae5aa1..52ee73c3835 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # ignore misc BYOND files Thumbs.db +Thumbs.db:encryptable *.log *.int *.rsc diff --git a/code/__defines/ai.dm b/code/__defines/ai.dm index 2123e2e8839..03fa03fa9e8 100644 --- a/code/__defines/ai.dm +++ b/code/__defines/ai.dm @@ -6,6 +6,8 @@ #define STANCE_ATTACKING /decl/mob_controller_stance/attacking #define STANCE_TIRED /decl/mob_controller_stance/tired #define STANCE_CONTAINED /decl/mob_controller_stance/contained +#define STANCE_BUSY /decl/mob_controller_stance/busy + //basically 'do nothing' #define STANCE_COMMANDED_STOP /decl/mob_controller_stance/commanded/stop //follows a target diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm index fc69009e88b..cd44840622a 100644 --- a/code/__defines/subsystem-priority.dm +++ b/code/__defines/subsystem-priority.dm @@ -39,6 +39,7 @@ #define SS_PRIORITY_GHOST_IMAGES 10 // Updates ghost client images. #define SS_PRIORITY_ZCOPY 10 // Builds appearances for Z-Mimic. #define SS_PRIORITY_PROJECTILES 10 // Projectile processing! +#define SS_PRIORITY_PATHFINDING 10 // Processing pathfinding requests // SS_BACKGROUND #define SS_PRIORITY_OBJECTS 100 // processing_objects processing. diff --git a/code/controllers/subsystems/mob_ai/auto_movement.dm b/code/controllers/subsystems/mob_ai/auto_movement.dm index 7e2990c1cb8..843f37ab2d0 100644 --- a/code/controllers/subsystems/mob_ai/auto_movement.dm +++ b/code/controllers/subsystems/mob_ai/auto_movement.dm @@ -51,5 +51,8 @@ SUBSYSTEM_DEF(automove) if(controller.handle_mover(mover, moving_metadata[mover]) == PROCESS_KILL && !QDELETED(mover)) mover.stop_automove() if(MC_TICK_CHECK) - processing_atoms.Cut(1, i+1) + if(i >= length(processing_atoms)) + processing_atoms.Cut() + else + processing_atoms.Cut(1, i+1) return diff --git a/code/controllers/subsystems/pathfinding.dm b/code/controllers/subsystems/pathfinding.dm new file mode 100644 index 00000000000..d6e63f4191f --- /dev/null +++ b/code/controllers/subsystems/pathfinding.dm @@ -0,0 +1,123 @@ +SUBSYSTEM_DEF(pathfinding) + name = "Pathfinding" + priority = SS_PRIORITY_PATHFINDING + init_order = SS_INIT_MISC_LATE + wait = 1 + + var/list/pending = list() + var/list/processing = list() + var/list/mover_metadata = list() + + VAR_PRIVATE/static/_default_adjacency_call = TYPE_PROC_REF(/turf, CardinalTurfsWithAccess) + VAR_PRIVATE/static/_default_distance_call = TYPE_PROC_REF(/turf, Distance) + +/atom/movable + var/waiting_for_path + +/atom/movable/proc/path_found(list/path) + SHOULD_CALL_PARENT(TRUE) + waiting_for_path = null + +/atom/movable/proc/path_not_found() + SHOULD_CALL_PARENT(TRUE) + waiting_for_path = null + +/datum/controller/subsystem/pathfinding/proc/dequeue_mover(atom/movable/mover, include_processing = TRUE) + if(!istype(mover)) + return + mover.waiting_for_path = null + pending -= mover + mover_metadata -= mover + if(include_processing) + processing -= mover + +// Hook to allow legacy use of AStar* to reuse the callback refs +/datum/controller/subsystem/pathfinding/proc/find_path_immediate(start, end, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude, check_tick = FALSE) + return find_path_astar(start, end, _default_adjacency_call, _default_distance_call, max_nodes, max_node_depth, min_target_dist, min_node_dist, id, exclude, check_tick) + +/datum/controller/subsystem/pathfinding/proc/enqueue_mover(atom/movable/mover, atom/target, datum/pathfinding_metadata/metadata) + if(!istype(mover) || mover.waiting_for_path) + return FALSE + if(!istype(target)) + return FALSE + pending |= mover + pending[mover] = target + if(istype(metadata)) + mover_metadata[mover] = metadata + mover.waiting_for_path = world.time + return TRUE + +/datum/controller/subsystem/pathfinding/stat_entry(msg) + . = ..("Q:[length(pending)] P:[length(processing)]") + +/datum/controller/subsystem/pathfinding/fire(resumed) + + if(!resumed) + processing = pending?.Copy() + + var/atom/movable/mover + var/atom/target + var/datum/pathfinding_metadata/metadata + var/i = 0 + + while(i < processing.len) + + i++ + mover = processing[i] + target = processing[mover] + metadata = mover_metadata[mover] + dequeue_mover(mover, include_processing = FALSE) + + if(!QDELETED(mover) && !QDELETED(target)) + try_find_path(mover, target, metadata) + + if (MC_TICK_CHECK) + processing.Cut(1, i+1) + return + + processing.Cut() + +/datum/controller/subsystem/pathfinding/proc/try_find_path(atom/movable/mover, atom/target, datum/pathfinding_metadata/metadata, adjacency_call = _default_adjacency_call, distance_call = _default_distance_call) + + var/started_pathing = world.time + mover.waiting_for_path = started_pathing + + var/list/path = find_path_astar( + get_turf(mover), + target, + adjacency_call, + distance_call, + (metadata?.max_nodes || null), + (metadata?.max_node_depth || 250), + metadata?.min_target_dist, + metadata?.min_node_depth, + (metadata?.id || mover.GetIdCard()), + metadata?.obstacle, + check_tick = TRUE + ) + if(mover.waiting_for_path == started_pathing) + if(length(path)) + mover.path_found(path) + else + mover.path_not_found() + +/datum/pathfinding_metadata + var/max_nodes = null + var/max_node_depth = 250 + var/atom/id = null + var/min_target_dist = null + var/min_node_depth = null + var/obstacle = null + +/datum/pathfinding_metadata/New(_max_nodes, _max_node_depth, _id, _min_target_dist, _min_node_depth, _obstacle) + + id = _id + obstacle = _obstacle + max_nodes = _max_nodes + + if(!isnull(_max_node_depth)) + max_node_depth = _max_node_depth + if(!isnull(_min_target_dist)) + min_target_dist = _min_target_dist + if(!isnull(_min_node_depth)) + min_node_depth = _min_node_depth diff --git a/code/datums/ai/_ai.dm b/code/datums/ai/_ai.dm index 6e40fd7ab35..9c943dadb4f 100644 --- a/code/datums/ai/_ai.dm +++ b/code/datums/ai/_ai.dm @@ -62,6 +62,23 @@ /// Aggressive AI var; defined here for reference without casting. var/try_destroy_surroundings = FALSE + /// Reference to the atom we are targetting. + var/weakref/target_ref + + /// Current path for A* pathfinding. + var/list/executing_path + /// A counter for times we have failed to progress along our path. + var/path_frustration = 0 + /// A list of any obstacles we should path around in future. + var/list/path_obstacles = null + + /// Radius of target scan area when looking for valid targets. Set to 0 to disable target scanning. + var/target_scan_distance = 0 + /// Time tracker for next target scan. + var/next_target_scan_time + /// How long minimum between scans. + var/target_scan_delay = 1 SECOND + /datum/mob_controller/New(var/mob/living/target_body) body = target_body if(expected_type && !istype(body, expected_type)) @@ -71,6 +88,7 @@ /datum/mob_controller/Destroy() LAZYCLEARLIST(_friends) LAZYCLEARLIST(_enemies) + set_target(null) if(is_processing) STOP_PROCESSING(SSmob_ai, src) if(body) @@ -79,12 +97,6 @@ body = null . = ..() -/datum/mob_controller/proc/get_automove_target(datum/automove_metadata/metadata) - return null - -/datum/mob_controller/proc/can_do_automated_move(variant_move_delay) - return body && !body.client - /datum/mob_controller/proc/can_process() if(!body || !body.loc || ((body.client || body.mind) && !(body.status_flags & ENABLE_AI))) return FALSE @@ -111,13 +123,13 @@ // This is the place to actually do work in the AI. /datum/mob_controller/proc/do_process() SHOULD_CALL_PARENT(TRUE) - if(!QDELETED(body) && !QDELETED(src)) + if(get_stance() != STANCE_BUSY && !QDELETED(body) && !QDELETED(src)) if(!body.stat) try_unbuckle() try_wander() try_bark() // Recheck in case we walked into lava or something during wandering. - return !QDELETED(body) && !QDELETED(src) + return get_stance() != STANCE_BUSY && !QDELETED(body) && !QDELETED(src) return TRUE return FALSE @@ -132,16 +144,6 @@ else if(prob(25)) body.visible_message(SPAN_WARNING("\The [body] struggles against \the [body.buckled]!")) - -/datum/mob_controller/proc/get_activity() - return current_activity - -/datum/mob_controller/proc/set_activity(new_activity) - if(current_activity != new_activity) - current_activity = new_activity - return TRUE - return FALSE - // The mob will periodically sit up or step 1 tile in a random direction. /datum/mob_controller/proc/try_wander() //Movement @@ -185,133 +187,17 @@ else if(ispath(do_emote, /decl/emote)) body.emote(do_emote) -/datum/mob_controller/proc/get_target() - return null - -/datum/mob_controller/proc/set_target(atom/new_target) - return - -/datum/mob_controller/proc/find_target() - return - -/datum/mob_controller/proc/valid_target(var/atom/A) - return - -/datum/mob_controller/proc/move_to_target(var/move_only = FALSE) - return - -/datum/mob_controller/proc/stop_wandering() - stop_wander = TRUE - -/datum/mob_controller/proc/resume_wandering() - stop_wander = FALSE - -/datum/mob_controller/proc/set_stance(new_stance) - if(stance != new_stance) - stance = new_stance - return TRUE - return FALSE - -/datum/mob_controller/proc/get_stance() - return stance - -/datum/mob_controller/proc/list_targets(var/dist = 7) - return - -/datum/mob_controller/proc/open_fire() - return - -/datum/mob_controller/proc/startle() - if(QDELETED(body) || body.stat != UNCONSCIOUS) - return - body.set_stat(CONSCIOUS) - if(body.current_posture?.prone) - body.set_posture(/decl/posture/standing) - -/datum/mob_controller/proc/retaliate(atom/source) - SHOULD_CALL_PARENT(TRUE) - if(!istype(body) || body.stat == DEAD) - return FALSE - startle() - if(isliving(source)) - remove_friend(source) - return TRUE - /datum/mob_controller/proc/destroy_surroundings() return -/datum/mob_controller/proc/lose_target() - return - -/datum/mob_controller/proc/lost_target() - return - /datum/mob_controller/proc/handle_death(gibbed) return -/datum/mob_controller/proc/pacify(mob/user) - lose_target() - add_friend(user) - -// General-purpose memorise proc, used by /commanded -/datum/mob_controller/proc/memorise(mob/speaker, message) - return - -// General-purpose memory checking proc, used by /faithful_hound -/datum/mob_controller/proc/check_memory(mob/speaker, message) - return FALSE - /// General-purpose scooping reaction proc, used by /passive. /// Returns TRUE if the scoop should proceed, FALSE if it should be canceled. /datum/mob_controller/proc/scooped_by(mob/initiator) return TRUE -// Enemy tracking - used on /aggressive -/datum/mob_controller/proc/get_enemies() - return _enemies - -/datum/mob_controller/proc/add_enemy(mob/enemy) - if(istype(enemy)) - LAZYDISTINCTADD(_enemies, weakref(enemy)) - -/datum/mob_controller/proc/add_enemies(list/enemies) - for(var/thing in enemies) - if(ismob(thing)) - add_friend(thing) - else if(istype(thing, /weakref)) - LAZYDISTINCTADD(_enemies, thing) - -/datum/mob_controller/proc/remove_enemy(mob/enemy) - LAZYREMOVE(_enemies, weakref(enemy)) - -/datum/mob_controller/proc/set_enemies(list/new_enemies) - _enemies = new_enemies - -/datum/mob_controller/proc/is_enemy(mob/enemy) - . = istype(enemy) && LAZYLEN(_enemies) && (weakref(enemy) in _enemies) - -/datum/mob_controller/proc/clear_enemies() - LAZYCLEARLIST(_enemies) - -// Friend tracking - used on /aggressive. -/datum/mob_controller/proc/get_friends() - return _friends - -/datum/mob_controller/proc/add_friend(mob/friend) - if(istype(friend)) - LAZYDISTINCTADD(_friends, weakref(friend)) - return TRUE - return FALSE - -/datum/mob_controller/proc/remove_friend(mob/friend) - LAZYREMOVE(_friends, weakref(friend)) - -/datum/mob_controller/proc/set_friends(list/new_friends) - _friends = new_friends - -/datum/mob_controller/proc/is_friend(mob/friend) - . = istype(friend) && LAZYLEN(_friends) && (weakref(friend) in _friends) - // By default, randomize the target area a bit to make armor/combat // a bit more dynamic (and avoid constant organ damage to the chest) /datum/mob_controller/proc/update_target_zone() diff --git a/code/datums/ai/_ai_enemies.dm b/code/datums/ai/_ai_enemies.dm new file mode 100644 index 00000000000..cbb7ddf4644 --- /dev/null +++ b/code/datums/ai/_ai_enemies.dm @@ -0,0 +1,35 @@ +// Enemy tracking - used on /aggressive +/datum/mob_controller/proc/get_enemies() + return _enemies + +/datum/mob_controller/proc/add_enemy(mob/enemy) + if(istype(enemy)) + LAZYDISTINCTADD(_enemies, weakref(enemy)) + +/datum/mob_controller/proc/add_enemies(list/enemies) + for(var/thing in enemies) + if(ismob(thing)) + add_friend(thing) + else if(istype(thing, /weakref)) + LAZYDISTINCTADD(_enemies, thing) + +/datum/mob_controller/proc/remove_enemy(mob/enemy) + LAZYREMOVE(_enemies, weakref(enemy)) + +/datum/mob_controller/proc/set_enemies(list/new_enemies) + _enemies = new_enemies + +/datum/mob_controller/proc/is_enemy(mob/enemy) + . = istype(enemy) && LAZYLEN(_enemies) && (weakref(enemy) in _enemies) + +/datum/mob_controller/proc/clear_enemies() + LAZYCLEARLIST(_enemies) + +/datum/mob_controller/proc/retaliate(atom/source) + SHOULD_CALL_PARENT(TRUE) + if(!istype(body) || body.stat == DEAD) + return FALSE + startle() + if(isliving(source)) + remove_friend(source) + return TRUE diff --git a/code/datums/ai/_ai_friends.dm b/code/datums/ai/_ai_friends.dm new file mode 100644 index 00000000000..3cab9046fce --- /dev/null +++ b/code/datums/ai/_ai_friends.dm @@ -0,0 +1,25 @@ +/datum/mob_controller/proc/pacify(mob/user) + lose_target() + add_friend(user) + +// Friend tracking - used on /aggressive. +/datum/mob_controller/proc/get_friends() + return _friends + +/datum/mob_controller/proc/add_friend(mob/friend) + if(istype(friend)) + LAZYDISTINCTADD(_friends, weakref(friend)) + return TRUE + return FALSE + +/datum/mob_controller/proc/remove_friend(mob/friend) + LAZYREMOVE(_friends, weakref(friend)) + +/datum/mob_controller/proc/set_friends(list/new_friends) + _friends = new_friends + +/datum/mob_controller/proc/is_friend(mob/friend) + . = istype(friend) && LAZYLEN(_friends) && (weakref(friend) in _friends) + +/datum/mob_controller/proc/clear_friends() + LAZYCLEARLIST(_friends) diff --git a/code/datums/ai/_ai_memory.dm b/code/datums/ai/_ai_memory.dm new file mode 100644 index 00000000000..f837a58cb8c --- /dev/null +++ b/code/datums/ai/_ai_memory.dm @@ -0,0 +1,7 @@ +// General-purpose memorise proc, used by /commanded +/datum/mob_controller/proc/memorise(mob/speaker, message) + return + +// General-purpose memory checking proc, used by /faithful_hound +/datum/mob_controller/proc/check_memory(mob/speaker, message) + return FALSE diff --git a/code/datums/ai/_ai_pathfinding.dm b/code/datums/ai/_ai_pathfinding.dm new file mode 100644 index 00000000000..e01abaa05ce --- /dev/null +++ b/code/datums/ai/_ai_pathfinding.dm @@ -0,0 +1,29 @@ +/datum/mob_controller/proc/can_do_automated_move(variant_move_delay) + return body && !body.client + +/datum/mob_controller/proc/clear_paths() + clear_path() + +/datum/mob_controller/proc/clear_path() + executing_path = null + body?.stop_automove() + +/datum/mob_controller/proc/get_automove_target(datum/automove_metadata/metadata) + var/turf/move_target = (islist(executing_path) && length(executing_path)) ? executing_path[1] : null + if(!istype(move_target) || QDELETED(move_target)) + clear_path() + return null + return move_target + +/datum/mob_controller/proc/handle_post_automoved(atom/old_loc) + if(!islist(executing_path) || length(executing_path) <= 0) + return + var/turf/body_turf = get_turf(body) + if(!istype(body_turf)) + return + if(executing_path[1] != body_turf) + return + if(length(executing_path) > 1) + executing_path.Cut(1, 2) + else + clear_path() diff --git a/code/datums/ai/_ai_stance.dm b/code/datums/ai/_ai_stance.dm index ca018169811..be3b11f94a7 100644 --- a/code/datums/ai/_ai_stance.dm +++ b/code/datums/ai/_ai_stance.dm @@ -1,30 +1,43 @@ // Stub type for future expansion/logic encapsulation. /decl/mob_controller_stance abstract_type = /decl/mob_controller_stance - /decl/mob_controller_stance/none - /decl/mob_controller_stance/idle - /decl/mob_controller_stance/alert - /decl/mob_controller_stance/attack - /decl/mob_controller_stance/attacking - /decl/mob_controller_stance/tired - /decl/mob_controller_stance/contained - /decl/mob_controller_stance/commanded abstract_type = /decl/mob_controller_stance/commanded - /decl/mob_controller_stance/commanded/stop - /decl/mob_controller_stance/commanded/follow - /decl/mob_controller_stance/commanded/misc - /decl/mob_controller_stance/commanded/heal - /decl/mob_controller_stance/commanded/healing +/decl/mob_controller_stance/busy + +/datum/mob_controller/proc/get_activity() + return current_activity + +/datum/mob_controller/proc/set_activity(new_activity) + if(current_activity != new_activity) + current_activity = new_activity + return TRUE + return FALSE + +/datum/mob_controller/proc/set_stance(new_stance) + if(stance != new_stance) + stance = new_stance + return TRUE + return FALSE + +/datum/mob_controller/proc/get_stance() + return stance + +/datum/mob_controller/proc/startle() + if(QDELETED(body) || body.stat != UNCONSCIOUS) + return + body.set_stat(CONSCIOUS) + if(body.current_posture?.prone) + body.set_posture(/decl/posture/standing) diff --git a/code/datums/ai/_ai_targets.dm b/code/datums/ai/_ai_targets.dm new file mode 100644 index 00000000000..49c5acedb09 --- /dev/null +++ b/code/datums/ai/_ai_targets.dm @@ -0,0 +1,77 @@ +/datum/mob_controller/proc/get_target() + if(isnull(target_ref)) + return null + var/atom/target = target_ref?.resolve() + if(!istype(target) || QDELETED(target)) + set_target(null) + return null + return target + +/datum/mob_controller/proc/set_target(atom/new_target) + var/weakref/new_target_ref = weakref(new_target) + if(target_ref != new_target_ref) + target_ref = new_target_ref + return TRUE + return FALSE + +/datum/mob_controller/proc/find_target() + SHOULD_CALL_PARENT(TRUE) + next_target_scan_time = world.time + target_scan_delay + +/datum/mob_controller/proc/valid_target(var/atom/A) + if(!istype(A)) + return FALSE + if(!A.simulated) + return FALSE + if(A == body) + return FALSE + if(A.invisibility > body.see_invisible) + return FALSE + if(LAZYLEN(_friends) && ismob(A) && (weakref(A) in _friends)) + return FALSE + if(!A.loc) + return FALSE + return TRUE + +/datum/mob_controller/proc/lose_target() + path_frustration = 0 + path_obstacles = null + set_target(null) + lost_target() + +/datum/mob_controller/proc/lost_target() + set_stance(STANCE_IDLE) + body.stop_automove() + +/datum/mob_controller/proc/list_targets() + // By default, we only target designated enemies. + var/list/enemies = get_enemies() + if(!LAZYLEN(enemies)) + return + var/list/possible_targets = get_raw_target_list() + if(!length(possible_targets)) + return + for(var/weakref/enemy in enemies) // Remove all entries that aren't in enemies + var/M = enemy.resolve() + if(M in possible_targets) + LAZYDISTINCTADD(., M) + +/datum/mob_controller/proc/do_target_scan() + . = target_scan_distance > 0 && world.time >= next_target_scan_time + +/datum/mob_controller/proc/move_to_target(var/move_only = FALSE) + return + +/datum/mob_controller/proc/get_raw_target_list() + if(target_scan_distance) + return hearers(body, target_scan_distance)-body + return null + +/datum/mob_controller/proc/get_valid_targets() + . = list() + for(var/target in list_targets(target_scan_distance)) + if(valid_target(target)) + . += target + +/datum/mob_controller/proc/handle_ranged_target(atom/ranged_target) + return FALSE diff --git a/code/datums/ai/_ai_wander.dm b/code/datums/ai/_ai_wander.dm new file mode 100644 index 00000000000..705d3fdcef1 --- /dev/null +++ b/code/datums/ai/_ai_wander.dm @@ -0,0 +1,5 @@ +/datum/mob_controller/proc/stop_wandering() + stop_wander = TRUE + +/datum/mob_controller/proc/resume_wandering() + stop_wander = FALSE diff --git a/code/datums/ai/aggressive.dm b/code/datums/ai/aggressive.dm index 62316d4d197..e50d4e124ae 100644 --- a/code/datums/ai/aggressive.dm +++ b/code/datums/ai/aggressive.dm @@ -2,36 +2,17 @@ stance = STANCE_IDLE stop_wander_when_pulled = FALSE try_destroy_surroundings = TRUE + target_scan_distance = 10 + var/attack_same_faction = FALSE var/only_attack_enemies = FALSE var/break_stuff_probability = 10 - var/weakref/target_ref /datum/mob_controller/aggressive/New() ..() if(isliving(body) && !QDELETED(body) && !QDELETED(src)) body.set_intent(I_FLAG_HARM) -/datum/mob_controller/aggressive/set_target(atom/new_target) - var/weakref/new_target_ref = weakref(new_target) - if(target_ref != new_target_ref) - target_ref = new_target_ref - return TRUE - return FALSE - -/datum/mob_controller/aggressive/get_target() - if(isnull(target_ref)) - return null - var/atom/target = target_ref?.resolve() - if(!istype(target) || QDELETED(target)) - set_target(null) - return null - return target - -/datum/mob_controller/aggressive/Destroy() - set_target(null) - return ..() - /datum/mob_controller/aggressive/do_process() if(!(. = ..())) @@ -42,24 +23,36 @@ set_stance(get_target() ? STANCE_ATTACK : STANCE_IDLE) return + if(isnull(stance)) + set_stance(get_target() ? STANCE_ATTACK : STANCE_IDLE) + if(isturf(body.loc) && !body.buckled) switch(stance) if(STANCE_IDLE) - set_target(find_target()) - set_stance(STANCE_ATTACK) + if(do_target_scan()) + set_target(find_target()) + if(get_target()) + set_stance(STANCE_ATTACK) if(STANCE_ATTACK) - body.face_atom(get_target()) - if(try_destroy_surroundings) - destroy_surroundings() - move_to_target() + + if(get_target()) + body.face_atom(get_target()) + if(try_destroy_surroundings) + destroy_surroundings() + move_to_target() + else + set_stance(STANCE_IDLE) if(STANCE_ATTACKING) - body.face_atom(get_target()) - if(try_destroy_surroundings) - destroy_surroundings() - handle_attacking_target() + if(get_target()) + body.face_atom(get_target()) + if(try_destroy_surroundings) + destroy_surroundings() + handle_attacking_target() + else + set_stance(STANCE_IDLE) if(STANCE_CONTAINED) //we aren't inside something so just switch set_stance(STANCE_IDLE) @@ -79,7 +72,7 @@ /datum/mob_controller/aggressive/proc/handle_attacking_target() stop_wandering() var/atom/target = get_target() - if(!istype(target) || !attackable(target) || !(target in list_targets(10))) // consider replacing this list_targets() call with a distance or LOS check + if(!istype(target) || !attackable(target) || !(target in get_raw_target_list())) lose_target() return FALSE if (ishuman(target)) @@ -209,51 +202,40 @@ return stop_wandering() var/atom/target = get_target() - if(!istype(target) || !attackable(target) || !(target in list_targets(10))) + if(!istype(target) || !attackable(target) || !(target in get_raw_target_list())) lose_target() return if(body.has_ranged_attack() && get_dist(body, target) <= body.get_ranged_attack_distance() && !move_only) body.stop_automove() - open_fire() + handle_ranged_target(target) return set_stance(STANCE_ATTACKING) body.start_automove(target) -/datum/mob_controller/aggressive/list_targets(var/dist = 7) +/datum/mob_controller/aggressive/list_targets() // Base hostile mobs will just destroy everything in view. // Mobs with an enemy list will filter the view by their enemies. if(!only_attack_enemies) - return hearers(body, dist)-body - var/list/enemies = get_enemies() - if(!LAZYLEN(enemies)) - return - var/list/possible_targets = hearers(body, dist)-body - if(!length(possible_targets)) - return - for(var/weakref/enemy in enemies) // Remove all entries that aren't in enemies - var/M = enemy.resolve() - if(M in possible_targets) - LAZYDISTINCTADD(., M) + return get_raw_target_list() + return ..() /datum/mob_controller/aggressive/find_target() + . = ..() if(!body.can_act() || !body.faction) return null resume_wandering() - for(var/atom/A in list_targets(10)) - if(valid_target(A)) - set_stance(STANCE_ATTACK) - body.face_atom(A) - return A + for(var/atom/A in get_valid_targets()) + set_stance(STANCE_ATTACK) + body.face_atom(A) + return A /datum/mob_controller/aggressive/valid_target(var/atom/A) - if(A == body) + if(!..()) return FALSE if(ismob(A)) var/mob/M = A if(M.faction == body.faction && !attack_same_faction) return FALSE - else if(weakref(M) in get_friends()) - return FALSE if(M.stat) return FALSE if(ishuman(M)) @@ -262,20 +244,12 @@ return FALSE return TRUE -/datum/mob_controller/aggressive/open_fire() - if(!body.can_act()) +/datum/mob_controller/aggressive/handle_ranged_target(atom/ranged_target) + if(!body.can_act() || !ranged_target) return FALSE - body.handle_ranged_attack(get_target()) + body.handle_ranged_attack(ranged_target) return TRUE -/datum/mob_controller/aggressive/lose_target() - set_target(null) - lost_target() - -/datum/mob_controller/aggressive/lost_target() - set_stance(STANCE_IDLE) - body.stop_automove() - /datum/mob_controller/aggressive/pacify(mob/user) ..() attack_same_faction = FALSE diff --git a/code/datums/ai/beast.dm b/code/datums/ai/beast.dm index 91ad1fec7a5..9090698f55a 100644 --- a/code/datums/ai/beast.dm +++ b/code/datums/ai/beast.dm @@ -36,7 +36,7 @@ qdel(S) break -/datum/mob_controller/aggressive/beast/list_targets(var/dist = 7) +/datum/mob_controller/aggressive/beast/list_targets() . = ..() if(!length(.)) if(LAZYLEN(prey)) @@ -46,6 +46,6 @@ if(M) . |= M else if(body.get_nutrition() < body.get_max_nutrition() * 0.75) //time to look for some food - for(var/mob/living/L in view(body, dist)) + for(var/mob/living/L in get_raw_target_list()) if(attack_same_faction || L.faction != body.faction) LAZYDISTINCTADD(prey, weakref(L)) diff --git a/code/datums/ai/commanded.dm b/code/datums/ai/commanded.dm index e4452782a84..c9e1e7f8e97 100644 --- a/code/datums/ai/commanded.dm +++ b/code/datums/ai/commanded.dm @@ -85,8 +85,8 @@ var/list/targets = get_targets_by_name(message) if(LAZYLEN(targets) != 1) //CONFUSED. WHO DO I FOLLOW? return 0 - var/weakref/target_ref = targets[1] - set_target(target_ref.resolve()) //YEAH GOOD IDEA + var/weakref/single_target_ref = targets[1] + set_target(single_target_ref.resolve()) //YEAH GOOD IDEA set_stance(STANCE_COMMANDED_FOLLOW) //GOT SOMEBODY. BETTER FOLLOW EM. return 1 @@ -98,7 +98,7 @@ return stop_wandering() var/atom/target = get_target() - if(istype(target) && (target in list_targets(10))) + if(istype(target) && (target in get_raw_target_list())) body.start_automove(target) /datum/mob_controller/aggressive/commanded/proc/commanded_stop() //basically a proc that runs whenever we are asked to stay put. Probably going to remain unused. @@ -125,12 +125,14 @@ LAZYADD(., weakref(M)) /datum/mob_controller/aggressive/commanded/find_target() + SHOULD_CALL_PARENT(FALSE) + next_target_scan_time = world.time + target_scan_delay if(!LAZYLEN(_allowed_targets)) return null var/mode = "specific" if(LAZYACCESS(_allowed_targets, 1) == "everyone") //we have been given the golden gift of murdering everything. Except our master, of course. And our friends. So just mostly everyone. mode = "everyone" - for(var/atom/A in list_targets(10)) + for(var/atom/A in get_raw_target_list()) if(A == src) continue if(isliving(A)) diff --git a/code/datums/movement/automove_controller.dm b/code/datums/movement/automove_controller.dm index 08dae443f35..b413d999849 100644 --- a/code/datums/movement/automove_controller.dm +++ b/code/datums/movement/automove_controller.dm @@ -1,7 +1,8 @@ /// Implements automove logic; can be overridden on mob procs if you want to vary the logic from the below. /decl/automove_controller - var/completion_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom reaches its target. - var/failure_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom fails to move. + var/completion_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom reaches its target. + var/failure_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom fails to move. + var/try_avoid_obstacles = TRUE // Will try to move 90 degrees around an obstacle. /decl/automove_controller/proc/handle_mover(atom/movable/mover, datum/automove_metadata/metadata) @@ -49,14 +50,16 @@ return TRUE // no idea how we would get into this position if(mover.SelfMove(target_dir) && (old_loc != mover.loc)) - return TRUE + mover.handle_post_automoved(old_loc) + return (mover.get_automove_target() == mover.loc) // We may have transitioned to the next step in a path. - // Try to move around any obstacle. - var/static/list/_alt_dir_rot = list(45, -45) - for(var/alt_dir in shuffle(_alt_dir_rot)) - mover.reset_movement_delay() - if(mover.SelfMove(turn(target_dir, alt_dir)) && (old_loc != mover.loc)) - return TRUE + if(try_avoid_obstacles) + // Try to move around any obstacle. + var/static/list/_alt_dir_rot = list(45, -45) + for(var/alt_dir in shuffle(_alt_dir_rot)) + mover.reset_movement_delay() + if(mover.SelfMove(turn(target_dir, alt_dir)) && (old_loc != mover.loc)) + return TRUE mover.failed_automove() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index d2be93779da..0c98725538c 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -581,6 +581,9 @@ if(!.) // If we're under or inside shelter, use the z-level rain (for ambience) . = SSweather.weather_by_z[my_turf.z] +/atom/movable/proc/handle_post_automoved(atom/old_loc) + return + /atom/movable/take_vaporized_reagent(reagent, amount) if(ATOM_IS_OPEN_CONTAINER(src)) return loc?.take_vaporized_reagent(reagent, amount) diff --git a/code/game/turfs/turf_navigation.dm b/code/game/turfs/turf_navigation.dm new file mode 100644 index 00000000000..9085f6b99d9 --- /dev/null +++ b/code/game/turfs/turf_navigation.dm @@ -0,0 +1,62 @@ +/******************************************************************/ +// Navigation procs +// Used for A-star pathfinding + +// Returns the surrounding cardinal turfs with open links +// Including through doors openable with the ID +/turf/proc/CardinalTurfsWithAccess(var/obj/item/card/id/ID) + var/L[] = new() + + for(var/d in global.cardinal) + var/turf/T = get_step(src, d) + if(istype(T) && !T.density && T.simulated && !LinkBlockedWithAccess(src, T, ID)) + L.Add(T) + return L + + +// Returns true if a link between A and B is blocked +// Movement through doors allowed if ID has access +/proc/LinkBlockedWithAccess(turf/A, turf/B, obj/item/card/id/ID) + + if(A == null || B == null) return 1 + var/adir = get_dir(A,B) + var/rdir = get_dir(B,A) + if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal + var/iStep = get_step(A,adir&(NORTH|SOUTH)) + if(!LinkBlockedWithAccess(A,iStep, ID) && !LinkBlockedWithAccess(iStep,B,ID)) + return 0 + + var/pStep = get_step(A,adir&(EAST|WEST)) + if(!LinkBlockedWithAccess(A,pStep,ID) && !LinkBlockedWithAccess(pStep,B,ID)) + return 0 + return 1 + + if(DirBlockedWithAccess(A,adir, ID)) + return 1 + + if(DirBlockedWithAccess(B,rdir, ID)) + return 1 + + for(var/obj/O in B) + if(O.density && !istype(O, /obj/machinery/door) && !(O.atom_flags & ATOM_FLAG_CHECKS_BORDER)) + return 1 + + return 0 + +// Returns true if direction is blocked from loc +// Checks doors against access with given ID +/proc/DirBlockedWithAccess(turf/loc,var/dir,var/obj/item/card/id/ID) + for(var/obj/structure/window/D in loc) + if(!D.density) continue + if(D.dir == SOUTHWEST) return 1 + if(D.dir == dir) return 1 + + for(var/obj/machinery/door/D in loc) + if(!D.density) continue + if(istype(D, /obj/machinery/door/window)) + if( dir & D.dir ) return !D.check_access(ID) + + //if((dir & SOUTH) && (D.dir & (EAST|WEST))) return !D.check_access(ID) + //if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return !D.check_access(ID) + else return !D.check_access(ID) // it's a real, air blocking door + return 0 diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm index 25f9dca52a4..f92af368dcd 100644 --- a/code/modules/integrated_electronics/subtypes/smart.dm +++ b/code/modules/integrated_electronics/subtypes/smart.dm @@ -96,6 +96,10 @@ return ..() /obj/item/integrated_circuit/smart/advanced_pathfinder/do_work() + + if(waiting_for_path) + return + if(!assembly) activate_pin(3) return @@ -119,20 +123,30 @@ if(Pl&&islist(Pl)) idc.access = Pl var/turf/a_loc = get_turf(assembly) - var/list/P = AStar(a_loc, locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, 200, id=idc, exclude=get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom))) + SSpathfinding.enqueue_mover( + src, + locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), + new /datum/pathfinding_metadata( + _max_node_depth = 200, + _id = idc, + _obstacle = get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom)) + ) + ) - if(!P) - activate_pin(3) - return - else - var/list/Xn = new/list(P.len) - var/list/Yn = new/list(P.len) - var/turf/T - for(var/i =1 to P.len) - T=P[i] - Xn[i] = T.x - Yn[i] = T.y - set_pin_data(IC_OUTPUT, 1, Xn) - set_pin_data(IC_OUTPUT, 2, Yn) - push_data() - activate_pin(2) +/obj/item/integrated_circuit/smart/advanced_pathfinder/path_not_found() + ..() + activate_pin(3) + +/obj/item/integrated_circuit/smart/advanced_pathfinder/path_found(list/path) + ..() + var/list/Xn = new/list(path.len) + var/list/Yn = new/list(path.len) + var/turf/T + for(var/i = 1 to path.len) + T=path[i] + Xn[i] = T.x + Yn[i] = T.y + set_pin_data(IC_OUTPUT, 1, Xn) + set_pin_data(IC_OUTPUT, 2, Yn) + push_data() + activate_pin(2) diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm index fab8219c169..cfe9577d41c 100644 --- a/code/modules/mob/living/bot/bot.dm +++ b/code/modules/mob/living/bot/bot.dm @@ -242,7 +242,7 @@ resetTarget() lookForTargets() if(will_patrol && !LAZYLEN(grabbed_by) && !target) - if(patrol_path && patrol_path.len) + if(length(patrol_path)) for(var/i = 1 to patrol_speed) sleep(20 / (patrol_speed + 1)) handlePatrol() @@ -266,7 +266,7 @@ if(!target || !target.loc) return if(get_dist(src, target) > min_target_dist) - if(!target_path.len || get_turf(target) != target_path[target_path.len]) + if(!length(target_path) || get_turf(target) != target_path[target_path.len]) calcTargetPath() if(makeStep(target_path)) frustration = 0 @@ -297,9 +297,9 @@ return /mob/living/bot/proc/startPatrol() - var/turf/T = getPatrolTurf() - if(T) - patrol_path = AStar(get_turf(loc), T, TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_patrol_dist, id = botcard, exclude = obstacle) + var/turf/target_turf = getPatrolTurf() + if(target_turf) + patrol_path = SSpathfinding.find_path_immediate(start = get_turf(loc), end = target_turf, max_node_depth = max_patrol_dist, id = botcard, exclude = obstacle, check_tick = TRUE) if(!patrol_path) patrol_path = list() obstacle = null @@ -331,23 +331,22 @@ return /mob/living/bot/proc/calcTargetPath() - target_path = AStar(get_turf(loc), get_turf(target), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_target_dist, id = botcard, exclude = obstacle) - if(!target_path) - if(target && target.loc) - ignore_list |= target - resetTarget() - obstacle = null - return + target_path = SSpathfinding.find_path_immediate(start = get_turf(loc), end = get_turf(target), max_node_depth = max_target_dist, min_target_dist = min_target_dist, id = botcard, exclude = obstacle, check_tick = TRUE) + if(length(target_path)) + return + if(target?.loc) + ignore_list |= target + resetTarget() + obstacle = null /mob/living/bot/proc/makeStep(var/list/path) - if(!path.len) - return 0 - var/turf/T = path[1] - if(get_turf(src) == T) - path -= T + if(!length(path)) + return FALSE + var/turf/target_turf = path[1] + if(get_turf(src) == target_turf) + path -= target_turf return makeStep(path) - - return step_towards(src, T) + return step_towards(src, target_turf) /mob/living/bot/proc/resetTarget() target = null @@ -371,70 +370,6 @@ set_light(0) update_icon() -/******************************************************************/ -// Navigation procs -// Used for A-star pathfinding - - -// Returns the surrounding cardinal turfs with open links -// Including through doors openable with the ID -/turf/proc/CardinalTurfsWithAccess(var/obj/item/card/id/ID) - var/L[] = new() - - for(var/d in global.cardinal) - var/turf/T = get_step(src, d) - if(istype(T) && !T.density && T.simulated && !LinkBlockedWithAccess(src, T, ID)) - L.Add(T) - return L - - -// Returns true if a link between A and B is blocked -// Movement through doors allowed if ID has access -/proc/LinkBlockedWithAccess(turf/A, turf/B, obj/item/card/id/ID) - - if(A == null || B == null) return 1 - var/adir = get_dir(A,B) - var/rdir = get_dir(B,A) - if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal - var/iStep = get_step(A,adir&(NORTH|SOUTH)) - if(!LinkBlockedWithAccess(A,iStep, ID) && !LinkBlockedWithAccess(iStep,B,ID)) - return 0 - - var/pStep = get_step(A,adir&(EAST|WEST)) - if(!LinkBlockedWithAccess(A,pStep,ID) && !LinkBlockedWithAccess(pStep,B,ID)) - return 0 - return 1 - - if(DirBlockedWithAccess(A,adir, ID)) - return 1 - - if(DirBlockedWithAccess(B,rdir, ID)) - return 1 - - for(var/obj/O in B) - if(O.density && !istype(O, /obj/machinery/door) && !(O.atom_flags & ATOM_FLAG_CHECKS_BORDER)) - return 1 - - return 0 - -// Returns true if direction is blocked from loc -// Checks doors against access with given ID -/proc/DirBlockedWithAccess(turf/loc,var/dir,var/obj/item/card/id/ID) - for(var/obj/structure/window/D in loc) - if(!D.density) continue - if(D.dir == SOUTHWEST) return 1 - if(D.dir == dir) return 1 - - for(var/obj/machinery/door/D in loc) - if(!D.density) continue - if(istype(D, /obj/machinery/door/window)) - if( dir & D.dir ) return !D.check_access(ID) - - //if((dir & SOUTH) && (D.dir & (EAST|WEST))) return !D.check_access(ID) - //if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return !D.check_access(ID) - else return !D.check_access(ID) // it's a real, air blocking door - return 0 - /mob/living/bot/GetIdCards(list/exceptions) . = ..() if(istype(botcard) && !is_type_in_list(botcard, exceptions)) diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm index e989ba53b32..0381d5de33f 100644 --- a/code/modules/mob/living/bot/cleanbot.dm +++ b/code/modules/mob/living/bot/cleanbot.dm @@ -43,11 +43,14 @@ /mob/living/bot/cleanbot/confirmTarget(var/obj/effect/decal/cleanable/D) if(!..()) - return 0 + return FALSE + if(istype(D, /obj/effect/decal/cleanable/dirt)) + var/obj/effect/decal/cleanable/dirt/dirt = D + return dirt.dirt_amount >= 25 for(var/T in target_types) if(istype(D, T)) - return 1 - return 0 + return TRUE + return FALSE /mob/living/bot/cleanbot/handleAdjacentTarget() if(get_turf(target) == src.loc) diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm index 86a8a941189..0e027aa687b 100644 --- a/code/modules/mob/living/bot/farmbot.dm +++ b/code/modules/mob/living/bot/farmbot.dm @@ -123,24 +123,6 @@ target = source return -/mob/living/bot/farmbot/calcTargetPath() // We need to land NEXT to the tray, because the tray itself is impassable - for(var/trayDir in list(NORTH, SOUTH, EAST, WEST)) - target_path = AStar(get_turf(loc), get_step(get_turf(target), trayDir), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_target_dist, id = botcard) - if(target_path) - break - if(!target_path) - ignore_list |= target - target = null - target_path = list() - return - -/mob/living/bot/farmbot/stepToTarget() // Same reason - var/turf/T = get_turf(target) - if(!target_path.len || !T.Adjacent(target_path[target_path.len])) - calcTargetPath() - makeStep(target_path) - return - /mob/living/bot/farmbot/UnarmedAttack(var/atom/A, var/proximity) . = ..() if(.) diff --git a/code/modules/mob/living/bot/mulebot.dm b/code/modules/mob/living/bot/mulebot.dm index bb9cf31e58e..437ef616451 100644 --- a/code/modules/mob/living/bot/mulebot.dm +++ b/code/modules/mob/living/bot/mulebot.dm @@ -183,7 +183,7 @@ /mob/living/bot/mulebot/calcTargetPath() ..() - if(!target_path.len && target != home) // I presume that target is not null + if(!length(target_path) && target != home) // I presume that target is not null resetTarget() target = home targetName = "Home" diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index f63669fb746..a8af9c4657a 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1359,11 +1359,6 @@ default behaviour is: return FALSE return TRUE -//gets name from ID or PDA itself, ID inside PDA doesn't matter -//Useful when player is being seen by other mobs -/mob/living/proc/get_id_name(if_no_id = "Unknown") - return GetIdCard(exceptions = list(/obj/item/holder))?.registered_name || if_no_id - /mob/living/get_default_temperature_threshold(threshold) if(isSynthetic()) switch(threshold) diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index c32b39b1a42..2f79162b6c9 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -64,7 +64,7 @@ stop_wandering() stance_step++ if(stance_step >= 20) - if(target && (target in list_targets(10))) + if(target && (target in get_raw_target_list())) set_stance(STANCE_ATTACK) //If the mob he was chasing is still nearby, resume the attack, otherwise go idle. else set_stance(STANCE_IDLE) @@ -72,7 +72,7 @@ if(STANCE_ALERT) stop_wandering() var/found_mob = 0 - if(target && (target in list_targets(10))) + if(target && (target in get_raw_target_list())) if(!attackable(target)) stance_step = max(0, stance_step) //If we have not seen a mob in a while, the stance_step will be negative, we need to reset it to 0 as soon as we see a mob again. stance_step++ diff --git a/code/modules/mob/living/simple_animal/hostile/commanded/nanomachines.dm b/code/modules/mob/living/simple_animal/hostile/commanded/nanomachines.dm index f504d63398e..1145b90b6e8 100644 --- a/code/modules/mob/living/simple_animal/hostile/commanded/nanomachines.dm +++ b/code/modules/mob/living/simple_animal/hostile/commanded/nanomachines.dm @@ -52,8 +52,8 @@ if(LAZYLEN(targets) != 1) body.say("ERROR. TARGET COULD NOT BE PARSED.") return 0 - var/weakref/target_ref = targets[1] - set_target(target_ref.resolve()) + var/weakref/single_target_ref = targets[1] + set_target(single_target_ref.resolve()) set_stance(STANCE_COMMANDED_HEAL) return 1 if(findtext(text,"emergency protocol")) diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spiders/ai_guard.dm b/code/modules/mob/living/simple_animal/hostile/giant_spiders/ai_guard.dm index be65f62f707..b828ae9e7b0 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spiders/ai_guard.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spiders/ai_guard.dm @@ -23,7 +23,7 @@ paired_nurse = null /datum/mob_controller/aggressive/giant_spider/guard/proc/find_nurse() - for(var/mob/living/simple_animal/hostile/giant_spider/nurse/nurse in list_targets(10)) + for(var/mob/living/simple_animal/hostile/giant_spider/nurse/nurse in get_raw_target_list()) if(nurse.stat || !istype(nurse.ai, /datum/mob_controller/aggressive/giant_spider/nurse)) continue var/datum/mob_controller/aggressive/giant_spider/nurse/nurse_ai = nurse.ai diff --git a/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm b/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm index 091230eca45..ead786ab340 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebots/megabot.dm @@ -26,7 +26,7 @@ /datum/mob_controller/aggressive/megahivebot can_escape_buckles = TRUE -/datum/mob_controller/aggressive/megahivebot/open_fire() +/datum/mob_controller/aggressive/megahivebot/handle_ranged_target(atom/ranged_target) var/mob/living/simple_animal/hostile/hivebot/mega/megabot = body if(!istype(megabot)) return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index c2e2b3c5a18..88defeeaf28 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -45,7 +45,7 @@ var/global/list/protected_objects = list( var/awake = TRUE // Return a list of targets that isn't the creator -/datum/mob_controller/aggressive/mimic/list_targets(var/dist = 7) +/datum/mob_controller/aggressive/mimic/get_valid_targets() var/mob/living/simple_animal/hostile/mimic/mimic = body . = istype(mimic) && mimic.awake && ..() if(length(.) && mimic.creator) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm index 498adac96b3..e3f5b4fe9ff 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm @@ -54,8 +54,15 @@ /mob/living/simple_animal/hostile/malf_drone/has_ranged_attack() return TRUE -/datum/mob_controller/aggressive/malf_drone/list_targets(var/dist = 7) - . = ..(hostile_drone ? hostile_range : dist) +/datum/mob_controller/aggressive/malf_drone/get_raw_target_list() + if(hostile_drone) + target_scan_distance = hostile_range + else + target_scan_distance = initial(target_scan_distance) + . = ..() + +/datum/mob_controller/aggressive/malf_drone/get_valid_targets() + . = ..() for(var/mob/M in .) if(istype(M, body.type)) . -= M diff --git a/code/modules/mob/living/simple_animal/hostile/slug.dm b/code/modules/mob/living/simple_animal/hostile/slug.dm index 8384fc93a6c..4218f9176bf 100644 --- a/code/modules/mob/living/simple_animal/hostile/slug.dm +++ b/code/modules/mob/living/simple_animal/hostile/slug.dm @@ -23,13 +23,14 @@ try_destroy_surroundings = FALSE can_escape_buckles = TRUE -/datum/mob_controller/aggressive/slug/list_targets(var/dist = 7) +/datum/mob_controller/aggressive/slug/valid_target(atom/A) . = ..() - var/mob/living/simple_animal/hostile/slug/slug = body - if(istype(slug)) - for(var/mob/living/M in .) - if(slug.check_friendly_species(M)) - . -= M + if(.) + if(!ismob(A)) + return FALSE + var/mob/living/simple_animal/hostile/slug/slug = body + if(slug.check_friendly_species(A)) + return FALSE /mob/living/simple_animal/hostile/slug/proc/check_friendly_species(var/mob/living/M) return istype(M) && M.faction == faction diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index edf74c14dd0..35df33c80fa 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1395,5 +1395,10 @@ /mob/proc/handle_footsteps() return +//gets name from ID or PDA itself, ID inside PDA doesn't matter +//Useful when player is being seen by other mobs +/mob/proc/get_id_name(if_no_id = "Unknown") + return GetIdCard(exceptions = list(/obj/item/holder))?.registered_name || if_no_id + /mob/proc/can_twohand_item(obj/item/item) return FALSE diff --git a/code/modules/mob/mob_automove.dm b/code/modules/mob/mob_automove.dm index ff8d1a6d7ba..f552d4f7c1e 100644 --- a/code/modules/mob/mob_automove.dm +++ b/code/modules/mob/mob_automove.dm @@ -6,18 +6,60 @@ _automove_target = null return ..() +/mob/path_found(list/path) + ..() + if(islist(path) && length(path) > 1) + path.Cut(1, 2) // Remove the first turf since it's going to be our origin. + if(length(path)) + start_automove(path) + +/mob/path_not_found() + ..() + stop_automove() + /// Called by get_movement_delay() to override the current move intent, in cases where an automove has a delay override. /mob/proc/get_automove_delay() var/datum/automove_metadata/metadata = SSautomove.moving_metadata[src] return metadata?.move_delay +/mob/failed_automove() + ..() + stop_automove() + _automove_target = null + return FALSE + /mob/start_automove(target, movement_type, datum/automove_metadata/metadata) _automove_target = target return ..() // The AI datum may decide to track a target instead of using the mob reference. /mob/get_automove_target(datum/automove_metadata/metadata) - . = (istype(ai) && ai.get_automove_target()) || _automove_target || ..() + . = _automove_target || (istype(ai) && ai.get_automove_target()) || ..() + if(islist(.)) + var/list/path = . + while(length(path) && path[1] == get_turf(src)) + path.Cut(1,2) + if(length(path)) + return path[1] + return null + +/mob/handle_post_automoved(atom/old_loc) + if(istype(ai)) + ai.handle_post_automoved(old_loc) + return + if(!islist(_automove_target) || length(_automove_target) <= 0) + return + var/turf/body_turf = get_turf(src) + if(!istype(body_turf)) + return + var/list/_automove_target_list = _automove_target + if(_automove_target_list[1] != body_turf) + return + if(length(_automove_target_list) > 1) + _automove_target_list.Cut(1, 2) + else + _automove_target_list = null + stop_automove() // We do some early checking here to avoid doing the same checks repeatedly by calling SelfMove(). /mob/can_do_automated_move(variant_move_delay) diff --git a/code/procs/AStar.dm b/code/procs/pathfinding.dm similarity index 78% rename from code/procs/AStar.dm rename to code/procs/pathfinding.dm index 01e4213f805..7732761b8d3 100644 --- a/code/procs/AStar.dm +++ b/code/procs/pathfinding.dm @@ -5,14 +5,14 @@ A Star pathfinding algorithm Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of windows along the route into account. Use: -your_list = AStar(start location, end location, adjacent turf proc, distance proc) +your_list = find_path_astar(start location, end location, adjacent turf proc, distance proc) For the adjacent turf proc i wrote: /turf/proc/AdjacentTurfs And for the distance one i wrote: /turf/proc/Distance So an example use might be: -src.path_list = AStar(src.loc, target.loc, TYPE_PROC_REF(/turf, AdjacentTurfs), TYPE_PROC_REF(/turf, Distance)) +src.path_list = find_path_astar(src.loc, target.loc, TYPE_PROC_REF(/turf, AdjacentTurfs), TYPE_PROC_REF(/turf, Distance)) Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use. @@ -60,7 +60,12 @@ length to avoid portals or something i guess?? Not that they're counted right no /proc/PathWeightCompare(PathNode/a, PathNode/b) return a.estimated_cost - b.estimated_cost -/proc/AStar(var/start, var/end, adjacent, dist, var/max_nodes, var/max_node_depth = 30, var/min_target_dist = 0, var/min_node_dist, var/id, var/datum/exclude) +/proc/find_path_astar_async(start, end, adjacent, dist, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude) + set waitfor = FALSE + return find_path_astar(start, end, adjacent, dist, max_nodes, max_node_depth, min_target_dist, min_node_dist, id, exclude, check_tick = TRUE) + +/proc/find_path_astar(start, end, adjacent, dist, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude, check_tick = FALSE) + var/datum/priority_queue/open = new /datum/priority_queue(/proc/PathWeightCompare) var/list/closed = list() var/list/path @@ -85,13 +90,11 @@ length to avoid portals or something i guess?? Not that they're counted right no path[index--] = current.position break - if(min_node_dist && max_node_depth) - if(call(current.position, min_node_dist)(end) + current.nodes_traversed >= max_node_depth) - continue + if(min_node_dist && max_node_depth && (call(current.position, min_node_dist)(end) + current.nodes_traversed >= max_node_depth)) + continue - if(max_node_depth) - if(current.nodes_traversed >= max_node_depth) - continue + if(max_node_depth && current.nodes_traversed >= max_node_depth) + continue for(var/datum/datum in call(current.position, adjacent)(id)) if(datum == exclude) @@ -115,4 +118,9 @@ length to avoid portals or something i guess?? Not that they're counted right no if(max_nodes && open.Length() > max_nodes) open.Remove(open.Length()) + if(check_tick) + CHECK_TICK + if(check_tick) + CHECK_TICK + return path diff --git a/nebula.dme b/nebula.dme index f083035f50c..9c6652668d1 100644 --- a/nebula.dme +++ b/nebula.dme @@ -284,6 +284,7 @@ #include "code\controllers\subsystems\mapping.dm" #include "code\controllers\subsystems\misc_late.dm" #include "code\controllers\subsystems\overlays.dm" +#include "code\controllers\subsystems\pathfinding.dm" #include "code\controllers\subsystems\plants.dm" #include "code\controllers\subsystems\radiation.dm" #include "code\controllers\subsystems\shuttle.dm" @@ -358,7 +359,13 @@ #include "code\datums\type_cloning.dm" #include "code\datums\weakref.dm" #include "code\datums\ai\_ai.dm" +#include "code\datums\ai\_ai_enemies.dm" +#include "code\datums\ai\_ai_friends.dm" +#include "code\datums\ai\_ai_memory.dm" +#include "code\datums\ai\_ai_pathfinding.dm" #include "code\datums\ai\_ai_stance.dm" +#include "code\datums\ai\_ai_targets.dm" +#include "code\datums\ai\_ai_wander.dm" #include "code\datums\ai\aggressive.dm" #include "code\datums\ai\beast.dm" #include "code\datums\ai\commanded.dm" @@ -1565,6 +1572,7 @@ #include "code\game\turfs\turf_footsteps.dm" #include "code\game\turfs\turf_height.dm" #include "code\game\turfs\turf_material.dm" +#include "code\game\turfs\turf_navigation.dm" #include "code\game\turfs\turf_ramps.dm" #include "code\game\turfs\unsimulated.dm" #include "code\game\turfs\flooring\_flooring.dm" @@ -4002,9 +4010,9 @@ #include "code\modules\ZAS\Variable Settings.dm" #include "code\modules\ZAS\Zone.dm" #include "code\procs\announce.dm" -#include "code\procs\AStar.dm" #include "code\procs\dbcore.dm" #include "code\procs\hud.dm" +#include "code\procs\pathfinding.dm" #include "code\procs\radio.dm" #include "code\unit_tests\_defines.dm" #include "code\unit_tests\_includes.dm"