mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'desttinghim/rtsim-path-following' into 'master'
RtSim pathfinding improvements See merge request veloren/veloren!2013
This commit is contained in:
commit
6656f1e4af
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Auto camera setting, making the game easier to play with one hand
|
||||
- Topographic map option
|
||||
- Search bars for social and crafting window
|
||||
- RTsim travellers now follow paths between towns
|
||||
|
||||
### Changed
|
||||
|
||||
|
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -2650,6 +2650,12 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.18.0"
|
||||
@ -3345,6 +3351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3587,7 +3594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3278e0492f961fd4ae70909f56b2723a7e8d01a228427294e19cdfdebda89a17"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libm",
|
||||
"libm 0.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3965,6 +3972,16 @@ dependencies = [
|
||||
"getrandom 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9e8f32ad24fb80d07d2323a9a2ce8b30d68a62b8cb4df88119ff49a698f038"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@ -5574,6 +5591,7 @@ dependencies = [
|
||||
"prometheus",
|
||||
"prometheus-hyper",
|
||||
"rand 0.8.3",
|
||||
"rand_distr",
|
||||
"rayon",
|
||||
"ron",
|
||||
"scan_fmt",
|
||||
|
@ -49,6 +49,7 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
dotenv = "0.15.0"
|
||||
slab = "0.4"
|
||||
rand_distr = "0.4.0"
|
||||
|
||||
# Plugins
|
||||
plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"}
|
||||
|
@ -6,6 +6,9 @@ use common::{
|
||||
store::Id,
|
||||
terrain::TerrainGrid,
|
||||
};
|
||||
use rand_distr::{Distribution, Normal};
|
||||
use std::f32::consts::PI;
|
||||
use tracing::warn;
|
||||
use world::{
|
||||
civ::{Site, Track},
|
||||
util::RandomPerm,
|
||||
@ -34,20 +37,19 @@ impl Entity {
|
||||
pub fn get_body(&self) -> comp::Body {
|
||||
match self.rng(PERM_GENUS).gen::<f32>() {
|
||||
// we want 5% airships, 45% birds, 50% humans
|
||||
// HUMANS TEMPORARILY DISABLED UNTIL PATHFINDING FIXED
|
||||
x if x < 0.10 => comp::Body::Ship(comp::ship::Body::DefaultAirship),
|
||||
_ => {
|
||||
x if x < 0.05 => comp::Body::Ship(comp::ship::Body::DefaultAirship),
|
||||
x if x < 0.50 => {
|
||||
let species = *(&comp::bird_medium::ALL_SPECIES)
|
||||
.choose(&mut self.rng(PERM_SPECIES))
|
||||
.unwrap();
|
||||
comp::bird_medium::Body::random_with(&mut self.rng(PERM_BODY), &species).into()
|
||||
},
|
||||
// _ => {
|
||||
// let species = *(&comp::humanoid::ALL_SPECIES)
|
||||
// .choose(&mut self.rng(PERM_SPECIES))
|
||||
// .unwrap();
|
||||
// comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into()
|
||||
// },
|
||||
_ => {
|
||||
let species = *(&comp::humanoid::ALL_SPECIES)
|
||||
.choose(&mut self.rng(PERM_SPECIES))
|
||||
.unwrap();
|
||||
comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,61 +135,361 @@ impl Entity {
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, time: &Time, terrain: &TerrainGrid, world: &World, index: &IndexRef) {
|
||||
let tgt_site = self.brain.tgt.or_else(|| {
|
||||
world
|
||||
.civs()
|
||||
.sites
|
||||
.iter()
|
||||
.filter(|s| match self.get_body() {
|
||||
comp::Body::Humanoid(_) => s.1.is_settlement() | s.1.is_castle(),
|
||||
comp::Body::Ship(_) => s.1.is_settlement(),
|
||||
_ => s.1.is_dungeon(),
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32;
|
||||
dist + if dist < 96 { 100_000 } else { 0 }
|
||||
})
|
||||
.map(|(id, _)| id)
|
||||
});
|
||||
self.brain.tgt = tgt_site;
|
||||
self.brain.route = match self.brain.route.clone() {
|
||||
Travel::Lost => {
|
||||
match self.get_body() {
|
||||
comp::Body::Humanoid(_) => {
|
||||
if let Some(nearest_site_id) = world
|
||||
.civs()
|
||||
.sites
|
||||
.iter()
|
||||
.filter(|s| s.1.is_settlement() || s.1.is_castle())
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32
|
||||
})
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
// The path choosing code works best when Humanoids can assume they are
|
||||
// in a town that has at least one path. If the Human isn't in a town
|
||||
// with at least one path, we need to get them to a town that does.
|
||||
let nearest_site = &world.civs().sites[nearest_site_id];
|
||||
let site_wpos =
|
||||
nearest_site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist =
|
||||
site_wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
if dist < 64_u32.pow(2) {
|
||||
Travel::InSite {
|
||||
site_id: nearest_site_id,
|
||||
}
|
||||
} else {
|
||||
Travel::Direct {
|
||||
target_id: nearest_site_id,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Somehow no nearest site could be found
|
||||
// Logically this should never happen, but if it does the rtsim entity
|
||||
// will just sit tight
|
||||
warn!("Nearest site could not be found");
|
||||
Travel::Lost
|
||||
}
|
||||
},
|
||||
comp::Body::Ship(_) => {
|
||||
if let Some((target_id, site)) = world
|
||||
.civs()
|
||||
.sites
|
||||
.iter()
|
||||
.filter(|s| match self.get_body() {
|
||||
comp::Body::Ship(_) => s.1.is_settlement(),
|
||||
_ => s.1.is_dungeon(),
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist =
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000_000 } else { 0 }
|
||||
})
|
||||
{
|
||||
let mut rng = thread_rng();
|
||||
if let (Ok(normalpos), Ok(normaloff)) =
|
||||
(Normal::new(0.0, 64.0), Normal::new(0.0, 256.0))
|
||||
{
|
||||
let mut path = Vec::<Vec2<i32>>::default();
|
||||
let target_site_pos = site.center.map(|e| e as f32)
|
||||
* TerrainChunk::RECT_SIZE.map(|e| e as f32);
|
||||
let offset_site_pos =
|
||||
target_site_pos.map(|v| v + normalpos.sample(&mut rng));
|
||||
let offset_dir = (offset_site_pos - self.pos.xy()).normalized();
|
||||
let dist = (offset_site_pos - self.pos.xy()).magnitude();
|
||||
let midpoint = self.pos.xy() + offset_dir * (dist / 2.0);
|
||||
let perp_dir = offset_dir.rotated_z(PI / 2.0);
|
||||
let offset = normaloff.sample(&mut rng);
|
||||
let inbetween_pos = midpoint + (perp_dir * offset);
|
||||
|
||||
tgt_site.map(|tgt_site| {
|
||||
let site = &world.civs().sites[tgt_site];
|
||||
path.push(inbetween_pos.map(|e| e as i32));
|
||||
path.push(target_site_pos.map(|e| e as i32));
|
||||
|
||||
let destination_name = site
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
Travel::CustomPath {
|
||||
target_id,
|
||||
path,
|
||||
progress: 0,
|
||||
}
|
||||
} else {
|
||||
Travel::Direct { target_id }
|
||||
}
|
||||
} else {
|
||||
Travel::Lost
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(target_id) = world
|
||||
.civs()
|
||||
.sites
|
||||
.iter()
|
||||
.filter(|s| match self.get_body() {
|
||||
comp::Body::Ship(_) => s.1.is_settlement(),
|
||||
_ => s.1.is_dungeon(),
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist =
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000 } else { 0 }
|
||||
})
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
Travel::Direct { target_id }
|
||||
} else {
|
||||
Travel::Lost
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Travel::InSite { site_id } => {
|
||||
if !self.get_body().is_humanoid() {
|
||||
// Non humanoids don't care if they start at a site
|
||||
Travel::Lost
|
||||
} else if let Some(target_id) = world
|
||||
.civs()
|
||||
.neighbors(site_id)
|
||||
.filter(|sid| {
|
||||
let site = world.civs().sites.get(*sid);
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist > 96_u32.pow(2)
|
||||
})
|
||||
.filter(|sid| {
|
||||
if let Some(last_visited) = self.brain.last_visited {
|
||||
*sid != last_visited
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.choose(&mut thread_rng())
|
||||
{
|
||||
if let Some(track_id) = world.civs().track_between(site_id, target_id) {
|
||||
self.brain.last_visited = Some(site_id);
|
||||
Travel::Path {
|
||||
target_id,
|
||||
track_id,
|
||||
progress: 0,
|
||||
reversed: false,
|
||||
}
|
||||
} else {
|
||||
// This should never trigger, since neighbors returns a list of sites for
|
||||
// which a track exists going from the current town.
|
||||
warn!("Could not get track after selecting from neighbor list");
|
||||
self.brain.last_visited = Some(site_id);
|
||||
Travel::Direct { target_id }
|
||||
}
|
||||
} else if let Some(target_id) = world
|
||||
.civs()
|
||||
.sites
|
||||
.iter()
|
||||
.filter(|s| s.1.is_settlement() | s.1.is_castle())
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000 } else { 0 }
|
||||
})
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
// This code should only trigger when no paths out of the current town exist.
|
||||
// The traveller will attempt to directly travel to another town
|
||||
self.brain.last_visited = Some(site_id);
|
||||
Travel::Direct { target_id }
|
||||
} else {
|
||||
// No paths we're picked, so stay in town. This will cause direct travel on the
|
||||
// next tick.
|
||||
self.brain.last_visited = Some(site_id);
|
||||
Travel::InSite { site_id }
|
||||
}
|
||||
},
|
||||
Travel::Direct { target_id } => {
|
||||
let site = &world.civs().sites[target_id];
|
||||
let destination_name = site
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32;
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
|
||||
if dist < 64 {
|
||||
self.brain.tgt = None;
|
||||
}
|
||||
if dist < 64_u32.pow(2) {
|
||||
Travel::InSite { site_id: target_id }
|
||||
} else {
|
||||
let travel_to = self.pos.xy()
|
||||
+ Vec3::from(
|
||||
(wpos.map(|e| e as f32 + 0.5) - self.pos.xy())
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::zero),
|
||||
) * 64.0;
|
||||
let travel_to_alt = world
|
||||
.sim()
|
||||
.get_alt_approx(travel_to.map(|e| e as i32))
|
||||
.unwrap_or(0.0) as i32;
|
||||
let travel_to = terrain
|
||||
.find_space(Vec3::new(
|
||||
travel_to.x as i32,
|
||||
travel_to.y as i32,
|
||||
travel_to_alt,
|
||||
))
|
||||
.map(|e| e as f32)
|
||||
+ Vec3::new(0.5, 0.5, 0.0);
|
||||
|
||||
let travel_to = self.pos.xy()
|
||||
+ Vec3::from(
|
||||
(wpos.map(|e| e as f32 + 0.5) - self.pos.xy())
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::zero),
|
||||
) * 64.0;
|
||||
let travel_to_alt = world
|
||||
.sim()
|
||||
.get_alt_approx(travel_to.map(|e| e as i32))
|
||||
.unwrap_or(0.0) as i32;
|
||||
let travel_to = terrain
|
||||
.find_space(Vec3::new(
|
||||
travel_to.x as i32,
|
||||
travel_to.y as i32,
|
||||
travel_to_alt,
|
||||
))
|
||||
.map(|e| e as f32)
|
||||
+ Vec3::new(0.5, 0.5, 0.0);
|
||||
self.controller.travel_to = Some((travel_to, destination_name));
|
||||
self.controller.speed_factor = 0.70;
|
||||
});
|
||||
self.controller.travel_to = Some((travel_to, destination_name));
|
||||
self.controller.speed_factor = 0.70;
|
||||
Travel::Direct { target_id }
|
||||
}
|
||||
},
|
||||
Travel::CustomPath {
|
||||
target_id,
|
||||
path,
|
||||
progress,
|
||||
} => {
|
||||
let site = &world.civs().sites[target_id];
|
||||
let destination_name = site
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
|
||||
if let Some(wpos) = &path.get(progress) {
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
if dist < 16_u32.pow(2) {
|
||||
if progress + 1 < path.len() {
|
||||
Travel::CustomPath {
|
||||
target_id,
|
||||
path,
|
||||
progress: progress + 1,
|
||||
}
|
||||
} else {
|
||||
Travel::InSite { site_id: target_id }
|
||||
}
|
||||
} else {
|
||||
let travel_to = self.pos.xy()
|
||||
+ Vec3::from(
|
||||
(wpos.map(|e| e as f32 + 0.5) - self.pos.xy())
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::zero),
|
||||
) * 64.0;
|
||||
let travel_to_alt = world
|
||||
.sim()
|
||||
.get_alt_approx(travel_to.map(|e| e as i32))
|
||||
.unwrap_or(0.0) as i32;
|
||||
let travel_to = terrain
|
||||
.find_space(Vec3::new(
|
||||
travel_to.x as i32,
|
||||
travel_to.y as i32,
|
||||
travel_to_alt,
|
||||
))
|
||||
.map(|e| e as f32)
|
||||
+ Vec3::new(0.5, 0.5, 0.0);
|
||||
|
||||
self.controller.travel_to = Some((travel_to, destination_name));
|
||||
self.controller.speed_factor = 0.70;
|
||||
Travel::CustomPath {
|
||||
target_id,
|
||||
path,
|
||||
progress,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Travel::Direct { target_id }
|
||||
}
|
||||
},
|
||||
Travel::Path {
|
||||
target_id,
|
||||
track_id,
|
||||
progress,
|
||||
reversed,
|
||||
} => {
|
||||
let track = &world.civs().tracks.get(track_id);
|
||||
let site = &world.civs().sites[target_id];
|
||||
let destination_name = site
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
let nth = if reversed {
|
||||
track.path().len() - progress - 1
|
||||
} else {
|
||||
progress
|
||||
};
|
||||
|
||||
if let Some(sim_pos) = track.path().iter().nth(nth) {
|
||||
let chunkpos = sim_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = if let Some(pathdata) = world.sim().get_nearest_path(chunkpos) {
|
||||
pathdata.1.map(|e| e as i32)
|
||||
} else {
|
||||
chunkpos
|
||||
};
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
|
||||
match dist {
|
||||
d if d < 16_u32.pow(2) => {
|
||||
if progress + 1 >= track.path().len() {
|
||||
Travel::Direct { target_id }
|
||||
} else {
|
||||
Travel::Path {
|
||||
target_id,
|
||||
track_id,
|
||||
progress: progress + 1,
|
||||
reversed,
|
||||
}
|
||||
}
|
||||
},
|
||||
d if d > 256_u32.pow(2) => {
|
||||
if !reversed && progress == 0 {
|
||||
Travel::Path {
|
||||
target_id,
|
||||
track_id,
|
||||
progress: 0,
|
||||
reversed: true,
|
||||
}
|
||||
} else {
|
||||
Travel::Lost
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let travel_to = self.pos.xy()
|
||||
+ Vec3::from(
|
||||
(wpos.map(|e| e as f32 + 0.5) - self.pos.xy())
|
||||
.try_normalized()
|
||||
.unwrap_or_else(Vec2::zero),
|
||||
) * 64.0;
|
||||
let travel_to_alt = world
|
||||
.sim()
|
||||
.get_alt_approx(travel_to.map(|e| e as i32))
|
||||
.unwrap_or(0.0)
|
||||
as i32;
|
||||
let travel_to = terrain
|
||||
.find_space(Vec3::new(
|
||||
travel_to.x as i32,
|
||||
travel_to.y as i32,
|
||||
travel_to_alt,
|
||||
))
|
||||
.map(|e| e as f32)
|
||||
+ Vec3::new(0.5, 0.5, 0.0);
|
||||
self.controller.travel_to = Some((travel_to, destination_name));
|
||||
self.controller.speed_factor = 0.70;
|
||||
Travel::Path {
|
||||
target_id,
|
||||
track_id,
|
||||
progress,
|
||||
reversed,
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// This code should never trigger. If we've gone outside the bounds of the
|
||||
// tracks vec then a logic bug has occured. I actually had
|
||||
// an off by one error that caused this to trigger and
|
||||
// resulted in travellers getting stuck in towns.
|
||||
warn!("Progress out of bounds while following track");
|
||||
Travel::Lost
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Forget old memories
|
||||
self.brain
|
||||
@ -196,10 +498,50 @@ impl Entity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Travel {
|
||||
// The initial state all entities start in, and a fallback for when a state has stopped making
|
||||
// sense. Non humanoids will always revert to this state after reaching their goal since the
|
||||
// current site they are in doesn't change their behavior.
|
||||
Lost,
|
||||
// When an rtsim entity reaches a site it will switch to this state to restart their
|
||||
// pathfinding from the beginning. Useful when the entity needs to know its current site to
|
||||
// decide their next target.
|
||||
InSite {
|
||||
site_id: Id<Site>,
|
||||
},
|
||||
// Move directly to a target site. Used by birds mostly, but also by humands who cannot find a
|
||||
// path.
|
||||
Direct {
|
||||
target_id: Id<Site>,
|
||||
},
|
||||
// Follow a custom path to reach the destination. Airships define a custom path to reduce the
|
||||
// chance of collisions.
|
||||
CustomPath {
|
||||
target_id: Id<Site>,
|
||||
path: Vec<Vec2<i32>>,
|
||||
progress: usize,
|
||||
},
|
||||
// Follow a track defined in the track_map to reach a site. Humanoids do this whenever
|
||||
// possible.
|
||||
Path {
|
||||
target_id: Id<Site>,
|
||||
track_id: Id<Track>,
|
||||
progress: usize,
|
||||
reversed: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Travel {
|
||||
fn default() -> Self { Self::Lost }
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Brain {
|
||||
begin: Option<Id<Site>>,
|
||||
tgt: Option<Id<Site>>,
|
||||
track: Option<(Track, usize)>,
|
||||
route: Travel,
|
||||
last_visited: Option<Id<Site>>,
|
||||
memories: Vec<Memory>,
|
||||
}
|
||||
|
||||
|
@ -914,8 +914,9 @@ impl<'a> AgentData<'a> {
|
||||
{
|
||||
format!(
|
||||
"Greetings fair {}! It has been far too \
|
||||
long since last I saw you.",
|
||||
&tgt_stats.name
|
||||
long since last I saw you. I'm going to \
|
||||
{} right now.",
|
||||
&tgt_stats.name, destination_name
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
|
@ -370,7 +370,7 @@ impl Civs {
|
||||
}
|
||||
|
||||
/// Return the direct track between two places
|
||||
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>> {
|
||||
self.track_map
|
||||
.get(&a)
|
||||
.and_then(|dests| dests.get(&b))
|
||||
@ -379,7 +379,7 @@ impl Civs {
|
||||
}
|
||||
|
||||
/// Return an iterator over a site's neighbors
|
||||
fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item = Id<Site>> + '_ {
|
||||
pub fn neighbors(&self, site: Id<Site>) -> impl Iterator<Item = Id<Site>> + '_ {
|
||||
let to = self
|
||||
.track_map
|
||||
.get(&site)
|
||||
@ -689,6 +689,10 @@ pub struct Track {
|
||||
path: Path<Vec2<i32>>,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
pub fn path(&self) -> &Path<Vec2<i32>> { &self.path }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Site {
|
||||
pub kind: SiteKind,
|
||||
|
Loading…
Reference in New Issue
Block a user