Skip to content

Commit

Permalink
Implement custom path finding
Browse files Browse the repository at this point in the history
I found the path finding code highly complicated and also inefficient, since we allocated on the heap multiple times, without amortising the allocations.

This implementation provides a struct that implements the A* and also line of sight path finding in a way, that is compatible with the server implementations. We also re-use the heap allocations.

The A* algorithm is a basic implementation that uses a binary heap. Moved the code in korangar_utils, so that it can be properly optimized and also tested.
  • Loading branch information
hasenbanck committed Dec 21, 2024
1 parent 137a680 commit e3d7a93
Show file tree
Hide file tree
Showing 8 changed files with 451 additions and 156 deletions.
42 changes: 0 additions & 42 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ lunify = "1.1"
mlua = "0.10"
num = "0.4"
option-ext = "0.2"
pathfinding = "4.11"
pcap = "2.2"
pollster = "0.4"
proc-macro2 = "1.0"
Expand Down
1 change: 0 additions & 1 deletion korangar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ lunify = { workspace = true }
mlua = { workspace = true, features = ["lua51", "vendored"] }
num = { workspace = true }
option-ext = { workspace = true }
pathfinding = { workspace = true }
pollster = { workspace = true }
ragnarok_bytes = { workspace = true, features = ["derive", "cgmath"] }
ragnarok_formats = { workspace = true, features = ["interface"] }
Expand Down
10 changes: 8 additions & 2 deletions korangar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use korangar_networking::{
DisconnectReason, HotkeyState, LoginServerLoginData, MessageColor, NetworkEvent, NetworkEventBuffer, NetworkingSystem, SellItem,
ShopItem,
};
use korangar_util::pathing::PathFinder;
#[cfg(feature = "debug")]
use korangar_util::texture_atlas::AtlasAllocation;
#[cfg(not(feature = "debug"))]
Expand Down Expand Up @@ -251,6 +252,7 @@ struct Client {
player_inventory: Inventory,
player_skill_tree: SkillTree,
hotbar: Hotbar,
path_finder: PathFinder,

point_light_set_buffer: ResourceSetBuffer<LightSourceKey>,
directional_shadow_object_set_buffer: ResourceSetBuffer<ObjectKey>,
Expand Down Expand Up @@ -503,6 +505,7 @@ impl Client {
let player_inventory = Inventory::default();
let player_skill_tree = SkillTree::default();
let hotbar = Hotbar::default();
let path_finder = PathFinder::default();

let point_light_set_buffer = ResourceSetBuffer::default();
let directional_shadow_object_set_buffer = ResourceSetBuffer::default();
Expand Down Expand Up @@ -650,6 +653,7 @@ impl Client {
player_inventory,
player_skill_tree,
hotbar,
path_finder,
point_light_set_buffer,
directional_shadow_object_set_buffer,
point_shadow_object_set_buffer,
Expand Down Expand Up @@ -943,6 +947,7 @@ impl Client {
&mut self.animation_loader,
&self.script_loader,
&self.map,
&mut self.path_finder,
saved_login_data.account_id,
character_information,
WorldPosition { x: 0, y: 0 },
Expand Down Expand Up @@ -1010,6 +1015,7 @@ impl Client {
&mut self.animation_loader,
&self.script_loader,
&self.map,
&mut self.path_finder,
entity_appeared_data,
client_tick,
);
Expand Down Expand Up @@ -1047,15 +1053,15 @@ impl Client {
let position_from = Vector2::new(position_from.x, position_from.y);
let position_to = Vector2::new(position_to.x, position_to.y);

entity.move_from_to(&self.map, position_from, position_to, starting_timestamp);
entity.move_from_to(&self.map, &mut self.path_finder, position_from, position_to, starting_timestamp);
#[cfg(feature = "debug")]
entity.generate_pathing_mesh(&self.device, &self.queue, &self.map, &self.pathing_texture_mapping);
}
}
NetworkEvent::PlayerMove(position_from, position_to, starting_timestamp) => {
let position_from = Vector2::new(position_from.x, position_from.y);
let position_to = Vector2::new(position_to.x, position_to.y);
self.entities[0].move_from_to(&self.map, position_from, position_to, starting_timestamp);
self.entities[0].move_from_to(&self.map, &mut self.path_finder, position_from, position_to, starting_timestamp);

#[cfg(feature = "debug")]
self.entities[0].generate_pathing_mesh(&self.device, &self.queue, &self.map, &self.pathing_texture_mapping);
Expand Down
137 changes: 35 additions & 102 deletions korangar/src/world/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use derive_new::new;
use korangar_interface::elements::PrototypeElement;
use korangar_interface::windows::{PrototypeWindow, Window};
use korangar_networking::EntityData;
use korangar_util::pathing::PathFinder;
#[cfg(feature = "debug")]
use korangar_util::texture_atlas::AtlasAllocation;
use ragnarok_formats::map::TileFlags;
use ragnarok_packets::{AccountId, CharacterInformation, ClientTick, EntityId, Sex, StatusType, WorldPosition};
#[cfg(feature = "debug")]
use wgpu::{BufferUsages, Device, Queue};
Expand Down Expand Up @@ -292,6 +292,7 @@ impl Common {
animation_loader: &mut AnimationLoader,
script_loader: &ScriptLoader,
map: &Map,
path_finder: &mut PathFinder,
entity_data: EntityData,
client_tick: ClientTick,
) -> Self {
Expand Down Expand Up @@ -347,7 +348,7 @@ impl Common {
if let Some(destination) = entity_data.destination {
let position_from = Vector2::new(entity_data.position.x, entity_data.position.y);
let position_to = Vector2::new(destination.x, destination.y);
common.move_from_to(map, position_from, position_to, client_tick);
common.move_from_to(map, path_finder, position_from, position_to, client_tick);
}

common
Expand Down Expand Up @@ -424,125 +425,46 @@ impl Common {
self.animation_state.update(client_tick);
}

pub fn move_from_to(&mut self, map: &Map, from: Vector2<usize>, to: Vector2<usize>, starting_timestamp: ClientTick) {
use pathfinding::prelude::astar;

#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct Pos(usize, usize);

impl Pos {
fn successors(&self, map: &Map) -> Vec<Pos> {
let &Pos(x, y) = self;
let mut successors = Vec::new();

if map.x_in_bounds(x + 1) {
successors.push(Pos(x + 1, y));
}

if x > 0 {
successors.push(Pos(x - 1, y));
}

if map.y_in_bounds(y + 1) {
successors.push(Pos(x, y + 1));
}

if y > 0 {
successors.push(Pos(x, y - 1));
}

if map.x_in_bounds(x + 1)
&& map.y_in_bounds(y + 1)
&& map.get_tile(Vector2::new(x + 1, y)).flags.contains(TileFlags::WALKABLE)
&& map.get_tile(Vector2::new(x, y + 1)).flags.contains(TileFlags::WALKABLE)
{
successors.push(Pos(x + 1, y + 1));
}

if x > 0
&& map.y_in_bounds(y + 1)
&& map.get_tile(Vector2::new(x - 1, y)).flags.contains(TileFlags::WALKABLE)
&& map.get_tile(Vector2::new(x, y + 1)).flags.contains(TileFlags::WALKABLE)
{
successors.push(Pos(x - 1, y + 1));
}

if map.x_in_bounds(x + 1)
&& y > 0
&& map.get_tile(Vector2::new(x + 1, y)).flags.contains(TileFlags::WALKABLE)
&& map.get_tile(Vector2::new(x, y - 1)).flags.contains(TileFlags::WALKABLE)
{
successors.push(Pos(x + 1, y - 1));
}

if x > 0
&& y > 0
&& map.get_tile(Vector2::new(x - 1, y)).flags.contains(TileFlags::WALKABLE)
&& map.get_tile(Vector2::new(x, y - 1)).flags.contains(TileFlags::WALKABLE)
{
successors.push(Pos(x - 1, y - 1));
}

let successors = successors
.drain(..)
.filter(|Pos(x, y)| map.get_tile(Vector2::new(*x, *y)).flags.contains(TileFlags::WALKABLE))
.collect::<Vec<Pos>>();

successors
}

fn convert_to_vector(self) -> Vector2<usize> {
Vector2::new(self.0, self.1)
pub fn move_from_to(
&mut self,
map: &Map,
path_finder: &mut PathFinder,
start: Vector2<usize>,
goal: Vector2<usize>,
starting_timestamp: ClientTick,
) {
if let Some(path) = path_finder.find_walkable_path(map, start, goal) {
if path.len() <= 1 {
return;
}
}

let result = astar(
&Pos(from.x, from.y),
|position| position.successors(map).into_iter().map(|position| (position, 0)),
|position| -> usize {
// Values taken from rAthena.
const MOVE_COST: usize = 10;
const DIAGONAL_MOVE_COST: usize = 14;

let distance_x = usize::abs_diff(position.0, to.x);
let distance_y = usize::abs_diff(position.1, to.y);

let straight_moves = usize::abs_diff(distance_x, distance_y);
let diagonal_moves = usize::min(distance_x, distance_y);

DIAGONAL_MOVE_COST * diagonal_moves + MOVE_COST * straight_moves
},
|position| *position == Pos(to.x, to.y),
)
.map(|x| x.0);

if let Some(path) = result {
let mut last_timestamp = starting_timestamp.0;
let mut last_position: Option<Vector2<usize>> = None;

// TODO: NHA When holding the mouse button, it seems we are "too fast".
let steps: Vec<(Vector2<usize>, u32)> = path
.into_iter()
.map(|pos| {
.iter()
.map(|&step| {
if let Some(position) = last_position {
const DIAGONAL_MULTIPLIER: f32 = 1.4;

let speed = match position.x == pos.0 || position.y == pos.1 {
let speed = match position == step {
// true means we are moving orthogonally
true => self.movement_speed as u32,
// false means we are moving diagonally
false => (self.movement_speed as f32 * DIAGONAL_MULTIPLIER) as u32,
};

let arrival_position = pos.convert_to_vector();
let arrival_position = step;
let arrival_timestamp = last_timestamp + speed;

last_timestamp = arrival_timestamp;
last_position = Some(arrival_position);

(arrival_position, arrival_timestamp)
} else {
last_position = Some(from);
(from, last_timestamp)
last_position = Some(start);
(start, last_timestamp)
}
})
.collect();
Expand All @@ -559,7 +481,7 @@ impl Common {
}

#[cfg(feature = "debug")]
fn pathing_texture_coordinates(steps: &Vec<(Vector2<usize>, u32)>, step: Vector2<usize>, index: usize) -> ([Vector2<f32>; 4], i32) {
fn pathing_texture_coordinates(steps: &[(Vector2<usize>, u32)], step: Vector2<usize>, index: usize) -> ([Vector2<f32>; 4], i32) {
if steps.len() - 1 == index {
return (
[
Expand Down Expand Up @@ -818,6 +740,7 @@ impl Player {
animation_loader: &mut AnimationLoader,
script_loader: &ScriptLoader,
map: &Map,
path_finder: &mut PathFinder,
account_id: AccountId,
character_information: CharacterInformation,
player_position: WorldPosition,
Expand All @@ -834,6 +757,7 @@ impl Player {
animation_loader,
script_loader,
map,
path_finder,
EntityData::from_character(account_id, character_information, player_position),
client_tick,
);
Expand Down Expand Up @@ -969,6 +893,7 @@ impl Npc {
animation_loader: &mut AnimationLoader,
script_loader: &ScriptLoader,
map: &Map,
path_finder: &mut PathFinder,
entity_data: EntityData,
client_tick: ClientTick,
) -> Self {
Expand All @@ -978,6 +903,7 @@ impl Npc {
animation_loader,
script_loader,
map,
path_finder,
entity_data,
client_tick,
);
Expand Down Expand Up @@ -1134,8 +1060,15 @@ impl Entity {
self.get_common_mut().update(map, delta_time, client_tick);
}

pub fn move_from_to(&mut self, map: &Map, from: Vector2<usize>, to: Vector2<usize>, starting_timestamp: ClientTick) {
self.get_common_mut().move_from_to(map, from, to, starting_timestamp);
pub fn move_from_to(
&mut self,
map: &Map,
path_finder: &mut PathFinder,
from: Vector2<usize>,
to: Vector2<usize>,
starting_timestamp: ClientTick,
) {
self.get_common_mut().move_from_to(map, path_finder, from, to, starting_timestamp);
}

#[cfg(feature = "debug")]
Expand Down
Loading

0 comments on commit e3d7a93

Please sign in to comment.