mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
tavern rtsim
This commit is contained in:
parent
c1aa9bd1b6
commit
a3a19ecc3a
@ -298,6 +298,12 @@ pub struct VolumeMounting {
|
|||||||
pub rider: Uid,
|
pub rider: Uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VolumeMounting {
|
||||||
|
pub fn is_steering_entity(&self) -> bool {
|
||||||
|
matches!(self.pos.kind, Volume::Entity(..)) && self.block.is_controller()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Link for VolumeMounting {
|
impl Link for VolumeMounting {
|
||||||
type CreateData<'a> = (
|
type CreateData<'a> = (
|
||||||
Write<'a, VolumeRiders>,
|
Write<'a, VolumeRiders>,
|
||||||
|
@ -7,6 +7,10 @@ use std::ops::{Mul, MulAssign};
|
|||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct TimeOfDay(pub f64);
|
pub struct TimeOfDay(pub f64);
|
||||||
|
|
||||||
|
impl TimeOfDay {
|
||||||
|
pub fn day(&self) -> f64 { self.0.rem_euclid(24.0 * 3600.0) }
|
||||||
|
}
|
||||||
|
|
||||||
/// A resource that stores the tick (i.e: physics) time.
|
/// A resource that stores the tick (i.e: physics) time.
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Time(pub f64);
|
pub struct Time(pub f64);
|
||||||
|
@ -252,7 +252,7 @@ pub enum NpcActivity {
|
|||||||
HuntAnimals,
|
HuntAnimals,
|
||||||
Dance(Option<Dir>),
|
Dance(Option<Dir>),
|
||||||
Cheer(Option<Dir>),
|
Cheer(Option<Dir>),
|
||||||
Sit(Option<Dir>),
|
Sit(Option<Dir>, Option<Vec3<i32>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents event-like actions that rtsim NPCs can perform to interact with
|
/// Represents event-like actions that rtsim NPCs can perform to interact with
|
||||||
|
@ -79,7 +79,9 @@ impl Controller {
|
|||||||
|
|
||||||
pub fn do_cheer(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Cheer(dir)); }
|
pub fn do_cheer(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Cheer(dir)); }
|
||||||
|
|
||||||
pub fn do_sit(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Sit(dir)); }
|
pub fn do_sit(&mut self, dir: Option<Dir>, pos: Option<Vec3<i32>>) {
|
||||||
|
self.activity = Some(NpcActivity::Sit(dir, pos));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
|
pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
|
||||||
self.actions.push(NpcAction::Say(target.into(), content));
|
self.actions.push(NpcAction::Say(target.into(), content));
|
||||||
|
@ -38,7 +38,7 @@ use vek::*;
|
|||||||
use world::{
|
use world::{
|
||||||
civ::{self, Track},
|
civ::{self, Track},
|
||||||
site::{Site as WorldSite, SiteKind},
|
site::{Site as WorldSite, SiteKind},
|
||||||
site2::{self, PlotKind, TileKind},
|
site2::{self, plot::tavern, PlotKind, TileKind},
|
||||||
util::NEIGHBORS,
|
util::NEIGHBORS,
|
||||||
IndexRef, World,
|
IndexRef, World,
|
||||||
};
|
};
|
||||||
@ -325,7 +325,7 @@ impl Rule for NpcAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idle<S: State>() -> impl Action<S> { just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") }
|
fn idle<S: State>() -> impl Action<S> + Clone { just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") }
|
||||||
|
|
||||||
/// Try to walk toward a 3D position without caring for obstacles.
|
/// Try to walk toward a 3D position without caring for obstacles.
|
||||||
fn goto<S: State>(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action<S> {
|
fn goto<S: State>(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action<S> {
|
||||||
@ -578,7 +578,7 @@ fn travel_to_site<S: State>(tgt_site: SiteId, speed_factor: f32) -> impl Action<
|
|||||||
.map(|_, _| ())
|
.map(|_, _| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn talk_to<S: State>(tgt: Actor, _subject: Option<Subject>) -> impl Action<S> {
|
fn talk_to<S: State>(tgt: Actor, _subject: Option<Subject>) -> impl Action<S> + Clone {
|
||||||
now(move |ctx, _| {
|
now(move |ctx, _| {
|
||||||
if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) {
|
if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) {
|
||||||
// Cut off the conversation sometimes to avoid infinite conversations (but only
|
// Cut off the conversation sometimes to avoid infinite conversations (but only
|
||||||
@ -630,7 +630,7 @@ fn talk_to<S: State>(tgt: Actor, _subject: Option<Subject>) -> impl Action<S> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn socialize() -> impl Action<EveryRange> {
|
fn socialize() -> impl Action<EveryRange> + Clone {
|
||||||
now(move |ctx, socialize: &mut EveryRange| {
|
now(move |ctx, socialize: &mut EveryRange| {
|
||||||
// Skip most socialising actions if we're not loaded
|
// Skip most socialising actions if we're not loaded
|
||||||
if matches!(ctx.npc.mode, SimulationMode::Loaded) && socialize.should(ctx) {
|
if matches!(ctx.npc.mode, SimulationMode::Loaded) && socialize.should(ctx) {
|
||||||
@ -758,6 +758,8 @@ fn choose_plaza(ctx: &mut NpcCtx, site: SiteId) -> Option<Vec2<f32>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WALKING_SPEED: f32 = 0.35;
|
||||||
|
|
||||||
fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
||||||
choose(move |ctx, state: &mut DefaultState| {
|
choose(move |ctx, state: &mut DefaultState| {
|
||||||
// Consider moving home if the home site gets too full
|
// Consider moving home if the home site gets too full
|
||||||
@ -804,8 +806,9 @@ fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
|||||||
.then(travel_to_site(new_home, 0.5))
|
.then(travel_to_site(new_home, 0.5))
|
||||||
.then(just(move |ctx, _| ctx.controller.set_new_home(new_home))));
|
.then(just(move |ctx, _| ctx.controller.set_new_home(new_home))));
|
||||||
}
|
}
|
||||||
|
let day_period = DayPeriod::from(ctx.time_of_day.0);
|
||||||
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
let is_weekend = ctx.time_of_day.day() as u64 % 6 == 0;
|
||||||
|
if day_period.is_dark()
|
||||||
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||||
{
|
{
|
||||||
return important(
|
return important(
|
||||||
@ -845,18 +848,18 @@ fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
|||||||
})
|
})
|
||||||
.debug(|| "find somewhere to sleep"),
|
.debug(|| "find somewhere to sleep"),
|
||||||
);
|
);
|
||||||
// Villagers with roles should perform those roles
|
|
||||||
}
|
}
|
||||||
// Visiting villagers in DesertCity who are not Merchants should sit down in the Arena during the day
|
// Go do something fun on evenings and holidays, or on random days.
|
||||||
else if matches!(ctx.state.data().sites[visiting_site].world_site.map(|ws| &ctx.index.sites.get(ws).kind), Some(SiteKind::DesertCity(_)))
|
else if
|
||||||
&& !matches!(ctx.npc.profession(), Some(Profession::Merchant | Profession::Guard))
|
// Ain't no rest for the wicked
|
||||||
&& ctx.rng.gen_bool(1.0 / 3.0)
|
!matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||||
{
|
&& (matches!(day_period, DayPeriod::Evening) || is_weekend || ctx.rng.gen_bool(0.05)) {
|
||||||
let wait_time = ctx.rng.gen_range(100.0..300.0);
|
let mut fun_stuff = Vec::new();
|
||||||
|
|
||||||
if let Some(ws_id) = ctx.state.data().sites[visiting_site].world_site
|
if let Some(ws_id) = ctx.state.data().sites[visiting_site].world_site
|
||||||
&& let Some(ws) = ctx.index.sites.get(ws_id).site2()
|
&& let Some(ws) = ctx.index.sites.get(ws_id).site2() {
|
||||||
&& let Some(arena) = ws.plots().find_map(|p| match p.kind() { PlotKind::DesertCityArena(a) => Some(a), _ => None})
|
if let Some(arena) = ws.plots().find_map(|p| match p.kind() { PlotKind::DesertCityArena(a) => Some(a), _ => None}) {
|
||||||
{
|
let wait_time = ctx.rng.gen_range(100.0..300.0);
|
||||||
// We don't use Z coordinates for seats because they are complicated to calculate from the Ramp procedural generation
|
// We don't use Z coordinates for seats because they are complicated to calculate from the Ramp procedural generation
|
||||||
// and using goto_2d seems to work just fine. However it also means that NPC will never go seat on the stands
|
// and using goto_2d seems to work just fine. However it also means that NPC will never go seat on the stands
|
||||||
// on the first floor of the arena. This is a compromise that was made because in the current arena procedural generation
|
// on the first floor of the arena. This is a compromise that was made because in the current arena procedural generation
|
||||||
@ -874,7 +877,7 @@ fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
|||||||
};
|
};
|
||||||
let look_dir = Dir::from_unnormalized(arena_center - seat);
|
let look_dir = Dir::from_unnormalized(arena_center - seat);
|
||||||
// Walk to an arena seat, cheer, sit and dance
|
// Walk to an arena seat, cheer, sit and dance
|
||||||
return casual(just(move |ctx, _| ctx.controller.say(None, Content::localized("npc-speech-arena")))
|
let action = casual(just(move |ctx, _| ctx.controller.say(None, Content::localized("npc-speech-arena")))
|
||||||
.then(goto_2d(seat.xy(), 0.6, 1.0).debug(|| "go to arena"))
|
.then(goto_2d(seat.xy(), 0.6, 1.0).debug(|| "go to arena"))
|
||||||
// Turn toward the centre of the arena and watch the action!
|
// Turn toward the centre of the arena and watch the action!
|
||||||
.then(choose(move |ctx, _| if ctx.rng.gen_bool(0.3) {
|
.then(choose(move |ctx, _| if ctx.rng.gen_bool(0.3) {
|
||||||
@ -882,14 +885,105 @@ fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
|||||||
} else if ctx.rng.gen_bool(0.15) {
|
} else if ctx.rng.gen_bool(0.15) {
|
||||||
casual(just(move |ctx,_| ctx.controller.do_dance(look_dir)).repeat().stop_if(timeout(5.0)))
|
casual(just(move |ctx,_| ctx.controller.do_dance(look_dir)).repeat().stop_if(timeout(5.0)))
|
||||||
} else {
|
} else {
|
||||||
casual(just(move |ctx,_| ctx.controller.do_sit(look_dir)).repeat().stop_if(timeout(15.0)))
|
casual(just(move |ctx,_| ctx.controller.do_sit(look_dir, None)).repeat().stop_if(timeout(15.0)))
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(timeout(wait_time)))
|
.stop_if(timeout(wait_time)))
|
||||||
.map(|_, _| ())
|
.map(|_, _| ())
|
||||||
.boxed());
|
.boxed());
|
||||||
|
fun_stuff.push(action);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession(), Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8)
|
if let Some(tavern) = ws.plots().filter_map(|p| match p.kind() { PlotKind::Tavern(a) => Some(a), _ => None }).choose(&mut ctx.rng) {
|
||||||
|
let wait_time = ctx.rng.gen_range(100.0..300.0);
|
||||||
|
|
||||||
|
let (stage_aabr, stage_z) = tavern.rooms.values().flat_map(|room| {
|
||||||
|
room.details.iter().filter_map(|detail| match detail {
|
||||||
|
tavern::Detail::Stage { aabr } => Some((*aabr, room.bounds.min.z + 1)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}).choose(&mut ctx.rng).unwrap_or((tavern.bounds, tavern.door_wpos.z));
|
||||||
|
|
||||||
|
let bar_pos = tavern.rooms.values().flat_map(|room|
|
||||||
|
room.details.iter().filter_map(|detail| match detail {
|
||||||
|
tavern::Detail::Bar { aabr } => {
|
||||||
|
let side = site2::util::Dir::from_vec2(room.bounds.center().xy() - aabr.center());
|
||||||
|
let pos = side.select_aabr_with(*aabr, aabr.center()) + side.to_vec2();
|
||||||
|
|
||||||
|
Some(pos.with_z(room.bounds.min.z))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
).choose(&mut ctx.rng).unwrap_or(stage_aabr.center().with_z(stage_z));
|
||||||
|
|
||||||
|
// Pick a chair that is theirs for the stay
|
||||||
|
let chair_pos = tavern.rooms.values().flat_map(|room| {
|
||||||
|
let z = room.bounds.min.z;
|
||||||
|
room.details.iter().filter_map(move |detail| match detail {
|
||||||
|
tavern::Detail::Table { pos, chairs } => Some(chairs.into_iter().map(move |dir| pos.with_z(z) + dir.to_vec2())),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
).choose(&mut ctx.rng)
|
||||||
|
// This path is possible, but highly unlikely.
|
||||||
|
.unwrap_or(bar_pos);
|
||||||
|
|
||||||
|
let stage_aabr = stage_aabr.as_::<f32>();
|
||||||
|
let stage_z = stage_z as f32;
|
||||||
|
|
||||||
|
let action = casual(travel_to_point(tavern.door_wpos.xy().as_() + 0.5, 0.8).then(choose(move |ctx, (last_action, _)| {
|
||||||
|
let action = [0, 1, 2].into_iter().filter(|i| *last_action != Some(*i)).choose(&mut ctx.rng).expect("We have at least 2 elements");
|
||||||
|
let socialize = socialize().map_state(|(_, timer)| timer).repeat();
|
||||||
|
match action {
|
||||||
|
// Go and dance on a stage.
|
||||||
|
0 => {
|
||||||
|
casual(now(move |ctx, (last_action, _)| {
|
||||||
|
*last_action = Some(action);
|
||||||
|
goto(stage_aabr.min.map2(stage_aabr.max, |a, b| ctx.rng.gen_range(a..b)).with_z(stage_z), WALKING_SPEED, 1.0)
|
||||||
|
})
|
||||||
|
.then(just(move |ctx,_| ctx.controller.do_dance(None)).repeat().stop_if(timeout(ctx.rng.gen_range(20.0..30.0))))
|
||||||
|
.map(|_, _| ())
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// Go and sit at a table.
|
||||||
|
1 => {
|
||||||
|
casual(
|
||||||
|
now(move |ctx, (last_action, _)| {
|
||||||
|
*last_action = Some(action);
|
||||||
|
goto(chair_pos.as_() + 0.5, WALKING_SPEED, 1.0).then(just(move |ctx, _| ctx.controller.do_sit(None, Some(chair_pos)))).then(socialize.clone().stop_if(timeout(ctx.rng.gen_range(30.0..60.0)))).map(|_, _| ())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// Go to the bar.
|
||||||
|
_ => {
|
||||||
|
casual(
|
||||||
|
now(move |ctx, (last_action, _)| {
|
||||||
|
*last_action = Some(action);
|
||||||
|
goto(bar_pos.as_() + 0.5, WALKING_SPEED, 1.0).then(socialize.clone().stop_if(timeout(ctx.rng.gen_range(10.0..25.0)))).map(|_, _| ())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_state((None::<u32>, every_range(5.0..10.0)))
|
||||||
|
.repeat()
|
||||||
|
.stop_if(timeout(wait_time)))
|
||||||
|
.map(|_, _| ())
|
||||||
|
.boxed()
|
||||||
|
);
|
||||||
|
|
||||||
|
fun_stuff.push(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if !fun_stuff.is_empty() {
|
||||||
|
let i = ctx.rng.gen_range(0..fun_stuff.len());
|
||||||
|
return fun_stuff.swap_remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Villagers with roles should perform those roles
|
||||||
|
else if matches!(ctx.npc.profession(), Some(Profession::Herbalist)) && ctx.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(
|
||||||
|
@ -254,7 +254,7 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
| NpcActivity::HuntAnimals
|
| NpcActivity::HuntAnimals
|
||||||
| NpcActivity::Dance(_)
|
| NpcActivity::Dance(_)
|
||||||
| NpcActivity::Cheer(_)
|
| NpcActivity::Cheer(_)
|
||||||
| NpcActivity::Sit(_),
|
| NpcActivity::Sit(..),
|
||||||
) => {
|
) => {
|
||||||
// TODO: Maybe they should walk around randomly
|
// TODO: Maybe they should walk around randomly
|
||||||
// when gathering resources?
|
// when gathering resources?
|
||||||
|
@ -29,6 +29,7 @@ use common::{
|
|||||||
consts::MAX_MOUNT_RANGE,
|
consts::MAX_MOUNT_RANGE,
|
||||||
effect::{BuffEffect, Effect},
|
effect::{BuffEffect, Effect},
|
||||||
event::{Emitter, ServerEvent},
|
event::{Emitter, ServerEvent},
|
||||||
|
mounting::VolumePos,
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
rtsim::NpcActivity,
|
rtsim::NpcActivity,
|
||||||
states::basic_beam,
|
states::basic_beam,
|
||||||
@ -51,9 +52,7 @@ impl<'a> AgentData<'a> {
|
|||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
|
|
||||||
pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
|
pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
self.dismount(controller, read_data);
|
||||||
controller.push_event(ControlEvent::Unmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.push_action(ControlAction::GlideWield);
|
controller.push_action(ControlAction::GlideWield);
|
||||||
|
|
||||||
@ -73,9 +72,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fly_upward(&self, controller: &mut Controller, read_data: &ReadData) {
|
pub fn fly_upward(&self, controller: &mut Controller, read_data: &ReadData) {
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
self.dismount(controller, read_data);
|
||||||
controller.push_event(ControlEvent::Unmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.push_basic_input(InputKind::Fly);
|
controller.push_basic_input(InputKind::Fly);
|
||||||
controller.inputs.move_z = 1.0;
|
controller.inputs.move_z = 1.0;
|
||||||
@ -96,9 +93,7 @@ impl<'a> AgentData<'a> {
|
|||||||
path: Path,
|
path: Path,
|
||||||
speed_multiplier: Option<f32>,
|
speed_multiplier: Option<f32>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
self.dismount(controller, read_data);
|
||||||
controller.push_event(ControlEvent::Unmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
let partial_path_tgt_pos = |pos_difference: Vec3<f32>| {
|
let partial_path_tgt_pos = |pos_difference: Vec3<f32>| {
|
||||||
self.pos.0
|
self.pos.0
|
||||||
@ -242,6 +237,13 @@ impl<'a> AgentData<'a> {
|
|||||||
'activity: {
|
'activity: {
|
||||||
match agent.rtsim_controller.activity {
|
match agent.rtsim_controller.activity {
|
||||||
Some(NpcActivity::Goto(travel_to, speed_factor)) => {
|
Some(NpcActivity::Goto(travel_to, speed_factor)) => {
|
||||||
|
if !read_data
|
||||||
|
.is_volume_riders
|
||||||
|
.get(*self.entity)
|
||||||
|
.map_or(false, |r| r.is_steering_entity())
|
||||||
|
{
|
||||||
|
controller.push_event(ControlEvent::Unmount);
|
||||||
|
}
|
||||||
// If it has an rtsim destination and can fly, then it should.
|
// If it has an rtsim destination and can fly, then it should.
|
||||||
// If it is flying and bumps something above it, then it should move down.
|
// If it is flying and bumps something above it, then it should move down.
|
||||||
if self.traversal_config.can_fly
|
if self.traversal_config.can_fly
|
||||||
@ -399,7 +401,15 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.push_action(ControlAction::Talk);
|
controller.push_action(ControlAction::Talk);
|
||||||
break 'activity; // Don't fall through to idle wandering
|
break 'activity; // Don't fall through to idle wandering
|
||||||
},
|
},
|
||||||
Some(NpcActivity::Sit(dir)) => {
|
Some(NpcActivity::Sit(dir, pos)) => {
|
||||||
|
if let Some(pos) =
|
||||||
|
pos.filter(|p| read_data.terrain.get(*p).is_ok_and(|b| b.is_mountable()))
|
||||||
|
{
|
||||||
|
if !read_data.is_volume_riders.contains(*self.entity) {
|
||||||
|
controller
|
||||||
|
.push_event(ControlEvent::MountVolume(VolumePos::terrain(pos)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if let Some(look_dir) = dir {
|
if let Some(look_dir) = dir {
|
||||||
controller.inputs.look_dir = look_dir;
|
controller.inputs.look_dir = look_dir;
|
||||||
if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 {
|
if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 {
|
||||||
@ -410,6 +420,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.push_action(ControlAction::Sit);
|
controller.push_action(ControlAction::Sit);
|
||||||
|
}
|
||||||
break 'activity; // Don't fall through to idle wandering
|
break 'activity; // Don't fall through to idle wandering
|
||||||
},
|
},
|
||||||
Some(NpcActivity::HuntAnimals) => {
|
Some(NpcActivity::HuntAnimals) => {
|
||||||
@ -581,7 +592,9 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
tgt_pos: &Pos,
|
tgt_pos: &Pos,
|
||||||
) {
|
) {
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
if read_data.is_riders.contains(*self.entity)
|
||||||
|
|| read_data.is_volume_riders.contains(*self.entity)
|
||||||
|
{
|
||||||
controller.push_event(ControlEvent::Unmount);
|
controller.push_event(ControlEvent::Unmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,7 +650,9 @@ impl<'a> AgentData<'a> {
|
|||||||
// Proportion of full speed
|
// Proportion of full speed
|
||||||
const MAX_FLEE_SPEED: f32 = 0.65;
|
const MAX_FLEE_SPEED: f32 = 0.65;
|
||||||
|
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
if read_data.is_riders.contains(*self.entity)
|
||||||
|
|| read_data.is_volume_riders.contains(*self.entity)
|
||||||
|
{
|
||||||
controller.push_event(ControlEvent::Unmount);
|
controller.push_event(ControlEvent::Unmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -993,7 +1008,12 @@ impl<'a> AgentData<'a> {
|
|||||||
#[cfg(feature = "be-dyn-lib")]
|
#[cfg(feature = "be-dyn-lib")]
|
||||||
let rng = &mut thread_rng();
|
let rng = &mut thread_rng();
|
||||||
|
|
||||||
if read_data.is_riders.contains(*self.entity) {
|
if read_data.is_riders.contains(*self.entity)
|
||||||
|
|| !read_data
|
||||||
|
.is_volume_riders
|
||||||
|
.get(*self.entity)
|
||||||
|
.map_or(false, |r| r.is_steering_entity())
|
||||||
|
{
|
||||||
controller.push_event(ControlEvent::Unmount);
|
controller.push_event(ControlEvent::Unmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1999,4 +2019,15 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dismount(&self, controller: &mut Controller, read_data: &ReadData) {
|
||||||
|
if read_data.is_riders.contains(*self.entity)
|
||||||
|
|| !read_data
|
||||||
|
.is_volume_riders
|
||||||
|
.get(*self.entity)
|
||||||
|
.map_or(false, |r| r.is_steering_entity())
|
||||||
|
{
|
||||||
|
controller.push_event(ControlEvent::Unmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,10 +69,22 @@ impl Animation for SteerAnimation {
|
|||||||
let hand_offset = Vec3::new(rot.cos(), 0.0, -rot.sin()) * 0.4 / s_a.scaler * 11.0;
|
let hand_offset = Vec3::new(rot.cos(), 0.0, -rot.sin()) * 0.4 / s_a.scaler * 11.0;
|
||||||
|
|
||||||
next.hand_l.position = helm_center - hand_offset;
|
next.hand_l.position = helm_center - hand_offset;
|
||||||
next.hand_l.orientation = hand_rotation;
|
|
||||||
|
|
||||||
next.hand_r.position = helm_center + hand_offset;
|
next.hand_r.position = helm_center + hand_offset;
|
||||||
next.hand_r.orientation = -hand_rotation;
|
|
||||||
|
let ori_l = Quaternion::rotation_x(
|
||||||
|
PI / 2.0 + (next.hand_l.position.z / next.hand_l.position.x).atan(),
|
||||||
|
);
|
||||||
|
let ori_r = Quaternion::rotation_x(
|
||||||
|
PI / 2.0 - (next.hand_r.position.z / next.hand_r.position.x).atan(),
|
||||||
|
);
|
||||||
|
|
||||||
|
next.hand_l.orientation = hand_rotation * ori_l;
|
||||||
|
next.hand_r.orientation = -hand_rotation * ori_r;
|
||||||
|
|
||||||
|
next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
||||||
|
next.shoulder_l.orientation = ori_r;
|
||||||
|
next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
||||||
|
next.shoulder_r.orientation = ori_l;
|
||||||
|
|
||||||
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2);
|
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2);
|
||||||
next.foot_l.orientation = Quaternion::identity();
|
next.foot_l.orientation = Quaternion::identity();
|
||||||
@ -80,10 +92,6 @@ impl Animation for SteerAnimation {
|
|||||||
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2);
|
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2);
|
||||||
next.foot_r.orientation = Quaternion::identity();
|
next.foot_r.orientation = Quaternion::identity();
|
||||||
|
|
||||||
next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
|
||||||
|
|
||||||
next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
|
||||||
|
|
||||||
next.glider.position = Vec3::new(0.0, 0.0, 10.0);
|
next.glider.position = Vec3::new(0.0, 0.0, 10.0);
|
||||||
next.glider.scale = Vec3::one() * 0.0;
|
next.glider.scale = Vec3::one() * 0.0;
|
||||||
next.hold.position = Vec3::new(0.4, -0.3, -5.8);
|
next.hold.position = Vec3::new(0.4, -0.3, -5.8);
|
||||||
|
@ -22,7 +22,7 @@ mod savannah_hut;
|
|||||||
mod savannah_pit;
|
mod savannah_pit;
|
||||||
mod savannah_workshop;
|
mod savannah_workshop;
|
||||||
mod sea_chapel;
|
mod sea_chapel;
|
||||||
mod tavern;
|
pub mod tavern;
|
||||||
mod troll_cave;
|
mod troll_cave;
|
||||||
mod workshop;
|
mod workshop;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||||||
|
|
||||||
type Neighbor = Option<Id<Room>>;
|
type Neighbor = Option<Id<Room>>;
|
||||||
|
|
||||||
struct Wall {
|
pub struct Wall {
|
||||||
start: Vec2<i32>,
|
start: Vec2<i32>,
|
||||||
end: Vec2<i32>,
|
end: Vec2<i32>,
|
||||||
base_alt: i32,
|
base_alt: i32,
|
||||||
@ -33,6 +33,15 @@ struct Wall {
|
|||||||
door: Option<i32>,
|
door: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Wall {
|
||||||
|
pub fn door_pos(&self) -> Option<Vec3<i32>> {
|
||||||
|
let wall_dir = Dir::from_vec2(self.end - self.start);
|
||||||
|
|
||||||
|
self.door
|
||||||
|
.map(|door| (self.start + wall_dir.to_vec2() * door).with_z(self.base_alt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter, enum_map::Enum)]
|
#[derive(Clone, Copy, EnumIter, enum_map::Enum)]
|
||||||
enum RoomKind {
|
enum RoomKind {
|
||||||
Garden,
|
Garden,
|
||||||
@ -54,7 +63,7 @@ impl RoomKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Detail {
|
pub enum Detail {
|
||||||
Bar {
|
Bar {
|
||||||
aabr: Aabr<i32>,
|
aabr: Aabr<i32>,
|
||||||
},
|
},
|
||||||
@ -67,15 +76,15 @@ enum Detail {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Room {
|
pub struct Room {
|
||||||
/// Inclusive
|
/// Inclusive
|
||||||
bounds: Aabb<i32>,
|
pub bounds: Aabb<i32>,
|
||||||
kind: RoomKind,
|
kind: RoomKind,
|
||||||
// stairs: Option<Id<Stairs>>,
|
// stairs: Option<Id<Stairs>>,
|
||||||
walls: EnumMap<Dir, Vec<Id<Wall>>>,
|
walls: EnumMap<Dir, Vec<Id<Wall>>>,
|
||||||
// TODO: Remove this, used for debugging
|
// TODO: Remove this, used for debugging
|
||||||
detail_areas: Vec<Aabr<i32>>,
|
detail_areas: Vec<Aabr<i32>>,
|
||||||
details: Vec<Detail>,
|
pub details: Vec<Detail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
@ -99,14 +108,14 @@ struct Stairs {
|
|||||||
|
|
||||||
pub struct Tavern {
|
pub struct Tavern {
|
||||||
name: String,
|
name: String,
|
||||||
rooms: Store<Room>,
|
pub rooms: Store<Room>,
|
||||||
stairs: Store<Stairs>,
|
stairs: Store<Stairs>,
|
||||||
walls: Store<Wall>,
|
walls: Store<Wall>,
|
||||||
/// Tile position of the door tile
|
/// Tile position of the door tile
|
||||||
pub door_tile: Vec2<i32>,
|
pub door_tile: Vec2<i32>,
|
||||||
pub(crate) door_wpos: Vec3<i32>,
|
pub door_wpos: Vec3<i32>,
|
||||||
/// Axis aligned bounding region for the house
|
/// Axis aligned bounding region for the house
|
||||||
bounds: Aabr<i32>,
|
pub bounds: Aabr<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tavern {
|
impl Tavern {
|
||||||
@ -138,9 +147,7 @@ impl Tavern {
|
|||||||
let door_tile_center = site.tile_center_wpos(door_tile);
|
let door_tile_center = site.tile_center_wpos(door_tile);
|
||||||
let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center);
|
let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center);
|
||||||
|
|
||||||
let door_alt = land
|
let door_alt = land.get_alt_approx(door_wpos);
|
||||||
.column_sample(door_wpos, index)
|
|
||||||
.map_or_else(|| land.get_alt_approx(door_wpos), |sample| sample.alt);
|
|
||||||
let door_wpos = door_wpos.with_z(door_alt.ceil() as i32);
|
let door_wpos = door_wpos.with_z(door_alt.ceil() as i32);
|
||||||
|
|
||||||
/// Place room in bounds.
|
/// Place room in bounds.
|
||||||
@ -605,10 +612,7 @@ impl Tavern {
|
|||||||
for door_pos in dir_walls.iter().filter_map(|wall_id| {
|
for door_pos in dir_walls.iter().filter_map(|wall_id| {
|
||||||
let wall = &walls[*wall_id];
|
let wall = &walls[*wall_id];
|
||||||
|
|
||||||
wall.door.map(|door| {
|
wall.door_pos()
|
||||||
let wall_dir = Dir::from_vec2(wall.end - wall.start);
|
|
||||||
wall.start + wall_dir.to_vec2() * door
|
|
||||||
})
|
|
||||||
}) {
|
}) {
|
||||||
let orth = dir.orthogonal();
|
let orth = dir.orthogonal();
|
||||||
for i in 0..room.detail_areas.len() {
|
for i in 0..room.detail_areas.len() {
|
||||||
@ -1005,8 +1009,7 @@ impl Structure for Tavern {
|
|||||||
.fill(dark_wood.clone());
|
.fill(dark_wood.clone());
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if let Some(door) = wall.door {
|
if let Some(door_pos) = wall.door_pos() {
|
||||||
let door_pos = wall.start + wall_dir.to_vec2() * door;
|
|
||||||
let min = match wall.from {
|
let min = match wall.from {
|
||||||
None => door_pos - wall.to_dir.to_vec2(),
|
None => door_pos - wall.to_dir.to_vec2(),
|
||||||
Some(_) => door_pos,
|
Some(_) => door_pos,
|
||||||
@ -1056,9 +1059,8 @@ impl Structure for Tavern {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if let Some(door) = wall.door {
|
if let Some(door_pos) = wall.door_pos() {
|
||||||
let door_pos = wall.start + wall_dir.to_vec2() * door;
|
let diff = door_pos.xy() - wall_aabb.center().xy();
|
||||||
let diff = door_pos - wall_aabb.center().xy();
|
|
||||||
let orth = if diff == Vec2::zero() {
|
let orth = if diff == Vec2::zero() {
|
||||||
wall_dir
|
wall_dir
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user