From 5320f83462c3581ed2a12727f17f24a4165c4584 Mon Sep 17 00:00:00 2001 From: Isse Date: Fri, 13 Oct 2023 21:28:56 +0200 Subject: [PATCH 01/26] configurable walking speed --- assets/voxygen/i18n/en/gameinput.ftl | 1 + assets/voxygen/i18n/en/hud/misc.ftl | 1 + assets/voxygen/i18n/en/hud/settings.ftl | 2 + voxygen/src/game_input.rs | 2 + voxygen/src/hud/mod.rs | 27 ++++++++ voxygen/src/hud/settings_window/gameplay.rs | 76 +++++++++++++++++++-- voxygen/src/key_state.rs | 4 +- voxygen/src/session/mod.rs | 18 +++++ voxygen/src/session/settings_change.rs | 8 +++ voxygen/src/settings/control.rs | 1 + voxygen/src/settings/gameplay.rs | 4 ++ 11 files changed, 139 insertions(+), 5 deletions(-) diff --git a/assets/voxygen/i18n/en/gameinput.ftl b/assets/voxygen/i18n/en/gameinput.ftl index 8c6527660b..e3de56ea0e 100644 --- a/assets/voxygen/i18n/en/gameinput.ftl +++ b/assets/voxygen/i18n/en/gameinput.ftl @@ -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 diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index 7f5b51943e..7984a521fa 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -36,6 +36,7 @@ hud-diary = Diary hud-free_look_indicator = Free look active. Press { $key } to disable. hud-camera_clamp_indicator = Camera vertical clamp active. Press { $key } to disable. hud-auto_walk_indicator = Auto walk/swim active +hud-walking_speed_indicator = Walking speed active hud-zoom_lock_indicator-remind = Zoom locked hud-zoom_lock_indicator-enable = Camera zoom locked hud-zoom_lock_indicator-disable = Camera zoom unlocked diff --git a/assets/voxygen/i18n/en/hud/settings.ftl b/assets/voxygen/i18n/en/hud/settings.ftl index 92befd432e..fc2ec20847 100644 --- a/assets/voxygen/i18n/en/hud/settings.ftl +++ b/assets/voxygen/i18n/en/hud/settings.ftl @@ -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) diff --git a/voxygen/src/game_input.rs b/voxygen/src/game_input.rs index e84a93f88c..0a494a1b66 100644 --- a/voxygen/src/game_input.rs +++ b/voxygen/src/game_input.rs @@ -170,6 +170,8 @@ pub enum GameInput { MuteSfx, #[strum(serialize = "gameinput-muteambience")] MuteAmbience, + #[strum(serialize = "gameinput-togglewalk")] + ToggleWalk, } impl GameInput { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 94045298f0..d9f6a37e17 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -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, @@ -909,6 +913,7 @@ pub struct Show { stats: bool, free_look: bool, auto_walk: bool, + walking_speed: bool, zoom_lock: ChangeNotification, camera_clamp: bool, prompt_dialog: Option, @@ -1416,6 +1421,7 @@ impl Hud { stats: false, free_look: false, auto_walk: false, + walking_speed: false, zoom_lock: ChangeNotification::default(), camera_clamp: false, prompt_dialog: None, @@ -3815,6 +3821,23 @@ impl Hud { .set(self.ids.auto_walk_txt, ui_widgets); } + // Walking speed indicator + if self.show.walking_speed { + Text::new(&i18n.get_msg("hud-walking_speed_indicator")) + .color(TEXT_BG) + .mid_top_with_margin_on(ui_widgets.window, indicator_offset) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(20)) + .set(self.ids.walking_speed_bg, ui_widgets); + indicator_offset += 30.0; + Text::new(&i18n.get_msg("hud-walking_speed_indicator")) + .color(KILL_COLOR) + .top_left_with_margins_on(self.ids.walking_speed_bg, -1.0, -1.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(20)) + .set(self.ids.walking_speed_txt, ui_widgets); + } + // Camera zoom lock self.show.zoom_lock.update(dt); @@ -4915,6 +4938,10 @@ impl Hud { pub fn auto_walk(&mut self, auto_walk: bool) { self.show.auto_walk = auto_walk; } + pub fn walking_speed(&mut self, walking_speed: bool) { + self.show.walking_speed = walking_speed; + } + pub fn camera_clamp(&mut self, camera_clamp: bool) { self.show.camera_clamp = camera_clamp; } /// Remind the player camera zoom is currently locked, for example if they diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index 666f8ba573..d64d4614c6 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -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 diff --git a/voxygen/src/key_state.rs b/voxygen/src/key_state.rs index 0b9dfc155d..281fa9ff00 100644 --- a/voxygen/src/key_state.rs +++ b/voxygen/src/key_state.rs @@ -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, } @@ -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(), } @@ -44,7 +46,7 @@ impl KeyState { ) } else { self.analog_matrix - }; + } * self.speed_mul; if dir.magnitude_squared() <= 1.0 { dir diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index a8eee89fd5..1a1bbe3aaf 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -101,6 +101,7 @@ pub struct SessionState { walk_right_dir: Vec2, 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, @@ -1214,6 +1216,22 @@ impl PlayState for SessionState { } } }, + GameInput::ToggleWalk if state => { + let hud = &mut self.hud; + global_state + .settings + .gameplay + .walking_speed_behavior + .update(state, &mut self.walking_speed, |b| { + hud.walking_speed(b) + }); + + self.key_state.speed_mul = if self.walking_speed { + global_state.settings.gameplay.walking_speed + } else { + 1.0 + }; + }, _ => {}, } }, diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 7315059f54..c624996aeb 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -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; }, diff --git a/voxygen/src/settings/control.rs b/voxygen/src/settings/control.rs index c7876901b7..a3a123def2 100644 --- a/voxygen/src/settings/control.rs +++ b/voxygen/src/settings/control.rs @@ -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)), } } } diff --git a/voxygen/src/settings/gameplay.rs b/voxygen/src/settings/gameplay.rs index 6a8fe35ce1..ebb06d6627 100644 --- a/voxygen/src/settings/gameplay.rs +++ b/voxygen/src/settings/gameplay.rs @@ -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, From 63e500d3d8e9af1d97c506cd338a7da4f7193dff Mon Sep 17 00:00:00 2001 From: Isse Date: Sat, 14 Oct 2023 10:25:22 +0200 Subject: [PATCH 02/26] laying and steering animation --- assets/voxygen/i18n/en/hud/misc.ftl | 1 + common/src/comp/character_state.rs | 3 + common/src/terrain/sprite.rs | 25 +++--- common/systems/src/mount.rs | 10 ++- voxygen/anim/src/character/mod.rs | 10 ++- voxygen/anim/src/character/sleep.rs | 114 ++++++++++++++++++++++++++++ voxygen/anim/src/character/steer.rs | 110 +++++++++++++++++++++++++++ voxygen/src/hud/mod.rs | 1 + voxygen/src/scene/figure/mod.rs | 26 ++++++- 9 files changed, 277 insertions(+), 23 deletions(-) create mode 100644 voxygen/anim/src/character/sleep.rs create mode 100644 voxygen/anim/src/character/steer.rs diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index 7984a521fa..9f80fbb611 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -60,6 +60,7 @@ hud-follow = Follow hud-stay= Stay hud-sit = Sit hud-steer = Steer +hud-lay = Lay hud-portal = Portal -server = Server diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 60fdf33870..82c1930b9d 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -986,6 +986,9 @@ pub struct CharacterActivity { /// `None` means that the look direction should be derived from the /// orientation pub look_dir: Option, + /// If the character is using a Helm, this is set to Some, with the y + /// direction we're steering. + pub steer_dir: Option, /// 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, diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index 125a4c60ac..89e02d8950 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -566,22 +566,15 @@ impl SpriteKind { #[inline] pub fn mount_offset(&self) -> Option<(Vec3, Vec3)> { 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, } } diff --git a/common/systems/src/mount.rs b/common/systems/src/mount.rs index ce6a7c46e4..c4a160efeb 100644 --- a/common/systems/src/mount.rs +++ b/common/systems/src/mount.rs @@ -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,9 @@ 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) { + character_activity.steer_dir = Some(inputs.move_dir.y); + } match is_volume_rider.pos.kind { common::mounting::Volume::Entity(uid) => { if let Some(controller) = diff --git a/voxygen/anim/src/character/mod.rs b/voxygen/anim/src/character/mod.rs index 0c14bfc1a2..945d253008 100644 --- a/voxygen/anim/src/character/mod.rs +++ b/voxygen/anim/src/character/mod.rs @@ -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::{ diff --git a/voxygen/anim/src/character/sleep.rs b/voxygen/anim/src/character/sleep.rs new file mode 100644 index 0000000000..abeaa6ba96 --- /dev/null +++ b/voxygen/anim/src/character/sleep.rs @@ -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, Option, 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 + } +} diff --git a/voxygen/anim/src/character/steer.rs b/voxygen/anim/src/character/steer.rs new file mode 100644 index 0000000000..32bba0fe3e --- /dev/null +++ b/voxygen/anim/src/character/steer.rs @@ -0,0 +1,110 @@ +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, Option, 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_l.orientation = hand_rotation; + + next.hand_r.position = helm_center + hand_offset; + next.hand_r.orientation = -hand_rotation; + + 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.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + + next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + + next.glider.position = Vec3::new(0.0, 0.0, 10.0); + next.glider.scale = Vec3::one() * 0.0; + next.hold.position = Vec3::new(0.4, -0.3, -5.8); + + 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 + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index d9f6a37e17..ebe20dbb65 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2155,6 +2155,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())] diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 84f2d9d753..4ac0c3afb8 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -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 + .and_then(|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, From ecb85a05342338e82d82ba9a2a83802746a079be Mon Sep 17 00:00:00 2001 From: Isse Date: Sat, 14 Oct 2023 20:28:20 +0200 Subject: [PATCH 03/26] Tavern room layout --- rtsim/src/gen/mod.rs | 1 + world/src/civ/mod.rs | 1 + world/src/site2/mod.rs | 72 ++++- world/src/site2/plot.rs | 4 +- world/src/site2/plot/tavern.rs | 535 +++++++++++++++++++++++++++++++++ world/src/site2/util/mod.rs | 2 +- 6 files changed, 605 insertions(+), 10 deletions(-) create mode 100644 world/src/site2/plot/tavern.rs diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index 67213bdaca..d2e0a5b355 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -113,6 +113,7 @@ impl Data { PlotKind::House(_) | PlotKind::Workshop(_) | PlotKind::AirshipDock(_) + | PlotKind::Tavern(_) | PlotKind::Plaza | PlotKind::SavannahPit(_) | PlotKind::SavannahHut(_) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 4f4609eaaa..89424214d1 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -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, diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index a9cfbd08ac..4b62ad7a70 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -573,7 +573,13 @@ impl Site { } // Size is 0..1 - pub fn generate_city(land: &Land, rng: &mut impl Rng, origin: Vec2, size: f32) -> Self { + pub fn generate_city( + land: &Land, + index: IndexRef, + rng: &mut impl Rng, + origin: Vec2, + size: f32, + ) -> Self { let mut rng = reseed(rng); let mut site = Site { @@ -587,12 +593,13 @@ impl Site { site.make_plaza(land, &mut rng); let build_chance = Lottery::from(vec![ - (64.0, 1), - (5.0, 2), - (8.0, 3), - (5.0, 4), - (5.0, 5), - (15.0, 6), + // (64.0, 1), + // (5.0, 2), + // (8.0, 3), + // (5.0, 4), + // (5.0, 5), + // (15.0, 6), + (15.0, 7), ]); let mut castles = 0; @@ -917,6 +924,42 @@ impl Site { } } }, + 7 if size > 0.125 => { + let size = (3.5 + rng.gen::().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_vector(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), + }); + workshops += 1; + } else { + site.make_plaza(land, &mut rng); + } + }, _ => {}, } } @@ -1824,6 +1867,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 +2007,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) -> Option { diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 15693441fc..2aeb1d783f 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -22,6 +22,7 @@ mod savannah_hut; mod savannah_pit; mod savannah_workshop; mod sea_chapel; +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), diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs new file mode 100644 index 0000000000..821fe4c213 --- /dev/null +++ b/world/src/site2/plot/tavern.rs @@ -0,0 +1,535 @@ +use std::{mem::swap, ops::RangeInclusive}; + +use common::{ + lottery::Lottery, + store::{Id, Store}, + terrain::{Block, BlockKind}, +}; +use enum_map::EnumMap; +use fxhash::hash; +use rand::Rng; +use strum::{EnumIter, IntoEnumIterator}; +use vek::*; + +use crate::{ + site2::{Dir, Fill, Site, Structure}, + IndexRef, Land, +}; + +type Neighbor = Option>; + +struct Wall { + start: Vec2, + end: Vec2, + base_alt: i32, + top_alt: i32, + from: Neighbor, + to: Neighbor, + out_dir: Dir, + door: Option, +} + +#[derive(Clone, Copy, EnumIter, enum_map::Enum)] +enum RoomKind { + Garden, + StageRoom, + BarRoom, + EntranceRoom, +} + +impl RoomKind { + /// Returns the (side length size range, area size range) + fn size_range(&self) -> (RangeInclusive, RangeInclusive) { + match self { + RoomKind::Garden => (4..=20, 25..=250), + RoomKind::StageRoom => (10..=20, 130..=400), + RoomKind::BarRoom => (7..=14, 56..=196), + RoomKind::EntranceRoom => (3..=10, 9..=50), + } + } +} + +struct Room { + /// Inclusive + bounds: Aabb, + kind: RoomKind, + // stairs: Option>, + // walls: Vec, +} + +struct Stairs { + end: Vec2, + dir: Dir, + in_room: Id, + to_room: Id, +} + +pub struct Tavern { + rooms: Store, + stairs: Store, + walls: Store, + /// Tile position of the door tile + pub door_tile: Vec2, + pub(crate) door_wpos: Vec3, + /// Axis aligned bounding region for the house + bounds: Aabr, +} + +impl Tavern { + pub fn generate( + land: &Land, + index: IndexRef, + rng: &mut impl Rng, + site: &Site, + door_tile: Vec2, + door_dir: Dir, + tile_aabr: Aabr, + ) -> Self { + let mut rooms = Store::default(); + let stairs = Store::default(); + let mut walls = Store::default(); + let mut room_counts = EnumMap::::default(); + + let bounds = Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }; + + let ibounds = Aabr { + min: bounds.min + 1, + max: bounds.max - 2, + }; + + let door_tile_center = site.tile_center_wpos(door_tile); + let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center); + + let door_alt = land + .column_sample(door_wpos, index) + .map_or_else(|| land.get_alt_approx(door_wpos), |sample| sample.alt); + let door_wpos = door_wpos.with_z(door_alt.ceil() as i32); + + /// Place room in bounds. + fn place_room_in( + room: RoomKind, + max_bounds: Aabr, + in_dir: Dir, + in_pos: Vec2, + rng: &mut impl Rng, + ) -> Option> { + let (size_range, area_range) = room.size_range(); + + let mut gen_range = |min, max, snap_max| { + let res = rng.gen_range(min..=max); + if snap_max <= max && snap_max - res <= 2 { + snap_max + } else { + res + } + }; + let min = *size_range.start(); + let snap_max = in_dir.select(max_bounds.size()); + let max = snap_max.min(*size_range.end()); + if max < min { + return None; + } + let size_x = gen_range(min, max, snap_max); + + let min = ((*area_range.start() + size_x - 1) / size_x).max(*size_range.start()); + let snap_max = in_dir.orthogonal().select(max_bounds.size()); + let max = snap_max + .min(*size_range.end()) + .min(*area_range.end() / size_x); + + if max < min { + return None; + } + let size_y = gen_range(min, max, snap_max); + + // calculate a valid aabr + let half_size_y = size_y / 2 + (size_y % 2) * rng.gen_range(0..=1); + let min = in_pos + in_dir.to_vec2() + in_dir.rotated_cw().to_vec2() * half_size_y; + let min = max_bounds.projected_point(min); + let max = min + in_dir.to_vec2() * size_x + in_dir.rotated_ccw().to_vec2() * size_y; + let max = max_bounds.projected_point(max); + let min = max - in_dir.to_vec2() * size_x + in_dir.rotated_cw().to_vec2() * size_y; + + let bounds = Aabr { min, max }.made_valid(); + Some(bounds) + } + struct RoomMeta { + id: Id, + walls: Vec, + } + + let mut room_metas = Vec::new(); + + { + let entrance_rooms = + Lottery::from(vec![(1.0, RoomKind::Garden), (2.0, RoomKind::EntranceRoom)]); + + let entrance_room = *entrance_rooms.choose_seeded(rng.gen()); + let entrance_room_hgt = rng.gen_range(3..=4); + let entrance_room_aabr = + place_room_in(entrance_room, ibounds, -door_dir, door_wpos.xy(), rng) + .expect("Not enough room in plot for a tavern"); + let entrance_room_aabb = Aabb { + min: entrance_room_aabr.min.with_z(door_wpos.z), + max: entrance_room_aabr + .max + .with_z(door_wpos.z + entrance_room_hgt), + } + .made_valid(); + + let entrance_id = rooms.insert(Room { + bounds: entrance_room_aabb, + kind: entrance_room, + }); + + let start = door_dir.select_aabr_with( + entrance_room_aabr, + Vec2::broadcast(door_dir.rotated_cw().select_aabr(entrance_room_aabr)), + ) + door_dir.rotated_cw().to_vec2() + + door_dir.to_vec2(); + walls.insert(Wall { + start, + end: door_dir.select_aabr_with( + entrance_room_aabr, + Vec2::broadcast(door_dir.rotated_ccw().select_aabr(entrance_room_aabr)), + ) + door_dir.rotated_ccw().to_vec2() + + door_dir.to_vec2(), + base_alt: entrance_room_aabb.min.z, + top_alt: entrance_room_aabb.max.z, + from: None, + to: Some(entrance_id), + out_dir: -door_dir, + door: Some(door_dir.rotated_cw().select(door_wpos.xy() - start).abs()), + }); + + room_metas.push(RoomMeta { + id: entrance_id, + walls: Dir::iter() + .filter(|d| *d != door_dir) + // .map(|d| { + // let a = d.rotated_cw().select_aabr(entrance_room_aabr); + // let b = d.rotated_ccw().select_aabr(entrance_room_aabr); + // (d, a.min(b)..=a.max(b)) + // }) + .collect(), + }); + + room_counts[entrance_room] += 1; + } + + let to_aabr = |aabb: Aabb| Aabr { + min: aabb.min.xy(), + max: aabb.max.xy(), + }; + 'room_gen: while room_metas.len() > 0 { + let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len())); + if room_meta.walls.is_empty() { + continue 'room_gen; + } + let in_dir = room_meta + .walls + .swap_remove(rng.gen_range(0..room_meta.walls.len())); + + let right = in_dir.orthogonal(); + let left = -right; + + let from_id = room_meta.id; + let from_room = &rooms[from_id]; + + if !room_meta.walls.is_empty() { + room_metas.push(room_meta); + } + + let from_bounds = to_aabr(from_room.bounds); + + // The maximum bounds, limited by the plot bounds and other rooms. + let mut max_bounds = Aabr { + min: in_dir.select_aabr_with(from_bounds, ibounds.min) + in_dir.to_vec2() * 2, + max: in_dir.select_aabr_with(ibounds, ibounds.max), + } + .made_valid(); + + // Take other rooms into account when calculating `max_bounds`. We don't care + // about this room if it's the originating room or at another + // height. + for (_, room) in rooms.iter().filter(|(room_id, room)| { + *room_id != from_id + && room.bounds.min.z <= from_room.bounds.max.z + && room.bounds.max.z >= from_room.bounds.min.z + }) { + let mut bounds = to_aabr(room.bounds); + bounds.min -= 2; + bounds.max += 2; + let intersection = bounds.intersection(max_bounds); + if intersection.is_valid() { + let Some(bounds) = Dir::iter() + .filter(|dir| { + *dir != in_dir + && dir.select_aabr(intersection) * dir.signum() + < dir.select_aabr(max_bounds) * dir.signum() + }) + .map(|min_dir| { + Aabr { + min: min_dir.select_aabr_with( + max_bounds, + Vec2::broadcast(min_dir.rotated_ccw().select_aabr(max_bounds)), + ), + max: min_dir.select_aabr_with( + intersection, + Vec2::broadcast(min_dir.rotated_cw().select_aabr(max_bounds)), + ), + } + .made_valid() + }) + .filter(|bounds| { + left.select_aabr(*bounds) < right.select_aabr(from_bounds) + && right.select_aabr(*bounds) > left.select_aabr(from_bounds) + }) + .max_by_key(|bounds| bounds.size().product()) + else { + continue 'room_gen; + }; + + max_bounds = bounds; + } + } + + // the smallest side on the maximum bounds + let max_min_size = max_bounds.size().reduce_min(); + // max bounds area + let max_area = max_bounds.size().product(); + + let room_lottery = RoomKind::iter() + // Filter out rooms that won't fit here. + .filter(|room_kind| { + let (size_range, area_range) = room_kind.size_range(); + *size_range.start() <= max_min_size && *area_range.start() <= max_area + }) + // Calculate chance for each room. + .map(|room_kind| { + ( + match room_kind { + RoomKind::Garden => { + 1.0 / (1.0 + room_counts[RoomKind::Garden] as f32 / 2.0) + }, + RoomKind::StageRoom => { + 2.0 / (1.0 + room_counts[RoomKind::StageRoom] as f32).powi(2) + }, + RoomKind::BarRoom => { + 2.0 / (1.0 + room_counts[RoomKind::BarRoom] as f32).powi(2) + }, + RoomKind::EntranceRoom => { + 0.1 / (1.0 + room_counts[RoomKind::EntranceRoom] as f32) + }, + }, + room_kind, + ) + }) + .collect::>(); + // We have no rooms to pick from. + if room_lottery.is_empty() { + continue 'room_gen; + } + + // Pick a room. + let room_lottery = Lottery::from(room_lottery); + let room = *room_lottery.choose_seeded(rng.gen()); + + // Select a door position + let mut min = left + .select_aabr(from_bounds) + .max(left.select_aabr(max_bounds)); + let mut max = right + .select_aabr(from_bounds) + .min(right.select_aabr(max_bounds)); + if max < min { + swap(&mut min, &mut max); + } + if min + 2 > max { + continue 'room_gen; + } + let in_pos = rng.gen_range(min + 1..=max - 1); + let in_pos = + in_dir.select_aabr_with(from_bounds, Vec2::broadcast(in_pos)) + in_dir.to_vec2(); + + let Some(bounds) = place_room_in(room, max_bounds, in_dir, in_pos, rng) else { + continue 'room_gen; + }; + + let room_hgt = rng.gen_range(3..=5); + + let bounds3 = Aabb { + min: bounds.min.with_z(from_room.bounds.min.z), + max: bounds.max.with_z(from_room.bounds.min.z + room_hgt), + }; + let id = rooms.insert(Room { + bounds: bounds3, + kind: room, + }); + + let start = in_dir.select_aabr_with( + from_bounds, + Vec2::broadcast(left.select_aabr(from_bounds).max(left.select_aabr(bounds))), + ) + in_dir.to_vec2() + + left.to_vec2(); + + let end = in_dir.select_aabr_with( + from_bounds, + Vec2::broadcast( + right + .select_aabr(from_bounds) + .min(right.select_aabr(bounds)), + ), + ) + in_dir.to_vec2() + + right.to_vec2(); + + walls.insert(Wall { + start, + end, + base_alt: bounds3.min.z, + top_alt: bounds3.max.z, + from: Some(from_id), + to: Some(id), + out_dir: in_dir, + door: Some(right.select(in_pos - start)), + }); + + room_metas.push(RoomMeta { + id, + walls: Dir::iter().filter(|d| *d != -in_dir).collect(), + }); + } + + Self { + rooms, + stairs, + walls, + door_tile, + door_wpos, + bounds, + } + } +} + +fn aabb(mut aabb: Aabb) -> Aabb { + aabb.make_valid(); + aabb.max += 1; + aabb +} + +impl Structure for Tavern { + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"render_tavern\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "render_tavern")] + fn render_inner(&self, _site: &Site, _land: &Land, painter: &crate::site2::Painter) { + let bounds = Aabr { + min: self.bounds.min, + max: self.bounds.max - 1, + }; + + let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 70, 70), 10); + let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(106, 73, 64))); + + painter + .aabb(aabb(Aabb { + min: bounds.min.with_z(self.door_wpos.z - 10), + max: bounds.max.with_z(self.door_wpos.z - 1), + })) + .fill(stone.clone()); + + for (id, room) in self.rooms.iter() { + painter.aabb(aabb(room.bounds)).clear(); + let hash = hash(&id).to_le_bytes(); + painter + .aabb(aabb(Aabb { + min: room.bounds.min.with_z(room.bounds.min.z - 1), + max: room.bounds.max.with_z(room.bounds.min.z - 1), + })) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(hash[0], hash[1], hash[2]), + ))); + } + + for (_, wall) in self.walls.iter() { + let get_kind = |room| self.rooms.get(room).kind; + let wall_aabb = Aabb { + min: wall.start.with_z(wall.base_alt), + max: wall.end.with_z(wall.top_alt), + }; + match (wall.from.map(get_kind), wall.to.map(get_kind)) { + (Some(RoomKind::Garden), Some(RoomKind::Garden) | None) + | (None, Some(RoomKind::Garden)) => { + let hgt = wall_aabb.min.z..=wall_aabb.max.z; + painter + .column(wall_aabb.min.xy(), hgt.clone()) + .fill(wood.clone()); + painter.column(wall_aabb.max.xy(), hgt).fill(wood.clone()); + painter + .aabb(aabb(Aabb { + min: wall_aabb.min, + max: wall_aabb.max.with_z(wall_aabb.min.z), + })) + .fill(wood.clone()); + }, + (None, None) => {}, + _ => { + painter.aabb(aabb(wall_aabb)).fill(wood.clone()); + }, + } + let wall_dir = Dir::from_vector(wall.end - wall.start); + if let Some(door) = wall.door { + let door_pos = wall.start + wall_dir.to_vec2() * door; + let min = match wall.from { + None => door_pos - wall.out_dir.to_vec2(), + Some(_) => door_pos, + }; + let max = match wall.to { + None => door_pos + wall.out_dir.to_vec2(), + Some(_) => door_pos, + }; + painter + .aabb(aabb(Aabb { + min: min.with_z(wall.base_alt), + max: max.with_z(wall.base_alt + 2), + })) + .clear(); + } + } + + for (_, stairs) in self.stairs.iter() { + let down_room = &self.rooms[stairs.in_room]; + let up_room = &self.rooms[stairs.to_room]; + + let down = -stairs.dir; + let right = stairs.dir.rotated_cw(); + + let aabr = Aabr { + min: stairs.end - right.to_vec2() + + down.to_vec2() * (up_room.bounds.min.z - 1 - down_room.bounds.min.z), + max: stairs.end + right.to_vec2(), + }; + + painter + .aabb(aabb(Aabb { + min: aabr.min.with_z(up_room.bounds.min.z - 1), + max: aabr.max.with_z(up_room.bounds.min.z - 1), + })) + .clear(); + + painter + .ramp( + aabb(Aabb { + min: aabr.min.with_z(down_room.bounds.min.z), + max: aabr.max.with_z(up_room.bounds.min.z - 1), + }), + stairs.dir, + ) + .fill(wood.clone()); + } + } +} diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index b43266c8d6..e0d7b5c436 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -6,7 +6,7 @@ use rand::Rng; use vek::*; /// A 2d direction. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, enum_map::Enum, strum::EnumIter)] pub enum Dir { X, Y, From eb2395a401f6cb13b4ee21efb29070a70ea08960 Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 15 Oct 2023 16:33:11 +0200 Subject: [PATCH 04/26] walk fixes --- common/src/comp/ability.rs | 1 - voxygen/src/key_state.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index a6669cad24..94729755a2 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -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, .. } diff --git a/voxygen/src/key_state.rs b/voxygen/src/key_state.rs index 281fa9ff00..9d517e5131 100644 --- a/voxygen/src/key_state.rs +++ b/voxygen/src/key_state.rs @@ -44,6 +44,8 @@ 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; From 9903b53105302cdddad04b37347019e55413f701 Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 15 Oct 2023 22:26:28 +0200 Subject: [PATCH 05/26] working rooms --- world/src/site2/plot/bridge.rs | 26 ++-- world/src/site2/plot/tavern.rs | 239 ++++++++++++++++++++++++++++++--- world/src/site2/util/mod.rs | 25 +++- 3 files changed, 259 insertions(+), 31 deletions(-) diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 44bda8eac1..64a0a94ae8 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -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, 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, 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), diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 821fe4c213..e59f3222c0 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -1,12 +1,12 @@ -use std::{mem::swap, ops::RangeInclusive}; +use std::{cmp::Ordering, mem::swap, ops::RangeInclusive}; use common::{ lottery::Lottery, store::{Id, Store}, - terrain::{Block, BlockKind}, + terrain::{Block, BlockKind, SpriteKind}, }; use enum_map::EnumMap; -use fxhash::hash; +use hashbrown::HashSet; use rand::Rng; use strum::{EnumIter, IntoEnumIterator}; use vek::*; @@ -25,7 +25,7 @@ struct Wall { top_alt: i32, from: Neighbor, to: Neighbor, - out_dir: Dir, + to_dir: Dir, door: Option, } @@ -54,7 +54,8 @@ struct Room { bounds: Aabb, kind: RoomKind, // stairs: Option>, - // walls: Vec, + walls: EnumMap>>, + detail_areas: Vec>, } struct Stairs { @@ -183,6 +184,8 @@ impl Tavern { let entrance_id = rooms.insert(Room { bounds: entrance_room_aabb, kind: entrance_room, + walls: EnumMap::default(), + detail_areas: Vec::new(), }); let start = door_dir.select_aabr_with( @@ -190,7 +193,7 @@ impl Tavern { Vec2::broadcast(door_dir.rotated_cw().select_aabr(entrance_room_aabr)), ) + door_dir.rotated_cw().to_vec2() + door_dir.to_vec2(); - walls.insert(Wall { + let wall_id = walls.insert(Wall { start, end: door_dir.select_aabr_with( entrance_room_aabr, @@ -201,9 +204,10 @@ impl Tavern { top_alt: entrance_room_aabb.max.z, from: None, to: Some(entrance_id), - out_dir: -door_dir, + to_dir: door_dir, door: Some(door_dir.rotated_cw().select(door_wpos.xy() - start).abs()), }); + rooms[entrance_id].walls[door_dir].push(wall_id); room_metas.push(RoomMeta { id: entrance_id, @@ -224,6 +228,11 @@ impl Tavern { min: aabb.min.xy(), max: aabb.max.xy(), }; + // Extend a valid aabr + let extend_aabr = |aabr: Aabr, amount: i32| Aabr { + min: aabr.min - amount, + max: aabr.max + amount, + }; 'room_gen: while room_metas.len() > 0 { let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len())); if room_meta.walls.is_empty() { @@ -260,9 +269,8 @@ impl Tavern { && room.bounds.min.z <= from_room.bounds.max.z && room.bounds.max.z >= from_room.bounds.min.z }) { - let mut bounds = to_aabr(room.bounds); - bounds.min -= 2; - bounds.max += 2; + let bounds = to_aabr(room.bounds); + let bounds = extend_aabr(bounds, 2); let intersection = bounds.intersection(max_bounds); if intersection.is_valid() { let Some(bounds) = Dir::iter() @@ -368,6 +376,8 @@ impl Tavern { let id = rooms.insert(Room { bounds: bounds3, kind: room, + walls: EnumMap::default(), + detail_areas: Vec::new(), }); let start = in_dir.select_aabr_with( @@ -386,21 +396,198 @@ impl Tavern { ) + in_dir.to_vec2() + right.to_vec2(); - walls.insert(Wall { + let wall_id = walls.insert(Wall { start, end, base_alt: bounds3.min.z, top_alt: bounds3.max.z, from: Some(from_id), to: Some(id), - out_dir: in_dir, + to_dir: in_dir, door: Some(right.select(in_pos - start)), }); + rooms[id].walls[-in_dir].push(wall_id); + rooms[from_id].walls[in_dir].push(wall_id); + room_metas.push(RoomMeta { id, walls: Dir::iter().filter(|d| *d != -in_dir).collect(), }); + room_counts[room] += 1; + } + + // Place walls where needed. + for from_id in rooms.ids() { + let room_bounds = to_aabr(rooms[from_id].bounds); + let mut skip = HashSet::new(); + skip.insert(from_id); + let mut wall_ranges = EnumMap::>::default(); + for dir in Dir::iter() { + let orth = dir.orthogonal(); + let range = (orth.select(room_bounds.min), orth.select(room_bounds.max)); + wall_ranges[dir].push(range); + } + let mut split_range = |dir: Dir, min: i32, max: i32| { + debug_assert!(min <= max); + let Ok(i) = wall_ranges[dir].binary_search_by(|(r_min, r_max)| { + match (min.cmp(r_min), min.cmp(r_max)) { + (Ordering::Less, _) => Ordering::Greater, + (Ordering::Greater | Ordering::Equal, Ordering::Less | Ordering::Equal) => { + Ordering::Equal + }, + (_, Ordering::Greater) => Ordering::Less, + } + }) else { + // TODO: Don't panic here. + dbg!((min, max)); + dbg!(&wall_ranges[dir]); + panic!("Couldn't find range"); + }; + + let range = &mut wall_ranges[dir][i]; + debug_assert!(range.0 <= min); + debug_assert!(range.1 >= max); + + match (range.0 == min, range.1 == max) { + (true, true) => { + wall_ranges[dir].remove(i); + }, + (true, false) => *range = (max + 1, range.1), + (false, true) => *range = (range.0, min - 1), + (false, false) => { + let tmp = range.1; + *range = (range.0, min - 1); + debug_assert!(range.0 <= range.1); + let m = (max + 1, tmp); + debug_assert!(m.0 <= m.1, "{m:?}"); + wall_ranges[dir].insert(i + 1, m); + }, + } + }; + for dir in Dir::iter() { + let connected_walls = &mut rooms[from_id].walls[dir]; + skip.extend( + connected_walls + .iter() + .flat_map(|wall| walls[*wall].from.into_iter().chain(walls[*wall].to)), + ); + let orth = dir.orthogonal(); + // Divide wall ranges by existing walls. + for wall in connected_walls.iter() { + let wall = &walls[*wall]; + let mut min = orth.select(wall.start); + let mut max = orth.select(wall.end); + if min > max { + swap(&mut min, &mut max); + } + min += 1; + max -= 1; + split_range(dir, min, max); + } + } + + // Divide wall ranges by neighbouring rooms + for to_id in rooms.ids().filter(|id| !skip.contains(id)) { + let a_min_z = rooms[from_id].bounds.min.z; + let a_max_z = rooms[from_id].bounds.max.z; + let b_min_z = rooms[to_id].bounds.min.z; + let b_max_z = rooms[to_id].bounds.max.z; + if a_min_z > b_max_z || a_max_z < b_min_z { + // We are not at the same altitude. + continue; + } + let min_z = a_min_z.min(b_min_z); + let max_z = a_max_z.max(b_max_z); + let n_room_bounds = to_aabr(rooms[to_id].bounds); + + let p1 = n_room_bounds.projected_point(room_bounds.center()); + let p0 = room_bounds.projected_point(p1); + + let to_dir = Dir::from_vector(p1 - p0); + + let intersection = to_dir + .extend_aabr(room_bounds, 1) + .intersection(to_dir.opposite().extend_aabr(n_room_bounds, 1)); + + if intersection.is_valid() { + let start = intersection.min; + let end = intersection.max; + + let orth = to_dir.orthogonal(); + + let min = orth.select(start); + let max = orth.select(end); + split_range(to_dir, min, max); + let door = if max - min >= 3 && rng.gen_bool(0.8) { + Some(rng.gen_range(1..=max - min)) + } else { + None + }; + + let id = walls.insert(Wall { + start: start - orth.to_vec2(), + end: end + orth.to_vec2(), + base_alt: min_z, + top_alt: max_z, + from: Some(from_id), + to: Some(to_id), + to_dir, + door, + }); + + rooms[from_id].walls[to_dir].push(id); + rooms[to_id].walls[-to_dir].push(id); + } + } + // Place remaining walls. + for (dir, ranges) in wall_ranges { + for (min, max) in ranges { + let start = + dir.select_aabr_with(room_bounds, Vec2::broadcast(min - 1)) + dir.to_vec2(); + let end = + dir.select_aabr_with(room_bounds, Vec2::broadcast(max + 1)) + dir.to_vec2(); + + let wall_id = walls.insert(Wall { + start, + end, + base_alt: rooms[from_id].bounds.min.z, + top_alt: rooms[from_id].bounds.max.z, + from: Some(from_id), + to: None, + to_dir: dir, + door: None, + }); + + rooms[from_id].walls[dir].push(wall_id); + } + } + } + + // Compute detail areas + for room in rooms.values_mut() { + room.detail_areas.push(to_aabr(room.bounds)); + for (dir, dir_walls) in room.walls.iter() { + for door_pos in dir_walls.iter().filter_map(|wall_id| { + let wall = &walls[*wall_id]; + + wall.door.map(|door| { + let wall_dir = Dir::from_vector(wall.end - wall.start); + wall.start + wall_dir.to_vec2() * door + }) + }) { + let orth = dir.orthogonal(); + for i in 0..room.detail_areas.len() { + if let Some([a, b]) = + orth.try_split_aabr(room.detail_areas[i], orth.select(door_pos)) + { + room.detail_areas[i] = a; + room.detail_areas.push(b); + } + } + } + } + room.detail_areas.retain(|area| area.size().product() >= 4); } Self { @@ -441,18 +628,30 @@ impl Structure for Tavern { })) .fill(stone.clone()); - for (id, room) in self.rooms.iter() { + for (_, room) in self.rooms.iter() { painter.aabb(aabb(room.bounds)).clear(); - let hash = hash(&id).to_le_bytes(); painter .aabb(aabb(Aabb { min: room.bounds.min.with_z(room.bounds.min.z - 1), max: room.bounds.max.with_z(room.bounds.min.z - 1), })) - .fill(Fill::Block(Block::new( - BlockKind::Wood, - Rgb::new(hash[0], hash[1], hash[2]), - ))); + .fill(wood.clone()); + + match room.kind { + RoomKind::Garden => { + for aabr in room.detail_areas.iter() { + painter + .aabb(aabb(Aabb { + min: aabr.min.with_z(room.bounds.min.z), + max: aabr.max.with_z(room.bounds.min.z), + })) + .fill(Fill::Sprite(SpriteKind::Apple)) + } + }, + RoomKind::StageRoom => {}, + RoomKind::BarRoom => {}, + RoomKind::EntranceRoom => {}, + } } for (_, wall) in self.walls.iter() { @@ -485,11 +684,11 @@ impl Structure for Tavern { if let Some(door) = wall.door { let door_pos = wall.start + wall_dir.to_vec2() * door; let min = match wall.from { - None => door_pos - wall.out_dir.to_vec2(), + None => door_pos - wall.to_dir.to_vec2(), Some(_) => door_pos, }; let max = match wall.to { - None => door_pos + wall.out_dir.to_vec2(), + None => door_pos + wall.to_dir.to_vec2(), Some(_) => door_pos, }; painter diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index e0d7b5c436..7d0b035072 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -223,7 +223,7 @@ impl Dir { } } - pub fn split_aabr(self, aabr: Aabr, offset: T) -> [Aabr; 2] + pub fn split_aabr_offset(self, aabr: Aabr, offset: T) -> [Aabr; 2] where T: Copy + PartialOrd + Add + Sub, { @@ -241,6 +241,29 @@ impl Dir { } } + /// Try to split an aabr in a certain direction + pub fn try_split_aabr(self, aabr: Aabr, sp: T) -> Option<[Aabr; 2]> + where + T: Copy + PartialOrd + Add + Sub, + { + 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, offset: i32) -> Aabr { Aabr { min: aabr.min + self.abs().to_vec2() * offset, From 4fa52db71d3033da582188a4738739d8a6e24ff2 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 16 Oct 2023 01:51:30 +0200 Subject: [PATCH 06/26] more work on tavern --- voxygen/src/scene/terrain/watcher.rs | 4 +- world/src/site/namegen.rs | 159 ++++++++++++ world/src/site2/mod.rs | 2 +- world/src/site2/plot/adlet.rs | 8 +- world/src/site2/plot/bridge.rs | 4 +- world/src/site2/plot/gnarling.rs | 4 +- world/src/site2/plot/tavern.rs | 369 +++++++++++++++++++++++++-- world/src/site2/util/mod.rs | 11 +- 8 files changed, 530 insertions(+), 31 deletions(-) diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index cfb9a8e338..0109c06715 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -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)), _ => {}, }, diff --git a/world/src/site/namegen.rs b/world/src/site/namegen.rs index 7ffde99f91..5ea1c7605f 100644 --- a/world/src/site/namegen.rs +++ b/world/src/site/namegen.rs @@ -689,4 +689,163 @@ 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", + "Emberrassed", + "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", + ]; + 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) + ), + } + } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 4b62ad7a70..9746cce08c 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -939,7 +939,7 @@ impl Site { &mut reseed(&mut rng), &site, door_tile, - Dir::from_vector(door_dir), + Dir::from_vec2(door_dir), aabr, ); let tavern_alt = tavern.door_wpos.z; diff --git a/world/src/site2/plot/adlet.rs b/world/src/site2/plot/adlet.rs index c333b6a756..4478520af8 100644 --- a/world/src/site2/plot/adlet.rs +++ b/world/src/site2/plot/adlet.rs @@ -129,7 +129,7 @@ impl AdletStronghold { let mut outer_structures = Vec::<(AdletStructure, Vec2, 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 = match dir { Dir::X => Vec2::new(self.entrance.x + 7, self.entrance.y), Dir::Y => Vec2::new(self.entrance.x, self.entrance.y + 7), diff --git a/world/src/site2/plot/bridge.rs b/world/src/site2/plot/bridge.rs index 64a0a94ae8..c4d0575288 100644 --- a/world/src/site2/plot/bridge.rs +++ b/world/src/site2/plot/bridge.rs @@ -884,7 +884,7 @@ impl Bridge { let min_water_dist = 5; let find_edge = |start: Vec2, end: Vec2| { 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 { @@ -938,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()) diff --git a/world/src/site2/plot/gnarling.rs b/world/src/site2/plot/gnarling.rs index ce25e7ad2a..5e4fff446b 100644 --- a/world/src/site2/plot/gnarling.rs +++ b/world/src/site2/plot/gnarling.rs @@ -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)), diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index e59f3222c0..bf638b385e 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -1,9 +1,10 @@ use std::{cmp::Ordering, mem::swap, ops::RangeInclusive}; use common::{ + comp::Content, lottery::Lottery, store::{Id, Store}, - terrain::{Block, BlockKind, SpriteKind}, + terrain::{Block, BlockKind, SpriteCfg, SpriteKind}, }; use enum_map::EnumMap; use hashbrown::HashSet; @@ -12,7 +13,9 @@ use strum::{EnumIter, IntoEnumIterator}; use vek::*; use crate::{ - site2::{Dir, Fill, Site, Structure}, + site::namegen, + site2::{gen::PrimitiveTransform, Dir, Fill, Site, Structure}, + util::RandomField, IndexRef, Land, }; @@ -66,6 +69,7 @@ struct Stairs { } pub struct Tavern { + name: String, rooms: Store, stairs: Store, walls: Store, @@ -204,7 +208,7 @@ impl Tavern { top_alt: entrance_room_aabb.max.z, from: None, to: Some(entrance_id), - to_dir: door_dir, + to_dir: -door_dir, door: Some(door_dir.rotated_cw().select(door_wpos.xy() - start).abs()), }); rooms[entrance_id].walls[door_dir].push(wall_id); @@ -504,7 +508,7 @@ impl Tavern { let p1 = n_room_bounds.projected_point(room_bounds.center()); let p0 = room_bounds.projected_point(p1); - let to_dir = Dir::from_vector(p1 - p0); + let to_dir = Dir::from_vec2(p1 - p0); let intersection = to_dir .extend_aabr(room_bounds, 1) @@ -566,31 +570,46 @@ impl Tavern { // Compute detail areas for room in rooms.values_mut() { - room.detail_areas.push(to_aabr(room.bounds)); + let bounds = to_aabr(room.bounds); + let c = bounds.center(); + let mut b = bounds.split_at_x(c.x); + b[0].max.x -= 1; + room.detail_areas.extend(b.into_iter().flat_map(|b| { + let mut b = b.split_at_y(c.y); + b[0].max.y -= 1; + b + })); for (dir, dir_walls) in room.walls.iter() { for door_pos in dir_walls.iter().filter_map(|wall_id| { let wall = &walls[*wall_id]; wall.door.map(|door| { - let wall_dir = Dir::from_vector(wall.end - wall.start); + let wall_dir = Dir::from_vec2(wall.end - wall.start); wall.start + wall_dir.to_vec2() * door }) }) { let orth = dir.orthogonal(); for i in 0..room.detail_areas.len() { - if let Some([a, b]) = - orth.try_split_aabr(room.detail_areas[i], orth.select(door_pos)) - { - room.detail_areas[i] = a; - room.detail_areas.push(b); + let bc = room.detail_areas[i].center(); + // Check if we are on the doors side of the center of the room. + if dir.select(bc - c) * dir.signum() >= 0 { + if let Some([a, b]) = + orth.try_split_aabr(room.detail_areas[i], orth.select(door_pos)) + { + room.detail_areas[i] = orth.extend_aabr(a, -1); + room.detail_areas.push(orth.opposite().extend_aabr(b, -1)); + } } } + room.detail_areas.retain(|area| area.is_valid()); } } - room.detail_areas.retain(|area| area.size().product() >= 4); } + let name = namegen::NameGen::location(rng).generate_tavern(); + Self { + name, rooms, stairs, walls, @@ -620,7 +639,9 @@ impl Structure for Tavern { let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 70, 70), 10); let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(106, 73, 64))); + let dark_wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(80, 53, 48))); + let field = RandomField::new(740384); painter .aabb(aabb(Aabb { min: bounds.min.with_z(self.door_wpos.z - 10), @@ -637,20 +658,244 @@ impl Structure for Tavern { })) .fill(wood.clone()); + let room_aabr = Aabr { + min: room.bounds.min.xy(), + max: room.bounds.max.xy(), + }; + let table_set = |pos, bounds: Aabr| -> bool { + if bounds.size().reduce_min() >= 1 { + painter.sprite(pos, SpriteKind::TableDining); + + for dir in Dir::iter() { + let pos = pos + dir.to_vec2(); + if bounds.contains_point(pos.xy()) { + painter.rotated_sprite( + pos, + SpriteKind::ChairSingle, + dir.opposite().sprite_ori(), + ); + } + } + true + } else { + false + } + }; + for (i, aabr) in room.detail_areas.iter().enumerate() { + let color = fxhash::hash32(&i).to_le_bytes(); + + painter + .aabb(aabb(Aabb { + min: aabr.min.with_z(room.bounds.min.z - 1), + max: aabr.max.with_z(room.bounds.min.z - 1), + })) + .fill(Fill::Block(Block::new( + BlockKind::Rock, + Rgb::new(color[0], color[1], color[3]), + ))); + } match room.kind { RoomKind::Garden => { for aabr in room.detail_areas.iter() { + let pos = aabr.center().with_z(room.bounds.min.z); + + if field.chance(pos, 0.6) { + table_set(pos, *aabr); + } + } + + let dir = Dir::from_vec2(room_aabr.size().into()); + + painter + .aabb(aabb(Aabb { + min: dir + .select_aabr_with(room_aabr, room_aabr.min - 2) + .with_z(room.bounds.max.z + 1), + max: dir + .select_aabr_with(room_aabr, room_aabr.max + 2) + .with_z(room.bounds.max.z + 1), + })) + .repeat( + -dir.to_vec3() * 2, + (dir.select(room_aabr.size()) as u32 + 3) / 2, + ) + .fill(dark_wood.clone()) + }, + RoomKind::StageRoom => { + let mut stage = None; + let mut stage_score = 0; + for aabr in room.detail_areas.iter() { + let edges = Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + .count() as i32; + let test_stage_score = edges * aabr.size().product(); + let Some(aabr) = (if stage_score < test_stage_score { + stage_score = test_stage_score; + stage.replace(*aabr) + } else { + Some(*aabr) + }) else { + continue; + }; + + table_set(aabr.center().with_z(room.bounds.min.z), aabr); + for dir in Dir::iter().filter(|dir| { + dir.select_aabr(aabr) == dir.select_aabr(room_aabr) + && dir.rotated_cw().select_aabr(aabr) + == dir.rotated_cw().select_aabr(room_aabr) + }) { + let pos = dir.select_aabr_with( + aabr, + Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)), + ); + painter.sprite(pos.with_z(room.bounds.min.z), SpriteKind::StreetLamp); + } + } + if let Some(aabr) = stage { painter .aabb(aabb(Aabb { min: aabr.min.with_z(room.bounds.min.z), max: aabr.max.with_z(room.bounds.min.z), })) - .fill(Fill::Sprite(SpriteKind::Apple)) + .fill(stone.clone()); + painter + .aabb(aabb(Aabb { + min: (aabr.min + 1).with_z(room.bounds.min.z), + max: (aabr.max - 1).with_z(room.bounds.min.z), + })) + .fill(wood.clone()); + for dir in Dir::iter().filter(|dir| { + dir.select_aabr(aabr) != dir.select_aabr(room_aabr) + && dir.rotated_cw().select_aabr(aabr) + != dir.rotated_cw().select_aabr(room_aabr) + }) { + let pos = dir.select_aabr_with( + aabr, + Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)), + ); + painter + .column(pos, room.bounds.min.z..=room.bounds.max.z) + .fill(dark_wood.clone()); + + for dir in Dir::iter() { + painter.rotated_sprite( + pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(), + SpriteKind::WallSconce, + dir.sprite_ori(), + ); + } + } + } + }, + RoomKind::BarRoom => { + let mut bar = None; + let mut bar_score = 0; + for aabr in room.detail_areas.iter() { + let test_stage_score = Dir::iter() + .any(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + as i32 + * aabr.size().product(); + let Some(aabr) = (if bar_score < test_stage_score { + bar_score = test_stage_score; + bar.replace(*aabr) + } else { + Some(*aabr) + }) else { + continue; + }; + + for dir in Dir::iter() + .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr)) + { + let pos = dir + .select_aabr_with(aabr, aabr.center()) + .with_z(room.bounds.center().z); + + painter.rotated_sprite( + pos, + SpriteKind::WallLampSmall, + dir.opposite().sprite_ori(), + ); + } + } + if let Some(aabr) = bar { + for dir in Dir::iter() { + let edge = dir.select_aabr(aabr); + let rot_dir = if field.chance(aabr.center().with_z(0), 0.5) { + dir.rotated_cw() + } else { + dir.rotated_ccw() + }; + let rot_edge = rot_dir.select_aabr(aabr); + match ( + edge == dir.select_aabr(room_aabr), + rot_edge == rot_dir.select_aabr(room_aabr), + ) { + (false, _) => { + let (min, max) = ( + dir.select_aabr_with( + aabr, + Vec2::broadcast(rot_dir.select_aabr(aabr)), + ), + dir.select_aabr_with( + aabr, + Vec2::broadcast(rot_dir.opposite().select_aabr(aabr)), + ), + ); + painter + .aabb(aabb(Aabb { + min: (min - rot_dir.to_vec2()) + .with_z(room.bounds.min.z), + max: max.with_z(room.bounds.min.z), + })) + .fill(dark_wood.clone()); + painter + .aabb(aabb(Aabb { + min: min.with_z(room.bounds.min.z + 3), + max: max.with_z(room.bounds.max.z), + })) + .fill(dark_wood.clone()); + }, + (true, true) => { + painter.sprite( + dir.vec2(edge, rot_edge).with_z(room.bounds.min.z), + SpriteKind::CookingPot, + ); + }, + (true, false) => {}, + } + } + } + }, + RoomKind::EntranceRoom => { + for aabr in room.detail_areas.iter() { + let edges = Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + .count(); + let hanger_pos = if edges == 2 { + let pos = aabr.center().with_z(room.bounds.min.z); + painter.sprite(pos, SpriteKind::CoatRack); + Some(pos) + } else { + None + }; + + for dir in Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + { + let pos = dir + .select_aabr_with(*aabr, aabr.center()) + .with_z(room.bounds.center().z + 1); + if hanger_pos.map_or(false, |p| p.xy() != pos.xy()) { + painter.rotated_sprite( + pos, + SpriteKind::WallLampSmall, + dir.opposite().sprite_ori(), + ); + } + } } }, - RoomKind::StageRoom => {}, - RoomKind::BarRoom => {}, - RoomKind::EntranceRoom => {}, } } @@ -660,27 +905,53 @@ impl Structure for Tavern { min: wall.start.with_z(wall.base_alt), max: wall.end.with_z(wall.top_alt), }; + let wall_dir = Dir::from_vec2(wall.end - wall.start); match (wall.from.map(get_kind), wall.to.map(get_kind)) { (Some(RoomKind::Garden), Some(RoomKind::Garden) | None) | (None, Some(RoomKind::Garden)) => { let hgt = wall_aabb.min.z..=wall_aabb.max.z; painter .column(wall_aabb.min.xy(), hgt.clone()) - .fill(wood.clone()); - painter.column(wall_aabb.max.xy(), hgt).fill(wood.clone()); + .fill(dark_wood.clone()); + painter + .column(wall_aabb.max.xy(), hgt) + .fill(dark_wood.clone()); + let z = (wall.base_alt + wall.top_alt) / 2; + + painter.rotated_sprite( + wall_aabb.min.with_z(z) + wall_dir.to_vec2(), + SpriteKind::WallSconce, + wall_dir.sprite_ori(), + ); + painter.rotated_sprite( + wall_aabb.max.with_z(z) - wall_dir.to_vec2(), + SpriteKind::WallSconce, + wall_dir.opposite().sprite_ori(), + ); painter .aabb(aabb(Aabb { min: wall_aabb.min, max: wall_aabb.max.with_z(wall_aabb.min.z), })) - .fill(wood.clone()); + .fill(dark_wood.clone()); + painter + .aabb(aabb(Aabb { + min: wall_aabb.min.with_z(wall_aabb.max.z), + max: wall_aabb.max, + })) + .fill(dark_wood.clone()); }, (None, None) => {}, _ => { painter.aabb(aabb(wall_aabb)).fill(wood.clone()); + painter + .column(wall.start, wall.base_alt..=wall.top_alt) + .fill(dark_wood.clone()); + painter + .column(wall.end, wall.base_alt..=wall.top_alt) + .fill(dark_wood.clone()); }, } - let wall_dir = Dir::from_vector(wall.end - wall.start); if let Some(door) = wall.door { let door_pos = wall.start + wall_dir.to_vec2() * door; let min = match wall.from { @@ -698,6 +969,64 @@ impl Structure for Tavern { })) .clear(); } + if let (Some(room), to @ None) | (None, to @ Some(room)) = + (wall.from.map(get_kind), wall.to.map(get_kind)) + { + let dir = if to.is_none() { + -wall.to_dir + } else { + wall.to_dir + }; + let width = dir.orthogonal().select(wall.end - wall.start).abs(); + let wall_center = (wall.start + wall.end) / 2; + let d = wall_dir.select(wall_center - wall.start) * wall_dir.signum(); + match room { + RoomKind::Garden => { + if wall.door.map_or(true, |door| (door - d).abs() >= 2) { + painter.rotated_sprite( + wall_center.with_z(wall.base_alt + 1), + SpriteKind::Planter, + dir.sprite_ori(), + ); + } + }, + _ => { + if width >= 5 && wall.door.map_or(true, |door| (door - d).abs() > 3) { + painter + .aabb(aabb(Aabb { + min: (wall_center + dir.rotated_ccw().to_vec2()) + .with_z(wall.base_alt + 1), + max: (wall_center + dir.rotated_cw().to_vec2()) + .with_z(wall.base_alt + 2), + })) + .fill(Fill::RotatedSprite(SpriteKind::Window1, dir.sprite_ori())); + } + }, + } + if let Some(door) = wall.door { + let door_pos = wall.start + wall_dir.to_vec2() * door; + let diff = door_pos - wall_aabb.center().xy(); + let orth = if diff == Vec2::zero() { + wall_dir + } else { + Dir::from_vec2(diff) + }; + let pos = door_pos - dir.to_vec2() + orth.to_vec2(); + let (sprite, z) = match room { + RoomKind::Garden => (SpriteKind::Sign, 0), + _ => (SpriteKind::HangingSign, 2), + }; + painter.rotated_sprite_with_cfg( + pos.with_z(wall.base_alt + z), + sprite, + dir.opposite().sprite_ori(), + SpriteCfg { + unlock: None, + content: Some(Content::Plain(self.name.clone())), + }, + ); + } + } } for (_, stairs) in self.stairs.iter() { diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 7d0b035072..50e008093c 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -26,7 +26,7 @@ impl Dir { } } - pub fn from_vector(vec: Vec2) -> Dir { + pub fn from_vec2(vec: Vec2) -> 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,15 @@ impl Dir { } } + pub fn vec2(self, x: i32, y: i32) -> Vec2 { + 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. /// From c1aa9bd1b61f42fac0df6fad291abf54ff5593a3 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 16 Oct 2023 23:43:05 +0200 Subject: [PATCH 07/26] move some details to generate --- Cargo.lock | 5 +- world/Cargo.toml | 1 + world/src/site2/plot/tavern.rs | 331 +++++++++++++++++++-------------- world/src/site2/util/mod.rs | 2 +- 4 files changed, 197 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d89a3ddff2..5861bca5eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/world/Cargo.toml b/world/Cargo.toml index dba7579d06..353524e425 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -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 } diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index bf638b385e..f5d1d9f68c 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -7,8 +7,9 @@ use common::{ terrain::{Block, BlockKind, SpriteCfg, SpriteKind}, }; use enum_map::EnumMap; +use enumset::EnumSet; use hashbrown::HashSet; -use rand::Rng; +use rand::{seq::IteratorRandom, Rng}; use strum::{EnumIter, IntoEnumIterator}; use vek::*; @@ -52,13 +53,41 @@ impl RoomKind { } } +#[derive(Clone, Copy)] +enum Detail { + Bar { + aabr: Aabr, + }, + Table { + pos: Vec2, + chairs: EnumSet, + }, + Stage { + aabr: Aabr, + }, +} + struct Room { /// Inclusive bounds: Aabb, kind: RoomKind, // stairs: Option>, walls: EnumMap>>, + // TODO: Remove this, used for debugging detail_areas: Vec>, + details: Vec, +} + +impl Room { + fn new(bounds: Aabb, kind: RoomKind) -> Self { + Self { + bounds, + kind, + walls: Default::default(), + detail_areas: Default::default(), + details: Default::default(), + } + } } struct Stairs { @@ -90,6 +119,7 @@ impl Tavern { door_dir: Dir, tile_aabr: Aabr, ) -> Self { + let start = std::time::Instant::now(); let mut rooms = Store::default(); let stairs = Store::default(); let mut walls = Store::default(); @@ -163,7 +193,7 @@ impl Tavern { } struct RoomMeta { id: Id, - walls: Vec, + walls: EnumSet, } let mut room_metas = Vec::new(); @@ -185,12 +215,7 @@ impl Tavern { } .made_valid(); - let entrance_id = rooms.insert(Room { - bounds: entrance_room_aabb, - kind: entrance_room, - walls: EnumMap::default(), - detail_areas: Vec::new(), - }); + let entrance_id = rooms.insert(Room::new(entrance_room_aabb, entrance_room)); let start = door_dir.select_aabr_with( entrance_room_aabr, @@ -242,9 +267,11 @@ impl Tavern { if room_meta.walls.is_empty() { continue 'room_gen; } - let in_dir = room_meta - .walls - .swap_remove(rng.gen_range(0..room_meta.walls.len())); + + let Some(in_dir) = room_meta.walls.into_iter().choose(rng) else { + continue 'room_gen; + }; + room_meta.walls.remove(in_dir); let right = in_dir.orthogonal(); let left = -right; @@ -377,12 +404,7 @@ impl Tavern { min: bounds.min.with_z(from_room.bounds.min.z), max: bounds.max.with_z(from_room.bounds.min.z + room_hgt), }; - let id = rooms.insert(Room { - bounds: bounds3, - kind: room, - walls: EnumMap::default(), - detail_areas: Vec::new(), - }); + let id = rooms.insert(Room::new(bounds3, room)); let start = in_dir.select_aabr_with( from_bounds, @@ -606,8 +628,82 @@ impl Tavern { } } + // Place details in detail areas. + + for room in rooms.values_mut() { + let room_aabr = to_aabr(room.bounds); + let table = |pos: Vec2, aabr: Aabr| Detail::Table { + pos, + chairs: Dir::iter() + .filter(|dir| aabr.contains_point(pos + dir.to_vec2())) + .collect(), + }; + match room.kind { + RoomKind::Garden => room.detail_areas.retain(|&aabr| { + if aabr.size().reduce_max() > 1 && rng.gen_bool(0.7) { + room.details.push(table(aabr.center(), aabr)); + false + } else { + true + } + }), + RoomKind::StageRoom => { + let mut best = None; + let mut best_score = 0; + for (i, aabr) in room.detail_areas.iter().enumerate() { + let edges = Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + .count() as i32; + let test_score = edges * aabr.size().product(); + if best_score < test_score { + best_score = test_score; + best = Some(i); + } + } + if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) { + room.details.push(Detail::Stage { aabr }) + } + room.detail_areas.retain(|&aabr| { + if aabr.size().reduce_max() > 1 && rng.gen_bool(0.8) { + room.details.push(table(aabr.center(), aabr)); + false + } else { + true + } + }); + }, + RoomKind::BarRoom => { + let mut best = None; + let mut best_score = 0; + for (i, aabr) in room.detail_areas.iter().enumerate() { + let test_score = Dir::iter() + .any(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + as i32 + * aabr.size().product(); + if best_score < test_score { + best_score = test_score; + best = Some(i); + } + } + if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) { + room.details.push(Detail::Bar { aabr }) + } + room.detail_areas.retain(|&aabr| { + if aabr.size().reduce_max() > 1 && rng.gen_bool(0.1) { + room.details.push(table(aabr.center(), aabr)); + false + } else { + true + } + }); + }, + RoomKind::EntranceRoom => {}, + } + } + let name = namegen::NameGen::location(rng).generate_tavern(); + println!("GENERATION TIME: {}μs", start.elapsed().as_micros()); Self { name, rooms, @@ -662,25 +758,6 @@ impl Structure for Tavern { min: room.bounds.min.xy(), max: room.bounds.max.xy(), }; - let table_set = |pos, bounds: Aabr| -> bool { - if bounds.size().reduce_min() >= 1 { - painter.sprite(pos, SpriteKind::TableDining); - - for dir in Dir::iter() { - let pos = pos + dir.to_vec2(); - if bounds.contains_point(pos.xy()) { - painter.rotated_sprite( - pos, - SpriteKind::ChairSingle, - dir.opposite().sprite_ori(), - ); - } - } - true - } else { - false - } - }; for (i, aabr) in room.detail_areas.iter().enumerate() { let color = fxhash::hash32(&i).to_le_bytes(); @@ -696,14 +773,6 @@ impl Structure for Tavern { } match room.kind { RoomKind::Garden => { - for aabr in room.detail_areas.iter() { - let pos = aabr.center().with_z(room.bounds.min.z); - - if field.chance(pos, 0.6) { - table_set(pos, *aabr); - } - } - let dir = Dir::from_vec2(room_aabr.size().into()); painter @@ -722,23 +791,7 @@ impl Structure for Tavern { .fill(dark_wood.clone()) }, RoomKind::StageRoom => { - let mut stage = None; - let mut stage_score = 0; - for aabr in room.detail_areas.iter() { - let edges = Dir::iter() - .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) - .count() as i32; - let test_stage_score = edges * aabr.size().product(); - let Some(aabr) = (if stage_score < test_stage_score { - stage_score = test_stage_score; - stage.replace(*aabr) - } else { - Some(*aabr) - }) else { - continue; - }; - - table_set(aabr.center().with_z(room.bounds.min.z), aabr); + for aabr in room.detail_areas.iter().copied() { for dir in Dir::iter().filter(|dir| { dir.select_aabr(aabr) == dir.select_aabr(room_aabr) && dir.rotated_cw().select_aabr(aabr) @@ -751,59 +804,9 @@ impl Structure for Tavern { painter.sprite(pos.with_z(room.bounds.min.z), SpriteKind::StreetLamp); } } - if let Some(aabr) = stage { - painter - .aabb(aabb(Aabb { - min: aabr.min.with_z(room.bounds.min.z), - max: aabr.max.with_z(room.bounds.min.z), - })) - .fill(stone.clone()); - painter - .aabb(aabb(Aabb { - min: (aabr.min + 1).with_z(room.bounds.min.z), - max: (aabr.max - 1).with_z(room.bounds.min.z), - })) - .fill(wood.clone()); - for dir in Dir::iter().filter(|dir| { - dir.select_aabr(aabr) != dir.select_aabr(room_aabr) - && dir.rotated_cw().select_aabr(aabr) - != dir.rotated_cw().select_aabr(room_aabr) - }) { - let pos = dir.select_aabr_with( - aabr, - Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)), - ); - painter - .column(pos, room.bounds.min.z..=room.bounds.max.z) - .fill(dark_wood.clone()); - - for dir in Dir::iter() { - painter.rotated_sprite( - pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(), - SpriteKind::WallSconce, - dir.sprite_ori(), - ); - } - } - } }, RoomKind::BarRoom => { - let mut bar = None; - let mut bar_score = 0; - for aabr in room.detail_areas.iter() { - let test_stage_score = Dir::iter() - .any(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) - as i32 - * aabr.size().product(); - let Some(aabr) = (if bar_score < test_stage_score { - bar_score = test_stage_score; - bar.replace(*aabr) - } else { - Some(*aabr) - }) else { - continue; - }; - + for aabr in room.detail_areas.iter().copied() { for dir in Dir::iter() .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr)) { @@ -818,7 +821,40 @@ impl Structure for Tavern { ); } } - if let Some(aabr) = bar { + }, + RoomKind::EntranceRoom => { + for aabr in room.detail_areas.iter() { + let edges = Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + .count(); + let hanger_pos = if edges == 2 { + let pos = aabr.center().with_z(room.bounds.min.z); + painter.sprite(pos, SpriteKind::CoatRack); + Some(pos) + } else { + None + }; + + for dir in Dir::iter() + .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) + { + let pos = dir + .select_aabr_with(*aabr, aabr.center()) + .with_z(room.bounds.center().z + 1); + if hanger_pos.map_or(false, |p| p.xy() != pos.xy()) { + painter.rotated_sprite( + pos, + SpriteKind::WallLampSmall, + dir.opposite().sprite_ori(), + ); + } + } + } + }, + } + for detail in room.details.iter() { + match *detail { + Detail::Bar { aabr } => { for dir in Dir::iter() { let edge = dir.select_aabr(aabr); let rot_dir = if field.chance(aabr.center().with_z(0), 0.5) { @@ -865,37 +901,54 @@ impl Structure for Tavern { (true, false) => {}, } } - } - }, - RoomKind::EntranceRoom => { - for aabr in room.detail_areas.iter() { - let edges = Dir::iter() - .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) - .count(); - let hanger_pos = if edges == 2 { - let pos = aabr.center().with_z(room.bounds.min.z); - painter.sprite(pos, SpriteKind::CoatRack); - Some(pos) - } else { - None - }; + }, + Detail::Stage { aabr } => { + painter + .aabb(aabb(Aabb { + min: aabr.min.with_z(room.bounds.min.z), + max: aabr.max.with_z(room.bounds.min.z), + })) + .fill(stone.clone()); + painter + .aabb(aabb(Aabb { + min: (aabr.min + 1).with_z(room.bounds.min.z), + max: (aabr.max - 1).with_z(room.bounds.min.z), + })) + .fill(wood.clone()); + for dir in Dir::iter().filter(|dir| { + dir.select_aabr(aabr) != dir.select_aabr(room_aabr) + && dir.rotated_cw().select_aabr(aabr) + != dir.rotated_cw().select_aabr(room_aabr) + }) { + let pos = dir.select_aabr_with( + aabr, + Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)), + ); + painter + .column(pos, room.bounds.min.z..=room.bounds.max.z) + .fill(dark_wood.clone()); - for dir in Dir::iter() - .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr)) - { - let pos = dir - .select_aabr_with(*aabr, aabr.center()) - .with_z(room.bounds.center().z + 1); - if hanger_pos.map_or(false, |p| p.xy() != pos.xy()) { + for dir in Dir::iter() { painter.rotated_sprite( - pos, - SpriteKind::WallLampSmall, - dir.opposite().sprite_ori(), + pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(), + SpriteKind::WallSconce, + dir.sprite_ori(), ); } } - } - }, + }, + Detail::Table { pos, chairs } => { + let pos = pos.with_z(room.bounds.min.z); + painter.sprite(pos, SpriteKind::TableDining); + for dir in chairs.into_iter() { + painter.rotated_sprite( + pos + dir.to_vec2(), + SpriteKind::ChairSingle, + dir.opposite().sprite_ori(), + ); + } + }, + } } } diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 50e008093c..83491664cf 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -6,7 +6,7 @@ use rand::Rng; use vek::*; /// A 2d direction. -#[derive(Clone, Copy, Debug, PartialEq, Eq, enum_map::Enum, strum::EnumIter)] +#[derive(Debug, enum_map::Enum, strum::EnumIter, enumset::EnumSetType)] pub enum Dir { X, Y, From a3a19ecc3ad1a28d920e2ca7a97421a29f5e2331 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 17 Oct 2023 13:59:38 +0200 Subject: [PATCH 08/26] tavern rtsim --- common/src/mounting.rs | 6 + common/src/resources.rs | 4 + common/src/rtsim.rs | 2 +- rtsim/src/data/npc.rs | 4 +- rtsim/src/rule/npc_ai.rs | 186 +++++++++++++++++++++------- rtsim/src/rule/simulate_npcs.rs | 2 +- server/agent/src/action_nodes.rs | 73 +++++++---- voxygen/anim/src/character/steer.rs | 22 ++-- world/src/site2/plot.rs | 2 +- world/src/site2/plot/tavern.rs | 42 ++++--- 10 files changed, 245 insertions(+), 98 deletions(-) diff --git a/common/src/mounting.rs b/common/src/mounting.rs index 79b666b0d3..6b25985bb4 100644 --- a/common/src/mounting.rs +++ b/common/src/mounting.rs @@ -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>, diff --git a/common/src/resources.rs b/common/src/resources.rs index a85b4e564c..007f34d0bd 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -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); diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 9dd2cd4a84..dc661ec1e0 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -252,7 +252,7 @@ pub enum NpcActivity { HuntAnimals, Dance(Option), Cheer(Option), - Sit(Option), + Sit(Option, Option>), } /// Represents event-like actions that rtsim NPCs can perform to interact with diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 1912667c61..0f7ef02cb9 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -79,7 +79,9 @@ impl Controller { pub fn do_cheer(&mut self, dir: Option) { self.activity = Some(NpcActivity::Cheer(dir)); } - pub fn do_sit(&mut self, dir: Option) { self.activity = Some(NpcActivity::Sit(dir)); } + pub fn do_sit(&mut self, dir: Option, pos: Option>) { + self.activity = Some(NpcActivity::Sit(dir, pos)); + } pub fn say(&mut self, target: impl Into>, content: comp::Content) { self.actions.push(NpcAction::Say(target.into(), content)); diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 8609dea34a..41bea4d61d 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -38,7 +38,7 @@ use vek::*; use world::{ civ::{self, Track}, site::{Site as WorldSite, SiteKind}, - site2::{self, PlotKind, TileKind}, + site2::{self, plot::tavern, PlotKind, TileKind}, util::NEIGHBORS, IndexRef, World, }; @@ -325,7 +325,7 @@ impl Rule for NpcAi { } } -fn idle() -> impl Action { just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") } +fn idle() -> impl Action + Clone { just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") } /// Try to walk toward a 3D position without caring for obstacles. fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32) -> impl Action { @@ -578,7 +578,7 @@ fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action< .map(|_, _| ()) } -fn talk_to(tgt: Actor, _subject: Option) -> impl Action { +fn talk_to(tgt: Actor, _subject: Option) -> impl Action + Clone { now(move |ctx, _| { if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) { // Cut off the conversation sometimes to avoid infinite conversations (but only @@ -630,7 +630,7 @@ fn talk_to(tgt: Actor, _subject: Option) -> impl Action { }) } -fn socialize() -> impl Action { +fn socialize() -> impl Action + Clone { now(move |ctx, socialize: &mut EveryRange| { // Skip most socialising actions if we're not loaded if matches!(ctx.npc.mode, SimulationMode::Loaded) && socialize.should(ctx) { @@ -758,6 +758,8 @@ fn choose_plaza(ctx: &mut NpcCtx, site: SiteId) -> Option> { }) } +const WALKING_SPEED: f32 = 0.35; + fn villager(visiting_site: SiteId) -> impl Action { choose(move |ctx, state: &mut DefaultState| { // Consider moving home if the home site gets too full @@ -804,8 +806,9 @@ fn villager(visiting_site: SiteId) -> impl Action { .then(travel_to_site(new_home, 0.5)) .then(just(move |ctx, _| ctx.controller.set_new_home(new_home)))); } - - if DayPeriod::from(ctx.time_of_day.0).is_dark() + let day_period = DayPeriod::from(ctx.time_of_day.0); + let is_weekend = ctx.time_of_day.day() as u64 % 6 == 0; + if day_period.is_dark() && !matches!(ctx.npc.profession(), Some(Profession::Guard)) { return important( @@ -845,51 +848,142 @@ fn villager(visiting_site: SiteId) -> impl Action { }) .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_::(); - 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_::(); + 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_::(); + 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::, 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( diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index c32faff891..f1b25dcfec 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -254,7 +254,7 @@ fn on_tick(ctx: EventCtx) { | NpcActivity::HuntAnimals | NpcActivity::Dance(_) | NpcActivity::Cheer(_) - | NpcActivity::Sit(_), + | NpcActivity::Sit(..), ) => { // TODO: Maybe they should walk around randomly // when gathering resources? diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index abf3ab97e3..2bd3fd5f00 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -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, ) -> 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| { self.pos.0 @@ -242,6 +237,13 @@ impl<'a> AgentData<'a> { 'activity: { match agent.rtsim_controller.activity { Some(NpcActivity::Goto(travel_to, speed_factor)) => { + if !read_data + .is_volume_riders + .get(*self.entity) + .map_or(false, |r| r.is_steering_entity()) + { + controller.push_event(ControlEvent::Unmount); + } // If it has an rtsim destination and can fly, then it should. // If it is flying and bumps something above it, then it should move down. if self.traversal_config.can_fly @@ -399,17 +401,26 @@ impl<'a> AgentData<'a> { controller.push_action(ControlAction::Talk); break 'activity; // Don't fall through to idle wandering }, - Some(NpcActivity::Sit(dir)) => { - if let Some(look_dir) = dir { - controller.inputs.look_dir = look_dir; - if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { - controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; - break 'activity; - } else { - controller.inputs.move_dir = Vec2::zero(); + Some(NpcActivity::Sit(dir, pos)) => { + if let Some(pos) = + pos.filter(|p| read_data.terrain.get(*p).is_ok_and(|b| b.is_mountable())) + { + if !read_data.is_volume_riders.contains(*self.entity) { + controller + .push_event(ControlEvent::MountVolume(VolumePos::terrain(pos))); } + } else { + if let Some(look_dir) = dir { + controller.inputs.look_dir = look_dir; + if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { + controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; + break 'activity; + } else { + controller.inputs.move_dir = Vec2::zero(); + } + } + controller.push_action(ControlAction::Sit); } - controller.push_action(ControlAction::Sit); break 'activity; // Don't fall through to idle wandering }, Some(NpcActivity::HuntAnimals) => { @@ -581,7 +592,9 @@ impl<'a> AgentData<'a> { read_data: &ReadData, tgt_pos: &Pos, ) { - if read_data.is_riders.contains(*self.entity) { + if read_data.is_riders.contains(*self.entity) + || read_data.is_volume_riders.contains(*self.entity) + { controller.push_event(ControlEvent::Unmount); } @@ -637,7 +650,9 @@ impl<'a> AgentData<'a> { // Proportion of full speed const MAX_FLEE_SPEED: f32 = 0.65; - if read_data.is_riders.contains(*self.entity) { + if read_data.is_riders.contains(*self.entity) + || read_data.is_volume_riders.contains(*self.entity) + { controller.push_event(ControlEvent::Unmount); } @@ -993,7 +1008,12 @@ impl<'a> AgentData<'a> { #[cfg(feature = "be-dyn-lib")] let rng = &mut thread_rng(); - if read_data.is_riders.contains(*self.entity) { + if read_data.is_riders.contains(*self.entity) + || !read_data + .is_volume_riders + .get(*self.entity) + .map_or(false, |r| r.is_steering_entity()) + { controller.push_event(ControlEvent::Unmount); } @@ -1999,4 +2019,15 @@ impl<'a> AgentData<'a> { } } } + + pub fn dismount(&self, controller: &mut Controller, read_data: &ReadData) { + if read_data.is_riders.contains(*self.entity) + || !read_data + .is_volume_riders + .get(*self.entity) + .map_or(false, |r| r.is_steering_entity()) + { + controller.push_event(ControlEvent::Unmount); + } + } } diff --git a/voxygen/anim/src/character/steer.rs b/voxygen/anim/src/character/steer.rs index 32bba0fe3e..f1d270ef1f 100644 --- a/voxygen/anim/src/character/steer.rs +++ b/voxygen/anim/src/character/steer.rs @@ -69,10 +69,22 @@ impl Animation for SteerAnimation { let hand_offset = Vec3::new(rot.cos(), 0.0, -rot.sin()) * 0.4 / s_a.scaler * 11.0; next.hand_l.position = helm_center - hand_offset; - next.hand_l.orientation = hand_rotation; - next.hand_r.position = helm_center + hand_offset; - next.hand_r.orientation = -hand_rotation; + + let ori_l = Quaternion::rotation_x( + PI / 2.0 + (next.hand_l.position.z / next.hand_l.position.x).atan(), + ); + let ori_r = Quaternion::rotation_x( + PI / 2.0 - (next.hand_r.position.z / next.hand_r.position.x).atan(), + ); + + next.hand_l.orientation = hand_rotation * ori_l; + next.hand_r.orientation = -hand_rotation * ori_r; + + next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + next.shoulder_l.orientation = ori_r; + next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + next.shoulder_r.orientation = ori_l; next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2); next.foot_l.orientation = Quaternion::identity(); @@ -80,10 +92,6 @@ impl Animation for SteerAnimation { next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2); next.foot_r.orientation = Quaternion::identity(); - next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); - - next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); - next.glider.position = Vec3::new(0.0, 0.0, 10.0); next.glider.scale = Vec3::one() * 0.0; next.hold.position = Vec3::new(0.4, -0.3, -5.8); diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 2aeb1d783f..ccb9118aff 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -22,7 +22,7 @@ mod savannah_hut; mod savannah_pit; mod savannah_workshop; mod sea_chapel; -mod tavern; +pub mod tavern; mod troll_cave; mod workshop; diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index f5d1d9f68c..a34ab14216 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -22,7 +22,7 @@ use crate::{ type Neighbor = Option>; -struct Wall { +pub struct Wall { start: Vec2, end: Vec2, base_alt: i32, @@ -33,6 +33,15 @@ struct Wall { door: Option, } +impl Wall { + pub fn door_pos(&self) -> Option> { + let wall_dir = Dir::from_vec2(self.end - self.start); + + self.door + .map(|door| (self.start + wall_dir.to_vec2() * door).with_z(self.base_alt)) + } +} + #[derive(Clone, Copy, EnumIter, enum_map::Enum)] enum RoomKind { Garden, @@ -54,7 +63,7 @@ impl RoomKind { } #[derive(Clone, Copy)] -enum Detail { +pub enum Detail { Bar { aabr: Aabr, }, @@ -67,15 +76,15 @@ enum Detail { }, } -struct Room { +pub struct Room { /// Inclusive - bounds: Aabb, + pub bounds: Aabb, kind: RoomKind, // stairs: Option>, walls: EnumMap>>, // TODO: Remove this, used for debugging detail_areas: Vec>, - details: Vec, + pub details: Vec, } impl Room { @@ -99,14 +108,14 @@ struct Stairs { pub struct Tavern { name: String, - rooms: Store, + pub rooms: Store, stairs: Store, walls: Store, /// Tile position of the door tile pub door_tile: Vec2, - pub(crate) door_wpos: Vec3, + pub door_wpos: Vec3, /// Axis aligned bounding region for the house - bounds: Aabr, + pub bounds: Aabr, } impl Tavern { @@ -138,9 +147,7 @@ impl Tavern { let door_tile_center = site.tile_center_wpos(door_tile); let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center); - let door_alt = land - .column_sample(door_wpos, index) - .map_or_else(|| land.get_alt_approx(door_wpos), |sample| sample.alt); + let door_alt = land.get_alt_approx(door_wpos); let door_wpos = door_wpos.with_z(door_alt.ceil() as i32); /// Place room in bounds. @@ -605,10 +612,7 @@ impl Tavern { for door_pos in dir_walls.iter().filter_map(|wall_id| { let wall = &walls[*wall_id]; - wall.door.map(|door| { - let wall_dir = Dir::from_vec2(wall.end - wall.start); - wall.start + wall_dir.to_vec2() * door - }) + wall.door_pos() }) { let orth = dir.orthogonal(); for i in 0..room.detail_areas.len() { @@ -1005,8 +1009,7 @@ impl Structure for Tavern { .fill(dark_wood.clone()); }, } - if let Some(door) = wall.door { - let door_pos = wall.start + wall_dir.to_vec2() * door; + if let Some(door_pos) = wall.door_pos() { let min = match wall.from { None => door_pos - wall.to_dir.to_vec2(), Some(_) => door_pos, @@ -1056,9 +1059,8 @@ impl Structure for Tavern { } }, } - if let Some(door) = wall.door { - let door_pos = wall.start + wall_dir.to_vec2() * door; - let diff = door_pos - wall_aabb.center().xy(); + if let Some(door_pos) = wall.door_pos() { + let diff = door_pos.xy() - wall_aabb.center().xy(); let orth = if diff == Vec2::zero() { wall_dir } else { From e8f8f20ad0f5976990649b9de9164b5685a66e5f Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 18 Oct 2023 15:09:26 +0200 Subject: [PATCH 09/26] somewhat 3d layouting --- world/src/site2/plot/tavern.rs | 338 ++++++++++++++++++++------------- 1 file changed, 202 insertions(+), 136 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index a34ab14216..b4ccf4420b 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, mem::swap, ops::RangeInclusive}; +use std::{mem::swap, ops::RangeInclusive}; use common::{ comp::Content, @@ -30,15 +30,29 @@ pub struct Wall { from: Neighbor, to: Neighbor, to_dir: Dir, - door: Option, + door: Option<(i32, i32)>, } impl Wall { - pub fn door_pos(&self) -> Option> { + pub fn door_pos(&self) -> Option> { let wall_dir = Dir::from_vec2(self.end - self.start); - self.door - .map(|door| (self.start + wall_dir.to_vec2() * door).with_z(self.base_alt)) + self.door.map(|(door_min, door_max)| { + (self.start.as_() + wall_dir.to_vec2().as_() * (door_min + door_max) as f32 / 2.0 + 0.5) + .with_z(self.base_alt as f32) + }) + } + + pub fn door_bounds(&self) -> Option> { + let wall_dir = Dir::from_vec2(self.end - self.start); + + self.door.map(|(door_min, door_max)| { + Aabr { + min: self.start + wall_dir.to_vec2() * door_min, + max: self.start + wall_dir.to_vec2() * door_max, + } + .made_valid() + }) } } @@ -128,6 +142,8 @@ impl Tavern { door_dir: Dir, tile_aabr: Aabr, ) -> Self { + let name = namegen::NameGen::location(rng).generate_tavern(); + let start = std::time::Instant::now(); let mut rooms = Store::default(); let stairs = Store::default(); @@ -229,6 +245,7 @@ impl Tavern { Vec2::broadcast(door_dir.rotated_cw().select_aabr(entrance_room_aabr)), ) + door_dir.rotated_cw().to_vec2() + door_dir.to_vec2(); + let door_center = door_dir.rotated_cw().select(door_wpos.xy() - start).abs(); let wall_id = walls.insert(Wall { start, end: door_dir.select_aabr_with( @@ -241,7 +258,7 @@ impl Tavern { from: None, to: Some(entrance_id), to_dir: -door_dir, - door: Some(door_dir.rotated_cw().select(door_wpos.xy() - start).abs()), + door: Some((door_center - 1, door_center + 1)), }); rooms[entrance_id].walls[door_dir].push(wall_id); @@ -298,19 +315,23 @@ impl Tavern { max: in_dir.select_aabr_with(ibounds, ibounds.max), } .made_valid(); + // Height of the new room + let room_hgt = rng.gen_range(3..=5); + let alt = land.get_alt_approx(max_bounds.center()) as i32; + let min_z = from_room.bounds.min.z.min(alt); + let max_z = from_room.bounds.max.z.max(alt + room_hgt); // Take other rooms into account when calculating `max_bounds`. We don't care // about this room if it's the originating room or at another // height. for (_, room) in rooms.iter().filter(|(room_id, room)| { - *room_id != from_id - && room.bounds.min.z <= from_room.bounds.max.z - && room.bounds.max.z >= from_room.bounds.min.z + *room_id != from_id && room.bounds.min.z <= max_z && room.bounds.max.z >= min_z }) { let bounds = to_aabr(room.bounds); let bounds = extend_aabr(bounds, 2); let intersection = bounds.intersection(max_bounds); if intersection.is_valid() { + // Find the direction to shrink in that yields the highest area. let Some(bounds) = Dir::iter() .filter(|dir| { *dir != in_dir @@ -368,7 +389,7 @@ impl Tavern { 2.0 / (1.0 + room_counts[RoomKind::BarRoom] as f32).powi(2) }, RoomKind::EntranceRoom => { - 0.1 / (1.0 + room_counts[RoomKind::EntranceRoom] as f32) + 0.05 / (1.0 + room_counts[RoomKind::EntranceRoom] as f32) }, }, room_kind, @@ -405,11 +426,9 @@ impl Tavern { continue 'room_gen; }; - let room_hgt = rng.gen_range(3..=5); - let bounds3 = Aabb { - min: bounds.min.with_z(from_room.bounds.min.z), - max: bounds.max.with_z(from_room.bounds.min.z + room_hgt), + min: bounds.min.with_z(alt), + max: bounds.max.with_z(alt + room_hgt), }; let id = rooms.insert(Room::new(bounds3, room)); @@ -429,15 +448,19 @@ impl Tavern { ) + in_dir.to_vec2() + right.to_vec2(); + let door_center = right.select(in_pos - start); + let b = rng.gen_bool(0.5); + let door_min = door_center - b as i32; + let door_max = door_center - (!b) as i32; let wall_id = walls.insert(Wall { start, end, - base_alt: bounds3.min.z, - top_alt: bounds3.max.z, + base_alt: min_z, + top_alt: max_z, from: Some(from_id), to: Some(id), to_dir: in_dir, - door: Some(right.select(in_pos - start)), + door: Some((door_min, door_max)), }); rooms[id].walls[-in_dir].push(wall_id); @@ -461,42 +484,33 @@ impl Tavern { let range = (orth.select(room_bounds.min), orth.select(room_bounds.max)); wall_ranges[dir].push(range); } + // Split the wall into parts. let mut split_range = |dir: Dir, min: i32, max: i32| { debug_assert!(min <= max); - let Ok(i) = wall_ranges[dir].binary_search_by(|(r_min, r_max)| { - match (min.cmp(r_min), min.cmp(r_max)) { - (Ordering::Less, _) => Ordering::Greater, - (Ordering::Greater | Ordering::Equal, Ordering::Less | Ordering::Equal) => { - Ordering::Equal - }, - (_, Ordering::Greater) => Ordering::Less, + let mut new_ranges = Vec::new(); + wall_ranges[dir].retain_mut(|(r_min, r_max)| { + if *r_min <= max && *r_max >= min { + match (*r_min >= min, *r_max <= max) { + (true, true) => false, + (true, false) => { + *r_min = max + 1; + true + }, + (false, true) => { + *r_max = min - 1; + true + }, + (false, false) => { + new_ranges.push((max + 1, *r_max)); + *r_max = min - 1; + true + }, + } + } else { + true } - }) else { - // TODO: Don't panic here. - dbg!((min, max)); - dbg!(&wall_ranges[dir]); - panic!("Couldn't find range"); - }; - - let range = &mut wall_ranges[dir][i]; - debug_assert!(range.0 <= min); - debug_assert!(range.1 >= max); - - match (range.0 == min, range.1 == max) { - (true, true) => { - wall_ranges[dir].remove(i); - }, - (true, false) => *range = (max + 1, range.1), - (false, true) => *range = (range.0, min - 1), - (false, false) => { - let tmp = range.1; - *range = (range.0, min - 1); - debug_assert!(range.0 <= range.1); - let m = (max + 1, tmp); - debug_assert!(m.0 <= m.1, "{m:?}"); - wall_ranges[dir].insert(i + 1, m); - }, - } + }); + wall_ranges[dir].extend(new_ranges); }; for dir in Dir::iter() { let connected_walls = &mut rooms[from_id].walls[dir]; @@ -526,7 +540,7 @@ impl Tavern { let a_max_z = rooms[from_id].bounds.max.z; let b_min_z = rooms[to_id].bounds.min.z; let b_max_z = rooms[to_id].bounds.max.z; - if a_min_z > b_max_z || a_max_z < b_min_z { + if a_min_z >= b_max_z || a_max_z <= b_min_z { // We are not at the same altitude. continue; } @@ -552,8 +566,9 @@ impl Tavern { let min = orth.select(start); let max = orth.select(end); split_range(to_dir, min, max); - let door = if max - min >= 3 && rng.gen_bool(0.8) { - Some(rng.gen_range(1..=max - min)) + let door = if max - min > 2 && max_z - min_z > 3 && rng.gen_bool(0.8) { + let door_center = rng.gen_range(1..=max - min - 2); + Some((door_center, door_center + 1)) } else { None }; @@ -600,40 +615,73 @@ impl Tavern { // Compute detail areas for room in rooms.values_mut() { let bounds = to_aabr(room.bounds); - let c = bounds.center(); - let mut b = bounds.split_at_x(c.x); - b[0].max.x -= 1; - room.detail_areas.extend(b.into_iter().flat_map(|b| { - let mut b = b.split_at_y(c.y); - b[0].max.y -= 1; - b - })); - for (dir, dir_walls) in room.walls.iter() { - for door_pos in dir_walls.iter().filter_map(|wall_id| { - let wall = &walls[*wall_id]; + let walls = &walls; + let mut avoid = room + .walls + .iter() + .flat_map(|(dir, dir_walls)| { + dir_walls.iter().filter_map(move |wall_id| { + let wall = &walls[*wall_id]; - wall.door_pos() - }) { - let orth = dir.orthogonal(); - for i in 0..room.detail_areas.len() { - let bc = room.detail_areas[i].center(); - // Check if we are on the doors side of the center of the room. - if dir.select(bc - c) * dir.signum() >= 0 { - if let Some([a, b]) = - orth.try_split_aabr(room.detail_areas[i], orth.select(door_pos)) - { - room.detail_areas[i] = orth.extend_aabr(a, -1); - room.detail_areas.push(orth.opposite().extend_aabr(b, -1)); + let door_bounds = wall.door_bounds()?; + + Some( + Aabr { + min: dir.select_aabr_with(bounds, door_bounds.min), + max: dir.select_with(bounds.center(), door_bounds.max), + } + .made_valid(), + ) + }) + }) + .collect::>(); + + let mut x = bounds.min.x; + while x <= bounds.max.x { + let mut y = bounds.min.y; + 'y_loop: while y <= bounds.max.y { + let min = Vec2::new(x, y); + let mut max_y = bounds.max.y; + for area in avoid.iter() { + let contains_x = area.min.x <= min.x && min.x <= area.max.x; + let contains_y = area.min.y <= min.y && min.y <= area.max.y; + if contains_x && contains_y { + y = area.max.y + 1; + continue 'y_loop; + } + + if contains_x && min.y < area.min.y { + if area.min.y - 1 < max_y { + max_y = area.min.y - 1; } } } - room.detail_areas.retain(|area| area.is_valid()); + + let max_x = avoid + .iter() + .filter_map(|area| { + if area.min.x > x && area.min.y <= max_y && area.max.y >= min.y { + Some(area.min.x - 1) + } else { + None + } + }) + .min() + .unwrap_or(bounds.max.x); + + let area = Aabr { + min, + max: Vec2::new(max_x, max_y), + }; + avoid.push(area); + room.detail_areas.push(area); + y = max_y + 1; } + x += 1; } } // Place details in detail areas. - for room in rooms.values_mut() { let room_aabr = to_aabr(room.bounds); let table = |pos: Vec2, aabr: Aabr| Detail::Table { @@ -705,8 +753,6 @@ impl Tavern { } } - let name = namegen::NameGen::location(rng).generate_tavern(); - println!("GENERATION TIME: {}μs", start.elapsed().as_micros()); Self { name, @@ -732,22 +778,11 @@ impl Structure for Tavern { #[cfg_attr(feature = "be-dyn-lib", export_name = "render_tavern")] fn render_inner(&self, _site: &Site, _land: &Land, painter: &crate::site2::Painter) { - let bounds = Aabr { - min: self.bounds.min, - max: self.bounds.max - 1, - }; - let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 70, 70), 10); let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(106, 73, 64))); let dark_wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(80, 53, 48))); let field = RandomField::new(740384); - painter - .aabb(aabb(Aabb { - min: bounds.min.with_z(self.door_wpos.z - 10), - max: bounds.max.with_z(self.door_wpos.z - 1), - })) - .fill(stone.clone()); for (_, room) in self.rooms.iter() { painter.aabb(aabb(room.bounds)).clear(); @@ -757,11 +792,6 @@ impl Structure for Tavern { max: room.bounds.max.with_z(room.bounds.min.z - 1), })) .fill(wood.clone()); - - let room_aabr = Aabr { - min: room.bounds.min.xy(), - max: room.bounds.max.xy(), - }; for (i, aabr) in room.detail_areas.iter().enumerate() { let color = fxhash::hash32(&i).to_le_bytes(); @@ -775,6 +805,11 @@ impl Structure for Tavern { Rgb::new(color[0], color[1], color[3]), ))); } + + let room_aabr = Aabr { + min: room.bounds.min.xy(), + max: room.bounds.max.xy(), + }; match room.kind { RoomKind::Garden => { let dir = Dir::from_vec2(room_aabr.size().into()); @@ -1009,72 +1044,103 @@ impl Structure for Tavern { .fill(dark_wood.clone()); }, } - if let Some(door_pos) = wall.door_pos() { - let min = match wall.from { - None => door_pos - wall.to_dir.to_vec2(), - Some(_) => door_pos, - }; - let max = match wall.to { - None => door_pos + wall.to_dir.to_vec2(), - Some(_) => door_pos, - }; - painter - .aabb(aabb(Aabb { - min: min.with_z(wall.base_alt), - max: max.with_z(wall.base_alt + 2), - })) - .clear(); - } - if let (Some(room), to @ None) | (None, to @ Some(room)) = + let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = (wall.from.map(get_kind), wall.to.map(get_kind)) { - let dir = if to.is_none() { + let in_dir = if to.is_none() { -wall.to_dir } else { wall.to_dir }; - let width = dir.orthogonal().select(wall.end - wall.start).abs(); + + Some((in_dir, room)) + } else { + None + }; + if let Some((in_dir, room)) = in_dir_room { + let width = in_dir.orthogonal().select(wall.end - wall.start).abs(); let wall_center = (wall.start + wall.end) / 2; - let d = wall_dir.select(wall_center - wall.start) * wall_dir.signum(); + let door_dist = wall.door_bounds().map_or(i32::MAX, |door| { + (door.min - wall_center) + .map(|x| x.abs()) + .reduce_max() + .max((door.max - wall_center).map(|x| x.abs()).reduce_max()) + }); match room { RoomKind::Garden => { - if wall.door.map_or(true, |door| (door - d).abs() >= 2) { + if door_dist >= 2 { painter.rotated_sprite( wall_center.with_z(wall.base_alt + 1), SpriteKind::Planter, - dir.sprite_ori(), + in_dir.sprite_ori(), ); } }, _ => { - if width >= 5 && wall.door.map_or(true, |door| (door - d).abs() > 3) { + if width >= 5 && door_dist > 3 { painter .aabb(aabb(Aabb { - min: (wall_center + dir.rotated_ccw().to_vec2()) + min: (wall_center + in_dir.rotated_ccw().to_vec2()) .with_z(wall.base_alt + 1), - max: (wall_center + dir.rotated_cw().to_vec2()) + max: (wall_center + in_dir.rotated_cw().to_vec2()) .with_z(wall.base_alt + 2), })) - .fill(Fill::RotatedSprite(SpriteKind::Window1, dir.sprite_ori())); + .fill(Fill::RotatedSprite( + SpriteKind::Window1, + in_dir.sprite_ori(), + )); } }, } - if let Some(door_pos) = wall.door_pos() { - let diff = door_pos.xy() - wall_aabb.center().xy(); - let orth = if diff == Vec2::zero() { - wall_dir - } else { - Dir::from_vec2(diff) + } + if let Some(door) = wall.door_bounds() { + let orth = wall.to_dir.orthogonal(); + painter + .aabb(aabb(Aabb { + min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1), + max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3), + })) + .fill(stone.clone()); + painter + .aabb(aabb(Aabb { + min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt), + max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2), + })) + .clear(); + if let Some((in_dir, _room)) = in_dir_room { + let sprite = match in_dir.rotated_cw().select(door.size()) { + 2.. => SpriteKind::DoorWide, + _ => SpriteKind::Door, }; - let pos = door_pos - dir.to_vec2() + orth.to_vec2(); - let (sprite, z) = match room { - RoomKind::Garden => (SpriteKind::Sign, 0), - _ => (SpriteKind::HangingSign, 2), - }; - painter.rotated_sprite_with_cfg( - pos.with_z(wall.base_alt + z), + painter.rotated_sprite( + in_dir + .rotated_cw() + .select_aabr_with(door, door.min) + .with_z(wall.base_alt), sprite, - dir.opposite().sprite_ori(), + in_dir.sprite_ori(), + ); + painter.rotated_sprite( + in_dir + .rotated_ccw() + .select_aabr_with(door, door.min) + .with_z(wall.base_alt), + sprite, + in_dir.opposite().sprite_ori(), + ); + + let dir = match field.chance(door.min.with_z(wall.base_alt), 0.5) { + true => in_dir.rotated_cw(), + false => in_dir.rotated_ccw(), + }; + + let pos = + dir.select_aabr_with(door, door.min) + dir.to_vec2() - in_dir.to_vec2(); + + painter.rotated_sprite_with_cfg( + pos.with_z(wall.base_alt + 2), + SpriteKind::HangingSign, + in_dir.opposite().sprite_ori(), SpriteCfg { unlock: None, content: Some(Content::Plain(self.name.clone())), From 6e19abd7ec07256c7e0690dc0342b55eca684587 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 18 Oct 2023 20:58:03 +0200 Subject: [PATCH 10/26] stairs --- world/src/site2/plot/tavern.rs | 108 ++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index b4ccf4420b..f4ce9d89b1 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -16,7 +16,7 @@ use vek::*; use crate::{ site::namegen, site2::{gen::PrimitiveTransform, Dir, Fill, Site, Structure}, - util::RandomField, + util::{RandomField, Sampler}, IndexRef, Land, }; @@ -380,7 +380,7 @@ impl Tavern { ( match room_kind { RoomKind::Garden => { - 1.0 / (1.0 + room_counts[RoomKind::Garden] as f32 / 2.0) + 0.5 / (1.0 + room_counts[RoomKind::Garden] as f32 * 0.8) }, RoomKind::StageRoom => { 2.0 / (1.0 + room_counts[RoomKind::StageRoom] as f32).powi(2) @@ -778,12 +778,30 @@ impl Structure for Tavern { #[cfg_attr(feature = "be-dyn-lib", export_name = "render_tavern")] fn render_inner(&self, _site: &Site, _land: &Land, painter: &crate::site2::Painter) { - let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 70, 70), 10); - let wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(106, 73, 64))); - let dark_wood = Fill::Block(Block::new(BlockKind::Wood, Rgb::new(80, 53, 48))); - let field = RandomField::new(740384); + const PALETTES: [[Rgb; 5]; 2] = [ + [ + Rgb::new(55, 65, 64), + Rgb::new(220, 53, 34), + Rgb::new(108, 100, 79), + Rgb::new(42, 44, 43), + Rgb::new(30, 30, 32), + ], + [ + Rgb::new(46, 62, 215), + Rgb::new(147, 51, 29), + Rgb::new(200, 200, 200), + Rgb::new(56, 18, 0), + Rgb::new(252, 100, 113), + ], + ]; + let palette = PALETTES[field.get(self.door_wpos) as usize % PALETTES.len()]; + let detail0 = Fill::Brick(BlockKind::Rock, palette[0], 10); + let color0 = Fill::Block(Block::new(BlockKind::Wood, palette[1])); + let color1 = Fill::Block(Block::new(BlockKind::Wood, palette[2])); + let color2 = Fill::Block(Block::new(BlockKind::Wood, palette[3])); + for (_, room) in self.rooms.iter() { painter.aabb(aabb(room.bounds)).clear(); painter @@ -791,20 +809,7 @@ impl Structure for Tavern { min: room.bounds.min.with_z(room.bounds.min.z - 1), max: room.bounds.max.with_z(room.bounds.min.z - 1), })) - .fill(wood.clone()); - for (i, aabr) in room.detail_areas.iter().enumerate() { - let color = fxhash::hash32(&i).to_le_bytes(); - - painter - .aabb(aabb(Aabb { - min: aabr.min.with_z(room.bounds.min.z - 1), - max: aabr.max.with_z(room.bounds.min.z - 1), - })) - .fill(Fill::Block(Block::new( - BlockKind::Rock, - Rgb::new(color[0], color[1], color[3]), - ))); - } + .fill(color2.clone()); let room_aabr = Aabr { min: room.bounds.min.xy(), @@ -814,6 +819,7 @@ impl Structure for Tavern { RoomKind::Garden => { let dir = Dir::from_vec2(room_aabr.size().into()); + /* painter .aabb(aabb(Aabb { min: dir @@ -827,7 +833,8 @@ impl Structure for Tavern { -dir.to_vec3() * 2, (dir.select(room_aabr.size()) as u32 + 3) / 2, ) - .fill(dark_wood.clone()) + .fill(color1.clone()) + */ }, RoomKind::StageRoom => { for aabr in room.detail_areas.iter().copied() { @@ -923,13 +930,13 @@ impl Structure for Tavern { .with_z(room.bounds.min.z), max: max.with_z(room.bounds.min.z), })) - .fill(dark_wood.clone()); + .fill(color1.clone()); painter .aabb(aabb(Aabb { min: min.with_z(room.bounds.min.z + 3), max: max.with_z(room.bounds.max.z), })) - .fill(dark_wood.clone()); + .fill(color1.clone()); }, (true, true) => { painter.sprite( @@ -947,13 +954,13 @@ impl Structure for Tavern { min: aabr.min.with_z(room.bounds.min.z), max: aabr.max.with_z(room.bounds.min.z), })) - .fill(stone.clone()); + .fill(detail0.clone()); painter .aabb(aabb(Aabb { min: (aabr.min + 1).with_z(room.bounds.min.z), max: (aabr.max - 1).with_z(room.bounds.min.z), })) - .fill(wood.clone()); + .fill(color0.clone()); for dir in Dir::iter().filter(|dir| { dir.select_aabr(aabr) != dir.select_aabr(room_aabr) && dir.rotated_cw().select_aabr(aabr) @@ -965,7 +972,7 @@ impl Structure for Tavern { ); painter .column(pos, room.bounds.min.z..=room.bounds.max.z) - .fill(dark_wood.clone()); + .fill(color1.clone()); for dir in Dir::iter() { painter.rotated_sprite( @@ -1004,10 +1011,8 @@ impl Structure for Tavern { let hgt = wall_aabb.min.z..=wall_aabb.max.z; painter .column(wall_aabb.min.xy(), hgt.clone()) - .fill(dark_wood.clone()); - painter - .column(wall_aabb.max.xy(), hgt) - .fill(dark_wood.clone()); + .fill(color1.clone()); + painter.column(wall_aabb.max.xy(), hgt).fill(color1.clone()); let z = (wall.base_alt + wall.top_alt) / 2; painter.rotated_sprite( @@ -1025,23 +1030,23 @@ impl Structure for Tavern { min: wall_aabb.min, max: wall_aabb.max.with_z(wall_aabb.min.z), })) - .fill(dark_wood.clone()); + .fill(color1.clone()); painter .aabb(aabb(Aabb { min: wall_aabb.min.with_z(wall_aabb.max.z), max: wall_aabb.max, })) - .fill(dark_wood.clone()); + .fill(color1.clone()); }, (None, None) => {}, _ => { - painter.aabb(aabb(wall_aabb)).fill(wood.clone()); + painter.aabb(aabb(wall_aabb)).fill(color0.clone()); painter .column(wall.start, wall.base_alt..=wall.top_alt) - .fill(dark_wood.clone()); + .fill(color1.clone()); painter .column(wall.end, wall.base_alt..=wall.top_alt) - .fill(dark_wood.clone()); + .fill(color1.clone()); }, } let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = @@ -1100,13 +1105,42 @@ impl Structure for Tavern { min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1), max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3), })) - .fill(stone.clone()); + .fill(detail0.clone()); painter .aabb(aabb(Aabb { min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt), max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2), })) .clear(); + let filter = |room: &Id| self.rooms[*room].bounds.min.z > wall.base_alt; + if let Some((room, to_dir)) = wall + .to + .filter(filter) + .zip(Some(wall.to_dir)) + .or(wall.from.filter(filter).zip(Some(-wall.to_dir))) + { + let room = &self.rooms[room]; + + let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1); + painter + .ramp( + aabb(Aabb { + min: (door.min - to_dir.to_vec2() * 3).with_z(wall.base_alt), + max: max.with_z(room.bounds.min.z + 2), + }), + to_dir, + ) + .clear(); + painter + .ramp( + aabb(Aabb { + min: (door.min + to_dir.to_vec2() * 2).with_z(wall.base_alt), + max: max.with_z(room.bounds.min.z - 1), + }), + to_dir, + ) + .fill(color2.clone()); + } if let Some((in_dir, _room)) = in_dir_room { let sprite = match in_dir.rotated_cw().select(door.size()) { 2.. => SpriteKind::DoorWide, @@ -1178,7 +1212,7 @@ impl Structure for Tavern { }), stairs.dir, ) - .fill(wood.clone()); + .fill(color0.clone()); } } } From a9191b96a9b1ec68d1802e7f00f5bf328d625b42 Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 22 Oct 2023 23:21:03 +0200 Subject: [PATCH 11/26] better stairs --- world/src/site2/plot/tavern.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index f4ce9d89b1..cbcc8f406d 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -15,7 +15,7 @@ use vek::*; use crate::{ site::namegen, - site2::{gen::PrimitiveTransform, Dir, Fill, Site, Structure}, + site2::{Dir, Fill, Site, Structure}, util::{RandomField, Sampler}, IndexRef, Land, }; @@ -317,7 +317,17 @@ impl Tavern { .made_valid(); // Height of the new room let room_hgt = rng.gen_range(3..=5); - let alt = land.get_alt_approx(max_bounds.center()) as i32; + let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32; + let max_stair_length = (in_dir.select(if wanted_alt < from_room.bounds.min.z { + from_bounds.size() + } else { + max_bounds.size() + }) / 2) + .min(5); + let alt = wanted_alt.clamp( + from_room.bounds.min.z - max_stair_length, + from_room.bounds.min.z + max_stair_length, + ); let min_z = from_room.bounds.min.z.min(alt); let max_z = from_room.bounds.max.z.max(alt + room_hgt); @@ -325,7 +335,9 @@ impl Tavern { // about this room if it's the originating room or at another // height. for (_, room) in rooms.iter().filter(|(room_id, room)| { - *room_id != from_id && room.bounds.min.z <= max_z && room.bounds.max.z >= min_z + *room_id != from_id + && room.bounds.min.z + 1 <= max_z + && room.bounds.max.z + 1 >= min_z }) { let bounds = to_aabr(room.bounds); let bounds = extend_aabr(bounds, 2); @@ -998,8 +1010,8 @@ impl Structure for Tavern { } } - for (_, wall) in self.walls.iter() { - let get_kind = |room| self.rooms.get(room).kind; + let get_kind = |room| self.rooms.get(room).kind; + for wall in self.walls.values() { let wall_aabb = Aabb { min: wall.start.with_z(wall.base_alt), max: wall.end.with_z(wall.top_alt), @@ -1049,6 +1061,8 @@ impl Structure for Tavern { .fill(color1.clone()); }, } + } + for wall in self.walls.values() { let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = (wall.from.map(get_kind), wall.to.map(get_kind)) { @@ -1184,7 +1198,7 @@ impl Structure for Tavern { } } - for (_, stairs) in self.stairs.iter() { + for stairs in self.stairs.values() { let down_room = &self.rooms[stairs.in_room]; let up_room = &self.rooms[stairs.to_room]; From 5a31b17bc38203364463d958cdba29e239ab2c4e Mon Sep 17 00:00:00 2001 From: Isse Date: Sat, 4 Nov 2023 20:38:48 +0100 Subject: [PATCH 12/26] Add simple roofs --- world/src/site2/plot/tavern.rs | 363 +++++++++++++++++++-------------- 1 file changed, 215 insertions(+), 148 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index cbcc8f406d..2242ae71ff 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -8,14 +8,17 @@ use common::{ }; use enum_map::EnumMap; use enumset::EnumSet; -use hashbrown::HashSet; -use rand::{seq::IteratorRandom, Rng}; +use hashbrown::{HashMap, HashSet}; +use rand::{ + seq::{IteratorRandom, SliceRandom}, + Rng, SeedableRng, +}; use strum::{EnumIter, IntoEnumIterator}; use vek::*; use crate::{ site::namegen, - site2::{Dir, Fill, Site, Structure}, + site2::{gen::PrimitiveTransform, Dir, Fill, Site, Structure}, util::{RandomField, Sampler}, IndexRef, Land, }; @@ -113,17 +116,9 @@ impl Room { } } -struct Stairs { - end: Vec2, - dir: Dir, - in_room: Id, - to_room: Id, -} - pub struct Tavern { name: String, pub rooms: Store, - stairs: Store, walls: Store, /// Tile position of the door tile pub door_tile: Vec2, @@ -146,7 +141,6 @@ impl Tavern { let start = std::time::Instant::now(); let mut rooms = Store::default(); - let stairs = Store::default(); let mut walls = Store::default(); let mut room_counts = EnumMap::::default(); @@ -336,7 +330,7 @@ impl Tavern { // height. for (_, room) in rooms.iter().filter(|(room_id, room)| { *room_id != from_id - && room.bounds.min.z + 1 <= max_z + && room.bounds.min.z - 1 <= max_z && room.bounds.max.z + 1 >= min_z }) { let bounds = to_aabr(room.bounds); @@ -769,7 +763,6 @@ impl Tavern { Self { name, rooms, - stairs, walls, door_tile, door_wpos, @@ -792,6 +785,8 @@ impl Structure for Tavern { fn render_inner(&self, _site: &Site, _land: &Land, painter: &crate::site2::Painter) { let field = RandomField::new(740384); + const DOWN: i32 = 6; + const PALETTES: [[Rgb; 5]; 2] = [ [ Rgb::new(55, 65, 64), @@ -809,19 +804,197 @@ impl Structure for Tavern { ], ]; let palette = PALETTES[field.get(self.door_wpos) as usize % PALETTES.len()]; - let detail0 = Fill::Brick(BlockKind::Rock, palette[0], 10); - let color0 = Fill::Block(Block::new(BlockKind::Wood, palette[1])); - let color1 = Fill::Block(Block::new(BlockKind::Wood, palette[2])); - let color2 = Fill::Block(Block::new(BlockKind::Wood, palette[3])); + let detail_fill = Fill::Brick(BlockKind::Rock, palette[0], 10); + let wall_fill = Fill::Block(Block::new(BlockKind::Wood, palette[1])); + let wall_detail_fill = Fill::Block(Block::new(BlockKind::Wood, palette[2])); + let floor_fill = Fill::Block(Block::new(BlockKind::Wood, palette[3])); + let roof_fill = Fill::Block(Block::new(BlockKind::Wood, palette[4])); - for (_, room) in self.rooms.iter() { - painter.aabb(aabb(room.bounds)).clear(); + let get_kind = |room| self.rooms.get(room).kind; + let get_door_stair = |wall: &Wall, door: Aabr| { + let filter = |room: &Id| self.rooms[*room].bounds.min.z > wall.base_alt; + wall.to + .filter(filter) + .zip(Some(wall.to_dir)) + .or(wall.from.filter(filter).zip(Some(-wall.to_dir))) + .map(|(room, to_dir)| { + let room = &self.rooms[room]; + + let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1); + (door.min, max, room, to_dir) + }) + }; + + enum RoofStyle { + Flat, + FlatBars(Dir), + LeanTo(Dir), + Gable(Dir), + Hip, + } + struct Roof { + bounds: Aabb, + style: RoofStyle, + } + let mut roof = Store::default(); + + let s = [field.get(self.door_wpos); 8]; + let rng = &mut rand_chacha::ChaChaRng::from_seed(unsafe { std::mem::transmute(s) }); + // let mut room_roofs = HashMap::new(); + + for room in self.rooms.values() { + match room.kind { + RoomKind::Garden => { + let dir = *[Dir::X, Dir::Y].choose(rng).expect("We have 2 elements"); + let orth = dir.orthogonal(); + roof.insert(Roof { + bounds: Aabb { + min: (room.bounds.min - 1 - orth.to_vec2()) + .with_z(room.bounds.max.z + 1), + max: (room.bounds.max + 1 + orth.to_vec2()) + .with_z(room.bounds.max.z + 1), + }, + style: RoofStyle::FlatBars(dir), + }); + }, + _ => { + roof.insert(Roof { + bounds: Aabb { + min: (room.bounds.min - 2).with_z(room.bounds.max.z + 1), + max: (room.bounds.max + 2).with_z(room.bounds.max.z + 5), + }, + style: RoofStyle::Hip, + }); + }, + } + } + + for roof in roof.values() { + let roof_aabr = Aabr { + min: roof.bounds.min.xy(), + max: roof.bounds.max.xy(), + }; + match &roof.style { + RoofStyle::Flat => { + painter.aabb(aabb(roof.bounds)).fill(roof_fill.clone()); + }, + RoofStyle::FlatBars(dir) => painter + .aabb(aabb(Aabb { + min: dir + .select_aabr_with(roof_aabr, roof_aabr.min) + .with_z(roof.bounds.max.z), + max: dir + .select_aabr_with(roof_aabr, roof_aabr.max) + .with_z(roof.bounds.max.z), + })) + .repeat( + -dir.to_vec3() * 2, + (dir.select(roof_aabr.size()) as u32 + 3) / 2, + ) + .fill(roof_fill.clone()), + RoofStyle::LeanTo(dir) => { + painter + .ramp(aabb(roof.bounds), *dir) + .fill(roof_fill.clone()); + }, + RoofStyle::Gable(dir) => { + painter + .gable(roof.bounds, roof.bounds.size().d, *dir) + .fill(roof_fill.clone()); + }, + RoofStyle::Hip => { + painter.pyramid(aabb(roof.bounds)).fill(roof_fill.clone()); + }, + } + } + + for room in self.rooms.values() { painter .aabb(aabb(Aabb { - min: room.bounds.min.with_z(room.bounds.min.z - 1), + min: room.bounds.min.with_z(room.bounds.min.z - DOWN), max: room.bounds.max.with_z(room.bounds.min.z - 1), })) - .fill(color2.clone()); + .fill(floor_fill.clone()); + } + for wall in self.walls.values() { + let wall_aabb = Aabb { + min: wall.start.with_z(wall.base_alt), + max: wall.end.with_z(wall.top_alt), + }; + let wall_dir = Dir::from_vec2(wall.end - wall.start); + match (wall.from.map(get_kind), wall.to.map(get_kind)) { + (Some(RoomKind::Garden), Some(RoomKind::Garden) | None) + | (None, Some(RoomKind::Garden)) => { + let hgt = wall_aabb.min.z..=wall_aabb.max.z; + painter + .column(wall_aabb.min.xy(), hgt.clone()) + .fill(wall_detail_fill.clone()); + painter + .column(wall_aabb.max.xy(), hgt) + .fill(wall_detail_fill.clone()); + let z = (wall.base_alt + wall.top_alt) / 2; + + painter + .aabb(aabb(Aabb { + min: (wall_aabb.min + wall_dir.to_vec2()).with_z(wall_aabb.min.z + 1), + max: (wall_aabb.max - wall_dir.to_vec2()).with_z(wall_aabb.max.z - 1), + })) + .clear(); + + painter.rotated_sprite( + wall_aabb.min.with_z(z) + wall_dir.to_vec2(), + SpriteKind::WallSconce, + wall_dir.sprite_ori(), + ); + painter.rotated_sprite( + wall_aabb.max.with_z(z) - wall_dir.to_vec2(), + SpriteKind::WallSconce, + wall_dir.opposite().sprite_ori(), + ); + painter + .aabb(aabb(Aabb { + min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN), + max: wall_aabb.max.with_z(wall_aabb.min.z), + })) + .fill(wall_detail_fill.clone()); + painter + .aabb(aabb(Aabb { + min: wall_aabb.min.with_z(wall_aabb.max.z), + max: wall_aabb.max, + })) + .fill(wall_detail_fill.clone()); + }, + (None, None) => {}, + _ => { + painter + .aabb(aabb(Aabb { + min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN), + max: wall_aabb.max, + })) + .fill(wall_fill.clone()); + painter + .column(wall.start, wall.base_alt - DOWN..=wall.top_alt) + .fill(wall_detail_fill.clone()); + painter + .column(wall.end, wall.base_alt - DOWN..=wall.top_alt) + .fill(wall_detail_fill.clone()); + }, + } + if let Some(door) = wall.door_bounds() { + let orth = wall.to_dir.orthogonal(); + if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) { + painter + .aabb(aabb(Aabb { + min: (min + to_dir.to_vec2() - orth.to_vec2()) + .with_z(wall.base_alt - 1), + max: (max + orth.to_vec2()).with_z(room.bounds.min.z - 1), + })) + .fill(floor_fill.clone()); + } + } + } + for room in self.rooms.values() { + painter.aabb(aabb(room.bounds)).clear(); let room_aabr = Aabr { min: room.bounds.min.xy(), @@ -830,23 +1003,6 @@ impl Structure for Tavern { match room.kind { RoomKind::Garden => { let dir = Dir::from_vec2(room_aabr.size().into()); - - /* - painter - .aabb(aabb(Aabb { - min: dir - .select_aabr_with(room_aabr, room_aabr.min - 2) - .with_z(room.bounds.max.z + 1), - max: dir - .select_aabr_with(room_aabr, room_aabr.max + 2) - .with_z(room.bounds.max.z + 1), - })) - .repeat( - -dir.to_vec3() * 2, - (dir.select(room_aabr.size()) as u32 + 3) / 2, - ) - .fill(color1.clone()) - */ }, RoomKind::StageRoom => { for aabr in room.detail_areas.iter().copied() { @@ -942,13 +1098,13 @@ impl Structure for Tavern { .with_z(room.bounds.min.z), max: max.with_z(room.bounds.min.z), })) - .fill(color1.clone()); + .fill(wall_detail_fill.clone()); painter .aabb(aabb(Aabb { min: min.with_z(room.bounds.min.z + 3), max: max.with_z(room.bounds.max.z), })) - .fill(color1.clone()); + .fill(wall_detail_fill.clone()); }, (true, true) => { painter.sprite( @@ -966,13 +1122,13 @@ impl Structure for Tavern { min: aabr.min.with_z(room.bounds.min.z), max: aabr.max.with_z(room.bounds.min.z), })) - .fill(detail0.clone()); + .fill(detail_fill.clone()); painter .aabb(aabb(Aabb { min: (aabr.min + 1).with_z(room.bounds.min.z), max: (aabr.max - 1).with_z(room.bounds.min.z), })) - .fill(color0.clone()); + .fill(wall_fill.clone()); for dir in Dir::iter().filter(|dir| { dir.select_aabr(aabr) != dir.select_aabr(room_aabr) && dir.rotated_cw().select_aabr(aabr) @@ -984,7 +1140,7 @@ impl Structure for Tavern { ); painter .column(pos, room.bounds.min.z..=room.bounds.max.z) - .fill(color1.clone()); + .fill(wall_detail_fill.clone()); for dir in Dir::iter() { painter.rotated_sprite( @@ -1010,58 +1166,6 @@ impl Structure for Tavern { } } - let get_kind = |room| self.rooms.get(room).kind; - for wall in self.walls.values() { - let wall_aabb = Aabb { - min: wall.start.with_z(wall.base_alt), - max: wall.end.with_z(wall.top_alt), - }; - let wall_dir = Dir::from_vec2(wall.end - wall.start); - match (wall.from.map(get_kind), wall.to.map(get_kind)) { - (Some(RoomKind::Garden), Some(RoomKind::Garden) | None) - | (None, Some(RoomKind::Garden)) => { - let hgt = wall_aabb.min.z..=wall_aabb.max.z; - painter - .column(wall_aabb.min.xy(), hgt.clone()) - .fill(color1.clone()); - painter.column(wall_aabb.max.xy(), hgt).fill(color1.clone()); - let z = (wall.base_alt + wall.top_alt) / 2; - - painter.rotated_sprite( - wall_aabb.min.with_z(z) + wall_dir.to_vec2(), - SpriteKind::WallSconce, - wall_dir.sprite_ori(), - ); - painter.rotated_sprite( - wall_aabb.max.with_z(z) - wall_dir.to_vec2(), - SpriteKind::WallSconce, - wall_dir.opposite().sprite_ori(), - ); - painter - .aabb(aabb(Aabb { - min: wall_aabb.min, - max: wall_aabb.max.with_z(wall_aabb.min.z), - })) - .fill(color1.clone()); - painter - .aabb(aabb(Aabb { - min: wall_aabb.min.with_z(wall_aabb.max.z), - max: wall_aabb.max, - })) - .fill(color1.clone()); - }, - (None, None) => {}, - _ => { - painter.aabb(aabb(wall_aabb)).fill(color0.clone()); - painter - .column(wall.start, wall.base_alt..=wall.top_alt) - .fill(color1.clone()); - painter - .column(wall.end, wall.base_alt..=wall.top_alt) - .fill(color1.clone()); - }, - } - } for wall in self.walls.values() { let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = (wall.from.map(get_kind), wall.to.map(get_kind)) @@ -1119,41 +1223,35 @@ impl Structure for Tavern { min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1), max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3), })) - .fill(detail0.clone()); + .fill(detail_fill.clone()); painter .aabb(aabb(Aabb { min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt), max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2), })) .clear(); - let filter = |room: &Id| self.rooms[*room].bounds.min.z > wall.base_alt; - if let Some((room, to_dir)) = wall - .to - .filter(filter) - .zip(Some(wall.to_dir)) - .or(wall.from.filter(filter).zip(Some(-wall.to_dir))) - { - let room = &self.rooms[room]; - - let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1); + if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) { + // Place a ramp if the door is lower than the room alt. painter .ramp( aabb(Aabb { - min: (door.min - to_dir.to_vec2() * 3).with_z(wall.base_alt), + min: (min - to_dir.to_vec2() * 3).with_z(wall.base_alt), max: max.with_z(room.bounds.min.z + 2), }), to_dir, ) - .clear(); - painter - .ramp( - aabb(Aabb { - min: (door.min + to_dir.to_vec2() * 2).with_z(wall.base_alt), - max: max.with_z(room.bounds.min.z - 1), - }), - to_dir, + // TOOD: For zoomy worldgen, this a sheared aabb. + .without( + painter + .ramp( + aabb(Aabb { + min: (min + to_dir.to_vec2() * 2).with_z(wall.base_alt), + max: max.with_z(room.bounds.min.z - 1), + }), + to_dir, + ) ) - .fill(color2.clone()); + .clear(); } if let Some((in_dir, _room)) = in_dir_room { let sprite = match in_dir.rotated_cw().select(door.size()) { @@ -1197,36 +1295,5 @@ impl Structure for Tavern { } } } - - for stairs in self.stairs.values() { - let down_room = &self.rooms[stairs.in_room]; - let up_room = &self.rooms[stairs.to_room]; - - let down = -stairs.dir; - let right = stairs.dir.rotated_cw(); - - let aabr = Aabr { - min: stairs.end - right.to_vec2() - + down.to_vec2() * (up_room.bounds.min.z - 1 - down_room.bounds.min.z), - max: stairs.end + right.to_vec2(), - }; - - painter - .aabb(aabb(Aabb { - min: aabr.min.with_z(up_room.bounds.min.z - 1), - max: aabr.max.with_z(up_room.bounds.min.z - 1), - })) - .clear(); - - painter - .ramp( - aabb(Aabb { - min: aabr.min.with_z(down_room.bounds.min.z), - max: aabr.max.with_z(up_room.bounds.min.z - 1), - }), - stairs.dir, - ) - .fill(color0.clone()); - } } } From ee4c6d006f7d5b790a33270420423f912c265e49 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 6 Nov 2023 06:28:59 +0100 Subject: [PATCH 13/26] More advanced roofs --- world/src/site2/plot/tavern.rs | 368 +++++++++++++++++++++++++-------- 1 file changed, 280 insertions(+), 88 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 2242ae71ff..38c7ed2ebc 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -8,11 +8,8 @@ use common::{ }; use enum_map::EnumMap; use enumset::EnumSet; -use hashbrown::{HashMap, HashSet}; -use rand::{ - seq::{IteratorRandom, SliceRandom}, - Rng, SeedableRng, -}; +use hashbrown::HashSet; +use rand::{seq::IteratorRandom, Rng}; use strum::{EnumIter, IntoEnumIterator}; use vek::*; @@ -59,6 +56,21 @@ impl Wall { } } +#[derive(Copy, Clone)] +enum RoofStyle { + Flat, + FlatBars { dir: Dir }, + LeanTo { dir: Dir, max_z: i32 }, + Gable { dir: Dir, max_z: i32 }, + Hip { max_z: i32 }, +} + +struct Roof { + bounds: Aabr, + min_z: i32, + style: RoofStyle, +} + #[derive(Clone, Copy, EnumIter, enum_map::Enum)] enum RoomKind { Garden, @@ -99,6 +111,7 @@ pub struct Room { kind: RoomKind, // stairs: Option>, walls: EnumMap>>, + roofs: Vec>, // TODO: Remove this, used for debugging detail_areas: Vec>, pub details: Vec, @@ -109,17 +122,33 @@ impl Room { Self { bounds, kind, + roofs: Default::default(), walls: Default::default(), detail_areas: Default::default(), details: Default::default(), } } + + /// Are any of this rooms roofs fully covering it? + fn is_covered_by_roof(&self, roofs: &Store) -> bool { + let aabr = Aabr { + min: self.bounds.min.xy(), + max: self.bounds.max.xy(), + }; + for roof in self.roofs.iter() { + if roofs[*roof].bounds.contains_aabr(aabr) { + return true; + } + } + false + } } pub struct Tavern { name: String, pub rooms: Store, walls: Store, + roofs: Store, /// Tile position of the door tile pub door_tile: Vec2, pub door_wpos: Vec3, @@ -130,7 +159,7 @@ pub struct Tavern { impl Tavern { pub fn generate( land: &Land, - index: IndexRef, + _index: IndexRef, rng: &mut impl Rng, site: &Site, door_tile: Vec2, @@ -139,9 +168,9 @@ impl Tavern { ) -> Self { let name = namegen::NameGen::location(rng).generate_tavern(); - let start = std::time::Instant::now(); let mut rooms = Store::default(); let mut walls = Store::default(); + let mut roofs = Store::default(); let mut room_counts = EnumMap::::default(); let bounds = Aabr { @@ -258,14 +287,7 @@ impl Tavern { room_metas.push(RoomMeta { id: entrance_id, - walls: Dir::iter() - .filter(|d| *d != door_dir) - // .map(|d| { - // let a = d.rotated_cw().select_aabr(entrance_room_aabr); - // let b = d.rotated_ccw().select_aabr(entrance_room_aabr); - // (d, a.min(b)..=a.max(b)) - // }) - .collect(), + walls: Dir::iter().filter(|d| *d != door_dir).collect(), }); room_counts[entrance_room] += 1; @@ -281,11 +303,13 @@ impl Tavern { max: aabr.max + amount, }; 'room_gen: while room_metas.len() > 0 { + // Continue extending from a random existing room let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len())); if room_meta.walls.is_empty() { continue 'room_gen; } + // Pick a direction to choose from let Some(in_dir) = room_meta.walls.into_iter().choose(rng) else { continue 'room_gen; }; @@ -297,6 +321,7 @@ impl Tavern { let from_id = room_meta.id; let from_room = &rooms[from_id]; + // If there are more directions to continue from, push this room again. if !room_meta.walls.is_empty() { room_metas.push(room_meta); } @@ -309,7 +334,7 @@ impl Tavern { max: in_dir.select_aabr_with(ibounds, ibounds.max), } .made_valid(); - // Height of the new room + // Pick a height of the new room let room_hgt = rng.gen_range(3..=5); let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32; let max_stair_length = (in_dir.select(if wanted_alt < from_room.bounds.min.z { @@ -409,7 +434,7 @@ impl Tavern { // Pick a room. let room_lottery = Lottery::from(room_lottery); - let room = *room_lottery.choose_seeded(rng.gen()); + let room_kind = *room_lottery.choose_seeded(rng.gen()); // Select a door position let mut min = left @@ -428,7 +453,8 @@ impl Tavern { let in_pos = in_dir.select_aabr_with(from_bounds, Vec2::broadcast(in_pos)) + in_dir.to_vec2(); - let Some(bounds) = place_room_in(room, max_bounds, in_dir, in_pos, rng) else { + // Place the room in the given max bounds + let Some(bounds) = place_room_in(room_kind, max_bounds, in_dir, in_pos, rng) else { continue 'room_gen; }; @@ -436,7 +462,7 @@ impl Tavern { min: bounds.min.with_z(alt), max: bounds.max.with_z(alt + room_hgt), }; - let id = rooms.insert(Room::new(bounds3, room)); + let id = rooms.insert(Room::new(bounds3, room_kind)); let start = in_dir.select_aabr_with( from_bounds, @@ -476,7 +502,7 @@ impl Tavern { id, walls: Dir::iter().filter(|d| *d != -in_dir).collect(), }); - room_counts[room] += 1; + room_counts[room_kind] += 1; } // Place walls where needed. @@ -643,6 +669,7 @@ impl Tavern { .collect::>(); let mut x = bounds.min.x; + // Basically greedy meshing, but for aabrs while x <= bounds.max.x { let mut y = bounds.min.y; 'y_loop: while y <= bounds.max.y { @@ -759,11 +786,139 @@ impl Tavern { } } - println!("GENERATION TIME: {}μs", start.elapsed().as_micros()); + for room_id in rooms.ids() { + let room = &rooms[room_id]; + // If a room is already fully covered by a roof, we skip it. + if room.is_covered_by_roof(&roofs) { + continue; + } + let roof_min_z = room.bounds.max.z + 1; + let mut roof_bounds = to_aabr(room.bounds); + roof_bounds.min -= 2; + roof_bounds.max += 2; + let mut dirs = Vec::from(Dir::ALL); + + let mut over_rooms = vec![room_id]; + // Extend roof over adjecent rooms. + while !dirs.is_empty() { + let dir = dirs.swap_remove(rng.gen_range(0..dirs.len())); + let orth = dir.orthogonal(); + // Check for room intersections in this direction. + for (room_id, room) in rooms.iter() { + let room_aabr = to_aabr(room.bounds); + if room.bounds.max.z == roof_min_z + && dir.select_aabr(roof_bounds) + dir.signum() + == (-dir).select_aabr(room_aabr) + && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2 + && (-orth).select_aabr(roof_bounds) >= (-orth).select_aabr(room_aabr) - 2 + { + // If the room we found is fully covered by a roof already, we don't go in + // this direction. + if room.is_covered_by_roof(&roofs) { + break; + } + roof_bounds = dir.extend_aabr(roof_bounds, dir.select(room_aabr.size())); + dirs.push(dir); + over_rooms.push(room_id); + break; + } + } + } + + // Build a lottery of valid roofs to pick from + let mut valid_styles = vec![(0.5, RoofStyle::Flat)]; + + let gardens = over_rooms + .iter() + .filter(|id| matches!(rooms[**id].kind, RoomKind::Garden)) + .count(); + + // If we just have gardens, we can use FlatBars style. + if gardens == over_rooms.len() { + let ratio = Dir::X.select(roof_bounds.size()) as f32 + / Dir::Y.select(roof_bounds.size()) as f32; + valid_styles.extend([ + (5.0 * ratio, RoofStyle::FlatBars { dir: Dir::X }), + (5.0 / ratio, RoofStyle::FlatBars { dir: Dir::Y }), + ]); + } + + // Find heights of possible adjecent rooms. + let mut dir_zs = EnumMap::default(); + for dir in Dir::iter() { + let orth = dir.orthogonal(); + for room in rooms.values() { + let room_aabr = to_aabr(room.bounds); + if room.bounds.max.z > roof_min_z + && dir.select_aabr(roof_bounds) == (-dir).select_aabr(room_aabr) + && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2 + && (-orth).select_aabr(roof_bounds) >= (-orth).select_aabr(room_aabr) - 2 + { + dir_zs[dir] = Some(room.bounds.max.z); + break; + } + } + } + + for dir in [Dir::X, Dir::Y] { + if dir_zs[dir.orthogonal()].is_none() && dir_zs[-dir.orthogonal()].is_none() { + let max_z = + roof_min_z + (dir.orthogonal().select(roof_bounds.size()) / 2 - 1).min(7); + let max_z = match (dir_zs[dir], dir_zs[-dir]) { + (Some(a), Some(b)) => { + if a.min(b) >= roof_min_z + 3 { + max_z.min(a.min(b)) + } else { + max_z + } + }, + (None, None) => max_z, + _ => continue, + }; + + for max_z in roof_min_z + 3..=max_z { + valid_styles.push((1.0, RoofStyle::Gable { dir, max_z })) + } + } + } + + for dir in Dir::iter() { + if let (Some(h), None) = (dir_zs[dir], dir_zs[-dir]) { + for max_z in roof_min_z + 2..=h { + valid_styles.push((1.0, RoofStyle::LeanTo { dir, max_z })) + } + } + } + + if Dir::iter().all(|d| dir_zs[d].is_none()) { + for max_z in roof_min_z + 3..=roof_min_z + 7 { + valid_styles.push((0.8, RoofStyle::Hip { max_z })) + } + } + + let style_lottery = Lottery::from(valid_styles); + + debug_assert!( + roof_bounds.is_valid(), + "Roof bounds aren't valid: {:?}", + roof_bounds + ); + let roof_id = roofs.insert(Roof { + bounds: roof_bounds, + min_z: roof_min_z, + style: *style_lottery.choose_seeded(rng.gen()), + }); + + for room_id in over_rooms { + rooms[room_id].roofs.push(roof_id); + } + } + Self { name, rooms, walls, + roofs, door_tile, door_wpos, bounds, @@ -825,85 +980,124 @@ impl Structure for Tavern { }) }; - enum RoofStyle { - Flat, - FlatBars(Dir), - LeanTo(Dir), - Gable(Dir), - Hip, - } - struct Roof { - bounds: Aabb, - style: RoofStyle, - } - let mut roof = Store::default(); - - let s = [field.get(self.door_wpos); 8]; - let rng = &mut rand_chacha::ChaChaRng::from_seed(unsafe { std::mem::transmute(s) }); - // let mut room_roofs = HashMap::new(); - - for room in self.rooms.values() { - match room.kind { - RoomKind::Garden => { - let dir = *[Dir::X, Dir::Y].choose(rng).expect("We have 2 elements"); - let orth = dir.orthogonal(); - roof.insert(Roof { - bounds: Aabb { - min: (room.bounds.min - 1 - orth.to_vec2()) - .with_z(room.bounds.max.z + 1), - max: (room.bounds.max + 1 + orth.to_vec2()) - .with_z(room.bounds.max.z + 1), - }, - style: RoofStyle::FlatBars(dir), - }); - }, - _ => { - roof.insert(Roof { - bounds: Aabb { - min: (room.bounds.min - 2).with_z(room.bounds.max.z + 1), - max: (room.bounds.max + 2).with_z(room.bounds.max.z + 5), - }, - style: RoofStyle::Hip, - }); - }, - } - } - - for roof in roof.values() { - let roof_aabr = Aabr { - min: roof.bounds.min.xy(), - max: roof.bounds.max.xy(), - }; - match &roof.style { + for roof in self.roofs.values() { + match roof.style { RoofStyle::Flat => { - painter.aabb(aabb(roof.bounds)).fill(roof_fill.clone()); + painter + .aabb(aabb(Aabb { + min: roof.bounds.min.with_z(roof.min_z), + max: roof.bounds.max.with_z(roof.min_z), + })) + .fill(roof_fill.clone()); }, - RoofStyle::FlatBars(dir) => painter + RoofStyle::FlatBars { dir } => painter .aabb(aabb(Aabb { min: dir - .select_aabr_with(roof_aabr, roof_aabr.min) - .with_z(roof.bounds.max.z), + .select_aabr_with(roof.bounds, roof.bounds.min) + .with_z(roof.min_z), max: dir - .select_aabr_with(roof_aabr, roof_aabr.max) - .with_z(roof.bounds.max.z), + .select_aabr_with(roof.bounds, roof.bounds.max) + .with_z(roof.min_z), })) .repeat( -dir.to_vec3() * 2, - (dir.select(roof_aabr.size()) as u32 + 3) / 2, + (dir.select(roof.bounds.size()) as u32 + 3) / 2, ) .fill(roof_fill.clone()), - RoofStyle::LeanTo(dir) => { + RoofStyle::LeanTo { dir, max_z } => { painter - .ramp(aabb(roof.bounds), *dir) + .aabb(aabb(Aabb { + min: roof.bounds.min.with_z(roof.min_z), + max: roof.bounds.max.with_z(roof.min_z), + })) .fill(roof_fill.clone()); - }, - RoofStyle::Gable(dir) => { painter - .gable(roof.bounds, roof.bounds.size().d, *dir) + .ramp( + aabb(Aabb { + min: roof.bounds.min.with_z(roof.min_z), + max: roof.bounds.max.with_z(max_z), + }), + dir, + ) .fill(roof_fill.clone()); + for d in [dir.orthogonal(), -dir.orthogonal()] { + painter + .ramp( + aabb(Aabb { + min: (d.select_aabr_with(roof.bounds, roof.bounds.min) + - d.to_vec2()) + .with_z(roof.min_z - 1), + max: (d.select_aabr_with(roof.bounds, roof.bounds.max) + - d.to_vec2()) + .with_z(max_z - 1), + }), + dir, + ) + .fill(wall_fill.clone()); + painter + .ramp( + aabb(Aabb { + min: d + .select_aabr_with(roof.bounds, roof.bounds.min) + .with_z(roof.min_z - 1), + max: d + .select_aabr_with(roof.bounds, roof.bounds.max) + .with_z(max_z - 1), + }), + dir, + ) + .clear(); + } }, - RoofStyle::Hip => { - painter.pyramid(aabb(roof.bounds)).fill(roof_fill.clone()); + RoofStyle::Gable { dir, max_z } => { + painter + .gable( + aabb(Aabb { + min: roof.bounds.min.with_z(roof.min_z), + max: roof.bounds.max.with_z(max_z), + }), + max_z - roof.min_z + 1, + dir, + ) + .fill(roof_fill.clone()); + for dir in [dir, -dir] { + painter + .gable( + aabb(Aabb { + min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1) + - dir.to_vec2()) + .with_z(roof.min_z), + max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1) + - dir.to_vec2()) + .with_z(max_z - 1), + }), + max_z - roof.min_z, + dir, + ) + .fill(wall_fill.clone()); + painter + .gable( + aabb(Aabb { + min: dir + .select_aabr_with(roof.bounds, roof.bounds.min + 1) + .with_z(roof.min_z), + max: dir + .select_aabr_with(roof.bounds, roof.bounds.max - 1) + .with_z(max_z - 1), + }), + max_z - roof.min_z, + dir, + ) + .clear(); + } + }, + RoofStyle::Hip { max_z } => { + painter + .pyramid(aabb(Aabb { + min: roof.bounds.min.with_z(roof.min_z), + max: roof.bounds.max.with_z(max_z), + })) + .fill(roof_fill.clone()); }, } } @@ -1001,9 +1195,7 @@ impl Structure for Tavern { max: room.bounds.max.xy(), }; match room.kind { - RoomKind::Garden => { - let dir = Dir::from_vec2(room_aabr.size().into()); - }, + RoomKind::Garden => {}, RoomKind::StageRoom => { for aabr in room.detail_areas.iter().copied() { for dir in Dir::iter().filter(|dir| { From 7a1612aa8a080676ed31154a26446d218063fbe8 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 6 Nov 2023 07:22:41 +0100 Subject: [PATCH 14/26] small fixes --- world/src/site2/mod.rs | 19 +++++++------ world/src/site2/plot/tavern.rs | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 9746cce08c..4c252a0d56 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -593,12 +593,12 @@ impl Site { site.make_plaza(land, &mut rng); let build_chance = Lottery::from(vec![ - // (64.0, 1), - // (5.0, 2), - // (8.0, 3), - // (5.0, 4), - // (5.0, 5), - // (15.0, 6), + (64.0, 1), + (5.0, 2), + (8.0, 3), + (5.0, 4), + (5.0, 5), + (15.0, 6), (15.0, 7), ]); @@ -607,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 @@ -924,7 +926,7 @@ impl Site { } } }, - 7 if size > 0.125 => { + 7 if (size > 0.125 && taverns < 2) => { let size = (3.5 + rng.gen::().powf(5.0) * 2.0).round() as u32; if let Some((aabr, door_tile, door_dir)) = attempt(32, || { site.find_roadside_aabr( @@ -955,7 +957,8 @@ impl Site { plot: Some(plot), hard_alt: Some(tavern_alt), }); - workshops += 1; + + taverns += 1; } else { site.make_plaza(land, &mut rng); } diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 38c7ed2ebc..46b2515a2b 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -1075,6 +1075,58 @@ impl Structure for Tavern { dir, ) .fill(wall_fill.clone()); + painter + .aabb(aabb(Aabb { + min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1) + - dir.to_vec2()) + .with_z(roof.min_z), + max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1) + - dir.to_vec2()) + .with_z(roof.min_z), + })) + .fill(wall_detail_fill.clone()); + let center_bounds = Aabr { + min: (dir.select_aabr_with(roof.bounds, roof.bounds.center()) + - dir.to_vec2()), + max: (dir.select_aabr_with( + roof.bounds, + (roof.bounds.min + roof.bounds.max + 1) / 2, + ) - dir.to_vec2()), + }; + painter + .aabb(aabb(Aabb { + min: center_bounds.min.with_z(roof.min_z), + max: center_bounds.max.with_z(max_z - 1), + })) + .fill(wall_detail_fill.clone()); + for d in [dir.orthogonal(), -dir.orthogonal()] { + let hgt = max_z - roof.min_z; + let half_size = d.select(roof.bounds.size() + 1) / 2; + let e = half_size - hgt + 1; + let e = e - e % 2; + let f = half_size - e; + let hgt = (hgt - 1).min(e - f % 2) - (d.signum() - 1) / 2; + let mut aabr = Aabr { + min: d.select_aabr_with(center_bounds, center_bounds.min), + max: d.select_aabr_with(center_bounds, center_bounds.max) + + d.to_vec2() * hgt, + } + .made_valid(); + aabr.max += 1; + painter + .plane( + aabr, + aabr.min + .with_z(if d.signum() < 0 { + roof.min_z + hgt + } else { + roof.min_z + }) + .as_(), + d.to_vec2().as_(), + ) + .fill(wall_detail_fill.clone()); + } painter .gable( aabb(Aabb { From 96a70d760ce351a66d3e1fb3b85ffe7f083abc71 Mon Sep 17 00:00:00 2001 From: Isse Date: Mon, 6 Nov 2023 07:30:36 +0100 Subject: [PATCH 15/26] clippyyyyy --- rtsim/src/rule/npc_ai.rs | 8 +++++--- world/src/site2/plot/tavern.rs | 8 +++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 41bea4d61d..eedc7796c3 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -325,7 +325,9 @@ impl Rule for NpcAi { } } -fn idle() -> impl Action + Clone { just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") } +fn idle() -> impl Action + Clone { + just(|ctx, _| ctx.controller.do_idle()).debug(|| "idle") +} /// Try to walk toward a 3D position without caring for obstacles. fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32) -> impl Action { @@ -914,9 +916,9 @@ fn villager(visiting_site: SiteId) -> impl Action { _ => 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 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())), diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 46b2515a2b..eab8b081e3 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -302,7 +302,7 @@ impl Tavern { min: aabr.min - amount, max: aabr.max + amount, }; - 'room_gen: while room_metas.len() > 0 { + 'room_gen: while !room_metas.is_empty() { // Continue extending from a random existing room let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len())); if room_meta.walls.is_empty() { @@ -683,10 +683,8 @@ impl Tavern { continue 'y_loop; } - if contains_x && min.y < area.min.y { - if area.min.y - 1 < max_y { - max_y = area.min.y - 1; - } + if contains_x && min.y < area.min.y && area.min.y - 1 < max_y { + max_y = area.min.y - 1; } } From 2097a499c7a1c0de5f4e001297c64388f3a24381 Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 26 Nov 2023 01:00:07 +0100 Subject: [PATCH 16/26] cancel walking on some inputs --- voxygen/src/session/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 1a1bbe3aaf..bcca8a1e06 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -686,6 +686,8 @@ impl PlayState for SessionState { // Throw out distance info, it will be useful in the future self.target_entity = entity_target.map(|t| t.kind.0); + let mut stop_walk = false; + // Handle window events. for event in events { // Pass all events to the ui first. @@ -712,6 +714,7 @@ impl PlayState for SessionState { } match input { GameInput::Primary => { + stop_walk = true; let mut client = self.client.borrow_mut(); // Mine and build targets can be the same block. make building // take precedence. @@ -730,6 +733,7 @@ impl PlayState for SessionState { } }, GameInput::Secondary => { + stop_walk = true; 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) @@ -749,6 +753,7 @@ impl PlayState for SessionState { } }, GameInput::Block => { + stop_walk = true; self.client.borrow_mut().handle_input( InputKind::Block, state, @@ -757,6 +762,7 @@ impl PlayState for SessionState { ); }, GameInput::Roll => { + stop_walk = true; let mut client = self.client.borrow_mut(); if can_build { if state { @@ -781,12 +787,14 @@ impl PlayState for SessionState { } }, GameInput::Respawn => { + stop_walk = true; self.stop_auto_walk(); if state { self.client.borrow_mut().respawn(); } }, GameInput::Jump => { + stop_walk = true; self.client.borrow_mut().handle_input( InputKind::Jump, state, @@ -849,6 +857,7 @@ impl PlayState for SessionState { self.key_state.right = state }, GameInput::Glide => { + stop_walk = true; let is_trading = self.client.borrow().is_trading(); if state && !is_trading { if global_state.settings.gameplay.stop_auto_walk_on_input { @@ -879,7 +888,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) { + stop_walk = true; + } + client.toggle_wield(); } }, GameInput::SwapLoadout => { @@ -1441,6 +1454,13 @@ impl PlayState for SessionState { } } + if stop_walk && self.walking_speed { + self.walking_speed = false; + self.hud.walking_speed(false); + + self.key_state.speed_mul = 1.0; + } + // Recompute dependents just in case some input modified the camera self.scene .camera_mut() From b60ebd4131da987b9438102f16530206a20035ec Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 26 Nov 2023 12:56:54 +0100 Subject: [PATCH 17/26] don't show red text when walking --- assets/voxygen/i18n/en/hud/misc.ftl | 1 - voxygen/src/hud/mod.rs | 23 ------------------ voxygen/src/session/mod.rs | 36 ++++++++++------------------- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index 9f80fbb611..e1307e867e 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -36,7 +36,6 @@ hud-diary = Diary hud-free_look_indicator = Free look active. Press { $key } to disable. hud-camera_clamp_indicator = Camera vertical clamp active. Press { $key } to disable. hud-auto_walk_indicator = Auto walk/swim active -hud-walking_speed_indicator = Walking speed active hud-zoom_lock_indicator-remind = Zoom locked hud-zoom_lock_indicator-enable = Camera zoom locked hud-zoom_lock_indicator-disable = Camera zoom unlocked diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index ebe20dbb65..8d43e185e3 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -913,7 +913,6 @@ pub struct Show { stats: bool, free_look: bool, auto_walk: bool, - walking_speed: bool, zoom_lock: ChangeNotification, camera_clamp: bool, prompt_dialog: Option, @@ -1421,7 +1420,6 @@ impl Hud { stats: false, free_look: false, auto_walk: false, - walking_speed: false, zoom_lock: ChangeNotification::default(), camera_clamp: false, prompt_dialog: None, @@ -3822,23 +3820,6 @@ impl Hud { .set(self.ids.auto_walk_txt, ui_widgets); } - // Walking speed indicator - if self.show.walking_speed { - Text::new(&i18n.get_msg("hud-walking_speed_indicator")) - .color(TEXT_BG) - .mid_top_with_margin_on(ui_widgets.window, indicator_offset) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(20)) - .set(self.ids.walking_speed_bg, ui_widgets); - indicator_offset += 30.0; - Text::new(&i18n.get_msg("hud-walking_speed_indicator")) - .color(KILL_COLOR) - .top_left_with_margins_on(self.ids.walking_speed_bg, -1.0, -1.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(20)) - .set(self.ids.walking_speed_txt, ui_widgets); - } - // Camera zoom lock self.show.zoom_lock.update(dt); @@ -4939,10 +4920,6 @@ impl Hud { pub fn auto_walk(&mut self, auto_walk: bool) { self.show.auto_walk = auto_walk; } - pub fn walking_speed(&mut self, walking_speed: bool) { - self.show.walking_speed = walking_speed; - } - pub fn camera_clamp(&mut self, camera_clamp: bool) { self.show.camera_clamp = camera_clamp; } /// Remind the player camera zoom is currently locked, for example if they diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index bcca8a1e06..41dc581495 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -686,8 +686,6 @@ impl PlayState for SessionState { // Throw out distance info, it will be useful in the future self.target_entity = entity_target.map(|t| t.kind.0); - let mut stop_walk = false; - // Handle window events. for event in events { // Pass all events to the ui first. @@ -714,7 +712,7 @@ impl PlayState for SessionState { } match input { GameInput::Primary => { - stop_walk = true; + self.walking_speed = false; let mut client = self.client.borrow_mut(); // Mine and build targets can be the same block. make building // take precedence. @@ -733,7 +731,7 @@ impl PlayState for SessionState { } }, GameInput::Secondary => { - stop_walk = true; + 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) @@ -753,7 +751,7 @@ impl PlayState for SessionState { } }, GameInput::Block => { - stop_walk = true; + self.walking_speed = false; self.client.borrow_mut().handle_input( InputKind::Block, state, @@ -762,7 +760,7 @@ impl PlayState for SessionState { ); }, GameInput::Roll => { - stop_walk = true; + self.walking_speed = false; let mut client = self.client.borrow_mut(); if can_build { if state { @@ -787,14 +785,14 @@ impl PlayState for SessionState { } }, GameInput::Respawn => { - stop_walk = true; + self.walking_speed = false; self.stop_auto_walk(); if state { self.client.borrow_mut().respawn(); } }, GameInput::Jump => { - stop_walk = true; + self.walking_speed = false; self.client.borrow_mut().handle_input( InputKind::Jump, state, @@ -857,7 +855,7 @@ impl PlayState for SessionState { self.key_state.right = state }, GameInput::Glide => { - stop_walk = true; + 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 { @@ -890,7 +888,7 @@ impl PlayState for SessionState { if state { let mut client = self.client.borrow_mut(); if client.is_wielding().is_some_and(|b| !b) { - stop_walk = true; + self.walking_speed = false; } client.toggle_wield(); } @@ -1230,20 +1228,11 @@ impl PlayState for SessionState { } }, GameInput::ToggleWalk if state => { - let hud = &mut self.hud; global_state .settings .gameplay .walking_speed_behavior - .update(state, &mut self.walking_speed, |b| { - hud.walking_speed(b) - }); - - self.key_state.speed_mul = if self.walking_speed { - global_state.settings.gameplay.walking_speed - } else { - 1.0 - }; + .update(state, &mut self.walking_speed, |_| {}); }, _ => {}, } @@ -1454,10 +1443,9 @@ impl PlayState for SessionState { } } - if stop_walk && self.walking_speed { - self.walking_speed = false; - self.hud.walking_speed(false); - + if self.walking_speed { + self.key_state.speed_mul = global_state.settings.gameplay.walking_speed; + } else { self.key_state.speed_mul = 1.0; } From 1000b7b73f20d675a11ec822ac4f27b60d42d2bc Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 26 Nov 2023 15:18:58 +0100 Subject: [PATCH 18/26] better colors for tavern --- world/src/site2/plot/tavern.rs | 88 ++++++++++++++++++++++++---------- world/src/util/random.rs | 10 ++++ 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index eab8b081e3..6c0640049a 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -4,7 +4,7 @@ use common::{ comp::Content, lottery::Lottery, store::{Id, Store}, - terrain::{Block, BlockKind, SpriteCfg, SpriteKind}, + terrain::{BlockKind, SpriteCfg, SpriteKind}, }; use enum_map::EnumMap; use enumset::EnumSet; @@ -16,7 +16,7 @@ use vek::*; use crate::{ site::namegen, site2::{gen::PrimitiveTransform, Dir, Fill, Site, Structure}, - util::{RandomField, Sampler}, + util::RandomField, IndexRef, Land, }; @@ -112,7 +112,6 @@ pub struct Room { // stairs: Option>, walls: EnumMap>>, roofs: Vec>, - // TODO: Remove this, used for debugging detail_areas: Vec>, pub details: Vec, } @@ -940,28 +939,61 @@ impl Structure for Tavern { const DOWN: i32 = 6; - const PALETTES: [[Rgb; 5]; 2] = [ - [ + let mut offset = 0; + let mut choose = |slice: &[Rgb]| -> Rgb { + offset += 1; + *field.choose(self.door_wpos + offset, slice) + }; + + let detail_fill = Fill::Brick( + BlockKind::Rock, + choose(&[ Rgb::new(55, 65, 64), - Rgb::new(220, 53, 34), - Rgb::new(108, 100, 79), - Rgb::new(42, 44, 43), - Rgb::new(30, 30, 32), - ], - [ - Rgb::new(46, 62, 215), + Rgb::new(46, 62, 100), + Rgb::new(46, 100, 62), + Rgb::new(100, 100, 105), + ]), + 15, + ); + let wall_fill = Fill::Brick( + BlockKind::Wood, + choose(&[ + Rgb::new(160, 53, 34), Rgb::new(147, 51, 29), - Rgb::new(200, 200, 200), - Rgb::new(56, 18, 0), - Rgb::new(252, 100, 113), - ], - ]; - let palette = PALETTES[field.get(self.door_wpos) as usize % PALETTES.len()]; - let detail_fill = Fill::Brick(BlockKind::Rock, palette[0], 10); - let wall_fill = Fill::Block(Block::new(BlockKind::Wood, palette[1])); - let wall_detail_fill = Fill::Block(Block::new(BlockKind::Wood, palette[2])); - let floor_fill = Fill::Block(Block::new(BlockKind::Wood, palette[3])); - let roof_fill = Fill::Block(Block::new(BlockKind::Wood, palette[4])); + Rgb::new(147, 101, 69), + Rgb::new(90, 90, 95), + Rgb::new(170, 140, 52), + ]), + 20, + ); + let wall_detail_fill = Fill::Brick( + BlockKind::Wood, + choose(&[Rgb::new(108, 100, 79), Rgb::new(150, 150, 150)]), + 25, + ); + let floor_fill = Fill::Brick( + BlockKind::Wood, + choose(&[Rgb::new(42, 44, 43), Rgb::new(56, 18, 10)]), + 10, + ); + let roof_fill = Fill::Brick( + BlockKind::Wood, + choose(&[ + Rgb::new(21, 43, 48), + Rgb::new(11, 23, 38), + Rgb::new(45, 28, 21), + Rgb::new(10, 55, 40), + Rgb::new(5, 35, 15), + Rgb::new(40, 5, 11), + Rgb::new(55, 45, 11), + ]), + 20, + ); + let simple_roof_fill = Fill::Brick( + BlockKind::Wood, + choose(&[Rgb::new(106, 73, 64), Rgb::new(85, 52, 43)]), + 20, + ); let get_kind = |room| self.rooms.get(room).kind; let get_door_stair = |wall: &Wall, door: Aabr| { @@ -1001,7 +1033,7 @@ impl Structure for Tavern { -dir.to_vec3() * 2, (dir.select(roof.bounds.size()) as u32 + 3) / 2, ) - .fill(roof_fill.clone()), + .fill(simple_roof_fill.clone()), RoofStyle::LeanTo { dir, max_z } => { painter .aabb(aabb(Aabb { @@ -1462,10 +1494,16 @@ impl Structure for Tavern { let orth = wall.to_dir.orthogonal(); painter .aabb(aabb(Aabb { - min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1), + min: (door.min - orth.to_vec2()).with_z(wall.base_alt), max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3), })) .fill(detail_fill.clone()); + painter + .aabb(aabb(Aabb { + min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1), + max: (door.max + orth.to_vec2()).with_z(wall.base_alt - 1), + })) + .fill(floor_fill.clone()); painter .aabb(aabb(Aabb { min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt), diff --git a/world/src/util/random.rs b/world/src/util/random.rs index 54aacf42ee..00e05bf1fb 100644 --- a/world/src/util/random.rs +++ b/world/src/util/random.rs @@ -15,6 +15,16 @@ impl RandomField { pub fn get_f32(&self, pos: Vec3) -> f32 { (self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) } + + /// # Panics + /// Panics if the slice is empty. + pub fn choose<'a, T>(&self, pos: Vec3, slice: &'a [T]) -> &'a T { + assert!(!slice.is_empty()); + + let i = self.get(pos) as usize; + + &slice[i % slice.len()] + } } impl Sampler<'static> for RandomField { From 110e36e468ec1af06bffd85ce172496247420a4f Mon Sep 17 00:00:00 2001 From: Isse Date: Sun, 26 Nov 2023 15:25:44 +0100 Subject: [PATCH 19/26] add to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a9a4fb98..d460eae9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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@`, with uid: `uid@`. - 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 From fbd72f7400fa277d1eabb5a7cd98d6aa44f059c9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 29 Nov 2023 17:00:33 +0000 Subject: [PATCH 20/26] Better walking animation --- voxygen/anim/src/character/mod.rs | 4 ++-- voxygen/anim/src/character/run.rs | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/voxygen/anim/src/character/mod.rs b/voxygen/anim/src/character/mod.rs index 945d253008..661b6e97c4 100644 --- a/voxygen/anim/src/character/mod.rs +++ b/voxygen/anim/src/character/mod.rs @@ -433,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; diff --git a/voxygen/anim/src/character/run.rs b/voxygen/anim/src/character/run.rs index acd671cd97..a77c5cd096 100644 --- a/voxygen/anim/src/character/run.rs +++ b/voxygen/anim/src/character/run.rs @@ -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( From 57bf7cfdace034001c31667119ff7ccf740e75ec Mon Sep 17 00:00:00 2001 From: Isse Date: Thu, 7 Dec 2023 22:21:34 +0100 Subject: [PATCH 21/26] place rooms at correct alt, remove walls between gardens --- world/src/site2/plot/tavern.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 6c0640049a..e9dede1c51 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -335,7 +335,7 @@ impl Tavern { .made_valid(); // Pick a height of the new room let room_hgt = rng.gen_range(3..=5); - let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32; + let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32 + 1; let max_stair_length = (in_dir.select(if wanted_alt < from_room.bounds.min.z { from_bounds.size() } else { @@ -1199,8 +1199,7 @@ impl Structure for Tavern { }; let wall_dir = Dir::from_vec2(wall.end - wall.start); match (wall.from.map(get_kind), wall.to.map(get_kind)) { - (Some(RoomKind::Garden), Some(RoomKind::Garden) | None) - | (None, Some(RoomKind::Garden)) => { + (Some(RoomKind::Garden), None) | (None, Some(RoomKind::Garden)) => { let hgt = wall_aabb.min.z..=wall_aabb.max.z; painter .column(wall_aabb.min.xy(), hgt.clone()) @@ -1240,6 +1239,15 @@ impl Structure for Tavern { })) .fill(wall_detail_fill.clone()); }, + (Some(RoomKind::Garden), Some(RoomKind::Garden)) => { + painter + .aabb(aabb(Aabb { + min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN), + max: wall_aabb.max.with_z(wall_aabb.min.z - 1), + })) + .fill(floor_fill.clone()); + painter.aabb(aabb(wall_aabb)).clear(); + }, (None, None) => {}, _ => { painter @@ -1441,9 +1449,8 @@ impl Structure for Tavern { } for wall in self.walls.values() { - let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = - (wall.from.map(get_kind), wall.to.map(get_kind)) - { + let kinds = (wall.from.map(get_kind), wall.to.map(get_kind)); + let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = kinds { let in_dir = if to.is_none() { -wall.to_dir } else { @@ -1490,7 +1497,7 @@ impl Structure for Tavern { }, } } - if let Some(door) = wall.door_bounds() { + if let Some(door) = wall.door_bounds() && !matches!(kinds, (Some(RoomKind::Garden), Some(RoomKind::Garden))) { let orth = wall.to_dir.orthogonal(); painter .aabb(aabb(Aabb { From 5361b290df0081c91af7c6a2d8b2e689f7ba536c Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 12 Dec 2023 19:25:06 +0100 Subject: [PATCH 22/26] make vec2 make more sense --- world/src/site2/plot/tavern.rs | 2 +- world/src/site2/util/mod.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index e9dede1c51..e647626dab 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -1390,7 +1390,7 @@ impl Structure for Tavern { }, (true, true) => { painter.sprite( - dir.vec2(edge, rot_edge).with_z(room.bounds.min.z), + dir.abs().vec2(edge, rot_edge).with_z(room.bounds.min.z), SpriteKind::CookingPot, ); }, diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs index 83491664cf..a57542c931 100644 --- a/world/src/site2/util/mod.rs +++ b/world/src/site2/util/mod.rs @@ -110,12 +110,14 @@ 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 { match self { Dir::X => Vec2::new(x, y), - Dir::NegX => Vec2::new(x, y), + Dir::NegX => Vec2::new(-x, -y), Dir::Y => Vec2::new(y, x), - Dir::NegY => Vec2::new(y, x), + Dir::NegY => Vec2::new(-y, -x), } } From 850112bd723a742b26a4f8cfa5e666f988838d35 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 12 Dec 2023 19:28:12 +0100 Subject: [PATCH 23/26] typo fix and add some adjectives to tavern namegen --- world/src/site/namegen.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/world/src/site/namegen.rs b/world/src/site/namegen.rs index 5ea1c7605f..b1911fdbe4 100644 --- a/world/src/site/namegen.rs +++ b/world/src/site/namegen.rs @@ -711,7 +711,7 @@ impl<'a, R: Rng> NameGen<'a, R> { "Sad", "Glad", "Scared", - "Emberrassed", + "Embarrassed", "Goofy", "Spicy", "Salty", @@ -765,6 +765,11 @@ impl<'a, R: Rng> NameGen<'a, R> { "Zesty", "Fancy", "Stylish", + "Thirsty", + "Dry", + "Dancing", + "Singing", + "Drunken", ]; let tavern_synonyms = ["Tavern", "Bar", "Pub"]; let subjectives = [ From 8569f3033629426c4803cc2e0a6aadfd6e320584 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 12 Dec 2023 19:41:40 +0100 Subject: [PATCH 24/26] review fixes --- common/src/comp/character_state.rs | 7 ++++--- common/systems/src/mount.rs | 7 +++++-- server/agent/src/action_nodes.rs | 22 +++++----------------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 82c1930b9d..1854ed5c00 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -986,9 +986,10 @@ pub struct CharacterActivity { /// `None` means that the look direction should be derived from the /// orientation pub look_dir: Option, - /// If the character is using a Helm, this is set to Some, with the y - /// direction we're steering. - pub steer_dir: Option, + /// 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, diff --git a/common/systems/src/mount.rs b/common/systems/src/mount.rs index c4a160efeb..ed1b7f3c46 100644 --- a/common/systems/src/mount.rs +++ b/common/systems/src/mount.rs @@ -179,8 +179,11 @@ 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) { - character_activity.steer_dir = Some(inputs.move_dir.y); + 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) => { diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 2bd3fd5f00..6189744136 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -237,13 +237,8 @@ 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); - } + self.dismount(controller, read_data); + // 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 @@ -1008,14 +1003,7 @@ impl<'a> AgentData<'a> { #[cfg(feature = "be-dyn-lib")] let rng = &mut thread_rng(); - 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); - } + self.dismount(controller, read_data); let tool_tactic = |tool_kind| match tool_kind { ToolKind::Bow => Tactic::Bow, @@ -2022,10 +2010,10 @@ impl<'a> AgentData<'a> { pub fn dismount(&self, controller: &mut Controller, read_data: &ReadData) { if read_data.is_riders.contains(*self.entity) - || !read_data + || read_data .is_volume_riders .get(*self.entity) - .map_or(false, |r| r.is_steering_entity()) + .map_or(false, |r| !r.is_steering_entity()) { controller.push_event(ControlEvent::Unmount); } From 96a3c81cbbc725ebc1f0189a162b65b8e8c0d82e Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 12 Dec 2023 20:12:45 +0100 Subject: [PATCH 25/26] fix captain dismounting and don't panic in RandomField::choose --- server/agent/src/action_nodes.rs | 8 +++++++- voxygen/src/scene/figure/mod.rs | 2 +- world/src/site2/plot/tavern.rs | 2 +- world/src/util/random.rs | 11 +++++------ 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 6189744136..bf83a93e44 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -237,7 +237,13 @@ impl<'a> AgentData<'a> { 'activity: { match agent.rtsim_controller.activity { Some(NpcActivity::Goto(travel_to, speed_factor)) => { - self.dismount(controller, read_data); + 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. diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 4ac0c3afb8..720baee7f0 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -2146,7 +2146,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, character_activity - .and_then(|a| a.steer_dir) + .map(|a| a.steer_dir) .unwrap_or(0.0), time, ), diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index e647626dab..0b1987d630 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -942,7 +942,7 @@ impl Structure for Tavern { let mut offset = 0; let mut choose = |slice: &[Rgb]| -> Rgb { offset += 1; - *field.choose(self.door_wpos + offset, slice) + *field.choose(self.door_wpos + offset, slice).unwrap() }; let detail_fill = Fill::Brick( diff --git a/world/src/util/random.rs b/world/src/util/random.rs index 00e05bf1fb..ceee42c7ab 100644 --- a/world/src/util/random.rs +++ b/world/src/util/random.rs @@ -16,14 +16,13 @@ impl RandomField { (self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) } - /// # Panics - /// Panics if the slice is empty. - pub fn choose<'a, T>(&self, pos: Vec3, slice: &'a [T]) -> &'a T { - assert!(!slice.is_empty()); + pub fn choose<'a, T>(&self, pos: Vec3, slice: &'a [T]) -> Option<&'a T> { + if slice.is_empty() { + return None; + } let i = self.get(pos) as usize; - - &slice[i % slice.len()] + slice.get(i % slice.len()) } } From b654c2f923d16ca9077a88d73aa8dce42f018b9f Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 12 Dec 2023 20:20:03 +0100 Subject: [PATCH 26/26] expect instead of unwrap --- world/src/site2/plot/tavern.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/world/src/site2/plot/tavern.rs b/world/src/site2/plot/tavern.rs index 0b1987d630..672c0f846b 100644 --- a/world/src/site2/plot/tavern.rs +++ b/world/src/site2/plot/tavern.rs @@ -942,7 +942,9 @@ impl Structure for Tavern { let mut offset = 0; let mut choose = |slice: &[Rgb]| -> Rgb { offset += 1; - *field.choose(self.door_wpos + offset, slice).unwrap() + *field + .choose(self.door_wpos + offset, slice) + .expect("Color slice should not be empty.") }; let detail_fill = Fill::Brick(