mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
More interesting idle behaviours
This commit is contained in:
parent
8f7b11f12f
commit
ac83cfc4a3
@ -65,14 +65,19 @@ impl Data {
|
||||
);
|
||||
|
||||
// Spawn some test entities at the sites
|
||||
for (site_id, site) in this.sites.iter().take(1) {
|
||||
for (site_id, site) in this.sites.iter()
|
||||
// TODO: Stupid
|
||||
.filter(|(_, site)| site.world_site.map_or(false, |ws| matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_))))
|
||||
.skip(1)
|
||||
.take(1)
|
||||
{
|
||||
let rand_wpos = |rng: &mut SmallRng| {
|
||||
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
||||
wpos2d
|
||||
.map(|e| e as f32 + 0.5)
|
||||
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
||||
};
|
||||
for _ in 0..10 {
|
||||
for _ in 0..16 {
|
||||
this.npcs.create(
|
||||
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||
.with_faction(site.faction)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::{collections::VecDeque, hash::BuildHasherDefault};
|
||||
|
||||
use crate::{
|
||||
ai::{casual, choose, finish, just, now, seq, urgent, watch, Action, NpcCtx},
|
||||
ai::{casual, choose, finish, important, just, now, seq, urgent, watch, Action, NpcCtx},
|
||||
data::{
|
||||
npc::{Brain, Controller, Npc, NpcId, PathData, PathingMemory},
|
||||
Sites,
|
||||
@ -208,24 +208,18 @@ fn path_towns(
|
||||
end: SiteId,
|
||||
sites: &Sites,
|
||||
world: &World,
|
||||
) -> Option<(PathData<(Id<Track>, bool), SiteId>, usize)> {
|
||||
) -> Option<PathData<(Id<Track>, bool), SiteId>> {
|
||||
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::Exhausted(p) => Some(PathData {
|
||||
end,
|
||||
path: p.nodes.into(),
|
||||
repoll: true,
|
||||
}),
|
||||
PathResult::Path(p) => Some(PathData {
|
||||
end,
|
||||
path: p.nodes.into(),
|
||||
repoll: false,
|
||||
}),
|
||||
PathResult::Pending | PathResult::None(_) => None,
|
||||
}
|
||||
}
|
||||
@ -323,16 +317,28 @@ fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()) }
|
||||
|
||||
/// Try to walk toward a 3D position without caring for obstacles.
|
||||
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
|
||||
const STEP_DIST: f32 = 16.0;
|
||||
const STEP_DIST: f32 = 24.0;
|
||||
const WAYPOINT_DIST: f32 = 12.0;
|
||||
const GOAL_DIST: f32 = 2.0;
|
||||
|
||||
let mut waypoint = None;
|
||||
|
||||
just(move |ctx| {
|
||||
let rpos = wpos - ctx.npc.wpos;
|
||||
let len = rpos.magnitude();
|
||||
ctx.controller.goto = Some((
|
||||
ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST),
|
||||
speed_factor,
|
||||
));
|
||||
|
||||
// If we're close to the next waypoint, complete it
|
||||
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
|
||||
ctx.npc.wpos.xy().distance_squared(waypoint.xy()) < WAYPOINT_DIST.powi(2)
|
||||
}) {
|
||||
waypoint = None;
|
||||
}
|
||||
|
||||
// Get the next waypoint on the route toward the goal
|
||||
let waypoint =
|
||||
waypoint.get_or_insert_with(|| ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST));
|
||||
|
||||
ctx.controller.goto = Some((*waypoint, speed_factor));
|
||||
})
|
||||
.repeat()
|
||||
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < GOAL_DIST.powi(2))
|
||||
@ -358,7 +364,7 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
||||
// If we're currently in a site, try to find a path to the target site via
|
||||
// tracks
|
||||
if let Some(current_site) = ctx.npc.current_site
|
||||
&& let Some((mut tracks, _)) = path_towns(current_site, tgt_site, sites, ctx.world)
|
||||
&& let Some(tracks) = path_towns(current_site, tgt_site, sites, ctx.world)
|
||||
{
|
||||
// For every track in the path we discovered between the sites...
|
||||
seq(tracks
|
||||
@ -408,6 +414,53 @@ fn timeout(ctx: &NpcCtx, time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone +
|
||||
move |ctx| ctx.time.0 > end
|
||||
}
|
||||
|
||||
fn villager() -> impl Action {
|
||||
choose(|ctx| {
|
||||
if let Some(home) = ctx.npc.home {
|
||||
if ctx.npc.current_site != Some(home) {
|
||||
// Travel home if we're not there already
|
||||
important(travel_to_site(home))
|
||||
} else if matches!(
|
||||
ctx.npc.profession,
|
||||
Some(Profession::Merchant | Profession::Blacksmith)
|
||||
) {
|
||||
// Trade professions just walk between town plazas
|
||||
casual(now(move |ctx| {
|
||||
// Choose a plaza in the NPC's home site to walk to
|
||||
if let Some(plaza_wpos) = ctx
|
||||
.state
|
||||
.data()
|
||||
.sites
|
||||
.get(home)
|
||||
.and_then(|home| ctx.index.sites.get(home.world_site?).site2())
|
||||
.and_then(|site2| {
|
||||
let plaza = &site2.plots[site2.plazas().choose(&mut thread_rng())?];
|
||||
Some(site2.tile_center_wpos(plaza.root_tile()).as_())
|
||||
})
|
||||
{
|
||||
// Walk to the plaza...
|
||||
goto_2d(plaza_wpos, 0.5)
|
||||
// ...then wait for some time before moving on
|
||||
.then(now(|ctx| {
|
||||
let wait_time = thread_rng().gen_range(10.0..30.0);
|
||||
idle().repeat().stop_if(timeout(ctx, wait_time))
|
||||
}))
|
||||
.map(|_| ())
|
||||
.boxed()
|
||||
} else {
|
||||
// No plazas? :(
|
||||
finish().boxed()
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
casual(idle())
|
||||
}
|
||||
} else {
|
||||
casual(finish()) // Nothing to do if we're homeless!
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn think() -> impl Action {
|
||||
choose(|ctx| {
|
||||
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
|
||||
@ -431,17 +484,8 @@ fn think() -> impl Action {
|
||||
} else {
|
||||
casual(finish())
|
||||
}
|
||||
} else if matches!(ctx.npc.profession, Some(Profession::Blacksmith)) {
|
||||
casual(idle())
|
||||
} else {
|
||||
casual(
|
||||
now(|ctx| goto(ctx.npc.wpos + Vec3::unit_x() * 10.0, 1.0))
|
||||
.then(now(|ctx| goto(ctx.npc.wpos - Vec3::unit_x() * 10.0, 1.0)))
|
||||
.repeat()
|
||||
.stop_if(timeout(ctx, 10.0))
|
||||
.then(now(|ctx| idle().repeat().stop_if(timeout(ctx, 5.0))))
|
||||
.map(|_| {}),
|
||||
)
|
||||
casual(villager())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -305,6 +305,25 @@ impl Site {
|
||||
| SiteKind::Settlement(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the inner site2 site, if this site has one.
|
||||
// TODO: Remove all of this when site1 gets removed.
|
||||
pub fn site2(&self) -> Option<&site2::Site> {
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(_) => None,
|
||||
SiteKind::Dungeon(site2) => Some(site2),
|
||||
SiteKind::Castle(_) => None,
|
||||
SiteKind::Refactor(site2) => Some(site2),
|
||||
SiteKind::CliffTown(site2) => Some(site2),
|
||||
SiteKind::SavannahPit(site2) => Some(site2),
|
||||
SiteKind::Tree(_) => None,
|
||||
SiteKind::DesertCity(site2) => Some(site2),
|
||||
SiteKind::ChapelSite(site2) => Some(site2),
|
||||
SiteKind::GiantTree(site2) => Some(site2),
|
||||
SiteKind::Gnarling(site2) => Some(site2),
|
||||
SiteKind::Bridge(site2) => Some(site2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SiteKind {
|
||||
|
Loading…
Reference in New Issue
Block a user