mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'tormod/proximity-spec' into 'master'
Make castles appear close to towns See merge request veloren/veloren!3801
This commit is contained in:
commit
475bc5c6ff
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Bats move slower and use a simple proportional controller to maintain altitude
|
- Bats move slower and use a simple proportional controller to maintain altitude
|
||||||
- Bats now have less health
|
- Bats now have less health
|
||||||
- Climbing no longer requires having 10 energy
|
- Climbing no longer requires having 10 energy
|
||||||
|
- Castles will now be placed close to towns
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -68,6 +68,84 @@ pub struct GenCtx<'a, R: Rng> {
|
|||||||
rng: R,
|
rng: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProximitySpec {
|
||||||
|
location: Vec2<i32>,
|
||||||
|
min_distance: Option<i32>,
|
||||||
|
max_distance: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProximitySpec {
|
||||||
|
pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
|
||||||
|
let distance_squared = site.distance_squared(self.location);
|
||||||
|
let min_ok = self
|
||||||
|
.min_distance
|
||||||
|
.map(|mind| distance_squared > (mind * mind))
|
||||||
|
.unwrap_or(true);
|
||||||
|
let max_ok = self
|
||||||
|
.max_distance
|
||||||
|
.map(|maxd| distance_squared < (maxd * maxd))
|
||||||
|
.unwrap_or(true);
|
||||||
|
min_ok && max_ok
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avoid(location: Vec2<i32>, min_distance: i32) -> Self {
|
||||||
|
ProximitySpec {
|
||||||
|
location,
|
||||||
|
min_distance: Some(min_distance),
|
||||||
|
max_distance: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn be_near(location: Vec2<i32>, max_distance: i32) -> Self {
|
||||||
|
ProximitySpec {
|
||||||
|
location,
|
||||||
|
min_distance: None,
|
||||||
|
max_distance: Some(max_distance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProximityRequirements {
|
||||||
|
all_of: Vec<ProximitySpec>,
|
||||||
|
any_of: Vec<ProximitySpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProximityRequirements {
|
||||||
|
pub fn satisfied_by(&self, site: Vec2<i32>) -> bool {
|
||||||
|
let all_of_compliance = self.all_of.iter().all(|spec| spec.satisfied_by(site));
|
||||||
|
let any_of_compliance =
|
||||||
|
self.any_of.is_empty() || self.any_of.iter().any(|spec| spec.satisfied_by(site));
|
||||||
|
all_of_compliance && any_of_compliance
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ProximityRequirements {
|
||||||
|
all_of: Vec::new(),
|
||||||
|
any_of: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avoid_all_of(
|
||||||
|
mut self,
|
||||||
|
locations: impl Iterator<Item = Vec2<i32>>,
|
||||||
|
distance: i32,
|
||||||
|
) -> Self {
|
||||||
|
let specs = locations.map(|loc| ProximitySpec::avoid(loc, distance));
|
||||||
|
self.all_of.extend(specs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_to_one_of(
|
||||||
|
mut self,
|
||||||
|
locations: impl Iterator<Item = Vec2<i32>>,
|
||||||
|
distance: i32,
|
||||||
|
) -> Self {
|
||||||
|
let specs = locations.map(|loc| ProximitySpec::be_near(loc, distance));
|
||||||
|
self.any_of.extend(specs);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, R: Rng> GenCtx<'a, R> {
|
impl<'a, R: Rng> GenCtx<'a, R> {
|
||||||
pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> {
|
pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> {
|
||||||
let mut entropy = self.rng.gen::<[u8; 32]>();
|
let mut entropy = self.rng.gen::<[u8; 32]>();
|
||||||
@ -114,7 +192,13 @@ impl Civs {
|
|||||||
attempt(5, || {
|
attempt(5, || {
|
||||||
let (loc, kind) = match ctx.rng.gen_range(0..64) {
|
let (loc, kind) = match ctx.rng.gen_range(0..64) {
|
||||||
0..=5 => (
|
0..=5 => (
|
||||||
find_site_loc(&mut ctx, (&this.castle_enemies(), 40), SiteKind::Castle)?,
|
find_site_loc(
|
||||||
|
&mut ctx,
|
||||||
|
&ProximityRequirements::new()
|
||||||
|
.avoid_all_of(this.castle_enemies(), 40)
|
||||||
|
.close_to_one_of(this.towns(), 20),
|
||||||
|
SiteKind::Castle,
|
||||||
|
)?,
|
||||||
SiteKind::Castle,
|
SiteKind::Castle,
|
||||||
),
|
),
|
||||||
28..=31 => {
|
28..=31 => {
|
||||||
@ -122,7 +206,8 @@ impl Civs {
|
|||||||
(
|
(
|
||||||
find_site_loc(
|
find_site_loc(
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
(&this.tree_enemies(), 40),
|
&ProximityRequirements::new()
|
||||||
|
.avoid_all_of(this.tree_enemies(), 40),
|
||||||
SiteKind::GiantTree,
|
SiteKind::GiantTree,
|
||||||
)?,
|
)?,
|
||||||
SiteKind::GiantTree,
|
SiteKind::GiantTree,
|
||||||
@ -131,7 +216,8 @@ impl Civs {
|
|||||||
(
|
(
|
||||||
find_site_loc(
|
find_site_loc(
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
(&this.tree_enemies(), 40),
|
&ProximityRequirements::new()
|
||||||
|
.avoid_all_of(this.tree_enemies(), 40),
|
||||||
SiteKind::Tree,
|
SiteKind::Tree,
|
||||||
)?,
|
)?,
|
||||||
SiteKind::Tree,
|
SiteKind::Tree,
|
||||||
@ -141,7 +227,7 @@ impl Civs {
|
|||||||
32..=37 => (
|
32..=37 => (
|
||||||
find_site_loc(
|
find_site_loc(
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
(&this.gnarling_enemies(), 40),
|
&ProximityRequirements::new().avoid_all_of(this.gnarling_enemies(), 40),
|
||||||
SiteKind::Gnarling,
|
SiteKind::Gnarling,
|
||||||
)?,
|
)?,
|
||||||
SiteKind::Gnarling,
|
SiteKind::Gnarling,
|
||||||
@ -150,13 +236,18 @@ impl Civs {
|
|||||||
38..=43 => (
|
38..=43 => (
|
||||||
find_site_loc(
|
find_site_loc(
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
(&this.chapel_site_enemies(), 40),
|
&ProximityRequirements::new()
|
||||||
|
.avoid_all_of(this.chapel_site_enemies(), 40),
|
||||||
SiteKind::ChapelSite,
|
SiteKind::ChapelSite,
|
||||||
)?,
|
)?,
|
||||||
SiteKind::ChapelSite,
|
SiteKind::ChapelSite,
|
||||||
),
|
),
|
||||||
_ => (
|
_ => (
|
||||||
find_site_loc(&mut ctx, (&this.dungeon_enemies(), 40), SiteKind::Dungeon)?,
|
find_site_loc(
|
||||||
|
&mut ctx,
|
||||||
|
&ProximityRequirements::new().avoid_all_of(this.dungeon_enemies(), 40),
|
||||||
|
SiteKind::Dungeon,
|
||||||
|
)?,
|
||||||
SiteKind::Dungeon,
|
SiteKind::Dungeon,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -600,7 +691,9 @@ impl Civs {
|
|||||||
_ => SiteKind::Refactor,
|
_ => SiteKind::Refactor,
|
||||||
};
|
};
|
||||||
let site = attempt(100, || {
|
let site = attempt(100, || {
|
||||||
let loc = find_site_loc(ctx, (&self.town_enemies(), 60), kind)?;
|
let avoid_town_enemies =
|
||||||
|
ProximityRequirements::new().avoid_all_of(self.town_enemies(), 60);
|
||||||
|
let loc = find_site_loc(ctx, &avoid_town_enemies, kind)?;
|
||||||
Some(self.establish_site(ctx, loc, |place| Site {
|
Some(self.establish_site(ctx, loc, |place| Site {
|
||||||
kind,
|
kind,
|
||||||
site_tmp: None,
|
site_tmp: None,
|
||||||
@ -1141,62 +1234,60 @@ impl Civs {
|
|||||||
site
|
site
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gnarling_enemies(&self) -> Vec<Vec2<i32>> {
|
fn gnarling_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| match s.kind {
|
||||||
.filter_map(|s| match s.kind {
|
SiteKind::Tree | SiteKind::GiantTree => None,
|
||||||
SiteKind::Tree | SiteKind::GiantTree => None,
|
_ => Some(s.center),
|
||||||
_ => Some(s.center),
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chapel_site_enemies(&self) -> Vec<Vec2<i32>> {
|
fn chapel_site_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| match s.kind {
|
||||||
.filter_map(|s| match s.kind {
|
SiteKind::Tree | SiteKind::GiantTree => None,
|
||||||
SiteKind::Tree | SiteKind::GiantTree => None,
|
_ => Some(s.center),
|
||||||
_ => Some(s.center),
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dungeon_enemies(&self) -> Vec<Vec2<i32>> {
|
fn dungeon_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| match s.kind {
|
||||||
.filter_map(|s| match s.kind {
|
SiteKind::Tree | SiteKind::GiantTree => None,
|
||||||
SiteKind::Tree | SiteKind::GiantTree => None,
|
_ => Some(s.center),
|
||||||
_ => Some(s.center),
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tree_enemies(&self) -> Vec<Vec2<i32>> {
|
fn tree_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| match s.kind {
|
||||||
.filter_map(|s| match s.kind {
|
SiteKind::Castle => Some(s.center),
|
||||||
SiteKind::Castle => Some(s.center),
|
_ if s.is_settlement() => Some(s.center),
|
||||||
_ if s.is_settlement() => Some(s.center),
|
_ => None,
|
||||||
_ => None,
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn castle_enemies(&self) -> Vec<Vec2<i32>> {
|
fn castle_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| {
|
||||||
.filter_map(|s| {
|
if s.is_settlement() {
|
||||||
if s.is_settlement() {
|
None
|
||||||
None
|
} else {
|
||||||
} else {
|
Some(s.center)
|
||||||
Some(s.center)
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn town_enemies(&self) -> Vec<Vec2<i32>> {
|
fn town_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
self.sites()
|
self.sites().filter_map(|s| match s.kind {
|
||||||
.filter_map(|s| match s.kind {
|
SiteKind::Castle | SiteKind::Citadel => None,
|
||||||
SiteKind::Castle | SiteKind::Citadel => None,
|
_ => Some(s.center),
|
||||||
_ => Some(s.center),
|
})
|
||||||
})
|
}
|
||||||
.collect()
|
|
||||||
|
fn towns(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
|
||||||
|
self.sites().filter_map(|s| {
|
||||||
|
if s.is_settlement() {
|
||||||
|
Some(s.center)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1314,12 +1405,11 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>, site_kind: SiteKind) ->
|
|||||||
/// Attempt to search for a location that's suitable for site construction
|
/// Attempt to search for a location that's suitable for site construction
|
||||||
fn find_site_loc(
|
fn find_site_loc(
|
||||||
ctx: &mut GenCtx<impl Rng>,
|
ctx: &mut GenCtx<impl Rng>,
|
||||||
avoid: (&Vec<Vec2<i32>>, i32),
|
proximity_reqs: &ProximityRequirements,
|
||||||
site_kind: SiteKind,
|
site_kind: SiteKind,
|
||||||
) -> Option<Vec2<i32>> {
|
) -> Option<Vec2<i32>> {
|
||||||
const MAX_ATTEMPTS: usize = 10000;
|
const MAX_ATTEMPTS: usize = 10000;
|
||||||
let mut loc = None;
|
let mut loc = None;
|
||||||
let (avoid_locs, distance) = avoid;
|
|
||||||
for _ in 0..MAX_ATTEMPTS {
|
for _ in 0..MAX_ATTEMPTS {
|
||||||
let test_loc = loc.unwrap_or_else(|| {
|
let test_loc = loc.unwrap_or_else(|| {
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
@ -1328,25 +1418,20 @@ fn find_site_loc(
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if avoid_locs
|
if proximity_reqs.satisfied_by(test_loc) {
|
||||||
.iter()
|
if loc_suitable_for_site(ctx.sim, test_loc, site_kind) {
|
||||||
.any(|l| l.distance_squared(test_loc) < distance * distance)
|
return Some(test_loc);
|
||||||
{
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if loc_suitable_for_site(ctx.sim, test_loc, site_kind) {
|
loc = ctx.sim.get(test_loc).and_then(|c| {
|
||||||
return Some(test_loc);
|
site_kind.is_suitable_loc(test_loc, ctx.sim).then_some(
|
||||||
|
c.downhill?
|
||||||
|
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32)),
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loc = ctx.sim.get(test_loc).and_then(|c| {
|
|
||||||
site_kind.is_suitable_loc(test_loc, ctx.sim).then_some(
|
|
||||||
c.downhill?
|
|
||||||
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / (sz as i32)),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
warn!("Failed to place site {:?}.", site_kind);
|
debug!("Failed to place site {:?}.", site_kind);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1642,3 +1727,30 @@ pub enum PoiKind {
|
|||||||
/// Lake stores a metric relating to size
|
/// Lake stores a metric relating to size
|
||||||
Biome(u32),
|
Biome(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_proximity_requirements() {
|
||||||
|
let reqs = ProximityRequirements::new();
|
||||||
|
assert!(reqs.satisfied_by(Vec2 { x: 0, y: 0 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn avoid_proximity_requirements() {
|
||||||
|
let reqs =
|
||||||
|
ProximityRequirements::new().avoid_all_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10);
|
||||||
|
assert!(reqs.satisfied_by(Vec2 { x: 8, y: -8 }));
|
||||||
|
assert!(!reqs.satisfied_by(Vec2 { x: -1, y: 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn near_proximity_requirements() {
|
||||||
|
let reqs =
|
||||||
|
ProximityRequirements::new().close_to_one_of(vec![Vec2 { x: 0, y: 0 }].into_iter(), 10);
|
||||||
|
assert!(reqs.satisfied_by(Vec2 { x: 1, y: -1 }));
|
||||||
|
assert!(!reqs.satisfied_by(Vec2 { x: -8, y: 8 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user