site pathing

This commit is contained in:
IsseW 2022-08-14 17:17:42 +02:00 committed by Joshua Barretto
parent f40cfb4ac3
commit afd9ea5462
7 changed files with 137 additions and 40 deletions

1
Cargo.lock generated
View File

@ -6948,6 +6948,7 @@ dependencies = [
"anymap2", "anymap2",
"atomic_refcell", "atomic_refcell",
"enum-map", "enum-map",
"fxhash",
"hashbrown 0.12.3", "hashbrown 0.12.3",
"rand 0.8.5", "rand 0.8.5",
"rmp-serde", "rmp-serde",

View File

@ -17,3 +17,4 @@ tracing = "0.1"
atomic_refcell = "0.1" atomic_refcell = "0.1"
slotmap = { version = "1.0.6", features = ["serde"] } slotmap = { version = "1.0.6", features = ["serde"] }
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
fxhash = "0.2.1"

View File

@ -1,11 +1,131 @@
use crate::{data::npc::NpcMode, event::OnTick, RtState, Rule, RuleError}; use std::hash::BuildHasherDefault;
use rand::seq::IteratorRandom;
use tracing::info; use crate::{
event::OnTick,
RtState, Rule, RuleError,
};
use common::{astar::{Astar, PathResult}, store::Id};
use fxhash::FxHasher64;
use rand::{seq::IteratorRandom, rngs::SmallRng, SeedableRng};
use vek::*; use vek::*;
use world::site::SiteKind; use world::{
site::{Site as WorldSite, SiteKind},
site2::{self, TileKind},
IndexRef,
};
pub struct NpcAi; pub struct NpcAi;
const NEIGHBOURS: &[Vec2<i32>] = &[
Vec2::new(1, 0),
Vec2::new(0, 1),
Vec2::new(-1, 0),
Vec2::new(0, -1),
Vec2::new(1, 1),
Vec2::new(-1, 1),
Vec2::new(-1, -1),
Vec2::new(1, -1),
];
const CARDINALS: &[Vec2<i32>] = &[
Vec2::new(1, 0),
Vec2::new(0, 1),
Vec2::new(-1, 0),
Vec2::new(0, -1),
];
fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
let heuristic = |tile: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
let mut astar = Astar::new(
100,
start,
&heuristic,
BuildHasherDefault::<FxHasher64>::default(),
);
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| {
let distance = a.as_::<f32>().distance(b.as_());
let a_tile = site.tiles.get(*a);
let b_tile = site.tiles.get(*b);
let terrain = match &b_tile.kind {
TileKind::Empty => 5.0,
TileKind::Hazard(_) => 20.0,
TileKind::Field => 12.0,
TileKind::Plaza
| TileKind::Road { .. } => 1.0,
TileKind::Building
| TileKind::Castle
| TileKind::Wall(_)
| TileKind::Tower(_)
| TileKind::Keep(_)
| TileKind::Gate
| TileKind::GnarlingFortification => 3.0,
};
let is_door_tile = |plot: Id<site2::Plot>, tile: Vec2<i32>| {
match site.plot(plot).kind() {
site2::PlotKind::House(house) => house.door_tile == tile,
site2::PlotKind::Workshop(_) => true,
_ => false,
}
};
let building = if a_tile.is_building() && b_tile.is_road() {
a_tile.plot.and_then(|plot| is_door_tile(plot, *a).then(|| 1.0)).unwrap_or(f32::INFINITY)
} else if b_tile.is_building() && a_tile.is_road() {
b_tile.plot.and_then(|plot| is_door_tile(plot, *b).then(|| 1.0)).unwrap_or(f32::INFINITY)
} else if (a_tile.is_building() || b_tile.is_building()) && a_tile.plot != b_tile.plot {
f32::INFINITY
} else {
1.0
};
distance * terrain * building
};
astar.poll(
100,
heuristic,
|&tile| NEIGHBOURS.iter().map(move |c| tile + *c),
transition,
|tile| *tile == end,
)
}
fn path_town(
wpos: Vec3<f32>,
site: Id<WorldSite>,
index: IndexRef,
time: f64,
seed: u32,
) -> Option<(Vec3<f32>, f32)> {
match &index.sites.get(site).kind {
SiteKind::Refactor(site) | SiteKind::CliffTown(site) | SiteKind::DesertCity(site) => {
let start = site.wpos_tile_pos(wpos.xy().as_());
let mut rng = SmallRng::from_seed([(time / 3.0) as u8 ^ seed as u8; 32]);
let end = site.plots[site.plazas().choose(&mut rng)?].root_tile();
if start == end {
return None;
}
let next_tile = match path_between(start, end, site) {
PathResult::None(p) | PathResult::Exhausted(p) | PathResult::Path(p) => p.into_iter().nth(2),
PathResult::Pending => None,
}.unwrap_or(end);
let wpos = site.tile_center_wpos(next_tile).as_().with_z(wpos.z);
Some((wpos, 1.0))
},
_ => {
// No brain T_T
None
},
}
}
impl Rule for NpcAi { impl Rule for NpcAi {
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> { fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
rtstate.bind::<Self, OnTick>(|ctx| { rtstate.bind::<Self, OnTick>(|ctx| {
@ -16,37 +136,11 @@ impl Rule for NpcAi {
.and_then(|site_id| data.sites.get(site_id)?.world_site) .and_then(|site_id| data.sites.get(site_id)?.world_site)
{ {
if let Some((target, _)) = npc.target { if let Some((target, _)) = npc.target {
if target.distance_squared(npc.wpos) < 1.0 { if target.xy().distance_squared(npc.wpos.xy()) < 1.0 {
npc.target = None; npc.target = None;
} }
} else { } else {
match &ctx.index.sites.get(home_id).kind { npc.target = path_town(npc.wpos, home_id, ctx.index, ctx.event.time, npc.seed);
SiteKind::Refactor(site)
| SiteKind::CliffTown(site)
| SiteKind::DesertCity(site) => {
let tile = site.wpos_tile_pos(npc.wpos.xy().as_());
let mut rng = rand::thread_rng();
let cardinals = [
Vec2::unit_x(),
Vec2::unit_y(),
-Vec2::unit_x(),
-Vec2::unit_y(),
];
let next_tile = cardinals
.iter()
.map(|c| tile + *c)
.filter(|tile| site.tiles.get(*tile).is_road()).choose(&mut rng).unwrap_or(tile);
let wpos =
site.tile_center_wpos(next_tile).as_().with_z(npc.wpos.z);
npc.target = Some((wpos, 1.0));
},
_ => {
// No brain T_T
},
}
} }
} else { } else {
// TODO: Don't make homeless people walk around in circles // TODO: Don't make homeless people walk around in circles

View File

@ -292,6 +292,7 @@ impl StateExt for State {
.with(comp::Combo::default()) .with(comp::Combo::default())
.with(comp::Auras::default()) .with(comp::Auras::default())
.with(comp::Stance::default()) .with(comp::Stance::default())
.with(RepositionOnChunkLoad)
} }
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {

View File

@ -3,11 +3,11 @@ pub mod plot;
mod tile; mod tile;
pub mod util; pub mod util;
use self::tile::{HazardKind, KeepKind, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE}; use self::tile::{HazardKind, KeepKind, RoofKind, Tile, TileGrid, TILE_SIZE};
pub use self::{ pub use self::{
gen::{aabr_with_z, Fill, Painter, Primitive, PrimitiveRef, Structure}, gen::{aabr_with_z, Fill, Painter, Primitive, PrimitiveRef, Structure},
plot::{Plot, PlotKind}, plot::{Plot, PlotKind},
util::Dir, util::Dir, tile::TileKind,
}; };
use crate::{ use crate::{
sim::Path, sim::Path,
@ -40,7 +40,7 @@ fn reseed(rng: &mut impl Rng) -> impl Rng { ChaChaRng::from_seed(rng.gen::<[u8;
#[derive(Default)] #[derive(Default)]
pub struct Site { pub struct Site {
pub(crate) origin: Vec2<i32>, pub origin: Vec2<i32>,
name: String, name: String,
// NOTE: Do we want these to be public? // NOTE: Do we want these to be public?
pub tiles: TileGrid, pub tiles: TileGrid,
@ -110,8 +110,8 @@ impl Site {
pub fn bounds(&self) -> Aabr<i32> { pub fn bounds(&self) -> Aabr<i32> {
let border = 1; let border = 1;
Aabr { Aabr {
min: self.origin + self.tile_wpos(self.tiles.bounds.min - border), min: self.tile_wpos(self.tiles.bounds.min - border),
max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + border), max: self.tile_wpos(self.tiles.bounds.max + 1 + border),
} }
} }

View File

@ -11,7 +11,7 @@ use vek::*;
/// Represents house data generated by the `generate()` method /// Represents house data generated by the `generate()` method
pub struct House { pub struct House {
/// Tile position of the door tile /// Tile position of the door tile
door_tile: Vec2<i32>, pub door_tile: Vec2<i32>,
/// Axis aligned bounding region of tiles /// Axis aligned bounding region of tiles
tile_aabr: Aabr<i32>, tile_aabr: Aabr<i32>,
/// Axis aligned bounding region for the house /// Axis aligned bounding region for the house

View File

@ -193,8 +193,8 @@ pub enum TileKind {
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Tile { pub struct Tile {
pub(crate) kind: TileKind, pub kind: TileKind,
pub(crate) plot: Option<Id<Plot>>, pub plot: Option<Id<Plot>>,
pub(crate) hard_alt: Option<i32>, pub(crate) hard_alt: Option<i32>,
} }