Use cheap RNG in NPC AI code

This commit is contained in:
Joshua Barretto 2023-04-03 21:41:22 +01:00
parent 2e047f6723
commit b2627e2690
4 changed files with 25 additions and 23 deletions

1
Cargo.lock generated
View File

@ -6952,6 +6952,7 @@ dependencies = [
"hashbrown 0.12.3", "hashbrown 0.12.3",
"itertools", "itertools",
"rand 0.8.5", "rand 0.8.5",
"rand_chacha 0.3.1",
"rayon", "rayon",
"rmp-serde", "rmp-serde",
"ron 0.8.0", "ron 0.8.0",

View File

@ -17,6 +17,7 @@ tracing = "0.1"
atomic_refcell = "0.1" atomic_refcell = "0.1"
slotmap = { version = "1.0.6", features = ["serde"] } slotmap = { version = "1.0.6", features = ["serde"] }
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
rand_chacha = "0.3"
fxhash = "0.2.1" fxhash = "0.2.1"
itertools = "0.10.3" itertools = "0.10.3"
rayon = "1.5" rayon = "1.5"

View File

@ -3,6 +3,7 @@ use crate::{
RtState, RtState,
}; };
use common::resources::{Time, TimeOfDay}; use common::resources::{Time, TimeOfDay};
use rand_chacha::ChaChaRng;
use std::{any::Any, marker::PhantomData, ops::ControlFlow}; use std::{any::Any, marker::PhantomData, ops::ControlFlow};
use world::{IndexRef, World}; use world::{IndexRef, World};
@ -20,6 +21,8 @@ pub struct NpcCtx<'a> {
pub npc_id: NpcId, pub npc_id: NpcId,
pub npc: &'a Npc, pub npc: &'a Npc,
pub controller: &'a mut Controller, pub controller: &'a mut Controller,
pub rng: ChaChaRng,
} }
/// A trait that describes 'actions': long-running tasks performed by rtsim /// A trait that describes 'actions': long-running tasks performed by rtsim

View File

