mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'isse/walk-toggle' into 'master'
Roleplay stuff See merge request veloren/veloren!4171
This commit is contained in:
commit
ba02d083da
@ -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>`.
|
||||
- Shorthand in voxygen for specific entities in commands, some examples `@target`, `@mount`, `@viewpoint`.
|
||||
- Added hit_timing to BasicMelee abilities
|
||||
- A tavern building where npcs go to relax.
|
||||
- Toggle for walking instead of running (Default: `I`).
|
||||
|
||||
### Changed
|
||||
|
||||
|
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -1880,9 +1880,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "enumset"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb"
|
||||
checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d"
|
||||
dependencies = [
|
||||
"enumset_derive",
|
||||
]
|
||||
@ -7430,6 +7430,7 @@ dependencies = [
|
||||
"csv",
|
||||
"deflate",
|
||||
"enum-map",
|
||||
"enumset",
|
||||
"fallible-iterator",
|
||||
"flate2",
|
||||
"fxhash",
|
||||
|
@ -75,3 +75,4 @@ gameinput-muteinactivemaster = Mute master volume (inactive window)
|
||||
gameinput-mutemusic = Mute music volume
|
||||
gameinput-mutesfx = Mute SFX volume
|
||||
gameinput-muteambience = Mute ambience volume
|
||||
gameinput-togglewalk = Toggle Walking
|
||||
|
@ -59,6 +59,7 @@ hud-follow = Follow
|
||||
hud-stay= Stay
|
||||
hud-sit = Sit
|
||||
hud-steer = Steer
|
||||
hud-lay = Lay
|
||||
hud-portal = Portal
|
||||
|
||||
-server = Server
|
||||
|
@ -53,6 +53,8 @@ hud-settings-invert_controller_y_axis = Invert Controller Y Axis
|
||||
hud-settings-enable_mouse_smoothing = Camera Smoothing
|
||||
hud-settings-free_look_behavior = Free look 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-zoom_lock_behavior = Camera zoom lock behavior
|
||||
hud-settings-player_physics_behavior = Player physics (experimental)
|
||||
|
@ -1071,7 +1071,6 @@ impl CharacterAbility {
|
||||
&& match self {
|
||||
CharacterAbility::Roll { energy_cost, .. } => {
|
||||
data.physics.on_ground.is_some()
|
||||
&& data.inputs.move_dir.magnitude_squared() > 0.25
|
||||
&& update.energy.try_change_by(-*energy_cost).is_ok()
|
||||
},
|
||||
CharacterAbility::DashMelee { energy_cost, .. }
|
||||
|
@ -986,6 +986,10 @@ pub struct CharacterActivity {
|
||||
/// `None` means that the look direction should be derived from the
|
||||
/// orientation
|
||||
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
|
||||
/// to not engage in combat
|
||||
pub is_pet_staying: bool,
|
||||
|
@ -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
|
||||
|
@ -566,22 +566,15 @@ impl SpriteKind {
|
||||
#[inline]
|
||||
pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
|
||||
match self {
|
||||
SpriteKind::ChairSingle | SpriteKind::ChairDouble | SpriteKind::Bench => Some((
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.5,
|
||||
},
|
||||
-Vec3::unit_y(),
|
||||
)),
|
||||
SpriteKind::Helm => Some((
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: -0.6,
|
||||
z: 0.2,
|
||||
},
|
||||
Vec3::unit_y(),
|
||||
)),
|
||||
SpriteKind::ChairSingle | SpriteKind::ChairDouble | SpriteKind::Bench => {
|
||||
Some((Vec3::new(0.0, 0.0, 0.5), -Vec3::unit_y()))
|
||||
},
|
||||
SpriteKind::Helm => Some((Vec3::new(0.0, -1.0, 0.0), Vec3::unit_y())),
|
||||
SpriteKind::Bed => Some((Vec3::new(0.0, 0.0, 0.6), -Vec3::unit_y())),
|
||||
SpriteKind::BedrollSnow | SpriteKind::BedrollPirate => {
|
||||
Some((Vec3::new(0.0, 0.0, 0.1), -Vec3::unit_x()))
|
||||
},
|
||||
SpriteKind::Bedroll => Some((Vec3::new(0.0, 0.0, 0.1), Vec3::unit_y())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
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,
|
||||
mounting::{Mount, VolumeRider},
|
||||
terrain::TerrainGrid,
|
||||
@ -24,6 +27,7 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
WriteStorage<'a, CharacterActivity>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Collider>,
|
||||
@ -45,6 +49,7 @@ impl<'a> System<'a> for Sys {
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
mut character_activity,
|
||||
bodies,
|
||||
scales,
|
||||
colliders,
|
||||
@ -174,6 +179,12 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
if is_volume_rider.block.is_controller() {
|
||||
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 {
|
||||
common::mounting::Volume::Entity(uid) => {
|
||||
if let Some(controller) =
|
||||
|
@ -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));
|
||||
|
@ -113,6 +113,7 @@ impl Data {
|
||||
PlotKind::House(_)
|
||||
| PlotKind::Workshop(_)
|
||||
| PlotKind::AirshipDock(_)
|
||||
| PlotKind::Tavern(_)
|
||||
| PlotKind::Plaza
|
||||
| PlotKind::SavannahPit(_)
|
||||
| PlotKind::SavannahHut(_)
|
||||
|
@ -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,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.
|
||||
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(|_, _| ())
|
||||
}
|
||||
|
||||
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 +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| {
|
||||
// Skip most socialising actions if we're not loaded
|
||||
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> {
|
||||
choose(move |ctx, state: &mut DefaultState| {
|
||||
// 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(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 +850,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,14 @@ 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 +402,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 +593,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 +651,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,9 +1009,7 @@ impl<'a> AgentData<'a> {
|
||||
#[cfg(feature = "be-dyn-lib")]
|
||||
let rng = &mut thread_rng();
|
||||
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
self.dismount(controller, read_data);
|
||||
|
||||
let tool_tactic = |tool_kind| match tool_kind {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,11 +28,13 @@ pub mod selfbuff;
|
||||
pub mod shockwave;
|
||||
pub mod shoot;
|
||||
pub mod sit;
|
||||
pub mod sleep;
|
||||
pub mod sneak;
|
||||
pub mod sneakequip;
|
||||
pub mod sneakwield;
|
||||
pub mod staggered;
|
||||
pub mod stand;
|
||||
pub mod steer;
|
||||
pub mod stunned;
|
||||
pub mod swim;
|
||||
pub mod swimwield;
|
||||
@ -51,11 +53,11 @@ pub use self::{
|
||||
mount::MountAnimation, music::MusicAnimation, rapidmelee::RapidMeleeAnimation,
|
||||
repeater::RepeaterAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation,
|
||||
run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation,
|
||||
shoot::ShootAnimation, sit::SitAnimation, sneak::SneakAnimation,
|
||||
shoot::ShootAnimation, sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation,
|
||||
sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation,
|
||||
staggered::StaggeredAnimation, stand::StandAnimation, stunned::StunnedAnimation,
|
||||
swim::SwimAnimation, swimwield::SwimWieldAnimation, talk::TalkAnimation,
|
||||
wallrun::WallrunAnimation, wield::WieldAnimation,
|
||||
staggered::StaggeredAnimation, stand::StandAnimation, steer::SteerAnimation,
|
||||
stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation,
|
||||
talk::TalkAnimation, wallrun::WallrunAnimation, wield::WieldAnimation,
|
||||
};
|
||||
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource};
|
||||
use common::comp::{
|
||||
@ -431,8 +433,8 @@ impl CharacterSkeleton {
|
||||
* ((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.orientation =
|
||||
Quaternion::rotation_x(shorte * 0.7 + 0.4) * Quaternion::rotation_y(shorte * 0.4);
|
||||
self.lantern.orientation = Quaternion::rotation_x(shorte * 0.7 * speednorm.powi(2) + 0.4)
|
||||
* Quaternion::rotation_y(shorte * 0.4 * speednorm.powi(2));
|
||||
self.lantern.scale = Vec3::one() * 0.65;
|
||||
self.hold.scale = Vec3::one() * 0.0;
|
||||
|
||||
|
@ -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.orientation =
|
||||
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.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
|
||||
+ (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.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 =
|
||||
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);
|
||||
|
||||
next.hand_r.position = Vec3::new(
|
||||
s_a.hand.0 * 1.2 + foothoril * 1.3 * speednorm
|
||||
- (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.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 =
|
||||
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);
|
||||
|
||||
next.foot_l.position = Vec3::new(
|
||||
|
114
voxygen/anim/src/character/sleep.rs
Normal file
114
voxygen/anim/src/character/sleep.rs
Normal 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
|
||||
}
|
||||
}
|
118
voxygen/anim/src/character/steer.rs
Normal file
118
voxygen/anim/src/character/steer.rs
Normal 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
|
||||
}
|
||||
}
|
@ -170,6 +170,8 @@ pub enum GameInput {
|
||||
MuteSfx,
|
||||
#[strum(serialize = "gameinput-muteambience")]
|
||||
MuteAmbience,
|
||||
#[strum(serialize = "gameinput-togglewalk")]
|
||||
ToggleWalk,
|
||||
}
|
||||
|
||||
impl GameInput {
|
||||
|
@ -342,6 +342,10 @@ widget_ids! {
|
||||
auto_walk_txt,
|
||||
auto_walk_bg,
|
||||
|
||||
// Walking speed indicator
|
||||
walking_speed_txt,
|
||||
walking_speed_bg,
|
||||
|
||||
// Temporal (fading) camera zoom lock indicator
|
||||
zoom_lock_txt,
|
||||
zoom_lock_bg,
|
||||
@ -2149,6 +2153,7 @@ impl Hud {
|
||||
BlockInteraction::Mount => {
|
||||
let key = match block.get_sprite() {
|
||||
Some(SpriteKind::Helm) => "hud-steer",
|
||||
Some(SpriteKind::Bed | SpriteKind::Bedroll | SpriteKind::BedrollSnow | SpriteKind::BedrollPirate) => "hud-lay",
|
||||
_ => "hud-sit",
|
||||
};
|
||||
vec![(Some(GameInput::Mount), i18n.get_msg(key).to_string())]
|
||||
|
@ -31,6 +31,9 @@ widget_ids! {
|
||||
camera_clamp_slider,
|
||||
camera_clamp_label,
|
||||
camera_clamp_value,
|
||||
walking_speed_slider,
|
||||
walking_speed_label,
|
||||
walking_speed_value,
|
||||
mouse_y_invert_button,
|
||||
mouse_y_invert_label,
|
||||
controller_y_invert_button,
|
||||
@ -42,6 +45,8 @@ widget_ids! {
|
||||
free_look_behavior_list,
|
||||
auto_walk_behavior_text,
|
||||
auto_walk_behavior_list,
|
||||
walking_speed_behavior_text,
|
||||
walking_speed_behavior_list,
|
||||
camera_clamp_behavior_text,
|
||||
camera_clamp_behavior_list,
|
||||
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_zoom = self.global_state.settings.gameplay.zoom_sensitivity;
|
||||
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
|
||||
Text::new(
|
||||
@ -233,6 +239,38 @@ impl<'a> Widget for Gameplay<'a> {
|
||||
.color(TEXT_COLOR)
|
||||
.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
|
||||
let zoom_inverted = ToggleButton::new(
|
||||
self.global_state.settings.gameplay.zoom_inversion,
|
||||
@ -240,7 +278,7 @@ impl<'a> Widget for Gameplay<'a> {
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.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)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.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
|
||||
Text::new(
|
||||
&self
|
||||
.localized_strings
|
||||
.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_id(self.fonts.cyri.conrod_id)
|
||||
.color(TEXT_COLOR)
|
||||
@ -545,7 +613,7 @@ impl<'a> Widget for Gameplay<'a> {
|
||||
.localized_strings
|
||||
.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_id(self.fonts.cyri.conrod_id)
|
||||
.color(TEXT_COLOR)
|
||||
@ -599,7 +667,7 @@ impl<'a> Widget for Gameplay<'a> {
|
||||
.w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.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(
|
||||
&self
|
||||
.localized_strings
|
||||
|
@ -11,6 +11,7 @@ pub struct KeyState {
|
||||
pub swim_down: bool,
|
||||
pub fly: bool,
|
||||
pub auto_walk: bool,
|
||||
pub speed_mul: f32,
|
||||
pub trade: bool,
|
||||
pub analog_matrix: Vec2<f32>,
|
||||
}
|
||||
@ -28,6 +29,7 @@ impl Default for KeyState {
|
||||
swim_down: false,
|
||||
fly: false,
|
||||
auto_walk: false,
|
||||
speed_mul: 1.0,
|
||||
trade: false,
|
||||
analog_matrix: Vec2::zero(),
|
||||
}
|
||||
@ -42,9 +44,11 @@ impl KeyState {
|
||||
if self.up || self.auto_walk { 1.0 } else { 0.0 }
|
||||
+ if self.down { -1.0 } else { 0.0 },
|
||||
)
|
||||
.try_normalized()
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
self.analog_matrix
|
||||
};
|
||||
} * self.speed_mul;
|
||||
|
||||
if dir.magnitude_squared() <= 1.0 {
|
||||
dir
|
||||
|
@ -1110,7 +1110,10 @@ impl FigureMgr {
|
||||
&& matches!(active_tool_hand, Some(Hands::One)))
|
||||
|| !character.map_or(false, |c| c.is_wield()))
|
||||
&& !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
|
||||
.and_then(|i| i.equipped(EquipSlot::Armor(ArmorSlot::Back)))
|
||||
@ -2137,7 +2140,26 @@ impl FigureMgr {
|
||||
{
|
||||
match sprite {
|
||||
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,
|
||||
(active_tool_kind, second_tool_kind, time),
|
||||
state.state_time,
|
||||
|
@ -207,7 +207,9 @@ impl BlocksOfInterest {
|
||||
)
|
||||
.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)),
|
||||
_ => {},
|
||||
},
|
||||
|
@ -101,6 +101,7 @@ pub struct SessionState {
|
||||
walk_right_dir: Vec2<f32>,
|
||||
free_look: bool,
|
||||
auto_walk: bool,
|
||||
walking_speed: bool,
|
||||
camera_clamp: bool,
|
||||
zoom_lock: bool,
|
||||
is_aiming: bool,
|
||||
@ -171,6 +172,7 @@ impl SessionState {
|
||||
walk_right_dir,
|
||||
free_look: false,
|
||||
auto_walk: false,
|
||||
walking_speed: false,
|
||||
camera_clamp: false,
|
||||
zoom_lock: false,
|
||||
is_aiming: false,
|
||||
@ -710,6 +712,7 @@ impl PlayState for SessionState {
|
||||
}
|
||||
match input {
|
||||
GameInput::Primary => {
|
||||
self.walking_speed = false;
|
||||
let mut client = self.client.borrow_mut();
|
||||
// Mine and build targets can be the same block. make building
|
||||
// take precedence.
|
||||
@ -728,6 +731,7 @@ impl PlayState for SessionState {
|
||||
}
|
||||
},
|
||||
GameInput::Secondary => {
|
||||
self.walking_speed = false;
|
||||
let mut client = self.client.borrow_mut();
|
||||
if let Some(build_target) = build_target.filter(|bt| {
|
||||
state && can_build && nearest_block_dist == Some(bt.distance)
|
||||
@ -747,6 +751,7 @@ impl PlayState for SessionState {
|
||||
}
|
||||
},
|
||||
GameInput::Block => {
|
||||
self.walking_speed = false;
|
||||
self.client.borrow_mut().handle_input(
|
||||
InputKind::Block,
|
||||
state,
|
||||
@ -755,6 +760,7 @@ impl PlayState for SessionState {
|
||||
);
|
||||
},
|
||||
GameInput::Roll => {
|
||||
self.walking_speed = false;
|
||||
let mut client = self.client.borrow_mut();
|
||||
if can_build {
|
||||
if state {
|
||||
@ -779,12 +785,14 @@ impl PlayState for SessionState {
|
||||
}
|
||||
},
|
||||
GameInput::Respawn => {
|
||||
self.walking_speed = false;
|
||||
self.stop_auto_walk();
|
||||
if state {
|
||||
self.client.borrow_mut().respawn();
|
||||
}
|
||||
},
|
||||
GameInput::Jump => {
|
||||
self.walking_speed = false;
|
||||
self.client.borrow_mut().handle_input(
|
||||
InputKind::Jump,
|
||||
state,
|
||||
@ -847,6 +855,7 @@ impl PlayState for SessionState {
|
||||
self.key_state.right = state
|
||||
},
|
||||
GameInput::Glide => {
|
||||
self.walking_speed = false;
|
||||
let is_trading = self.client.borrow().is_trading();
|
||||
if state && !is_trading {
|
||||
if global_state.settings.gameplay.stop_auto_walk_on_input {
|
||||
@ -877,7 +886,11 @@ impl PlayState for SessionState {
|
||||
},
|
||||
GameInput::ToggleWield => {
|
||||
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 => {
|
||||
@ -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
|
||||
self.scene
|
||||
.camera_mut()
|
||||
|
@ -59,6 +59,7 @@ pub enum Gameplay {
|
||||
AdjustMousePan(u32),
|
||||
AdjustMouseZoom(u32),
|
||||
AdjustCameraClamp(u32),
|
||||
AdjustWalkingSpeed(f32),
|
||||
|
||||
ToggleControllerYInvert(bool),
|
||||
ToggleMouseYInvert(bool),
|
||||
@ -68,6 +69,7 @@ pub enum Gameplay {
|
||||
|
||||
ChangeFreeLookBehavior(PressBehavior),
|
||||
ChangeAutoWalkBehavior(PressBehavior),
|
||||
ChangeWalkingSpeedBehavior(PressBehavior),
|
||||
ChangeCameraClampBehavior(PressBehavior),
|
||||
ChangeZoomLockBehavior(AutoPressBehavior),
|
||||
ChangeStopAutoWalkOnInput(bool),
|
||||
@ -379,6 +381,9 @@ impl SettingsChange {
|
||||
Gameplay::AdjustCameraClamp(angle) => {
|
||||
settings.gameplay.camera_clamp_angle = angle;
|
||||
},
|
||||
Gameplay::AdjustWalkingSpeed(speed) => {
|
||||
settings.gameplay.walking_speed = speed;
|
||||
},
|
||||
Gameplay::ToggleControllerYInvert(controller_y_inverted) => {
|
||||
window.controller_settings.pan_invert_y = controller_y_inverted;
|
||||
settings.controller.pan_invert_y = controller_y_inverted;
|
||||
@ -400,6 +405,9 @@ impl SettingsChange {
|
||||
Gameplay::ChangeAutoWalkBehavior(behavior) => {
|
||||
settings.gameplay.auto_walk_behavior = behavior;
|
||||
},
|
||||
Gameplay::ChangeWalkingSpeedBehavior(behavior) => {
|
||||
settings.gameplay.walking_speed_behavior = behavior;
|
||||
},
|
||||
Gameplay::ChangeCameraClampBehavior(behavior) => {
|
||||
settings.gameplay.camera_clamp_behavior = behavior;
|
||||
},
|
||||
|
@ -200,6 +200,7 @@ impl ControlSettings {
|
||||
GameInput::MuteMusic => Some(KeyMouse::Key(VirtualKeyCode::F8)),
|
||||
GameInput::MuteSfx => None,
|
||||
GameInput::MuteAmbience => None,
|
||||
GameInput::ToggleWalk => Some(KeyMouse::Key(VirtualKeyCode::I)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,13 @@ pub struct GameplaySettings {
|
||||
pub pan_sensitivity: u32,
|
||||
pub zoom_sensitivity: u32,
|
||||
pub camera_clamp_angle: u32,
|
||||
pub walking_speed: f32,
|
||||
pub zoom_inversion: bool,
|
||||
pub mouse_y_inversion: bool,
|
||||
pub smooth_pan_enable: bool,
|
||||
pub free_look_behavior: PressBehavior,
|
||||
pub auto_walk_behavior: PressBehavior,
|
||||
pub walking_speed_behavior: PressBehavior,
|
||||
pub camera_clamp_behavior: PressBehavior,
|
||||
pub zoom_lock_behavior: AutoPressBehavior,
|
||||
pub stop_auto_walk_on_input: bool,
|
||||
@ -27,11 +29,13 @@ impl Default for GameplaySettings {
|
||||
pan_sensitivity: 100,
|
||||
zoom_sensitivity: 100,
|
||||
camera_clamp_angle: 45,
|
||||
walking_speed: 0.35,
|
||||
zoom_inversion: false,
|
||||
mouse_y_inversion: false,
|
||||
smooth_pan_enable: false,
|
||||
free_look_behavior: PressBehavior::Toggle,
|
||||
auto_walk_behavior: PressBehavior::Toggle,
|
||||
walking_speed_behavior: PressBehavior::Toggle,
|
||||
camera_clamp_behavior: PressBehavior::Toggle,
|
||||
zoom_lock_behavior: AutoPressBehavior::Auto,
|
||||
stop_auto_walk_on_input: true,
|
||||
|
@ -21,6 +21,7 @@ common-dynlib = {package = "veloren-common-dynlib", path = "../common/dynlib", o
|
||||
bincode = { workspace = true }
|
||||
bitvec = "1.0.1"
|
||||
enum-map = { workspace = true }
|
||||
enumset = "1.1.3"
|
||||
fxhash = { workspace = true }
|
||||
image = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
@ -539,6 +539,7 @@ impl Civs {
|
||||
let size = Lerp::lerp(0.03, 1.0, rng.gen_range(0.0..1f32).powi(5));
|
||||
WorldSite::refactor(site2::Site::generate_city(
|
||||
&Land::from_sim(ctx.sim),
|
||||
index_ref,
|
||||
&mut rng,
|
||||
wpos,
|
||||
size,
|
||||
|
@ -689,4 +689,168 @@ impl<'a, R: Rng> NameGen<'a, R> {
|
||||
];
|
||||
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)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -573,7 +573,13 @@ impl Site {
|
||||
}
|
||||
|
||||
// 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 site = Site {
|
||||
@ -593,6 +599,7 @@ impl Site {
|
||||
(5.0, 4),
|
||||
(5.0, 5),
|
||||
(15.0, 6),
|
||||
(15.0, 7),
|
||||
]);
|
||||
|
||||
let mut castles = 0;
|
||||
@ -600,6 +607,8 @@ impl Site {
|
||||
let mut workshops = 0;
|
||||
|
||||
let mut airship_docks = 0;
|
||||
|
||||
let mut taverns = 0;
|
||||
for _ in 0..(size * 200.0) as i32 {
|
||||
match *build_chance.choose_seeded(rng.gen()) {
|
||||
// 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 {
|
||||
PlotKind::House(house) => house.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::CoastalWorkshop(coastal_workshop) => {
|
||||
coastal_workshop.render_collect(self, canvas)
|
||||
@ -1963,7 +2010,19 @@ impl 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> {
|
||||
|
@ -22,6 +22,7 @@ mod savannah_hut;
|
||||
mod savannah_pit;
|
||||
mod savannah_workshop;
|
||||
mod sea_chapel;
|
||||
pub mod tavern;
|
||||
mod troll_cave;
|
||||
mod workshop;
|
||||
|
||||
@ -34,7 +35,7 @@ pub use self::{
|
||||
gnarling::GnarlingFortification, house::House, jungle_ruin::JungleRuin,
|
||||
pirate_hideout::PirateHideout, rock_circle::RockCircle, savannah_hut::SavannahHut,
|
||||
savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel,
|
||||
troll_cave::TrollCave, workshop::Workshop,
|
||||
tavern::Tavern, troll_cave::TrollCave, workshop::Workshop,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -77,6 +78,7 @@ impl Plot {
|
||||
pub enum PlotKind {
|
||||
House(House),
|
||||
AirshipDock(AirshipDock),
|
||||
Tavern(Tavern),
|
||||
CoastalHouse(CoastalHouse),
|
||||
CoastalWorkshop(CoastalWorkshop),
|
||||
Workshop(Workshop),
|
||||
|
@ -129,7 +129,7 @@ impl AdletStronghold {
|
||||
|
||||
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));
|
||||
|
||||
let desired_structures = surface_radius.pow(2) / 100;
|
||||
@ -176,7 +176,7 @@ impl AdletStronghold {
|
||||
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_dir = match door_rng {
|
||||
0..=3 => dir_to_wall,
|
||||
@ -352,7 +352,7 @@ impl AdletStronghold {
|
||||
.then_some((structure, rpos))
|
||||
}) {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
@ -493,7 +493,7 @@ impl Structure for AdletStronghold {
|
||||
|
||||
// Tunnel
|
||||
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 {
|
||||
Dir::X => Vec2::new(self.entrance.x + 7, self.entrance.y),
|
||||
Dir::Y => Vec2::new(self.entrance.x, self.entrance.y + 7),
|
||||
|
@ -227,7 +227,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) {
|
||||
}
|
||||
.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| {
|
||||
painter
|
||||
@ -254,7 +254,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) {
|
||||
let vault_offset = 5;
|
||||
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 true_offset = vault_width + vault_offset;
|
||||
@ -321,8 +321,11 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten
|
||||
}
|
||||
.made_valid();
|
||||
|
||||
let [_start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z);
|
||||
let [_end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z);
|
||||
let [_start_aabr, rest] = bridge
|
||||
.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 bridge_prim = |bridge_width: i32| {
|
||||
@ -334,11 +337,14 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten
|
||||
}
|
||||
.made_valid();
|
||||
|
||||
let [start_aabr, rest] = bridge.dir.split_aabr(aabr, bridge_start_z - bridge.start.z);
|
||||
let [end_aabr, bridge_aabr] = (-bridge.dir).split_aabr(rest, bridge_start_z - bridge.end.z);
|
||||
let [start_aabr, rest] = bridge
|
||||
.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
|
||||
.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 inset = dir.select(aabr.size());
|
||||
@ -592,7 +598,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) {
|
||||
let aabr = bridge
|
||||
.dir
|
||||
.rotated_cw()
|
||||
.split_aabr(tower_aabr, stair_thickness + 1)[1];
|
||||
.split_aabr_offset(tower_aabr, stair_thickness + 1)[1];
|
||||
|
||||
painter
|
||||
.aabb(aabb(
|
||||
@ -748,7 +754,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) {
|
||||
let top_offset = 4;
|
||||
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
|
||||
.aabb(aabb(
|
||||
@ -764,7 +770,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) {
|
||||
)
|
||||
.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
|
||||
.aabb(aabb(
|
||||
ramp_b.min.with_z(bridge.end.z - 10),
|
||||
@ -878,7 +884,7 @@ impl Bridge {
|
||||
let min_water_dist = 5;
|
||||
let find_edge = |start: Vec2<i32>, end: Vec2<i32>| {
|
||||
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) {
|
||||
col.alt as i32
|
||||
} else {
|
||||
@ -932,7 +938,7 @@ impl Bridge {
|
||||
start,
|
||||
end,
|
||||
center,
|
||||
dir: Dir::from_vector(end.xy() - start.xy()),
|
||||
dir: Dir::from_vec2(end.xy() - start.xy()),
|
||||
kind: bridge,
|
||||
biome: land
|
||||
.get_chunk_wpos(center.xy())
|
||||
|
@ -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_dir = match door_rng {
|
||||
0..=3 => dir_to_center,
|
||||
@ -262,7 +262,7 @@ impl GnarlingFortification {
|
||||
let chieftain_hut_loc = ((inner_tower_locs[0] + inner_tower_locs[1])
|
||||
+ 2 * outer_wall_corners[chieftain_indices[1]])
|
||||
/ 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((
|
||||
GnarlingStructure::ChieftainHut,
|
||||
chieftain_hut_loc.with_z(rpos_height(chieftain_hut_loc)),
|
||||
|
1588
world/src/site2/plot/tavern.rs
Normal file
1588
world/src/site2/plot/tavern.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ use rand::Rng;
|
||||
use vek::*;
|
||||
|
||||
/// A 2d direction.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, enum_map::Enum, strum::EnumIter, enumset::EnumSetType)]
|
||||
pub enum Dir {
|
||||
X,
|
||||
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 > 0 { Dir::X } else { Dir::NegX }
|
||||
} 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
|
||||
/// 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
|
||||
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> {
|
||||
Aabr {
|
||||
min: aabr.min + self.abs().to_vec2() * offset,
|
||||
|
@ -15,6 +15,15 @@ impl RandomField {
|
||||
pub fn get_f32(&self, pos: Vec3<i32>) -> 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 {
|
||||
|
Loading…
Reference in New Issue
Block a user