Better NPC spawning

This commit is contained in:
Joshua Barretto 2023-03-31 23:57:01 +01:00
parent 64324262c7
commit 5062920b5c
3 changed files with 83 additions and 43 deletions

View File

@ -18,7 +18,7 @@ use common::{
use rand::prelude::*; use rand::prelude::*;
use tracing::info; use tracing::info;
use vek::*; use vek::*;
use world::{site::SiteKind, IndexRef, World}; use world::{site::SiteKind, site2::PlotKind, IndexRef, World};
impl Data { impl Data {
pub fn generate(settings: &WorldSettings, world: &World, index: IndexRef) -> Self { pub fn generate(settings: &WorldSettings, world: &World, index: IndexRef) -> Self {
@ -77,10 +77,16 @@ impl Data {
this.sites.len() this.sites.len()
); );
// Spawn some test entities at the sites // Spawn some test entities at the sites
for (site_id, site) in this.sites.iter() for (site_id, site, site2) in this.sites.iter()
// TODO: Stupid // TODO: Stupid. Only find site2 towns
.filter(|(_, site)| site.world_site.map_or(false, |ws| .filter_map(|(site_id, site)| Some((site_id, site, site.world_site
matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_)))) .and_then(|ws| match &index.sites.get(ws).kind {
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::SavannahPit(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
})?)))
{ {
let Some(good_or_evil) = site let Some(good_or_evil) = site
.faction .faction
@ -88,8 +94,13 @@ impl Data {
.map(|f| f.good_or_evil) .map(|f| f.good_or_evil)
else { continue }; else { continue };
let rand_wpos = |rng: &mut SmallRng| { let rand_wpos = |rng: &mut SmallRng, matches_plot: fn(&PlotKind) -> bool| {
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); let wpos2d = site2
.plots()
.filter(|plot| matches_plot(plot.kind()))
.choose(&mut thread_rng())
.map(|plot| site2.tile_center_wpos(plot.root_tile()))
.unwrap_or_else(|| site.wpos.map(|e| e + rng.gen_range(-10..10)));
wpos2d wpos2d
.map(|e| e as f32 + 0.5) .map(|e| e as f32 + 0.5)
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
@ -98,47 +109,71 @@ impl Data {
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap(); let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
Body::Humanoid(comp::humanoid::Body::random_with(rng, species)) Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
}; };
let matches_buildings = (|kind: &PlotKind| {
matches!(
kind,
PlotKind::House(_) | PlotKind::Workshop(_) | PlotKind::Plaza
)
}) as _;
let matches_plazas = (|kind: &PlotKind| matches!(kind, PlotKind::Plaza)) as _;
if good_or_evil { if good_or_evil {
for _ in 0..32 { for _ in 0..site2.plots().len() {
this.npcs.create_npc( this.npcs.create_npc(
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) Npc::new(
.with_faction(site.faction) rng.gen(),
.with_home(site_id) rand_wpos(&mut rng, matches_buildings),
.with_personality(Personality::random(&mut rng)) random_humanoid(&mut rng),
.with_profession(match rng.gen_range(0..20) { )
0 => Profession::Hunter, .with_faction(site.faction)
1 => Profession::Blacksmith, .with_home(site_id)
2 => Profession::Chef, .with_personality(Personality::random(&mut rng))
3 => Profession::Alchemist, .with_profession(match rng.gen_range(0..20) {
5..=8 => Profession::Farmer, 0 => Profession::Hunter,
9..=10 => Profession::Herbalist, 1 => Profession::Blacksmith,
11..=15 => Profession::Guard, 2 => Profession::Chef,
_ => Profession::Adventurer(rng.gen_range(0..=3)), 3 => Profession::Alchemist,
}), 5..=8 => Profession::Farmer,
9..=10 => Profession::Herbalist,
11..=16 => Profession::Guard,
_ => Profession::Adventurer(rng.gen_range(0..=3)),
}),
); );
} }
} else { } else {
for _ in 0..15 { for _ in 0..15 {
this.npcs.create_npc( this.npcs.create_npc(
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) Npc::new(
.with_personality(Personality::random_evil(&mut rng)) rng.gen(),
.with_faction(site.faction) rand_wpos(&mut rng, matches_buildings),
.with_home(site_id) random_humanoid(&mut rng),
.with_profession(match rng.gen_range(0..20) { )
_ => Profession::Cultist, .with_personality(Personality::random_evil(&mut rng))
}), .with_faction(site.faction)
.with_home(site_id)
.with_profession(match rng.gen_range(0..20) {
_ => Profession::Cultist,
}),
);
}
}
// Merchants
if good_or_evil {
for _ in 0..(site2.plots().len() / 6) + 1 {
this.npcs.create_npc(
Npc::new(
rng.gen(),
rand_wpos(&mut rng, matches_plazas),
random_humanoid(&mut rng),
)
.with_home(site_id)
.with_personality(Personality::random_good(&mut rng))
.with_profession(Profession::Merchant),
); );
} }
} }
this.npcs.create_npc(
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
.with_home(site_id)
.with_personality(Personality::random_good(&mut rng))
.with_profession(Profession::Merchant),
);
if rng.gen_bool(0.4) { if rng.gen_bool(0.4) {
let wpos = rand_wpos(&mut rng) + Vec3::unit_z() * 50.0; let wpos = rand_wpos(&mut rng, matches_plazas) + Vec3::unit_z() * 50.0;
let vehicle_id = this let vehicle_id = this
.npcs .npcs
.create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship)); .create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship));

View File

@ -395,13 +395,13 @@ fn travel_to_point(wpos: Vec2<f32>) -> impl Action {
const WAYPOINT: f32 = 24.0; const WAYPOINT: f32 = 24.0;
let start = ctx.npc.wpos.xy(); let start = ctx.npc.wpos.xy();
let diff = wpos - start; let diff = wpos - start;
// if diff.magnitude() > 1.0 {
let n = (diff.magnitude() / WAYPOINT).max(1.0); let n = (diff.magnitude() / WAYPOINT).max(1.0);
let mut points = (1..n as usize + 1).map(move |i| start + diff * (i as f32 / n)); let mut points = (1..n as usize + 1).map(move |i| start + diff * (i as f32 / n));
if diff.magnitude() > 1.0 { traverse_points(move |_| points.next()).boxed()
traverse_points(move |_| points.next()).boxed() // } else {
} else { // finish().boxed()
finish().boxed() // }
}
}) })
.debug(|| "travel to point") .debug(|| "travel to point")
} }
@ -525,11 +525,16 @@ fn adventure() -> impl Action {
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32) .min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
.map(|(site_id, _)| site_id) .map(|(site_id, _)| site_id)
{ {
let wait_time = if matches!(ctx.npc.profession, Some(Profession::Merchant)) {
60.0 * 15.0
} else {
60.0 * 3.0
};
// Travel to the site // Travel to the site
important( important(
travel_to_site(tgt_site) travel_to_site(tgt_site)
// Stop for a few minutes // Stop for a few minutes
.then(villager(tgt_site).repeat().stop_if(timeout(60.0 * 3.0))) .then(villager(tgt_site).repeat().stop_if(timeout(wait_time)))
.map(|_| ()) .map(|_| ())
.boxed(), .boxed(),
) )

View File

@ -212,7 +212,7 @@ impl RtSim {
}); });
if wait_until_finished { if wait_until_finished {
handle.join(); handle.join().expect("Save thread failed to join");
} }
self.last_saved = Some(Instant::now()); self.last_saved = Some(Instant::now());