mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Allow NPCs to migrate away from towns with a high population density
This commit is contained in:
parent
9e17042bf6
commit
2a1ea63910
@ -225,6 +225,9 @@ npc-speech-prisoner =
|
||||
.a4 = I wish i still had my pick!
|
||||
npc-speech-moving_on =
|
||||
.a0 = I've spent enough time here, onward to { $site }!
|
||||
npc-speech-migrating =
|
||||
.a0 = I'm no longer happy living here. Time to migrate to { $site }.
|
||||
.a1 = Time to move to { $site }, I've had it with this place.
|
||||
npc-speech-night_time =
|
||||
.a0 = It's dark, time to head home.
|
||||
.a1 = I'm tired.
|
||||
|
@ -55,6 +55,7 @@ pub struct PathingMemory {
|
||||
pub struct Controller {
|
||||
pub actions: Vec<NpcAction>,
|
||||
pub activity: Option<NpcActivity>,
|
||||
pub new_home: Option<SiteId>,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
@ -79,6 +80,8 @@ impl Controller {
|
||||
pub fn attack(&mut self, target: impl Into<Actor>) {
|
||||
self.actions.push(NpcAction::Attack(target.into()));
|
||||
}
|
||||
|
||||
pub fn set_new_home(&mut self, new_home: SiteId) { self.new_home = Some(new_home); }
|
||||
}
|
||||
|
||||
pub struct Brain {
|
||||
|
@ -599,28 +599,50 @@ fn choose_plaza(ctx: &mut NpcCtx, site: SiteId) -> Option<Vec2<f32>> {
|
||||
|
||||
fn villager(visiting_site: SiteId) -> impl Action {
|
||||
choose(move |ctx| {
|
||||
/*
|
||||
if ctx
|
||||
.state
|
||||
.data()
|
||||
.sites
|
||||
.get(visiting_site)
|
||||
.map_or(true, |s| s.world_site.is_none())
|
||||
// Consider moving home if the home site gets too full
|
||||
if ctx.rng.gen_bool(0.0001)
|
||||
&& let Some(home) = ctx.npc.home
|
||||
&& Some(home) == ctx.npc.current_site
|
||||
&& let Some(home_pop_ratio) = ctx.state.data().sites.get(home)
|
||||
.and_then(|site| Some((site, ctx.index.sites.get(site.world_site?).site2()?)))
|
||||
.map(|(site, site2)| site.population.len() as f32 / site2.plots().len() as f32)
|
||||
// Only consider moving if the population is more than 1.5x the number of homes
|
||||
.filter(|pop_ratio| *pop_ratio > 1.5)
|
||||
&& let Some(new_home) = ctx
|
||||
.state
|
||||
.data()
|
||||
.sites
|
||||
.iter()
|
||||
// Don't try to move to the site that's currently our home
|
||||
.filter(|(site_id, _)| Some(*site_id) != ctx.npc.home)
|
||||
// Only consider towns as potential homes
|
||||
.filter_map(|(site_id, site)| {
|
||||
let site2 = match site.world_site.map(|ws| &ctx.index.sites.get(ws).kind) {
|
||||
Some(SiteKind::Refactor(site2)
|
||||
| SiteKind::CliffTown(site2)
|
||||
| SiteKind::SavannahPit(site2)
|
||||
| SiteKind::DesertCity(site2)) => site2,
|
||||
_ => return None,
|
||||
};
|
||||
Some((site_id, site, site2))
|
||||
})
|
||||
// Only select sites that are less densely populated than our own
|
||||
.filter(|(_, site, site2)| (site.population.len() as f32 / site2.plots().len() as f32) < home_pop_ratio)
|
||||
// Find the closest of the candidate sites
|
||||
.min_by_key(|(_, site, _)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
||||
.map(|(site_id, _, _)| site_id)
|
||||
{
|
||||
return casual(idle()
|
||||
.debug(|| "idling (visiting site does not exist, perhaps it's stale data?)"));
|
||||
} else if ctx.npc.current_site != Some(visiting_site) {
|
||||
let npc_home = ctx.npc.home;
|
||||
// Travel to the site we're supposed to be in
|
||||
return urgent(travel_to_site(visiting_site, 1.0).debug(move || {
|
||||
if npc_home == Some(visiting_site) {
|
||||
"travel home".to_string()
|
||||
} else {
|
||||
"travel to visiting site".to_string()
|
||||
let site_name = ctx.state.data().sites[new_home].world_site
|
||||
.map(|ws| ctx.index.sites.get(ws).name().to_string());
|
||||
return important(just(move |ctx| {
|
||||
if let Some(site_name) = &site_name {
|
||||
ctx.controller.say(None, Content::localized_with_args("npc-speech-migrating", [("site", site_name.clone())]))
|
||||
}
|
||||
}));
|
||||
} else
|
||||
*/
|
||||
})
|
||||
.then(travel_to_site(new_home, 0.5))
|
||||
.then(just(move |ctx| ctx.controller.set_new_home(new_home))));
|
||||
}
|
||||
|
||||
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
||||
&& !matches!(ctx.npc.profession, Some(Profession::Guard))
|
||||
{
|
||||
|
@ -151,125 +151,129 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
||||
|
||||
fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
||||
let data = &mut *ctx.state.data_mut();
|
||||
for npc in data
|
||||
.npcs
|
||||
.npcs
|
||||
.values_mut()
|
||||
.filter(|npc| matches!(npc.mode, SimulationMode::Simulated) && !npc.is_dead)
|
||||
{
|
||||
// Simulate NPC movement when riding
|
||||
if let Some(riding) = &npc.riding {
|
||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
||||
for npc in data.npcs.npcs.values_mut().filter(|npc| !npc.is_dead) {
|
||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||
// Simulate NPC movement when riding
|
||||
if let Some(riding) = &npc.riding {
|
||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
||||
match npc.controller.activity {
|
||||
// If steering, the NPC controls the vehicle's motion
|
||||
Some(NpcActivity::Goto(target, speed_factor)) if riding.steering => {
|
||||
let diff = target.xy() - vehicle.wpos.xy();
|
||||
let dist2 = diff.magnitude_squared();
|
||||
|
||||
if dist2 > 0.5f32.powi(2) {
|
||||
let mut wpos = vehicle.wpos
|
||||
+ (diff
|
||||
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
||||
/ dist2.sqrt())
|
||||
.min(1.0))
|
||||
.with_z(0.0);
|
||||
|
||||
let is_valid = match vehicle.body {
|
||||
common::comp::ship::Body::DefaultAirship
|
||||
| common::comp::ship::Body::AirBalloon => true,
|
||||
common::comp::ship::Body::SailBoat
|
||||
| common::comp::ship::Body::Galleon => {
|
||||
let chunk_pos = wpos.xy().as_().wpos_to_cpos();
|
||||
ctx.world
|
||||
.sim()
|
||||
.get(chunk_pos)
|
||||
.map_or(true, |f| f.river.river_kind.is_some())
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_valid {
|
||||
match vehicle.body {
|
||||
common::comp::ship::Body::DefaultAirship
|
||||
| common::comp::ship::Body::AirBalloon => {
|
||||
if let Some(alt) = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_alt_approx(wpos.xy().as_())
|
||||
.filter(|alt| wpos.z < *alt)
|
||||
{
|
||||
wpos.z = alt;
|
||||
}
|
||||
},
|
||||
common::comp::ship::Body::SailBoat
|
||||
| common::comp::ship::Body::Galleon => {
|
||||
wpos.z = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_interpolated(
|
||||
wpos.xy().map(|e| e as i32),
|
||||
|chunk| chunk.water_alt,
|
||||
)
|
||||
.unwrap_or(0.0);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
vehicle.wpos = wpos;
|
||||
}
|
||||
}
|
||||
},
|
||||
// When riding, other actions are disabled
|
||||
Some(
|
||||
NpcActivity::Goto(_, _)
|
||||
| NpcActivity::Gather(_)
|
||||
| NpcActivity::HuntAnimals
|
||||
| NpcActivity::Dance,
|
||||
) => {},
|
||||
None => {},
|
||||
}
|
||||
npc.wpos = vehicle.wpos;
|
||||
} else {
|
||||
// Vehicle doens't exist anymore
|
||||
npc.riding = None;
|
||||
}
|
||||
// If not riding, we assume they're just walking
|
||||
} else {
|
||||
match npc.controller.activity {
|
||||
// If steering, the NPC controls the vehicle's motion
|
||||
Some(NpcActivity::Goto(target, speed_factor)) if riding.steering => {
|
||||
let diff = target.xy() - vehicle.wpos.xy();
|
||||
// Move NPCs if they have a target destination
|
||||
Some(NpcActivity::Goto(target, speed_factor)) => {
|
||||
let diff = target.xy() - npc.wpos.xy();
|
||||
let dist2 = diff.magnitude_squared();
|
||||
|
||||
if dist2 > 0.5f32.powi(2) {
|
||||
let mut wpos = vehicle.wpos
|
||||
+ (diff
|
||||
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
||||
/ dist2.sqrt())
|
||||
.min(1.0))
|
||||
.with_z(0.0);
|
||||
|
||||
let is_valid = match vehicle.body {
|
||||
common::comp::ship::Body::DefaultAirship
|
||||
| common::comp::ship::Body::AirBalloon => true,
|
||||
common::comp::ship::Body::SailBoat
|
||||
| common::comp::ship::Body::Galleon => {
|
||||
let chunk_pos = wpos.xy().as_().wpos_to_cpos();
|
||||
ctx.world
|
||||
.sim()
|
||||
.get(chunk_pos)
|
||||
.map_or(true, |f| f.river.river_kind.is_some())
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_valid {
|
||||
match vehicle.body {
|
||||
common::comp::ship::Body::DefaultAirship
|
||||
| common::comp::ship::Body::AirBalloon => {
|
||||
if let Some(alt) = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_alt_approx(wpos.xy().as_())
|
||||
.filter(|alt| wpos.z < *alt)
|
||||
{
|
||||
wpos.z = alt;
|
||||
}
|
||||
},
|
||||
common::comp::ship::Body::SailBoat
|
||||
| common::comp::ship::Body::Galleon => {
|
||||
wpos.z = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_interpolated(
|
||||
wpos.xy().map(|e| e as i32),
|
||||
|chunk| chunk.water_alt,
|
||||
)
|
||||
.unwrap_or(0.0);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
vehicle.wpos = wpos;
|
||||
}
|
||||
npc.wpos += (diff
|
||||
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
||||
/ dist2.sqrt())
|
||||
.min(1.0))
|
||||
.with_z(0.0);
|
||||
}
|
||||
},
|
||||
// When riding, other actions are disabled
|
||||
Some(
|
||||
NpcActivity::Goto(_, _)
|
||||
| NpcActivity::Gather(_)
|
||||
| NpcActivity::HuntAnimals
|
||||
| NpcActivity::Dance,
|
||||
) => {},
|
||||
NpcActivity::Gather(_) | NpcActivity::HuntAnimals | NpcActivity::Dance,
|
||||
) => {
|
||||
// TODO: Maybe they should walk around randomly
|
||||
// when gathering resources?
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
npc.wpos = vehicle.wpos;
|
||||
} else {
|
||||
// Vehicle doens't exist anymore
|
||||
npc.riding = None;
|
||||
}
|
||||
// If not riding, we assume they're just walking
|
||||
} else {
|
||||
match npc.controller.activity {
|
||||
// Move NPCs if they have a target destination
|
||||
Some(NpcActivity::Goto(target, speed_factor)) => {
|
||||
let diff = target.xy() - npc.wpos.xy();
|
||||
let dist2 = diff.magnitude_squared();
|
||||
|
||||
if dist2 > 0.5f32.powi(2) {
|
||||
npc.wpos += (diff
|
||||
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
||||
/ dist2.sqrt())
|
||||
.min(1.0))
|
||||
.with_z(0.0);
|
||||
}
|
||||
},
|
||||
Some(NpcActivity::Gather(_) | NpcActivity::HuntAnimals | NpcActivity::Dance) => {
|
||||
// TODO: Maybe they should walk around randomly
|
||||
// when gathering resources?
|
||||
},
|
||||
None => {},
|
||||
// Consume NPC actions
|
||||
for action in std::mem::take(&mut npc.controller.actions) {
|
||||
match action {
|
||||
NpcAction::Say(_, _) => {}, // Currently, just swallow interactions
|
||||
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure NPCs remain on the surface
|
||||
npc.wpos.z = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||
.unwrap_or(0.0)
|
||||
+ npc.body.flying_height();
|
||||
}
|
||||
|
||||
// Consume NPC actions
|
||||
for action in std::mem::take(&mut npc.controller.actions) {
|
||||
match action {
|
||||
NpcAction::Say(_, _) => {}, // Currently, just swallow interactions
|
||||
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
||||
}
|
||||
// Move home if required
|
||||
if let Some(new_home) = npc.controller.new_home.take() {
|
||||
npc.home = Some(new_home);
|
||||
}
|
||||
|
||||
// Make sure NPCs remain on the surface
|
||||
npc.wpos.z = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||
.unwrap_or(0.0)
|
||||
+ npc.body.flying_height();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user