Merge branch 'isse/walk-toggle' into 'master'

Roleplay stuff

See merge request veloren/veloren!4171
This commit is contained in:
Isse 2023-12-12 19:59:58 +00:00
commit ba02d083da
42 changed files with 2526 additions and 136 deletions

View File

@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A way to target non-player entities with commands. With rtsim_id: `rtsim@<id>`, with uid: `uid@<id>`. - A way to target non-player entities with commands. With rtsim_id: `rtsim@<id>`, with uid: `uid@<id>`.
- Shorthand in voxygen for specific entities in commands, some examples `@target`, `@mount`, `@viewpoint`. - Shorthand in voxygen for specific entities in commands, some examples `@target`, `@mount`, `@viewpoint`.
- Added hit_timing to BasicMelee abilities - Added hit_timing to BasicMelee abilities
- A tavern building where npcs go to relax.
- Toggle for walking instead of running (Default: `I`).
### Changed ### Changed

5
Cargo.lock generated
View File

@ -1880,9 +1880,9 @@ dependencies = [
[[package]] [[package]]
name = "enumset" name = "enumset"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb" checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d"
dependencies = [ dependencies = [
"enumset_derive", "enumset_derive",
] ]
@ -7430,6 +7430,7 @@ dependencies = [
"csv", "csv",
"deflate", "deflate",
"enum-map", "enum-map",
"enumset",
"fallible-iterator", "fallible-iterator",
"flate2", "flate2",
"fxhash", "fxhash",

View File

@ -75,3 +75,4 @@ gameinput-muteinactivemaster = Mute master volume (inactive window)
gameinput-mutemusic = Mute music volume gameinput-mutemusic = Mute music volume
gameinput-mutesfx = Mute SFX volume gameinput-mutesfx = Mute SFX volume
gameinput-muteambience = Mute ambience volume gameinput-muteambience = Mute ambience volume
gameinput-togglewalk = Toggle Walking

View File

@ -59,6 +59,7 @@ hud-follow = Follow
hud-stay= Stay hud-stay= Stay
hud-sit = Sit hud-sit = Sit
hud-steer = Steer hud-steer = Steer
hud-lay = Lay
hud-portal = Portal hud-portal = Portal
-server = Server -server = Server

View File

@ -53,6 +53,8 @@ hud-settings-invert_controller_y_axis = Invert Controller Y Axis
hud-settings-enable_mouse_smoothing = Camera Smoothing hud-settings-enable_mouse_smoothing = Camera Smoothing
hud-settings-free_look_behavior = Free look behavior hud-settings-free_look_behavior = Free look behavior
hud-settings-auto_walk_behavior = Auto walk behavior hud-settings-auto_walk_behavior = Auto walk behavior
hud-settings-walking_speed_behavior = Walking speed behavior
hud-settings-walking_speed = Walking speed
hud-settings-camera_clamp_behavior = Camera clamp behavior hud-settings-camera_clamp_behavior = Camera clamp behavior
hud-settings-zoom_lock_behavior = Camera zoom lock behavior hud-settings-zoom_lock_behavior = Camera zoom lock behavior
hud-settings-player_physics_behavior = Player physics (experimental) hud-settings-player_physics_behavior = Player physics (experimental)

View File

@ -1071,7 +1071,6 @@ impl CharacterAbility {
&& match self { && match self {
CharacterAbility::Roll { energy_cost, .. } => { CharacterAbility::Roll { energy_cost, .. } => {
data.physics.on_ground.is_some() data.physics.on_ground.is_some()
&& data.inputs.move_dir.magnitude_squared() > 0.25
&& update.energy.try_change_by(-*energy_cost).is_ok() && update.energy.try_change_by(-*energy_cost).is_ok()
}, },
CharacterAbility::DashMelee { energy_cost, .. } CharacterAbility::DashMelee { energy_cost, .. }

View File

@ -986,6 +986,10 @@ pub struct CharacterActivity {
/// `None` means that the look direction should be derived from the /// `None` means that the look direction should be derived from the
/// orientation /// orientation
pub look_dir: Option<Dir>, pub look_dir: Option<Dir>,
/// If the character is using a Helm, this is the y direction the
/// character steering. If the character is not steering this is
/// a stale value.
pub steer_dir: f32,
/// If true, the owner has set this pet to stay at a fixed location and /// If true, the owner has set this pet to stay at a fixed location and
/// to not engage in combat /// to not engage in combat
pub is_pet_staying: bool, pub is_pet_staying: bool,

View File

@ -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>,

View File

@ -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);

View File

@ -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

View File

@ -566,22 +566,15 @@ impl SpriteKind {
#[inline] #[inline]
pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> { pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
match self { match self {
SpriteKind::ChairSingle | SpriteKind::ChairDouble | SpriteKind::Bench => Some(( SpriteKind::ChairSingle | SpriteKind::ChairDouble | SpriteKind::Bench => {
Vec3 { Some((Vec3::new(0.0, 0.0, 0.5), -Vec3::unit_y()))
x: 0.0, },
y: 0.0, SpriteKind::Helm => Some((Vec3::new(0.0, -1.0, 0.0), Vec3::unit_y())),
z: 0.5, SpriteKind::Bed => Some((Vec3::new(0.0, 0.0, 0.6), -Vec3::unit_y())),
}, SpriteKind::BedrollSnow | SpriteKind::BedrollPirate => {
-Vec3::unit_y(), Some((Vec3::new(0.0, 0.0, 0.1), -Vec3::unit_x()))
)), },
SpriteKind::Helm => Some(( SpriteKind::Bedroll => Some((Vec3::new(0.0, 0.0, 0.1), Vec3::unit_y())),
Vec3 {
x: 0.0,
y: -0.6,
z: 0.2,
},
Vec3::unit_y(),
)),
_ => None, _ => None,
} }
} }

View File

@ -1,5 +1,8 @@
use common::{ use common::{
comp::{Body, Collider, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel}, comp::{
Body, CharacterActivity, Collider, ControlAction, Controller, InputKind, Ori, Pos, Scale,
Vel,
},
link::Is, link::Is,
mounting::{Mount, VolumeRider}, mounting::{Mount, VolumeRider},
terrain::TerrainGrid, terrain::TerrainGrid,
@ -24,6 +27,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
WriteStorage<'a, CharacterActivity>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, Scale>, ReadStorage<'a, Scale>,
ReadStorage<'a, Collider>, ReadStorage<'a, Collider>,
@ -45,6 +49,7 @@ impl<'a> System<'a> for Sys {
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
mut character_activity,
bodies, bodies,
scales, scales,
colliders, colliders,
@ -174,6 +179,12 @@ impl<'a> System<'a> for Sys {
if is_volume_rider.block.is_controller() { if is_volume_rider.block.is_controller() {
if let Some((actions, inputs)) = inputs { if let Some((actions, inputs)) = inputs {
if let Some(mut character_activity) = character_activity
.get_mut(entity)
.filter(|c| c.steer_dir != inputs.move_dir.y)
{
character_activity.steer_dir = inputs.move_dir.y;
}
match is_volume_rider.pos.kind { match is_volume_rider.pos.kind {
common::mounting::Volume::Entity(uid) => { common::mounting::Volume::Entity(uid) => {
if let Some(controller) = if let Some(controller) =

View File

@ -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));

View File

@ -113,6 +113,7 @@ impl Data {
PlotKind::House(_) PlotKind::House(_)
| PlotKind::Workshop(_) | PlotKind::Workshop(_)
| PlotKind::AirshipDock(_) | PlotKind::AirshipDock(_)
| PlotKind::Tavern(_)
| PlotKind::Plaza | PlotKind::Plaza
| PlotKind::SavannahPit(_) | PlotKind::SavannahPit(_)
| PlotKind::SavannahHut(_) | PlotKind::SavannahHut(_)

View File

@ -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,9 @@ 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 +580,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 +632,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 +760,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 +808,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,51 +850,142 @@ 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
// there is also no pathways to the stands on the first floor for NPCs. // 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 arena_center = Vec3::new(arena.center.x, arena.center.y, arena.base).as_::<f32>();
let stand_dist = arena.stand_dist 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_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; 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 // Select a seat on one of the 4 arena stands
let seat = match ctx.rng.gen_range(0..4) { 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), 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), 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), 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), _ => 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); 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) {
casual(just(move |ctx,_| ctx.controller.do_cheer(look_dir)).repeat().stop_if(timeout(5.0))) casual(just(move |ctx,_| ctx.controller.do_cheer(look_dir)).repeat().stop_if(timeout(5.0)))
} 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()
.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() .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 !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(

View File

@ -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?

View File

@ -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,14 @@ 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,17 +402,26 @@ 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(look_dir) = dir { if let Some(pos) =
controller.inputs.look_dir = look_dir; pos.filter(|p| read_data.terrain.get(*p).is_ok_and(|b| b.is_mountable()))
if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { {
controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; if !read_data.is_volume_riders.contains(*self.entity) {
break 'activity; controller
} else { .push_event(ControlEvent::MountVolume(VolumePos::terrain(pos)));
controller.inputs.move_dir = Vec2::zero();
} }
} 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 break 'activity; // Don't fall through to idle wandering
}, },
Some(NpcActivity::HuntAnimals) => { Some(NpcActivity::HuntAnimals) => {
@ -581,7 +593,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 +651,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,9 +1009,7 @@ 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) { self.dismount(controller, read_data);
controller.push_event(ControlEvent::Unmount);
}
let tool_tactic = |tool_kind| match tool_kind { let tool_tactic = |tool_kind| match tool_kind {
ToolKind::Bow => Tactic::Bow, ToolKind::Bow => Tactic::Bow,
@ -1999,4 +2013,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);
}
}
} }

View File

@ -28,11 +28,13 @@ pub mod selfbuff;
pub mod shockwave; pub mod shockwave;
pub mod shoot; pub mod shoot;
pub mod sit; pub mod sit;
pub mod sleep;
pub mod sneak; pub mod sneak;
pub mod sneakequip; pub mod sneakequip;
pub mod sneakwield; pub mod sneakwield;
pub mod staggered; pub mod staggered;
pub mod stand; pub mod stand;
pub mod steer;
pub mod stunned; pub mod stunned;
pub mod swim; pub mod swim;
pub mod swimwield; pub mod swimwield;
@ -51,11 +53,11 @@ pub use self::{
mount::MountAnimation, music::MusicAnimation, rapidmelee::RapidMeleeAnimation, mount::MountAnimation, music::MusicAnimation, rapidmelee::RapidMeleeAnimation,
repeater::RepeaterAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation, repeater::RepeaterAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation,
run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation, run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation,
shoot::ShootAnimation, sit::SitAnimation, sneak::SneakAnimation, shoot::ShootAnimation, sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation,
sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation, sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation,
staggered::StaggeredAnimation, stand::StandAnimation, stunned::StunnedAnimation, staggered::StaggeredAnimation, stand::StandAnimation, steer::SteerAnimation,
swim::SwimAnimation, swimwield::SwimWieldAnimation, talk::TalkAnimation, stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation,
wallrun::WallrunAnimation, wield::WieldAnimation, talk::TalkAnimation, wallrun::WallrunAnimation, wield::WieldAnimation,
}; };
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource}; use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource};
use common::comp::{ use common::comp::{
@ -431,8 +433,8 @@ impl CharacterSkeleton {
* ((acc_vel * lab * 1.6).sin()); * ((acc_vel * lab * 1.6).sin());
self.lantern.position = Vec3::new(s_a.lantern.0, s_a.lantern.1, s_a.lantern.2); self.lantern.position = Vec3::new(s_a.lantern.0, s_a.lantern.1, s_a.lantern.2);
self.lantern.orientation = self.lantern.orientation = Quaternion::rotation_x(shorte * 0.7 * speednorm.powi(2) + 0.4)
Quaternion::rotation_x(shorte * 0.7 + 0.4) * Quaternion::rotation_y(shorte * 0.4); * Quaternion::rotation_y(shorte * 0.4 * speednorm.powi(2));
self.lantern.scale = Vec3::one() * 0.65; self.lantern.scale = Vec3::one() * 0.65;
self.hold.scale = Vec3::one() * 0.0; self.hold.scale = Vec3::one() * 0.0;

View File

@ -152,7 +152,7 @@ impl Animation for RunAnimation {
next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1); next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1);
next.back.orientation = next.back.orientation =
Quaternion::rotation_x(-0.05 + short * 0.02 + noisea * 0.02 + noiseb * 0.02) Quaternion::rotation_x(-0.05 + short * 0.02 + noisea * 0.02 + noiseb * 0.02)
* Quaternion::rotation_y(foothorir * 0.2); * Quaternion::rotation_y(foothorir * 0.35 * speednorm.powi(2));
next.shorts.position = Vec3::new(0.0, 0.65 + s_a.shorts.0, 0.65 * speednorm + s_a.shorts.1); next.shorts.position = Vec3::new(0.0, 0.65 + s_a.shorts.0, 0.65 * speednorm + s_a.shorts.1);
next.shorts.orientation = Quaternion::rotation_x(0.2 * speednorm) next.shorts.orientation = Quaternion::rotation_x(0.2 * speednorm)
@ -163,22 +163,24 @@ impl Animation for RunAnimation {
-s_a.hand.0 * 1.2 - foothorir * 1.3 * speednorm -s_a.hand.0 * 1.2 - foothorir * 1.3 * speednorm
+ (foothoril.abs().powi(2) - 0.5) * speednorm * 4.0, + (foothoril.abs().powi(2) - 0.5) * speednorm * 4.0,
s_a.hand.1 * 1.3 + foothorir * -7.0 * speednorm.powi(2) * (1.0 - sideabs), s_a.hand.1 * 1.3 + foothorir * -7.0 * speednorm.powi(2) * (1.0 - sideabs),
s_a.hand.2 - foothorir * 2.75 * speednorm + foothoril.abs().powi(3) * speednorm * 8.0, s_a.hand.2 - foothorir * 2.75 * speednorm
+ foothoril.abs().powi(3) * speednorm.powi(2) * 8.0,
); );
next.hand_l.orientation = next.hand_l.orientation =
Quaternion::rotation_x( Quaternion::rotation_x(
0.6 * speednorm + (footrotr * -1.5 + 0.5) * speednorm * (1.0 - sideabs), 0.6 * speednorm + (footrotr * -1.5 + 0.5) * speednorm.powi(2) * (1.0 - sideabs),
) * Quaternion::rotation_y(footrotr * 0.4 * speednorm + PI * 0.07); ) * Quaternion::rotation_y(footrotr * 0.4 * speednorm + PI * 0.07);
next.hand_r.position = Vec3::new( next.hand_r.position = Vec3::new(
s_a.hand.0 * 1.2 + foothoril * 1.3 * speednorm s_a.hand.0 * 1.2 + foothoril * 1.3 * speednorm
- (foothorir.abs().powi(2) - 0.5) * speednorm * 4.0, - (foothorir.abs().powi(2) - 0.5) * speednorm * 4.0,
s_a.hand.1 * 1.3 + foothoril * -7.0 * speednorm.powi(2) * (1.0 - sideabs), s_a.hand.1 * 1.3 + foothoril * -7.0 * speednorm.powi(2) * (1.0 - sideabs),
s_a.hand.2 - foothoril * 2.75 * speednorm + foothorir.abs().powi(3) * speednorm * 8.0, s_a.hand.2 - foothoril * 2.75 * speednorm
+ foothorir.abs().powi(3) * speednorm.powi(2) * 8.0,
); );
next.hand_r.orientation = next.hand_r.orientation =
Quaternion::rotation_x( Quaternion::rotation_x(
0.6 * speednorm + (footrotl * -1.5 + 0.5) * speednorm * (1.0 - sideabs), 0.6 * speednorm + (footrotl * -1.5 + 0.5) * speednorm.powi(2) * (1.0 - sideabs),
) * Quaternion::rotation_y(footrotl * -0.4 * speednorm - PI * 0.07); ) * Quaternion::rotation_y(footrotl * -0.4 * speednorm - PI * 0.07);
next.foot_l.position = Vec3::new( next.foot_l.position = Vec3::new(

View File

@ -0,0 +1,114 @@
use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use common::comp::item::ToolKind;
use std::{f32::consts::PI, ops::Mul};
pub struct SleepAnimation;
impl Animation for SleepAnimation {
type Dependency<'a> = (Option<ToolKind>, Option<ToolKind>, f32);
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"character_sleep\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_sleep")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(_active_tool_kind, _second_tool_kind, global_time): Self::Dependency<'_>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let slow = (anim_time * 1.0).sin();
let slowa = (anim_time * 1.0 + PI / 2.0).sin();
let stop = (anim_time * 3.0).min(PI / 2.0).sin();
let head_look = Vec2::new(
(global_time * 0.05 + anim_time / 15.0)
.floor()
.mul(7331.0)
.sin()
* 0.25,
(global_time * 0.05 + anim_time / 15.0)
.floor()
.mul(1337.0)
.sin()
* 0.125,
);
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1 + slow * 0.1 + stop * -0.8);
next.head.orientation = Quaternion::rotation_z(head_look.x + slow * 0.2 - slow * 0.1)
* Quaternion::rotation_x((slowa * -0.1 + slow * 0.1 + head_look.y).abs());
next.chest.position = Vec3::new(
0.0,
s_a.chest.0 + stop * -0.4,
s_a.chest.1 + slow * 0.1 + stop * -0.8,
);
next.chest.orientation = Quaternion::rotation_x(stop * 0.15 + 1.0);
next.belt.position = Vec3::new(0.0, s_a.belt.0 + stop * 1.2, s_a.belt.1);
next.belt.orientation = Quaternion::rotation_x(stop * 0.3);
next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1);
next.shorts.position = Vec3::new(0.0, s_a.shorts.0 + stop * 2.5, s_a.shorts.1 + stop * 0.6);
next.shorts.orientation = Quaternion::rotation_x(stop * 0.6);
next.hand_l.position = Vec3::new(
-s_a.hand.0 - 1.0,
s_a.hand.1 + slowa * 0.15 + 2.0,
s_a.hand.2 + slow * 0.7 + stop * -2.0,
);
next.hand_l.orientation =
Quaternion::rotation_x(slowa * -0.1 + slow * 0.1) * Quaternion::rotation_y(PI * 0.15);
next.hand_r.position = Vec3::new(
s_a.hand.0 + 1.0,
s_a.hand.1 + slowa * 0.15 + 2.0,
s_a.hand.2 + slow * 0.7 + stop * -2.0,
);
next.hand_r.orientation =
Quaternion::rotation_x(slow * -0.1 + slowa * 0.1) * Quaternion::rotation_y(PI * -0.15);
next.foot_l.position = Vec3::new(-s_a.foot.0, 6.0 + s_a.foot.1, 6.0 + s_a.foot.2);
next.foot_l.orientation = Quaternion::rotation_x(slow * 0.1 + stop * 1.2 + slow * 0.1);
next.foot_r.position = Vec3::new(s_a.foot.0, 6.0 + s_a.foot.1, 6.0 + s_a.foot.2);
next.foot_r.orientation = Quaternion::rotation_x(slowa * 0.1 + stop * 1.2 + slowa * 0.1);
next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
next.shoulder_l.orientation = Quaternion::rotation_x(0.0);
next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
next.shoulder_r.orientation = Quaternion::rotation_x(0.0);
next.torso.position = Vec3::new(0.0, -2.2, stop * -1.76);
if skeleton.holding_lantern {
next.hand_r.position = Vec3::new(
s_a.hand.0 + 1.0 - head_look.x * 8.0,
s_a.hand.1 + 5.0 + head_look.x * 6.0,
s_a.hand.2 + 9.0 + head_look.y * 6.0,
);
next.hand_r.orientation = Quaternion::rotation_x(2.25)
* Quaternion::rotation_z(0.9)
* Quaternion::rotation_y(head_look.x * 3.0)
* Quaternion::rotation_x(head_look.y * 3.0);
let fast = (anim_time * 5.0).sin();
let fast2 = (anim_time * 4.5 + 8.0).sin();
next.lantern.position = Vec3::new(-0.5, -0.5, -2.5);
next.lantern.orientation = next.hand_r.orientation.inverse()
* Quaternion::rotation_x(fast * 0.1)
* Quaternion::rotation_y(fast2 * 0.1);
}
next
}
}

View File

@ -0,0 +1,118 @@
use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use common::comp::item::ToolKind;
use std::{f32::consts::PI, ops::Mul};
pub struct SteerAnimation;
impl Animation for SteerAnimation {
type Dependency<'a> = (Option<ToolKind>, Option<ToolKind>, f32, f32);
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"character_steer\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_steer")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(_active_tool_kind, _second_tool_kind, steer_dir, global_time): Self::Dependency<'_>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let slow = (anim_time * 1.0).sin();
let head_look = Vec2::new(
(global_time + anim_time / 12.0).floor().mul(7331.0).sin() * 0.1,
(global_time + anim_time / 12.0).floor().mul(1337.0).sin() * 0.05,
);
next.head.scale = Vec3::one() * s_a.head_scale;
next.chest.scale = Vec3::one() * 1.01;
next.hand_l.scale = Vec3::one() * 1.04;
next.hand_r.scale = Vec3::one() * 1.04;
next.back.scale = Vec3::one() * 1.02;
next.hold.scale = Vec3::one() * 0.0;
next.lantern.scale = Vec3::one() * 0.65;
next.shoulder_l.scale = Vec3::one() * 1.1;
next.shoulder_r.scale = Vec3::one() * 1.1;
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1 + slow * 0.3);
next.head.orientation =
Quaternion::rotation_z(head_look.x) * Quaternion::rotation_x(head_look.y.abs());
next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + slow * 0.3);
next.chest.orientation = Quaternion::rotation_z(head_look.x * 0.06);
next.belt.position = Vec3::new(0.0, s_a.belt.0, s_a.belt.1);
next.belt.orientation = Quaternion::rotation_z(head_look.x * -0.1);
next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1);
next.shorts.position = Vec3::new(0.0, s_a.shorts.0, s_a.shorts.1);
next.shorts.orientation = Quaternion::rotation_z(head_look.x * -0.2);
next.hand_l.position = Vec3::new(
-s_a.hand.0,
s_a.hand.1 + slow * 0.15,
s_a.hand.2 + slow * 0.5,
);
let helm_center = Vec3::new(0.0, 0.6, 0.75) / s_a.scaler * 11.0;
let rot = steer_dir * 0.5;
let hand_rotation = Quaternion::rotation_y(rot) * Quaternion::rotation_x(PI / 2.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_r.position = helm_center + hand_offset;
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();
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.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);
if skeleton.holding_lantern {
next.hand_r.position = Vec3::new(
s_a.hand.0 - head_look.x * 6.0,
s_a.hand.1 + 5.0 - head_look.y * 10.0 + slow * 0.15,
s_a.hand.2 + 12.0 + head_look.y * 6.0 + slow * 0.5,
);
next.hand_r.orientation = Quaternion::rotation_x(2.25 + slow * -0.06)
* Quaternion::rotation_z(0.9)
* Quaternion::rotation_y(head_look.x * 1.5)
* Quaternion::rotation_x(head_look.y * 1.5);
next.lantern.position = Vec3::new(-0.5, -0.5, -2.5);
next.lantern.orientation = next.hand_r.orientation.inverse();
}
next.torso.position = Vec3::new(0.0, 0.0, 0.0);
next
}
}

