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",
|
||||
"fxhash",
|
||||
"hashbrown 0.12.3",
|
||||
"itertools",
|
||||
"rand 0.8.5",
|
||||
"rmp-serde",
|
||||
"ron 0.8.0",
|
||||
|
@ -45,6 +45,15 @@ impl<T> PathResult<T> {
|
||||
_ => 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)]
|
||||
|
@ -19,7 +19,7 @@ use vek::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Path<T> {
|
||||
nodes: Vec<T>,
|
||||
pub nodes: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for Path<T> {
|
||||
|
@ -17,4 +17,5 @@ tracing = "0.1"
|
||||
atomic_refcell = "0.1"
|
||||
slotmap = { version = "1.0.6", features = ["serde"] }
|
||||
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 vek::*;
|
||||
use rand::prelude::*;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{ops::{Deref, DerefMut}, collections::VecDeque};
|
||||
use common::{
|
||||
uid::Uid,
|
||||
store::Id,
|
||||
rtsim::{SiteId, FactionId, RtSimController},
|
||||
comp,
|
||||
};
|
||||
use world::util::RandomPerm;
|
||||
use world::{util::RandomPerm, civ::Track};
|
||||
use world::site::Site as WorldSite;
|
||||
pub use common::rtsim::{NpcId, Profession};
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
@ -22,6 +23,19 @@ pub enum NpcMode {
|
||||
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)]
|
||||
pub struct Npc {
|
||||
// Persisted state
|
||||
@ -35,6 +49,11 @@ pub struct Npc {
|
||||
pub faction: Option<FactionId>,
|
||||
|
||||
// Unpersisted state
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub pathing: PathingMemory,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub current_site: Option<SiteId>,
|
||||
|
||||
/// (wpos, speed_factor)
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
@ -57,6 +76,8 @@ impl Npc {
|
||||
profession: None,
|
||||
home: None,
|
||||
faction: None,
|
||||
pathing: Default::default(),
|
||||
current_site: None,
|
||||
target: None,
|
||||
mode: NpcMode::Simulated,
|
||||
}
|
||||
|
@ -38,11 +38,19 @@ impl Site {
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Sites {
|
||||
pub sites: HopSlotMap<SiteId, Site>,
|
||||
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub world_site_map: HashMap<Id<WorldSite>, SiteId>,
|
||||
}
|
||||
|
||||
impl Sites {
|
||||
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 {
|
||||
nature: Nature::generate(world),
|
||||
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() },
|
||||
|
||||
time_of_day: TimeOfDay(settings.start_time),
|
||||
@ -72,13 +72,14 @@ impl Data {
|
||||
|
||||
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||
.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,
|
||||
1 => Profession::Blacksmith,
|
||||
2 => Profession::Chef,
|
||||
3 => Profession::Alchemist,
|
||||
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));
|
||||
|
@ -1,14 +1,23 @@
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::{collections::VecDeque, hash::BuildHasherDefault};
|
||||
|
||||
use crate::{
|
||||
data::{npc::PathData, Sites},
|
||||
event::OnTick,
|
||||
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 rand::{seq::IteratorRandom, rngs::SmallRng, SeedableRng};
|
||||
use itertools::Itertools;
|
||||
use rand::seq::IteratorRandom;
|
||||
use vek::*;
|
||||
use world::{
|
||||
civ::{self, Track},
|
||||
site::{Site as WorldSite, SiteKind},
|
||||
site2::{self, TileKind},
|
||||
IndexRef, World,
|
||||
@ -33,7 +42,7 @@ const CARDINALS: &[Vec2<i32>] = &[
|
||||
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 mut astar = Astar::new(
|
||||
100,
|
||||
@ -51,8 +60,7 @@ fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
||||
TileKind::Empty => 5.0,
|
||||
TileKind::Hazard(_) => 20.0,
|
||||
TileKind::Field => 12.0,
|
||||
TileKind::Plaza
|
||||
| TileKind::Road { .. } => 1.0,
|
||||
TileKind::Plaza | TileKind::Road { .. } => 1.0,
|
||||
|
||||
TileKind::Building
|
||||
| TileKind::Castle
|
||||
@ -62,17 +70,21 @@ fn path_between(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
||||
| 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 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)
|
||||
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)
|
||||
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 {
|
||||
@ -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(
|
||||
wpos: Vec3<f32>,
|
||||
site: Id<WorldSite>,
|
||||
index: IndexRef,
|
||||
time: f64,
|
||||
seed: u32,
|
||||
world: &World,
|
||||
) -> Option<(Vec3<f32>, f32)> {
|
||||
end: impl FnOnce(&site2::Site) -> Option<Vec2<i32>>,
|
||||
) -> Option<PathData<Vec2<i32>, Vec2<i32>>> {
|
||||
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();
|
||||
let end = end(site)?;
|
||||
|
||||
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);
|
||||
// We pop the first element of the path
|
||||
fn pop_first<T>(mut queue: VecDeque<T>) -> VecDeque<T> {
|
||||
queue.pop_front();
|
||||
queue
|
||||
}
|
||||
|
||||
let wpos = site.tile_center_wpos(next_tile);
|
||||
let wpos = wpos.as_::<f32>().with_z(world.sim().get_alt_approx(wpos).unwrap_or(0.0));
|
||||
|
||||
Some((wpos, 1.0))
|
||||
match path_in_site(start, end, site) {
|
||||
PathResult::Path(p) => Some(PathData {
|
||||
end,
|
||||
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
|
||||
@ -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 {
|
||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||
rtstate.bind::<Self, OnTick>(|ctx| {
|
||||
let data = &mut *ctx.state.data_mut();
|
||||
let mut dynamic_rng = rand::thread_rng();
|
||||
for npc in data.npcs.values_mut() {
|
||||
if let Some(home_id) = npc
|
||||
.home
|
||||
.and_then(|site_id| data.sites.get(site_id)?.world_site)
|
||||
{
|
||||
if let Some(home_id) = npc.home {
|
||||
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;
|
||||
}
|
||||
} 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 {
|
||||
// TODO: Don't make homeless people walk around in circles
|
||||
|
@ -1,3 +1,4 @@
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use tracing::info;
|
||||
use vek::*;
|
||||
use crate::{
|
||||
@ -12,8 +13,8 @@ impl Rule for SimulateNpcs {
|
||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||
|
||||
rtstate.bind::<Self, OnTick>(|ctx| {
|
||||
for npc in ctx.state
|
||||
.data_mut()
|
||||
let data = &mut *ctx.state.data_mut();
|
||||
for npc in data
|
||||
.npcs
|
||||
.values_mut()
|
||||
.filter(|npc| matches!(npc.mode, NpcMode::Simulated))
|
||||
@ -35,6 +36,9 @@ impl Rule for SimulateNpcs {
|
||||
npc.wpos.z = ctx.world.sim()
|
||||
.get_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||
.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",
|
||||
1 => "common.entity.world.traveler1",
|
||||
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::Chef => "common.entity.village.chef",
|
||||
|
@ -628,13 +628,12 @@ impl Civs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the direct track between two places
|
||||
pub fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<Id<Track>> {
|
||||
/// 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>, bool)> {
|
||||
self.track_map
|
||||
.get(&a)
|
||||
.and_then(|dests| dests.get(&b))
|
||||
.or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a)))
|
||||
.copied()
|
||||
.and_then(|dests| Some((*dests.get(&b)?, false)))
|
||||
.or_else(|| self.track_map.get(&b).and_then(|dests| Some((*dests.get(&a)?, true))))
|
||||
}
|
||||
|
||||
/// Return an iterator over a site's neighbors
|
||||
@ -665,7 +664,7 @@ impl Civs {
|
||||
};
|
||||
let neighbors = |p: &Id<Site>| self.neighbors(*p);
|
||||
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;
|
||||
// We use this hasher (FxHasher64) because
|
||||
// (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
|
||||
/// arbitrary unit and doesn't make sense unless compared to other track
|
||||
/// costs.
|
||||
cost: f32,
|
||||
pub cost: f32,
|
||||
path: Path<Vec2<i32>>,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user