diff options
| author | untodesu <kirill@untode.su> | 2025-12-11 15:14:26 +0500 |
|---|---|---|
| committer | untodesu <kirill@untode.su> | 2025-12-11 15:14:26 +0500 |
| commit | f40d09cb8f712e87691af4912f3630d92d692779 (patch) | |
| tree | 7ac3a4168ff722689372fd489c6f94d0a2546e8f /src/game/client/entity | |
| parent | 8bcbd2729388edc63c82d77d314b583af1447c49 (diff) | |
| download | voxelius-f40d09cb8f712e87691af4912f3630d92d692779.tar.bz2 voxelius-f40d09cb8f712e87691af4912f3630d92d692779.zip | |
Shuffle stuff around
- Use the new and improved hierarchy I figured out when making Prospero chat
- Re-add NSIS scripts, again from Prospero
- Update most build and utility scripts with their most recent versions
Diffstat (limited to 'src/game/client/entity')
| -rw-r--r-- | src/game/client/entity/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | src/game/client/entity/camera.cc | 114 | ||||
| -rw-r--r-- | src/game/client/entity/camera.hh | 31 | ||||
| -rw-r--r-- | src/game/client/entity/factory.cc | 30 | ||||
| -rw-r--r-- | src/game/client/entity/factory.hh | 11 | ||||
| -rw-r--r-- | src/game/client/entity/interpolation.cc | 64 | ||||
| -rw-r--r-- | src/game/client/entity/interpolation.hh | 6 | ||||
| -rw-r--r-- | src/game/client/entity/listener.cc | 42 | ||||
| -rw-r--r-- | src/game/client/entity/listener.hh | 6 | ||||
| -rw-r--r-- | src/game/client/entity/player_look.cc | 155 | ||||
| -rw-r--r-- | src/game/client/entity/player_look.hh | 7 | ||||
| -rw-r--r-- | src/game/client/entity/player_move.cc | 298 | ||||
| -rw-r--r-- | src/game/client/entity/player_move.hh | 15 | ||||
| -rw-r--r-- | src/game/client/entity/sound_emitter.cc | 63 | ||||
| -rw-r--r-- | src/game/client/entity/sound_emitter.hh | 20 |
15 files changed, 877 insertions, 0 deletions
diff --git a/src/game/client/entity/CMakeLists.txt b/src/game/client/entity/CMakeLists.txt new file mode 100644 index 0000000..4da10d6 --- /dev/null +++ b/src/game/client/entity/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(vclient PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/camera.cc" + "${CMAKE_CURRENT_LIST_DIR}/camera.hh" + "${CMAKE_CURRENT_LIST_DIR}/factory.cc" + "${CMAKE_CURRENT_LIST_DIR}/factory.hh" + "${CMAKE_CURRENT_LIST_DIR}/interpolation.cc" + "${CMAKE_CURRENT_LIST_DIR}/interpolation.hh" + "${CMAKE_CURRENT_LIST_DIR}/listener.cc" + "${CMAKE_CURRENT_LIST_DIR}/listener.hh" + "${CMAKE_CURRENT_LIST_DIR}/player_look.cc" + "${CMAKE_CURRENT_LIST_DIR}/player_look.hh" + "${CMAKE_CURRENT_LIST_DIR}/player_move.cc" + "${CMAKE_CURRENT_LIST_DIR}/player_move.hh" + "${CMAKE_CURRENT_LIST_DIR}/sound_emitter.cc" + "${CMAKE_CURRENT_LIST_DIR}/sound_emitter.hh") diff --git a/src/game/client/entity/camera.cc b/src/game/client/entity/camera.cc new file mode 100644 index 0000000..3badd9d --- /dev/null +++ b/src/game/client/entity/camera.cc @@ -0,0 +1,114 @@ +#include "client/pch.hh" + +#include "client/entity/camera.hh" + +#include "core/config/number.hh" + +#include "core/io/config_map.hh" + +#include "core/math/angles.hh" + +#include "shared/entity/head.hh" +#include "shared/entity/transform.hh" +#include "shared/entity/velocity.hh" + +#include "shared/world/dimension.hh" + +#include "client/entity/player_move.hh" + +#include "client/gui/settings.hh" + +#include "client/const.hh" +#include "client/globals.hh" +#include "client/session.hh" +#include "client/toggles.hh" + +config::Float entity::camera::roll_angle(2.0f, 0.0f, 4.0f); +config::Float entity::camera::vertical_fov(90.0f, 45.0f, 110.0f); +config::Unsigned entity::camera::view_distance(16U, 4U, 32U); + +glm::fvec3 entity::camera::angles; +glm::fvec3 entity::camera::direction; +glm::fmat4x4 entity::camera::matrix; +chunk_pos entity::camera::position_chunk; +glm::fvec3 entity::camera::position_local; + +static void reset_camera(void) +{ + entity::camera::angles = glm::fvec3(0.0f, 0.0f, 0.0f); + entity::camera::direction = DIR_FORWARD<float>; + entity::camera::matrix = glm::identity<glm::fmat4x4>(); + entity::camera::position_chunk = chunk_pos(0, 0, 0); + entity::camera::position_local = glm::fvec3(0.0f, 0.0f, 0.0f); +} + +// Gracefully contributed by PQCraft himself in 2024 +// making PlatinumSrc and Voxelius kind of related to each other +static glm::fmat4x4 platinumsrc_viewmatrix(const glm::fvec3& position, const glm::fvec3& angles) +{ + glm::fvec3 forward, up; + math::vectors(angles, &forward, nullptr, &up); + + auto result = glm::identity<glm::fmat4x4>(); + result[0][0] = forward.y * up.z - forward.z * up.y; + result[1][0] = forward.z * up.x - forward.x * up.z; + result[2][0] = forward.x * up.y - forward.y * up.x; + result[3][0] = -result[0][0] * position.x - result[1][0] * position.y - result[2][0] * position.z; + result[0][1] = up.x; + result[1][1] = up.y; + result[2][1] = up.z; + result[3][1] = -up.x * position.x - up.y * position.y - up.z * position.z; + result[0][2] = -forward.x; + result[1][2] = -forward.y; + result[2][2] = -forward.z; + result[3][2] = forward.x * position.x + forward.y * position.y + forward.z * position.z; + return result; +} + +void entity::camera::init(void) +{ + globals::client_config.add_value("camera.roll_angle", entity::camera::roll_angle); + globals::client_config.add_value("camera.vertical_fov", entity::camera::vertical_fov); + globals::client_config.add_value("camera.view_distance", entity::camera::view_distance); + + settings::add_slider(1, entity::camera::vertical_fov, settings_location::GENERAL, "camera.vertical_fov", true, "%.0f"); + settings::add_slider(0, entity::camera::view_distance, settings_location::VIDEO, "camera.view_distance", false); + settings::add_slider(10, entity::camera::roll_angle, settings_location::VIDEO, "camera.roll_angle", true, "%.01f"); + + reset_camera(); +} + +void entity::camera::update(void) +{ + if(!session::is_ingame()) { + reset_camera(); + return; + } + + const auto& head = globals::dimension->entities.get<entity::client::HeadIntr>(globals::player); + const auto& transform = globals::dimension->entities.get<entity::client::TransformIntr>(globals::player); + const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player); + + entity::camera::angles = transform.angles + head.angles; + entity::camera::position_chunk = transform.chunk; + entity::camera::position_local = transform.local + head.offset; + + glm::fvec3 right_vector, up_vector; + math::vectors(entity::camera::angles, &entity::camera::direction, &right_vector, &up_vector); + + auto client_angles = entity::camera::angles; + + if(!toggles::get(TOGGLE_PM_FLIGHT)) { + // Apply the quake-like view rolling + client_angles[2] = math::radians(-entity::camera::roll_angle.get_value() + * glm::dot(velocity.value / PMOVE_MAX_SPEED_GROUND, right_vector)); + } + + const auto z_near = 0.01f; + const auto z_far = 1.25f * static_cast<float>(CHUNK_SIZE * entity::camera::view_distance.get_value()); + + auto proj = glm::perspective(math::radians(entity::camera::vertical_fov.get_value()), globals::aspect, z_near, z_far); + auto view = platinumsrc_viewmatrix(entity::camera::position_local, client_angles); + + entity::camera::matrix = proj * view; +} diff --git a/src/game/client/entity/camera.hh b/src/game/client/entity/camera.hh new file mode 100644 index 0000000..67baf72 --- /dev/null +++ b/src/game/client/entity/camera.hh @@ -0,0 +1,31 @@ +#pragma once + +#include "shared/types.hh" + +namespace config +{ +class Float; +class Unsigned; +} // namespace config + +namespace entity::camera +{ +extern config::Float roll_angle; +extern config::Float vertical_fov; +extern config::Unsigned view_distance; +} // namespace entity::camera + +namespace entity::camera +{ +extern glm::fvec3 angles; +extern glm::fvec3 direction; +extern glm::fmat4x4 matrix; +extern chunk_pos position_chunk; +extern glm::fvec3 position_local; +} // namespace entity::camera + +namespace entity::camera +{ +void init(void); +void update(void); +} // namespace entity::camera diff --git a/src/game/client/entity/factory.cc b/src/game/client/entity/factory.cc new file mode 100644 index 0000000..f6f6079 --- /dev/null +++ b/src/game/client/entity/factory.cc @@ -0,0 +1,30 @@ +#include "client/pch.hh" + +#include "client/entity/factory.hh" + +#include "shared/entity/factory.hh" +#include "shared/entity/head.hh" +#include "shared/entity/transform.hh" + +#include "shared/world/dimension.hh" + +#include "client/entity/sound_emitter.hh" + +#include "client/globals.hh" + +void entity::client::create_player(world::Dimension* dimension, entt::entity entity) +{ + entity::shared::create_player(dimension, entity); + + const auto& head = dimension->entities.get<entity::Head>(entity); + dimension->entities.emplace_or_replace<entity::client::HeadIntr>(entity, head); + dimension->entities.emplace_or_replace<entity::client::HeadPrev>(entity, head); + + const auto& transform = dimension->entities.get<entity::Transform>(entity); + dimension->entities.emplace_or_replace<entity::client::TransformIntr>(entity, transform); + dimension->entities.emplace_or_replace<entity::client::TransformPrev>(entity, transform); + + if(globals::sound_ctx) { + dimension->entities.emplace_or_replace<entity::SoundEmitter>(entity); + } +} diff --git a/src/game/client/entity/factory.hh b/src/game/client/entity/factory.hh new file mode 100644 index 0000000..63e6e44 --- /dev/null +++ b/src/game/client/entity/factory.hh @@ -0,0 +1,11 @@ +#pragma once + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity::client +{ +void create_player(world::Dimension* dimension, entt::entity entity); +} // namespace entity::client diff --git a/src/game/client/entity/interpolation.cc b/src/game/client/entity/interpolation.cc new file mode 100644 index 0000000..a9a3349 --- /dev/null +++ b/src/game/client/entity/interpolation.cc @@ -0,0 +1,64 @@ +#include "client/pch.hh" + +#include "client/entity/interpolation.hh" + +#include "core/math/constexpr.hh" + +#include "shared/entity/head.hh" +#include "shared/entity/transform.hh" + +#include "shared/world/dimension.hh" + +#include "shared/coord.hh" + +#include "client/globals.hh" + +static void transform_interpolate(float alpha) +{ + auto group = globals::dimension->entities.group<entity::client::TransformIntr>( + entt::get<entity::Transform, entity::client::TransformPrev>); + + for(auto [entity, interp, current, previous] : group.each()) { + interp.angles[0] = glm::mix(previous.angles[0], current.angles[0], alpha); + interp.angles[1] = glm::mix(previous.angles[1], current.angles[1], alpha); + interp.angles[2] = glm::mix(previous.angles[2], current.angles[2], alpha); + + // Figure out previous chunk-local floating-point coordinates transformed + // to the current WorldCoord's chunk domain coordinates; we're interpolating + // against these instead of using previous.position.local to prevent jittering + auto previous_shift = coord::to_relative(current.chunk, current.local, previous.chunk, previous.local); + auto previous_local = current.local + previous_shift; + + interp.chunk.x = current.chunk.x; + interp.chunk.y = current.chunk.y; + interp.chunk.z = current.chunk.z; + + interp.local.x = glm::mix(previous_local.x, current.local.x, alpha); + interp.local.y = glm::mix(previous_local.y, current.local.y, alpha); + interp.local.z = glm::mix(previous_local.z, current.local.z, alpha); + } +} + +static void head_interpolate(float alpha) +{ + auto group = globals::dimension->entities.group<entity::client::HeadIntr>(entt::get<entity::Head, entity::client::HeadPrev>); + + for(auto [entity, interp, current, previous] : group.each()) { + interp.angles[0] = glm::mix(previous.angles[0], current.angles[0], alpha); + interp.angles[1] = glm::mix(previous.angles[1], current.angles[1], alpha); + interp.angles[2] = glm::mix(previous.angles[2], current.angles[2], alpha); + + interp.offset.x = glm::mix(previous.offset.x, current.offset.x, alpha); + interp.offset.y = glm::mix(previous.offset.y, current.offset.y, alpha); + interp.offset.z = glm::mix(previous.offset.z, current.offset.z, alpha); + } +} + +void entity::interpolation::update(void) +{ + if(globals::dimension) { + auto alpha = static_cast<float>(globals::fixed_accumulator) / static_cast<float>(globals::fixed_frametime_us); + transform_interpolate(alpha); + head_interpolate(alpha); + } +}
\ No newline at end of file diff --git a/src/game/client/entity/interpolation.hh b/src/game/client/entity/interpolation.hh new file mode 100644 index 0000000..6935fe8 --- /dev/null +++ b/src/game/client/entity/interpolation.hh @@ -0,0 +1,6 @@ +#pragma once + +namespace entity::interpolation +{ +void update(void); +} // namespace entity::interpolation diff --git a/src/game/client/entity/listener.cc b/src/game/client/entity/listener.cc new file mode 100644 index 0000000..a79e8a5 --- /dev/null +++ b/src/game/client/entity/listener.cc @@ -0,0 +1,42 @@ +#include "client/pch.hh" + +#include "client/entity/listener.hh" + +#include "core/config/number.hh" + +#include "core/math/constexpr.hh" + +#include "shared/entity/velocity.hh" + +#include "shared/world/dimension.hh" + +#include "client/entity/camera.hh" + +#include "client/sound/sound.hh" + +#include "client/const.hh" +#include "client/globals.hh" +#include "client/session.hh" + +void entity::listener::update(void) +{ + if(session::is_ingame()) { + const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player).value; + const auto& position = entity::camera::position_local; + + alListener3f(AL_POSITION, position.x, position.y, position.z); + alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z); + + float orientation[6]; + orientation[0] = entity::camera::direction.x; + orientation[1] = entity::camera::direction.y; + orientation[2] = entity::camera::direction.z; + orientation[3] = DIR_UP<float>.x; + orientation[4] = DIR_UP<float>.y; + orientation[5] = DIR_UP<float>.z; + + alListenerfv(AL_ORIENTATION, orientation); + } + + alListenerf(AL_GAIN, glm::clamp(sound::volume_master.get_value() * 0.01f, 0.0f, 1.0f)); +} diff --git a/src/game/client/entity/listener.hh b/src/game/client/entity/listener.hh new file mode 100644 index 0000000..594cde1 --- /dev/null +++ b/src/game/client/entity/listener.hh @@ -0,0 +1,6 @@ +#pragma once + +namespace entity::listener +{ +void update(void); +} // namespace entity::listener diff --git a/src/game/client/entity/player_look.cc b/src/game/client/entity/player_look.cc new file mode 100644 index 0000000..0752e78 --- /dev/null +++ b/src/game/client/entity/player_look.cc @@ -0,0 +1,155 @@ +#include "client/pch.hh" + +#include "client/entity/player_look.hh" + +#include "core/config/boolean.hh" +#include "core/config/number.hh" + +#include "core/io/config_map.hh" + +#include "core/math/angles.hh" + +#include "shared/entity/head.hh" + +#include "shared/world/dimension.hh" + +#include "client/config/gamepad_axis.hh" +#include "client/config/gamepad_button.hh" +#include "client/config/keybind.hh" + +#include "client/gui/settings.hh" + +#include "client/io/gamepad.hh" +#include "client/io/glfw.hh" + +#include "client/const.hh" +#include "client/globals.hh" +#include "client/session.hh" + +constexpr static float PITCH_MIN = -1.0f * math::radians(90.0f); +constexpr static float PITCH_MAX = +1.0f * math::radians(90.0f); + +// Mouse options +static config::Boolean mouse_raw_input(true); +static config::Unsigned mouse_sensitivity(25U, 1U, 100U); + +// Gamepad options +static config::Float gamepad_fastlook_factor(1.5f, 1.0f, 5.0f); +static config::Unsigned gamepad_accel_pitch(15U, 1U, 100U); +static config::Unsigned gamepad_accel_yaw(25U, 1U, 100U); + +// Gamepad axes +static config::GamepadAxis axis_pitch(GLFW_GAMEPAD_AXIS_LEFT_Y, false); +static config::GamepadAxis axis_yaw(GLFW_GAMEPAD_AXIS_LEFT_X, false); + +// Gamepad buttons +static config::GamepadButton button_fastlook(GLFW_GAMEPAD_BUTTON_LEFT_THUMB); + +static bool fastlook_enabled; +static glm::fvec2 last_cursor; + +static void add_angles(float pitch, float yaw) +{ + if(session::is_ingame()) { + auto& head = globals::dimension->entities.get<entity::Head>(globals::player); + + head.angles[0] += pitch; + head.angles[1] += yaw; + head.angles[0] = glm::clamp(head.angles[0], PITCH_MIN, PITCH_MAX); + head.angles = math::wrap_180(head.angles); + + // Client-side head angles are not interpolated; + // Re-assigning the previous state after the current + // state has been already modified is certainly a way + // to circumvent the interpolation applied to anything with a head + globals::dimension->entities.emplace_or_replace<entity::client::HeadPrev>(globals::player, head); + } +} + +static void on_glfw_cursor_pos(const io::GlfwCursorPosEvent& event) +{ + if(io::gamepad::available && io::gamepad::active.get_value()) { + // The player is assumed to be using + // a gamepad instead of mouse and keyboard + last_cursor = event.pos; + return; + } + + if(globals::gui_screen || !session::is_ingame()) { + // UI is visible or we're not in-game + last_cursor = event.pos; + return; + } + + auto dx = -0.01f * static_cast<float>(mouse_sensitivity.get_value()) * math::radians(event.pos.x - last_cursor.x); + auto dy = -0.01f * static_cast<float>(mouse_sensitivity.get_value()) * math::radians(event.pos.y - last_cursor.y); + add_angles(dy, dx); + + last_cursor = event.pos; +} + +static void on_gamepad_button(const io::GamepadButtonEvent& event) +{ + if(button_fastlook.equals(event.button)) { + fastlook_enabled = event.action == GLFW_PRESS; + } +} + +void entity::player_look::init(void) +{ + globals::client_config.add_value("player_look.mouse.raw_input", mouse_raw_input); + globals::client_config.add_value("player_look.mouse.sensitivity", mouse_sensitivity); + globals::client_config.add_value("player_look.gamepad.fastlook_factor", gamepad_fastlook_factor); + globals::client_config.add_value("player_look.gamepad.accel_pitch", gamepad_accel_pitch); + globals::client_config.add_value("player_look.gamepad.accel_yaw", gamepad_accel_yaw); + globals::client_config.add_value("player_look.gp_axis.pitch", axis_pitch); + globals::client_config.add_value("player_look.gp_axis.yaw", axis_yaw); + globals::client_config.add_value("player_look.gp_button.fastlook", button_fastlook); + + settings::add_slider(0, mouse_sensitivity, settings_location::MOUSE, "player_look.mouse.sensitivity", true); + settings::add_checkbox(1, mouse_raw_input, settings_location::MOUSE, "player_look.mouse.raw_input", true); + + settings::add_slider(0, gamepad_accel_pitch, settings_location::GAMEPAD_GAMEPLAY, "player_look.gamepad.accel_pitch", false); + settings::add_slider(1, gamepad_accel_yaw, settings_location::GAMEPAD_GAMEPLAY, "player_look.gamepad.accel_yaw", false); + settings::add_gamepad_axis(2, axis_pitch, settings_location::GAMEPAD_GAMEPLAY, "player_look.gp_axis.pitch"); + settings::add_gamepad_axis(3, axis_yaw, settings_location::GAMEPAD_GAMEPLAY, "player_look.gp_axis.yaw"); + settings::add_slider(4, gamepad_fastlook_factor, settings_location::GAMEPAD_GAMEPLAY, "player_look.gamepad.fastlook_factor", true, + "%.02f"); + settings::add_gamepad_button(5, button_fastlook, settings_location::GAMEPAD_GAMEPLAY, "player_look.gp_button.fastlook"); + + fastlook_enabled = false; + last_cursor.x = 0.5f * static_cast<float>(globals::width); + last_cursor.y = 0.5f * static_cast<float>(globals::height); + + globals::dispatcher.sink<io::GlfwCursorPosEvent>().connect<&on_glfw_cursor_pos>(); + globals::dispatcher.sink<io::GamepadButtonEvent>().connect<&on_gamepad_button>(); +} + +void entity::player_look::update_late(void) +{ + if(io::gamepad::available && io::gamepad::active.get_value() && !globals::gui_screen) { + auto pitch_value = axis_pitch.get_value(io::gamepad::state, io::gamepad::deadzone.get_value()); + auto yaw_value = axis_yaw.get_value(io::gamepad::state, io::gamepad::deadzone.get_value()); + + if(fastlook_enabled) { + // Fastlook allows the camera to + // rotate quicker when a button is held down + pitch_value *= gamepad_fastlook_factor.get_value(); + yaw_value *= gamepad_fastlook_factor.get_value(); + } + + pitch_value *= 0.001f * static_cast<float>(gamepad_accel_pitch.get_value()); + yaw_value *= 0.001f * static_cast<float>(gamepad_accel_yaw.get_value()); + + add_angles(pitch_value, yaw_value); + } + + if(!globals::gui_screen && session::is_ingame()) { + glfwSetInputMode(globals::window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glfwSetInputMode(globals::window, GLFW_RAW_MOUSE_MOTION, mouse_raw_input.get_value()); + } + else { + glfwSetInputMode(globals::window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + glfwSetInputMode(globals::window, GLFW_RAW_MOUSE_MOTION, false); + } +} diff --git a/src/game/client/entity/player_look.hh b/src/game/client/entity/player_look.hh new file mode 100644 index 0000000..0ae18db --- /dev/null +++ b/src/game/client/entity/player_look.hh @@ -0,0 +1,7 @@ +#pragma once + +namespace entity::player_look +{ +void init(void); +void update_late(void); +} // namespace entity::player_look diff --git a/src/game/client/entity/player_move.cc b/src/game/client/entity/player_move.cc new file mode 100644 index 0000000..4087b04 --- /dev/null +++ b/src/game/client/entity/player_move.cc @@ -0,0 +1,298 @@ +#include "client/pch.hh" + +#include "client/entity/player_move.hh" + +#include "core/config/boolean.hh" +#include "core/config/number.hh" + +#include "core/io/config_map.hh" + +#include "core/math/angles.hh" +#include "core/math/constexpr.hh" + +#include "shared/entity/grounded.hh" +#include "shared/entity/head.hh" +#include "shared/entity/transform.hh" +#include "shared/entity/velocity.hh" + +#include "shared/world/dimension.hh" + +#include "client/config/gamepad_axis.hh" +#include "client/config/gamepad_button.hh" +#include "client/config/keybind.hh" + +#include "client/gui/gui_screen.hh" +#include "client/gui/settings.hh" +#include "client/gui/status_lines.hh" + +#include "client/io/gamepad.hh" + +#include "client/sound/sound.hh" + +#include "client/world/voxel_sounds.hh" + +#include "client/const.hh" +#include "client/globals.hh" +#include "client/session.hh" +#include "client/toggles.hh" + +constexpr static std::uint64_t PMOVE_JUMP_COOLDOWN = 500000; // 0.5 seconds + +constexpr static float PMOVE_FOOTSTEP_SIZE = 2.0f; + +// Movement keys +static config::KeyBind key_move_forward(GLFW_KEY_W); +static config::KeyBind key_move_back(GLFW_KEY_S); +static config::KeyBind key_move_left(GLFW_KEY_A); +static config::KeyBind key_move_right(GLFW_KEY_D); +static config::KeyBind key_move_down(GLFW_KEY_LEFT_SHIFT); +static config::KeyBind key_move_up(GLFW_KEY_SPACE); + +// Movement gamepad axes +static config::GamepadAxis axis_move_forward(GLFW_GAMEPAD_AXIS_RIGHT_X, false); +static config::GamepadAxis axis_move_sideways(GLFW_GAMEPAD_AXIS_RIGHT_Y, false); + +// Movement gamepad buttons +static config::GamepadButton button_move_down(GLFW_GAMEPAD_BUTTON_DPAD_DOWN); +static config::GamepadButton button_move_up(GLFW_GAMEPAD_BUTTON_DPAD_UP); + +// General movement options +static config::Boolean enable_speedometer(true); + +static glm::fvec3 movement_direction; + +static std::uint64_t next_jump_us; +static float speedometer_value; +static float footsteps_distance; + +static std::mt19937_64 pitch_random; +static std::uniform_real_distribution<float> pitch_distrib; + +// Quake III's PM_Accelerate-ish function used for +// conventional (gravity-affected non-flight) movement +static glm::fvec3 pm_accelerate(const glm::fvec3& wishdir, const glm::fvec3& velocity, float wishspeed, float accel) +{ + auto current_speed = glm::dot(velocity, wishdir); + auto add_speed = wishspeed - current_speed; + + if(add_speed <= 0.0f) { + // Not accelerating + return velocity; + } + + auto accel_speed = glm::min(add_speed, accel * globals::fixed_frametime * wishspeed); + + auto result = glm::fvec3(velocity); + result.x += accel_speed * wishdir.x; + result.z += accel_speed * wishdir.z; + return result; +} + +// Conventional movement - velocity update when not on the ground +static glm::fvec3 pm_air_move(const glm::fvec3& wishdir, const glm::fvec3& velocity) +{ + return pm_accelerate(wishdir, velocity, PMOVE_ACCELERATION_AIR, PMOVE_MAX_SPEED_AIR); +} + +// Conventional movement - velocity uodate when on the ground +static glm::fvec3 pm_ground_move(const glm::fvec3& wishdir, const glm::fvec3& velocity) +{ + if(auto speed = glm::length(velocity)) { + auto speed_drop = speed * PMOVE_FRICTION_GROUND * globals::fixed_frametime; + auto speed_factor = glm::max(speed - speed_drop, 0.0f) / speed; + return pm_accelerate(wishdir, velocity * speed_factor, PMOVE_ACCELERATION_GROUND, PMOVE_MAX_SPEED_GROUND); + } + + return pm_accelerate(wishdir, velocity, PMOVE_ACCELERATION_GROUND, PMOVE_MAX_SPEED_GROUND); +} + +// A simpler minecraft-like acceleration model +// used whenever the TOGGLE_PM_FLIGHT is enabled +static glm::fvec3 pm_flight_move(const glm::fvec3& wishdir) +{ + // FIXME: make it smoother + return wishdir * PMOVE_MAX_SPEED_AIR; +} + +void entity::player_move::init(void) +{ + movement_direction = ZERO_VEC3<float>; + + next_jump_us = 0x0000000000000000U; + speedometer_value = 0.0f; + footsteps_distance = 0.0f; + + // UNDONE: make this a separate subsystem + pitch_random.seed(std::random_device()()); + pitch_distrib = std::uniform_real_distribution<float>(0.9f, 1.1f); + + globals::client_config.add_value("player_move.key.forward", key_move_forward); + globals::client_config.add_value("player_move.key.back", key_move_back); + globals::client_config.add_value("player_move.key.left", key_move_left); + globals::client_config.add_value("player_move.key.right", key_move_right); + globals::client_config.add_value("player_move.key.down", key_move_down); + globals::client_config.add_value("player_move.key.up", key_move_up); + globals::client_config.add_value("player_move.gp_axis.move_forward", axis_move_forward); + globals::client_config.add_value("player_move.gp_axis.move_sideways", axis_move_sideways); + globals::client_config.add_value("player_move.gp_button.move_down", button_move_down); + globals::client_config.add_value("player_move.gp_button.move_up", button_move_up); + globals::client_config.add_value("player_move.enable_speedometer", enable_speedometer); + + settings::add_keybind(1, key_move_forward, settings_location::KEYBOARD_MOVEMENT, "player_move.key.forward"); + settings::add_keybind(2, key_move_back, settings_location::KEYBOARD_MOVEMENT, "player_move.key.back"); + settings::add_keybind(3, key_move_left, settings_location::KEYBOARD_MOVEMENT, "player_move.key.left"); + settings::add_keybind(4, key_move_right, settings_location::KEYBOARD_MOVEMENT, "player_move.key.right"); + settings::add_keybind(5, key_move_down, settings_location::KEYBOARD_MOVEMENT, "player_move.key.down"); + settings::add_keybind(6, key_move_up, settings_location::KEYBOARD_MOVEMENT, "player_move.key.up"); + + settings::add_gamepad_axis(0, axis_move_forward, settings_location::GAMEPAD_MOVEMENT, "player_move.gp_axis.move_forward"); + settings::add_gamepad_axis(1, axis_move_sideways, settings_location::GAMEPAD_MOVEMENT, "player_move.gp_axis.move_sideways"); + settings::add_gamepad_button(2, button_move_down, settings_location::GAMEPAD_MOVEMENT, "player_move.gp_button.move_down"); + settings::add_gamepad_button(3, button_move_up, settings_location::GAMEPAD_MOVEMENT, "player_move.gp_button.move_up"); + + settings::add_checkbox(2, enable_speedometer, settings_location::VIDEO_GUI, "player_move.enable_speedometer", true); +} + +void entity::player_move::fixed_update(void) +{ + const auto& head = globals::dimension->entities.get<entity::Head>(globals::player); + auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player); + auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player); + + // Interpolation - preserve current component states + globals::dimension->entities.emplace_or_replace<entity::client::TransformPrev>(globals::player, transform); + + glm::fvec3 forward, right; + math::vectors(glm::fvec3(0.0f, head.angles[1], 0.0f), &forward, &right, nullptr); + + glm::fvec3 wishdir = ZERO_VEC3<float>; + glm::fvec3 movevars = glm::fvec3(movement_direction.x, 0.0f, movement_direction.z); + wishdir.x = glm::dot(movevars, right); + wishdir.z = glm::dot(movevars, forward); + + if(toggles::get(TOGGLE_PM_FLIGHT)) { + velocity.value = pm_flight_move(glm::fvec3(wishdir.x, movement_direction.y, wishdir.z)); + return; + } + + auto grounded = globals::dimension->entities.try_get<entity::Grounded>(globals::player); + auto velocity_horizontal = glm::fvec3(velocity.value.x, 0.0f, velocity.value.z); + + if(grounded) { + auto new_velocity = pm_ground_move(wishdir, velocity_horizontal); + velocity.value.x = new_velocity.x; + velocity.value.z = new_velocity.z; + + auto new_speed = glm::length(new_velocity); + + if(new_speed > 0.01f) { + footsteps_distance += globals::fixed_frametime * new_speed; + } + else { + footsteps_distance = 0.0f; + } + + if(footsteps_distance >= PMOVE_FOOTSTEP_SIZE) { + if(auto effect = world::voxel_sounds::get_footsteps(grounded->surface)) { + sound::play_player(effect, false, pitch_distrib(pitch_random)); + } + + footsteps_distance = 0.0f; + } + } + else { + auto new_velocity = pm_air_move(wishdir, velocity_horizontal); + velocity.value.x = new_velocity.x; + velocity.value.z = new_velocity.z; + } + + if(movement_direction.y == 0.0f) { + // Allow players to queue bunny-jumps by quickly + // releasing and pressing the jump key again without a cooldown + next_jump_us = 0x0000000000000000U; + return; + } + + if(grounded && (movement_direction.y > 0.0f) && (globals::curtime >= next_jump_us)) { + velocity.value.y = -PMOVE_JUMP_FORCE * globals::dimension->get_gravity(); + + auto new_speed = glm::length(glm::fvec2(velocity.value.x, velocity.value.z)); + auto new_speed_text = std::format("{:.02f} M/S", new_speed); + auto speed_change = new_speed - speedometer_value; + + speedometer_value = new_speed; + + next_jump_us = globals::curtime + PMOVE_JUMP_COOLDOWN; + + if(enable_speedometer.get_value()) { + if(glm::abs(speed_change) < 0.01f) { + // No considerable speed increase within + // the precision we use to draw the speedometer + gui::status_lines::set(gui::STATUS_DEBUG, new_speed_text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f), 1.0f); + } + else if(speed_change < 0.0f) { + // Speed change is negative, we are actively + // slowing down; use the red color for the status line + gui::status_lines::set(gui::STATUS_DEBUG, new_speed_text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 1.0f); + } + else { + // Speed change is positive, we are actively + // speeding up; use the green color for the status line + gui::status_lines::set(gui::STATUS_DEBUG, new_speed_text, ImVec4(0.0f, 1.0f, 0.0f, 1.0f), 1.0f); + } + } + + if(auto effect = world::voxel_sounds::get_footsteps(grounded->surface)) { + sound::play_player(effect, false, 1.0f); + } + } +} + +void entity::player_move::update_late(void) +{ + movement_direction = ZERO_VEC3<float>; + + if(globals::gui_screen || !session::is_ingame()) { + // We're either disconnected or have the + // UI opened up; anyways we shouldn't move + return; + } + + if(io::gamepad::available && io::gamepad::active.get_value()) { + if(button_move_down.is_pressed(io::gamepad::state)) { + movement_direction += DIR_DOWN<float>; + } + + if(button_move_up.is_pressed(io::gamepad::state)) { + movement_direction += DIR_UP<float>; + } + + movement_direction.x += axis_move_sideways.get_value(io::gamepad::state, io::gamepad::deadzone.get_value()); + movement_direction.z -= axis_move_forward.get_value(io::gamepad::state, io::gamepad::deadzone.get_value()); + } + else { + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_forward.get_key())) { + movement_direction += DIR_FORWARD<float>; + } + + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_back.get_key())) { + movement_direction += DIR_BACK<float>; + } + + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_left.get_key())) { + movement_direction += DIR_LEFT<float>; + } + + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_right.get_key())) { + movement_direction += DIR_RIGHT<float>; + } + + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_down.get_key())) { + movement_direction += DIR_DOWN<float>; + } + + if(GLFW_PRESS == glfwGetKey(globals::window, key_move_up.get_key())) { + movement_direction += DIR_UP<float>; + } + } +} diff --git a/src/game/client/entity/player_move.hh b/src/game/client/entity/player_move.hh new file mode 100644 index 0000000..8c033cc --- /dev/null +++ b/src/game/client/entity/player_move.hh @@ -0,0 +1,15 @@ +#pragma once + +constexpr static float PMOVE_MAX_SPEED_AIR = 16.0f; +constexpr static float PMOVE_MAX_SPEED_GROUND = 8.0f; +constexpr static float PMOVE_ACCELERATION_AIR = 3.0f; +constexpr static float PMOVE_ACCELERATION_GROUND = 6.0f; +constexpr static float PMOVE_FRICTION_GROUND = 10.0f; +constexpr static float PMOVE_JUMP_FORCE = 0.275f; + +namespace entity::player_move +{ +void init(void); +void fixed_update(void); +void update_late(void); +} // namespace entity::player_move diff --git a/src/game/client/entity/sound_emitter.cc b/src/game/client/entity/sound_emitter.cc new file mode 100644 index 0000000..57909ec --- /dev/null +++ b/src/game/client/entity/sound_emitter.cc @@ -0,0 +1,63 @@ +#include "client/pch.hh" + +#include "client/entity/sound_emitter.hh" + +#include "core/config/number.hh" + +#include "core/math/constexpr.hh" + +#include "shared/entity/transform.hh" +#include "shared/entity/velocity.hh" + +#include "shared/world/dimension.hh" + +#include "shared/coord.hh" + +#include "client/entity/camera.hh" + +#include "client/sound/sound.hh" + +#include "client/globals.hh" + +entity::SoundEmitter::SoundEmitter(void) +{ + alGenSources(1, &source); + sound = nullptr; +} + +entity::SoundEmitter::~SoundEmitter(void) +{ + alSourceStop(source); + alDeleteSources(1, &source); +} + +void entity::SoundEmitter::update(void) +{ + if(globals::dimension) { + const auto view = globals::dimension->entities.view<entity::SoundEmitter>(); + + const auto& pivot = entity::camera::position_chunk; + const auto gain = glm::clamp(sound::volume_effects.get_value() * 0.01f, 0.0f, 1.0f); + + for(const auto [entity, emitter] : view.each()) { + alSourcef(emitter.source, AL_GAIN, gain); + + if(const auto transform = globals::dimension->entities.try_get<entity::client::TransformIntr>(entity)) { + auto position = coord::to_relative(pivot, transform->chunk, transform->local); + alSource3f(emitter.source, AL_POSITION, position.x, position.y, position.z); + } + + if(const auto velocity = globals::dimension->entities.try_get<entity::Velocity>(entity)) { + alSource3f(emitter.source, AL_VELOCITY, velocity->value.x, velocity->value.y, velocity->value.z); + } + + ALint source_state; + alGetSourcei(emitter.source, AL_SOURCE_STATE, &source_state); + + if(source_state == AL_STOPPED) { + alSourceRewind(emitter.source); + emitter.sound = nullptr; + } + } + } +} diff --git a/src/game/client/entity/sound_emitter.hh b/src/game/client/entity/sound_emitter.hh new file mode 100644 index 0000000..72a3f74 --- /dev/null +++ b/src/game/client/entity/sound_emitter.hh @@ -0,0 +1,20 @@ +#pragma once + +#include "core/resource/resource.hh" + +struct SoundEffect; + +namespace entity +{ +struct SoundEmitter final { + resource_ptr<SoundEffect> sound; + ALuint source; + +public: + explicit SoundEmitter(void); + virtual ~SoundEmitter(void); + +public: + static void update(void); +}; +} // namespace entity |
