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,
|
||||
}
|
||||
|
||||
impl VolumeMounting {
|
||||
pub fn is_steering_entity(&self) -> bool {
|
||||
matches!(self.pos.kind, Volume::Entity(..)) && self.block.is_controller()
|
||||
}
|
||||
}
|
||||
|
||||
impl Link for VolumeMounting {
|
||||
type CreateData<'a> = (
|
||||
Write<'a, VolumeRiders>,
|
||||
|
@ -7,6 +7,10 @@ use std::ops::{Mul, MulAssign};
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
|
||||
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.
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Time(pub f64);
|
||||
|
@ -252,7 +252,7 @@ pub enum NpcActivity {
|
||||
HuntAnimals,
|
||||
Dance(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
|
||||
|
@ -79,7 +79,9 @@ impl Controller {
|
||||
|
||||
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) {
|
||||
self.actions.push(NpcAction::Say(target.into(), content));
|
||||
|
@ -38,7 +38,7 @@ use vek::*;
|
||||
use world::{
|
||||
civ::{self, Track},
|
||||
site::{Site as WorldSite, SiteKind},
|
||||
site2::{self, PlotKind, TileKind},
|
||||
site2::{self, plot::tavern, PlotKind, TileKind},
|
||||
util::NEIGHBORS,
|
||||
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.
|
||||
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(|_, _| ())
|
||||
}
|
||||
|
||||
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, _| {
|
||||
if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) {
|
||||
// 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| {
|
||||
// Skip most socialising actions if we're not loaded
|
||||
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> {
|
||||
choose(move |ctx, state: &mut DefaultState| {
|
||||
// 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(just(move |ctx, _| ctx.controller.set_new_home(new_home))));
|
||||
}
|
||||
|
||||
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
||||
let day_period = DayPeriod::from(ctx.time_of_day.0);
|
||||
let is_weekend = ctx.time_of_day.day() as u64 % 6 == 0;
|
||||
if day_period.is_dark()
|
||||
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||
{
|
||||
return important(
|
||||
@ -845,51 +848,142 @@ fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
||||
})
|
||||
.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
|
||||
else if matches!(ctx.state.data().sites[visiting_site].world_site.map(|ws| &ctx.index.sites.get(ws).kind), Some(SiteKind::DesertCity(_)))
|
||||
&& !matches!(ctx.npc.profession(), Some(Profession::Merchant | Profession::Guard))
|
||||
&& ctx.rng.gen_bool(1.0 / 3.0)
|
||||
{
|
||||
let wait_time = ctx.rng.gen_range(100.0..300.0);
|
||||
// Go do something fun on evenings and holidays, or on random days.
|
||||
else if
|
||||
// Ain't no rest for the wicked
|
||||
!matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||
&& (matches!(day_period, DayPeriod::Evening) || is_weekend || ctx.rng.gen_bool(0.05)) {
|
||||
let mut fun_stuff = Vec::new();
|
||||
|
||||
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(arena) = ws.plots().find_map(|p| match p.kind() { PlotKind::DesertCityArena(a) => Some(a), _ => None})
|
||||
{
|
||||
// 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
|
||||
// on the first floor of the arena. This is a compromise that was made because in the current arena procedural generation
|
||||
// there is also no pathways to the stands on the first floor for NPCs.
|
||||
let arena_center = Vec3::new(arena.center.x, arena.center.y, arena.base).as_::<f32>();
|
||||
let stand_dist = arena.stand_dist as f32;
|
||||
let seat_var_width = ctx.rng.gen_range(0..arena.stand_width) as f32;
|
||||
let seat_var_length = ctx.rng.gen_range(-arena.stand_length..arena.stand_length) as f32;
|
||||
// Select a seat on one of the 4 arena stands
|
||||
let seat = match ctx.rng.gen_range(0..4) {
|
||||
0 => Vec3::new(arena_center.x - stand_dist + seat_var_width, arena_center.y + seat_var_length, arena_center.z),
|
||||
1 => Vec3::new(arena_center.x + stand_dist - seat_var_width, arena_center.y + seat_var_length, arena_center.z),
|
||||
2 => Vec3::new(arena_center.x + seat_var_length, arena_center.y - stand_dist + seat_var_width, arena_center.z),
|
||||
_ => Vec3::new(arena_center.x + seat_var_length, arena_center.y + stand_dist - seat_var_width, arena_center.z),
|
||||
};
|
||||
let look_dir = Dir::from_unnormalized(arena_center - seat);
|
||||
// Walk to an arena seat, cheer, sit and dance
|
||||
return 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"))
|
||||
// Turn toward the centre of the arena and watch the action!
|
||||
.then(choose(move |ctx, _| if ctx.rng.gen_bool(0.3) {
|
||||
casual(just(move |ctx,_| ctx.controller.do_cheer(look_dir)).repeat().stop_if(timeout(5.0)))
|
||||
} else if ctx.rng.gen_bool(0.15) {
|
||||
casual(just(move |ctx,_| ctx.controller.do_dance(look_dir)).repeat().stop_if(timeout(5.0)))
|
||||
} else {
|
||||
casual(just(move |ctx,_| ctx.controller.do_sit(look_dir)).repeat().stop_if(timeout(15.0)))
|
||||
})
|
||||
&& let Some(ws) = ctx.index.sites.get(ws_id).site2() {
|
||||
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
|
||||
// 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
|
||||
// there is also no pathways to the stands on the first floor for NPCs.
|
||||
let arena_center = Vec3::new(arena.center.x, arena.center.y, arena.base).as_::<f32>();
|
||||
let stand_dist = arena.stand_dist as f32;
|
||||
let seat_var_width = ctx.rng.gen_range(0..arena.stand_width) as f32;
|
||||
let seat_var_length = ctx.rng.gen_range(-arena.stand_length..arena.stand_length) as f32;
|
||||
// Select a seat on one of the 4 arena stands
|
||||
let seat = match ctx.rng.gen_range(0..4) {
|
||||
0 => Vec3::new(arena_center.x - stand_dist + seat_var_width, arena_center.y + seat_var_length, arena_center.z),
|
||||
1 => Vec3::new(arena_center.x + stand_dist - seat_var_width, arena_center.y + seat_var_length, arena_center.z),
|
||||
2 => Vec3::new(arena_center.x + seat_var_length, arena_center.y - stand_dist + seat_var_width, arena_center.z),
|
||||
_ => Vec3::new(arena_center.x + seat_var_length, arena_center.y + stand_dist - seat_var_width, arena_center.z),
|
||||
};
|
||||
let look_dir = Dir::from_unnormalized(arena_center - seat);
|
||||
// Walk to an arena seat, cheer, sit and dance
|
||||
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"))
|
||||
// Turn toward the centre of the arena and watch the action!
|
||||
.then(choose(move |ctx, _| if ctx.rng.gen_bool(0.3) {
|
||||
casual(just(move |ctx,_| ctx.controller.do_cheer(look_dir)).repeat().stop_if(timeout(5.0)))
|
||||
} else if ctx.rng.gen_bool(0.15) {
|
||||
casual(just(move |ctx,_| ctx.controller.do_dance(look_dir)).repeat().stop_if(timeout(5.0)))
|
||||
} else {
|
||||
casual(just(move |ctx,_| ctx.controller.do_sit(look_dir, None)).repeat().stop_if(timeout(15.0)))
|
||||
})
|
||||
.repeat()
|
||||
.stop_if(timeout(wait_time)))
|
||||
.map(|_, _| ())
|
||||
.boxed());
|
||||
fun_stuff.push(action);
|
||||
}
|
||||
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());
|
||||
.map(|_, _| ())
|
||||
.boxed()
|
||||
);
|
||||
|
||||
fun_stuff.push(action);
|
||||
}
|
||||
}
|
||||
} else if matches!(ctx.npc.profession(), Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8)
|
||||
|
||||
|
||||
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) {
|
||||
return casual(
|
||||
|
@ -254,7 +254,7 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
||||
| NpcActivity::HuntAnimals
|
||||
| NpcActivity::Dance(_)
|
||||
| NpcActivity::Cheer(_)
|
||||
| NpcActivity::Sit(_),
|
||||
| NpcActivity::Sit(..),
|
||||
) => {
|
||||
// TODO: Maybe they should walk around randomly
|
||||
// when gathering resources?
|
||||
|
@ -29,6 +29,7 @@ use common::{
|
||||
consts::MAX_MOUNT_RANGE,
|
||||
effect::{BuffEffect, Effect},
|
||||
event::{Emitter, ServerEvent},
|
||||
mounting::VolumePos,
|
||||
path::TraversalConfig,
|
||||
rtsim::NpcActivity,
|
||||
states::basic_beam,
|
||||
@ -51,9 +52,7 @@ impl<'a> AgentData<'a> {
|
||||
////////////////////////////////////////
|
||||
|
||||
pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
self.dismount(controller, read_data);
|
||||
|
||||
controller.push_action(ControlAction::GlideWield);
|
||||
|
||||
@ -73,9 +72,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
|
||||
pub fn fly_upward(&self, controller: &mut Controller, read_data: &ReadData) {
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
self.dismount(controller, read_data);
|
||||
|
||||
controller.push_basic_input(InputKind::Fly);
|
||||
controller.inputs.move_z = 1.0;
|
||||
@ -96,9 +93,7 @@ impl<'a> AgentData<'a> {
|
||||
path: Path,
|
||||
speed_multiplier: Option<f32>,
|
||||
) -> bool {
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
self.dismount(controller, read_data);
|
||||
|
||||
let partial_path_tgt_pos = |pos_difference: Vec3<f32>| {
|
||||
self.pos.0
|
||||
@ -242,6 +237,13 @@ impl<'a> AgentData<'a> {
|
||||
'activity: {
|
||||
match agent.rtsim_controller.activity {
|
||||
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 is flying and bumps something above it, then it should move down.
|
||||
if self.traversal_config.can_fly
|
||||
@ -399,17 +401,26 @@ impl<'a> AgentData<'a> {
|
||||
controller.push_action(ControlAction::Talk);
|
||||
break 'activity; // Don't fall through to idle wandering
|
||||
},
|
||||
Some(NpcActivity::Sit(dir)) => {
|
||||
if let Some(look_dir) = dir {
|
||||
controller.inputs.look_dir = look_dir;
|
||||
if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 {
|
||||
controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01;
|
||||
break 'activity;
|
||||
} else {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
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 {
|
||||
controller.inputs.look_dir = look_dir;
|
||||
if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 {
|
||||
controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01;
|
||||
break 'activity;
|
||||
} else {
|
||||
controller.inputs.move_dir = Vec2::zero();
|
||||
}
|
||||
}
|
||||
controller.push_action(ControlAction::Sit);
|
||||
}
|
||||
controller.push_action(ControlAction::Sit);
|
||||
break 'activity; // Don't fall through to idle wandering
|
||||
},
|
||||
Some(NpcActivity::HuntAnimals) => {
|
||||
@ -581,7 +592,9 @@ impl<'a> AgentData<'a> {
|
||||
read_data: &ReadData,
|
||||
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);
|
||||
}
|
||||
|
||||
@ -637,7 +650,9 @@ impl<'a> AgentData<'a> {
|
||||
// Proportion of full speed
|
||||
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);
|
||||
}
|
||||
|
||||
@ -993,7 +1008,12 @@ impl<'a> AgentData<'a> {
|
||||
#[cfg(feature = "be-dyn-lib")]
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
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.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.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.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.scale = Vec3::one() * 0.0;
|
||||
next.hold.position = Vec3::new(0.4, -0.3, -5.8);
|
||||
|
@ -22,7 +22,7 @@ mod savannah_hut;
|
||||
mod savannah_pit;
|
||||
mod savannah_workshop;
|
||||
mod sea_chapel;
|
||||
mod tavern;
|
||||
pub mod tavern;
|
||||
mod troll_cave;
|
||||
mod workshop;
|
||||
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||
|
||||
type Neighbor = Option<Id<Room>>;
|
||||
|
||||
struct Wall {
|
||||
pub struct Wall {
|
||||
start: Vec2<i32>,
|
||||
end: Vec2<i32>,
|
||||
base_alt: i32,
|
||||
@ -33,6 +33,15 @@ struct Wall {
|
||||
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)]
|
||||
enum RoomKind {
|
||||
Garden,
|
||||
@ -54,7 +63,7 @@ impl RoomKind {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Detail {
|
||||
pub enum Detail {
|
||||
Bar {
|
||||
aabr: Aabr<i32>,
|
||||
},
|
||||
@ -67,15 +76,15 @@ enum Detail {
|
||||
},
|
||||
}
|
||||
|
||||
struct Room {
|
||||
pub struct Room {
|
||||
/// Inclusive
|
||||
bounds: Aabb<i32>,
|
||||
pub bounds: Aabb<i32>,
|
||||
kind: RoomKind,
|
||||
// stairs: Option<Id<Stairs>>,
|
||||
walls: EnumMap<Dir, Vec<Id<Wall>>>,
|
||||
// TODO: Remove this, used for debugging
|
||||
detail_areas: Vec<Aabr<i32>>,
|
||||
details: Vec<Detail>,
|
||||
pub details: Vec<Detail>,
|
||||
}
|
||||
|
||||
impl Room {
|
||||
@ -99,14 +108,14 @@ struct Stairs {
|
||||
|
||||
pub struct Tavern {
|
||||
name: String,
|
||||
rooms: Store<Room>,
|
||||
pub rooms: Store<Room>,
|
||||
stairs: Store<Stairs>,
|
||||
walls: Store<Wall>,
|
||||
/// Tile position of the door tile
|
||||
pub door_tile: Vec2<i32>,
|
||||
pub(crate) door_wpos: Vec3<i32>,
|
||||
pub door_wpos: Vec3<i32>,
|
||||
/// Axis aligned bounding region for the house
|
||||
bounds: Aabr<i32>,
|
||||
pub bounds: Aabr<i32>,
|
||||
}
|
||||
|
||||
impl Tavern {
|
||||
@ -138,9 +147,7 @@ impl Tavern {
|
||||
let door_tile_center = site.tile_center_wpos(door_tile);
|
||||
let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center);
|
||||
|
||||
let door_alt = land
|
||||
.column_sample(door_wpos, index)
|
||||
.map_or_else(|| land.get_alt_approx(door_wpos), |sample| sample.alt);
|
||||
let door_alt = land.get_alt_approx(door_wpos);
|
||||
let door_wpos = door_wpos.with_z(door_alt.ceil() as i32);
|
||||
|
||||
/// Place room in bounds.
|
||||
@ -605,10 +612,7 @@ impl Tavern {
|
||||
for door_pos in dir_walls.iter().filter_map(|wall_id| {
|
||||
let wall = &walls[*wall_id];
|
||||
|
||||
wall.door.map(|door| {
|
||||
let wall_dir = Dir::from_vec2(wall.end - wall.start);
|
||||
wall.start + wall_dir.to_vec2() * door
|
||||
})
|
||||
wall.door_pos()
|
||||
}) {
|
||||
let orth = dir.orthogonal();
|
||||
for i in 0..room.detail_areas.len() {
|
||||
@ -1005,8 +1009,7 @@ impl Structure for Tavern {
|
||||
.fill(dark_wood.clone());
|
||||
},
|
||||
}
|
||||
if let Some(door) = wall.door {
|
||||
let door_pos = wall.start + wall_dir.to_vec2() * door;
|
||||
if let Some(door_pos) = wall.door_pos() {
|
||||
let min = match wall.from {
|
||||
None => door_pos - wall.to_dir.to_vec2(),
|
||||
Some(_) => door_pos,
|
||||
@ -1056,9 +1059,8 @@ impl Structure for Tavern {
|
||||
}
|
||||
},
|
||||
}
|
||||
if let Some(door) = wall.door {
|
||||
let door_pos = wall.start + wall_dir.to_vec2() * door;
|
||||
let diff = door_pos - wall_aabb.center().xy();
|
||||
if let Some(door_pos) = wall.door_pos() {
|
||||
let diff = door_pos.xy() - wall_aabb.center().xy();
|
||||
let orth = if diff == Vec2::zero() {
|
||||
wall_dir
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user