From 84eb7b0653ae7550be1f9c6fdda17d9e984cdbee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 5 Jan 2023 17:15:33 +0000 Subject: [PATCH] Made adventurers explore sites before moving on --- rtsim/src/rule/npc_ai.rs | 124 +++++++++++++++++--------------- rtsim/src/rule/simulate_npcs.rs | 55 +++++++------- server/src/cmd.rs | 2 + 3 files changed, 95 insertions(+), 86 deletions(-) diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 480dd62185..aee8047f85 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -423,7 +423,7 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync { } fn adventure() -> impl Action { - now(|ctx| { + choose(|ctx| { // Choose a random site that's fairly close by if let Some(tgt_site) = ctx .state @@ -441,33 +441,43 @@ fn adventure() -> impl Action { .map(|(site_id, _)| site_id) { // Travel to the site - travel_to_site(tgt_site) + important( + travel_to_site(tgt_site) // Stop for a few minutes - .then(villager().stop_if(timeout(60.0 * 3.0))) + .then(villager(tgt_site).repeat().stop_if(timeout(60.0 * 3.0))) .map(|_| ()) - .boxed() + .boxed(), + ) } else { - finish().boxed() + casual(finish().boxed()) } }) + .debug(move || format!("adventure")) } -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 - urgent(travel_to_site(home).debug(move || format!("travel home"))) - } else if DayPeriod::from(ctx.time_of_day.0).is_dark() { - important(now(move |ctx| { +fn villager(visiting_site: SiteId) -> impl Action { + choose(move |ctx| { + if ctx.npc.current_site != Some(visiting_site) { + let npc_home = ctx.npc.home; + // Travel to the site we're supposed to be in + urgent(travel_to_site(visiting_site).debug(move || { + if npc_home == Some(visiting_site) { + format!("travel home") + } else { + format!("travel to visiting site") + } + })) + } else if DayPeriod::from(ctx.time_of_day.0).is_dark() { + important( + now(move |ctx| { if let Some(house_wpos) = ctx .state .data() .sites - .get(home) - .and_then(|home| ctx.index.sites.get(home.world_site?).site2()) + .get(visiting_site) + .and_then(|site| ctx.index.sites.get(site.world_site?).site2()) .and_then(|site2| { - // Find a house + // Find a house in the site we're visiting let house = site2 .plots() .filter(|p| matches!(p.kind(), PlotKind::House(_))) @@ -484,57 +494,55 @@ fn villager() -> impl Action { } else { finish().boxed() } - })) - } 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) - .debug(|| "walk to plaza") - // ...then wait for some time before moving on - .then({ - let wait_time = thread_rng().gen_range(10.0..30.0); - idle().repeat().stop_if(timeout(wait_time)) - .debug(|| "wait at plaza") - }) - .map(|_| ()) - .boxed() - } else { - // No plazas? :( - finish().boxed() - } - })) - } else { - casual(idle()) - } + }) + .debug(|| "find somewhere to sleep"), + ) } else { - casual(finish()) // Nothing to do if we're homeless! + casual(now(move |ctx| { + // Choose a plaza in the site we're visiting to walk to + if let Some(plaza_wpos) = ctx + .state + .data() + .sites + .get(visiting_site) + .and_then(|site| ctx.index.sites.get(site.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) + .debug(|| "walk to plaza") + // ...then wait for some time before moving on + .then({ + let wait_time = thread_rng().gen_range(10.0..30.0); + idle().repeat().stop_if(timeout(wait_time)) + .debug(|| "wait at plaza") + }) + .map(|_| ()) + .boxed() + } else { + // No plazas? :( + finish().boxed() + } + })) } }) - .debug(move || format!("villager")) + .debug(move || format!("villager at site {:?}", visiting_site)) } fn think() -> impl Action { choose(|ctx| { - if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) { + if matches!( + ctx.npc.profession, + Some(Profession::Adventurer(_) | Profession::Merchant) + ) { casual(adventure()) + } else if let Some(home) = ctx.npc.home { + casual(villager(home)) } else { - casual(villager()) + casual(finish()) // Homeless } }) } diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index 06a816b6dd..4bb83f02f6 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -9,40 +9,39 @@ impl Rule for SimulateNpcs { fn start(rtstate: &mut RtState) -> Result { rtstate.bind::(|ctx| { let data = &mut *ctx.state.data_mut(); - for npc in data - .npcs - .values_mut() - .filter(|npc| matches!(npc.mode, NpcMode::Simulated)) - { - let body = npc.get_body(); - - // Move NPCs if they have a target destination - if let Some((target, speed_factor)) = npc.goto { - let diff = target.xy() - npc.wpos.xy(); - let dist2 = diff.magnitude_squared(); - - if dist2 > 0.5f32.powi(2) { - npc.wpos += (diff - * (body.max_speed_approx() * speed_factor * ctx.event.dt - / dist2.sqrt()) - .min(1.0)) - .with_z(0.0); - } - } - - // Make sure NPCs remain on the surface - npc.wpos.z = ctx - .world - .sim() - .get_alt_approx(npc.wpos.xy().map(|e| e as i32)) - .unwrap_or(0.0); - + for npc in data.npcs.values_mut() { // Update the NPC's current site, if any npc.current_site = ctx .world .sim() .get(npc.wpos.xy().as_::() / TerrainChunkSize::RECT_SIZE.as_()) .and_then(|chunk| data.sites.world_site_map.get(chunk.sites.first()?).copied()); + + // Simulate the NPC's movement and interactions + if matches!(npc.mode, NpcMode::Simulated) { + let body = npc.get_body(); + + // Move NPCs if they have a target destination + if let Some((target, speed_factor)) = npc.goto { + let diff = target.xy() - npc.wpos.xy(); + let dist2 = diff.magnitude_squared(); + + if dist2 > 0.5f32.powi(2) { + npc.wpos += (diff + * (body.max_speed_approx() * speed_factor * ctx.event.dt + / dist2.sqrt()) + .min(1.0)) + .with_z(0.0); + } + } + + // Make sure NPCs remain on the surface + npc.wpos.z = ctx + .world + .sim() + .get_alt_approx(npc.wpos.xy().map(|e| e as i32)) + .unwrap_or(0.0); + } } }); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 3a887e7513..6f7cf7b884 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1237,6 +1237,8 @@ fn handle_npc_info( let _ = writeln!(&mut info, "Seed: {}", npc.seed); let _ = writeln!(&mut info, "Profession: {:?}", npc.profession); let _ = writeln!(&mut info, "Home: {:?}", npc.home); + let _ = writeln!(&mut info, "-- Status --"); + let _ = writeln!(&mut info, "Current site: {:?}", npc.current_site); let _ = writeln!(&mut info, "Current mode: {:?}", npc.mode); let _ = writeln!(&mut info, "-- Action State --"); if let Some(brain) = &npc.brain {