@ -22,6 +22,7 @@ use common::{
use fxhash::FxHasher64; use fxhash::FxHasher64;
use itertools::Itertools; use itertools::Itertools;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaChaRng;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use vek::*; use vek::*;
use world::{ use world::{
@ -245,6 +246,7 @@ impl Rule for NpcAi {
npc, npc,
npc_id: *npc_id, npc_id: *npc_id,
controller, controller,
rng: ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>()),
}); });
}); });
} }
@ -462,17 +464,16 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
fn socialize() -> impl Action { fn socialize() -> impl Action {
now(|ctx| { now(|ctx| {
let mut rng = thread_rng();
// TODO: Bit odd, should wait for a while after greeting // TODO: Bit odd, should wait for a while after greeting
if thread_rng().gen_bool(0.0003) && let Some(other) = ctx if ctx.rng.gen_bool(0.0003) && let Some(other) = ctx
.state .state
.data() .data()
.npcs .npcs
.nearby(ctx.npc.wpos.xy(), 8.0) .nearby(ctx.npc.wpos.xy(), 8.0)
.choose(&mut rng) .choose(&mut ctx.rng)
{ {
just(move |ctx| ctx.controller.greet(other)).boxed() just(move |ctx| ctx.controller.greet(other)).boxed()
} else if thread_rng().gen_bool(0.0003) { } else if ctx.rng.gen_bool(0.0003) {
just(|ctx| ctx.controller.do_dance()) just(|ctx| ctx.controller.do_dance())
.repeat() .repeat()
.stop_if(timeout(6.0)) .stop_if(timeout(6.0))
@ -504,7 +505,7 @@ fn adventure() -> impl Action {
| SiteKind::DesertCity(_) | SiteKind::DesertCity(_)
), ),
) && ctx.npc.current_site.map_or(true, |cs| *site_id != cs) ) && ctx.npc.current_site.map_or(true, |cs| *site_id != cs)
&& thread_rng().gen_bool(0.25) && ctx.rng.gen_bool(0.25)
}) })
.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)
@ -549,10 +550,10 @@ fn hunt_animals() -> impl Action {
just(|ctx| ctx.controller.do_hunt_animals()).debug(|| "hunt_animals") just(|ctx| ctx.controller.do_hunt_animals()).debug(|| "hunt_animals")
} }
fn find_forest(ctx: &NpcCtx) -> Option<Vec2<f32>> { fn find_forest(ctx: &mut NpcCtx) -> Option<Vec2<f32>> {
let chunk_pos = ctx.npc.wpos.xy().as_() / TerrainChunkSize::RECT_SIZE.as_(); let chunk_pos = ctx.npc.wpos.xy().as_() / TerrainChunkSize::RECT_SIZE.as_();
Spiral2d::new() Spiral2d::new()
.skip(thread_rng().gen_range(1..=8)) .skip(ctx.rng.gen_range(1..=8))
.take(49) .take(49)
.map(|rpos| chunk_pos + rpos) .map(|rpos| chunk_pos + rpos)
.find(|cpos| { .find(|cpos| {
@ -604,7 +605,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
let house = site2 let house = site2
.plots() .plots()
.filter(|p| matches!(p.kind(), PlotKind::House(_))) .filter(|p| matches!(p.kind(), PlotKind::House(_)))
.choose(&mut thread_rng())?; .choose(&mut ctx.rng)?;
Some(site2.tile_center_wpos(house.root_tile()).as_()) Some(site2.tile_center_wpos(house.root_tile()).as_())
}) })
{ {
@ -623,37 +624,33 @@ fn villager(visiting_site: SiteId) -> impl Action {
.debug(|| "find somewhere to sleep"), .debug(|| "find somewhere to sleep"),
); );
// Villagers with roles should perform those roles // Villagers with roles should perform those roles
} else if matches!(ctx.npc.profession, Some(Profession::Herbalist)) } else if matches!(ctx.npc.profession, Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8)
&& thread_rng().gen_bool(0.8)
{ {
if let Some(forest_wpos) = find_forest(ctx) { if let Some(forest_wpos) = find_forest(ctx) {
return casual( return casual(
travel_to_point(forest_wpos, 0.5) travel_to_point(forest_wpos, 0.5)
.debug(|| "walk to forest") .debug(|| "walk to forest")
.then({ .then({
let wait_time = thread_rng().gen_range(10.0..30.0); let wait_time = ctx.rng.gen_range(10.0..30.0);
gather_ingredients().repeat().stop_if(timeout(wait_time)) gather_ingredients().repeat().stop_if(timeout(wait_time))
}) })
.map(|_| ()), .map(|_| ()),
); );
} }
} else if matches!(ctx.npc.profession, Some(Profession::Hunter)) } else if matches!(ctx.npc.profession, Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
&& thread_rng().gen_bool(0.8)
{
if let Some(forest_wpos) = find_forest(ctx) { if let Some(forest_wpos) = find_forest(ctx) {
return casual( return casual(
just(|ctx| ctx.controller.say("Time to go hunting!")) just(|ctx| ctx.controller.say("Time to go hunting!"))
.then(travel_to_point(forest_wpos, 0.75)) .then(travel_to_point(forest_wpos, 0.75))
.debug(|| "walk to forest") .debug(|| "walk to forest")
.then({ .then({
let wait_time = thread_rng().gen_range(30.0..60.0); let wait_time = ctx.rng.gen_range(30.0..60.0);
hunt_animals().repeat().stop_if(timeout(wait_time)) hunt_animals().repeat().stop_if(timeout(wait_time))
}) })
.map(|_| ()), .map(|_| ()),
); );
} }
} else if matches!(ctx.npc.profession, Some(Profession::Merchant)) } else if matches!(ctx.npc.profession, Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
&& thread_rng().gen_bool(0.8)
{ {
return casual( return casual(
just(|ctx| { just(|ctx| {
@ -665,7 +662,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
"Looking for supplies? I've got you covered.", "Looking for supplies? I've got you covered.",
] ]
.iter() .iter()
.choose(&mut thread_rng()) .choose(&mut ctx.rng)
.unwrap(), .unwrap(),
) // Can't fail ) // Can't fail
}) })
@ -687,7 +684,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
.get(visiting_site) .get(visiting_site)
.and_then(|site| ctx.index.sites.get(site.world_site?).site2()) .and_then(|site| ctx.index.sites.get(site.world_site?).site2())
.and_then(|site2| { .and_then(|site2| {
let plaza = &site2.plots[site2.plazas().choose(&mut thread_rng())?]; let plaza = &site2.plots[site2.plazas().choose(&mut ctx.rng)?];
Some(site2.tile_center_wpos(plaza.root_tile()).as_()) Some(site2.tile_center_wpos(plaza.root_tile()).as_())
}) })
{ {
@ -696,7 +693,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
.debug(|| "walk to plaza") .debug(|| "walk to plaza")
// ...then wait for some time before moving on // ...then wait for some time before moving on
.then({ .then({
let wait_time = thread_rng().gen_range(30.0..90.0); let wait_time = ctx.rng.gen_range(30.0..90.0);
socialize().repeat().stop_if(timeout(wait_time)) socialize().repeat().stop_if(timeout(wait_time))
.debug(|| "wait at plaza") .debug(|| "wait at plaza")
}) })
@ -794,7 +791,7 @@ fn pilot() -> impl Action {
.and_then(|site| ctx.index.sites.get(site).kind.convert_to_meta()) .and_then(|site| ctx.index.sites.get(site).kind.convert_to_meta())
.map_or(false, |meta| matches!(meta, SiteKindMeta::Settlement(_))) .map_or(false, |meta| matches!(meta, SiteKindMeta::Settlement(_)))
}) })
.choose(&mut thread_rng()); .choose(&mut ctx.rng);
if let Some((_id, site)) = site { if let Some((_id, site)) = site {
let start_chunk = let start_chunk =
ctx.npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>(); ctx.npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
@ -826,7 +823,7 @@ fn captain() -> impl Action {
.get(*neighbor) .get(*neighbor)
.map_or(false, |c| c.river.river_kind.is_some()) .map_or(false, |c| c.river.river_kind.is_some())
}) })
.choose(&mut thread_rng()) .choose(&mut ctx.rng)
{ {
let wpos = TerrainChunkSize::center_wpos(chunk); let wpos = TerrainChunkSize::center_wpos(chunk);
let wpos = wpos.as_().with_z( let wpos = wpos.as_().with_z(
@ -891,7 +888,7 @@ fn bird_large() -> impl Action {
matches!(ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)) matches!(ctx.index.sites.get(site).kind, SiteKind::Dungeon(_))
}) })
}) })
.choose(&mut thread_rng()) .choose(&mut ctx.rng)
{ {
casual(goto( casual(goto(
site.wpos.as_::<f32>().with_z( site.wpos.as_::<f32>().with_z(