mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Pathing between sites.
This commit is contained in:
parent
feaaaa9a25
commit
9be6c7b527
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6950,6 +6950,7 @@ dependencies = [
|
|||||||
"enum-map",
|
"enum-map",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
"itertools",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
"ron 0.8.0",
|
"ron 0.8.0",
|
||||||
|
@ -45,6 +45,15 @@ impl<T> PathResult<T> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map<U>(self, f: impl FnOnce(Path<T>) -> Path<U>) -> PathResult<U> {
|
||||||
|
match self {
|
||||||
|
PathResult::None(p) => PathResult::None(f(p)),
|
||||||
|
PathResult::Exhausted(p) => PathResult::Exhausted(f(p)),
|
||||||
|
PathResult::Path(p) => PathResult::Path(f(p)),
|
||||||
|
PathResult::Pending => PathResult::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -19,7 +19,7 @@ use vek::*;
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Path<T> {
|
pub struct Path<T> {
|
||||||
nodes: Vec<T>,
|
pub nodes: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for Path<T> {
|
impl<T> Default for Path<T> {
|
||||||
|
@ -17,4 +17,5 @@ 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"
|
fxhash = "0.2.1"
|
||||||
|
itertools = "0.10.3"
|
@ -3,14 +3,15 @@ use serde::{Serialize, Deserialize};
|
|||||||
use slotmap::HopSlotMap;
|
use slotmap::HopSlotMap;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::{ops::{Deref, DerefMut}, collections::VecDeque};
|
||||||
use common::{
|
use common::{
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
store::Id,
|
store::Id,
|
||||||
rtsim::{SiteId, FactionId, RtSimController},
|
rtsim::{SiteId, FactionId, RtSimController},
|
||||||
comp,
|
comp,
|
||||||
};
|
};
|
||||||
use world::util::RandomPerm;
|
use world::{util::RandomPerm, civ::Track};
|
||||||
|
use world::site::Site as WorldSite;
|
||||||
pub use common::rtsim::{NpcId, Profession};
|
pub use common::rtsim::{NpcId, Profession};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
@ -22,6 +23,19 @@ pub enum NpcMode {
|
|||||||
Loaded,
|
Loaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PathData<P, N> {
|
||||||
|
pub end: N,
|
||||||
|
pub path: VecDeque<P>,
|
||||||
|
pub repoll: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct PathingMemory {
|
||||||
|
pub intrasite_path: Option<(PathData<Vec2<i32>, Vec2<i32>>, Id<WorldSite>)>,
|
||||||
|
pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Npc {
|
pub struct Npc {
|
||||||
// Persisted state
|
// Persisted state
|
||||||
@ -35,6 +49,11 @@ pub struct Npc {
|
|||||||
pub faction: Option<FactionId>,
|
pub faction: Option<FactionId>,
|
||||||
|
|
||||||
// Unpersisted state
|
// Unpersisted state
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub pathing: PathingMemory,
|
||||||
|
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub current_site: Option<SiteId>,
|
||||||
|
|
||||||
/// (wpos, speed_factor)
|
/// (wpos, speed_factor)
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@ -57,6 +76,8 @@ impl Npc {
|
|||||||
profession: None,
|
profession: None,
|
||||||
home: None,
|
home: None,
|
||||||
faction: None,
|
faction: None,
|
||||||
|
pathing: Default::default(),
|
||||||
|
current_site: None,
|
||||||
target: None,
|
target: None,
|
||||||
mode: NpcMode::Simulated,
|
mode: NpcMode::Simulated,
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,19 @@ impl Site {
|
|||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Sites {
|
pub struct Sites {
|
||||||
pub sites: HopSlotMap<SiteId, Site>,
|
pub sites: HopSlotMap<SiteId, Site>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub world_site_map: HashMap<Id<WorldSite>, SiteId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sites {
|
impl Sites {
|
||||||
pub fn create(&mut self, site: Site) -> SiteId {
|
pub fn create(&mut self, site: Site) -> SiteId {
|
||||||
self.sites.insert(site)
|
let world_site = site.world_site;
|
||||||
|
let key = self.sites.insert(site);
|
||||||
|
if let Some(world_site) = world_site {
|
||||||
|
self.world_site_map.insert(world_site, key);
|
||||||
|
}
|
||||||
|
key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ impl Data {
|
|||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
nature: Nature::generate(world),
|
nature: Nature::generate(world),
|
||||||
npcs: Npcs { npcs: Default::default() },
|
npcs: Npcs { npcs: Default::default() },
|
||||||
sites: Sites { sites: Default::default() },
|
sites: Sites { sites: Default::default(), world_site_map: Default::default() },
|
||||||
factions: Factions { factions: Default::default() },
|
factions: Factions { factions: Default::default() },
|
||||||
|
|
||||||
time_of_day: TimeOfDay(settings.start_time),
|
time_of_day: TimeOfDay(settings.start_time),
|
||||||
@ -72,13 +72,14 @@ impl Data {
|
|||||||
|
|
||||||
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng))
|
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||||
.with_faction(site.faction)
|
.with_faction(site.faction)
|
||||||
.with_home(site_id).with_profession(match rng.gen_range(0..15) {
|
.with_home(site_id).with_profession(match rng.gen_range(0..20) {
|
||||||
0 => Profession::Hunter,
|
0 => Profession::Hunter,
|
||||||
1 => Profession::Blacksmith,
|
1 => Profession::Blacksmith,
|
||||||
2 => Profession::Chef,
|
2 => Profession::Chef,
|
||||||
3 => Profession::Alchemist,
|
3 => Profession::Alchemist,
|
||||||
5..=10 => Profession::Farmer,
|
5..=10 => Profession::Farmer,
|
||||||
_ => Profession::Guard,
|
11..=15 => Profession::Guard,
|
||||||
|
_ => Profession::Adventurer(rng.gen_range(0..=3)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng)).with_home(site_id).with_profession(Profession::Merchant));
|
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng)).with_home(site_id).with_profession(Profession::Merchant));
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
use std::hash::BuildHasherDefault;
|
use std::{collections::VecDeque, hash::BuildHasherDefault};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
data::{npc::PathData, Sites},
|
||||||
event::OnTick,
|
event::OnTick,
|
||||||
RtState, Rule, RuleError,
|
RtState, Rule, RuleError,
|
||||||
};
|
};
|
||||||
use common::{astar::{Astar, PathResult}, store::Id};
|
use common::{
|
||||||
|
astar::{Astar, PathResult},
|
||||||
|
path::Path,
|
||||||
|
rtsim::{Profession, SiteId},
|
||||||
|
store::Id,
|
||||||
|
terrain::TerrainChunkSize,
|
||||||
|
};
|
||||||
use fxhash::FxHasher64;
|
use fxhash::FxHasher64;
|
||||||
use rand::{seq::IteratorRandom, rngs::SmallRng, SeedableRng};
|
use itertools::Itertools;
|
||||||
|
use rand::seq::IteratorRandom;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{
|
use world::{
|
||||||
|
civ::{self, Track},
|
||||||
site::{Site as WorldSite, SiteKind},
|
site::{Site as WorldSite, SiteKind},
|
||||||
site2::{self, TileKind},
|
site2::{self, TileKind},
|
||||||
IndexRef, World,
|
IndexRef, World,
|
||||||
@ -33,7 +42,7 @@ const CARDINALS: &[Vec2<i32>] = &[
|
|||||||
Vec2::new(0, -1),
|
Vec2::new(0, -1),
|
||||||
];
|
];
|
||||||
|
|
||||||
fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
|
fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
|
||||||
let heuristic = |tile: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
|
let heuristic = |tile: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
|
||||||
let mut astar = Astar::new(
|
let mut astar = Astar::new(
|
||||||
100,
|
100,
|
||||||
@ -51,8 +60,7 @@ fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
|||||||
TileKind::Empty => 5.0,
|
TileKind::Empty => 5.0,
|
||||||
TileKind::Hazard(_) => 20.0,
|
TileKind::Hazard(_) => 20.0,
|
||||||
TileKind::Field => 12.0,
|
TileKind::Field => 12.0,
|
||||||
TileKind::Plaza
|
TileKind::Plaza | TileKind::Road { .. } => 1.0,
|
||||||
| TileKind::Road { .. } => 1.0,
|
|
||||||
|
|
||||||
TileKind::Building
|
TileKind::Building
|
||||||
| TileKind::Castle
|
| TileKind::Castle
|
||||||
@ -62,17 +70,21 @@ fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
|||||||
| TileKind::Gate
|
| TileKind::Gate
|
||||||
| TileKind::GnarlingFortification => 3.0,
|
| TileKind::GnarlingFortification => 3.0,
|
||||||
};
|
};
|
||||||
let is_door_tile = |plot: Id<site2::Plot>, tile: Vec2<i32>| {
|
let is_door_tile = |plot: Id<site2::Plot>, tile: Vec2<i32>| match site.plot(plot).kind() {
|
||||||
match site.plot(plot).kind() {
|
site2::PlotKind::House(house) => house.door_tile == tile,
|
||||||
site2::PlotKind::House(house) => house.door_tile == tile,
|
site2::PlotKind::Workshop(_) => true,
|
||||||
site2::PlotKind::Workshop(_) => true,
|
_ => false,
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let building = if a_tile.is_building() && b_tile.is_road() {
|
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)
|
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() {
|
} 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)
|
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 {
|
} else if (a_tile.is_building() || b_tile.is_building()) && a_tile.plot != b_tile.plot {
|
||||||
f32::INFINITY
|
f32::INFINITY
|
||||||
} else {
|
} else {
|
||||||
@ -91,35 +103,98 @@ fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_between_sites(
|
||||||
|
start: SiteId,
|
||||||
|
end: SiteId,
|
||||||
|
sites: &Sites,
|
||||||
|
world: &World,
|
||||||
|
) -> PathResult<(Id<Track>, bool)> {
|
||||||
|
let world_site = |site_id: SiteId| {
|
||||||
|
let id = sites.get(site_id).and_then(|site| site.world_site)?;
|
||||||
|
world.civs().sites.recreate_id(id.id())
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = if let Some(start) = world_site(start) {
|
||||||
|
start
|
||||||
|
} else {
|
||||||
|
return PathResult::Pending;
|
||||||
|
};
|
||||||
|
let end = if let Some(end) = world_site(end) {
|
||||||
|
end
|
||||||
|
} else {
|
||||||
|
return PathResult::Pending;
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_site = |site: &Id<civ::Site>| world.civs().sites.get(*site);
|
||||||
|
|
||||||
|
let end_pos = get_site(&end).center.as_::<f32>();
|
||||||
|
let heuristic = |site: &Id<civ::Site>| get_site(site).center.as_().distance(end_pos);
|
||||||
|
|
||||||
|
let mut astar = Astar::new(
|
||||||
|
100,
|
||||||
|
start,
|
||||||
|
heuristic,
|
||||||
|
BuildHasherDefault::<FxHasher64>::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let neighbors = |site: &Id<civ::Site>| world.civs().neighbors(*site);
|
||||||
|
|
||||||
|
let track_between = |a: Id<civ::Site>, b: Id<civ::Site>| {
|
||||||
|
world
|
||||||
|
.civs()
|
||||||
|
.tracks
|
||||||
|
.get(world.civs().track_between(a, b).unwrap().0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let transition = |a: &Id<civ::Site>, b: &Id<civ::Site>| track_between(*a, *b).cost;
|
||||||
|
|
||||||
|
let path = astar.poll(100, heuristic, neighbors, transition, |site| *site == end);
|
||||||
|
|
||||||
|
path.map(|path| {
|
||||||
|
let path = path
|
||||||
|
.into_iter()
|
||||||
|
.tuple_windows::<(_, _)>()
|
||||||
|
.map(|(a, b)| world.civs().track_between(a, b).unwrap())
|
||||||
|
.collect_vec();
|
||||||
|
Path { nodes: path }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn path_town(
|
fn path_town(
|
||||||
wpos: Vec3<f32>,
|
wpos: Vec3<f32>,
|
||||||
site: Id<WorldSite>,
|
site: Id<WorldSite>,
|
||||||
index: IndexRef,
|
index: IndexRef,
|
||||||
time: f64,
|
end: impl FnOnce(&site2::Site) -> Option<Vec2<i32>>,
|
||||||
seed: u32,
|
) -> Option<PathData<Vec2<i32>, Vec2<i32>>> {
|
||||||
world: &World,
|
|
||||||
) -> Option<(Vec3<f32>, f32)> {
|
|
||||||
match &index.sites.get(site).kind {
|
match &index.sites.get(site).kind {
|
||||||
SiteKind::Refactor(site) | SiteKind::CliffTown(site) | SiteKind::DesertCity(site) => {
|
SiteKind::Refactor(site) | SiteKind::CliffTown(site) | SiteKind::DesertCity(site) => {
|
||||||
let start = site.wpos_tile_pos(wpos.xy().as_());
|
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 = end(site)?;
|
||||||
|
|
||||||
let end = site.plots[site.plazas().choose(&mut rng)?].root_tile();
|
|
||||||
|
|
||||||
if start == end {
|
if start == end {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_tile = match path_between(start, end, site) {
|
// We pop the first element of the path
|
||||||
PathResult::None(p) | PathResult::Exhausted(p) | PathResult::Path(p) => p.into_iter().nth(2),
|
fn pop_first<T>(mut queue: VecDeque<T>) -> VecDeque<T> {
|
||||||
PathResult::Pending => None,
|
queue.pop_front();
|
||||||
}.unwrap_or(end);
|
queue
|
||||||
|
}
|
||||||
|
|
||||||
let wpos = site.tile_center_wpos(next_tile);
|
match path_in_site(start, end, site) {
|
||||||
let wpos = wpos.as_::<f32>().with_z(world.sim().get_alt_approx(wpos).unwrap_or(0.0));
|
PathResult::Path(p) => Some(PathData {
|
||||||
|
end,
|
||||||
Some((wpos, 1.0))
|
path: pop_first(p.nodes.into()),
|
||||||
|
repoll: false,
|
||||||
|
}),
|
||||||
|
PathResult::Exhausted(p) => Some(PathData {
|
||||||
|
end,
|
||||||
|
path: pop_first(p.nodes.into()),
|
||||||
|
repoll: true,
|
||||||
|
}),
|
||||||
|
PathResult::None(_) | PathResult::Pending => None,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
// No brain T_T
|
// No brain T_T
|
||||||
@ -128,21 +203,213 @@ fn path_town(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_towns(
|
||||||
|
start: SiteId,
|
||||||
|
end: SiteId,
|
||||||
|
sites: &Sites,
|
||||||
|
world: &World,
|
||||||
|
) -> Option<(PathData<(Id<Track>, bool), SiteId>, usize)> {
|
||||||
|
match path_between_sites(start, end, sites, world) {
|
||||||
|
PathResult::Exhausted(p) => Some((
|
||||||
|
PathData {
|
||||||
|
end,
|
||||||
|
path: p.nodes.into(),
|
||||||
|
repoll: true,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)),
|
||||||
|
PathResult::Path(p) => Some((
|
||||||
|
PathData {
|
||||||
|
end,
|
||||||
|
path: p.nodes.into(),
|
||||||
|
repoll: false,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)),
|
||||||
|
PathResult::Pending | PathResult::None(_) => 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| {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
let mut dynamic_rng = rand::thread_rng();
|
||||||
for npc in data.npcs.values_mut() {
|
for npc in data.npcs.values_mut() {
|
||||||
if let Some(home_id) = npc
|
if let Some(home_id) = npc.home {
|
||||||
.home
|
|
||||||
.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.xy().distance_squared(npc.wpos.xy()) < 1.0 {
|
// Walk to the current target
|
||||||
|
if target.xy().distance_squared(npc.wpos.xy()) < 4.0 {
|
||||||
npc.target = None;
|
npc.target = None;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
npc.target = path_town(npc.wpos, home_id, ctx.index, ctx.event.time.0, npc.seed, ctx.world);
|
if let Some((ref mut path, site)) = npc.pathing.intrasite_path {
|
||||||
|
// If the npc walking in a site and want to reroll (because the path was
|
||||||
|
// exhausted.) to try to find a complete path.
|
||||||
|
if path.repoll {
|
||||||
|
npc.pathing.intrasite_path =
|
||||||
|
path_town(npc.wpos, site, ctx.index, |_| Some(path.end))
|
||||||
|
.map(|path| (path, site));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((ref mut path, site)) = npc.pathing.intrasite_path {
|
||||||
|
if let Some(next_tile) = path.path.pop_front() {
|
||||||
|
match &ctx.index.sites.get(site).kind {
|
||||||
|
SiteKind::Refactor(site)
|
||||||
|
| SiteKind::CliffTown(site)
|
||||||
|
| SiteKind::DesertCity(site) => {
|
||||||
|
// Set the target to the next node in the path.
|
||||||
|
let wpos = site.tile_center_wpos(next_tile);
|
||||||
|
let wpos = wpos.as_::<f32>().with_z(
|
||||||
|
ctx.world.sim().get_alt_approx(wpos).unwrap_or(0.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
npc.target = Some((wpos, 1.0));
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the path is empty, we're done.
|
||||||
|
npc.pathing.intrasite_path = None;
|
||||||
|
}
|
||||||
|
} else if let Some((path, progress)) = {
|
||||||
|
// Check if we are done with this part of the inter site path.
|
||||||
|
if let Some((path, progress)) = &mut npc.pathing.intersite_path {
|
||||||
|
if let Some((track_id, _)) = path.path.front() {
|
||||||
|
let track = ctx.world.civs().tracks.get(*track_id);
|
||||||
|
if *progress >= track.path().len() {
|
||||||
|
if path.repoll {
|
||||||
|
// Repoll if last path wasn't complete.
|
||||||
|
npc.pathing.intersite_path = path_towns(
|
||||||
|
npc.current_site.unwrap(),
|
||||||
|
path.end,
|
||||||
|
&data.sites,
|
||||||
|
ctx.world,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise just take the next in the calculated path.
|
||||||
|
path.path.pop_front();
|
||||||
|
*progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&mut npc.pathing.intersite_path
|
||||||
|
} {
|
||||||
|
if let Some((track_id, reversed)) = path.path.front() {
|
||||||
|
let track = ctx.world.civs().tracks.get(*track_id);
|
||||||
|
let get_progress = |progress: usize| {
|
||||||
|
if *reversed {
|
||||||
|
track.path().len().wrapping_sub(progress + 1)
|
||||||
|
} else {
|
||||||
|
progress
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let transform_path_pos = |chunk_pos| {
|
||||||
|
let chunk_wpos = TerrainChunkSize::center_wpos(chunk_pos);
|
||||||
|
if let Some(pathdata) =
|
||||||
|
ctx.world.sim().get_nearest_path(chunk_wpos)
|
||||||
|
{
|
||||||
|
pathdata.1.map(|e| e as i32)
|
||||||
|
} else {
|
||||||
|
chunk_wpos
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop through and skip nodes that are inside a site, and use intra
|
||||||
|
// site path finding there instead.
|
||||||
|
let walk_path = loop {
|
||||||
|
if let Some(chunk_pos) =
|
||||||
|
track.path().nodes.get(get_progress(*progress))
|
||||||
|
{
|
||||||
|
if let Some((wpos, site_id, site)) =
|
||||||
|
ctx.world.sim().get(*chunk_pos).and_then(|chunk| {
|
||||||
|
let site_id = *chunk.sites.first()?;
|
||||||
|
let wpos = transform_path_pos(*chunk_pos);
|
||||||
|
match &ctx.index.sites.get(site_id).kind {
|
||||||
|
SiteKind::Refactor(site)
|
||||||
|
| SiteKind::CliffTown(site)
|
||||||
|
| SiteKind::DesertCity(site)
|
||||||
|
if !site.wpos_tile(wpos).is_empty() =>
|
||||||
|
{
|
||||||
|
Some((wpos, site_id, site))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if !site.wpos_tile(wpos).is_empty() {
|
||||||
|
*progress += 1;
|
||||||
|
} else {
|
||||||
|
let end = site.wpos_tile_pos(wpos);
|
||||||
|
npc.pathing.intrasite_path =
|
||||||
|
path_town(npc.wpos, site_id, ctx.index, |_| {
|
||||||
|
Some(end)
|
||||||
|
})
|
||||||
|
.map(|path| (path, site_id));
|
||||||
|
break false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if walk_path {
|
||||||
|
// Find the next wpos on the path.
|
||||||
|
// NOTE: Consider not having this big gap between current
|
||||||
|
// position and next. For better path finding. Maybe that would
|
||||||
|
// mean having a float for progress.
|
||||||
|
let wpos = transform_path_pos(
|
||||||
|
track.path().nodes[get_progress(*progress)],
|
||||||
|
);
|
||||||
|
let wpos = wpos.as_::<f32>().with_z(
|
||||||
|
ctx.world.sim().get_alt_approx(wpos).unwrap_or(0.0),
|
||||||
|
);
|
||||||
|
npc.target = Some((wpos, 1.0));
|
||||||
|
*progress += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
npc.pathing.intersite_path = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if matches!(npc.profession, Some(Profession::Adventurer(_))) {
|
||||||
|
// If the npc is home, choose a random site to go to, otherwise go
|
||||||
|
// home.
|
||||||
|
if let Some(start) = npc.current_site {
|
||||||
|
let end = if home_id == start {
|
||||||
|
data.sites
|
||||||
|
.keys()
|
||||||
|
.filter(|site| *site != home_id)
|
||||||
|
.choose(&mut dynamic_rng)
|
||||||
|
.unwrap_or(home_id)
|
||||||
|
} else {
|
||||||
|
home_id
|
||||||
|
};
|
||||||
|
npc.pathing.intersite_path =
|
||||||
|
path_towns(start, end, &data.sites, ctx.world);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Choose a random plaza in the npcs home site (which should be the
|
||||||
|
// current here) to go to.
|
||||||
|
if let Some(home_id) =
|
||||||
|
data.sites.get(home_id).and_then(|site| site.world_site)
|
||||||
|
{
|
||||||
|
npc.pathing.intrasite_path =
|
||||||
|
path_town(npc.wpos, home_id, ctx.index, |site| {
|
||||||
|
Some(
|
||||||
|
site.plots
|
||||||
|
[site.plazas().choose(&mut dynamic_rng)?]
|
||||||
|
.root_tile(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|path| (path, home_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Don't make homeless people walk around in circles
|
// TODO: Don't make homeless people walk around in circles
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -12,8 +13,8 @@ impl Rule for SimulateNpcs {
|
|||||||
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| {
|
||||||
for npc in ctx.state
|
let data = &mut *ctx.state.data_mut();
|
||||||
.data_mut()
|
for npc in data
|
||||||
.npcs
|
.npcs
|
||||||
.values_mut()
|
.values_mut()
|
||||||
.filter(|npc| matches!(npc.mode, NpcMode::Simulated))
|
.filter(|npc| matches!(npc.mode, NpcMode::Simulated))
|
||||||
@ -35,6 +36,9 @@ impl Rule for SimulateNpcs {
|
|||||||
npc.wpos.z = ctx.world.sim()
|
npc.wpos.z = ctx.world.sim()
|
||||||
.get_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
.get_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
|
npc.current_site = ctx.world.sim().get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_()).and_then(|chunk| {
|
||||||
|
data.sites.world_site_map.get(chunk.sites.first()?).copied()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,7 +28,8 @@ fn humanoid_config(profession: &Profession) -> &'static str {
|
|||||||
0 => "common.entity.world.traveler0",
|
0 => "common.entity.world.traveler0",
|
||||||
1 => "common.entity.world.traveler1",
|
1 => "common.entity.world.traveler1",
|
||||||
2 => "common.entity.world.traveler2",
|
2 => "common.entity.world.traveler2",
|
||||||
_ => "common.entity.world.traveler3",
|
3 => "common.entity.world.traveler3",
|
||||||
|
_ => panic!("Not a valid adventurer rank"),
|
||||||
},
|
},
|
||||||
Profession::Blacksmith => "common.entity.village.blacksmith",
|
Profession::Blacksmith => "common.entity.village.blacksmith",
|
||||||
Profession::Chef => "common.entity.village.chef",
|
Profession::Chef => "common.entity.village.chef",
|
||||||
|
@ -628,13 +628,12 @@ impl Civs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the direct track between two places
|
/// Return the direct track between two places, bool if the track should be reversed or not
|
||||||
pub fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
|
pub fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Id<Track>, bool)> {
|
||||||
self.track_map
|
self.track_map
|
||||||
.get(&a)
|
.get(&a)
|
||||||
.and_then(|dests| dests.get(&b))
|
.and_then(|dests| Some((*dests.get(&b)?, false)))
|
||||||
.or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a)))
|
.or_else(|| self.track_map.get(&b).and_then(|dests| Some((*dests.get(&a)?, true))))
|
||||||
.copied()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over a site's neighbors
|
/// Return an iterator over a site's neighbors
|
||||||
@ -665,7 +664,7 @@ impl Civs {
|
|||||||
};
|
};
|
||||||
let neighbors = |p: &Id<Site>| self.neighbors(*p);
|
let neighbors = |p: &Id<Site>| self.neighbors(*p);
|
||||||
let transition =
|
let transition =
|
||||||
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap().0).cost;
|
||||||
let satisfied = |p: &Id<Site>| *p == b;
|
let satisfied = |p: &Id<Site>| *p == b;
|
||||||
// We use this hasher (FxHasher64) because
|
// We use this hasher (FxHasher64) because
|
||||||
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||||
@ -1453,7 +1452,7 @@ pub struct Track {
|
|||||||
/// Cost of using this track relative to other paths. This cost is an
|
/// Cost of using this track relative to other paths. This cost is an
|
||||||
/// arbitrary unit and doesn't make sense unless compared to other track
|
/// arbitrary unit and doesn't make sense unless compared to other track
|
||||||
/// costs.
|
/// costs.
|
||||||
cost: f32,
|
pub cost: f32,
|
||||||
path: Path<Vec2<i32>>,
|
path: Path<Vec2<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user