Skip to content

Commit

Permalink
feat: namigator integrated with server
Browse files Browse the repository at this point in the history
  • Loading branch information
pikdum committed Sep 12, 2024
1 parent 41aec2f commit 86bffa2
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 63 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ mix deps.get
# wget https://pomf2.lain.la/f/jxcam7ob.sqlite -O ./db/mangos0.sqlite

# need a vanilla wow client, this is directory with WoW.exe
# this is only for generating dbc.sqlite
# this is only for generating dbc.sqlite + maps
# also, change server to localhost in realmlist.wtf
export WOW_DIR="/path/to/vanilla/client"

# need docker
./scripts/generate-dbc-db.sh

# this takes a very long time
# probably 30+ minutes
mix build_maps

iex -S mix
# default logins are in application.ex
# test:test
Expand Down
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Config

config :thistle_tea, :map_dir, "maps"
config :thistle_tea, ecto_repos: [ThistleTea.DBC, ThistleTea.Mangos]
config :thistle_tea, ThistleTea.DBC, database: "db/dbc.sqlite", log: false
config :thistle_tea, ThistleTea.Mangos, database: "db/mangos0.sqlite", log: false
Expand Down
4 changes: 4 additions & 0 deletions lib/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ defmodule ThistleTea.Application do
nil
)

Logger.info("Loading maps...")
map_dir = Application.fetch_env!(:thistle_tea, :map_dir)
ThistleTea.Namigator.load(map_dir)

Logger.info("ThistleTea started.")

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
15 changes: 15 additions & 0 deletions lib/mix/tasks/build_maps.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Mix.Tasks.BuildMaps do
use Mix.Task

@shortdoc "Builds the maps for pathfinding"
def run(_) do
wow_dir = System.get_env("WOW_DIR")
data_dir = Path.join(wow_dir, "Data")
map_dir = Application.fetch_env!(:thistle_tea, :map_dir)

case ThistleTea.Namigator.build(data_dir, map_dir) do
true -> Mix.shell().info("Maps built successfully.")
false -> Mix.shell().error("Failed to build maps.")
end
end
end
3 changes: 2 additions & 1 deletion lib/namigator.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
defmodule ThistleTea.Namigator do
use Rustler, otp_app: :thistle_tea, crate: "thistletea_namigator"

def build(_wow_dir, _out_dir), do: :erlang.nif_error(:nif_not_loaded)
def build(_wow_dir, _map_dir), do: :erlang.nif_error(:nif_not_loaded)
def load(_map_dir), do: :erlang.nif_error(:nif_not_loaded)
def get_zone_and_area(_map, _x, _y, _z), do: :erlang.nif_error(:nif_not_loaded)

def find_random_point_around_circle(_map, _x, _y, _z, _radius),
Expand Down
Empty file added maps/.gitkeep
Empty file.
135 changes: 74 additions & 61 deletions native/thistletea_namigator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use namigator::raw::{build_bvh, build_map, bvh_files_exist, map_files_exist, PathfindMap};
use namigator::vanilla::{Map, VanillaMap};
use namigator::vanilla::Map;
use namigator::Vector3d;
use once_cell::sync::Lazy;
use rustler::NifResult;
Expand All @@ -15,89 +15,60 @@ pub struct PathfindingMaps {
}