View File

@ -170,6 +170,8 @@ pub enum GameInput {
MuteSfx, MuteSfx,
#[strum(serialize = "gameinput-muteambience")] #[strum(serialize = "gameinput-muteambience")]
MuteAmbience, MuteAmbience,
#[strum(serialize = "gameinput-togglewalk")]
ToggleWalk,
} }
impl GameInput { impl GameInput {

View File

@ -342,6 +342,10 @@ widget_ids! {
auto_walk_txt, auto_walk_txt,
auto_walk_bg, auto_walk_bg,
// Walking speed indicator
walking_speed_txt,
walking_speed_bg,
// Temporal (fading) camera zoom lock indicator // Temporal (fading) camera zoom lock indicator
zoom_lock_txt, zoom_lock_txt,
zoom_lock_bg, zoom_lock_bg,
@ -2149,6 +2153,7 @@ impl Hud {
BlockInteraction::Mount => { BlockInteraction::Mount => {
let key = match block.get_sprite() { let key = match block.get_sprite() {
Some(SpriteKind::Helm) => "hud-steer", Some(SpriteKind::Helm) => "hud-steer",
Some(SpriteKind::Bed | SpriteKind::Bedroll | SpriteKind::BedrollSnow | SpriteKind::BedrollPirate) => "hud-lay",
_ => "hud-sit", _ => "hud-sit",
}; };
vec![(Some(GameInput::Mount), i18n.get_msg(key).to_string())] vec![(Some(GameInput::Mount), i18n.get_msg(key).to_string())]

View File

@ -31,6 +31,9 @@ widget_ids! {
camera_clamp_slider, camera_clamp_slider,
camera_clamp_label, camera_clamp_label,
camera_clamp_value, camera_clamp_value,
walking_speed_slider,
walking_speed_label,
walking_speed_value,
mouse_y_invert_button, mouse_y_invert_button,
mouse_y_invert_label, mouse_y_invert_label,
controller_y_invert_button, controller_y_invert_button,
@ -42,6 +45,8 @@ widget_ids! {
free_look_behavior_list, free_look_behavior_list,
auto_walk_behavior_text, auto_walk_behavior_text,
auto_walk_behavior_list, auto_walk_behavior_list,
walking_speed_behavior_text,
walking_speed_behavior_list,
camera_clamp_behavior_text, camera_clamp_behavior_text,
camera_clamp_behavior_list, camera_clamp_behavior_list,
zoom_lock_behavior_text, zoom_lock_behavior_text,
@ -124,6 +129,7 @@ impl<'a> Widget for Gameplay<'a> {
let display_pan = self.global_state.settings.gameplay.pan_sensitivity; let display_pan = self.global_state.settings.gameplay.pan_sensitivity;
let display_zoom = self.global_state.settings.gameplay.zoom_sensitivity; let display_zoom = self.global_state.settings.gameplay.zoom_sensitivity;
let display_clamp = self.global_state.settings.gameplay.camera_clamp_angle; let display_clamp = self.global_state.settings.gameplay.camera_clamp_angle;
let display_walking_speed = self.global_state.settings.gameplay.walking_speed;
// Mouse Pan Sensitivity // Mouse Pan Sensitivity
Text::new( Text::new(
@ -233,6 +239,38 @@ impl<'a> Widget for Gameplay<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.camera_clamp_value, ui); .set(state.ids.camera_clamp_value, ui);
// Walking speed
Text::new(&self.localized_strings.get_msg("hud-settings-walking_speed"))
.down_from(state.ids.camera_clamp_slider, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.walking_speed_label, ui);
if let Some(new_val) = ImageSlider::continuous(
display_walking_speed,
0.0,
1.0,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(550.0, 22.0)
.down_from(state.ids.walking_speed_label, 10.0)
.track_breadth(30.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.walking_speed_slider, ui)
{
events.push(AdjustWalkingSpeed(new_val));
}
Text::new(&format!("{:.2}", display_walking_speed))
.right_from(state.ids.walking_speed_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.walking_speed_value, ui);
// Zoom Inversion // Zoom Inversion
let zoom_inverted = ToggleButton::new( let zoom_inverted = ToggleButton::new(
self.global_state.settings.gameplay.zoom_inversion, self.global_state.settings.gameplay.zoom_inversion,
@ -240,7 +278,7 @@ impl<'a> Widget for Gameplay<'a> {
self.imgs.checkbox_checked, self.imgs.checkbox_checked,
) )
.w_h(18.0, 18.0) .w_h(18.0, 18.0)
.down_from(state.ids.camera_clamp_slider, 20.0) .down_from(state.ids.walking_speed_slider, 20.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.mouse_zoom_invert_button, ui); .set(state.ids.mouse_zoom_invert_button, ui);
@ -420,13 +458,43 @@ impl<'a> Widget for Gameplay<'a> {
} }
} }
// Walking speed behavior
Text::new(
&self
.localized_strings
.get_msg("hud-settings-walking_speed_behavior"),
)
.down_from(state.ids.free_look_behavior_list, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.walking_speed_behavior_text, ui);
let walking_speed_selected =
self.global_state.settings.gameplay.walking_speed_behavior as usize;
if let Some(clicked) = DropDownList::new(&mode_label_list, Some(walking_speed_selected))
.w_h(200.0, 30.0)
.color(MENU_BG)
.label_color(TEXT_COLOR)
.label_font_id(self.fonts.cyri.conrod_id)
.down_from(state.ids.walking_speed_behavior_text, 8.0)
.set(state.ids.walking_speed_behavior_list, ui)
{
match clicked {
0 => events.push(ChangeWalkingSpeedBehavior(PressBehavior::Toggle)),
1 => events.push(ChangeWalkingSpeedBehavior(PressBehavior::Hold)),
_ => unreachable!(),
}
}
// Camera clamp behavior // Camera clamp behavior
Text::new( Text::new(
&self &self
.localized_strings .localized_strings
.get_msg("hud-settings-camera_clamp_behavior"), .get_msg("hud-settings-camera_clamp_behavior"),
) )
.down_from(state.ids.free_look_behavior_list, 10.0) .down_from(state.ids.auto_walk_behavior_list, 10.0)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -545,7 +613,7 @@ impl<'a> Widget for Gameplay<'a> {
.localized_strings .localized_strings
.get_msg("hud-settings-zoom_lock_behavior"), .get_msg("hud-settings-zoom_lock_behavior"),
) )
.down_from(state.ids.auto_walk_behavior_list, 10.0) .down_from(state.ids.walking_speed_behavior_list, 10.0)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -599,7 +667,7 @@ impl<'a> Widget for Gameplay<'a> {
.w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT) .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
.hover_image(self.imgs.button_hover) .hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press) .press_image(self.imgs.button_press)
.down_from(state.ids.camera_clamp_behavior_list, 12.0) .down_from(state.ids.zoom_lock_behavior_list, 12.0)
.label( .label(
&self &self
.localized_strings .localized_strings

View File

@ -11,6 +11,7 @@ pub struct KeyState {
pub swim_down: bool, pub swim_down: bool,
pub fly: bool, pub fly: bool,
pub auto_walk: bool, pub auto_walk: bool,
pub speed_mul: f32,
pub trade: bool, pub trade: bool,
pub analog_matrix: Vec2<f32>, pub analog_matrix: Vec2<f32>,
} }
@ -28,6 +29,7 @@ impl Default for KeyState {
swim_down: false, swim_down: false,
fly: false, fly: false,
auto_walk: false, auto_walk: false,
speed_mul: 1.0,
trade: false, trade: false,
analog_matrix: Vec2::zero(), analog_matrix: Vec2::zero(),
} }
@ -42,9 +44,11 @@ impl KeyState {
if self.up || self.auto_walk { 1.0 } else { 0.0 } if self.up || self.auto_walk { 1.0 } else { 0.0 }
+ if self.down { -1.0 } else { 0.0 }, + if self.down { -1.0 } else { 0.0 },
) )
.try_normalized()
.unwrap_or_default()
} else { } else {
self.analog_matrix self.analog_matrix
}; } * self.speed_mul;
if dir.magnitude_squared() <= 1.0 { if dir.magnitude_squared() <= 1.0 {
dir dir

View File

@ -1110,7 +1110,10 @@ impl FigureMgr {
&& matches!(active_tool_hand, Some(Hands::One))) && matches!(active_tool_hand, Some(Hands::One)))
|| !character.map_or(false, |c| c.is_wield())) || !character.map_or(false, |c| c.is_wield()))
&& !character.map_or(false, |c| c.is_using_hands()) && !character.map_or(false, |c| c.is_using_hands())
&& physics.in_liquid().is_none(); && physics.in_liquid().is_none()
&& is_volume_rider.map_or(true, |volume_rider| {
!matches!(volume_rider.block.get_sprite(), Some(SpriteKind::Helm))
});
let back_carry_offset = inventory let back_carry_offset = inventory
.and_then(|i| i.equipped(EquipSlot::Armor(ArmorSlot::Back))) .and_then(|i| i.equipped(EquipSlot::Armor(ArmorSlot::Back)))
@ -2137,7 +2140,26 @@ impl FigureMgr {
{ {
match sprite { match sprite {
SpriteKind::Helm => { SpriteKind::Helm => {
anim::character::DanceAnimation::update_skeleton( anim::character::SteerAnimation::update_skeleton(
&target_base,
(
active_tool_kind,
second_tool_kind,
character_activity
.map(|a| a.steer_dir)
.unwrap_or(0.0),
time,
),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
},
SpriteKind::Bed
| SpriteKind::Bedroll
| SpriteKind::BedrollSnow
| SpriteKind::BedrollPirate => {
anim::character::SleepAnimation::update_skeleton(
&target_base, &target_base,
(active_tool_kind, second_tool_kind, time), (active_tool_kind, second_tool_kind, time),
state.state_time, state.state_time,

View File

@ -207,7 +207,9 @@ impl BlocksOfInterest {
) )
.with_z(0.0), .with_z(0.0),
)), )),
Some(SpriteKind::Sign) => interactables.push((pos, Interaction::Read)), Some(SpriteKind::Sign | SpriteKind::HangingSign) => {
interactables.push((pos, Interaction::Read))
},
_ if block.is_mountable() => interactables.push((pos, Interaction::Mount)), _ if block.is_mountable() => interactables.push((pos, Interaction::Mount)),
_ => {}, _ => {},
}, },

View File

@ -101,6 +101,7 @@ pub struct SessionState {
walk_right_dir: Vec2<f32>, walk_right_dir: Vec2<f32>,
free_look: bool, free_look: bool,
auto_walk: bool, auto_walk: bool,
walking_speed: bool,
camera_clamp: bool, camera_clamp: bool,
zoom_lock: bool, zoom_lock: bool,
is_aiming: bool, is_aiming: bool,
@ -171,6 +172,7 @@ impl SessionState {
walk_right_dir, walk_right_dir,
free_look: false, free_look: false,
auto_walk: false, auto_walk: false,
walking_speed: false,
camera_clamp: false, camera_clamp: false,
zoom_lock: false, zoom_lock: false,
is_aiming: false, is_aiming: false,
@ -710,6 +712,7 @@ impl PlayState for SessionState {
} }
match input { match input {
GameInput::Primary => { GameInput::Primary => {
self.walking_speed = false;
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
// Mine and build targets can be the same block. make building // Mine and build targets can be the same block. make building
// take precedence. // take precedence.
@ -728,6 +731,7 @@ impl PlayState for SessionState {
} }
}, },
GameInput::Secondary => { GameInput::Secondary => {
self.walking_speed = false;
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if let Some(build_target) = build_target.filter(|bt| { if let Some(build_target) = build_target.filter(|bt| {
state && can_build && nearest_block_dist == Some(bt.distance) state && can_build && nearest_block_dist == Some(bt.distance)
@ -747,6 +751,7 @@ impl PlayState for SessionState {
} }
}, },
GameInput::Block => { GameInput::Block => {
self.walking_speed = false;
self.client.borrow_mut().handle_input( self.client.borrow_mut().handle_input(
InputKind::Block, InputKind::Block,
state, state,
@ -755,6 +760,7 @@ impl PlayState for SessionState {
); );
}, },
GameInput::Roll => { GameInput::Roll => {
self.walking_speed = false;
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if can_build { if can_build {
if state { if state {
@ -779,12 +785,14 @@ impl PlayState for SessionState {
} }
}, },
GameInput::Respawn => { GameInput::Respawn => {
self.walking_speed = false;
self.stop_auto_walk(); self.stop_auto_walk();
if state { if state {
self.client.borrow_mut().respawn(); self.client.borrow_mut().respawn();
} }
}, },
GameInput::Jump => { GameInput::Jump => {
self.walking_speed = false;
self.client.borrow_mut().handle_input( self.client.borrow_mut().handle_input(
InputKind::Jump, InputKind::Jump,
state, state,
@ -847,6 +855,7 @@ impl PlayState for SessionState {
self.key_state.right = state self.key_state.right = state
}, },
GameInput::Glide => { GameInput::Glide => {
self.walking_speed = false;
let is_trading = self.client.borrow().is_trading(); let is_trading = self.client.borrow().is_trading();
if state && !is_trading { if state && !is_trading {
if global_state.settings.gameplay.stop_auto_walk_on_input { if global_state.settings.gameplay.stop_auto_walk_on_input {
@ -877,7 +886,11 @@ impl PlayState for SessionState {
}, },
GameInput::ToggleWield => { GameInput::ToggleWield => {
if state { if state {
self.client.borrow_mut().toggle_wield(); let mut client = self.client.borrow_mut();
if client.is_wielding().is_some_and(|b| !b) {
self.walking_speed = false;
}
client.toggle_wield();
} }
}, },
GameInput::SwapLoadout => { GameInput::SwapLoadout => {
@ -1214,6 +1227,13 @@ impl PlayState for SessionState {
} }
} }
}, },
GameInput::ToggleWalk if state => {
global_state
.settings
.gameplay
.walking_speed_behavior
.update(state, &mut self.walking_speed, |_| {});
},
_ => {}, _ => {},
} }
}, },
@ -1423,6 +1443,12 @@ impl PlayState for SessionState {
} }
} }
if self.walking_speed {
self.key_state.speed_mul = global_state.settings.gameplay.walking_speed;
} else {
self.key_state.speed_mul = 1.0;
}
// Recompute dependents just in case some input modified the camera // Recompute dependents just in case some input modified the camera
self.scene self.scene
.camera_mut() .camera_mut()

View File

@ -59,6 +59,7 @@ pub enum Gameplay {
AdjustMousePan(u32), AdjustMousePan(u32),
AdjustMouseZoom(u32), AdjustMouseZoom(u32),
AdjustCameraClamp(u32), AdjustCameraClamp(u32),
AdjustWalkingSpeed(f32),
ToggleControllerYInvert(bool), ToggleControllerYInvert(bool),
ToggleMouseYInvert(bool), ToggleMouseYInvert(bool),
@ -68,6 +69,7 @@ pub enum Gameplay {
ChangeFreeLookBehavior(PressBehavior), ChangeFreeLookBehavior(PressBehavior),
ChangeAutoWalkBehavior(PressBehavior), ChangeAutoWalkBehavior(PressBehavior),
ChangeWalkingSpeedBehavior(PressBehavior),
ChangeCameraClampBehavior(PressBehavior), ChangeCameraClampBehavior(PressBehavior),
ChangeZoomLockBehavior(AutoPressBehavior), ChangeZoomLockBehavior(AutoPressBehavior),
ChangeStopAutoWalkOnInput(bool), ChangeStopAutoWalkOnInput(bool),
@ -379,6 +381,9 @@ impl SettingsChange {
Gameplay::AdjustCameraClamp(angle) => { Gameplay::AdjustCameraClamp(angle) => {
settings.gameplay.camera_clamp_angle = angle; settings.gameplay.camera_clamp_angle = angle;
}, },
Gameplay::AdjustWalkingSpeed(speed) => {
settings.gameplay.walking_speed = speed;
},
Gameplay::ToggleControllerYInvert(controller_y_inverted) => { Gameplay::ToggleControllerYInvert(controller_y_inverted) => {
window.controller_settings.pan_invert_y = controller_y_inverted; window.controller_settings.pan_invert_y = controller_y_inverted;
settings.controller.pan_invert_y = controller_y_inverted; settings.controller.pan_invert_y = controller_y_inverted;
@ -400,6 +405,9 @@ impl SettingsChange {
Gameplay::ChangeAutoWalkBehavior(behavior) => { Gameplay::ChangeAutoWalkBehavior(behavior) => {
settings.gameplay.auto_walk_behavior = behavior; settings.gameplay.auto_walk_behavior = behavior;
}, },
Gameplay::ChangeWalkingSpeedBehavior(behavior) => {
settings.gameplay.walking_speed_behavior = behavior;
},
Gameplay::ChangeCameraClampBehavior(behavior) => { Gameplay::ChangeCameraClampBehavior(behavior) => {
settings.gameplay.camera_clamp_behavior = behavior; settings.gameplay.camera_clamp_behavior = behavior;
}, },

View File

@ -200,6 +200,7 @@ impl ControlSettings {
GameInput::MuteMusic => Some(KeyMouse::Key(VirtualKeyCode::F8)), GameInput::MuteMusic => Some(KeyMouse::Key(VirtualKeyCode::F8)),
GameInput::MuteSfx => None, GameInput::MuteSfx => None,
GameInput::MuteAmbience => None, GameInput::MuteAmbience => None,
GameInput::ToggleWalk => Some(KeyMouse::Key(VirtualKeyCode::I)),
} }
} }
} }

View File

@ -8,11 +8,13 @@ pub struct GameplaySettings {
pub pan_sensitivity: u32, pub pan_sensitivity: u32,
pub zoom_sensitivity: u32, pub zoom_sensitivity: u32,
pub camera_clamp_angle: u32, pub camera_clamp_angle: u32,
pub walking_speed: f32,
pub zoom_inversion: bool, pub zoom_inversion: bool,
pub mouse_y_inversion: bool, pub mouse_y_inversion: bool,
pub smooth_pan_enable: bool, pub smooth_pan_enable: bool,
pub free_look_behavior: PressBehavior, pub free_look_behavior: PressBehavior,
pub auto_walk_behavior: PressBehavior, pub auto_walk_behavior: PressBehavior,
pub walking_speed_behavior: PressBehavior,
pub camera_clamp_behavior: PressBehavior, pub camera_clamp_behavior: PressBehavior,
pub zoom_lock_behavior: AutoPressBehavior, pub zoom_lock_behavior: AutoPressBehavior,
pub stop_auto_walk_on_input: bool, pub stop_auto_walk_on_input: bool,
@ -27,11 +29,13 @@ impl Default for GameplaySettings {
pan_sensitivity: 100, pan_sensitivity: 100,
zoom_sensitivity: 100, zoom_sensitivity: 100,
camera_clamp_angle: 45, camera_clamp_angle: 45,
walking_speed: 0.35,
zoom_inversion: false, zoom_inversion: false,
mouse_y_inversion: false, mouse_y_inversion: false,
smooth_pan_enable: false, smooth_pan_enable: false,
free_look_behavior: PressBehavior::Toggle, free_look_behavior: PressBehavior::Toggle,
auto_walk_behavior: PressBehavior::Toggle, auto_walk_behavior: PressBehavior::Toggle,
walking_speed_behavior: PressBehavior::Toggle,
camera_clamp_behavior: PressBehavior::Toggle, camera_clamp_behavior: PressBehavior::Toggle,
zoom_lock_behavior: AutoPressBehavior::Auto, zoom_lock_behavior: AutoPressBehavior::Auto,
stop_auto_walk_on_input: true, stop_auto_walk_on_input: true,

View File

@ -21,6 +21,7 @@ common-dynlib = {package = "veloren-common-dynlib", path = "../common/dynlib", o
bincode = { workspace = true } bincode = { workspace = true }
bitvec = "1.0.1" bitvec = "1.0.1"
enum-map = { workspace = true } enum-map = { workspace = true }
enumset = "1.1.3"
fxhash = { workspace = true } fxhash = { workspace = true }
image = { workspace = true } image = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }

View File

@ -539,6 +539,7 @@ impl Civs {
let size = Lerp::lerp(0.03, 1.0, rng.gen_range(0.0..1f32).powi(5)); let size = Lerp::lerp(0.03, 1.0, rng.gen_range(0.0..1f32).powi(5));
WorldSite::refactor(site2::Site::generate_city( WorldSite::refactor(site2::Site::generate_city(
&Land::from_sim(ctx.sim), &Land::from_sim(ctx.sim),
index_ref,
&mut rng, &mut rng,
wpos, wpos,
size, size,

View File

@ -689,4 +689,168 @@ impl<'a, R: Rng> NameGen<'a, R> {
]; ];
self.generate_theme_from_parts(&start, &middle, &vowel, &end) self.generate_theme_from_parts(&start, &middle, &vowel, &end)
} }
pub fn generate_tavern(&mut self) -> String {
let adjectives = [
"Crazy",
"Big",
"Tiny",
"Slimy",
"Warm",
"Rigid",
"Soft",
"Wet",
"Humid",
"Smelly",
"Hidden",
"Smart",
"Fragile",
"Strong",
"Weak",
"Happy",
"Sad",
"Glad",
"Scared",
"Embarrassed",
"Goofy",
"Spicy",
"Salty",
"Peaceful",
"Awful",
"Sweet",
"Colossal",
"Puzzled",
"Cheap",
"Valuable",
"Rich",
"Obnoxious",
"Puzzled",
"Snoring",
"Fast",
"Quick",
"Magical",
"Violet",
"Red",
"Blue",
"Green",
"Yellow",
"Golden",
"Shiny",
"Tired",
"Twin",
"Incompetent",
"Light",
"Dark",
"Glorious",
"Best",
"Free",
"Odd",
"Juicy",
"Shaking",
"Tall",
"Short",
"Precious",
"Regular",
"Slow",
"Anxious",
"Naive",
"Sore",
"Next",
"Silver",
"Secret",
"Honorable",
"Rapid",
"Sleepy",
"Lying",
"Zesty",
"Fancy",
"Stylish",
"Thirsty",
"Dry",
"Dancing",
"Singing",
"Drunken",
];
let tavern_synonyms = ["Tavern", "Bar", "Pub"];
let subjectives = [
"Apple",
"Pumpkin",
"Cucumber",
"Squash",
"Demons",
"Mango",
"Coconut",
"Cats",
"Hill",
"Mountain",
"Squirrel",
"Rabbit",
"Moose",
"Driggle",
"Iron",
"Velorite",
"Plate",
"Eagle",
"Birds",
"Drumstick",
"Dog",
"Tiger",
"Knight",
"Leader",
"Huntress",
"Hunter",
"Dwarf",
"Toad",
"Clams",
"Bell",
"Avocado",
"Egg",
"Spade",
"Stream",
"Cabbage",
"Tomato",
"Rapier",
"Katana",
"Whisper",
"Hammer",
"Axe",
"Sword",
"Saurok",
"Danari",
"Elf",
"Human",
"Draugr",
"Orc",
"Pie",
"Stick",
"Rope",
"Knife",
"Shield",
"Bow",
"Spear",
"Staff",
"Crow",
"Crown",
"Parrot",
"Parrots",
"Pelican",
"Whale",
"Cube",
"Minotaur",
"Oni",
"Monster",
];
let kind = self.rng.gen_range(0..10);
let mut choose = |slice: &[&'static str]| *slice.choose(self.rng).unwrap();
match kind {
0 => format!("The {} {}", choose(&adjectives), choose(&tavern_synonyms)),
1..=7 => format!("The {} {}", choose(&adjectives), choose(&subjectives)),
_ => format!(
"The {} {} {}",
choose(&adjectives),
choose(&subjectives),
choose(&tavern_synonyms)
),
}
}
} }

View File

@ -573,7 +573,13 @@ impl Site {
} }
// Size is 0..1 // Size is 0..1
pub fn generate_city(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>, size: f32) -> Self { pub fn generate_city(
land: &Land,
index: IndexRef,
rng: &mut impl Rng,
origin: Vec2<i32>,
size: f32,
) -> Self {
let mut rng = reseed(rng); let mut rng = reseed(rng);
let mut site = Site { let mut site = Site {
@ -593,6 +599,7 @@ impl Site {
(5.0, 4), (5.0, 4),
(5.0, 5), (5.0, 5),
(15.0, 6), (15.0, 6),
(15.0, 7),
]); ]);
let mut castles = 0; let mut castles = 0;
@ -600,6 +607,8 @@ impl Site {
let mut workshops = 0; let mut workshops = 0;
let mut airship_docks = 0; let mut airship_docks = 0;
let mut taverns = 0;
for _ in 0..(size * 200.0) as i32 { for _ in 0..(size * 200.0) as i32 {
match *build_chance.choose_seeded(rng.gen()) { match *build_chance.choose_seeded(rng.gen()) {
// Workshop // Workshop
@ -917,6 +926,43 @@ impl Site {
} }
} }
}, },
7 if (size > 0.125 && taverns < 2) => {
let size = (3.5 + rng.gen::<f32>().powf(5.0) * 2.0).round() as u32;
if let Some((aabr, door_tile, door_dir)) = attempt(32, || {
site.find_roadside_aabr(
&mut rng,
7..(size + 1).pow(2),
Extent2::broadcast(size),
)
}) {
let tavern = plot::Tavern::generate(
land,
index,
&mut reseed(&mut rng),
&site,
door_tile,
Dir::from_vec2(door_dir),
aabr,
);
let tavern_alt = tavern.door_wpos.z;
let plot = site.create_plot(Plot {
kind: PlotKind::Tavern(tavern),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: Some(tavern_alt),
});
taverns += 1;
} else {
site.make_plaza(land, &mut rng);
}
},
_ => {}, _ => {},
} }
} }
@ -1824,6 +1870,7 @@ impl Site {
let (prim_tree, fills, mut entities) = match &self.plots[plot].kind { let (prim_tree, fills, mut entities) = match &self.plots[plot].kind {
PlotKind::House(house) => house.render_collect(self, canvas), PlotKind::House(house) => house.render_collect(self, canvas),
PlotKind::AirshipDock(airship_dock) => airship_dock.render_collect(self, canvas), PlotKind::AirshipDock(airship_dock) => airship_dock.render_collect(self, canvas),
PlotKind::Tavern(tavern) => tavern.render_collect(self, canvas),
PlotKind::CoastalHouse(coastal_house) => coastal_house.render_collect(self, canvas), PlotKind::CoastalHouse(coastal_house) => coastal_house.render_collect(self, canvas),
PlotKind::CoastalWorkshop(coastal_workshop) => { PlotKind::CoastalWorkshop(coastal_workshop) => {
coastal_workshop.render_collect(self, canvas) coastal_workshop.render_collect(self, canvas)
@ -1963,7 +2010,19 @@ impl Site {
} }
pub fn test_site() -> Site { pub fn test_site() -> Site {
Site::generate_city(&Land::empty(), &mut thread_rng(), Vec2::zero(), 0.5) let index = crate::index::Index::new(0);
let index_ref = IndexRef {
colors: &index.colors(),
features: &index.features(),
index: &index,
};
Site::generate_city(
&Land::empty(),
index_ref,
&mut thread_rng(),
Vec2::zero(),
0.5,
)
} }
fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> { fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {

View File

@ -22,6 +22,7 @@ mod savannah_hut;
mod savannah_pit; mod savannah_pit;
mod savannah_workshop; mod savannah_workshop;
mod sea_chapel; mod sea_chapel;
pub mod tavern;
mod troll_cave; mod troll_cave;
mod workshop; mod workshop;
@ -34,7 +35,7 @@ pub use self::{
gnarling::GnarlingFortification, house::House, jungle_ruin::JungleRuin, gnarling::GnarlingFortification, house::House, jungle_ruin::JungleRuin,
pirate_hideout::PirateHideout, rock_circle::RockCircle, savannah_hut::SavannahHut, pirate_hideout::PirateHideout, rock_circle::RockCircle, savannah_hut::SavannahHut,
savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel, savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel,
troll_cave::TrollCave, workshop::Workshop, tavern::Tavern, troll_cave::TrollCave, workshop::Workshop,
}; };
use super::*; use super::*;
@ -77,6 +78,7 @@ impl Plot {
pub enum PlotKind { pub enum PlotKind {
House(House), House(House),
AirshipDock(AirshipDock), AirshipDock(AirshipDock),
Tavern(Tavern),
CoastalHouse(CoastalHouse), CoastalHouse(CoastalHouse),
CoastalWorkshop(CoastalWorkshop), CoastalWorkshop(CoastalWorkshop),
Workshop(Workshop), Workshop(Workshop),

View File

@ -129,7 +129,7 @@ impl AdletStronghold {
let mut outer_structures = Vec::<(AdletStructure, Vec2<i32>, Dir)>::new(); let mut outer_structures = Vec::<(AdletStructure, Vec2<i32>, Dir)>::new();
let entrance_dir = Dir::from_vector(entrance - cavern_center); let entrance_dir = Dir::from_vec2(entrance - cavern_center);
outer_structures.push((AdletStructure::TunnelEntrance, Vec2::zero(), entrance_dir)); outer_structures.push((AdletStructure::TunnelEntrance, Vec2::zero(), entrance_dir));
let desired_structures = surface_radius.pow(2) / 100; let desired_structures = surface_radius.pow(2) / 100;
@ -176,7 +176,7 @@ impl AdletStronghold {
Some((structure_center, structure_kind)) Some((structure_center, structure_kind))
} }
}) { }) {
let dir_to_wall = Dir::from_vector(rpos); let dir_to_wall = Dir::from_vec2(rpos);
let door_rng: u32 = rng.gen_range(0..9); let door_rng: u32 = rng.gen_range(0..9);
let door_dir = match door_rng { let door_dir = match door_rng {
0..=3 => dir_to_wall, 0..=3 => dir_to_wall,
@ -352,7 +352,7 @@ impl AdletStronghold {
.then_some((structure, rpos)) .then_some((structure, rpos))
}) { }) {
// Direction facing the central bonfire // Direction facing the central bonfire
let dir = Dir::from_vector(rpos).opposite(); let dir = Dir::from_vec2(rpos).opposite();
cavern_structures.push((structure, rpos, dir)); cavern_structures.push((structure, rpos, dir));
} }
} }
@ -493,7 +493,7 @@ impl Structure for AdletStronghold {
// Tunnel // Tunnel
let dist: f32 = self.cavern_center.as_().distance(self.entrance.as_()); let dist: f32 = self.cavern_center.as_().distance(self.entrance.as_());
let dir = Dir::from_vector(self.entrance - self.cavern_center); let dir = Dir::from_vec2(self.entrance - self.cavern_center);
let tunnel_start: Vec3<f32> = match dir { let tunnel_start: Vec3<f32> = match dir {
Dir::X => Vec2::new(self.entrance.x + 7, self.entrance.y), Dir::X => Vec2::new(self.entrance.x + 7, self.entrance.y),
Dir::Y => Vec2::new(self.entrance.x, self.entrance.y + 7), Dir::Y => Vec2::new(self.entrance.x, self.entrance.y + 7),

View File

@ -227,7 +227,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) {
} }
.made_valid(); .made_valid();
let [ramp_aabr, aabr] = bridge.dir.split_aabr(aabr, height); let [ramp_aabr, aabr] = bridge.dir.split_aabr_offset(aabr, height);
let ramp_prim = |ramp_aabr: Aabr<i32>, offset: i32| { let ramp_prim = |ramp_aabr: Aabr<i32>, offset: i32| {
painter painter
@ -254,7 +254,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) {
let vault_offset = 5; let vault_offset = 5;
let bridge_thickness = 4; let bridge_thickness = 4;
let [vault, _] = bridge.dir.split_aabr(aabr, vault_width); let [vault, _] = bridge.dir.split_aabr_offset(aabr, vault_width);
let len = bridge.dir.select(aabr.size()); let len = bridge.dir.select(aabr.size());
let true_offset = vault_width + vault_offset; let true_offset = vault_width + vault_offset;
@ -321,8 +321,11 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten
} }
.made_valid(); .made_valid();
let [_start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z); let [_start_aabr, rest] = bridge
let [_end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z); .dir
.split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
let [_end_aabr, bridge_aabr] =
(-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
let under = bridge.center.z - 15; let under = bridge.center.z - 15;
let bridge_prim = |bridge_width: i32| { let bridge_prim = |bridge_width: i32| {
@ -334,11 +337,14 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten
} }
.made_valid(); .made_valid();
let [start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z); let [start_aabr, rest] = bridge
let [end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z); .dir
.split_aabr_offset(aabr, bridge_start_z - bridge.start.z);
let [end_aabr, bridge_aabr] =
(-bridge.dir).split_aabr_offset(rest, bridge_start_z - bridge.end.z);
let [bridge_start, bridge_end] = bridge let [bridge_start, bridge_end] = bridge
.dir .dir
.split_aabr(bridge_aabr, bridge.dir.select(bridge_aabr.size()) / 2); .split_aabr_offset(bridge_aabr, bridge.dir.select(bridge_aabr.size()) / 2);
let ramp_in_aabr = |aabr: Aabr<i32>, dir: Dir, zmin, zmax| { let ramp_in_aabr = |aabr: Aabr<i32>, dir: Dir, zmin, zmax| {
let inset = dir.select(aabr.size()); let inset = dir.select(aabr.size());
@ -592,7 +598,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) {
let aabr = bridge let aabr = bridge
.dir .dir
.rotated_cw() .rotated_cw()
.split_aabr(tower_aabr, stair_thickness + 1)[1]; .split_aabr_offset(tower_aabr, stair_thickness + 1)[1];
painter painter
.aabb(aabb( .aabb(aabb(
@ -748,7 +754,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) {
let top_offset = 4; let top_offset = 4;
let top = bridge.end.z + top_offset; let top = bridge.end.z + top_offset;
let [ramp_f, aabr] = bridge.dir.split_aabr(aabr, top - bridge.start.z + 1); let [ramp_f, aabr] = bridge.dir.split_aabr_offset(aabr, top - bridge.start.z + 1);
painter painter
.aabb(aabb( .aabb(aabb(
@ -764,7 +770,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) {
) )
.fill(rock.clone()); .fill(rock.clone());
let [ramp_b, aabr] = (-bridge.dir).split_aabr(aabr, top_offset + 1); let [ramp_b, aabr] = (-bridge.dir).split_aabr_offset(aabr, top_offset + 1);
painter painter
.aabb(aabb( .aabb(aabb(
ramp_b.min.with_z(bridge.end.z - 10), ramp_b.min.with_z(bridge.end.z - 10),
@ -878,7 +884,7 @@ impl Bridge {
let min_water_dist = 5; let min_water_dist = 5;
let find_edge = |start: Vec2<i32>, end: Vec2<i32>| { let find_edge = |start: Vec2<i32>, end: Vec2<i32>| {
let mut test_start = start; let mut test_start = start;
let dir = Dir::from_vector(end - start).to_vec2(); let dir = Dir::from_vec2(end - start).to_vec2();
let mut last_alt = if let Some(col) = land.column_sample(start, index) { let mut last_alt = if let Some(col) = land.column_sample(start, index) {
col.alt as i32 col.alt as i32
} else { } else {
@ -932,7 +938,7 @@ impl Bridge {
start, start,
end, end,
center, center,
dir: Dir::from_vector(end.xy() - start.xy()), dir: Dir::from_vec2(end.xy() - start.xy()),
kind: bridge, kind: bridge,
biome: land biome: land
.get_chunk_wpos(center.xy()) .get_chunk_wpos(center.xy())

View File

@ -237,7 +237,7 @@ impl GnarlingFortification {
)) ))
} }
}) { }) {
let dir_to_center = Dir::from_vector(hut_loc.xy()).opposite(); let dir_to_center = Dir::from_vec2(hut_loc.xy()).opposite();
let door_rng: u32 = rng.gen_range(0..9); let door_rng: u32 = rng.gen_range(0..9);
let door_dir = match door_rng { let door_dir = match door_rng {
0..=3 => dir_to_center, 0..=3 => dir_to_center,
@ -262,7 +262,7 @@ impl GnarlingFortification {
let chieftain_hut_loc = ((inner_tower_locs[0] + inner_tower_locs[1]) let chieftain_hut_loc = ((inner_tower_locs[0] + inner_tower_locs[1])
+ 2 * outer_wall_corners[chieftain_indices[1]]) + 2 * outer_wall_corners[chieftain_indices[1]])
/ 4; / 4;
let chieftain_hut_ori = Dir::from_vector(chieftain_hut_loc).opposite(); let chieftain_hut_ori = Dir::from_vec2(chieftain_hut_loc).opposite();
structure_locations.push(( structure_locations.push((
GnarlingStructure::ChieftainHut, GnarlingStructure::ChieftainHut,
chieftain_hut_loc.with_z(rpos_height(chieftain_hut_loc)), chieftain_hut_loc.with_z(rpos_height(chieftain_hut_loc)),

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ use rand::Rng;
use vek::*; use vek::*;
/// A 2d direction. /// A 2d direction.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Debug, enum_map::Enum, strum::EnumIter, enumset::EnumSetType)]
pub enum Dir { pub enum Dir {
X, X,
Y, Y,
@ -26,7 +26,7 @@ impl Dir {
} }
} }
pub fn from_vector(vec: Vec2<i32>) -> Dir { pub fn from_vec2(vec: Vec2<i32>) -> Dir {
if vec.x.abs() > vec.y.abs() { if vec.x.abs() > vec.y.abs() {
if vec.x > 0 { Dir::X } else { Dir::NegX } if vec.x > 0 { Dir::X } else { Dir::NegX }
} else if vec.y > 0 { } else if vec.y > 0 {
@ -110,6 +110,17 @@ impl Dir {
} }
} }
/// Create a vec2 where x is in the direction of `self`, and y is anti
/// clockwise of `self`.
pub fn vec2(self, x: i32, y: i32) -> Vec2<i32> {
match self {
Dir::X => Vec2::new(x, y),
Dir::NegX => Vec2::new(-x, -y),
Dir::Y => Vec2::new(y, x),
Dir::NegY => Vec2::new(-y, -x),
}
}
/// Returns a 3x3 matrix that rotates Vec3(1, 0, 0) to the direction you get /// Returns a 3x3 matrix that rotates Vec3(1, 0, 0) to the direction you get
/// in to_vec3. Inteded to be used with Primitive::Rotate. /// in to_vec3. Inteded to be used with Primitive::Rotate.
/// ///
@ -223,7 +234,7 @@ impl Dir {
} }
} }
pub fn split_aabr<T>(self, aabr: Aabr<T>, offset: T) -> [Aabr<T>; 2] pub fn split_aabr_offset<T>(self, aabr: Aabr<T>, offset: T) -> [Aabr<T>; 2]
where where
T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>, T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
{ {
@ -241,6 +252,29 @@ impl Dir {
} }
} }
/// Try to split an aabr in a certain direction
pub fn try_split_aabr<T>(self, aabr: Aabr<T>, sp: T) -> Option<[Aabr<T>; 2]>
where
T: Copy + PartialOrd + Add<T, Output = T> + Sub<T, Output = T>,
{
match self {
Dir::NegX | Dir::X => {
if aabr.min.x <= sp && sp <= aabr.max.x {
Some(aabr.split_at_x(sp))
} else {
None
}
},
Dir::NegY | Dir::Y => {
if aabr.min.y <= sp && sp <= aabr.max.y {
Some(aabr.split_at_y(sp))
} else {
None
}
},
}
}
pub fn trim_aabr(self, aabr: Aabr<i32>, offset: i32) -> Aabr<i32> { pub fn trim_aabr(self, aabr: Aabr<i32>, offset: i32) -> Aabr<i32> {
Aabr { Aabr {
min: aabr.min + self.abs().to_vec2() * offset, min: aabr.min + self.abs().to_vec2() * offset,

View File

@ -15,6 +15,15 @@ impl RandomField {
pub fn get_f32(&self, pos: Vec3<i32>) -> f32 { pub fn get_f32(&self, pos: Vec3<i32>) -> f32 {
(self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) (self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32)
} }
pub fn choose<'a, T>(&self, pos: Vec3<i32>, slice: &'a [T]) -> Option<&'a T> {
if slice.is_empty() {
return None;
}
let i = self.get(pos) as usize;
slice.get(i % slice.len())
}
} }
impl Sampler<'static> for RandomField { impl Sampler<'static> for RandomField {