impl PathfindingMaps {
pub fn new(wow_dir: &str, out_dir: &str) -> Result<Self, String> {
let data_path = PathBuf::from(wow_dir);
let output = PathBuf::from(out_dir);

println!("Building and using maps for pathfind from data directory '{}' and outputting to '{}'. This may take a while.", data_path.display(), output.display());
let mut maps = HashMap::new();

let threads = {
let t = std::thread::available_parallelism().unwrap().get() as u32;
let t = t.saturating_sub(2);
if t == 0 {
1
} else {
t
}
};

if !bvh_files_exist(&output).map_err(|e| e.to_string())? {
println!("Building gameobjects.");
build_bvh(&data_path, &output, threads).map_err(|e| e.to_string())?;
println!("Gameobjects built.");
} else {
println!("Gameobjects already built.");
pub fn new() -> Self {
Self {
maps: HashMap::new(),
}
}

pub fn load(&mut self, out_dir: &str) -> Result<(), String> {
let output = PathBuf::from(out_dir);

// Hardcoded list of Map variants to process
const MAPS_TO_PROCESS: &[Map] = &[
Map::EasternKingdoms,
Map::Kalimdor,
Map::DevelopmentLand,
// Add other Map variants you want to process
// TODO: add all
];

for &map in MAPS_TO_PROCESS {
let map_file_name = map.directory_name();
let map_id = map.as_int();

if !map_files_exist(&output, map_file_name).map_err(|e| e.to_string())? {
println!("Building map {:?} ({map_file_name})", map);
build_map(&data_path, &output, map_file_name, "", threads)
.map_err(|e| e.to_string())?;
println!("Finished building {:?} ({map_file_name})", map);
} else {
println!("{:?} ({map_file_name}) already built.", map);
}

let mut vanilla_map = VanillaMap::new(&output, map).map_err(|e| e.to_string())?;
vanilla_map.load_all_adts().map_err(|e| e.to_string())?;

let mut pathfind_map =
let pathfind_map =
PathfindMap::new(&output, map_file_name).map_err(|e| e.to_string())?;
pathfind_map.load_all_adts().map_err(|e| e.to_string())?;

maps.insert(map_id, pathfind_map);
println!("PathfindMap inserted with key: {}", map_id);
self.maps.insert(map_id, pathfind_map);
}

println!("Finished setting up maps");

Ok(Self { maps })
Ok(())
}

pub fn get_zone_and_area(
&self,
&mut self,
map_id: u32,
x: f32,
y: f32,
z: f32,
) -> Result<(u32, u32), String> {
if let Some(map) = self.maps.get(&map_id) {
if let Some(map) = self.maps.get_mut(&map_id) {
let _adt = map.load_adt_at(x, y);
// TODO: maybe keep tack of loaded adts, so i can unload them later?
map.get_zone_and_area(x, y, z).map_err(|e| e.to_string())
} else {
Err(format!("Map with ID '{}' not found", map_id))
}
}

pub fn find_random_point_around_circle(
&self,
&mut self,
map_id: u32,
start: Vector3d,
radius: f32,
) -> Result<Vector3d, String> {
if let Some(map) = self.maps.get(&map_id) {
if let Some(map) = self.maps.get_mut(&map_id) {
let _adt = map.load_adt_at(start.x, start.y);
// TODO: maybe keep tack of loaded adts, so i can unload them later?
map.find_random_point_around_circle(start, radius)
.map_err(|e| e.to_string())
} else {
Expand All @@ -106,26 +77,68 @@ impl PathfindingMaps {
}
}

#[rustler::nif]
#[rustler::nif(schedule = "DirtyCpu")]
fn build(wow_dir: String, out_dir: String) -> NifResult<bool> {
match PathfindingMaps::new(&wow_dir, &out_dir) {
Ok(maps) => {
let data_path = PathBuf::from(wow_dir);
let output = PathBuf::from(out_dir);

println!("Building maps...");
println!("Source: {:?}", data_path);
println!("Output: {:?}", output);

let threads = std::thread::available_parallelism()
.map(|n| n.get().saturating_sub(2).max(1) as u32)
.unwrap_or(1);

if !bvh_files_exist(&output).map_err(|e| rustler::Error::Term(Box::new(e.to_string())))? {
println!("Building BVH...");
build_bvh(&data_path, &output, threads)
.map_err(|e| rustler::Error::Term(Box::new(e.to_string())))?;
} else {
println!("BVH already built, skipping...");
}

const MAPS_TO_PROCESS: &[Map] = &[
Map::EasternKingdoms,
Map::Kalimdor,
Map::DevelopmentLand,
// TODO: add all
];

for &map in MAPS_TO_PROCESS {
let map_file_name = map.directory_name();

if !map_files_exist(&output, map_file_name)
.map_err(|e| rustler::Error::Term(Box::new(e.to_string())))?
{
println!("Building {map} [{map_file_name}]...");
build_map(&data_path, &output, map_file_name, "", threads)
.map_err(|e| rustler::Error::Term(Box::new(e.to_string())))?;
} else {
println!("{map} ({map_file_name}) already built, skipping...");
}
}

Ok(true)
}

#[rustler::nif(schedule = "DirtyCpu")]
fn load(out_dir: String) -> NifResult<bool> {
let mut maps = PathfindingMaps::new();
match maps.load(&out_dir) {
Ok(()) => {
let mut global_maps = PATHFINDING_MAPS.lock().unwrap();
*global_maps = Some(maps);
println!("PathfindingMaps built successfully");
Ok(true)
}
Err(e) => {
eprintln!("Error building PathfindingMaps: {}", e);
Ok(false)
}
Err(_) => Ok(false),
}
}

#[rustler::nif]
fn get_zone_and_area(map_id: u32, x: f32, y: f32, z: f32) -> NifResult<Option<(u32, u32)>> {
let global_maps = PATHFINDING_MAPS.lock().unwrap();
match &*global_maps {
let mut global_maps = PATHFINDING_MAPS.lock().unwrap();
match &mut *global_maps {
Some(maps) => match maps.get_zone_and_area(map_id, x, y, z) {
Ok((zone, area)) => Ok(Some((zone, area))),
Err(_) => Ok(None),
Expand All @@ -142,8 +155,8 @@ fn find_random_point_around_circle(
z: f32,
radius: f32,
) -> NifResult<Option<(f32, f32, f32)>> {
let global_maps = PATHFINDING_MAPS.lock().unwrap();
match &*global_maps {
let mut global_maps = PATHFINDING_MAPS.lock().unwrap();
match &mut *global_maps {
Some(maps) => {
let start = Vector3d { x, y, z };
match maps.find_random_point_around_circle(map_id, start, radius) {
Expand Down

0 comments on commit 86bffa2

Please sign in to comment.