diff options
| author | untodesu <kirill@untode.su> | 2025-09-11 15:48:53 +0500 |
|---|---|---|
| committer | untodesu <kirill@untode.su> | 2025-09-11 15:48:53 +0500 |
| commit | d0fbd68055e3f4a796330cc8acc6c0954b5327ff (patch) | |
| tree | e581014ea02711efa5e71f00f9862e5bca58f2ed | |
| parent | cbd823aa2154a956e7da4319eecbf7afc10441ae (diff) | |
| download | voxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.tar.bz2 voxelius-d0fbd68055e3f4a796330cc8acc6c0954b5327ff.zip | |
Run clang-format across the project
212 files changed, 17689 insertions, 17699 deletions
diff --git a/core/config/boolean.cc b/core/config/boolean.cc index 45d5a38..5981b6d 100644 --- a/core/config/boolean.cc +++ b/core/config/boolean.cc @@ -1,43 +1,43 @@ -#include "core/pch.hh" - -#include "core/config/boolean.hh" - -config::Boolean::Boolean(bool default_value) -{ - m_value = default_value; -} - -void config::Boolean::set(std::string_view value) -{ - m_value = from_string(value); -} - -std::string_view config::Boolean::get(void) const -{ - return to_string(m_value); -} - -bool config::Boolean::get_value(void) const -{ - return m_value; -} - -void config::Boolean::set_value(bool value) -{ - m_value = value; -} - -std::string_view config::Boolean::to_string(bool value) -{ - if(value) { - return "true"; - } - else { - return "false"; - } -} - -bool config::Boolean::from_string(std::string_view value) -{ - return value == "true" && value != "false"; -} +#include "core/pch.hh"
+
+#include "core/config/boolean.hh"
+
+config::Boolean::Boolean(bool default_value)
+{
+ m_value = default_value;
+}
+
+void config::Boolean::set(std::string_view value)
+{
+ m_value = from_string(value);
+}
+
+std::string_view config::Boolean::get(void) const
+{
+ return to_string(m_value);
+}
+
+bool config::Boolean::get_value(void) const
+{
+ return m_value;
+}
+
+void config::Boolean::set_value(bool value)
+{
+ m_value = value;
+}
+
+std::string_view config::Boolean::to_string(bool value)
+{
+ if(value) {
+ return "true";
+ }
+ else {
+ return "false";
+ }
+}
+
+bool config::Boolean::from_string(std::string_view value)
+{
+ return value == "true" && value != "false";
+}
diff --git a/core/config/boolean.hh b/core/config/boolean.hh index cfd8b0b..f4d3acd 100644 --- a/core/config/boolean.hh +++ b/core/config/boolean.hh @@ -1,25 +1,25 @@ -#pragma once - -#include "core/config/ivalue.hh" - -namespace config -{ -class Boolean : public IValue { -public: - explicit Boolean(bool default_value = false); - virtual ~Boolean(void) = default; - - virtual void set(std::string_view value) override; - virtual std::string_view get(void) const override; - - bool get_value(void) const; - void set_value(bool value); - -private: - bool m_value; - -public: - static std::string_view to_string(bool value); - static bool from_string(std::string_view value); -}; -} // namespace config +#pragma once
+
+#include "core/config/ivalue.hh"
+
+namespace config
+{
+class Boolean : public IValue {
+public:
+ explicit Boolean(bool default_value = false);
+ virtual ~Boolean(void) = default;
+
+ virtual void set(std::string_view value) override;
+ virtual std::string_view get(void) const override;
+
+ bool get_value(void) const;
+ void set_value(bool value);
+
+private:
+ bool m_value;
+
+public:
+ static std::string_view to_string(bool value);
+ static bool from_string(std::string_view value);
+};
+} // namespace config
diff --git a/core/config/ivalue.hh b/core/config/ivalue.hh index 13e9a3a..782da76 100644 --- a/core/config/ivalue.hh +++ b/core/config/ivalue.hh @@ -1,11 +1,11 @@ -#pragma once - -namespace config -{ -class IValue { -public: - virtual ~IValue(void) = default; - virtual void set(std::string_view value) = 0; - virtual std::string_view get(void) const = 0; -}; -} // namespace config +#pragma once
+
+namespace config
+{
+class IValue {
+public:
+ virtual ~IValue(void) = default;
+ virtual void set(std::string_view value) = 0;
+ virtual std::string_view get(void) const = 0;
+};
+} // namespace config
diff --git a/core/config/number.hh b/core/config/number.hh index 45d4773..5533459 100644 --- a/core/config/number.hh +++ b/core/config/number.hh @@ -1,130 +1,130 @@ -#pragma once - -#include "core/config/ivalue.hh" - -#include "core/math/concepts.hh" - -namespace config -{ -template<math::Arithmetic T> -class Number : public IValue { -public: - explicit Number(T default_value = T(0)); - explicit Number(T default_value, T min_value, T max_value); - virtual ~Number(void) = default; - - virtual void set(std::string_view value) override; - virtual std::string_view get(void) const override; - - T get_value(void) const; - void set_value(T value); - - T get_min_value(void) const; - T get_max_value(void) const; - void set_limits(T min_value, T max_value); - -private: - T m_value; - T m_min_value; - T m_max_value; - std::string m_string; -}; -} // namespace config - -namespace config -{ -class Int final : public Number<int> { -public: - using Number<int>::Number; -}; - -class Float final : public Number<float> { -public: - using Number<float>::Number; -}; - -class Unsigned final : public Number<unsigned int> { -public: - using Number<unsigned int>::Number; -}; - -class Unsigned64 final : public Number<std::uint64_t> { -public: - using Number<std::uint64_t>::Number; -}; - -class SizeType final : public Number<std::size_t> { -public: - using Number<std::size_t>::Number; -}; -} // namespace config - -template<math::Arithmetic T> -inline config::Number<T>::Number(T default_value) -{ - m_value = default_value; - m_min_value = std::numeric_limits<T>::lowest(); - m_max_value = std::numeric_limits<T>::max(); - m_string = std::to_string(default_value); -} - -template<math::Arithmetic T> -inline config::Number<T>::Number(T default_value, T min_value, T max_value) -{ - m_value = default_value; - m_min_value = min_value; - m_max_value = max_value; - m_string = std::to_string(default_value); -} - -template<math::Arithmetic T> -inline void config::Number<T>::set(std::string_view value) -{ - T parsed_value; - auto result = std::from_chars(value.data(), value.data() + value.size(), parsed_value); - - if(result.ec == std::errc()) { - m_value = std::clamp(parsed_value, m_min_value, m_max_value); - m_string = std::to_string(m_value); - } -} - -template<math::Arithmetic T> -inline std::string_view config::Number<T>::get(void) const -{ - return m_string; -} - -template<math::Arithmetic T> -inline T config::Number<T>::get_value(void) const -{ - return m_value; -} - -template<math::Arithmetic T> -inline void config::Number<T>::set_value(T value) -{ - m_value = std::clamp(value, m_min_value, m_max_value); - m_string = std::to_string(m_value); -} - -template<math::Arithmetic T> -inline T config::Number<T>::get_min_value(void) const -{ - return m_min_value; -} - -template<math::Arithmetic T> -inline T config::Number<T>::get_max_value(void) const -{ - return m_max_value; -} - -template<math::Arithmetic T> -inline void config::Number<T>::set_limits(T min_value, T max_value) -{ - m_min_value = min_value; - m_max_value = max_value; - m_value = std::clamp(m_value, m_min_value, m_max_value); - m_string = std::to_string(m_value); -} +#pragma once
+
+#include "core/config/ivalue.hh"
+
+#include "core/math/concepts.hh"
+
+namespace config
+{
+template<math::Arithmetic T>
+class Number : public IValue {
+public:
+ explicit Number(T default_value = T(0));
+ explicit Number(T default_value, T min_value, T max_value);
+ virtual ~Number(void) = default;
+
+ virtual void set(std::string_view value) override;
+ virtual std::string_view get(void) const override;
+
+ T get_value(void) const;
+ void set_value(T value);
+
+ T get_min_value(void) const;
+ T get_max_value(void) const;
+ void set_limits(T min_value, T max_value);
+
+private:
+ T m_value;
+ T m_min_value;
+ T m_max_value;
+ std::string m_string;
+};
+} // namespace config
+
+namespace config
+{
+class Int final : public Number<int> {
+public:
+ using Number<int>::Number;
+};
+
+class Float final : public Number<float> {
+public:
+ using Number<float>::Number;
+};
+
+class Unsigned final : public Number<unsigned int> {
+public:
+ using Number<unsigned int>::Number;
+};
+
+class Unsigned64 final : public Number<std::uint64_t> {
+public:
+ using Number<std::uint64_t>::Number;
+};
+
+class SizeType final : public Number<std::size_t> {
+public:
+ using Number<std::size_t>::Number;
+};
+} // namespace config
+
+template<math::Arithmetic T>
+inline config::Number<T>::Number(T default_value)
+{
+ m_value = default_value;
+ m_min_value = std::numeric_limits<T>::lowest();
+ m_max_value = std::numeric_limits<T>::max();
+ m_string = std::to_string(default_value);
+}
+
+template<math::Arithmetic T>
+inline config::Number<T>::Number(T default_value, T min_value, T max_value)
+{
+ m_value = default_value;
+ m_min_value = min_value;
+ m_max_value = max_value;
+ m_string = std::to_string(default_value);
+}
+
+template<math::Arithmetic T>
+inline void config::Number<T>::set(std::string_view value)
+{
+ T parsed_value;
+ auto result = std::from_chars(value.data(), value.data() + value.size(), parsed_value);
+
+ if(result.ec == std::errc()) {
+ m_value = std::clamp(parsed_value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+ }
+}
+
+template<math::Arithmetic T>
+inline std::string_view config::Number<T>::get(void) const
+{
+ return m_string;
+}
+
+template<math::Arithmetic T>
+inline T config::Number<T>::get_value(void) const
+{
+ return m_value;
+}
+
+template<math::Arithmetic T>
+inline void config::Number<T>::set_value(T value)
+{
+ m_value = std::clamp(value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+}
+
+template<math::Arithmetic T>
+inline T config::Number<T>::get_min_value(void) const
+{
+ return m_min_value;
+}
+
+template<math::Arithmetic T>
+inline T config::Number<T>::get_max_value(void) const
+{
+ return m_max_value;
+}
+
+template<math::Arithmetic T>
+inline void config::Number<T>::set_limits(T min_value, T max_value)
+{
+ m_min_value = min_value;
+ m_max_value = max_value;
+ m_value = std::clamp(m_value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+}
diff --git a/core/config/string.cc b/core/config/string.cc index 198b448..8fb9d70 100644 --- a/core/config/string.cc +++ b/core/config/string.cc @@ -1,18 +1,18 @@ -#include "core/pch.hh" - -#include "core/config/string.hh" - -config::String::String(std::string_view default_value) -{ - m_value = default_value; -} - -void config::String::set(std::string_view value) -{ - m_value = value; -} - -std::string_view config::String::get(void) const -{ - return m_value; -} +#include "core/pch.hh"
+
+#include "core/config/string.hh"
+
+config::String::String(std::string_view default_value)
+{
+ m_value = default_value;
+}
+
+void config::String::set(std::string_view value)
+{
+ m_value = value;
+}
+
+std::string_view config::String::get(void) const
+{
+ return m_value;
+}
diff --git a/core/config/string.hh b/core/config/string.hh index 8fd3901..d7bbbc1 100644 --- a/core/config/string.hh +++ b/core/config/string.hh @@ -1,31 +1,31 @@ -#pragma once - -#include "core/config/ivalue.hh" - -namespace config -{ -class String : public IValue { -public: - explicit String(std::string_view default_value); - virtual ~String(void) = default; - - virtual void set(std::string_view value) override; - virtual std::string_view get(void) const override; - - constexpr const std::string& get_value(void) const noexcept; - constexpr const char* c_str(void) const noexcept; - -private: - std::string m_value; -}; -} // namespace config - -constexpr const std::string& config::String::get_value(void) const noexcept -{ - return m_value; -} - -constexpr const char* config::String::c_str(void) const noexcept -{ - return m_value.c_str(); -} +#pragma once
+
+#include "core/config/ivalue.hh"
+
+namespace config
+{
+class String : public IValue {
+public:
+ explicit String(std::string_view default_value);
+ virtual ~String(void) = default;
+
+ virtual void set(std::string_view value) override;
+ virtual std::string_view get(void) const override;
+
+ constexpr const std::string& get_value(void) const noexcept;
+ constexpr const char* c_str(void) const noexcept;
+
+private:
+ std::string m_value;
+};
+} // namespace config
+
+constexpr const std::string& config::String::get_value(void) const noexcept
+{
+ return m_value;
+}
+
+constexpr const char* config::String::c_str(void) const noexcept
+{
+ return m_value.c_str();
+}
diff --git a/core/io/buffer.cc b/core/io/buffer.cc index 02b810e..d3bf7bb 100644 --- a/core/io/buffer.cc +++ b/core/io/buffer.cc @@ -1,345 +1,345 @@ -#include "core/pch.hh" - -#include "core/io/buffer.hh" - -#include "core/math/constexpr.hh" - -io::ReadBuffer::ReadBuffer(const ReadBuffer& other) -{ - reset(other.data(), other.size()); -} - -io::ReadBuffer::ReadBuffer(const void* data, std::size_t size) -{ - assert(data); - - reset(data, size); -} - -io::ReadBuffer::ReadBuffer(const ENetPacket* packet) -{ - assert(packet); - - reset(packet); -} - -io::ReadBuffer::ReadBuffer(PHYSFS_File* file) -{ - assert(file); - - reset(file); -} - -std::size_t io::ReadBuffer::size(void) const -{ - return m_vector.size(); -} - -const std::byte* io::ReadBuffer::data(void) const -{ - return m_vector.data(); -} - -void io::ReadBuffer::reset(const void* data, std::size_t size) -{ - assert(data); - - auto bytes = reinterpret_cast<const std::byte*>(data); - m_vector.assign(bytes, bytes + size); - m_position = 0U; -} - -void io::ReadBuffer::reset(const ENetPacket* packet) -{ - assert(packet); - - auto bytes_ptr = reinterpret_cast<const std::byte*>(packet->data); - m_vector.assign(bytes_ptr, bytes_ptr + packet->dataLength); - m_position = 0; -} - -void io::ReadBuffer::reset(PHYSFS_File* file) -{ - assert(file); - - m_vector.resize(PHYSFS_fileLength(file)); - m_position = 0; - - PHYSFS_seek(file, 0); - PHYSFS_readBytes(file, m_vector.data(), m_vector.size()); -} - -template<> -std::byte io::ReadBuffer::read<std::byte>(void) -{ - if(m_position < m_vector.size()) { - auto result = m_vector[m_position]; - m_position += 1U; - return result; - } - - m_position += 1U; - return static_cast<std::byte>(0x00); -} - -template<> -std::uint8_t io::ReadBuffer::read<std::uint8_t>(void) -{ - if((m_position + 1U) <= m_vector.size()) { - auto result = static_cast<std::uint8_t>(m_vector[m_position]); - m_position += 1U; - return result; - } - - m_position += 1U; - return 0; -} - -template<> -std::uint16_t io::ReadBuffer::read<std::uint16_t>(void) -{ - if((m_position + 2U) <= m_vector.size()) { - auto result = UINT16_C(0x0000); - result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 0U])) << 8U; - result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 1U])) << 0U; - m_position += 2U; - return result; - } - - m_position += 2U; - return 0; -} - -template<> -std::uint32_t io::ReadBuffer::read<std::uint32_t>(void) -{ - if((m_position + 4U) <= m_vector.size()) { - auto result = UINT32_C(0x00000000); - result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 0U])) << 24U; - result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 1U])) << 16U; - result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 2U])) << 8U; - result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 3U])) << 0U; - m_position += 4U; - return result; - } - - m_position += 4U; - return 0; -} - -template<> -std::uint64_t io::ReadBuffer::read<std::uint64_t>(void) -{ - if((m_position + 8U) <= m_vector.size()) { - auto result = UINT64_C(0x0000000000000000); - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 0U])) << 56U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 1U])) << 48U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 2U])) << 40U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 3U])) << 32U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 4U])) << 24U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 5U])) << 16U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 6U])) << 8U; - result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 7U])) << 0U; - m_position += 8U; - return result; - } - - m_position += 8U; - return 0; -} - -template<> -float io::ReadBuffer::read<float>(void) -{ - return std::bit_cast<float>(read<std::uint32_t>()); -} - -template<> -std::int8_t io::ReadBuffer::read<std::int8_t>(void) -{ - return std::bit_cast<std::int8_t>(read<std::uint8_t>()); -} - -template<> -std::int16_t io::ReadBuffer::read<std::int16_t>(void) -{ - return std::bit_cast<std::int16_t>(read<std::uint16_t>()); -} - -template<> -std::int32_t io::ReadBuffer::read<std::int32_t>(void) -{ - return std::bit_cast<std::int32_t>(read<std::uint32_t>()); -} - -template<> -std::int64_t io::ReadBuffer::read<std::int64_t>(void) -{ - return std::bit_cast<std::int64_t>(read<std::uint64_t>()); -} - -template<> -std::string io::ReadBuffer::read<std::string>(void) -{ - std::string result; - result.resize(read<std::uint16_t>()); - - for(std::size_t i = 0; i < result.size(); ++i) { - if(m_position < m_vector.size()) { - result[i] = static_cast<char>(m_vector[m_position]); - } - - m_position += 1U; - } - - return result; -} - -void io::ReadBuffer::read(void* buffer, std::size_t size) -{ - auto bytes = reinterpret_cast<std::byte*>(buffer); - auto amount_to_read = std::min(size, m_vector.size() - m_position); - - if(amount_to_read > 0) { - std::copy(m_vector.cbegin() + m_position, m_vector.cbegin() + m_position + amount_to_read, bytes); - } - - m_position += size; -} - -io::WriteBuffer::WriteBuffer(const WriteBuffer& other) -{ - m_vector = other.m_vector; -} - -std::size_t io::WriteBuffer::size(void) const -{ - return m_vector.size(); -} - -const std::byte* io::WriteBuffer::data(void) const -{ - return m_vector.data(); -} - -void io::WriteBuffer::reset(void) -{ - m_vector.clear(); -} - -void io::WriteBuffer::write(const WriteBuffer& other) -{ - m_vector.insert(m_vector.end(), other.m_vector.begin(), other.m_vector.end()); -} - -void io::WriteBuffer::write(const void* data, std::size_t size) -{ - assert(data); - - auto bytes = reinterpret_cast<const std::byte*>(data); - m_vector.insert(m_vector.end(), bytes, bytes + size); -} - -template<> -void io::WriteBuffer::write<std::byte>(const std::byte value) -{ - m_vector.push_back(value); -} - -template<> -void io::WriteBuffer::write<std::uint8_t>(const std::uint8_t value) -{ - m_vector.push_back(static_cast<std::byte>(value)); -} - -template<> -void io::WriteBuffer::write<std::uint16_t>(const std::uint16_t value) -{ - m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0xFF00)) >> 8U))); - m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0x00FF)) >> 0U))); -} - -template<> -void io::WriteBuffer::write<std::uint32_t>(const std::uint32_t value) -{ - m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0xFF000000)) >> 24U))); - m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x00FF0000)) >> 16U))); - m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x0000FF00)) >> 8U))); - m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x000000FF)) >> 0U))); -} - -template<> -void io::WriteBuffer::write<std::uint64_t>(const std::uint64_t value) -{ - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0xFF00000000000000)) >> 56U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00FF000000000000)) >> 48U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000FF0000000000)) >> 40U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000FF00000000)) >> 32U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000FF000000)) >> 24U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000000000FF0000)) >> 16U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000000000FF00)) >> 8U))); - m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000000000FF)) >> 0U))); -} - -template<> -void io::WriteBuffer::write(const float value) -{ - write(std::bit_cast<std::uint32_t>(value)); -} - -template<> -void io::WriteBuffer::write(const std::int8_t value) -{ - write(std::bit_cast<std::uint8_t>(value)); -} - -template<> -void io::WriteBuffer::write(const std::int16_t value) -{ - write(std::bit_cast<std::uint16_t>(value)); -} - -template<> -void io::WriteBuffer::write(const std::int32_t value) -{ - write(std::bit_cast<std::uint32_t>(value)); -} - -template<> -void io::WriteBuffer::write(const std::int64_t value) -{ - write(std::bit_cast<std::uint64_t>(value)); -} - -template<> -void io::WriteBuffer::write<std::string_view>(const std::string_view value) -{ - write<std::uint16_t>(value.size()); - - for(const auto& character : value) { - m_vector.push_back(static_cast<std::byte>(character)); - } -} - -PHYSFS_File* io::WriteBuffer::to_file(const std::string& path, bool append) const -{ - PHYSFS_File* file = nullptr; - - if(append) { - file = PHYSFS_openAppend(path.c_str()); - } - else { - file = PHYSFS_openWrite(path.c_str()); - } - - if(file) { - PHYSFS_writeBytes(file, m_vector.data(), m_vector.size()); - } - - return file; -} - -ENetPacket* io::WriteBuffer::to_packet(enet_uint32 flags) const -{ - return enet_packet_create(m_vector.data(), m_vector.size(), flags); -} +#include "core/pch.hh"
+
+#include "core/io/buffer.hh"
+
+#include "core/math/constexpr.hh"
+
+io::ReadBuffer::ReadBuffer(const ReadBuffer& other)
+{
+ reset(other.data(), other.size());
+}
+
+io::ReadBuffer::ReadBuffer(const void* data, std::size_t size)
+{
+ assert(data);
+
+ reset(data, size);
+}
+
+io::ReadBuffer::ReadBuffer(const ENetPacket* packet)
+{
+ assert(packet);
+
+ reset(packet);
+}
+
+io::ReadBuffer::ReadBuffer(PHYSFS_File* file)
+{
+ assert(file);
+
+ reset(file);
+}
+
+std::size_t io::ReadBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* io::ReadBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void io::ReadBuffer::reset(const void* data, std::size_t size)
+{
+ assert(data);
+
+ auto bytes = reinterpret_cast<const std::byte*>(data);
+ m_vector.assign(bytes, bytes + size);
+ m_position = 0U;
+}
+
+void io::ReadBuffer::reset(const ENetPacket* packet)
+{
+ assert(packet);
+
+ auto bytes_ptr = reinterpret_cast<const std::byte*>(packet->data);
+ m_vector.assign(bytes_ptr, bytes_ptr + packet->dataLength);
+ m_position = 0;
+}
+
+void io::ReadBuffer::reset(PHYSFS_File* file)
+{
+ assert(file);
+
+ m_vector.resize(PHYSFS_fileLength(file));
+ m_position = 0;
+
+ PHYSFS_seek(file, 0);
+ PHYSFS_readBytes(file, m_vector.data(), m_vector.size());
+}
+
+template<>
+std::byte io::ReadBuffer::read<std::byte>(void)
+{
+ if(m_position < m_vector.size()) {
+ auto result = m_vector[m_position];
+ m_position += 1U;
+ return result;
+ }
+
+ m_position += 1U;
+ return static_cast<std::byte>(0x00);
+}
+
+template<>
+std::uint8_t io::ReadBuffer::read<std::uint8_t>(void)
+{
+ if((m_position + 1U) <= m_vector.size()) {
+ auto result = static_cast<std::uint8_t>(m_vector[m_position]);
+ m_position += 1U;
+ return result;
+ }
+
+ m_position += 1U;
+ return 0;
+}
+
+template<>
+std::uint16_t io::ReadBuffer::read<std::uint16_t>(void)
+{
+ if((m_position + 2U) <= m_vector.size()) {
+ auto result = UINT16_C(0x0000);
+ result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 0U])) << 8U;
+ result |= (UINT16_C(0x00FF) & static_cast<std::uint16_t>(m_vector[m_position + 1U])) << 0U;
+ m_position += 2U;
+ return result;
+ }
+
+ m_position += 2U;
+ return 0;
+}
+
+template<>
+std::uint32_t io::ReadBuffer::read<std::uint32_t>(void)
+{
+ if((m_position + 4U) <= m_vector.size()) {
+ auto result = UINT32_C(0x00000000);
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 0U])) << 24U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 1U])) << 16U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 2U])) << 8U;
+ result |= (UINT32_C(0x000000FF) & static_cast<std::uint32_t>(m_vector[m_position + 3U])) << 0U;
+ m_position += 4U;
+ return result;
+ }
+
+ m_position += 4U;
+ return 0;
+}
+
+template<>
+std::uint64_t io::ReadBuffer::read<std::uint64_t>(void)
+{
+ if((m_position + 8U) <= m_vector.size()) {
+ auto result = UINT64_C(0x0000000000000000);
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 0U])) << 56U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 1U])) << 48U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 2U])) << 40U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 3U])) << 32U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 4U])) << 24U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 5U])) << 16U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 6U])) << 8U;
+ result |= (UINT64_C(0x00000000000000FF) & static_cast<std::uint64_t>(m_vector[m_position + 7U])) << 0U;
+ m_position += 8U;
+ return result;
+ }
+
+ m_position += 8U;
+ return 0;
+}
+
+template<>
+float io::ReadBuffer::read<float>(void)
+{
+ return std::bit_cast<float>(read<std::uint32_t>());
+}
+
+template<>
+std::int8_t io::ReadBuffer::read<std::int8_t>(void)
+{
+ return std::bit_cast<std::int8_t>(read<std::uint8_t>());
+}
+
+template<>
+std::int16_t io::ReadBuffer::read<std::int16_t>(void)
+{
+ return std::bit_cast<std::int16_t>(read<std::uint16_t>());
+}
+
+template<>
+std::int32_t io::ReadBuffer::read<std::int32_t>(void)
+{
+ return std::bit_cast<std::int32_t>(read<std::uint32_t>());
+}
+
+template<>
+std::int64_t io::ReadBuffer::read<std::int64_t>(void)
+{
+ return std::bit_cast<std::int64_t>(read<std::uint64_t>());
+}
+
+template<>
+std::string io::ReadBuffer::read<std::string>(void)
+{
+ std::string result;
+ result.resize(read<std::uint16_t>());
+
+ for(std::size_t i = 0; i < result.size(); ++i) {
+ if(m_position < m_vector.size()) {
+ result[i] = static_cast<char>(m_vector[m_position]);
+ }
+
+ m_position += 1U;
+ }
+
+ return result;
+}
+
+void io::ReadBuffer::read(void* buffer, std::size_t size)
+{
+ auto bytes = reinterpret_cast<std::byte*>(buffer);
+ auto amount_to_read = std::min(size, m_vector.size() - m_position);
+
+ if(amount_to_read > 0) {
+ std::copy(m_vector.cbegin() + m_position, m_vector.cbegin() + m_position + amount_to_read, bytes);
+ }
+
+ m_position += size;
+}
+
+io::WriteBuffer::WriteBuffer(const WriteBuffer& other)
+{
+ m_vector = other.m_vector;
+}
+
+std::size_t io::WriteBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* io::WriteBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void io::WriteBuffer::reset(void)
+{
+ m_vector.clear();
+}
+
+void io::WriteBuffer::write(const WriteBuffer& other)
+{
+ m_vector.insert(m_vector.end(), other.m_vector.begin(), other.m_vector.end());
+}
+
+void io::WriteBuffer::write(const void* data, std::size_t size)
+{
+ assert(data);
+
+ auto bytes = reinterpret_cast<const std::byte*>(data);
+ m_vector.insert(m_vector.end(), bytes, bytes + size);
+}
+
+template<>
+void io::WriteBuffer::write<std::byte>(const std::byte value)
+{
+ m_vector.push_back(value);
+}
+
+template<>
+void io::WriteBuffer::write<std::uint8_t>(const std::uint8_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(value));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint16_t>(const std::uint16_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0xFF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0x00FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint32_t>(const std::uint32_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0xFF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x00FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x0000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x000000FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write<std::uint64_t>(const std::uint64_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0xFF00000000000000)) >> 56U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00FF000000000000)) >> 48U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000FF0000000000)) >> 40U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000FF00000000)) >> 32U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000FF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000000000FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000000000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000000000FF)) >> 0U)));
+}
+
+template<>
+void io::WriteBuffer::write(const float value)
+{
+ write(std::bit_cast<std::uint32_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int8_t value)
+{
+ write(std::bit_cast<std::uint8_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int16_t value)
+{
+ write(std::bit_cast<std::uint16_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int32_t value)
+{
+ write(std::bit_cast<std::uint32_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write(const std::int64_t value)
+{
+ write(std::bit_cast<std::uint64_t>(value));
+}
+
+template<>
+void io::WriteBuffer::write<std::string_view>(const std::string_view value)
+{
+ write<std::uint16_t>(value.size());
+
+ for(const auto& character : value) {
+ m_vector.push_back(static_cast<std::byte>(character));
+ }
+}
+
+PHYSFS_File* io::WriteBuffer::to_file(const std::string& path, bool append) const
+{
+ PHYSFS_File* file = nullptr;
+
+ if(append) {
+ file = PHYSFS_openAppend(path.c_str());
+ }
+ else {
+ file = PHYSFS_openWrite(path.c_str());
+ }
+
+ if(file) {
+ PHYSFS_writeBytes(file, m_vector.data(), m_vector.size());
+ }
+
+ return file;
+}
+
+ENetPacket* io::WriteBuffer::to_packet(enet_uint32 flags) const
+{
+ return enet_packet_create(m_vector.data(), m_vector.size(), flags);
+}
diff --git a/core/io/buffer.hh b/core/io/buffer.hh index 96a37b1..5f1ab66 100644 --- a/core/io/buffer.hh +++ b/core/io/buffer.hh @@ -1,88 +1,88 @@ -namespace io -{ -class ReadBuffer final { -public: - ReadBuffer(void) = default; - explicit ReadBuffer(const ReadBuffer& other); - explicit ReadBuffer(const void* data, std::size_t size); - explicit ReadBuffer(const ENetPacket* packet); - explicit ReadBuffer(PHYSFS_File* file); - virtual ~ReadBuffer(void) = default; - - std::size_t size(void) const; - const std::byte* data(void) const; - - void reset(const void* data, std::size_t size); - void reset(const ENetPacket* packet); - void reset(PHYSFS_File* file); - - constexpr void rewind(void); - constexpr bool is_ended(void) const; - - void read(void* buffer, std::size_t size); - - template<typename T> - T read(void); - - template<typename T> - ReadBuffer& operator>>(T& value); - -private: - std::vector<std::byte> m_vector; - std::size_t m_position; -}; -} // namespace io - -namespace io -{ -class WriteBuffer final { -public: - WriteBuffer(void) = default; - explicit WriteBuffer(const WriteBuffer& other); - virtual ~WriteBuffer(void) = default; - - std::size_t size(void) const; - const std::byte* data(void) const; - - void reset(void); - - void write(const WriteBuffer& other); - void write(const void* data, std::size_t size); - - template<typename T> - void write(const T value); - - template<typename T> - WriteBuffer& operator<<(const T value); - - PHYSFS_File* to_file(const std::string& path, bool append = false) const; - ENetPacket* to_packet(enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE) const; - -private: - std::vector<std::byte> m_vector; -}; -} // namespace io - -constexpr void io::ReadBuffer::rewind(void) -{ - m_position = 0; -} - -constexpr bool io::ReadBuffer::is_ended(void) const -{ - return m_position >= m_vector.size(); -} - -template<typename T> -io::ReadBuffer& io::ReadBuffer::operator>>(T& value) -{ - value = read<T>(); - return *this; -} - -template<typename T> -io::WriteBuffer& io::WriteBuffer::operator<<(const T value) -{ - write<T>(value); - return *this; -} +namespace io
+{
+class ReadBuffer final {
+public:
+ ReadBuffer(void) = default;
+ explicit ReadBuffer(const ReadBuffer& other);
+ explicit ReadBuffer(const void* data, std::size_t size);
+ explicit ReadBuffer(const ENetPacket* packet);
+ explicit ReadBuffer(PHYSFS_File* file);
+ virtual ~ReadBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(const void* data, std::size_t size);
+ void reset(const ENetPacket* packet);
+ void reset(PHYSFS_File* file);
+
+ constexpr void rewind(void);
+ constexpr bool is_ended(void) const;
+
+ void read(void* buffer, std::size_t size);
+
+ template<typename T>
+ T read(void);
+
+ template<typename T>
+ ReadBuffer& operator>>(T& value);
+
+private:
+ std::vector<std::byte> m_vector;
+ std::size_t m_position;
+};
+} // namespace io
+
+namespace io
+{
+class WriteBuffer final {
+public:
+ WriteBuffer(void) = default;
+ explicit WriteBuffer(const WriteBuffer& other);
+ virtual ~WriteBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(void);
+
+ void write(const WriteBuffer& other);
+ void write(const void* data, std::size_t size);
+
+ template<typename T>
+ void write(const T value);
+
+ template<typename T>
+ WriteBuffer& operator<<(const T value);
+
+ PHYSFS_File* to_file(const std::string& path, bool append = false) const;
+ ENetPacket* to_packet(enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE) const;
+
+private:
+ std::vector<std::byte> m_vector;
+};
+} // namespace io
+
+constexpr void io::ReadBuffer::rewind(void)
+{
+ m_position = 0;
+}
+
+constexpr bool io::ReadBuffer::is_ended(void) const
+{
+ return m_position >= m_vector.size();
+}
+
+template<typename T>
+io::ReadBuffer& io::ReadBuffer::operator>>(T& value)
+{
+ value = read<T>();
+ return *this;
+}
+
+template<typename T>
+io::WriteBuffer& io::WriteBuffer::operator<<(const T value)
+{
+ write<T>(value);
+ return *this;
+}
diff --git a/core/io/cmdline.cc b/core/io/cmdline.cc index 7a00f37..a876ace 100644 --- a/core/io/cmdline.cc +++ b/core/io/cmdline.cc @@ -1,95 +1,95 @@ -#include "core/pch.hh" - -#include "core/io/cmdline.hh" - -// Valid options always start with OPTION_PREFIX, can contain -// a bunch of OPTION_PREFIX'es inside and never end with one -constexpr static char OPTION_PREFIX = '-'; - -static std::unordered_map<std::string, std::string> options; - -static inline bool is_option_string(const std::string& string) -{ - if(string.find_last_of(OPTION_PREFIX) >= (string.size() - 1)) { - return false; - } - - return string[0] == OPTION_PREFIX; -} - -static inline std::string get_option(const std::string& string) -{ - std::size_t i; - for(i = 0; string[i] == OPTION_PREFIX; ++i) { - // empty - } - - return std::string(string.cbegin() + i, string.cend()); -} - -void io::cmdline::create(int argc, char** argv) -{ - for(int idx = 1; idx < argc; ++idx) { - std::string string = argv[idx]; - - if(!is_option_string(string)) { - spdlog::warn("cmdline: non-argument at {}: {}", idx, string); - continue; - } - - auto option_string = get_option(string); - auto next_idx = idx + 1; - - if(next_idx < argc) { - std::string argument = argv[next_idx]; - - if(!is_option_string(argument)) { - options.insert_or_assign(option_string, argument); - idx = next_idx; - continue; - } - } - - // The option is either last or has no - // argument (happens when there is a valid - // option right next to the one we're parsing) - options.insert_or_assign(option_string, std::string()); - } -} - -void io::cmdline::insert(std::string_view option) -{ - options.insert_or_assign(std::string(option), std::string()); -} - -void io::cmdline::insert(std::string_view option, std::string_view argument) -{ - options.insert_or_assign(std::string(option), std::string(argument)); -} - -std::string_view io::cmdline::get(std::string_view option, std::string_view fallback) -{ - auto it = options.find(std::string(option)); - - if(it == options.cend()) { - return fallback; - } - - return it->second; -} - -const char* io::cmdline::get_cstr(std::string_view option, const char* fallback) -{ - auto it = options.find(std::string(option)); - - if(it == options.cend()) { - return fallback; - } - - return it->second.c_str(); -} - -bool io::cmdline::contains(std::string_view option) -{ - return options.count(std::string(option)); -} +#include "core/pch.hh"
+
+#include "core/io/cmdline.hh"
+
+// Valid options always start with OPTION_PREFIX, can contain
+// a bunch of OPTION_PREFIX'es inside and never end with one
+constexpr static char OPTION_PREFIX = '-';
+
+static std::unordered_map<std::string, std::string> options;
+
+static inline bool is_option_string(const std::string& string)
+{
+ if(string.find_last_of(OPTION_PREFIX) >= (string.size() - 1)) {
+ return false;
+ }
+
+ return string[0] == OPTION_PREFIX;
+}
+
+static inline std::string get_option(const std::string& string)
+{
+ std::size_t i;
+ for(i = 0; string[i] == OPTION_PREFIX; ++i) {
+ // empty
+ }
+
+ return std::string(string.cbegin() + i, string.cend());
+}
+
+void io::cmdline::create(int argc, char** argv)
+{
+ for(int idx = 1; idx < argc; ++idx) {
+ std::string string = argv[idx];
+
+ if(!is_option_string(string)) {
+ spdlog::warn("cmdline: non-argument at {}: {}", idx, string);
+ continue;
+ }
+
+ auto option_string = get_option(string);
+ auto next_idx = idx + 1;
+
+ if(next_idx < argc) {
+ std::string argument = argv[next_idx];
+
+ if(!is_option_string(argument)) {
+ options.insert_or_assign(option_string, argument);
+ idx = next_idx;
+ continue;
+ }
+ }
+
+ // The option is either last or has no
+ // argument (happens when there is a valid
+ // option right next to the one we're parsing)
+ options.insert_or_assign(option_string, std::string());
+ }
+}
+
+void io::cmdline::insert(std::string_view option)
+{
+ options.insert_or_assign(std::string(option), std::string());
+}
+
+void io::cmdline::insert(std::string_view option, std::string_view argument)
+{
+ options.insert_or_assign(std::string(option), std::string(argument));
+}
+
+std::string_view io::cmdline::get(std::string_view option, std::string_view fallback)
+{
+ auto it = options.find(std::string(option));
+
+ if(it == options.cend()) {
+ return fallback;
+ }
+
+ return it->second;
+}
+
+const char* io::cmdline::get_cstr(std::string_view option, const char* fallback)
+{
+ auto it = options.find(std::string(option));
+
+ if(it == options.cend()) {
+ return fallback;
+ }
+
+ return it->second.c_str();
+}
+
+bool io::cmdline::contains(std::string_view option)
+{
+ return options.count(std::string(option));
+}
diff --git a/core/io/cmdline.hh b/core/io/cmdline.hh index e90433c..6e2ab9a 100644 --- a/core/io/cmdline.hh +++ b/core/io/cmdline.hh @@ -1,11 +1,11 @@ -#pragma once - -namespace io::cmdline -{ -void create(int argc, char** argv); -void insert(std::string_view option); -void insert(std::string_view option, std::string_view argument); -std::string_view get(std::string_view option, std::string_view fallback = ""); -const char* get_cstr(std::string_view option, const char* fallback = nullptr); -bool contains(std::string_view option); -} // namespace io::cmdline +#pragma once
+
+namespace io::cmdline
+{
+void create(int argc, char** argv);
+void insert(std::string_view option);
+void insert(std::string_view option, std::string_view argument);
+std::string_view get(std::string_view option, std::string_view fallback = "");
+const char* get_cstr(std::string_view option, const char* fallback = nullptr);
+bool contains(std::string_view option);
+} // namespace io::cmdline
diff --git a/core/io/config_map.cc b/core/io/config_map.cc index d22913e..24206f2 100644 --- a/core/io/config_map.cc +++ b/core/io/config_map.cc @@ -1,136 +1,136 @@ -#include "core/pch.hh" - -#include "core/io/config_map.hh" - -#include "core/config/ivalue.hh" - -#include "core/io/cmdline.hh" - -#include "core/utils/string.hh" - -#include "core/version.hh" - -void io::ConfigMap::load_cmdline(void) -{ - for(auto it : m_values) { - if(auto value = io::cmdline::get_cstr(it.first.c_str())) { - it.second->set(value); - } - } -} - -bool io::ConfigMap::load_file(std::string_view path) -{ - if(auto file = PHYSFS_openRead(std::string(path).c_str())) { - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - std::string line; - std::string kv_string; - std::istringstream stream(source); - - while(std::getline(stream, line)) { - auto comment = line.find_first_of('#'); - - if(comment == std::string::npos) { - kv_string = utils::trim_whitespace(line); - } - else { - kv_string = utils::trim_whitespace(line.substr(0, comment)); - } - - if(utils::is_whitespace(kv_string)) { - // Ignore empty or commented out lines - continue; - } - - auto separator = kv_string.find('='); - - if(separator == std::string::npos) { - spdlog::warn("config: {}: invalid line: {}", path, line); - continue; - } - - auto kv_name = utils::trim_whitespace(kv_string.substr(0, separator)); - auto kv_value = utils::trim_whitespace(kv_string.substr(separator + 1)); - - auto kv_pair = m_values.find(kv_name); - - if(kv_pair == m_values.cend()) { - spdlog::warn("config: {}: unknown key: {}", path, kv_name); - continue; - } - - kv_pair->second->set(kv_value.c_str()); - } - - return true; - } - - return false; -} - -bool io::ConfigMap::save_file(std::string_view path) const -{ - std::ostringstream stream; - - auto curtime = std::time(nullptr); - - stream << "# Voxelius " << version::semver << " configuration file" << std::endl; - stream << "# Generated at: " << std::put_time(std::gmtime(&curtime), "%Y-%m-%d %H:%M:%S %z") << std::endl << std::endl; - - for(const auto& it : m_values) { - stream << it.first << "="; - stream << it.second->get(); - stream << std::endl; - } - - if(auto file = PHYSFS_openWrite(std::string(path).c_str())) { - auto source = stream.str(); - PHYSFS_writeBytes(file, source.data(), source.size()); - PHYSFS_close(file); - return true; - } - - return false; -} - -bool io::ConfigMap::set_value(std::string_view name, std::string_view value) -{ - auto kv_pair = m_values.find(std::string(name)); - - if(kv_pair != m_values.cend()) { - kv_pair->second->set(value); - return true; - } - - return false; -} - -std::string_view io::ConfigMap::get_value(std::string_view name) const -{ - auto kv_pair = m_values.find(std::string(name)); - if(kv_pair != m_values.cend()) { - return kv_pair->second->get(); - } - - return std::string_view(); -} - -void io::ConfigMap::add_value(std::string_view name, config::IValue& vref) -{ - m_values.insert_or_assign(std::string(name), &vref); -} - -const config::IValue* io::ConfigMap::find(std::string_view name) const -{ - auto kv_pair = m_values.find(std::string(name)); - - if(kv_pair != m_values.cend()) { - return kv_pair->second; - } - else { - return nullptr; - } -} +#include "core/pch.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/config/ivalue.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/utils/string.hh"
+
+#include "core/version.hh"
+
+void io::ConfigMap::load_cmdline(void)
+{
+ for(auto it : m_values) {
+ if(auto value = io::cmdline::get_cstr(it.first.c_str())) {
+ it.second->set(value);
+ }
+ }
+}
+
+bool io::ConfigMap::load_file(std::string_view path)
+{
+ if(auto file = PHYSFS_openRead(std::string(path).c_str())) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::string line;
+ std::string kv_string;
+ std::istringstream stream(source);
+
+ while(std::getline(stream, line)) {
+ auto comment = line.find_first_of('#');
+
+ if(comment == std::string::npos) {
+ kv_string = utils::trim_whitespace(line);
+ }
+ else {
+ kv_string = utils::trim_whitespace(line.substr(0, comment));
+ }
+
+ if(utils::is_whitespace(kv_string)) {
+ // Ignore empty or commented out lines
+ continue;
+ }
+
+ auto separator = kv_string.find('=');
+
+ if(separator == std::string::npos) {
+ spdlog::warn("config: {}: invalid line: {}", path, line);
+ continue;
+ }
+
+ auto kv_name = utils::trim_whitespace(kv_string.substr(0, separator));
+ auto kv_value = utils::trim_whitespace(kv_string.substr(separator + 1));
+
+ auto kv_pair = m_values.find(kv_name);
+
+ if(kv_pair == m_values.cend()) {
+ spdlog::warn("config: {}: unknown key: {}", path, kv_name);
+ continue;
+ }
+
+ kv_pair->second->set(kv_value.c_str());
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool io::ConfigMap::save_file(std::string_view path) const
+{
+ std::ostringstream stream;
+
+ auto curtime = std::time(nullptr);
+
+ stream << "# Voxelius " << version::semver << " configuration file" << std::endl;
+ stream << "# Generated at: " << std::put_time(std::gmtime(&curtime), "%Y-%m-%d %H:%M:%S %z") << std::endl << std::endl;
+
+ for(const auto& it : m_values) {
+ stream << it.first << "=";
+ stream << it.second->get();
+ stream << std::endl;
+ }
+
+ if(auto file = PHYSFS_openWrite(std::string(path).c_str())) {
+ auto source = stream.str();
+ PHYSFS_writeBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+ return true;
+ }
+
+ return false;
+}
+
+bool io::ConfigMap::set_value(std::string_view name, std::string_view value)
+{
+ auto kv_pair = m_values.find(std::string(name));
+
+ if(kv_pair != m_values.cend()) {
+ kv_pair->second->set(value);
+ return true;
+ }
+
+ return false;
+}
+
+std::string_view io::ConfigMap::get_value(std::string_view name) const
+{
+ auto kv_pair = m_values.find(std::string(name));
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second->get();
+ }
+
+ return std::string_view();
+}
+
+void io::ConfigMap::add_value(std::string_view name, config::IValue& vref)
+{
+ m_values.insert_or_assign(std::string(name), &vref);
+}
+
+const config::IValue* io::ConfigMap::find(std::string_view name) const
+{
+ auto kv_pair = m_values.find(std::string(name));
+
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second;
+ }
+ else {
+ return nullptr;
+ }
+}
diff --git a/core/io/config_map.hh b/core/io/config_map.hh index b0cd579..6b99564 100644 --- a/core/io/config_map.hh +++ b/core/io/config_map.hh @@ -1,29 +1,29 @@ -#pragma once - -namespace config -{ -class IValue; -} // namespace config - -namespace io -{ -class ConfigMap final { -public: - ConfigMap(void) = default; - virtual ~ConfigMap(void) = default; - - void load_cmdline(void); - bool load_file(std::string_view path); - bool save_file(std::string_view path) const; - - bool set_value(std::string_view name, std::string_view value); - std::string_view get_value(std::string_view name) const; - - void add_value(std::string_view name, config::IValue& vref); - - const config::IValue* find(std::string_view name) const; - -private: - std::unordered_map<std::string, config::IValue*> m_values; -}; -} // namespace io +#pragma once
+
+namespace config
+{
+class IValue;
+} // namespace config
+
+namespace io
+{
+class ConfigMap final {
+public:
+ ConfigMap(void) = default;
+ virtual ~ConfigMap(void) = default;
+
+ void load_cmdline(void);
+ bool load_file(std::string_view path);
+ bool save_file(std::string_view path) const;
+
+ bool set_value(std::string_view name, std::string_view value);
+ std::string_view get_value(std::string_view name) const;
+
+ void add_value(std::string_view name, config::IValue& vref);
+
+ const config::IValue* find(std::string_view name) const;
+
+private:
+ std::unordered_map<std::string, config::IValue*> m_values;
+};
+} // namespace io
diff --git a/core/math/aabb.cc b/core/math/aabb.cc index f5c7e14..1111149 100644 --- a/core/math/aabb.cc +++ b/core/math/aabb.cc @@ -1,59 +1,59 @@ -#include "core/pch.hh" - -#include "core/math/aabb.hh" - -math::AABB::AABB(const glm::fvec3& min, const glm::fvec3& max) -{ - set_bounds(min, max); -} - -void math::AABB::set_bounds(const glm::fvec3& min, const glm::fvec3& max) -{ - this->min = min; - this->max = max; -} - -void math::AABB::set_offset(const glm::fvec3& base, const glm::fvec3& size) -{ - this->min = base; - this->max = base + size; -} - -bool math::AABB::contains(const glm::fvec3& point) const -{ - auto result = true; - result = result && (point.x >= min.x) && (point.x <= max.x); - result = result && (point.y >= min.y) && (point.y <= max.y); - result = result && (point.z >= min.z) && (point.z <= max.z); - return result; -} - -bool math::AABB::intersect(const AABB& other_box) const -{ - auto result = true; - result = result && (min.x < other_box.max.x) && (max.x > other_box.min.x); - result = result && (min.y < other_box.max.y) && (max.y > other_box.min.y); - result = result && (min.z < other_box.max.z) && (max.z > other_box.min.z); - return result; -} - -math::AABB math::AABB::combine_with(const math::AABB& other_box) const -{ - AABB result; - result.set_bounds(min, other_box.max); - return result; -} - -math::AABB math::AABB::multiply_with(const math::AABB& other_box) const -{ - AABB result; - result.set_bounds(other_box.min, max); - return result; -} - -math::AABB math::AABB::push(const glm::fvec3& vector) const -{ - AABB result; - result.set_bounds(min + vector, max + vector); - return result; -} +#include "core/pch.hh"
+
+#include "core/math/aabb.hh"
+
+math::AABB::AABB(const glm::fvec3& min, const glm::fvec3& max)
+{
+ set_bounds(min, max);
+}
+
+void math::AABB::set_bounds(const glm::fvec3& min, const glm::fvec3& max)
+{
+ this->min = min;
+ this->max = max;
+}
+
+void math::AABB::set_offset(const glm::fvec3& base, const glm::fvec3& size)
+{
+ this->min = base;
+ this->max = base + size;
+}
+
+bool math::AABB::contains(const glm::fvec3& point) const
+{
+ auto result = true;
+ result = result && (point.x >= min.x) && (point.x <= max.x);
+ result = result && (point.y >= min.y) && (point.y <= max.y);
+ result = result && (point.z >= min.z) && (point.z <= max.z);
+ return result;
+}
+
+bool math::AABB::intersect(const AABB& other_box) const
+{
+ auto result = true;
+ result = result && (min.x < other_box.max.x) && (max.x > other_box.min.x);
+ result = result && (min.y < other_box.max.y) && (max.y > other_box.min.y);
+ result = result && (min.z < other_box.max.z) && (max.z > other_box.min.z);
+ return result;
+}
+
+math::AABB math::AABB::combine_with(const math::AABB& other_box) const
+{
+ AABB result;
+ result.set_bounds(min, other_box.max);
+ return result;
+}
+
+math::AABB math::AABB::multiply_with(const math::AABB& other_box) const
+{
+ AABB result;
+ result.set_bounds(other_box.min, max);
+ return result;
+}
+
+math::AABB math::AABB::push(const glm::fvec3& vector) const
+{
+ AABB result;
+ result.set_bounds(min + vector, max + vector);
+ return result;
+}
diff --git a/core/math/aabb.hh b/core/math/aabb.hh index b843cd9..bff8e04 100644 --- a/core/math/aabb.hh +++ b/core/math/aabb.hh @@ -1,28 +1,28 @@ -#pragma once - -namespace math -{ -class AABB final { -public: - AABB(void) = default; - explicit AABB(const glm::fvec3& min, const glm::fvec3& max); - virtual ~AABB(void) = default; - - void set_bounds(const glm::fvec3& min, const glm::fvec3& max); - void set_offset(const glm::fvec3& base, const glm::fvec3& size); - - const glm::fvec3& get_min(void) const; - const glm::fvec3& get_max(void) const; - - bool contains(const glm::fvec3& point) const; - bool intersect(const AABB& other_box) const; - - AABB combine_with(const AABB& other_box) const; - AABB multiply_with(const AABB& other_box) const; - AABB push(const glm::fvec3& vector) const; - -public: - glm::fvec3 min; - glm::fvec3 max; -}; -} // namespace math +#pragma once
+
+namespace math
+{
+class AABB final {
+public:
+ AABB(void) = default;
+ explicit AABB(const glm::fvec3& min, const glm::fvec3& max);
+ virtual ~AABB(void) = default;
+
+ void set_bounds(const glm::fvec3& min, const glm::fvec3& max);
+ void set_offset(const glm::fvec3& base, const glm::fvec3& size);
+
+ const glm::fvec3& get_min(void) const;
+ const glm::fvec3& get_max(void) const;
+
+ bool contains(const glm::fvec3& point) const;
+ bool intersect(const AABB& other_box) const;
+
+ AABB combine_with(const AABB& other_box) const;
+ AABB multiply_with(const AABB& other_box) const;
+ AABB push(const glm::fvec3& vector) const;
+
+public:
+ glm::fvec3 min;
+ glm::fvec3 max;
+};
+} // namespace math
diff --git a/core/math/angles.hh b/core/math/angles.hh index 174f320..edc2f65 100644 --- a/core/math/angles.hh +++ b/core/math/angles.hh @@ -1,103 +1,103 @@ -#pragma once - -#include "core/math/constexpr.hh" - -constexpr float A180 = math::radians(180.0f); -constexpr float A360 = math::radians(360.0f); - -namespace math -{ -float wrap_180(float angle); -float wrap_360(float angle); -} // namespace math - -namespace math -{ -glm::fvec3 wrap_180(const glm::fvec3& angles); -glm::fvec3 wrap_360(const glm::fvec3& angles); -} // namespace math - -namespace math -{ -void vectors(const glm::fvec3& angles, glm::fvec3& forward); -void vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up); -} // namespace math - -inline float math::wrap_180(float angle) -{ - const auto result = std::fmod(angle + A180, A360); - - if(result < 0.0f) { - return result + A180; - } - - return result - A180; -} - -inline float math::wrap_360(float angle) -{ - return std::fmod(std::fmod(angle, A360) + A360, A360); -} - -inline glm::fvec3 math::wrap_180(const glm::fvec3& angles) -{ - return glm::fvec3 { - math::wrap_180(angles.x), - math::wrap_180(angles.y), - math::wrap_180(angles.z), - }; -} - -inline glm::fvec3 math::wrap_360(const glm::fvec3& angles) -{ - return glm::fvec3 { - math::wrap_360(angles.x), - math::wrap_360(angles.y), - math::wrap_360(angles.z), - }; -} - -inline void math::vectors(const glm::fvec3& angles, glm::fvec3& forward) -{ - const float cosp = std::cos(angles.x); - const float cosy = std::cos(angles.y); - const float sinp = std::sin(angles.x); - const float siny = std::sin(angles.y); - - forward.x = cosp * siny * (-1.0f); - forward.y = sinp; - forward.z = cosp * cosy * (-1.0f); -} - -inline void math::vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up) -{ - if(!forward && !right && !up) { - // There's no point in figuring out - // direction vectors if nothing is passed - // in the function to store that stuff in - return; - } - - const auto pcv = glm::cos(angles); - const auto psv = glm::sin(angles); - const auto ncv = pcv * (-1.0f); - const auto nsv = psv * (-1.0f); - - if(forward) { - forward->x = pcv.x * nsv.y; - forward->y = psv.x; - forward->z = pcv.x * ncv.y; - } - - if(right) { - right->x = pcv.z * pcv.y; - right->y = psv.z * pcv.y; - right->z = nsv.y; - } - - if(up) { - up->x = psv.x * psv.y * pcv.z + ncv.y * psv.z; - up->y = pcv.x * pcv.z; - up->z = nsv.x * ncv.y * pcv.z + psv.y * psv.z; - } -} +#pragma once
+
+#include "core/math/constexpr.hh"
+
+constexpr float A180 = math::radians(180.0f);
+constexpr float A360 = math::radians(360.0f);
+
+namespace math
+{
+float wrap_180(float angle);
+float wrap_360(float angle);
+} // namespace math
+
+namespace math
+{
+glm::fvec3 wrap_180(const glm::fvec3& angles);
+glm::fvec3 wrap_360(const glm::fvec3& angles);
+} // namespace math
+
+namespace math
+{
+void vectors(const glm::fvec3& angles, glm::fvec3& forward);
+void vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up);
+} // namespace math
+
+inline float math::wrap_180(float angle)
+{
+ const auto result = std::fmod(angle + A180, A360);
+
+ if(result < 0.0f) {
+ return result + A180;
+ }
+
+ return result - A180;
+}
+
+inline float math::wrap_360(float angle)
+{
+ return std::fmod(std::fmod(angle, A360) + A360, A360);
+}
+
+inline glm::fvec3 math::wrap_180(const glm::fvec3& angles)
+{
+ return glm::fvec3 {
+ math::wrap_180(angles.x),
+ math::wrap_180(angles.y),
+ math::wrap_180(angles.z),
+ };
+}
+
+inline glm::fvec3 math::wrap_360(const glm::fvec3& angles)
+{
+ return glm::fvec3 {
+ math::wrap_360(angles.x),
+ math::wrap_360(angles.y),
+ math::wrap_360(angles.z),
+ };
+}
+
+inline void math::vectors(const glm::fvec3& angles, glm::fvec3& forward)
+{
+ const float cosp = std::cos(angles.x);
+ const float cosy = std::cos(angles.y);
+ const float sinp = std::sin(angles.x);
+ const float siny = std::sin(angles.y);
+
+ forward.x = cosp * siny * (-1.0f);
+ forward.y = sinp;
+ forward.z = cosp * cosy * (-1.0f);
+}
+
+inline void math::vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up)
+{
+ if(!forward && !right && !up) {
+ // There's no point in figuring out
+ // direction vectors if nothing is passed
+ // in the function to store that stuff in
+ return;
+ }
+
+ const auto pcv = glm::cos(angles);
+ const auto psv = glm::sin(angles);
+ const auto ncv = pcv * (-1.0f);
+ const auto nsv = psv * (-1.0f);
+
+ if(forward) {
+ forward->x = pcv.x * nsv.y;
+ forward->y = psv.x;
+ forward->z = pcv.x * ncv.y;
+ }
+
+ if(right) {
+ right->x = pcv.z * pcv.y;
+ right->y = psv.z * pcv.y;
+ right->z = nsv.y;
+ }
+
+ if(up) {
+ up->x = psv.x * psv.y * pcv.z + ncv.y * psv.z;
+ up->y = pcv.x * pcv.z;
+ up->z = nsv.x * ncv.y * pcv.z + psv.y * psv.z;
+ }
+}
diff --git a/core/math/concepts.hh b/core/math/concepts.hh index 70859c2..10cb74a 100644 --- a/core/math/concepts.hh +++ b/core/math/concepts.hh @@ -1,11 +1,11 @@ -#pragma once - -namespace math -{ -template<typename T> -concept Arithmetic = std::is_arithmetic_v<T>; -template<typename T> -concept Integer = std::is_integral_v<T>; -template<typename T> -concept FloatingPoint = std::is_floating_point_v<T>; -} // namespace math +#pragma once
+
+namespace math
+{
+template<typename T>
+concept Arithmetic = std::is_arithmetic_v<T>;
+template<typename T>
+concept Integer = std::is_integral_v<T>;
+template<typename T>
+concept FloatingPoint = std::is_floating_point_v<T>;
+} // namespace math
diff --git a/core/math/constexpr.hh b/core/math/constexpr.hh index 65dbbbf..e6c4b6d 100644 --- a/core/math/constexpr.hh +++ b/core/math/constexpr.hh @@ -1,198 +1,198 @@ -#pragma once - -#include "core/math/concepts.hh" - -namespace math -{ -template<math::Arithmetic T> -constexpr static inline const T abs(const T x); -template<typename T, std::size_t L> -constexpr static inline const std::size_t array_size(const T (&)[L]); -template<math::Integer T, math::FloatingPoint F> -constexpr static inline const T ceil(const F x); -template<math::Arithmetic T> -constexpr static inline const T degrees(const T x); -template<math::Integer T, math::FloatingPoint F> -constexpr static inline const T floor(const F x); -template<math::Arithmetic T> -constexpr static inline const T clamp(const T x, const T min, const T max); -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T lerp(const T x, const T y, const F a); -template<math::Arithmetic T> -constexpr static inline const T log2(const T x); -template<math::Arithmetic T> -constexpr static inline const T max(const T x, const T y); -template<math::Arithmetic T> -constexpr static inline const T min(const T x, const T y); -template<math::Integer T> -requires std::is_signed_v<T> -constexpr static inline const T mod_signed(const T x, const T m); -template<math::Arithmetic T> -constexpr static inline const T pow2(const T x); -template<math::Arithmetic T> -constexpr static inline const T radians(const T x); -template<math::Arithmetic T> -constexpr static inline const bool range(const T x, const T min, const T max); -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T sign(const F x); -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T smoothstep(const T x, const T y, const F a); -} // namespace math - -template<math::Arithmetic T> -constexpr static inline const T math::abs(const T x) -{ - if(x < static_cast<T>(0)) { - return -x; - } - else { - return x; - } -} - -template<typename T, std::size_t L> -constexpr static inline const std::size_t math::array_size(const T (&)[L]) -{ - return L; -} - -template<math::Integer T, math::FloatingPoint F> -constexpr static inline const T math::ceil(const F x) -{ - const T ival = static_cast<T>(x); - - if(ival < x) { - return ival + static_cast<T>(1); - } - else { - return ival; - } -} - -template<math::Arithmetic T> -constexpr static inline const T math::degrees(const T x) -{ - return x * static_cast<T>(180.0) / static_cast<T>(M_PI); -} - -template<math::Integer T, math::FloatingPoint F> -constexpr static inline const T math::floor(const F x) -{ - const T ival = static_cast<T>(x); - - if(ival > x) { - return ival - static_cast<T>(1); - } - else { - return ival; - } -} - -template<math::Arithmetic T> -constexpr static inline const T math::clamp(const T x, const T min, const T max) -{ - if(x < min) { - return min; - } - else if(x > max) { - return max; - } - else { - return x; - } -} - -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T math::lerp(const T x, const T y, const F a) -{ - return static_cast<T>(static_cast<F>(x) * (static_cast<F>(1.0f) - a) + static_cast<F>(y) * a); -} - -template<math::Arithmetic T> -constexpr static inline const T math::log2(const T x) -{ - if(x < 2) { - return 0; - } - else { - return math::log2<T>((x + 1) >> 1) + 1; - } -} - -template<math::Arithmetic T> -constexpr static inline const T math::max(const T x, const T y) -{ - if(x < y) { - return y; - } - else { - return x; - } -} - -template<math::Arithmetic T> -constexpr static inline const T math::min(const T x, const T y) -{ - if(x > y) { - return y; - } - else { - return x; - } -} - -template<math::Integer T> -requires std::is_signed_v<T> -constexpr static inline const T math::mod_signed(const T x, const T m) -{ - auto result = static_cast<T>(x % m); - - if(result < T(0)) { - return result + m; - } - else { - return result; - } -} - -template<math::Arithmetic T> -constexpr static inline const T math::pow2(const T x) -{ - T value = static_cast<T>(1); - while(value < x) - value *= static_cast<T>(2); - return value; -} - -template<math::Arithmetic T> -constexpr static inline const T math::radians(const T x) -{ - return x * static_cast<T>(M_PI) / static_cast<T>(180.0); -} - -template<math::Arithmetic T> -constexpr static inline const bool math::range(const T x, const T min, const T max) -{ - return ((x >= min) && (x <= max)); -} - -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T math::sign(const F x) -{ - if(x < F(0)) { - return T(-1); - } - else if(x > F(0)) { - return T(+1); - } - else { - return T(0); - } -} - -template<math::Arithmetic T, math::FloatingPoint F> -constexpr static inline const T math::smoothstep(const T x, const T y, const F a) -{ - const F t = math::clamp<F>((a - x) / (y - x), F(0), F(1)); - return static_cast<T>(t * t * (F(3) - F(2) * t)); -} +#pragma once
+
+#include "core/math/concepts.hh"
+
+namespace math
+{
+template<math::Arithmetic T>
+constexpr static inline const T abs(const T x);
+template<typename T, std::size_t L>
+constexpr static inline const std::size_t array_size(const T (&)[L]);
+template<math::Integer T, math::FloatingPoint F>
+constexpr static inline const T ceil(const F x);
+template<math::Arithmetic T>
+constexpr static inline const T degrees(const T x);
+template<math::Integer T, math::FloatingPoint F>
+constexpr static inline const T floor(const F x);
+template<math::Arithmetic T>
+constexpr static inline const T clamp(const T x, const T min, const T max);
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T lerp(const T x, const T y, const F a);
+template<math::Arithmetic T>
+constexpr static inline const T log2(const T x);
+template<math::Arithmetic T>
+constexpr static inline const T max(const T x, const T y);
+template<math::Arithmetic T>
+constexpr static inline const T min(const T x, const T y);
+template<math::Integer T>
+requires std::is_signed_v<T>
+constexpr static inline const T mod_signed(const T x, const T m);
+template<math::Arithmetic T>
+constexpr static inline const T pow2(const T x);
+template<math::Arithmetic T>
+constexpr static inline const T radians(const T x);
+template<math::Arithmetic T>
+constexpr static inline const bool range(const T x, const T min, const T max);
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T sign(const F x);
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T smoothstep(const T x, const T y, const F a);
+} // namespace math
+
+template<math::Arithmetic T>
+constexpr static inline const T math::abs(const T x)
+{
+ if(x < static_cast<T>(0)) {
+ return -x;
+ }
+ else {
+ return x;
+ }
+}
+
+template<typename T, std::size_t L>
+constexpr static inline const std::size_t math::array_size(const T (&)[L])
+{
+ return L;
+}
+
+template<math::Integer T, math::FloatingPoint F>
+constexpr static inline const T math::ceil(const F x)
+{
+ const T ival = static_cast<T>(x);
+
+ if(ival < x) {
+ return ival + static_cast<T>(1);
+ }
+ else {
+ return ival;
+ }
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::degrees(const T x)
+{
+ return x * static_cast<T>(180.0) / static_cast<T>(M_PI);
+}
+
+template<math::Integer T, math::FloatingPoint F>
+constexpr static inline const T math::floor(const F x)
+{
+ const T ival = static_cast<T>(x);
+
+ if(ival > x) {
+ return ival - static_cast<T>(1);
+ }
+ else {
+ return ival;
+ }
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::clamp(const T x, const T min, const T max)
+{
+ if(x < min) {
+ return min;
+ }
+ else if(x > max) {
+ return max;
+ }
+ else {
+ return x;
+ }
+}
+
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T math::lerp(const T x, const T y, const F a)
+{
+ return static_cast<T>(static_cast<F>(x) * (static_cast<F>(1.0f) - a) + static_cast<F>(y) * a);
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::log2(const T x)
+{
+ if(x < 2) {
+ return 0;
+ }
+ else {
+ return math::log2<T>((x + 1) >> 1) + 1;
+ }
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::max(const T x, const T y)
+{
+ if(x < y) {
+ return y;
+ }
+ else {
+ return x;
+ }
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::min(const T x, const T y)
+{
+ if(x > y) {
+ return y;
+ }
+ else {
+ return x;
+ }
+}
+
+template<math::Integer T>
+requires std::is_signed_v<T>
+constexpr static inline const T math::mod_signed(const T x, const T m)
+{
+ auto result = static_cast<T>(x % m);
+
+ if(result < T(0)) {
+ return result + m;
+ }
+ else {
+ return result;
+ }
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::pow2(const T x)
+{
+ T value = static_cast<T>(1);
+ while(value < x)
+ value *= static_cast<T>(2);
+ return value;
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::radians(const T x)
+{
+ return x * static_cast<T>(M_PI) / static_cast<T>(180.0);
+}
+
+template<math::Arithmetic T>
+constexpr static inline const bool math::range(const T x, const T min, const T max)
+{
+ return ((x >= min) && (x <= max));
+}
+
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T math::sign(const F x)
+{
+ if(x < F(0)) {
+ return T(-1);
+ }
+ else if(x > F(0)) {
+ return T(+1);
+ }
+ else {
+ return T(0);
+ }
+}
+
+template<math::Arithmetic T, math::FloatingPoint F>
+constexpr static inline const T math::smoothstep(const T x, const T y, const F a)
+{
+ const F t = math::clamp<F>((a - x) / (y - x), F(0), F(1));
+ return static_cast<T>(t * t * (F(3) - F(2) * t));
+}
diff --git a/core/math/crc64.cc b/core/math/crc64.cc index 4311eaf..0b6a830 100644 --- a/core/math/crc64.cc +++ b/core/math/crc64.cc @@ -1,71 +1,71 @@ -#include "core/pch.hh" - -#include "core/math/crc64.hh" - -// The lookup table for CRC64 checksum; this lookup -// table is generated using ECMA-182 compilant parameters: -// - Polynomial: `0x42F0E1EBA9EA3693` -// - Initial value: `0x0000000000000000` -// - Final xor: `0x0000000000000000` -// CRC Calculator: https://www.sunshine2k.de/coding/javascript/crc/crc_js.html -constexpr static const std::uint64_t crc_table[256] = { 0x0000000000000000, 0x42F0E1EBA9EA3693, 0x85E1C3D753D46D26, 0xC711223CFA3E5BB5, - 0x493366450E42ECDF, 0x0BC387AEA7A8DA4C, 0xCCD2A5925D9681F9, 0x8E224479F47CB76A, 0x9266CC8A1C85D9BE, 0xD0962D61B56FEF2D, - 0x17870F5D4F51B498, 0x5577EEB6E6BB820B, 0xDB55AACF12C73561, 0x99A54B24BB2D03F2, 0x5EB4691841135847, 0x1C4488F3E8F96ED4, - 0x663D78FF90E185EF, 0x24CD9914390BB37C, 0xE3DCBB28C335E8C9, 0xA12C5AC36ADFDE5A, 0x2F0E1EBA9EA36930, 0x6DFEFF5137495FA3, - 0xAAEFDD6DCD770416, 0xE81F3C86649D3285, 0xF45BB4758C645C51, 0xB6AB559E258E6AC2, 0x71BA77A2DFB03177, 0x334A9649765A07E4, - 0xBD68D2308226B08E, 0xFF9833DB2BCC861D, 0x388911E7D1F2DDA8, 0x7A79F00C7818EB3B, 0xCC7AF1FF21C30BDE, 0x8E8A101488293D4D, - 0x499B3228721766F8, 0x0B6BD3C3DBFD506B, 0x854997BA2F81E701, 0xC7B97651866BD192, 0x00A8546D7C558A27, 0x4258B586D5BFBCB4, - 0x5E1C3D753D46D260, 0x1CECDC9E94ACE4F3, 0xDBFDFEA26E92BF46, 0x990D1F49C77889D5, 0x172F5B3033043EBF, 0x55DFBADB9AEE082C, - 0x92CE98E760D05399, 0xD03E790CC93A650A, 0xAA478900B1228E31, 0xE8B768EB18C8B8A2, 0x2FA64AD7E2F6E317, 0x6D56AB3C4B1CD584, - 0xE374EF45BF6062EE, 0xA1840EAE168A547D, 0x66952C92ECB40FC8, 0x2465CD79455E395B, 0x3821458AADA7578F, 0x7AD1A461044D611C, - 0xBDC0865DFE733AA9, 0xFF3067B657990C3A, 0x711223CFA3E5BB50, 0x33E2C2240A0F8DC3, 0xF4F3E018F031D676, 0xB60301F359DBE0E5, - 0xDA050215EA6C212F, 0x98F5E3FE438617BC, 0x5FE4C1C2B9B84C09, 0x1D14202910527A9A, 0x93366450E42ECDF0, 0xD1C685BB4DC4FB63, - 0x16D7A787B7FAA0D6, 0x5427466C1E109645, 0x4863CE9FF6E9F891, 0x0A932F745F03CE02, 0xCD820D48A53D95B7, 0x8F72ECA30CD7A324, - 0x0150A8DAF8AB144E, 0x43A04931514122DD, 0x84B16B0DAB7F7968, 0xC6418AE602954FFB, 0xBC387AEA7A8DA4C0, 0xFEC89B01D3679253, - 0x39D9B93D2959C9E6, 0x7B2958D680B3FF75, 0xF50B1CAF74CF481F, 0xB7FBFD44DD257E8C, 0x70EADF78271B2539, 0x321A3E938EF113AA, - 0x2E5EB66066087D7E, 0x6CAE578BCFE24BED, 0xABBF75B735DC1058, 0xE94F945C9C3626CB, 0x676DD025684A91A1, 0x259D31CEC1A0A732, - 0xE28C13F23B9EFC87, 0xA07CF2199274CA14, 0x167FF3EACBAF2AF1, 0x548F120162451C62, 0x939E303D987B47D7, 0xD16ED1D631917144, - 0x5F4C95AFC5EDC62E, 0x1DBC74446C07F0BD, 0xDAAD56789639AB08, 0x985DB7933FD39D9B, 0x84193F60D72AF34F, 0xC6E9DE8B7EC0C5DC, - 0x01F8FCB784FE9E69, 0x43081D5C2D14A8FA, 0xCD2A5925D9681F90, 0x8FDAB8CE70822903, 0x48CB9AF28ABC72B6, 0x0A3B7B1923564425, - 0x70428B155B4EAF1E, 0x32B26AFEF2A4998D, 0xF5A348C2089AC238, 0xB753A929A170F4AB, 0x3971ED50550C43C1, 0x7B810CBBFCE67552, - 0xBC902E8706D82EE7, 0xFE60CF6CAF321874, 0xE224479F47CB76A0, 0xA0D4A674EE214033, 0x67C58448141F1B86, 0x253565A3BDF52D15, - 0xAB1721DA49899A7F, 0xE9E7C031E063ACEC, 0x2EF6E20D1A5DF759, 0x6C0603E6B3B7C1CA, 0xF6FAE5C07D3274CD, 0xB40A042BD4D8425E, - 0x731B26172EE619EB, 0x31EBC7FC870C2F78, 0xBFC9838573709812, 0xFD39626EDA9AAE81, 0x3A28405220A4F534, 0x78D8A1B9894EC3A7, - 0x649C294A61B7AD73, 0x266CC8A1C85D9BE0, 0xE17DEA9D3263C055, 0xA38D0B769B89F6C6, 0x2DAF4F0F6FF541AC, 0x6F5FAEE4C61F773F, - 0xA84E8CD83C212C8A, 0xEABE6D3395CB1A19, 0x90C79D3FEDD3F122, 0xD2377CD44439C7B1, 0x15265EE8BE079C04, 0x57D6BF0317EDAA97, - 0xD9F4FB7AE3911DFD, 0x9B041A914A7B2B6E, 0x5C1538ADB04570DB, 0x1EE5D94619AF4648, 0x02A151B5F156289C, 0x4051B05E58BC1E0F, - 0x87409262A28245BA, 0xC5B073890B687329, 0x4B9237F0FF14C443, 0x0962D61B56FEF2D0, 0xCE73F427ACC0A965, 0x8C8315CC052A9FF6, - 0x3A80143F5CF17F13, 0x7870F5D4F51B4980, 0xBF61D7E80F251235, 0xFD913603A6CF24A6, 0x73B3727A52B393CC, 0x31439391FB59A55F, - 0xF652B1AD0167FEEA, 0xB4A25046A88DC879, 0xA8E6D8B54074A6AD, 0xEA16395EE99E903E, 0x2D071B6213A0CB8B, 0x6FF7FA89BA4AFD18, - 0xE1D5BEF04E364A72, 0xA3255F1BE7DC7CE1, 0x64347D271DE22754, 0x26C49CCCB40811C7, 0x5CBD6CC0CC10FAFC, 0x1E4D8D2B65FACC6F, - 0xD95CAF179FC497DA, 0x9BAC4EFC362EA149, 0x158E0A85C2521623, 0x577EEB6E6BB820B0, 0x906FC95291867B05, 0xD29F28B9386C4D96, - 0xCEDBA04AD0952342, 0x8C2B41A1797F15D1, 0x4B3A639D83414E64, 0x09CA82762AAB78F7, 0x87E8C60FDED7CF9D, 0xC51827E4773DF90E, - 0x020905D88D03A2BB, 0x40F9E43324E99428, 0x2CFFE7D5975E55E2, 0x6E0F063E3EB46371, 0xA91E2402C48A38C4, 0xEBEEC5E96D600E57, - 0x65CC8190991CB93D, 0x273C607B30F68FAE, 0xE02D4247CAC8D41B, 0xA2DDA3AC6322E288, 0xBE992B5F8BDB8C5C, 0xFC69CAB42231BACF, - 0x3B78E888D80FE17A, 0x7988096371E5D7E9, 0xF7AA4D1A85996083, 0xB55AACF12C735610, 0x724B8ECDD64D0DA5, 0x30BB6F267FA73B36, - 0x4AC29F2A07BFD00D, 0x08327EC1AE55E69E, 0xCF235CFD546BBD2B, 0x8DD3BD16FD818BB8, 0x03F1F96F09FD3CD2, 0x41011884A0170A41, - 0x86103AB85A2951F4, 0xC4E0DB53F3C36767, 0xD8A453A01B3A09B3, 0x9A54B24BB2D03F20, 0x5D45907748EE6495, 0x1FB5719CE1045206, - 0x919735E51578E56C, 0xD367D40EBC92D3FF, 0x1476F63246AC884A, 0x568617D9EF46BED9, 0xE085162AB69D5E3C, 0xA275F7C11F7768AF, - 0x6564D5FDE549331A, 0x279434164CA30589, 0xA9B6706FB8DFB2E3, 0xEB46918411358470, 0x2C57B3B8EB0BDFC5, 0x6EA7525342E1E956, - 0x72E3DAA0AA188782, 0x30133B4B03F2B111, 0xF7021977F9CCEAA4, 0xB5F2F89C5026DC37, 0x3BD0BCE5A45A6B5D, 0x79205D0E0DB05DCE, - 0xBE317F32F78E067B, 0xFCC19ED95E6430E8, 0x86B86ED5267CDBD3, 0xC4488F3E8F96ED40, 0x0359AD0275A8B6F5, 0x41A94CE9DC428066, - 0xCF8B0890283E370C, 0x8D7BE97B81D4019F, 0x4A6ACB477BEA5A2A, 0x089A2AACD2006CB9, 0x14DEA25F3AF9026D, 0x562E43B4931334FE, - 0x913F6188692D6F4B, 0xD3CF8063C0C759D8, 0x5DEDC41A34BBEEB2, 0x1F1D25F19D51D821, 0xD80C07CD676F8394, 0x9AFCE626CE85B507 }; - -std::uint64_t math::crc64(const void* buffer, std::size_t size, std::uint64_t combine) -{ - auto data = reinterpret_cast<const std::uint8_t*>(buffer); - for(std::size_t i = 0; i < size; ++i) - combine = crc_table[((combine >> 56) ^ data[i]) & 0xFF] ^ (combine << 8); - return combine; -} - -std::uint64_t math::crc64(const std::vector<std::byte>& buffer, std::uint64_t combine) -{ - return math::crc64(buffer.data(), buffer.size(), combine); -} - -std::uint64_t math::crc64(const std::string& buffer, std::uint64_t combine) -{ - return math::crc64(buffer.data(), buffer.size(), combine); -} +#include "core/pch.hh"
+
+#include "core/math/crc64.hh"
+
+// The lookup table for CRC64 checksum; this lookup
+// table is generated using ECMA-182 compilant parameters:
+// - Polynomial: `0x42F0E1EBA9EA3693`
+// - Initial value: `0x0000000000000000`
+// - Final xor: `0x0000000000000000`
+// CRC Calculator: https://www.sunshine2k.de/coding/javascript/crc/crc_js.html
+constexpr static const std::uint64_t crc_table[256] = { 0x0000000000000000, 0x42F0E1EBA9EA3693, 0x85E1C3D753D46D26, 0xC711223CFA3E5BB5,
+ 0x493366450E42ECDF, 0x0BC387AEA7A8DA4C, 0xCCD2A5925D9681F9, 0x8E224479F47CB76A, 0x9266CC8A1C85D9BE, 0xD0962D61B56FEF2D,
+ 0x17870F5D4F51B498, 0x5577EEB6E6BB820B, 0xDB55AACF12C73561, 0x99A54B24BB2D03F2, 0x5EB4691841135847, 0x1C4488F3E8F96ED4,
+ 0x663D78FF90E185EF, 0x24CD9914390BB37C, 0xE3DCBB28C335E8C9, 0xA12C5AC36ADFDE5A, 0x2F0E1EBA9EA36930, 0x6DFEFF5137495FA3,
+ 0xAAEFDD6DCD770416, 0xE81F3C86649D3285, 0xF45BB4758C645C51, 0xB6AB559E258E6AC2, 0x71BA77A2DFB03177, 0x334A9649765A07E4,
+ 0xBD68D2308226B08E, 0xFF9833DB2BCC861D, 0x388911E7D1F2DDA8, 0x7A79F00C7818EB3B, 0xCC7AF1FF21C30BDE, 0x8E8A101488293D4D,
+ 0x499B3228721766F8, 0x0B6BD3C3DBFD506B, 0x854997BA2F81E701, 0xC7B97651866BD192, 0x00A8546D7C558A27, 0x4258B586D5BFBCB4,
+ 0x5E1C3D753D46D260, 0x1CECDC9E94ACE4F3, 0xDBFDFEA26E92BF46, 0x990D1F49C77889D5, 0x172F5B3033043EBF, 0x55DFBADB9AEE082C,
+ 0x92CE98E760D05399, 0xD03E790CC93A650A, 0xAA478900B1228E31, 0xE8B768EB18C8B8A2, 0x2FA64AD7E2F6E317, 0x6D56AB3C4B1CD584,
+ 0xE374EF45BF6062EE, 0xA1840EAE168A547D, 0x66952C92ECB40FC8, 0x2465CD79455E395B, 0x3821458AADA7578F, 0x7AD1A461044D611C,
+ 0xBDC0865DFE733AA9, 0xFF3067B657990C3A, 0x711223CFA3E5BB50, 0x33E2C2240A0F8DC3, 0xF4F3E018F031D676, 0xB60301F359DBE0E5,
+ 0xDA050215EA6C212F, 0x98F5E3FE438617BC, 0x5FE4C1C2B9B84C09, 0x1D14202910527A9A, 0x93366450E42ECDF0, 0xD1C685BB4DC4FB63,
+ 0x16D7A787B7FAA0D6, 0x5427466C1E109645, 0x4863CE9FF6E9F891, 0x0A932F745F03CE02, 0xCD820D48A53D95B7, 0x8F72ECA30CD7A324,
+ 0x0150A8DAF8AB144E, 0x43A04931514122DD, 0x84B16B0DAB7F7968, 0xC6418AE602954FFB, 0xBC387AEA7A8DA4C0, 0xFEC89B01D3679253,
+ 0x39D9B93D2959C9E6, 0x7B2958D680B3FF75, 0xF50B1CAF74CF481F, 0xB7FBFD44DD257E8C, 0x70EADF78271B2539, 0x321A3E938EF113AA,
+ 0x2E5EB66066087D7E, 0x6CAE578BCFE24BED, 0xABBF75B735DC1058, 0xE94F945C9C3626CB, 0x676DD025684A91A1, 0x259D31CEC1A0A732,
+ 0xE28C13F23B9EFC87, 0xA07CF2199274CA14, 0x167FF3EACBAF2AF1, 0x548F120162451C62, 0x939E303D987B47D7, 0xD16ED1D631917144,
+ 0x5F4C95AFC5EDC62E, 0x1DBC74446C07F0BD, 0xDAAD56789639AB08, 0x985DB7933FD39D9B, 0x84193F60D72AF34F, 0xC6E9DE8B7EC0C5DC,
+ 0x01F8FCB784FE9E69, 0x43081D5C2D14A8FA, 0xCD2A5925D9681F90, 0x8FDAB8CE70822903, 0x48CB9AF28ABC72B6, 0x0A3B7B1923564425,
+ 0x70428B155B4EAF1E, 0x32B26AFEF2A4998D, 0xF5A348C2089AC238, 0xB753A929A170F4AB, 0x3971ED50550C43C1, 0x7B810CBBFCE67552,
+ 0xBC902E8706D82EE7, 0xFE60CF6CAF321874, 0xE224479F47CB76A0, 0xA0D4A674EE214033, 0x67C58448141F1B86, 0x253565A3BDF52D15,
+ 0xAB1721DA49899A7F, 0xE9E7C031E063ACEC, 0x2EF6E20D1A5DF759, 0x6C0603E6B3B7C1CA, 0xF6FAE5C07D3274CD, 0xB40A042BD4D8425E,
+ 0x731B26172EE619EB, 0x31EBC7FC870C2F78, 0xBFC9838573709812, 0xFD39626EDA9AAE81, 0x3A28405220A4F534, 0x78D8A1B9894EC3A7,
+ 0x649C294A61B7AD73, 0x266CC8A1C85D9BE0, 0xE17DEA9D3263C055, 0xA38D0B769B89F6C6, 0x2DAF4F0F6FF541AC, 0x6F5FAEE4C61F773F,
+ 0xA84E8CD83C212C8A, 0xEABE6D3395CB1A19, 0x90C79D3FEDD3F122, 0xD2377CD44439C7B1, 0x15265EE8BE079C04, 0x57D6BF0317EDAA97,
+ 0xD9F4FB7AE3911DFD, 0x9B041A914A7B2B6E, 0x5C1538ADB04570DB, 0x1EE5D94619AF4648, 0x02A151B5F156289C, 0x4051B05E58BC1E0F,
+ 0x87409262A28245BA, 0xC5B073890B687329, 0x4B9237F0FF14C443, 0x0962D61B56FEF2D0, 0xCE73F427ACC0A965, 0x8C8315CC052A9FF6,
+ 0x3A80143F5CF17F13, 0x7870F5D4F51B4980, 0xBF61D7E80F251235, 0xFD913603A6CF24A6, 0x73B3727A52B393CC, 0x31439391FB59A55F,
+ 0xF652B1AD0167FEEA, 0xB4A25046A88DC879, 0xA8E6D8B54074A6AD, 0xEA16395EE99E903E, 0x2D071B6213A0CB8B, 0x6FF7FA89BA4AFD18,
+ 0xE1D5BEF04E364A72, 0xA3255F1BE7DC7CE1, 0x64347D271DE22754, 0x26C49CCCB40811C7, 0x5CBD6CC0CC10FAFC, 0x1E4D8D2B65FACC6F,
+ 0xD95CAF179FC497DA, 0x9BAC4EFC362EA149, 0x158E0A85C2521623, 0x577EEB6E6BB820B0, 0x906FC95291867B05, 0xD29F28B9386C4D96,
+ 0xCEDBA04AD0952342, 0x8C2B41A1797F15D1, 0x4B3A639D83414E64, 0x09CA82762AAB78F7, 0x87E8C60FDED7CF9D, 0xC51827E4773DF90E,
+ 0x020905D88D03A2BB, 0x40F9E43324E99428, 0x2CFFE7D5975E55E2, 0x6E0F063E3EB46371, 0xA91E2402C48A38C4, 0xEBEEC5E96D600E57,
+ 0x65CC8190991CB93D, 0x273C607B30F68FAE, 0xE02D4247CAC8D41B, 0xA2DDA3AC6322E288, 0xBE992B5F8BDB8C5C, 0xFC69CAB42231BACF,
+ 0x3B78E888D80FE17A, 0x7988096371E5D7E9, 0xF7AA4D1A85996083, 0xB55AACF12C735610, 0x724B8ECDD64D0DA5, 0x30BB6F267FA73B36,
+ 0x4AC29F2A07BFD00D, 0x08327EC1AE55E69E, 0xCF235CFD546BBD2B, 0x8DD3BD16FD818BB8, 0x03F1F96F09FD3CD2, 0x41011884A0170A41,
+ 0x86103AB85A2951F4, 0xC4E0DB53F3C36767, 0xD8A453A01B3A09B3, 0x9A54B24BB2D03F20, 0x5D45907748EE6495, 0x1FB5719CE1045206,
+ 0x919735E51578E56C, 0xD367D40EBC92D3FF, 0x1476F63246AC884A, 0x568617D9EF46BED9, 0xE085162AB69D5E3C, 0xA275F7C11F7768AF,
+ 0x6564D5FDE549331A, 0x279434164CA30589, 0xA9B6706FB8DFB2E3, 0xEB46918411358470, 0x2C57B3B8EB0BDFC5, 0x6EA7525342E1E956,
+ 0x72E3DAA0AA188782, 0x30133B4B03F2B111, 0xF7021977F9CCEAA4, 0xB5F2F89C5026DC37, 0x3BD0BCE5A45A6B5D, 0x79205D0E0DB05DCE,
+ 0xBE317F32F78E067B, 0xFCC19ED95E6430E8, 0x86B86ED5267CDBD3, 0xC4488F3E8F96ED40, 0x0359AD0275A8B6F5, 0x41A94CE9DC428066,
+ 0xCF8B0890283E370C, 0x8D7BE97B81D4019F, 0x4A6ACB477BEA5A2A, 0x089A2AACD2006CB9, 0x14DEA25F3AF9026D, 0x562E43B4931334FE,
+ 0x913F6188692D6F4B, 0xD3CF8063C0C759D8, 0x5DEDC41A34BBEEB2, 0x1F1D25F19D51D821, 0xD80C07CD676F8394, 0x9AFCE626CE85B507 };
+
+std::uint64_t math::crc64(const void* buffer, std::size_t size, std::uint64_t combine)
+{
+ auto data = reinterpret_cast<const std::uint8_t*>(buffer);
+ for(std::size_t i = 0; i < size; ++i)
+ combine = crc_table[((combine >> 56) ^ data[i]) & 0xFF] ^ (combine << 8);
+ return combine;
+}
+
+std::uint64_t math::crc64(const std::vector<std::byte>& buffer, std::uint64_t combine)
+{
+ return math::crc64(buffer.data(), buffer.size(), combine);
+}
+
+std::uint64_t math::crc64(const std::string& buffer, std::uint64_t combine)
+{
+ return math::crc64(buffer.data(), buffer.size(), combine);
+}
diff --git a/core/math/crc64.hh b/core/math/crc64.hh index 4dc484d..f68c951 100644 --- a/core/math/crc64.hh +++ b/core/math/crc64.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace math -{ -std::uint64_t crc64(const void* buffer, std::size_t size, std::uint64_t combine = UINT64_C(0)); -std::uint64_t crc64(const std::vector<std::byte>& buffer, std::uint64_t combine = UINT64_C(0)); -std::uint64_t crc64(const std::string& buffer, std::uint64_t combine = UINT64_C(0)); -} // namespace math +#pragma once
+
+namespace math
+{
+std::uint64_t crc64(const void* buffer, std::size_t size, std::uint64_t combine = UINT64_C(0));
+std::uint64_t crc64(const std::vector<std::byte>& buffer, std::uint64_t combine = UINT64_C(0));
+std::uint64_t crc64(const std::string& buffer, std::uint64_t combine = UINT64_C(0));
+} // namespace math
diff --git a/core/math/randomizer.hh b/core/math/randomizer.hh index 04665f9..db21c95 100644 --- a/core/math/randomizer.hh +++ b/core/math/randomizer.hh @@ -1,56 +1,56 @@ -#pragma once - -namespace math -{ -template<typename T> -class Randomizer final { -public: - explicit Randomizer(void); - explicit Randomizer(std::uint64_t seed); - virtual ~Randomizer(void) = default; - void add(const T& value); - const T& get(void); - void clear(void); - -private: - std::vector<T> m_vector; - std::mt19937_64 m_twister; - std::uniform_int_distribution<std::size_t> m_dist; -}; -} // namespace math - -template<typename T> -inline math::Randomizer<T>::Randomizer(void) -{ - m_vector.clear(); - m_twister.seed(std::random_device()()); - m_dist = std::uniform_int_distribution<std::size_t>(0, 0); -} - -template<typename T> -inline math::Randomizer<T>::Randomizer(std::uint64_t seed) -{ - m_vector.clear(); - m_twister.seed(seed); - m_dist = std::uniform_int_distribution<std::size_t>(0, 0); -} - -template<typename T> -inline void math::Randomizer<T>::add(const T& value) -{ - m_vector.push_back(value); - m_dist = std::uniform_int_distribution<std::size_t>(0, m_vector.size() - 1); -} - -template<typename T> -inline const T& math::Randomizer<T>::get(void) -{ - return m_vector.at(m_dist(m_twister)); -} - -template<typename T> -inline void math::Randomizer<T>::clear(void) -{ - m_vector.clear(); - m_dist = std::uniform_int_distribution<std::size_t>(0, 0); -} +#pragma once
+
+namespace math
+{
+template<typename T>
+class Randomizer final {
+public:
+ explicit Randomizer(void);
+ explicit Randomizer(std::uint64_t seed);
+ virtual ~Randomizer(void) = default;
+ void add(const T& value);
+ const T& get(void);
+ void clear(void);
+
+private:
+ std::vector<T> m_vector;
+ std::mt19937_64 m_twister;
+ std::uniform_int_distribution<std::size_t> m_dist;
+};
+} // namespace math
+
+template<typename T>
+inline math::Randomizer<T>::Randomizer(void)
+{
+ m_vector.clear();
+ m_twister.seed(std::random_device()());
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
+
+template<typename T>
+inline math::Randomizer<T>::Randomizer(std::uint64_t seed)
+{
+ m_vector.clear();
+ m_twister.seed(seed);
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
+
+template<typename T>
+inline void math::Randomizer<T>::add(const T& value)
+{
+ m_vector.push_back(value);
+ m_dist = std::uniform_int_distribution<std::size_t>(0, m_vector.size() - 1);
+}
+
+template<typename T>
+inline const T& math::Randomizer<T>::get(void)
+{
+ return m_vector.at(m_dist(m_twister));
+}
+
+template<typename T>
+inline void math::Randomizer<T>::clear(void)
+{
+ m_vector.clear();
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
diff --git a/core/math/vectors.hh b/core/math/vectors.hh index 73c9ab0..9b9e762 100644 --- a/core/math/vectors.hh +++ b/core/math/vectors.hh @@ -1,43 +1,43 @@ -#pragma once - -#include "core/math/concepts.hh" - -// core/vectors.hh - because NO ONE would POSSIBLY -// need integer-based distance calculations in a -// game about voxels. That would be INSANE! :D - -namespace math -{ -template<math::Arithmetic T> -constexpr static inline const T length2(const glm::vec<2, T>& vector); -template<math::Arithmetic T> -constexpr static inline const T length2(const glm::vec<3, T>& vector); -template<math::Arithmetic T> -constexpr static inline const T distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b); -template<math::Arithmetic T> -constexpr static inline const T distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b); -} // namespace math - -template<math::Arithmetic T> -constexpr static inline const T math::length2(const glm::vec<2, T>& vector) -{ - return (vector.x * vector.x) + (vector.y * vector.y); -} - -template<math::Arithmetic T> -constexpr static inline const T math::length2(const glm::vec<3, T>& vector) -{ - return (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z); -} - -template<math::Arithmetic T> -constexpr static inline const T math::distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b) -{ - return math::length2(vector_a - vector_b); -} - -template<math::Arithmetic T> -constexpr static inline const T math::distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b) -{ - return math::length2(vector_a - vector_b); -} +#pragma once
+
+#include "core/math/concepts.hh"
+
+// core/vectors.hh - because NO ONE would POSSIBLY
+// need integer-based distance calculations in a
+// game about voxels. That would be INSANE! :D
+
+namespace math
+{
+template<math::Arithmetic T>
+constexpr static inline const T length2(const glm::vec<2, T>& vector);
+template<math::Arithmetic T>
+constexpr static inline const T length2(const glm::vec<3, T>& vector);
+template<math::Arithmetic T>
+constexpr static inline const T distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b);
+template<math::Arithmetic T>
+constexpr static inline const T distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b);
+} // namespace math
+
+template<math::Arithmetic T>
+constexpr static inline const T math::length2(const glm::vec<2, T>& vector)
+{
+ return (vector.x * vector.x) + (vector.y * vector.y);
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::length2(const glm::vec<3, T>& vector)
+{
+ return (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z);
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b)
+{
+ return math::length2(vector_a - vector_b);
+}
+
+template<math::Arithmetic T>
+constexpr static inline const T math::distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b)
+{
+ return math::length2(vector_a - vector_b);
+}
diff --git a/core/pch.hh b/core/pch.hh index 17417a5..410b819 100644 --- a/core/pch.hh +++ b/core/pch.hh @@ -1,49 +1,49 @@ -#pragma once - -#include <cinttypes> -#include <cmath> -#include <cstdarg> -#include <cstddef> -#include <cstdint> - -#include <algorithm> -#include <chrono> -#include <concepts> -#include <filesystem> -#include <format> -#include <iostream> -#include <limits> -#include <mutex> -#include <random> -#include <sstream> -#include <stdexcept> -#include <thread> -#include <type_traits> -#include <typeindex> -#include <unordered_map> -#include <unordered_set> -#include <vector> - -#include <BS_thread_pool.hpp> - -#include <emhash/hash_table8.hpp> - -#include <enet/enet.h> - -#include <glm/fwd.hpp> - -#include <glm/mat4x4.hpp> -#include <glm/vec2.hpp> -#include <glm/vec3.hpp> -#include <glm/vec4.hpp> - -#include <glm/gtc/matrix_transform.hpp> -#include <glm/gtc/quaternion.hpp> -#include <glm/gtc/type_ptr.hpp> - -#include <physfs.h> - -#include <spdlog/spdlog.h> - -#include <stb_image.h> -#include <stb_image_write.h> +#pragma once
+
+#include <cinttypes>
+#include <cmath>
+#include <cstdarg>
+#include <cstddef>
+#include <cstdint>
+
+#include <algorithm>
+#include <chrono>
+#include <concepts>
+#include <filesystem>
+#include <format>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <random>
+#include <sstream>
+#include <stdexcept>
+#include <thread>
+#include <type_traits>
+#include <typeindex>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <BS_thread_pool.hpp>
+
+#include <emhash/hash_table8.hpp>
+
+#include <enet/enet.h>
+
+#include <glm/fwd.hpp>
+
+#include <glm/mat4x4.hpp>
+#include <glm/vec2.hpp>
+#include <glm/vec3.hpp>
+#include <glm/vec4.hpp>
+
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/quaternion.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#include <physfs.h>
+
+#include <spdlog/spdlog.h>
+
+#include <stb_image.h>
+#include <stb_image_write.h>
diff --git a/core/resource/image.cc b/core/resource/image.cc index d5e256d..45fe96e 100644 --- a/core/resource/image.cc +++ b/core/resource/image.cc @@ -1,81 +1,81 @@ -#include "core/pch.hh" - -#include "core/resource/image.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/physfs.hh" - -static int stbi_physfs_read(void* context, char* data, int size) -{ - return PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(context), data, size); -} - -static void stbi_physfs_skip(void* context, int count) -{ - auto file = reinterpret_cast<PHYSFS_File*>(context); - PHYSFS_seek(file, PHYSFS_tell(file) + count); -} - -static int stbi_physfs_eof(void* context) -{ - return PHYSFS_eof(reinterpret_cast<PHYSFS_File*>(context)); -} - -static const void* image_load_func(const char* name, std::uint32_t flags) -{ - assert(name); - - stbi_io_callbacks callbacks; - callbacks.read = &stbi_physfs_read; - callbacks.skip = &stbi_physfs_skip; - callbacks.eof = &stbi_physfs_eof; - - stbi_set_flip_vertically_on_load(bool(flags & IMAGE_LOAD_FLIP)); - - auto file = PHYSFS_openRead(name); - - if(file == nullptr) { - spdlog::error("image: {}: {}", name, utils::physfs_error()); - return nullptr; - } - - int desired_channels; - - if(flags & IMAGE_LOAD_GRAY) { - desired_channels = STBI_grey; - } - else { - desired_channels = STBI_rgb_alpha; - } - - int width, height, channels; - auto pixels = stbi_load_from_callbacks(&callbacks, file, &width, &height, &channels, desired_channels); - - PHYSFS_close(file); - - if(pixels == nullptr) { - spdlog::error("image: {}: {}", name, stbi_failure_reason()); - return nullptr; - } - - auto image = new Image; - image->pixels = pixels; - image->size = glm::ivec2(width, height); - return image; -} - -static void image_free_func(const void* resource) -{ - assert(resource); - - auto image = reinterpret_cast<const Image*>(resource); - stbi_image_free(image->pixels); - - delete image; -} - -void Image::register_resource(void) -{ - resource::register_loader<Image>(&image_load_func, &image_free_func); -} +#include "core/pch.hh"
+
+#include "core/resource/image.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/physfs.hh"
+
+static int stbi_physfs_read(void* context, char* data, int size)
+{
+ return PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(context), data, size);
+}
+
+static void stbi_physfs_skip(void* context, int count)
+{
+ auto file = reinterpret_cast<PHYSFS_File*>(context);
+ PHYSFS_seek(file, PHYSFS_tell(file) + count);
+}
+
+static int stbi_physfs_eof(void* context)
+{
+ return PHYSFS_eof(reinterpret_cast<PHYSFS_File*>(context));
+}
+
+static const void* image_load_func(const char* name, std::uint32_t flags)
+{
+ assert(name);
+
+ stbi_io_callbacks callbacks;
+ callbacks.read = &stbi_physfs_read;
+ callbacks.skip = &stbi_physfs_skip;
+ callbacks.eof = &stbi_physfs_eof;
+
+ stbi_set_flip_vertically_on_load(bool(flags & IMAGE_LOAD_FLIP));
+
+ auto file = PHYSFS_openRead(name);
+
+ if(file == nullptr) {
+ spdlog::error("image: {}: {}", name, utils::physfs_error());
+ return nullptr;
+ }
+
+ int desired_channels;
+
+ if(flags & IMAGE_LOAD_GRAY) {
+ desired_channels = STBI_grey;
+ }
+ else {
+ desired_channels = STBI_rgb_alpha;
+ }
+
+ int width, height, channels;
+ auto pixels = stbi_load_from_callbacks(&callbacks, file, &width, &height, &channels, desired_channels);
+
+ PHYSFS_close(file);
+
+ if(pixels == nullptr) {
+ spdlog::error("image: {}: {}", name, stbi_failure_reason());
+ return nullptr;
+ }
+
+ auto image = new Image;
+ image->pixels = pixels;
+ image->size = glm::ivec2(width, height);
+ return image;
+}
+
+static void image_free_func(const void* resource)
+{
+ assert(resource);
+
+ auto image = reinterpret_cast<const Image*>(resource);
+ stbi_image_free(image->pixels);
+
+ delete image;
+}
+
+void Image::register_resource(void)
+{
+ resource::register_loader<Image>(&image_load_func, &image_free_func);
+}
diff --git a/core/resource/image.hh b/core/resource/image.hh index 575591f..8867b97 100644 --- a/core/resource/image.hh +++ b/core/resource/image.hh @@ -1,11 +1,11 @@ -#pragma once - -constexpr static unsigned int IMAGE_LOAD_GRAY = 0x0001U; -constexpr static unsigned int IMAGE_LOAD_FLIP = 0x0002U; - -struct Image final { - static void register_resource(void); - - stbi_uc* pixels; - glm::ivec2 size; -}; +#pragma once
+
+constexpr static unsigned int IMAGE_LOAD_GRAY = 0x0001U;
+constexpr static unsigned int IMAGE_LOAD_FLIP = 0x0002U;
+
+struct Image final {
+ static void register_resource(void);
+
+ stbi_uc* pixels;
+ glm::ivec2 size;
+};
diff --git a/core/resource/resource.cc b/core/resource/resource.cc index 600843f..21d1e4e 100644 --- a/core/resource/resource.cc +++ b/core/resource/resource.cc @@ -1,129 +1,129 @@ -#include "core/pch.hh" - -#include "core/resource/resource.hh" - -struct ResourceLoader final { - ResourceLoadFunc load_func; - ResourceFreeFunc free_func; - emhash8::HashMap<std::string, std::shared_ptr<const void>> resources; - std::vector<std::shared_ptr<const void>> cache; - std::string class_name; -}; - -namespace -{ -emhash8::HashMap<std::type_index, std::unique_ptr<ResourceLoader>> loaders; -} // namespace - -void resource::detail::register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func) -{ - assert(load_func); - assert(free_func); - - auto type_index = std::type_index(type); - auto loader = std::make_unique<ResourceLoader>(); - loader->class_name = type.name(); - loader->load_func = load_func; - loader->free_func = free_func; - - assert(!loaders.contains(type_index)); - - loaders.insert_or_assign(type_index, std::move(loader)); -} - -std::shared_ptr<const void> resource::detail::load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags) -{ - auto name_str = std::string(name); - auto type_index = std::type_index(type); - auto loader = loaders.find(type_index); - - if(loader == loaders.cend()) { - spdlog::error("resource: no loader registered for type [{}]", type.name()); - return nullptr; - } - - auto resource_it = loader->second->resources.find(name_str); - - if(resource_it == loader->second->resources.cend()) { - auto resource_raw = loader->second->load_func(name_str.c_str(), flags); - - if(resource_raw == nullptr) { - spdlog::error("resource: {} [{}]: load failed", loader->second->class_name, name); - return nullptr; - } - - std::shared_ptr<const void> resource_ptr(resource_raw, [](const void* ptr) { /* empty */ }); - auto loaded_it = loader->second->resources.insert_or_assign(name_str, std::move(resource_ptr)); - - if(flags & RESOURCE_CACHE) { - loader->second->cache.push_back(loaded_it.first->second); - } - - return loaded_it.first->second; - } - - return resource_it->second; -} - -std::shared_ptr<const void> resource::detail::find_resource(const std::type_info& type, std::string_view name) -{ - auto name_str = std::string(name); - auto type_index = std::type_index(type); - auto loader = loaders.find(type_index); - - if(loader == loaders.cend()) { - spdlog::error("resource: no loader registered for type [{}]", type.name()); - return nullptr; - } - - auto resource_it = loader->second->resources.find(name_str); - - if(resource_it == loader->second->resources.cend()) { - spdlog::error("resource: {} [{}]: not found", loader->second->class_name, name); - return nullptr; - } - - return resource_it->second; -} - -void resource::hard_cleanup(void) -{ - for(auto& [type_index, loader] : loaders) { - loader->cache.clear(); - - for(auto& [name, resource_ptr] : loader->resources) { - if(resource_ptr.use_count() > 1) { - spdlog::warn("resource: zombie resource: {} [{}] [use_count={}]", name, loader->class_name, resource_ptr.use_count()); - } - else { - spdlog::debug("resource: releasing {} [{}]", name, loader->class_name); - } - - loader->free_func(resource_ptr.get()); - } - - loader->resources.clear(); - } - - loaders.clear(); -} - -void resource::soft_cleanup(void) -{ - for(auto& [type_index, loader] : loaders) { - auto resource_it = loader->resources.begin(); - - while(resource_it != loader->resources.end()) { - if(resource_it->second.use_count() <= 1) { - spdlog::debug("resource: releasing {} [{}]", resource_it->first, loader->class_name); - - loader->free_func(resource_it->second.get()); - resource_it = loader->resources.erase(resource_it); - - continue; - } - - resource_it = std::next(resource_it); - } - } -} +#include "core/pch.hh"
+
+#include "core/resource/resource.hh"
+
+struct ResourceLoader final {
+ ResourceLoadFunc load_func;
+ ResourceFreeFunc free_func;
+ emhash8::HashMap<std::string, std::shared_ptr<const void>> resources;
+ std::vector<std::shared_ptr<const void>> cache;
+ std::string class_name;
+};
+
+namespace
+{
+emhash8::HashMap<std::type_index, std::unique_ptr<ResourceLoader>> loaders;
+} // namespace
+
+void resource::detail::register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func)
+{
+ assert(load_func);
+ assert(free_func);
+
+ auto type_index = std::type_index(type);
+ auto loader = std::make_unique<ResourceLoader>();
+ loader->class_name = type.name();
+ loader->load_func = load_func;
+ loader->free_func = free_func;
+
+ assert(!loaders.contains(type_index));
+
+ loaders.insert_or_assign(type_index, std::move(loader));
+}
+
+std::shared_ptr<const void> resource::detail::load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags)
+{
+ auto name_str = std::string(name);
+ auto type_index = std::type_index(type);
+ auto loader = loaders.find(type_index);
+
+ if(loader == loaders.cend()) {
+ spdlog::error("resource: no loader registered for type [{}]", type.name());
+ return nullptr;
+ }
+
+ auto resource_it = loader->second->resources.find(name_str);
+
+ if(resource_it == loader->second->resources.cend()) {
+ auto resource_raw = loader->second->load_func(name_str.c_str(), flags);
+
+ if(resource_raw == nullptr) {
+ spdlog::error("resource: {} [{}]: load failed", loader->second->class_name, name);
+ return nullptr;
+ }
+
+ std::shared_ptr<const void> resource_ptr(resource_raw, [](const void* ptr) { /* empty */ });
+ auto loaded_it = loader->second->resources.insert_or_assign(name_str, std::move(resource_ptr));
+
+ if(flags & RESOURCE_CACHE) {
+ loader->second->cache.push_back(loaded_it.first->second);
+ }
+
+ return loaded_it.first->second;
+ }
+
+ return resource_it->second;
+}
+
+std::shared_ptr<const void> resource::detail::find_resource(const std::type_info& type, std::string_view name)
+{
+ auto name_str = std::string(name);
+ auto type_index = std::type_index(type);
+ auto loader = loaders.find(type_index);
+
+ if(loader == loaders.cend()) {
+ spdlog::error("resource: no loader registered for type [{}]", type.name());
+ return nullptr;
+ }
+
+ auto resource_it = loader->second->resources.find(name_str);
+
+ if(resource_it == loader->second->resources.cend()) {
+ spdlog::error("resource: {} [{}]: not found", loader->second->class_name, name);
+ return nullptr;
+ }
+
+ return resource_it->second;
+}
+
+void resource::hard_cleanup(void)
+{
+ for(auto& [type_index, loader] : loaders) {
+ loader->cache.clear();
+
+ for(auto& [name, resource_ptr] : loader->resources) {
+ if(resource_ptr.use_count() > 1) {
+ spdlog::warn("resource: zombie resource: {} [{}] [use_count={}]", name, loader->class_name, resource_ptr.use_count());
+ }
+ else {
+ spdlog::debug("resource: releasing {} [{}]", name, loader->class_name);
+ }
+
+ loader->free_func(resource_ptr.get());
+ }
+
+ loader->resources.clear();
+ }
+
+ loaders.clear();
+}
+
+void resource::soft_cleanup(void)
+{
+ for(auto& [type_index, loader] : loaders) {
+ auto resource_it = loader->resources.begin();
+
+ while(resource_it != loader->resources.end()) {
+ if(resource_it->second.use_count() <= 1) {
+ spdlog::debug("resource: releasing {} [{}]", resource_it->first, loader->class_name);
+
+ loader->free_func(resource_it->second.get());
+ resource_it = loader->resources.erase(resource_it);
+
+ continue;
+ }
+
+ resource_it = std::next(resource_it);
+ }
+ }
+}
diff --git a/core/resource/resource.hh b/core/resource/resource.hh index 105c7ff..fbca130 100644 --- a/core/resource/resource.hh +++ b/core/resource/resource.hh @@ -1,53 +1,53 @@ -#pragma once - -template<typename T> -using resource_ptr = std::shared_ptr<const T>; - -constexpr std::uint32_t RESOURCE_CACHE = 0x00000001U; ///< Cache the resource after loading -constexpr std::uint32_t RESOURCE_USER = 0xFFFFFF00U; ///< User-defined flags for custom behavior - -using ResourceLoadFunc = const void* (*)(const char* name, std::uint32_t flags); -using ResourceFreeFunc = void (*)(const void* resource); - -namespace resource::detail -{ -void register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func); -resource_ptr<void> load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags); -resource_ptr<void> find_resource(const std::type_info& type, std::string_view name); -} // namespace resource::detail - -namespace resource -{ -template<typename T> -void register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func); -template<typename T> -resource_ptr<T> load(std::string_view name, std::uint32_t flags = 0U); -template<typename T> -resource_ptr<T> find(std::string_view name); -} // namespace resource - -namespace resource -{ -void hard_cleanup(void); -void soft_cleanup(void); -} // namespace resource - -template<typename T> -void resource::register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func) -{ - resource::detail::register_loader(typeid(T), load_func, free_func); -} - -template<typename T> -resource_ptr<T> resource::load(std::string_view name, std::uint32_t flags) -{ - auto result = resource::detail::load_resource(typeid(T), name, flags); - return std::reinterpret_pointer_cast<const T>(result); -} - -template<typename T> -resource_ptr<T> resource::find(std::string_view name) -{ - auto result = resource::detail::find_resource(typeid(T), name); - return std::reinterpret_pointer_cast<const T>(result); -} +#pragma once
+
+template<typename T>
+using resource_ptr = std::shared_ptr<const T>;
+
+constexpr std::uint32_t RESOURCE_CACHE = 0x00000001U; ///< Cache the resource after loading
+constexpr std::uint32_t RESOURCE_USER = 0xFFFFFF00U; ///< User-defined flags for custom behavior
+
+using ResourceLoadFunc = const void* (*)(const char* name, std::uint32_t flags);
+using ResourceFreeFunc = void (*)(const void* resource);
+
+namespace resource::detail
+{
+void register_loader(const std::type_info& type, ResourceLoadFunc load_func, ResourceFreeFunc free_func);
+resource_ptr<void> load_resource(const std::type_info& type, std::string_view name, std::uint32_t flags);
+resource_ptr<void> find_resource(const std::type_info& type, std::string_view name);
+} // namespace resource::detail
+
+namespace resource
+{
+template<typename T>
+void register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func);
+template<typename T>
+resource_ptr<T> load(std::string_view name, std::uint32_t flags = 0U);
+template<typename T>
+resource_ptr<T> find(std::string_view name);
+} // namespace resource
+
+namespace resource
+{
+void hard_cleanup(void);
+void soft_cleanup(void);
+} // namespace resource
+
+template<typename T>
+void resource::register_loader(ResourceLoadFunc load_func, ResourceFreeFunc free_func)
+{
+ resource::detail::register_loader(typeid(T), load_func, free_func);
+}
+
+template<typename T>
+resource_ptr<T> resource::load(std::string_view name, std::uint32_t flags)
+{
+ auto result = resource::detail::load_resource(typeid(T), name, flags);
+ return std::reinterpret_pointer_cast<const T>(result);
+}
+
+template<typename T>
+resource_ptr<T> resource::find(std::string_view name)
+{
+ auto result = resource::detail::find_resource(typeid(T), name);
+ return std::reinterpret_pointer_cast<const T>(result);
+}
diff --git a/core/threading.cc b/core/threading.cc index 641e634..dcbdb9a 100644 --- a/core/threading.cc +++ b/core/threading.cc @@ -1,128 +1,128 @@ -#include "core/pch.hh" - -#include "core/threading.hh" - -#include "core/io/cmdline.hh" - -#include "core/math/constexpr.hh" - -constexpr static std::string_view DEFAULT_POOL_SIZE_ARG = "4"; - -static BS::light_thread_pool* thread_pool; -static std::deque<Task*> task_deque; - -static void task_process(Task* task) -{ - task->set_status(task_status::PROCESSING); - task->process(); - - if(task->get_status() == task_status::PROCESSING) { - // If the task status is still PROCESSING - // it can be deduced it hasn't been cancelled - task->set_status(task_status::COMPLETED); - } -} - -task_status Task::get_status(void) const -{ - return m_status; -} - -void Task::set_status(task_status status) -{ - m_status = status; -} - -void threading::init(void) -{ - auto argument = io::cmdline::get("threads", DEFAULT_POOL_SIZE_ARG); - auto num_concurrent_threads = std::thread::hardware_concurrency(); - unsigned int thread_pool_size; - - if(num_concurrent_threads && 0 == argument.compare("max")) { - // Use the maximum available number of concurrent - // hardware threads provided by the implementation - thread_pool_size = num_concurrent_threads; - } - else { - if(num_concurrent_threads) { - auto result = std::from_chars(argument.data(), argument.data() + argument.size(), thread_pool_size); - - if(result.ec == std::errc()) { - thread_pool_size = math::clamp<unsigned int>(thread_pool_size, 1U, num_concurrent_threads); - } - else { - thread_pool_size = 4U; - } - } - else { - auto result = std::from_chars(argument.data(), argument.data() + argument.size(), thread_pool_size); - - if(result.ec == std::errc()) { - thread_pool_size = math::max<unsigned int>(thread_pool_size, 1U); - } - else { - thread_pool_size = 4U; - } - } - } - - spdlog::info("threading: using {} threads for pooling tasks", thread_pool_size); - - thread_pool = new BS::light_thread_pool(thread_pool_size); - - task_deque.clear(); -} - -void threading::shutdown(void) -{ - for(auto task : task_deque) { - auto status = task->get_status(); - if((status != task_status::CANCELLED) || (status != task_status::COMPLETED)) { - task->set_status(task_status::CANCELLED); - } - } - - thread_pool->purge(); - thread_pool->wait(); - - for(auto task : task_deque) - delete task; - task_deque.clear(); - - delete thread_pool; -} - -void threading::update(void) -{ - auto task_iter = task_deque.cbegin(); - - while(task_iter != task_deque.cend()) { - auto task_ptr = *task_iter; - auto status = task_ptr->get_status(); - - if(status == task_status::CANCELLED) { - delete task_ptr; - task_iter = task_deque.erase(task_iter); - continue; - } - - if(status == task_status::COMPLETED) { - task_ptr->finalize(); - delete task_ptr; - task_iter = task_deque.erase(task_iter); - continue; - } - - task_iter = std::next(task_iter); - } -} - -void threading::detail::submit_new(Task* task) -{ - task->set_status(task_status::ENQUEUED); - - static_cast<void>(thread_pool->submit_task(std::bind(&task_process, task))); - - task_deque.push_back(task); -} +#include "core/pch.hh"
+
+#include "core/threading.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/math/constexpr.hh"
+
+constexpr static std::string_view DEFAULT_POOL_SIZE_ARG = "4";
+
+static BS::light_thread_pool* thread_pool;
+static std::deque<Task*> task_deque;
+
+static void task_process(Task* task)
+{
+ task->set_status(task_status::PROCESSING);
+ task->process();
+
+ if(task->get_status() == task_status::PROCESSING) {
+ // If the task status is still PROCESSING
+ // it can be deduced it hasn't been cancelled
+ task->set_status(task_status::COMPLETED);
+ }
+}
+
+task_status Task::get_status(void) const
+{
+ return m_status;
+}
+
+void Task::set_status(task_status status)
+{
+ m_status = status;
+}
+
+void threading::init(void)
+{
+ auto argument = io::cmdline::get("threads", DEFAULT_POOL_SIZE_ARG);
+ auto num_concurrent_threads = std::thread::hardware_concurrency();
+ unsigned int thread_pool_size;
+
+ if(num_concurrent_threads && 0 == argument.compare("max")) {
+ // Use the maximum available number of concurrent
+ // hardware threads provided by the implementation
+ thread_pool_size = num_concurrent_threads;
+ }
+ else {
+ if(num_concurrent_threads) {
+ auto result = std::from_chars(argument.data(), argument.data() + argument.size(), thread_pool_size);
+
+ if(result.ec == std::errc()) {
+ thread_pool_size = math::clamp<unsigned int>(thread_pool_size, 1U, num_concurrent_threads);
+ }
+ else {
+ thread_pool_size = 4U;
+ }
+ }
+ else {
+ auto result = std::from_chars(argument.data(), argument.data() + argument.size(), thread_pool_size);
+
+ if(result.ec == std::errc()) {
+ thread_pool_size = math::max<unsigned int>(thread_pool_size, 1U);
+ }
+ else {
+ thread_pool_size = 4U;
+ }
+ }
+ }
+
+ spdlog::info("threading: using {} threads for pooling tasks", thread_pool_size);
+
+ thread_pool = new BS::light_thread_pool(thread_pool_size);
+
+ task_deque.clear();
+}
+
+void threading::shutdown(void)
+{
+ for(auto task : task_deque) {
+ auto status = task->get_status();
+ if((status != task_status::CANCELLED) || (status != task_status::COMPLETED)) {
+ task->set_status(task_status::CANCELLED);
+ }
+ }
+
+ thread_pool->purge();
+ thread_pool->wait();
+
+ for(auto task : task_deque)
+ delete task;
+ task_deque.clear();
+
+ delete thread_pool;
+}
+
+void threading::update(void)
+{
+ auto task_iter = task_deque.cbegin();
+
+ while(task_iter != task_deque.cend()) {
+ auto task_ptr = *task_iter;
+ auto status = task_ptr->get_status();
+
+ if(status == task_status::CANCELLED) {
+ delete task_ptr;
+ task_iter = task_deque.erase(task_iter);
+ continue;
+ }
+
+ if(status == task_status::COMPLETED) {
+ task_ptr->finalize();
+ delete task_ptr;
+ task_iter = task_deque.erase(task_iter);
+ continue;
+ }
+
+ task_iter = std::next(task_iter);
+ }
+}
+
+void threading::detail::submit_new(Task* task)
+{
+ task->set_status(task_status::ENQUEUED);
+
+ static_cast<void>(thread_pool->submit_task(std::bind(&task_process, task)));
+
+ task_deque.push_back(task);
+}
diff --git a/core/threading.hh b/core/threading.hh index 14f17f8..2225907 100644 --- a/core/threading.hh +++ b/core/threading.hh @@ -1,46 +1,46 @@ -#pragma once - -enum class task_status : unsigned int { - ENQUEUED = 0x0000U, - PROCESSING = 0x0001U, - COMPLETED = 0x0002U, - CANCELLED = 0x0004U, -}; - -class Task { -public: - virtual ~Task(void) = default; - virtual void process(void) = 0; - virtual void finalize(void) = 0; - - task_status get_status(void) const; - void set_status(task_status status); - -protected: - std::atomic<task_status> m_status; - std::future<void> m_future; -}; - -namespace threading -{ -void init(void); -void shutdown(void); -void update(void); -} // namespace threading - -namespace threading::detail -{ -void submit_new(Task* task); -} // namespace threading::detail - -namespace threading -{ -template<typename T, typename... AT> -void submit(AT&&... args); -} // namespace threading - -template<typename T, typename... AT> -inline void threading::submit(AT&&... args) -{ - threading::detail::submit_new(new T(args...)); -} +#pragma once
+
+enum class task_status : unsigned int {
+ ENQUEUED = 0x0000U,
+ PROCESSING = 0x0001U,
+ COMPLETED = 0x0002U,
+ CANCELLED = 0x0004U,
+};
+
+class Task {
+public:
+ virtual ~Task(void) = default;
+ virtual void process(void) = 0;
+ virtual void finalize(void) = 0;
+
+ task_status get_status(void) const;
+ void set_status(task_status status);
+
+protected:
+ std::atomic<task_status> m_status;
+ std::future<void> m_future;
+};
+
+namespace threading
+{
+void init(void);
+void shutdown(void);
+void update(void);
+} // namespace threading
+
+namespace threading::detail
+{
+void submit_new(Task* task);
+} // namespace threading::detail
+
+namespace threading
+{
+template<typename T, typename... AT>
+void submit(AT&&... args);
+} // namespace threading
+
+template<typename T, typename... AT>
+inline void threading::submit(AT&&... args)
+{
+ threading::detail::submit_new(new T(args...));
+}
diff --git a/core/utils/epoch.cc b/core/utils/epoch.cc index 36bdb79..be5d4bd 100644 --- a/core/utils/epoch.cc +++ b/core/utils/epoch.cc @@ -1,39 +1,39 @@ -#include "core/pch.hh" - -#include "core/utils/epoch.hh" - -std::uint64_t utils::unix_seconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count()); -} - -std::uint64_t utils::unix_milliseconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()); -} - -std::uint64_t utils::unix_microseconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()); -} - -std::int64_t utils::signed_unix_seconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count()); -} - -std::int64_t utils::signed_unix_milliseconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()); -} - -std::int64_t utils::signed_unix_microseconds(void) -{ - const auto elapsed = std::chrono::system_clock::now().time_since_epoch(); - return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()); -} +#include "core/pch.hh"
+
+#include "core/utils/epoch.hh"
+
+std::uint64_t utils::unix_seconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count());
+}
+
+std::uint64_t utils::unix_milliseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
+}
+
+std::uint64_t utils::unix_microseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count());
+}
+
+std::int64_t utils::signed_unix_seconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count());
+}
+
+std::int64_t utils::signed_unix_milliseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
+}
+
+std::int64_t utils::signed_unix_microseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count());
+}
diff --git a/core/utils/epoch.hh b/core/utils/epoch.hh index 4bf5460..1df564c 100644 --- a/core/utils/epoch.hh +++ b/core/utils/epoch.hh @@ -1,15 +1,15 @@ -#pragma once - -namespace utils -{ -std::uint64_t unix_seconds(void); -std::uint64_t unix_milliseconds(void); -std::uint64_t unix_microseconds(void); -} // namespace utils - -namespace utils -{ -std::int64_t signed_unix_seconds(void); -std::int64_t signed_unix_milliseconds(void); -std::int64_t signed_unix_microseconds(void); -} // namespace utils +#pragma once
+
+namespace utils
+{
+std::uint64_t unix_seconds(void);
+std::uint64_t unix_milliseconds(void);
+std::uint64_t unix_microseconds(void);
+} // namespace utils
+
+namespace utils
+{
+std::int64_t signed_unix_seconds(void);
+std::int64_t signed_unix_milliseconds(void);
+std::int64_t signed_unix_microseconds(void);
+} // namespace utils
diff --git a/core/utils/physfs.cc b/core/utils/physfs.cc index cb310df..b801963 100644 --- a/core/utils/physfs.cc +++ b/core/utils/physfs.cc @@ -1,76 +1,76 @@ -#include "core/pch.hh" - -#include "core/utils/physfs.hh" - -bool utils::read_file(std::string_view path, std::vector<std::byte>& buffer) -{ - auto file = PHYSFS_openRead(std::string(path).c_str()); - - if(file == nullptr) { - spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - PHYSFS_sint64 file_size = PHYSFS_fileLength(file); - buffer.resize(static_cast<std::size_t>(file_size)); - - PHYSFS_readBytes(file, buffer.data(), file_size); - PHYSFS_close(file); - - return true; -} - -bool utils::read_file(std::string_view path, std::string& buffer) -{ - auto file = PHYSFS_openRead(std::string(path).c_str()); - - if(file == nullptr) { - spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - PHYSFS_sint64 file_size = PHYSFS_fileLength(file); - buffer.resize(static_cast<std::size_t>(file_size)); - - PHYSFS_readBytes(file, buffer.data(), file_size); - PHYSFS_close(file); - - return true; -} - -bool utils::write_file(std::string_view path, const std::vector<std::byte>& buffer) -{ - auto file = PHYSFS_openWrite(std::string(path).c_str()); - - if(file == nullptr) { - spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size())); - PHYSFS_close(file); - - return true; -} - -bool utils::write_file(std::string_view path, const std::string& buffer) -{ - auto file = PHYSFS_openWrite(std::string(path).c_str()); - - if(file == nullptr) { - spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size())); - PHYSFS_close(file); - - return true; -} - -std::string_view utils::physfs_error(void) -{ - auto error_code = PHYSFS_getLastErrorCode(); - auto error_string = PHYSFS_getErrorByCode(error_code); - return std::string_view(error_string, std::strlen(error_string)); -} +#include "core/pch.hh"
+
+#include "core/utils/physfs.hh"
+
+bool utils::read_file(std::string_view path, std::vector<std::byte>& buffer)
+{
+ auto file = PHYSFS_openRead(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
+ buffer.resize(static_cast<std::size_t>(file_size));
+
+ PHYSFS_readBytes(file, buffer.data(), file_size);
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool utils::read_file(std::string_view path, std::string& buffer)
+{
+ auto file = PHYSFS_openRead(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
+ buffer.resize(static_cast<std::size_t>(file_size));
+
+ PHYSFS_readBytes(file, buffer.data(), file_size);
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool utils::write_file(std::string_view path, const std::vector<std::byte>& buffer)
+{
+ auto file = PHYSFS_openWrite(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size()));
+ PHYSFS_close(file);
+
+ return true;
+}
+
+bool utils::write_file(std::string_view path, const std::string& buffer)
+{
+ auto file = PHYSFS_openWrite(std::string(path).c_str());
+
+ if(file == nullptr) {
+ spdlog::error("physfs: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ PHYSFS_writeBytes(file, buffer.data(), static_cast<PHYSFS_uint64>(buffer.size()));
+ PHYSFS_close(file);
+
+ return true;
+}
+
+std::string_view utils::physfs_error(void)
+{
+ auto error_code = PHYSFS_getLastErrorCode();
+ auto error_string = PHYSFS_getErrorByCode(error_code);
+ return std::string_view(error_string, std::strlen(error_string));
+}
diff --git a/core/utils/physfs.hh b/core/utils/physfs.hh index 4af94dc..220954e 100644 --- a/core/utils/physfs.hh +++ b/core/utils/physfs.hh @@ -1,14 +1,14 @@ -#pragma once - -namespace utils -{ -bool read_file(std::string_view path, std::vector<std::byte>& buffer); -bool read_file(std::string_view path, std::string& buffer); -bool write_file(std::string_view path, const std::vector<std::byte>& buffer); -bool write_file(std::string_view path, const std::string& buffer); -} // namespace utils - -namespace utils -{ -std::string_view physfs_error(void); -} // namespace utils +#pragma once
+
+namespace utils
+{
+bool read_file(std::string_view path, std::vector<std::byte>& buffer);
+bool read_file(std::string_view path, std::string& buffer);
+bool write_file(std::string_view path, const std::vector<std::byte>& buffer);
+bool write_file(std::string_view path, const std::string& buffer);
+} // namespace utils
+
+namespace utils
+{
+std::string_view physfs_error(void);
+} // namespace utils
diff --git a/core/utils/string.cc b/core/utils/string.cc index dd3d567..4bdc073 100644 --- a/core/utils/string.cc +++ b/core/utils/string.cc @@ -1,57 +1,57 @@ -#include "core/pch.hh" - -#include "core/utils/string.hh" - -constexpr static const char* WHITESPACE_CHARS = " \t\r\n"; - -bool utils::is_whitespace(const std::string& string) -{ - if(string.find_first_not_of(WHITESPACE_CHARS) == std::string::npos) { - return true; - } - else if((string.size() == 1) && string[0] == 0x00) { - return true; - } - else { - return string.empty(); - } -} - -std::string utils::join(const std::vector<std::string>& strings, const std::string& separator) -{ - std::ostringstream stream; - for(const std::string& str : strings) - stream << str << separator; - return stream.str(); -} - -std::vector<std::string> utils::split(const std::string& string, const std::string& separator) -{ - std::size_t pos = 0; - std::size_t prev = 0; - std::vector<std::string> result; - - while((pos = string.find(separator, prev)) != std::string::npos) { - result.push_back(string.substr(prev, pos - prev)); - prev = pos + separator.length(); - } - - if(prev <= string.length()) { - result.push_back(string.substr(prev, string.length() - prev)); - } - - return result; -} - -std::string utils::trim_whitespace(const std::string& string) -{ - auto su = string.find_first_not_of(WHITESPACE_CHARS); - auto sv = string.find_last_not_of(WHITESPACE_CHARS); - - if(su == std::string::npos) { - return std::string(); - } - else { - return string.substr(su, sv - su + 1); - } -} +#include "core/pch.hh"
+
+#include "core/utils/string.hh"
+
+constexpr static const char* WHITESPACE_CHARS = " \t\r\n";
+
+bool utils::is_whitespace(const std::string& string)
+{
+ if(string.find_first_not_of(WHITESPACE_CHARS) == std::string::npos) {
+ return true;
+ }
+ else if((string.size() == 1) && string[0] == 0x00) {
+ return true;
+ }
+ else {
+ return string.empty();
+ }
+}
+
+std::string utils::join(const std::vector<std::string>& strings, const std::string& separator)
+{
+ std::ostringstream stream;
+ for(const std::string& str : strings)
+ stream << str << separator;
+ return stream.str();
+}
+
+std::vector<std::string> utils::split(const std::string& string, const std::string& separator)
+{
+ std::size_t pos = 0;
+ std::size_t prev = 0;
+ std::vector<std::string> result;
+
+ while((pos = string.find(separator, prev)) != std::string::npos) {
+ result.push_back(string.substr(prev, pos - prev));
+ prev = pos + separator.length();
+ }
+
+ if(prev <= string.length()) {
+ result.push_back(string.substr(prev, string.length() - prev));
+ }
+
+ return result;
+}
+
+std::string utils::trim_whitespace(const std::string& string)
+{
+ auto su = string.find_first_not_of(WHITESPACE_CHARS);
+ auto sv = string.find_last_not_of(WHITESPACE_CHARS);
+
+ if(su == std::string::npos) {
+ return std::string();
+ }
+ else {
+ return string.substr(su, sv - su + 1);
+ }
+}
diff --git a/core/utils/string.hh b/core/utils/string.hh index 827c3ca..0e896fd 100644 --- a/core/utils/string.hh +++ b/core/utils/string.hh @@ -1,17 +1,17 @@ -#pragma once - -namespace utils -{ -bool is_whitespace(const std::string& string); -} // namespace utils - -namespace utils -{ -std::string join(const std::vector<std::string>& strings, const std::string& separator); -std::vector<std::string> split(const std::string& string, const std::string& separator); -} // namespace utils - -namespace utils -{ -std::string trim_whitespace(const std::string& string); -} // namespace utils +#pragma once
+
+namespace utils
+{
+bool is_whitespace(const std::string& string);
+} // namespace utils
+
+namespace utils
+{
+std::string join(const std::vector<std::string>& strings, const std::string& separator);
+std::vector<std::string> split(const std::string& string, const std::string& separator);
+} // namespace utils
+
+namespace utils
+{
+std::string trim_whitespace(const std::string& string);
+} // namespace utils
diff --git a/core/version.hh b/core/version.hh index fc33374..50f128a 100644 --- a/core/version.hh +++ b/core/version.hh @@ -1,14 +1,14 @@ -#pragma once - -namespace version -{ -extern const unsigned long major; -extern const unsigned long minor; -extern const unsigned long patch; -} // namespace version - -namespace version -{ -extern const std::string_view commit; -extern const std::string_view semver; -} // namespace version +#pragma once
+
+namespace version
+{
+extern const unsigned long major;
+extern const unsigned long minor;
+extern const unsigned long patch;
+} // namespace version
+
+namespace version
+{
+extern const std::string_view commit;
+extern const std::string_view semver;
+} // namespace version
diff --git a/game/client/config/gamepad_axis.cc b/game/client/config/gamepad_axis.cc index 8ae74be..c782c19 100644 --- a/game/client/config/gamepad_axis.cc +++ b/game/client/config/gamepad_axis.cc @@ -1,115 +1,115 @@ -#include "client/pch.hh" - -#include "client/config/gamepad_axis.hh" - -#include "core/math/constexpr.hh" - -#include "client/io/gamepad.hh" - -constexpr static std::string_view UNKNOWN_AXIS_NAME = "UNKNOWN"; - -static const std::pair<int, std::string_view> axis_names[] = { - { GLFW_GAMEPAD_AXIS_LEFT_X, "LEFT_X" }, - { GLFW_GAMEPAD_AXIS_LEFT_Y, "LEFT_Y" }, - { GLFW_GAMEPAD_AXIS_RIGHT_X, "RIGHT_X" }, - { GLFW_GAMEPAD_AXIS_RIGHT_Y, "RIGHT_Y" }, - { GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, "LEFT_TRIG" }, - { GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, "RIGHT_TRIG" }, -}; - -static std::string_view get_axis_name(int axis) -{ - for(const auto& it : axis_names) { - if(it.first != axis) { - continue; - } - - return it.second; - } - - return UNKNOWN_AXIS_NAME; -} - -config::GamepadAxis::GamepadAxis(void) : GamepadAxis(io::INVALID_GAMEPAD_AXIS, false) -{ -} - -config::GamepadAxis::GamepadAxis(int axis, bool inverted) -{ - m_inverted = inverted; - m_gamepad_axis = axis; - m_name = get_axis_name(axis); - m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U); -} - -std::string_view config::GamepadAxis::get(void) const -{ - return m_full_string; -} - -void config::GamepadAxis::set(std::string_view value) -{ - char new_name[64]; - unsigned int new_invert; - std::string value_str(value); - - if(2 == std::sscanf(value_str.c_str(), "%63[^:]:%u", new_name, &new_invert)) { - for(const auto& it : axis_names) { - if(0 == it.second.compare(new_name)) { - m_inverted = new_invert; - m_gamepad_axis = it.first; - m_name = get_axis_name(m_gamepad_axis); - m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U); - return; - } - } - } - - m_inverted = false; - m_gamepad_axis = io::INVALID_GAMEPAD_AXIS; - m_name = UNKNOWN_AXIS_NAME; - m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U); -} - -int config::GamepadAxis::get_axis(void) const -{ - return m_gamepad_axis; -} - -void config::GamepadAxis::set_axis(int axis) -{ - m_gamepad_axis = axis; - m_name = get_axis_name(axis); - m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U); -} - -bool config::GamepadAxis::is_inverted(void) const -{ - return m_inverted; -} - -void config::GamepadAxis::set_inverted(bool inverted) -{ - m_inverted = inverted; - m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U); -} - -float config::GamepadAxis::get_value(const GLFWgamepadstate& state, float deadzone) const -{ - if(m_gamepad_axis <= math::array_size(state.axes)) { - auto value = state.axes[m_gamepad_axis]; - - if(math::abs(value) > deadzone) { - return m_inverted ? -value : value; - } - - return 0.0f; - } - - return 0.0f; -} - -std::string_view config::GamepadAxis::get_name(void) const -{ - return m_name; -} +#include "client/pch.hh"
+
+#include "client/config/gamepad_axis.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/io/gamepad.hh"
+
+constexpr static std::string_view UNKNOWN_AXIS_NAME = "UNKNOWN";
+
+static const std::pair<int, std::string_view> axis_names[] = {
+ { GLFW_GAMEPAD_AXIS_LEFT_X, "LEFT_X" },
+ { GLFW_GAMEPAD_AXIS_LEFT_Y, "LEFT_Y" },
+ { GLFW_GAMEPAD_AXIS_RIGHT_X, "RIGHT_X" },
+ { GLFW_GAMEPAD_AXIS_RIGHT_Y, "RIGHT_Y" },
+ { GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, "LEFT_TRIG" },
+ { GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, "RIGHT_TRIG" },
+};
+
+static std::string_view get_axis_name(int axis)
+{
+ for(const auto& it : axis_names) {
+ if(it.first != axis) {
+ continue;
+ }
+
+ return it.second;
+ }
+
+ return UNKNOWN_AXIS_NAME;
+}
+
+config::GamepadAxis::GamepadAxis(void) : GamepadAxis(io::INVALID_GAMEPAD_AXIS, false)
+{
+}
+
+config::GamepadAxis::GamepadAxis(int axis, bool inverted)
+{
+ m_inverted = inverted;
+ m_gamepad_axis = axis;
+ m_name = get_axis_name(axis);
+ m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U);
+}
+
+std::string_view config::GamepadAxis::get(void) const
+{
+ return m_full_string;
+}
+
+void config::GamepadAxis::set(std::string_view value)
+{
+ char new_name[64];
+ unsigned int new_invert;
+ std::string value_str(value);
+
+ if(2 == std::sscanf(value_str.c_str(), "%63[^:]:%u", new_name, &new_invert)) {
+ for(const auto& it : axis_names) {
+ if(0 == it.second.compare(new_name)) {
+ m_inverted = new_invert;
+ m_gamepad_axis = it.first;
+ m_name = get_axis_name(m_gamepad_axis);
+ m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U);
+ return;
+ }
+ }
+ }
+
+ m_inverted = false;
+ m_gamepad_axis = io::INVALID_GAMEPAD_AXIS;
+ m_name = UNKNOWN_AXIS_NAME;
+ m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U);
+}
+
+int config::GamepadAxis::get_axis(void) const
+{
+ return m_gamepad_axis;
+}
+
+void config::GamepadAxis::set_axis(int axis)
+{
+ m_gamepad_axis = axis;
+ m_name = get_axis_name(axis);
+ m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U);
+}
+
+bool config::GamepadAxis::is_inverted(void) const
+{
+ return m_inverted;
+}
+
+void config::GamepadAxis::set_inverted(bool inverted)
+{
+ m_inverted = inverted;
+ m_full_string = std::format("{}:{}", m_name, m_inverted ? 1U : 0U);
+}
+
+float config::GamepadAxis::get_value(const GLFWgamepadstate& state, float deadzone) const
+{
+ if(m_gamepad_axis <= math::array_size(state.axes)) {
+ auto value = state.axes[m_gamepad_axis];
+
+ if(math::abs(value) > deadzone) {
+ return m_inverted ? -value : value;
+ }
+
+ return 0.0f;
+ }
+
+ return 0.0f;
+}
+
+std::string_view config::GamepadAxis::get_name(void) const
+{
+ return m_name;
+}
diff --git a/game/client/config/gamepad_axis.hh b/game/client/config/gamepad_axis.hh index 9392a35..80c323f 100644 --- a/game/client/config/gamepad_axis.hh +++ b/game/client/config/gamepad_axis.hh @@ -1,38 +1,38 @@ -#pragma once - -#include "core/config/ivalue.hh" - -struct GLFWgamepadstate; - -namespace config -{ -class GamepadAxis final : public IValue { -public: - explicit GamepadAxis(void); - explicit GamepadAxis(int axis, bool inverted); - virtual ~GamepadAxis(void) = default; - - virtual std::string_view get(void) const override; - virtual void set(std::string_view value) override; - - int get_axis(void) const; - void set_axis(int axis); - - bool is_inverted(void) const; - void set_inverted(bool inverted); - - float get_value(const GLFWgamepadstate& state, float deadzone = 0.0f) const; - - // Conventional get/set methods implemented by - // this configuration value actually contain the - // inversion flag. Since we're updating that flag - // in the UI by means of a separate checkbox, we only need the name here - std::string_view get_name(void) const; - -private: - bool m_inverted; - int m_gamepad_axis; - std::string m_full_string; - std::string_view m_name; -}; -} // namespace config +#pragma once
+
+#include "core/config/ivalue.hh"
+
+struct GLFWgamepadstate;
+
+namespace config
+{
+class GamepadAxis final : public IValue {
+public:
+ explicit GamepadAxis(void);
+ explicit GamepadAxis(int axis, bool inverted);
+ virtual ~GamepadAxis(void) = default;
+
+ virtual std::string_view get(void) const override;
+ virtual void set(std::string_view value) override;
+
+ int get_axis(void) const;
+ void set_axis(int axis);
+
+ bool is_inverted(void) const;
+ void set_inverted(bool inverted);
+
+ float get_value(const GLFWgamepadstate& state, float deadzone = 0.0f) const;
+
+ // Conventional get/set methods implemented by
+ // this configuration value actually contain the
+ // inversion flag. Since we're updating that flag
+ // in the UI by means of a separate checkbox, we only need the name here
+ std::string_view get_name(void) const;
+
+private:
+ bool m_inverted;
+ int m_gamepad_axis;
+ std::string m_full_string;
+ std::string_view m_name;
+};
+} // namespace config
diff --git a/game/client/config/gamepad_button.cc b/game/client/config/gamepad_button.cc index 07e4457..5141de3 100644 --- a/game/client/config/gamepad_button.cc +++ b/game/client/config/gamepad_button.cc @@ -1,90 +1,90 @@ -#include "client/pch.hh" - -#include "client/config/gamepad_button.hh" - -#include "core/math/constexpr.hh" - -#include "client/io/gamepad.hh" - -constexpr static std::string_view UNKNOWN_BUTTON_NAME = "UNKNOWN"; - -static const std::pair<int, std::string_view> button_names[] = { - { GLFW_GAMEPAD_BUTTON_A, "A" }, - { GLFW_GAMEPAD_BUTTON_B, "B" }, - { GLFW_GAMEPAD_BUTTON_X, "X" }, - { GLFW_GAMEPAD_BUTTON_Y, "Y" }, - { GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, "L_BUMP" }, - { GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, "R_BUMP" }, - { GLFW_GAMEPAD_BUTTON_BACK, "BACK" }, - { GLFW_GAMEPAD_BUTTON_START, "START" }, - { GLFW_GAMEPAD_BUTTON_GUIDE, "GUIDE" }, - { GLFW_GAMEPAD_BUTTON_LEFT_THUMB, "L_THUMB" }, - { GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, "R_THUMB" }, - { GLFW_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP" }, - { GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT" }, - { GLFW_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN" }, - { GLFW_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT" }, -}; - -static std::string_view get_button_name(int button) -{ - for(const auto& it : button_names) { - if(it.first == button) { - return it.second; - } - } - - return UNKNOWN_BUTTON_NAME; -} - -config::GamepadButton::GamepadButton(void) -{ - m_gamepad_button = io::INVALID_GAMEPAD_BUTTON; - m_name = UNKNOWN_BUTTON_NAME; -} - -config::GamepadButton::GamepadButton(int button) -{ - m_gamepad_button = button; - m_name = get_button_name(button); -} - -std::string_view config::GamepadButton::get(void) const -{ - return m_name; -} - -void config::GamepadButton::set(std::string_view value) -{ - for(const auto& it : button_names) { - if(0 == it.second.compare(value)) { - m_gamepad_button = it.first; - m_name = it.second; - return; - } - } - - m_gamepad_button = io::INVALID_GAMEPAD_BUTTON; - m_name = UNKNOWN_BUTTON_NAME; -} - -int config::GamepadButton::get_button(void) const -{ - return m_gamepad_button; -} - -void config::GamepadButton::set_button(int button) -{ - m_gamepad_button = button; - m_name = get_button_name(button); -} - -bool config::GamepadButton::equals(int button) const -{ - return m_gamepad_button == button; -} - -bool config::GamepadButton::is_pressed(const GLFWgamepadstate& state) const -{ - return m_gamepad_button < math::array_size(state.buttons) && state.buttons[m_gamepad_button] == GLFW_PRESS; -} +#include "client/pch.hh"
+
+#include "client/config/gamepad_button.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/io/gamepad.hh"
+
+constexpr static std::string_view UNKNOWN_BUTTON_NAME = "UNKNOWN";
+
+static const std::pair<int, std::string_view> button_names[] = {
+ { GLFW_GAMEPAD_BUTTON_A, "A" },
+ { GLFW_GAMEPAD_BUTTON_B, "B" },
+ { GLFW_GAMEPAD_BUTTON_X, "X" },
+ { GLFW_GAMEPAD_BUTTON_Y, "Y" },
+ { GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, "L_BUMP" },
+ { GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, "R_BUMP" },
+ { GLFW_GAMEPAD_BUTTON_BACK, "BACK" },
+ { GLFW_GAMEPAD_BUTTON_START, "START" },
+ { GLFW_GAMEPAD_BUTTON_GUIDE, "GUIDE" },
+ { GLFW_GAMEPAD_BUTTON_LEFT_THUMB, "L_THUMB" },
+ { GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, "R_THUMB" },
+ { GLFW_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP" },
+ { GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT" },
+ { GLFW_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN" },
+ { GLFW_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT" },
+};
+
+static std::string_view get_button_name(int button)
+{
+ for(const auto& it : button_names) {
+ if(it.first == button) {
+ return it.second;
+ }
+ }
+
+ return UNKNOWN_BUTTON_NAME;
+}
+
+config::GamepadButton::GamepadButton(void)
+{
+ m_gamepad_button = io::INVALID_GAMEPAD_BUTTON;
+ m_name = UNKNOWN_BUTTON_NAME;
+}
+
+config::GamepadButton::GamepadButton(int button)
+{
+ m_gamepad_button = button;
+ m_name = get_button_name(button);
+}
+
+std::string_view config::GamepadButton::get(void) const
+{
+ return m_name;
+}
+
+void config::GamepadButton::set(std::string_view value)
+{
+ for(const auto& it : button_names) {
+ if(0 == it.second.compare(value)) {
+ m_gamepad_button = it.first;
+ m_name = it.second;
+ return;
+ }
+ }
+
+ m_gamepad_button = io::INVALID_GAMEPAD_BUTTON;
+ m_name = UNKNOWN_BUTTON_NAME;
+}
+
+int config::GamepadButton::get_button(void) const
+{
+ return m_gamepad_button;
+}
+
+void config::GamepadButton::set_button(int button)
+{
+ m_gamepad_button = button;
+ m_name = get_button_name(button);
+}
+
+bool config::GamepadButton::equals(int button) const
+{
+ return m_gamepad_button == button;
+}
+
+bool config::GamepadButton::is_pressed(const GLFWgamepadstate& state) const
+{
+ return m_gamepad_button < math::array_size(state.buttons) && state.buttons[m_gamepad_button] == GLFW_PRESS;
+}
diff --git a/game/client/config/gamepad_button.hh b/game/client/config/gamepad_button.hh index 584c353..f11fe4f 100644 --- a/game/client/config/gamepad_button.hh +++ b/game/client/config/gamepad_button.hh @@ -1,28 +1,28 @@ -#pragma once - -#include "core/config/ivalue.hh" - -struct GLFWgamepadstate; - -namespace config -{ -class GamepadButton final : public IValue { -public: - explicit GamepadButton(void); - explicit GamepadButton(int button); - virtual ~GamepadButton(void) = default; - - virtual std::string_view get(void) const override; - virtual void set(std::string_view value) override; - - int get_button(void) const; - void set_button(int button); - - bool equals(int button) const; - bool is_pressed(const GLFWgamepadstate& state) const; - -private: - int m_gamepad_button; - std::string_view m_name; -}; -} // namespace config +#pragma once
+
+#include "core/config/ivalue.hh"
+
+struct GLFWgamepadstate;
+
+namespace config
+{
+class GamepadButton final : public IValue {
+public:
+ explicit GamepadButton(void);
+ explicit GamepadButton(int button);
+ virtual ~GamepadButton(void) = default;
+
+ virtual std::string_view get(void) const override;
+ virtual void set(std::string_view value) override;
+
+ int get_button(void) const;
+ void set_button(int button);
+
+ bool equals(int button) const;
+ bool is_pressed(const GLFWgamepadstate& state) const;
+
+private:
+ int m_gamepad_button;
+ std::string_view m_name;
+};
+} // namespace config
diff --git a/game/client/config/keybind.cc b/game/client/config/keybind.cc index e254f7b..befdca2 100644 --- a/game/client/config/keybind.cc +++ b/game/client/config/keybind.cc @@ -1,202 +1,202 @@ -#include "client/pch.hh" - -#include "client/config/keybind.hh" - -#include "core/math/constexpr.hh" - -#include "client/const.hh" - -constexpr static std::string_view UNKNOWN_KEY_NAME = "UNKNOWN"; - -static const std::pair<int, std::string_view> key_names[] = { - { GLFW_KEY_SPACE, "SPACE" }, - { GLFW_KEY_APOSTROPHE, "'" }, - { GLFW_KEY_COMMA, "," }, - { GLFW_KEY_MINUS, "-" }, - { GLFW_KEY_PERIOD, "." }, - { GLFW_KEY_SLASH, "/" }, - { GLFW_KEY_0, "0" }, - { GLFW_KEY_1, "1" }, - { GLFW_KEY_2, "2" }, - { GLFW_KEY_3, "3" }, - { GLFW_KEY_4, "4" }, - { GLFW_KEY_5, "5" }, - { GLFW_KEY_6, "6" }, - { GLFW_KEY_7, "7" }, - { GLFW_KEY_8, "8" }, - { GLFW_KEY_9, "9" }, - { GLFW_KEY_SEMICOLON, ";" }, - { GLFW_KEY_EQUAL, "=" }, - { GLFW_KEY_A, "A" }, - { GLFW_KEY_B, "B" }, - { GLFW_KEY_C, "C" }, - { GLFW_KEY_D, "D" }, - { GLFW_KEY_E, "E" }, - { GLFW_KEY_F, "F" }, - { GLFW_KEY_G, "G" }, - { GLFW_KEY_H, "H" }, - { GLFW_KEY_I, "I" }, - { GLFW_KEY_J, "J" }, - { GLFW_KEY_K, "K" }, - { GLFW_KEY_L, "L" }, - { GLFW_KEY_M, "M" }, - { GLFW_KEY_N, "N" }, - { GLFW_KEY_O, "O" }, - { GLFW_KEY_P, "P" }, - { GLFW_KEY_Q, "Q" }, - { GLFW_KEY_R, "R" }, - { GLFW_KEY_S, "S" }, - { GLFW_KEY_T, "T" }, - { GLFW_KEY_U, "U" }, - { GLFW_KEY_V, "V" }, - { GLFW_KEY_W, "W" }, - { GLFW_KEY_X, "X" }, - { GLFW_KEY_Y, "Y" }, - { GLFW_KEY_Z, "Z" }, - { GLFW_KEY_LEFT_BRACKET, "[" }, - { GLFW_KEY_BACKSLASH, "\\" }, - { GLFW_KEY_RIGHT_BRACKET, "]" }, - { GLFW_KEY_GRAVE_ACCENT, "`" }, - { GLFW_KEY_WORLD_1, "WORLD_1" }, - { GLFW_KEY_WORLD_2, "WORLD_2" }, - { GLFW_KEY_ESCAPE, "ESCAPE" }, - { GLFW_KEY_ENTER, "ENTER" }, - { GLFW_KEY_TAB, "TAB" }, - { GLFW_KEY_BACKSPACE, "BACKSPACE" }, - { GLFW_KEY_INSERT, "INSERT" }, - { GLFW_KEY_DELETE, "DELETE" }, - { GLFW_KEY_RIGHT, "RIGHT" }, - { GLFW_KEY_LEFT, "LEFT" }, - { GLFW_KEY_DOWN, "DOWN" }, - { GLFW_KEY_UP, "UP" }, - { GLFW_KEY_PAGE_UP, "PAGE_UP" }, - { GLFW_KEY_PAGE_DOWN, "PAGE_DOWN" }, - { GLFW_KEY_HOME, "HOME" }, - { GLFW_KEY_END, "END" }, - { GLFW_KEY_CAPS_LOCK, "CAPS_LOCK" }, - { GLFW_KEY_SCROLL_LOCK, "SCROLL_LOCK" }, - { GLFW_KEY_NUM_LOCK, "NUM_LOCK" }, - { GLFW_KEY_PRINT_SCREEN, "PRINT_SCREEN" }, - { GLFW_KEY_PAUSE, "PAUSE" }, - { GLFW_KEY_F1, "F1" }, - { GLFW_KEY_F2, "F2" }, - { GLFW_KEY_F3, "F3" }, - { GLFW_KEY_F4, "F4" }, - { GLFW_KEY_F5, "F5" }, - { GLFW_KEY_F6, "F6" }, - { GLFW_KEY_F7, "F7" }, - { GLFW_KEY_F8, "F8" }, - { GLFW_KEY_F9, "F9" }, - { GLFW_KEY_F10, "F10" }, - { GLFW_KEY_F11, "F11" }, - { GLFW_KEY_F12, "F12" }, - { GLFW_KEY_F13, "F13" }, - { GLFW_KEY_F14, "F14" }, - { GLFW_KEY_F15, "F15" }, - { GLFW_KEY_F16, "F16" }, - { GLFW_KEY_F17, "F17" }, - { GLFW_KEY_F18, "F18" }, - { GLFW_KEY_F19, "F19" }, - { GLFW_KEY_F20, "F20" }, - { GLFW_KEY_F21, "F21" }, - { GLFW_KEY_F22, "F22" }, - { GLFW_KEY_F23, "F23" }, - { GLFW_KEY_F24, "F24" }, - { GLFW_KEY_F25, "F25" }, - { GLFW_KEY_KP_0, "KEYPAD_0" }, - { GLFW_KEY_KP_1, "KEYPAD_1" }, - { GLFW_KEY_KP_2, "KEYPAD_2" }, - { GLFW_KEY_KP_3, "KEYPAD_3" }, - { GLFW_KEY_KP_4, "KEYPAD_4" }, - { GLFW_KEY_KP_5, "KEYPAD_5" }, - { GLFW_KEY_KP_6, "KEYPAD_6" }, - { GLFW_KEY_KP_7, "KEYPAD_7" }, - { GLFW_KEY_KP_8, "KEYPAD_8" }, - { GLFW_KEY_KP_9, "KEYPAD_9" }, - { GLFW_KEY_KP_DECIMAL, "KEYPAD_POINT" }, - { GLFW_KEY_KP_DIVIDE, "KEYPAD_DIV" }, - { GLFW_KEY_KP_MULTIPLY, "KEYPAD_MUL" }, - { GLFW_KEY_KP_SUBTRACT, "KEYPAD_MINUS" }, - { GLFW_KEY_KP_ADD, "KEYPAD_PLUS" }, - { GLFW_KEY_KP_ENTER, "KEYPAD_ENTER" }, - { GLFW_KEY_KP_EQUAL, "KEYPAD_EQUAL" }, - { GLFW_KEY_LEFT_SHIFT, "LEFT_SHIFT" }, - { GLFW_KEY_LEFT_CONTROL, "LEFT_CTRL" }, - { GLFW_KEY_LEFT_ALT, "LEFT_ALT" }, - { GLFW_KEY_LEFT_SUPER, "LEFT_SUPER" }, - { GLFW_KEY_RIGHT_SHIFT, "RIGHT_SHIFT" }, - { GLFW_KEY_RIGHT_CONTROL, "RIGHT_CTRL" }, - { GLFW_KEY_RIGHT_ALT, "RIGHT_ALT" }, - { GLFW_KEY_RIGHT_SUPER, "RIGHT_SUPER" }, - { GLFW_KEY_MENU, "MENU" }, -}; - -static std::string_view get_key_name(int keycode) -{ - for(const auto& it : key_names) { - if(it.first == keycode) { - return it.second; - } - } - - return UNKNOWN_KEY_NAME; -} - -config::KeyBind::KeyBind(void) -{ - m_glfw_keycode = GLFW_KEY_UNKNOWN; - m_name = UNKNOWN_KEY_NAME; -} - -config::KeyBind::KeyBind(int default_value) -{ - if(default_value == DEBUG_KEY) { - m_glfw_keycode = GLFW_KEY_UNKNOWN; - m_name = UNKNOWN_KEY_NAME; - } - else { - m_glfw_keycode = default_value; - m_name = get_key_name(default_value); - } -} - -void config::KeyBind::set(std::string_view value) -{ - for(const auto& it : key_names) { - if((it.first != DEBUG_KEY) && 0 == it.second.compare(value)) { - m_glfw_keycode = it.first; - m_name = it.second; - return; - } - } - - m_glfw_keycode = GLFW_KEY_UNKNOWN; - m_name = UNKNOWN_KEY_NAME; -} - -std::string_view config::KeyBind::get(void) const -{ - return m_name; -} - -void config::KeyBind::set_key(int keycode) -{ - if(keycode == DEBUG_KEY) { - m_glfw_keycode = GLFW_KEY_UNKNOWN; - m_name = UNKNOWN_KEY_NAME; - } - else { - m_glfw_keycode = keycode; - m_name = get_key_name(keycode); - } -} - -int config::KeyBind::get_key(void) const -{ - return m_glfw_keycode; -} - -bool config::KeyBind::equals(int keycode) const -{ - return m_glfw_keycode == keycode; -} +#include "client/pch.hh"
+
+#include "client/config/keybind.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/const.hh"
+
+constexpr static std::string_view UNKNOWN_KEY_NAME = "UNKNOWN";
+
+static const std::pair<int, std::string_view> key_names[] = {
+ { GLFW_KEY_SPACE, "SPACE" },
+ { GLFW_KEY_APOSTROPHE, "'" },
+ { GLFW_KEY_COMMA, "," },
+ { GLFW_KEY_MINUS, "-" },
+ { GLFW_KEY_PERIOD, "." },
+ { GLFW_KEY_SLASH, "/" },
+ { GLFW_KEY_0, "0" },
+ { GLFW_KEY_1, "1" },
+ { GLFW_KEY_2, "2" },
+ { GLFW_KEY_3, "3" },
+ { GLFW_KEY_4, "4" },
+ { GLFW_KEY_5, "5" },
+ { GLFW_KEY_6, "6" },
+ { GLFW_KEY_7, "7" },
+ { GLFW_KEY_8, "8" },
+ { GLFW_KEY_9, "9" },
+ { GLFW_KEY_SEMICOLON, ";" },
+ { GLFW_KEY_EQUAL, "=" },
+ { GLFW_KEY_A, "A" },
+ { GLFW_KEY_B, "B" },
+ { GLFW_KEY_C, "C" },
+ { GLFW_KEY_D, "D" },
+ { GLFW_KEY_E, "E" },
+ { GLFW_KEY_F, "F" },
+ { GLFW_KEY_G, "G" },
+ { GLFW_KEY_H, "H" },
+ { GLFW_KEY_I, "I" },
+ { GLFW_KEY_J, "J" },
+ { GLFW_KEY_K, "K" },
+ { GLFW_KEY_L, "L" },
+ { GLFW_KEY_M, "M" },
+ { GLFW_KEY_N, "N" },
+ { GLFW_KEY_O, "O" },
+ { GLFW_KEY_P, "P" },
+ { GLFW_KEY_Q, "Q" },
+ { GLFW_KEY_R, "R" },
+ { GLFW_KEY_S, "S" },
+ { GLFW_KEY_T, "T" },
+ { GLFW_KEY_U, "U" },
+ { GLFW_KEY_V, "V" },
+ { GLFW_KEY_W, "W" },
+ { GLFW_KEY_X, "X" },
+ { GLFW_KEY_Y, "Y" },
+ { GLFW_KEY_Z, "Z" },
+ { GLFW_KEY_LEFT_BRACKET, "[" },
+ { GLFW_KEY_BACKSLASH, "\\" },
+ { GLFW_KEY_RIGHT_BRACKET, "]" },
+ { GLFW_KEY_GRAVE_ACCENT, "`" },
+ { GLFW_KEY_WORLD_1, "WORLD_1" },
+ { GLFW_KEY_WORLD_2, "WORLD_2" },
+ { GLFW_KEY_ESCAPE, "ESCAPE" },
+ { GLFW_KEY_ENTER, "ENTER" },
+ { GLFW_KEY_TAB, "TAB" },
+ { GLFW_KEY_BACKSPACE, "BACKSPACE" },
+ { GLFW_KEY_INSERT, "INSERT" },
+ { GLFW_KEY_DELETE, "DELETE" },
+ { GLFW_KEY_RIGHT, "RIGHT" },
+ { GLFW_KEY_LEFT, "LEFT" },
+ { GLFW_KEY_DOWN, "DOWN" },
+ { GLFW_KEY_UP, "UP" },
+ { GLFW_KEY_PAGE_UP, "PAGE_UP" },
+ { GLFW_KEY_PAGE_DOWN, "PAGE_DOWN" },
+ { GLFW_KEY_HOME, "HOME" },
+ { GLFW_KEY_END, "END" },
+ { GLFW_KEY_CAPS_LOCK, "CAPS_LOCK" },
+ { GLFW_KEY_SCROLL_LOCK, "SCROLL_LOCK" },
+ { GLFW_KEY_NUM_LOCK, "NUM_LOCK" },
+ { GLFW_KEY_PRINT_SCREEN, "PRINT_SCREEN" },
+ { GLFW_KEY_PAUSE, "PAUSE" },
+ { GLFW_KEY_F1, "F1" },
+ { GLFW_KEY_F2, "F2" },
+ { GLFW_KEY_F3, "F3" },
+ { GLFW_KEY_F4, "F4" },
+ { GLFW_KEY_F5, "F5" },
+ { GLFW_KEY_F6, "F6" },
+ { GLFW_KEY_F7, "F7" },
+ { GLFW_KEY_F8, "F8" },
+ { GLFW_KEY_F9, "F9" },
+ { GLFW_KEY_F10, "F10" },
+ { GLFW_KEY_F11, "F11" },
+ { GLFW_KEY_F12, "F12" },
+ { GLFW_KEY_F13, "F13" },
+ { GLFW_KEY_F14, "F14" },
+ { GLFW_KEY_F15, "F15" },
+ { GLFW_KEY_F16, "F16" },
+ { GLFW_KEY_F17, "F17" },
+ { GLFW_KEY_F18, "F18" },
+ { GLFW_KEY_F19, "F19" },
+ { GLFW_KEY_F20, "F20" },
+ { GLFW_KEY_F21, "F21" },
+ { GLFW_KEY_F22, "F22" },
+ { GLFW_KEY_F23, "F23" },
+ { GLFW_KEY_F24, "F24" },
+ { GLFW_KEY_F25, "F25" },
+ { GLFW_KEY_KP_0, "KEYPAD_0" },
+ { GLFW_KEY_KP_1, "KEYPAD_1" },
+ { GLFW_KEY_KP_2, "KEYPAD_2" },
+ { GLFW_KEY_KP_3, "KEYPAD_3" },
+ { GLFW_KEY_KP_4, "KEYPAD_4" },
+ { GLFW_KEY_KP_5, "KEYPAD_5" },
+ { GLFW_KEY_KP_6, "KEYPAD_6" },
+ { GLFW_KEY_KP_7, "KEYPAD_7" },
+ { GLFW_KEY_KP_8, "KEYPAD_8" },
+ { GLFW_KEY_KP_9, "KEYPAD_9" },
+ { GLFW_KEY_KP_DECIMAL, "KEYPAD_POINT" },
+ { GLFW_KEY_KP_DIVIDE, "KEYPAD_DIV" },
+ { GLFW_KEY_KP_MULTIPLY, "KEYPAD_MUL" },
+ { GLFW_KEY_KP_SUBTRACT, "KEYPAD_MINUS" },
+ { GLFW_KEY_KP_ADD, "KEYPAD_PLUS" },
+ { GLFW_KEY_KP_ENTER, "KEYPAD_ENTER" },
+ { GLFW_KEY_KP_EQUAL, "KEYPAD_EQUAL" },
+ { GLFW_KEY_LEFT_SHIFT, "LEFT_SHIFT" },
+ { GLFW_KEY_LEFT_CONTROL, "LEFT_CTRL" },
+ { GLFW_KEY_LEFT_ALT, "LEFT_ALT" },
+ { GLFW_KEY_LEFT_SUPER, "LEFT_SUPER" },
+ { GLFW_KEY_RIGHT_SHIFT, "RIGHT_SHIFT" },
+ { GLFW_KEY_RIGHT_CONTROL, "RIGHT_CTRL" },
+ { GLFW_KEY_RIGHT_ALT, "RIGHT_ALT" },
+ { GLFW_KEY_RIGHT_SUPER, "RIGHT_SUPER" },
+ { GLFW_KEY_MENU, "MENU" },
+};
+
+static std::string_view get_key_name(int keycode)
+{
+ for(const auto& it : key_names) {
+ if(it.first == keycode) {
+ return it.second;
+ }
+ }
+
+ return UNKNOWN_KEY_NAME;
+}
+
+config::KeyBind::KeyBind(void)
+{
+ m_glfw_keycode = GLFW_KEY_UNKNOWN;
+ m_name = UNKNOWN_KEY_NAME;
+}
+
+config::KeyBind::KeyBind(int default_value)
+{
+ if(default_value == DEBUG_KEY) {
+ m_glfw_keycode = GLFW_KEY_UNKNOWN;
+ m_name = UNKNOWN_KEY_NAME;
+ }
+ else {
+ m_glfw_keycode = default_value;
+ m_name = get_key_name(default_value);
+ }
+}
+
+void config::KeyBind::set(std::string_view value)
+{
+ for(const auto& it : key_names) {
+ if((it.first != DEBUG_KEY) && 0 == it.second.compare(value)) {
+ m_glfw_keycode = it.first;
+ m_name = it.second;
+ return;
+ }
+ }
+
+ m_glfw_keycode = GLFW_KEY_UNKNOWN;
+ m_name = UNKNOWN_KEY_NAME;
+}
+
+std::string_view config::KeyBind::get(void) const
+{
+ return m_name;
+}
+
+void config::KeyBind::set_key(int keycode)
+{
+ if(keycode == DEBUG_KEY) {
+ m_glfw_keycode = GLFW_KEY_UNKNOWN;
+ m_name = UNKNOWN_KEY_NAME;
+ }
+ else {
+ m_glfw_keycode = keycode;
+ m_name = get_key_name(keycode);
+ }
+}
+
+int config::KeyBind::get_key(void) const
+{
+ return m_glfw_keycode;
+}
+
+bool config::KeyBind::equals(int keycode) const
+{
+ return m_glfw_keycode == keycode;
+}
diff --git a/game/client/config/keybind.hh b/game/client/config/keybind.hh index dff6c18..51a67d6 100644 --- a/game/client/config/keybind.hh +++ b/game/client/config/keybind.hh @@ -1,25 +1,25 @@ -#pragma once - -#include "core/config/ivalue.hh" - -namespace config -{ -class KeyBind final : public IValue { -public: - explicit KeyBind(void); - explicit KeyBind(int default_value); - virtual ~KeyBind(void) = default; - - virtual void set(std::string_view value) override; - virtual std::string_view get(void) const override; - - void set_key(int keycode); - int get_key(void) const; - - bool equals(int keycode) const; - -private: - std::string_view m_name; - int m_glfw_keycode; -}; -} // namespace config +#pragma once
+
+#include "core/config/ivalue.hh"
+
+namespace config
+{
+class KeyBind final : public IValue {
+public:
+ explicit KeyBind(void);
+ explicit KeyBind(int default_value);
+ virtual ~KeyBind(void) = default;
+
+ virtual void set(std::string_view value) override;
+ virtual std::string_view get(void) const override;
+
+ void set_key(int keycode);
+ int get_key(void) const;
+
+ bool equals(int keycode) const;
+
+private:
+ std::string_view m_name;
+ int m_glfw_keycode;
+};
+} // namespace config
diff --git a/game/client/const.hh b/game/client/const.hh index 461b500..b1e458f 100644 --- a/game/client/const.hh +++ b/game/client/const.hh @@ -1,23 +1,23 @@ -#pragma once - -#include "shared/const.hh" - -// This key is then going to be reserved for only -// the debug toggles and users won't be able to -// use this key for conventional gameplay things -constexpr static int DEBUG_KEY = GLFW_KEY_F3; - -constexpr static int BASE_WIDTH = 320; -constexpr static int BASE_HEIGHT = 240; - -constexpr static int MIN_WIDTH = 2 * BASE_WIDTH; -constexpr static int MIN_HEIGHT = 2 * BASE_HEIGHT; - -constexpr static int DEFAULT_WIDTH = 720; -constexpr static int DEFAULT_HEIGHT = 480; - -static_assert(DEFAULT_WIDTH >= MIN_WIDTH); -static_assert(DEFAULT_HEIGHT >= MIN_HEIGHT); - -constexpr static float MIN_PITCH = 0.0625f; -constexpr static float MAX_PITCH = 10.0f; +#pragma once
+
+#include "shared/const.hh"
+
+// This key is then going to be reserved for only
+// the debug toggles and users won't be able to
+// use this key for conventional gameplay things
+constexpr static int DEBUG_KEY = GLFW_KEY_F3;
+
+constexpr static int BASE_WIDTH = 320;
+constexpr static int BASE_HEIGHT = 240;
+
+constexpr static int MIN_WIDTH = 2 * BASE_WIDTH;
+constexpr static int MIN_HEIGHT = 2 * BASE_HEIGHT;
+
+constexpr static int DEFAULT_WIDTH = 720;
+constexpr static int DEFAULT_HEIGHT = 480;
+
+static_assert(DEFAULT_WIDTH >= MIN_WIDTH);
+static_assert(DEFAULT_HEIGHT >= MIN_HEIGHT);
+
+constexpr static float MIN_PITCH = 0.0625f;
+constexpr static float MAX_PITCH = 10.0f;
diff --git a/game/client/entity/camera.cc b/game/client/entity/camera.cc index c17291f..471f2b6 100644 --- a/game/client/entity/camera.cc +++ b/game/client/entity/camera.cc @@ -1,114 +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; -} +#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/game/client/entity/camera.hh b/game/client/entity/camera.hh index 67baf72..faefe78 100644 --- a/game/client/entity/camera.hh +++ b/game/client/entity/camera.hh @@ -1,31 +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 +#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/game/client/entity/factory.cc b/game/client/entity/factory.cc index f6f6079..5a1785a 100644 --- a/game/client/entity/factory.cc +++ b/game/client/entity/factory.cc @@ -1,30 +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); - } -} +#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/game/client/entity/factory.hh b/game/client/entity/factory.hh index 63e6e44..8cc8208 100644 --- a/game/client/entity/factory.hh +++ b/game/client/entity/factory.hh @@ -1,11 +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 +#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/game/client/entity/interpolation.cc b/game/client/entity/interpolation.cc index 9eca735..ef23a4c 100644 --- a/game/client/entity/interpolation.cc +++ b/game/client/entity/interpolation.cc @@ -1,64 +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] = math::lerp(previous.angles[0], current.angles[0], alpha); - interp.angles[1] = math::lerp(previous.angles[1], current.angles[1], alpha); - interp.angles[2] = math::lerp(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 = math::lerp(previous_local.x, current.local.x, alpha); - interp.local.y = math::lerp(previous_local.y, current.local.y, alpha); - interp.local.z = math::lerp(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] = math::lerp(previous.angles[0], current.angles[0], alpha); - interp.angles[1] = math::lerp(previous.angles[1], current.angles[1], alpha); - interp.angles[2] = math::lerp(previous.angles[2], current.angles[2], alpha); - - interp.offset.x = math::lerp(previous.offset.x, current.offset.x, alpha); - interp.offset.y = math::lerp(previous.offset.y, current.offset.y, alpha); - interp.offset.z = math::lerp(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); - } +#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] = math::lerp(previous.angles[0], current.angles[0], alpha);
+ interp.angles[1] = math::lerp(previous.angles[1], current.angles[1], alpha);
+ interp.angles[2] = math::lerp(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 = math::lerp(previous_local.x, current.local.x, alpha);
+ interp.local.y = math::lerp(previous_local.y, current.local.y, alpha);
+ interp.local.z = math::lerp(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] = math::lerp(previous.angles[0], current.angles[0], alpha);
+ interp.angles[1] = math::lerp(previous.angles[1], current.angles[1], alpha);
+ interp.angles[2] = math::lerp(previous.angles[2], current.angles[2], alpha);
+
+ interp.offset.x = math::lerp(previous.offset.x, current.offset.x, alpha);
+ interp.offset.y = math::lerp(previous.offset.y, current.offset.y, alpha);
+ interp.offset.z = math::lerp(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/game/client/entity/interpolation.hh b/game/client/entity/interpolation.hh index 6935fe8..a7c4dac 100644 --- a/game/client/entity/interpolation.hh +++ b/game/client/entity/interpolation.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace entity::interpolation -{ -void update(void); -} // namespace entity::interpolation +#pragma once
+
+namespace entity::interpolation
+{
+void update(void);
+} // namespace entity::interpolation
diff --git a/game/client/entity/listener.cc b/game/client/entity/listener.cc index 3b1a35b..3d732ae 100644 --- a/game/client/entity/listener.cc +++ b/game/client/entity/listener.cc @@ -1,42 +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, math::clamp(sound::volume_master.get_value() * 0.01f, 0.0f, 1.0f)); -} +#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, math::clamp(sound::volume_master.get_value() * 0.01f, 0.0f, 1.0f));
+}
diff --git a/game/client/entity/listener.hh b/game/client/entity/listener.hh index 594cde1..817e2fd 100644 --- a/game/client/entity/listener.hh +++ b/game/client/entity/listener.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace entity::listener -{ -void update(void); -} // namespace entity::listener +#pragma once
+
+namespace entity::listener
+{
+void update(void);
+} // namespace entity::listener
diff --git a/game/client/entity/player_look.cc b/game/client/entity/player_look.cc index 715475b..caa367e 100644 --- a/game/client/entity/player_look.cc +++ b/game/client/entity/player_look.cc @@ -1,155 +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] = math::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); - } -} +#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] = math::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/game/client/entity/player_look.hh b/game/client/entity/player_look.hh index 0ae18db..b347031 100644 --- a/game/client/entity/player_look.hh +++ b/game/client/entity/player_look.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace entity::player_look -{ -void init(void); -void update_late(void); -} // namespace entity::player_look +#pragma once
+
+namespace entity::player_look
+{
+void init(void);
+void update_late(void);
+} // namespace entity::player_look
diff --git a/game/client/entity/player_move.cc b/game/client/entity/player_move.cc index fb49754..2570780 100644 --- a/game/client/entity/player_move.cc +++ b/game/client/entity/player_move.cc @@ -1,298 +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 = math::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 = math::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(math::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>; - } - } -} +#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 = math::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 = math::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(math::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/game/client/entity/player_move.hh b/game/client/entity/player_move.hh index 8c033cc..8026acf 100644 --- a/game/client/entity/player_move.hh +++ b/game/client/entity/player_move.hh @@ -1,15 +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 +#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/game/client/entity/sound_emitter.cc b/game/client/entity/sound_emitter.cc index eeb7294..c2a93a1 100644 --- a/game/client/entity/sound_emitter.cc +++ b/game/client/entity/sound_emitter.cc @@ -1,63 +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 = math::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; - } - } - } -} +#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 = math::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/game/client/entity/sound_emitter.hh b/game/client/entity/sound_emitter.hh index 72a3f74..14093db 100644 --- a/game/client/entity/sound_emitter.hh +++ b/game/client/entity/sound_emitter.hh @@ -1,20 +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 +#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
diff --git a/game/client/experiments.cc b/game/client/experiments.cc index 247bce9..2b9fe89 100644 --- a/game/client/experiments.cc +++ b/game/client/experiments.cc @@ -1,81 +1,81 @@ -#include "client/pch.hh" - -#include "client/experiments.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/item_registry.hh" - -#include "shared/game_items.hh" -#include "shared/game_voxels.hh" - -#include "client/gui/chat.hh" -#include "client/gui/hotbar.hh" -#include "client/gui/status_lines.hh" - -#include "client/io/glfw.hh" - -#include "client/world/player_target.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event) -{ - if(!globals::gui_screen && session::is_ingame()) { - if((event.action == GLFW_PRESS) && (world::player_target::voxel != NULL_VOXEL_ID)) { - if(event.button == GLFW_MOUSE_BUTTON_LEFT) { - experiments::attack(); - return; - } - - if(event.button == GLFW_MOUSE_BUTTON_RIGHT) { - experiments::interact(); - return; - } - } - } -} - -void experiments::init(void) -{ - globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>(); -} - -void experiments::init_late(void) -{ - gui::hotbar::slots[0] = game_items::cobblestone; - gui::hotbar::slots[1] = game_items::stone; - gui::hotbar::slots[2] = game_items::dirt; - gui::hotbar::slots[3] = game_items::grass; - gui::hotbar::slots[4] = game_items::oak_leaves; - gui::hotbar::slots[5] = game_items::oak_planks; - gui::hotbar::slots[6] = game_items::oak_log; - gui::hotbar::slots[7] = game_items::glass; - gui::hotbar::slots[8] = game_items::slime; -} - -void experiments::shutdown(void) -{ -} - -void experiments::update(void) -{ -} - -void experiments::update_late(void) -{ -} - -void experiments::attack(void) -{ - globals::dimension->set_voxel(NULL_VOXEL_ID, world::player_target::coord); -} - -void experiments::interact(void) -{ - if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - if(info->place_voxel != NULL_VOXEL_ID) { - globals::dimension->set_voxel(info->place_voxel, world::player_target::coord + world::player_target::normal); - } - } -} +#include "client/pch.hh"
+
+#include "client/experiments.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/item_registry.hh"
+
+#include "shared/game_items.hh"
+#include "shared/game_voxels.hh"
+
+#include "client/gui/chat.hh"
+#include "client/gui/hotbar.hh"
+#include "client/gui/status_lines.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/world/player_target.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event)
+{
+ if(!globals::gui_screen && session::is_ingame()) {
+ if((event.action == GLFW_PRESS) && (world::player_target::voxel != NULL_VOXEL_ID)) {
+ if(event.button == GLFW_MOUSE_BUTTON_LEFT) {
+ experiments::attack();
+ return;
+ }
+
+ if(event.button == GLFW_MOUSE_BUTTON_RIGHT) {
+ experiments::interact();
+ return;
+ }
+ }
+ }
+}
+
+void experiments::init(void)
+{
+ globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>();
+}
+
+void experiments::init_late(void)
+{
+ gui::hotbar::slots[0] = game_items::cobblestone;
+ gui::hotbar::slots[1] = game_items::stone;
+ gui::hotbar::slots[2] = game_items::dirt;
+ gui::hotbar::slots[3] = game_items::grass;
+ gui::hotbar::slots[4] = game_items::oak_leaves;
+ gui::hotbar::slots[5] = game_items::oak_planks;
+ gui::hotbar::slots[6] = game_items::oak_log;
+ gui::hotbar::slots[7] = game_items::glass;
+ gui::hotbar::slots[8] = game_items::slime;
+}
+
+void experiments::shutdown(void)
+{
+}
+
+void experiments::update(void)
+{
+}
+
+void experiments::update_late(void)
+{
+}
+
+void experiments::attack(void)
+{
+ globals::dimension->set_voxel(NULL_VOXEL_ID, world::player_target::coord);
+}
+
+void experiments::interact(void)
+{
+ if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) {
+ if(info->place_voxel != NULL_VOXEL_ID) {
+ globals::dimension->set_voxel(info->place_voxel, world::player_target::coord + world::player_target::normal);
+ }
+ }
+}
diff --git a/game/client/experiments.hh b/game/client/experiments.hh index ff2cbad..c1b0769 100644 --- a/game/client/experiments.hh +++ b/game/client/experiments.hh @@ -1,16 +1,16 @@ -#pragma once - -namespace experiments -{ -void init(void); -void init_late(void); -void shutdown(void); -void update(void); -void update_late(void); -} // namespace experiments - -namespace experiments -{ -void attack(void); -void interact(void); -} // namespace experiments +#pragma once
+
+namespace experiments
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void update(void);
+void update_late(void);
+} // namespace experiments
+
+namespace experiments
+{
+void attack(void);
+void interact(void);
+} // namespace experiments
diff --git a/game/client/game.cc b/game/client/game.cc index 118ac66..a59aec3 100644 --- a/game/client/game.cc +++ b/game/client/game.cc @@ -1,693 +1,693 @@ -#include "client/pch.hh" - -#include "client/game.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/math/angles.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/physfs.hh" - -#include "shared/entity/collision.hh" -#include "shared/entity/gravity.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/stasis.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/game_items.hh" -#include "shared/game_voxels.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/item_registry.hh" -#include "shared/world/ray_dda.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "client/config/keybind.hh" - -#include "client/entity/camera.hh" -#include "client/entity/interpolation.hh" -#include "client/entity/listener.hh" -#include "client/entity/player_look.hh" -#include "client/entity/player_move.hh" -#include "client/entity/sound_emitter.hh" - -#include "client/gui/background.hh" -#include "client/gui/bother.hh" -#include "client/gui/chat.hh" -#include "client/gui/crosshair.hh" -#include "client/gui/direct_connection.hh" -#include "client/gui/gui_screen.hh" -#include "client/gui/hotbar.hh" -#include "client/gui/language.hh" -#include "client/gui/main_menu.hh" -#include "client/gui/message_box.hh" -#include "client/gui/metrics.hh" -#include "client/gui/play_menu.hh" -#include "client/gui/progress_bar.hh" -#include "client/gui/scoreboard.hh" -#include "client/gui/settings.hh" -#include "client/gui/splash.hh" -#include "client/gui/status_lines.hh" -#include "client/gui/window_title.hh" - -#include "client/io/gamepad.hh" -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/sound/sound.hh" - -#include "client/world/chunk_mesher.hh" -#include "client/world/chunk_renderer.hh" -#include "client/world/chunk_visibility.hh" -#include "client/world/outline.hh" -#include "client/world/player_target.hh" -#include "client/world/skybox.hh" -#include "client/world/voxel_anims.hh" -#include "client/world/voxel_atlas.hh" -#include "client/world/voxel_sounds.hh" - -#include "client/const.hh" -#include "client/experiments.hh" -#include "client/globals.hh" -#include "client/receive.hh" -#include "client/screenshot.hh" -#include "client/session.hh" -#include "client/toggles.hh" - -config::Boolean client_game::streamer_mode(false); -config::Boolean client_game::vertical_sync(true); -config::Boolean client_game::world_curvature(true); -config::Unsigned client_game::fog_mode(1U, 0U, 2U); -config::String client_game::username("player"); - -bool client_game::hide_hud = false; - -static config::KeyBind hide_hud_toggle(GLFW_KEY_F1); - -static ImFont* load_font(std::string_view path, float size, ImFontConfig& font_config, ImVector<ImWchar>& ranges) -{ - bool font_load_success; - std::vector<std::byte> font; - - if(!utils::read_file(path, font)) { - spdlog::error("{}: utils::read_file failed", path); - std::terminate(); - } - - auto& io = ImGui::GetIO(); - auto font_ptr = io.Fonts->AddFontFromMemoryTTF(font.data(), font.size(), size, &font_config, ranges.Data); - - if(font_ptr == nullptr) { - spdlog::error("{}: AddFontFromMemoryTTF failed", path); - std::terminate(); - } - - return font_ptr; -} - -static void on_glfw_framebuffer_size(const io::GlfwFramebufferSizeEvent& event) -{ - if(globals::world_fbo) { - glDeleteRenderbuffers(1, &globals::world_fbo_depth); - glDeleteTextures(1, &globals::world_fbo_color); - glDeleteFramebuffers(1, &globals::world_fbo); - } - - glGenFramebuffers(1, &globals::world_fbo); - glGenTextures(1, &globals::world_fbo_color); - glGenRenderbuffers(1, &globals::world_fbo_depth); - - glBindTexture(GL_TEXTURE_2D, globals::world_fbo_color); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, event.size.x, event.size.y, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); - - glBindRenderbuffer(GL_RENDERBUFFER, globals::world_fbo_depth); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, event.size.x, event.size.y); - - glBindFramebuffer(GL_FRAMEBUFFER, globals::world_fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, globals::world_fbo_color, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, globals::world_fbo_depth); - - if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - spdlog::critical("opengl: world framebuffer is incomplete"); - glDeleteRenderbuffers(1, &globals::world_fbo_depth); - glDeleteTextures(1, &globals::world_fbo_color); - glDeleteFramebuffers(1, &globals::world_fbo); - std::terminate(); - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(!globals::gui_keybind_ptr && hide_hud_toggle.equals(event.key) && (event.action == GLFW_PRESS)) { - client_game::hide_hud = !client_game::hide_hud; - } -} - -void client_game::init(void) -{ - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.IniFilename = nullptr; - io.Fonts->Clear(); - - ImFontConfig font_config; - font_config.FontDataOwnedByAtlas = false; - - ImFontGlyphRangesBuilder builder; - builder.AddRanges(io.Fonts->GetGlyphRangesDefault()); - builder.AddRanges(io.Fonts->GetGlyphRangesCyrillic()); - - ImVector<ImWchar> ranges; - builder.BuildRanges(&ranges); - - globals::font_unscii16 = load_font("fonts/unscii-16.ttf", 16.0f, font_config, ranges); - globals::font_unscii8 = load_font("fonts/unscii-8.ttf", 8.0f, font_config, ranges); - - gui::client_splash::init(); - gui::client_splash::render(); - - globals::client_config.add_value("game.streamer_mode", client_game::streamer_mode); - globals::client_config.add_value("game.vertical_sync", client_game::vertical_sync); - globals::client_config.add_value("game.world_curvature", client_game::world_curvature); - globals::client_config.add_value("game.fog_mode", client_game::fog_mode); - globals::client_config.add_value("game.username", client_game::username); - globals::client_config.add_value("game.key.toggle_hide_hud", hide_hud_toggle); - - settings::init(); - - settings::add_checkbox(0, client_game::streamer_mode, settings_location::VIDEO_GUI, "game.streamer_mode", true); - settings::add_checkbox(5, client_game::vertical_sync, settings_location::VIDEO, "game.vertical_sync", false); - settings::add_checkbox(4, client_game::world_curvature, settings_location::VIDEO, "game.world_curvature", true); - settings::add_stepper(3, client_game::fog_mode, settings_location::VIDEO, "game.fog_mode", false); - settings::add_input(1, client_game::username, settings_location::GENERAL, "game.username", true, false); - settings::add_keybind(4, hide_hud_toggle, settings_location::KEYBOARD_MISC, "game.key.toggle_hide_hud"); - - globals::client_host = enet_host_create(nullptr, 1, 1, 0, 0); - - if(!globals::client_host) { - spdlog::critical("game: unable to setup an ENet host"); - std::terminate(); - } - - gui::language::init(); - - session::init(); - - entity::player_look::init(); - entity::player_move::init(); - world::player_target::init(); - - io::gamepad::init(); - - entity::camera::init(); - - world::voxel_anims::init(); - - world::outline::init(); - world::chunk_mesher::init(); - world::chunk_renderer::init(); - - globals::world_fbo = 0; - globals::world_fbo_color = 0; - globals::world_fbo_depth = 0; - - world::voxel_sounds::init(); - - world::skybox::init(); - - ImGuiStyle& style = ImGui::GetStyle(); - - // Black buttons on a dark background - // may be harder to read than the text on them - style.FrameBorderSize = 1.0; - style.TabBorderSize = 1.0; - - // Rounding on elements looks cool but I am - // aiming for a more or less blocky and - // visually simple HiDPI-friendly UI style - style.TabRounding = 0.0f; - style.GrabRounding = 0.0f; - style.ChildRounding = 0.0f; - style.FrameRounding = 0.0f; - style.PopupRounding = 0.0f; - style.WindowRounding = 0.0f; - style.ScrollbarRounding = 0.0f; - - style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - style.Colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); - style.Colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - style.Colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); - style.Colors[ImGuiCol_Border] = ImVec4(0.79f, 0.79f, 0.79f, 0.50f); - style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - style.Colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.54f); - style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.36f, 0.36f, 0.36f, 0.40f); - style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.63f, 0.63f, 0.63f, 0.67f); - style.Colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); - style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); - style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); - style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f); - style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - style.Colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.81f, 0.81f, 0.81f, 0.75f); - style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - style.Colors[ImGuiCol_Button] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - style.Colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f); - style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - style.Colors[ImGuiCol_Separator] = ImVec4(0.49f, 0.49f, 0.49f, 0.50f); - style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.56f, 0.56f, 0.56f, 0.78f); - style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.34f, 0.34f, 0.34f, 0.20f); - style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.57f, 0.57f, 0.57f, 0.67f); - style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.95f); - style.Colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f); - style.Colors[ImGuiCol_TabHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - style.Colors[ImGuiCol_TabActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); - style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.13f, 0.13f, 0.13f, 0.97f); - style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f); - style.Colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); - style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.69f, 0.00f, 0.00f, 1.00f); - style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.00f, 1.00f, 0.20f, 1.00f); - style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); - style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); - style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); - style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); - style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.61f, 0.61f, 0.61f, 0.35f); - style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f); - style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); - - // Making my own Game UI for Source Engine - // taught me one important thing: dimensions - // of UI elements must be calculated at semi-runtime - // so there's simply no point for an INI file. - io.IniFilename = nullptr; - - toggles::init(); - - gui::background::init(); - - gui::scoreboard::init(); - - gui::client_chat::init(); - - gui::bother::init(); - - gui::main_menu::init(); - gui::play_menu::init(); - gui::progress_bar::init(); - gui::message_box::init(); - gui::direct_connection::init(); - - gui::crosshair::init(); - gui::hotbar::init(); - gui::metrics::init(); - gui::status_lines::init(); - - screenshot::init(); - - globals::gui_keybind_ptr = nullptr; - globals::gui_scale = 0U; - globals::gui_screen = GUI_MAIN_MENU; - - sound::init_config(); - - if(globals::sound_ctx) { - sound::init(); - } - - client_receive::init(); - - experiments::init(); - - globals::dispatcher.sink<io::GlfwFramebufferSizeEvent>().connect<&on_glfw_framebuffer_size>(); - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); -} - -void client_game::init_late(void) -{ - toggles::init_late(); - - if(globals::sound_ctx) { - sound::init_late(); - } - - gui::language::init_late(); - - settings::init_late(); - - gui::client_chat::init_late(); - - gui::status_lines::init_late(); - - game_voxels::populate(); - game_items::populate(); - - std::size_t max_texture_count = 0; - - // Figure out the total texture count - // NOTE: this is very debug, early and a quite - // conservative limit choice; there must be a better - // way to make this limit way smaller than it currently is - for(const std::shared_ptr<world::VoxelInfo>& info : world::voxel_registry::voxels) { - for(const world::VoxelTexture& vtex : info->textures) { - max_texture_count += vtex.paths.size(); - } - } - - // UNDONE: asset packs for non-16x16 stuff - world::voxel_atlas::create(16, 16, max_texture_count); - - for(std::shared_ptr<world::VoxelInfo>& info : world::voxel_registry::voxels) { - for(world::VoxelTexture& vtex : info->textures) { - if(auto strip = world::voxel_atlas::find_or_load(vtex.paths)) { - vtex.cached_offset = strip->offset; - vtex.cached_plane = strip->plane; - continue; - } - - spdlog::critical("client_gl: {}: failed to load atlas strips", info->name); - std::terminate(); - } - } - - world::voxel_atlas::generate_mipmaps(); - - for(std::shared_ptr<world::ItemInfo>& info : world::item_registry::items) { - info->cached_texture = resource::load<TextureGUI>(info->texture.c_str(), TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - } - - experiments::init_late(); - - gui::client_splash::init_late(); - - gui::window_title::update(); -} - -void client_game::shutdown(void) -{ - world::voxel_sounds::shutdown(); - - experiments::shutdown(); - - session::shutdown(); - - if(globals::sound_ctx) { - sound::shutdown(); - } - - gui::hotbar::shutdown(); - gui::main_menu::shutdown(); - gui::play_menu::shutdown(); - - gui::bother::shutdown(); - - gui::client_chat::shutdown(); - - gui::background::shutdown(); - - gui::crosshair::shutdown(); - - delete globals::dimension; - globals::player = entt::null; - globals::dimension = nullptr; - - world::item_registry::purge(); - world::voxel_registry::purge(); - - world::voxel_atlas::destroy(); - - glDeleteRenderbuffers(1, &globals::world_fbo_depth); - glDeleteTextures(1, &globals::world_fbo_color); - glDeleteFramebuffers(1, &globals::world_fbo); - - world::outline::shutdown(); - world::chunk_renderer::shutdown(); - world::chunk_mesher::shutdown(); - - enet_host_destroy(globals::client_host); -} - -void client_game::fixed_update(void) -{ - entity::player_move::fixed_update(); - - // Only update world simulation gamesystems - // if the player can actually observe all the - // changes these gamesystems cause visually - if(session::is_ingame()) { - entity::Collision::fixed_update(globals::dimension); - entity::Velocity::fixed_update(globals::dimension); - entity::Transform::fixed_update(globals::dimension); - entity::Gravity::fixed_update(globals::dimension); - entity::Stasis::fixed_update(globals::dimension); - } -} - -void client_game::fixed_update_late(void) -{ - if(session::is_ingame()) { - const auto& head = globals::dimension->entities.get<entity::Head>(globals::player); - const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player); - const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player); - - protocol::EntityHead head_packet; - head_packet.entity = entt::null; // ignored by server - head_packet.angles = head.angles; - - protocol::EntityTransform transform_packet; - transform_packet.entity = entt::null; // ignored by server - transform_packet.angles = transform.angles; - transform_packet.chunk = transform.chunk; - transform_packet.local = transform.local; - - protocol::EntityVelocity velocity_packet; - velocity_packet.entity = entt::null; // ignored by server - velocity_packet.value = velocity.value; - - protocol::send(session::peer, protocol::encode(head_packet)); - protocol::send(session::peer, protocol::encode(transform_packet)); - protocol::send(session::peer, protocol::encode(velocity_packet)); - } -} - -void client_game::update(void) -{ - if(session::is_ingame()) { - if(toggles::get(TOGGLE_PM_FLIGHT)) { - globals::dimension->entities.remove<entity::Gravity>(globals::player); - } - else { - globals::dimension->entities.emplace_or_replace<entity::Gravity>(globals::player); - } - } - - if(globals::sound_ctx) { - sound::update(); - - entity::listener::update(); - - entity::SoundEmitter::update(); - } - - entity::interpolation::update(); - - world::player_target::update(); - - entity::camera::update(); - - world::voxel_anims::update(); - - world::chunk_mesher::update(); - - gui::client_chat::update(); - - experiments::update(); - - constexpr auto half_base_width = 0.5f * static_cast<float>(BASE_WIDTH); - constexpr auto half_base_height = 0.5f * static_cast<float>(BASE_HEIGHT); - - auto twice_scale_x = static_cast<float>(globals::width) / half_base_width; - auto twice_scale_y = static_cast<float>(globals::height) / half_base_height; - - auto scale_x = math::max(1.0f, 0.5f * glm::floor(twice_scale_x)); - auto scale_y = math::max(1.0f, 0.5f * glm::floor(twice_scale_y)); - auto scale_min = math::ceil<unsigned int>(math::min(scale_x, scale_y)); - auto scale_int = math::max(1U, (scale_min / 2U) * 2U); - - auto& io = ImGui::GetIO(); - io.FontGlobalScale = scale_int; - globals::gui_scale = scale_int; -} - -void client_game::update_late(void) -{ - ENetEvent enet_event; - - while(0 < enet_host_service(globals::client_host, &enet_event, 0)) { - switch(enet_event.type) { - case ENET_EVENT_TYPE_CONNECT: - session::send_login_request(); - break; - case ENET_EVENT_TYPE_DISCONNECT: - session::invalidate(); - break; - case ENET_EVENT_TYPE_RECEIVE: - protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer); - enet_packet_destroy(enet_event.packet); - break; - } - } - - entity::player_look::update_late(); - entity::player_move::update_late(); - - gui::play_menu::update_late(); - - gui::bother::update_late(); - - experiments::update_late(); - - io::gamepad::update_late(); - - world::chunk_visibility::update_late(); - - if(client_game::vertical_sync.get_value()) { - glfwSwapInterval(1); - } - else { - glfwSwapInterval(0); - } -} - -void client_game::render(void) -{ - glViewport(0, 0, globals::width, globals::height); - glBindFramebuffer(GL_FRAMEBUFFER, globals::world_fbo); - glClearColor(world::skybox::fog_color.r, world::skybox::fog_color.g, world::skybox::fog_color.b, 1.000f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - if(globals::dimension) { - world::chunk_renderer::render(); - } - - glEnable(GL_DEPTH_TEST); - - world::player_target::render(); - - if(globals::dimension) { - auto group = globals::dimension->entities.group( - entt::get<entity::Player, entity::Collision, entity::client::HeadIntr, entity::client::TransformIntr>); - - world::outline::prepare(); - - for(const auto [entity, collision, head, transform] : group.each()) { - if(entity == globals::player) { - // Don't render ourselves - continue; - } - - glm::fvec3 forward; - math::vectors(transform.angles + head.angles, forward); - forward *= 2.0f; - - glm::fvec3 hull_size = collision.aabb.max - collision.aabb.min; - glm::fvec3 hull_fpos = transform.local + collision.aabb.min; - glm::fvec3 look = transform.local + head.offset; - - world::outline::cube(transform.chunk, hull_fpos, hull_size, 1.0f, glm::fvec4(1.0f, 0.0f, 0.0f, 1.0f)); - world::outline::line(transform.chunk, look, forward, 1.0f, glm::fvec4(0.9f, 0.9f, 0.9f, 1.0f)); - } - } - - glEnable(GL_DEPTH_TEST); - - glViewport(0, 0, globals::width, globals::height); - glClearColor(0.000f, 0.000f, 0.000f, 1.000f); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClear(GL_COLOR_BUFFER_BIT); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, globals::world_fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBlitFramebuffer(0, 0, globals::width, globals::height, 0, 0, globals::width, globals::height, GL_COLOR_BUFFER_BIT, GL_NEAREST); -} - -void client_game::layout(void) -{ - if(!session::is_ingame()) { - gui::background::layout(); - } - - if(!globals::gui_screen || (globals::gui_screen == GUI_CHAT)) { - if(toggles::get(TOGGLE_METRICS_UI) && !client_game::hide_hud) { - // This contains Minecraft-esque debug information - // about the hardware, world state and other - // things that might be uesful - gui::metrics::layout(); - } - } - - if(session::is_ingame()) { - gui::client_chat::layout(); - gui::scoreboard::layout(); - - if(!globals::gui_screen && !client_game::hide_hud) { - gui::hotbar::layout(); - gui::status_lines::layout(); - gui::crosshair::layout(); - } - } - - if(globals::gui_screen) { - if(session::is_ingame() && (globals::gui_screen != GUI_CHAT)) { - const float width_f = static_cast<float>(globals::width); - const float height_f = static_cast<float>(globals::height); - const ImU32 darken = ImGui::GetColorU32(ImVec4(0.00f, 0.00f, 0.00f, 0.75f)); - ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(), ImVec2(width_f, height_f), darken); - } - - switch(globals::gui_screen) { - case GUI_MAIN_MENU: - gui::main_menu::layout(); - break; - case GUI_PLAY_MENU: - gui::play_menu::layout(); - break; - case GUI_SETTINGS: - settings::layout(); - break; - case GUI_PROGRESS_BAR: - gui::progress_bar::layout(); - break; - case GUI_MESSAGE_BOX: - gui::message_box::layout(); - break; - case GUI_DIRECT_CONNECTION: - gui::direct_connection::layout(); - break; - } - } -} +#include "client/pch.hh"
+
+#include "client/game.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/angles.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/physfs.hh"
+
+#include "shared/entity/collision.hh"
+#include "shared/entity/gravity.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/stasis.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/game_items.hh"
+#include "shared/game_voxels.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/item_registry.hh"
+#include "shared/world/ray_dda.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/entity/camera.hh"
+#include "client/entity/interpolation.hh"
+#include "client/entity/listener.hh"
+#include "client/entity/player_look.hh"
+#include "client/entity/player_move.hh"
+#include "client/entity/sound_emitter.hh"
+
+#include "client/gui/background.hh"
+#include "client/gui/bother.hh"
+#include "client/gui/chat.hh"
+#include "client/gui/crosshair.hh"
+#include "client/gui/direct_connection.hh"
+#include "client/gui/gui_screen.hh"
+#include "client/gui/hotbar.hh"
+#include "client/gui/language.hh"
+#include "client/gui/main_menu.hh"
+#include "client/gui/message_box.hh"
+#include "client/gui/metrics.hh"
+#include "client/gui/play_menu.hh"
+#include "client/gui/progress_bar.hh"
+#include "client/gui/scoreboard.hh"
+#include "client/gui/settings.hh"
+#include "client/gui/splash.hh"
+#include "client/gui/status_lines.hh"
+#include "client/gui/window_title.hh"
+
+#include "client/io/gamepad.hh"
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/sound/sound.hh"
+
+#include "client/world/chunk_mesher.hh"
+#include "client/world/chunk_renderer.hh"
+#include "client/world/chunk_visibility.hh"
+#include "client/world/outline.hh"
+#include "client/world/player_target.hh"
+#include "client/world/skybox.hh"
+#include "client/world/voxel_anims.hh"
+#include "client/world/voxel_atlas.hh"
+#include "client/world/voxel_sounds.hh"
+
+#include "client/const.hh"
+#include "client/experiments.hh"
+#include "client/globals.hh"
+#include "client/receive.hh"
+#include "client/screenshot.hh"
+#include "client/session.hh"
+#include "client/toggles.hh"
+
+config::Boolean client_game::streamer_mode(false);
+config::Boolean client_game::vertical_sync(true);
+config::Boolean client_game::world_curvature(true);
+config::Unsigned client_game::fog_mode(1U, 0U, 2U);
+config::String client_game::username("player");
+
+bool client_game::hide_hud = false;
+
+static config::KeyBind hide_hud_toggle(GLFW_KEY_F1);
+
+static ImFont* load_font(std::string_view path, float size, ImFontConfig& font_config, ImVector<ImWchar>& ranges)
+{
+ bool font_load_success;
+ std::vector<std::byte> font;
+
+ if(!utils::read_file(path, font)) {
+ spdlog::error("{}: utils::read_file failed", path);
+ std::terminate();
+ }
+
+ auto& io = ImGui::GetIO();
+ auto font_ptr = io.Fonts->AddFontFromMemoryTTF(font.data(), font.size(), size, &font_config, ranges.Data);
+
+ if(font_ptr == nullptr) {
+ spdlog::error("{}: AddFontFromMemoryTTF failed", path);
+ std::terminate();
+ }
+
+ return font_ptr;
+}
+
+static void on_glfw_framebuffer_size(const io::GlfwFramebufferSizeEvent& event)
+{
+ if(globals::world_fbo) {
+ glDeleteRenderbuffers(1, &globals::world_fbo_depth);
+ glDeleteTextures(1, &globals::world_fbo_color);
+ glDeleteFramebuffers(1, &globals::world_fbo);
+ }
+
+ glGenFramebuffers(1, &globals::world_fbo);
+ glGenTextures(1, &globals::world_fbo_color);
+ glGenRenderbuffers(1, &globals::world_fbo_depth);
+
+ glBindTexture(GL_TEXTURE_2D, globals::world_fbo_color);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, event.size.x, event.size.y, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+
+ glBindRenderbuffer(GL_RENDERBUFFER, globals::world_fbo_depth);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, event.size.x, event.size.y);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, globals::world_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, globals::world_fbo_color, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, globals::world_fbo_depth);
+
+ if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ spdlog::critical("opengl: world framebuffer is incomplete");
+ glDeleteRenderbuffers(1, &globals::world_fbo_depth);
+ glDeleteTextures(1, &globals::world_fbo_color);
+ glDeleteFramebuffers(1, &globals::world_fbo);
+ std::terminate();
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(!globals::gui_keybind_ptr && hide_hud_toggle.equals(event.key) && (event.action == GLFW_PRESS)) {
+ client_game::hide_hud = !client_game::hide_hud;
+ }
+}
+
+void client_game::init(void)
+{
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ io.IniFilename = nullptr;
+ io.Fonts->Clear();
+
+ ImFontConfig font_config;
+ font_config.FontDataOwnedByAtlas = false;
+
+ ImFontGlyphRangesBuilder builder;
+ builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
+ builder.AddRanges(io.Fonts->GetGlyphRangesCyrillic());
+
+ ImVector<ImWchar> ranges;
+ builder.BuildRanges(&ranges);
+
+ globals::font_unscii16 = load_font("fonts/unscii-16.ttf", 16.0f, font_config, ranges);
+ globals::font_unscii8 = load_font("fonts/unscii-8.ttf", 8.0f, font_config, ranges);
+
+ gui::client_splash::init();
+ gui::client_splash::render();
+
+ globals::client_config.add_value("game.streamer_mode", client_game::streamer_mode);
+ globals::client_config.add_value("game.vertical_sync", client_game::vertical_sync);
+ globals::client_config.add_value("game.world_curvature", client_game::world_curvature);
+ globals::client_config.add_value("game.fog_mode", client_game::fog_mode);
+ globals::client_config.add_value("game.username", client_game::username);
+ globals::client_config.add_value("game.key.toggle_hide_hud", hide_hud_toggle);
+
+ settings::init();
+
+ settings::add_checkbox(0, client_game::streamer_mode, settings_location::VIDEO_GUI, "game.streamer_mode", true);
+ settings::add_checkbox(5, client_game::vertical_sync, settings_location::VIDEO, "game.vertical_sync", false);
+ settings::add_checkbox(4, client_game::world_curvature, settings_location::VIDEO, "game.world_curvature", true);
+ settings::add_stepper(3, client_game::fog_mode, settings_location::VIDEO, "game.fog_mode", false);
+ settings::add_input(1, client_game::username, settings_location::GENERAL, "game.username", true, false);
+ settings::add_keybind(4, hide_hud_toggle, settings_location::KEYBOARD_MISC, "game.key.toggle_hide_hud");
+
+ globals::client_host = enet_host_create(nullptr, 1, 1, 0, 0);
+
+ if(!globals::client_host) {
+ spdlog::critical("game: unable to setup an ENet host");
+ std::terminate();
+ }
+
+ gui::language::init();
+
+ session::init();
+
+ entity::player_look::init();
+ entity::player_move::init();
+ world::player_target::init();
+
+ io::gamepad::init();
+
+ entity::camera::init();
+
+ world::voxel_anims::init();
+
+ world::outline::init();
+ world::chunk_mesher::init();
+ world::chunk_renderer::init();
+
+ globals::world_fbo = 0;
+ globals::world_fbo_color = 0;
+ globals::world_fbo_depth = 0;
+
+ world::voxel_sounds::init();
+
+ world::skybox::init();
+
+ ImGuiStyle& style = ImGui::GetStyle();
+
+ // Black buttons on a dark background
+ // may be harder to read than the text on them
+ style.FrameBorderSize = 1.0;
+ style.TabBorderSize = 1.0;
+
+ // Rounding on elements looks cool but I am
+ // aiming for a more or less blocky and
+ // visually simple HiDPI-friendly UI style
+ style.TabRounding = 0.0f;
+ style.GrabRounding = 0.0f;
+ style.ChildRounding = 0.0f;
+ style.FrameRounding = 0.0f;
+ style.PopupRounding = 0.0f;
+ style.WindowRounding = 0.0f;
+ style.ScrollbarRounding = 0.0f;
+
+ style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
+ style.Colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
+ style.Colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+ style.Colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
+ style.Colors[ImGuiCol_Border] = ImVec4(0.79f, 0.79f, 0.79f, 0.50f);
+ style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+ style.Colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.54f);
+ style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.36f, 0.36f, 0.36f, 0.40f);
+ style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.63f, 0.63f, 0.63f, 0.67f);
+ style.Colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
+ style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
+ style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
+ style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
+ style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f);
+ style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
+ style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
+ style.Colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.81f, 0.81f, 0.81f, 0.75f);
+ style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_Button] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
+ style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
+ style.Colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f);
+ style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
+ style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
+ style.Colors[ImGuiCol_Separator] = ImVec4(0.49f, 0.49f, 0.49f, 0.50f);
+ style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.56f, 0.56f, 0.56f, 0.78f);
+ style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.34f, 0.34f, 0.34f, 0.20f);
+ style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.57f, 0.57f, 0.57f, 0.67f);
+ style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.95f);
+ style.Colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.75f);
+ style.Colors[ImGuiCol_TabHovered] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
+ style.Colors[ImGuiCol_TabActive] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
+ style.Colors[ImGuiCol_TabUnfocused] = ImVec4(0.13f, 0.13f, 0.13f, 0.97f);
+ style.Colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
+ style.Colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
+ style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.69f, 0.00f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.00f, 1.00f, 0.20f, 1.00f);
+ style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
+ style.Colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
+ style.Colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
+ style.Colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+ style.Colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
+ style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.61f, 0.61f, 0.61f, 0.35f);
+ style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f);
+ style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
+ style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
+ style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
+ style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
+
+ // Making my own Game UI for Source Engine
+ // taught me one important thing: dimensions
+ // of UI elements must be calculated at semi-runtime
+ // so there's simply no point for an INI file.
+ io.IniFilename = nullptr;
+
+ toggles::init();
+
+ gui::background::init();
+
+ gui::scoreboard::init();
+
+ gui::client_chat::init();
+
+ gui::bother::init();
+
+ gui::main_menu::init();
+ gui::play_menu::init();
+ gui::progress_bar::init();
+ gui::message_box::init();
+ gui::direct_connection::init();
+
+ gui::crosshair::init();
+ gui::hotbar::init();
+ gui::metrics::init();
+ gui::status_lines::init();
+
+ screenshot::init();
+
+ globals::gui_keybind_ptr = nullptr;
+ globals::gui_scale = 0U;
+ globals::gui_screen = GUI_MAIN_MENU;
+
+ sound::init_config();
+
+ if(globals::sound_ctx) {
+ sound::init();
+ }
+
+ client_receive::init();
+
+ experiments::init();
+
+ globals::dispatcher.sink<io::GlfwFramebufferSizeEvent>().connect<&on_glfw_framebuffer_size>();
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+}
+
+void client_game::init_late(void)
+{
+ toggles::init_late();
+
+ if(globals::sound_ctx) {
+ sound::init_late();
+ }
+
+ gui::language::init_late();
+
+ settings::init_late();
+
+ gui::client_chat::init_late();
+
+ gui::status_lines::init_late();
+
+ game_voxels::populate();
+ game_items::populate();
+
+ std::size_t max_texture_count = 0;
+
+ // Figure out the total texture count
+ // NOTE: this is very debug, early and a quite
+ // conservative limit choice; there must be a better
+ // way to make this limit way smaller than it currently is
+ for(const std::shared_ptr<world::VoxelInfo>& info : world::voxel_registry::voxels) {
+ for(const world::VoxelTexture& vtex : info->textures) {
+ max_texture_count += vtex.paths.size();
+ }
+ }
+
+ // UNDONE: asset packs for non-16x16 stuff
+ world::voxel_atlas::create(16, 16, max_texture_count);
+
+ for(std::shared_ptr<world::VoxelInfo>& info : world::voxel_registry::voxels) {
+ for(world::VoxelTexture& vtex : info->textures) {
+ if(auto strip = world::voxel_atlas::find_or_load(vtex.paths)) {
+ vtex.cached_offset = strip->offset;
+ vtex.cached_plane = strip->plane;
+ continue;
+ }
+
+ spdlog::critical("client_gl: {}: failed to load atlas strips", info->name);
+ std::terminate();
+ }
+ }
+
+ world::voxel_atlas::generate_mipmaps();
+
+ for(std::shared_ptr<world::ItemInfo>& info : world::item_registry::items) {
+ info->cached_texture = resource::load<TextureGUI>(info->texture.c_str(), TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+ }
+
+ experiments::init_late();
+
+ gui::client_splash::init_late();
+
+ gui::window_title::update();
+}
+
+void client_game::shutdown(void)
+{
+ world::voxel_sounds::shutdown();
+
+ experiments::shutdown();
+
+ session::shutdown();
+
+ if(globals::sound_ctx) {
+ sound::shutdown();
+ }
+
+ gui::hotbar::shutdown();
+ gui::main_menu::shutdown();
+ gui::play_menu::shutdown();
+
+ gui::bother::shutdown();
+
+ gui::client_chat::shutdown();
+
+ gui::background::shutdown();
+
+ gui::crosshair::shutdown();
+
+ delete globals::dimension;
+ globals::player = entt::null;
+ globals::dimension = nullptr;
+
+ world::item_registry::purge();
+ world::voxel_registry::purge();
+
+ world::voxel_atlas::destroy();
+
+ glDeleteRenderbuffers(1, &globals::world_fbo_depth);
+ glDeleteTextures(1, &globals::world_fbo_color);
+ glDeleteFramebuffers(1, &globals::world_fbo);
+
+ world::outline::shutdown();
+ world::chunk_renderer::shutdown();
+ world::chunk_mesher::shutdown();
+
+ enet_host_destroy(globals::client_host);
+}
+
+void client_game::fixed_update(void)
+{
+ entity::player_move::fixed_update();
+
+ // Only update world simulation gamesystems
+ // if the player can actually observe all the
+ // changes these gamesystems cause visually
+ if(session::is_ingame()) {
+ entity::Collision::fixed_update(globals::dimension);
+ entity::Velocity::fixed_update(globals::dimension);
+ entity::Transform::fixed_update(globals::dimension);
+ entity::Gravity::fixed_update(globals::dimension);
+ entity::Stasis::fixed_update(globals::dimension);
+ }
+}
+
+void client_game::fixed_update_late(void)
+{
+ if(session::is_ingame()) {
+ const auto& head = globals::dimension->entities.get<entity::Head>(globals::player);
+ const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player);
+ const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player);
+
+ protocol::EntityHead head_packet;
+ head_packet.entity = entt::null; // ignored by server
+ head_packet.angles = head.angles;
+
+ protocol::EntityTransform transform_packet;
+ transform_packet.entity = entt::null; // ignored by server
+ transform_packet.angles = transform.angles;
+ transform_packet.chunk = transform.chunk;
+ transform_packet.local = transform.local;
+
+ protocol::EntityVelocity velocity_packet;
+ velocity_packet.entity = entt::null; // ignored by server
+ velocity_packet.value = velocity.value;
+
+ protocol::send(session::peer, protocol::encode(head_packet));
+ protocol::send(session::peer, protocol::encode(transform_packet));
+ protocol::send(session::peer, protocol::encode(velocity_packet));
+ }
+}
+
+void client_game::update(void)
+{
+ if(session::is_ingame()) {
+ if(toggles::get(TOGGLE_PM_FLIGHT)) {
+ globals::dimension->entities.remove<entity::Gravity>(globals::player);
+ }
+ else {
+ globals::dimension->entities.emplace_or_replace<entity::Gravity>(globals::player);
+ }
+ }
+
+ if(globals::sound_ctx) {
+ sound::update();
+
+ entity::listener::update();
+
+ entity::SoundEmitter::update();
+ }
+
+ entity::interpolation::update();
+
+ world::player_target::update();
+
+ entity::camera::update();
+
+ world::voxel_anims::update();
+
+ world::chunk_mesher::update();
+
+ gui::client_chat::update();
+
+ experiments::update();
+
+ constexpr auto half_base_width = 0.5f * static_cast<float>(BASE_WIDTH);
+ constexpr auto half_base_height = 0.5f * static_cast<float>(BASE_HEIGHT);
+
+ auto twice_scale_x = static_cast<float>(globals::width) / half_base_width;
+ auto twice_scale_y = static_cast<float>(globals::height) / half_base_height;
+
+ auto scale_x = math::max(1.0f, 0.5f * glm::floor(twice_scale_x));
+ auto scale_y = math::max(1.0f, 0.5f * glm::floor(twice_scale_y));
+ auto scale_min = math::ceil<unsigned int>(math::min(scale_x, scale_y));
+ auto scale_int = math::max(1U, (scale_min / 2U) * 2U);
+
+ auto& io = ImGui::GetIO();
+ io.FontGlobalScale = scale_int;
+ globals::gui_scale = scale_int;
+}
+
+void client_game::update_late(void)
+{
+ ENetEvent enet_event;
+
+ while(0 < enet_host_service(globals::client_host, &enet_event, 0)) {
+ switch(enet_event.type) {
+ case ENET_EVENT_TYPE_CONNECT:
+ session::send_login_request();
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ session::invalidate();
+ break;
+ case ENET_EVENT_TYPE_RECEIVE:
+ protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer);
+ enet_packet_destroy(enet_event.packet);
+ break;
+ }
+ }
+
+ entity::player_look::update_late();
+ entity::player_move::update_late();
+
+ gui::play_menu::update_late();
+
+ gui::bother::update_late();
+
+ experiments::update_late();
+
+ io::gamepad::update_late();
+
+ world::chunk_visibility::update_late();
+
+ if(client_game::vertical_sync.get_value()) {
+ glfwSwapInterval(1);
+ }
+ else {
+ glfwSwapInterval(0);
+ }
+}
+
+void client_game::render(void)
+{
+ glViewport(0, 0, globals::width, globals::height);
+ glBindFramebuffer(GL_FRAMEBUFFER, globals::world_fbo);
+ glClearColor(world::skybox::fog_color.r, world::skybox::fog_color.g, world::skybox::fog_color.b, 1.000f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ if(globals::dimension) {
+ world::chunk_renderer::render();
+ }
+
+ glEnable(GL_DEPTH_TEST);
+
+ world::player_target::render();
+
+ if(globals::dimension) {
+ auto group = globals::dimension->entities.group(
+ entt::get<entity::Player, entity::Collision, entity::client::HeadIntr, entity::client::TransformIntr>);
+
+ world::outline::prepare();
+
+ for(const auto [entity, collision, head, transform] : group.each()) {
+ if(entity == globals::player) {
+ // Don't render ourselves
+ continue;
+ }
+
+ glm::fvec3 forward;
+ math::vectors(transform.angles + head.angles, forward);
+ forward *= 2.0f;
+
+ glm::fvec3 hull_size = collision.aabb.max - collision.aabb.min;
+ glm::fvec3 hull_fpos = transform.local + collision.aabb.min;
+ glm::fvec3 look = transform.local + head.offset;
+
+ world::outline::cube(transform.chunk, hull_fpos, hull_size, 1.0f, glm::fvec4(1.0f, 0.0f, 0.0f, 1.0f));
+ world::outline::line(transform.chunk, look, forward, 1.0f, glm::fvec4(0.9f, 0.9f, 0.9f, 1.0f));
+ }
+ }
+
+ glEnable(GL_DEPTH_TEST);
+
+ glViewport(0, 0, globals::width, globals::height);
+ glClearColor(0.000f, 0.000f, 0.000f, 1.000f);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, globals::world_fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glBlitFramebuffer(0, 0, globals::width, globals::height, 0, 0, globals::width, globals::height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+}
+
+void client_game::layout(void)
+{
+ if(!session::is_ingame()) {
+ gui::background::layout();
+ }
+
+ if(!globals::gui_screen || (globals::gui_screen == GUI_CHAT)) {
+ if(toggles::get(TOGGLE_METRICS_UI) && !client_game::hide_hud) {
+ // This contains Minecraft-esque debug information
+ // about the hardware, world state and other
+ // things that might be uesful
+ gui::metrics::layout();
+ }
+ }
+
+ if(session::is_ingame()) {
+ gui::client_chat::layout();
+ gui::scoreboard::layout();
+
+ if(!globals::gui_screen && !client_game::hide_hud) {
+ gui::hotbar::layout();
+ gui::status_lines::layout();
+ gui::crosshair::layout();
+ }
+ }
+
+ if(globals::gui_screen) {
+ if(session::is_ingame() && (globals::gui_screen != GUI_CHAT)) {
+ const float width_f = static_cast<float>(globals::width);
+ const float height_f = static_cast<float>(globals::height);
+ const ImU32 darken = ImGui::GetColorU32(ImVec4(0.00f, 0.00f, 0.00f, 0.75f));
+ ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(), ImVec2(width_f, height_f), darken);
+ }
+
+ switch(globals::gui_screen) {
+ case GUI_MAIN_MENU:
+ gui::main_menu::layout();
+ break;
+ case GUI_PLAY_MENU:
+ gui::play_menu::layout();
+ break;
+ case GUI_SETTINGS:
+ settings::layout();
+ break;
+ case GUI_PROGRESS_BAR:
+ gui::progress_bar::layout();
+ break;
+ case GUI_MESSAGE_BOX:
+ gui::message_box::layout();
+ break;
+ case GUI_DIRECT_CONNECTION:
+ gui::direct_connection::layout();
+ break;
+ }
+ }
+}
diff --git a/game/client/game.hh b/game/client/game.hh index 395acc9..649700a 100644 --- a/game/client/game.hh +++ b/game/client/game.hh @@ -1,35 +1,35 @@ -#pragma once - -namespace config -{ -class Boolean; -class String; -class Unsigned; -} // namespace config - -namespace client_game -{ -extern config::Boolean streamer_mode; -extern config::Boolean vertical_sync; -extern config::Boolean world_curvature; -extern config::Unsigned fog_mode; -extern config::String username; -} // namespace client_game - -namespace client_game -{ -extern bool hide_hud; -} // namespace client_game - -namespace client_game -{ -void init(void); -void init_late(void); -void shutdown(void); -void fixed_update(void); -void fixed_update_late(void); -void update(void); -void update_late(void); -void render(void); -void layout(void); -} // namespace client_game +#pragma once
+
+namespace config
+{
+class Boolean;
+class String;
+class Unsigned;
+} // namespace config
+
+namespace client_game
+{
+extern config::Boolean streamer_mode;
+extern config::Boolean vertical_sync;
+extern config::Boolean world_curvature;
+extern config::Unsigned fog_mode;
+extern config::String username;
+} // namespace client_game
+
+namespace client_game
+{
+extern bool hide_hud;
+} // namespace client_game
+
+namespace client_game
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void fixed_update(void);
+void fixed_update_late(void);
+void update(void);
+void update_late(void);
+void render(void);
+void layout(void);
+} // namespace client_game
diff --git a/game/client/globals.cc b/game/client/globals.cc index 1932675..1cf1aeb 100644 --- a/game/client/globals.cc +++ b/game/client/globals.cc @@ -1,47 +1,47 @@ -#include "client/pch.hh" - -#include "client/globals.hh" - -#include "core/io/config_map.hh" - -#include "client/gui/gui_screen.hh" - -io::ConfigMap globals::client_config; - -GLFWwindow* globals::window; - -float globals::window_frametime; -float globals::window_frametime_avg; -std::uint64_t globals::window_frametime_us; -std::uint64_t globals::window_framecount; - -std::uint64_t globals::fixed_accumulator; - -int globals::width; -int globals::height; -float globals::aspect; - -GLuint globals::world_fbo; -GLuint globals::world_fbo_color; -GLuint globals::world_fbo_depth; - -std::size_t globals::num_drawcalls; -std::size_t globals::num_triangles; - -ENetHost* globals::client_host; - -world::Dimension* globals::dimension = nullptr; -entt::entity globals::player; - -ImFont* globals::font_unscii16; -ImFont* globals::font_unscii8; - -config::KeyBind* globals::gui_keybind_ptr = nullptr; -config::GamepadAxis* globals::gui_gamepad_axis_ptr = nullptr; -config::GamepadButton* globals::gui_gamepad_button_ptr = nullptr; - -unsigned int globals::gui_scale = 0U; -unsigned int globals::gui_screen = GUI_SCREEN_NONE; - -ALCdevice* globals::sound_dev; -ALCcontext* globals::sound_ctx; +#include "client/pch.hh"
+
+#include "client/globals.hh"
+
+#include "core/io/config_map.hh"
+
+#include "client/gui/gui_screen.hh"
+
+io::ConfigMap globals::client_config;
+
+GLFWwindow* globals::window;
+
+float globals::window_frametime;
+float globals::window_frametime_avg;
+std::uint64_t globals::window_frametime_us;
+std::uint64_t globals::window_framecount;
+
+std::uint64_t globals::fixed_accumulator;
+
+int globals::width;
+int globals::height;
+float globals::aspect;
+
+GLuint globals::world_fbo;
+GLuint globals::world_fbo_color;
+GLuint globals::world_fbo_depth;
+
+std::size_t globals::num_drawcalls;
+std::size_t globals::num_triangles;
+
+ENetHost* globals::client_host;
+
+world::Dimension* globals::dimension = nullptr;
+entt::entity globals::player;
+
+ImFont* globals::font_unscii16;
+ImFont* globals::font_unscii8;
+
+config::KeyBind* globals::gui_keybind_ptr = nullptr;
+config::GamepadAxis* globals::gui_gamepad_axis_ptr = nullptr;
+config::GamepadButton* globals::gui_gamepad_button_ptr = nullptr;
+
+unsigned int globals::gui_scale = 0U;
+unsigned int globals::gui_screen = GUI_SCREEN_NONE;
+
+ALCdevice* globals::sound_dev;
+ALCcontext* globals::sound_ctx;
diff --git a/game/client/globals.hh b/game/client/globals.hh index ba57a9e..41531a9 100644 --- a/game/client/globals.hh +++ b/game/client/globals.hh @@ -1,71 +1,71 @@ -#pragma once - -#include "shared/globals.hh" - -namespace config -{ -class KeyBind; -class GamepadAxis; -class GamepadButton; -} // namespace config - -namespace io -{ -class ConfigMap; -} // namespace io - -struct GLFWwindow; -struct ImFont; - -namespace world -{ -class Dimension; -} // namespace world - -namespace globals -{ -extern io::ConfigMap client_config; - -extern GLFWwindow* window; - -// Some gamesystems that aren't really -// gameplay-oriented might still use client -// framerate to interpolate discrete things -// so it's still a good idea to keep these available -extern float window_frametime; -extern float window_frametime_avg; -extern std::uint64_t window_frametime_us; -extern std::uint64_t window_framecount; - -// https://gafferongames.com/post/fix_your_timestep/ -extern std::uint64_t fixed_accumulator; - -extern int width; -extern int height; -extern float aspect; - -extern GLuint world_fbo; -extern GLuint world_fbo_color; -extern GLuint world_fbo_depth; - -extern std::size_t num_drawcalls; -extern std::size_t num_triangles; - -extern ENetHost* client_host; - -extern world::Dimension* dimension; -extern entt::entity player; - -extern ImFont* font_unscii16; -extern ImFont* font_unscii8; - -extern config::KeyBind* gui_keybind_ptr; -extern config::GamepadAxis* gui_gamepad_axis_ptr; -extern config::GamepadButton* gui_gamepad_button_ptr; - -extern unsigned int gui_scale; -extern unsigned int gui_screen; - -extern ALCdevice* sound_dev; -extern ALCcontext* sound_ctx; -} // namespace globals +#pragma once
+
+#include "shared/globals.hh"
+
+namespace config
+{
+class KeyBind;
+class GamepadAxis;
+class GamepadButton;
+} // namespace config
+
+namespace io
+{
+class ConfigMap;
+} // namespace io
+
+struct GLFWwindow;
+struct ImFont;
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace globals
+{
+extern io::ConfigMap client_config;
+
+extern GLFWwindow* window;
+
+// Some gamesystems that aren't really
+// gameplay-oriented might still use client
+// framerate to interpolate discrete things
+// so it's still a good idea to keep these available
+extern float window_frametime;
+extern float window_frametime_avg;
+extern std::uint64_t window_frametime_us;
+extern std::uint64_t window_framecount;
+
+// https://gafferongames.com/post/fix_your_timestep/
+extern std::uint64_t fixed_accumulator;
+
+extern int width;
+extern int height;
+extern float aspect;
+
+extern GLuint world_fbo;
+extern GLuint world_fbo_color;
+extern GLuint world_fbo_depth;
+
+extern std::size_t num_drawcalls;
+extern std::size_t num_triangles;
+
+extern ENetHost* client_host;
+
+extern world::Dimension* dimension;
+extern entt::entity player;
+
+extern ImFont* font_unscii16;
+extern ImFont* font_unscii8;
+
+extern config::KeyBind* gui_keybind_ptr;
+extern config::GamepadAxis* gui_gamepad_axis_ptr;
+extern config::GamepadButton* gui_gamepad_button_ptr;
+
+extern unsigned int gui_scale;
+extern unsigned int gui_screen;
+
+extern ALCdevice* sound_dev;
+extern ALCcontext* sound_ctx;
+} // namespace globals
diff --git a/game/client/gui/background.cc b/game/client/gui/background.cc index 50fef01..d1500bb 100644 --- a/game/client/gui/background.cc +++ b/game/client/gui/background.cc @@ -1,39 +1,39 @@ -#include "client/pch.hh" - -#include "client/gui/background.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -static resource_ptr<TextureGUI> texture; - -void gui::background::init(void) -{ - texture = resource::load<TextureGUI>("textures/gui/background.png", TEXTURE_GUI_LOAD_VFLIP); - - if(texture == nullptr) { - spdlog::critical("background: texture load failed"); - std::terminate(); - } -} - -void gui::background::shutdown(void) -{ - texture = nullptr; -} - -void gui::background::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetBackgroundDrawList(); - - auto scaled_width = 0.75f * static_cast<float>(globals::width / globals::gui_scale); - auto scaled_height = 0.75f * static_cast<float>(globals::height / globals::gui_scale); - auto scale_uv = ImVec2(scaled_width / static_cast<float>(texture->size.x), scaled_height / static_cast<float>(texture->size.y)); - draw_list->AddImage(texture->handle, ImVec2(0.0f, 0.0f), viewport->Size, ImVec2(0.0f, 0.0f), scale_uv); -} +#include "client/pch.hh"
+
+#include "client/gui/background.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+static resource_ptr<TextureGUI> texture;
+
+void gui::background::init(void)
+{
+ texture = resource::load<TextureGUI>("textures/gui/background.png", TEXTURE_GUI_LOAD_VFLIP);
+
+ if(texture == nullptr) {
+ spdlog::critical("background: texture load failed");
+ std::terminate();
+ }
+}
+
+void gui::background::shutdown(void)
+{
+ texture = nullptr;
+}
+
+void gui::background::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetBackgroundDrawList();
+
+ auto scaled_width = 0.75f * static_cast<float>(globals::width / globals::gui_scale);
+ auto scaled_height = 0.75f * static_cast<float>(globals::height / globals::gui_scale);
+ auto scale_uv = ImVec2(scaled_width / static_cast<float>(texture->size.x), scaled_height / static_cast<float>(texture->size.y));
+ draw_list->AddImage(texture->handle, ImVec2(0.0f, 0.0f), viewport->Size, ImVec2(0.0f, 0.0f), scale_uv);
+}
diff --git a/game/client/gui/background.hh b/game/client/gui/background.hh index 5c72a3f..6145484 100644 --- a/game/client/gui/background.hh +++ b/game/client/gui/background.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::background -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::background +#pragma once
+
+namespace gui::background
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::background
diff --git a/game/client/gui/bother.cc b/game/client/gui/bother.cc index d87f220..1bb7097 100644 --- a/game/client/gui/bother.cc +++ b/game/client/gui/bother.cc @@ -1,163 +1,163 @@ -#include "client/pch.hh" - -#include "client/gui/bother.hh" - -#include "shared/protocol.hh" - -#include "client/globals.hh" - -// Maximum amount of peers used for bothering -constexpr static std::size_t BOTHER_PEERS = 4; - -struct BotherQueueItem final { - unsigned int identity; - std::string hostname; - std::uint16_t port; -}; - -static ENetHost* bother_host; -static entt::dispatcher bother_dispatcher; -static std::unordered_set<unsigned int> bother_set; -static std::deque<BotherQueueItem> bother_queue; - -static void on_status_response_packet(const protocol::StatusResponse& packet) -{ - auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(packet.peer->data)); - - bother_set.erase(identity); - - gui::BotherResponseEvent event; - event.identity = identity; - event.is_server_unreachable = false; - event.protocol_version = packet.version; - event.num_players = packet.num_players; - event.max_players = packet.max_players; - event.motd = packet.motd; - globals::dispatcher.trigger(event); - - enet_peer_disconnect(packet.peer, protocol::CHANNEL); -} - -void gui::bother::init(void) -{ - bother_host = enet_host_create(nullptr, BOTHER_PEERS, 1, 0, 0); - bother_dispatcher.clear(); - bother_set.clear(); - - bother_dispatcher.sink<protocol::StatusResponse>().connect<&on_status_response_packet>(); -} - -void gui::bother::shutdown(void) -{ - enet_host_destroy(bother_host); - bother_dispatcher.clear(); - bother_set.clear(); -} - -void gui::bother::update_late(void) -{ - unsigned int free_peers = 0U; - - // Figure out how much times we can call - // enet_host_connect and reallistically succeed - for(unsigned int i = 0U; i < bother_host->peerCount; ++i) { - if(bother_host->peers[i].state != ENET_PEER_STATE_DISCONNECTED) { - continue; - } - - free_peers += 1U; - } - - for(unsigned int i = 0U; (i < free_peers) && bother_queue.size(); ++i) { - const auto& item = bother_queue.front(); - - ENetAddress address; - enet_address_set_host(&address, item.hostname.c_str()); - address.port = enet_uint16(item.port); - - if(auto peer = enet_host_connect(bother_host, &address, 1, 0)) { - peer->data = reinterpret_cast<void*>(static_cast<std::uintptr_t>(item.identity)); - bother_set.insert(item.identity); - enet_host_flush(bother_host); - } - - bother_queue.pop_front(); - } - - ENetEvent enet_event; - - if(0 < enet_host_service(bother_host, &enet_event, 0)) { - if(enet_event.type == ENET_EVENT_TYPE_CONNECT) { - protocol::StatusRequest packet; - packet.version = protocol::VERSION; - protocol::send(enet_event.peer, protocol::encode(packet)); - return; - } - - if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) { - protocol::decode(bother_dispatcher, enet_event.packet, enet_event.peer); - enet_packet_destroy(enet_event.packet); - return; - } - - if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) { - auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(enet_event.peer->data)); - - if(bother_set.count(identity)) { - BotherResponseEvent event; - event.identity = identity; - event.is_server_unreachable = true; - globals::dispatcher.trigger(event); - } - - bother_set.erase(identity); - - return; - } - } -} - -void gui::bother::ping(unsigned int identity, std::string_view host, std::uint16_t port) -{ - if(bother_set.count(identity)) { - // Already in the process - return; - } - - for(const auto& item : bother_queue) { - if(item.identity == identity) { - // Already in the queue - return; - } - } - - BotherQueueItem item; - item.identity = identity; - item.hostname = host; - item.port = port; - - bother_queue.push_back(item); -} - -void gui::bother::cancel(unsigned int identity) -{ - bother_set.erase(identity); - - auto item = bother_queue.cbegin(); - - while(item != bother_queue.cend()) { - if(item->identity == identity) { - item = bother_queue.erase(item); - continue; - } - - item = std::next(item); - } - - for(unsigned int i = 0U; i < bother_host->peerCount; ++i) { - if(bother_host->peers[i].data == reinterpret_cast<void*>(static_cast<std::uintptr_t>(identity))) { - enet_peer_reset(&bother_host->peers[i]); - break; - } - } -} +#include "client/pch.hh"
+
+#include "client/gui/bother.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/globals.hh"
+
+// Maximum amount of peers used for bothering
+constexpr static std::size_t BOTHER_PEERS = 4;
+
+struct BotherQueueItem final {
+ unsigned int identity;
+ std::string hostname;
+ std::uint16_t port;
+};
+
+static ENetHost* bother_host;
+static entt::dispatcher bother_dispatcher;
+static std::unordered_set<unsigned int> bother_set;
+static std::deque<BotherQueueItem> bother_queue;
+
+static void on_status_response_packet(const protocol::StatusResponse& packet)
+{
+ auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(packet.peer->data));
+
+ bother_set.erase(identity);
+
+ gui::BotherResponseEvent event;
+ event.identity = identity;
+ event.is_server_unreachable = false;
+ event.protocol_version = packet.version;
+ event.num_players = packet.num_players;
+ event.max_players = packet.max_players;
+ event.motd = packet.motd;
+ globals::dispatcher.trigger(event);
+
+ enet_peer_disconnect(packet.peer, protocol::CHANNEL);
+}
+
+void gui::bother::init(void)
+{
+ bother_host = enet_host_create(nullptr, BOTHER_PEERS, 1, 0, 0);
+ bother_dispatcher.clear();
+ bother_set.clear();
+
+ bother_dispatcher.sink<protocol::StatusResponse>().connect<&on_status_response_packet>();
+}
+
+void gui::bother::shutdown(void)
+{
+ enet_host_destroy(bother_host);
+ bother_dispatcher.clear();
+ bother_set.clear();
+}
+
+void gui::bother::update_late(void)
+{
+ unsigned int free_peers = 0U;
+
+ // Figure out how much times we can call
+ // enet_host_connect and reallistically succeed
+ for(unsigned int i = 0U; i < bother_host->peerCount; ++i) {
+ if(bother_host->peers[i].state != ENET_PEER_STATE_DISCONNECTED) {
+ continue;
+ }
+
+ free_peers += 1U;
+ }
+
+ for(unsigned int i = 0U; (i < free_peers) && bother_queue.size(); ++i) {
+ const auto& item = bother_queue.front();
+
+ ENetAddress address;
+ enet_address_set_host(&address, item.hostname.c_str());
+ address.port = enet_uint16(item.port);
+
+ if(auto peer = enet_host_connect(bother_host, &address, 1, 0)) {
+ peer->data = reinterpret_cast<void*>(static_cast<std::uintptr_t>(item.identity));
+ bother_set.insert(item.identity);
+ enet_host_flush(bother_host);
+ }
+
+ bother_queue.pop_front();
+ }
+
+ ENetEvent enet_event;
+
+ if(0 < enet_host_service(bother_host, &enet_event, 0)) {
+ if(enet_event.type == ENET_EVENT_TYPE_CONNECT) {
+ protocol::StatusRequest packet;
+ packet.version = protocol::VERSION;
+ protocol::send(enet_event.peer, protocol::encode(packet));
+ return;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) {
+ protocol::decode(bother_dispatcher, enet_event.packet, enet_event.peer);
+ enet_packet_destroy(enet_event.packet);
+ return;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) {
+ auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(enet_event.peer->data));
+
+ if(bother_set.count(identity)) {
+ BotherResponseEvent event;
+ event.identity = identity;
+ event.is_server_unreachable = true;
+ globals::dispatcher.trigger(event);
+ }
+
+ bother_set.erase(identity);
+
+ return;
+ }
+ }
+}
+
+void gui::bother::ping(unsigned int identity, std::string_view host, std::uint16_t port)
+{
+ if(bother_set.count(identity)) {
+ // Already in the process
+ return;
+ }
+
+ for(const auto& item : bother_queue) {
+ if(item.identity == identity) {
+ // Already in the queue
+ return;
+ }
+ }
+
+ BotherQueueItem item;
+ item.identity = identity;
+ item.hostname = host;
+ item.port = port;
+
+ bother_queue.push_back(item);
+}
+
+void gui::bother::cancel(unsigned int identity)
+{
+ bother_set.erase(identity);
+
+ auto item = bother_queue.cbegin();
+
+ while(item != bother_queue.cend()) {
+ if(item->identity == identity) {
+ item = bother_queue.erase(item);
+ continue;
+ }
+
+ item = std::next(item);
+ }
+
+ for(unsigned int i = 0U; i < bother_host->peerCount; ++i) {
+ if(bother_host->peers[i].data == reinterpret_cast<void*>(static_cast<std::uintptr_t>(identity))) {
+ enet_peer_reset(&bother_host->peers[i]);
+ break;
+ }
+ }
+}
diff --git a/game/client/gui/bother.hh b/game/client/gui/bother.hh index c83294c..fc5bab4 100644 --- a/game/client/gui/bother.hh +++ b/game/client/gui/bother.hh @@ -1,22 +1,22 @@ -#pragma once - -namespace gui -{ -struct BotherResponseEvent final { - unsigned int identity; - bool is_server_unreachable; - std::uint32_t protocol_version; - std::uint16_t num_players; - std::uint16_t max_players; - std::string motd; -}; -} // namespace gui - -namespace gui::bother -{ -void init(void); -void shutdown(void); -void update_late(void); -void ping(unsigned int identity, std::string_view host, std::uint16_t port); -void cancel(unsigned int identity); -} // namespace gui::bother +#pragma once
+
+namespace gui
+{
+struct BotherResponseEvent final {
+ unsigned int identity;
+ bool is_server_unreachable;
+ std::uint32_t protocol_version;
+ std::uint16_t num_players;
+ std::uint16_t max_players;
+ std::string motd;
+};
+} // namespace gui
+
+namespace gui::bother
+{
+void init(void);
+void shutdown(void);
+void update_late(void);
+void ping(unsigned int identity, std::string_view host, std::uint16_t port);
+void cancel(unsigned int identity);
+} // namespace gui::bother
diff --git a/game/client/gui/chat.cc b/game/client/gui/chat.cc index 6e4498b..aaee8c6 100644 --- a/game/client/gui/chat.cc +++ b/game/client/gui/chat.cc @@ -1,273 +1,273 @@ -#include "client/pch.hh" - -#include "client/gui/chat.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/imdraw_ext.hh" -#include "client/gui/language.hh" -#include "client/gui/settings.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/sound_effect.hh" - -#include "client/sound/sound.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static unsigned int MAX_HISTORY_SIZE = 128U; - -struct GuiChatMessage final { - std::uint64_t spawn; - std::string text; - ImVec4 color; -}; - -static config::KeyBind key_chat(GLFW_KEY_ENTER); -static config::Unsigned history_size(32U, 0U, MAX_HISTORY_SIZE); - -static std::deque<GuiChatMessage> history; -static std::string chat_input; -static bool needs_focus; - -static resource_ptr<SoundEffect> sfx_chat_message; - -static void append_text_message(const std::string& sender, const std::string& text) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("<{}> {}", sender, text); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void append_player_join(const std::string& sender) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("{} {}", sender, gui::language::resolve("chat.client_join")); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void append_player_leave(const std::string& sender, const std::string& reason) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("{} {} ({})", sender, gui::language::resolve("chat.client_left"), gui::language::resolve(reason.c_str())); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void on_chat_message_packet(const protocol::ChatMessage& packet) -{ - if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) { - append_text_message(packet.sender, packet.message); - return; - } - - if(packet.type == protocol::ChatMessage::PLAYER_JOIN) { - append_player_join(packet.sender); - return; - } - - if(packet.type == protocol::ChatMessage::PLAYER_LEAVE) { - append_player_leave(packet.sender, packet.message); - return; - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(event.action == GLFW_PRESS) { - if((event.key == GLFW_KEY_ENTER) && (globals::gui_screen == GUI_CHAT)) { - if(!utils::is_whitespace(chat_input)) { - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.sender = client_game::username.get(); - packet.message = chat_input; - - protocol::send(session::peer, protocol::encode(packet)); - } - - globals::gui_screen = GUI_SCREEN_NONE; - - chat_input.clear(); - - return; - } - - if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_CHAT)) { - globals::gui_screen = GUI_SCREEN_NONE; - return; - } - - if(key_chat.equals(event.key) && !globals::gui_screen) { - globals::gui_screen = GUI_CHAT; - needs_focus = true; - return; - } - } -} - -void gui::client_chat::init(void) -{ - globals::client_config.add_value("chat.key", key_chat); - globals::client_config.add_value("chat.history_size", history_size); - - settings::add_keybind(2, key_chat, settings_location::KEYBOARD_MISC, "key.chat"); - settings::add_slider(1, history_size, settings_location::VIDEO_GUI, "chat.history_size", false); - - globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>(); - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - - sfx_chat_message = resource::load<SoundEffect>("sounds/ui/chat_message.wav"); -} - -void gui::client_chat::init_late(void) -{ -} - -void gui::client_chat::shutdown(void) -{ - sfx_chat_message = nullptr; -} - -void gui::client_chat::update(void) -{ - while(history.size() > history_size.get_value()) { - history.pop_front(); - } -} - -void gui::client_chat::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.0f, 0.0f); - auto window_size = ImVec2(0.75f * viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - ImGui::PushFont(globals::font_unscii16, 8.0f); - - if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) { - ImGui::End(); - return; - } - - auto& padding = ImGui::GetStyle().FramePadding; - auto& spacing = ImGui::GetStyle().ItemSpacing; - auto font = ImGui::GetFont(); - - auto draw_list = ImGui::GetWindowDrawList(); - - // The text input widget occupies the bottom part - // of the chat window, we need to reserve some space for it - auto ypos = window_size.y - 2.5f * ImGui::GetFontSize() - 2.0f * padding.y - 2.0f * spacing.y; - - if(globals::gui_screen == GUI_CHAT) { - if(needs_focus) { - ImGui::SetKeyboardFocusHere(); - needs_focus = false; - } - - ImGui::SetNextItemWidth(window_size.x + 32.0f * padding.x); - ImGui::SetCursorScreenPos(ImVec2(padding.x, ypos)); - ImGui::InputText("###chat.input", &chat_input); - } - - if(!client_game::hide_hud && ((globals::gui_screen == GUI_SCREEN_NONE) || (globals::gui_screen == GUI_CHAT))) { - for(auto it = history.crbegin(); it < history.crend(); ++it) { - auto text_size = ImGui::CalcTextSize(it->text.c_str(), it->text.c_str() + it->text.size(), false, window_size.x); - auto rect_size = ImVec2(window_size.x, text_size.y + 2.0f * padding.y); - - auto rect_pos = ImVec2(padding.x, ypos - text_size.y - 2.0f * padding.y); - auto rect_end = ImVec2(rect_pos.x + rect_size.x, rect_pos.y + rect_size.y); - auto text_pos = ImVec2(rect_pos.x + padding.x, rect_pos.y + padding.y); - - auto fadeout_seconds = 10.0f; - auto fadeout = std::exp(-1.0f * std::pow(1.0e-6 * static_cast<float>(globals::curtime - it->spawn) / fadeout_seconds, 10.0f)); - - float rect_alpha; - float text_alpha; - - if(globals::gui_screen == GUI_CHAT) { - rect_alpha = 0.75f; - text_alpha = 1.00f; - } - else { - rect_alpha = 0.50f * fadeout; - text_alpha = 1.00f * fadeout; - } - - auto rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, rect_alpha); - auto text_col = ImGui::GetColorU32(ImVec4(it->color.x, it->color.y, it->color.z, it->color.w * text_alpha)); - auto shadow_col = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, text_alpha)); - - draw_list->AddRectFilled(rect_pos, rect_end, rect_col); - - imdraw_ext::text_shadow_w(it->text, text_pos, text_col, shadow_col, font, draw_list, 8.0f, window_size.x); - - ypos -= rect_size.y; - } - } - - ImGui::End(); - ImGui::PopFont(); -} - -void gui::client_chat::clear(void) -{ - history.clear(); -} - -void gui::client_chat::refresh_timings(void) -{ - for(auto it = history.begin(); it < history.end(); ++it) { - // Reset the spawn time so the fadeout timer - // is reset; SpawnPlayer handler might call this - it->spawn = globals::curtime; - } -} - -void gui::client_chat::print(const std::string& text) -{ - GuiChatMessage message = {}; - message.spawn = globals::curtime; - message.text = text; - message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} +#include "client/pch.hh"
+
+#include "client/gui/chat.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/imdraw_ext.hh"
+#include "client/gui/language.hh"
+#include "client/gui/settings.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/sound_effect.hh"
+
+#include "client/sound/sound.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static unsigned int MAX_HISTORY_SIZE = 128U;
+
+struct GuiChatMessage final {
+ std::uint64_t spawn;
+ std::string text;
+ ImVec4 color;
+};
+
+static config::KeyBind key_chat(GLFW_KEY_ENTER);
+static config::Unsigned history_size(32U, 0U, MAX_HISTORY_SIZE);
+
+static std::deque<GuiChatMessage> history;
+static std::string chat_input;
+static bool needs_focus;
+
+static resource_ptr<SoundEffect> sfx_chat_message;
+
+static void append_text_message(const std::string& sender, const std::string& text)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("<{}> {}", sender, text);
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void append_player_join(const std::string& sender)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("{} {}", sender, gui::language::resolve("chat.client_join"));
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void append_player_leave(const std::string& sender, const std::string& reason)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("{} {} ({})", sender, gui::language::resolve("chat.client_left"), gui::language::resolve(reason.c_str()));
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void on_chat_message_packet(const protocol::ChatMessage& packet)
+{
+ if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) {
+ append_text_message(packet.sender, packet.message);
+ return;
+ }
+
+ if(packet.type == protocol::ChatMessage::PLAYER_JOIN) {
+ append_player_join(packet.sender);
+ return;
+ }
+
+ if(packet.type == protocol::ChatMessage::PLAYER_LEAVE) {
+ append_player_leave(packet.sender, packet.message);
+ return;
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(event.action == GLFW_PRESS) {
+ if((event.key == GLFW_KEY_ENTER) && (globals::gui_screen == GUI_CHAT)) {
+ if(!utils::is_whitespace(chat_input)) {
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.sender = client_game::username.get();
+ packet.message = chat_input;
+
+ protocol::send(session::peer, protocol::encode(packet));
+ }
+
+ globals::gui_screen = GUI_SCREEN_NONE;
+
+ chat_input.clear();
+
+ return;
+ }
+
+ if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_CHAT)) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ return;
+ }
+
+ if(key_chat.equals(event.key) && !globals::gui_screen) {
+ globals::gui_screen = GUI_CHAT;
+ needs_focus = true;
+ return;
+ }
+ }
+}
+
+void gui::client_chat::init(void)
+{
+ globals::client_config.add_value("chat.key", key_chat);
+ globals::client_config.add_value("chat.history_size", history_size);
+
+ settings::add_keybind(2, key_chat, settings_location::KEYBOARD_MISC, "key.chat");
+ settings::add_slider(1, history_size, settings_location::VIDEO_GUI, "chat.history_size", false);
+
+ globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>();
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+
+ sfx_chat_message = resource::load<SoundEffect>("sounds/ui/chat_message.wav");
+}
+
+void gui::client_chat::init_late(void)
+{
+}
+
+void gui::client_chat::shutdown(void)
+{
+ sfx_chat_message = nullptr;
+}
+
+void gui::client_chat::update(void)
+{
+ while(history.size() > history_size.get_value()) {
+ history.pop_front();
+ }
+}
+
+void gui::client_chat::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.0f, 0.0f);
+ auto window_size = ImVec2(0.75f * viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ ImGui::PushFont(globals::font_unscii16, 8.0f);
+
+ if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) {
+ ImGui::End();
+ return;
+ }
+
+ auto& padding = ImGui::GetStyle().FramePadding;
+ auto& spacing = ImGui::GetStyle().ItemSpacing;
+ auto font = ImGui::GetFont();
+
+ auto draw_list = ImGui::GetWindowDrawList();
+
+ // The text input widget occupies the bottom part
+ // of the chat window, we need to reserve some space for it
+ auto ypos = window_size.y - 2.5f * ImGui::GetFontSize() - 2.0f * padding.y - 2.0f * spacing.y;
+
+ if(globals::gui_screen == GUI_CHAT) {
+ if(needs_focus) {
+ ImGui::SetKeyboardFocusHere();
+ needs_focus = false;
+ }
+
+ ImGui::SetNextItemWidth(window_size.x + 32.0f * padding.x);
+ ImGui::SetCursorScreenPos(ImVec2(padding.x, ypos));
+ ImGui::InputText("###chat.input", &chat_input);
+ }
+
+ if(!client_game::hide_hud && ((globals::gui_screen == GUI_SCREEN_NONE) || (globals::gui_screen == GUI_CHAT))) {
+ for(auto it = history.crbegin(); it < history.crend(); ++it) {
+ auto text_size = ImGui::CalcTextSize(it->text.c_str(), it->text.c_str() + it->text.size(), false, window_size.x);
+ auto rect_size = ImVec2(window_size.x, text_size.y + 2.0f * padding.y);
+
+ auto rect_pos = ImVec2(padding.x, ypos - text_size.y - 2.0f * padding.y);
+ auto rect_end = ImVec2(rect_pos.x + rect_size.x, rect_pos.y + rect_size.y);
+ auto text_pos = ImVec2(rect_pos.x + padding.x, rect_pos.y + padding.y);
+
+ auto fadeout_seconds = 10.0f;
+ auto fadeout = std::exp(-1.0f * std::pow(1.0e-6 * static_cast<float>(globals::curtime - it->spawn) / fadeout_seconds, 10.0f));
+
+ float rect_alpha;
+ float text_alpha;
+
+ if(globals::gui_screen == GUI_CHAT) {
+ rect_alpha = 0.75f;
+ text_alpha = 1.00f;
+ }
+ else {
+ rect_alpha = 0.50f * fadeout;
+ text_alpha = 1.00f * fadeout;
+ }
+
+ auto rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, rect_alpha);
+ auto text_col = ImGui::GetColorU32(ImVec4(it->color.x, it->color.y, it->color.z, it->color.w * text_alpha));
+ auto shadow_col = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, text_alpha));
+
+ draw_list->AddRectFilled(rect_pos, rect_end, rect_col);
+
+ imdraw_ext::text_shadow_w(it->text, text_pos, text_col, shadow_col, font, draw_list, 8.0f, window_size.x);
+
+ ypos -= rect_size.y;
+ }
+ }
+
+ ImGui::End();
+ ImGui::PopFont();
+}
+
+void gui::client_chat::clear(void)
+{
+ history.clear();
+}
+
+void gui::client_chat::refresh_timings(void)
+{
+ for(auto it = history.begin(); it < history.end(); ++it) {
+ // Reset the spawn time so the fadeout timer
+ // is reset; SpawnPlayer handler might call this
+ it->spawn = globals::curtime;
+ }
+}
+
+void gui::client_chat::print(const std::string& text)
+{
+ GuiChatMessage message = {};
+ message.spawn = globals::curtime;
+ message.text = text;
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
diff --git a/game/client/gui/chat.hh b/game/client/gui/chat.hh index 6a3ea33..2fc6fb3 100644 --- a/game/client/gui/chat.hh +++ b/game/client/gui/chat.hh @@ -1,17 +1,17 @@ -#pragma once - -namespace gui::client_chat -{ -void init(void); -void init_late(void); -void shutdown(void); -void update(void); -void layout(void); -} // namespace gui::client_chat - -namespace gui::client_chat -{ -void clear(void); -void refresh_timings(void); -void print(const std::string& string); -} // namespace gui::client_chat +#pragma once
+
+namespace gui::client_chat
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void update(void);
+void layout(void);
+} // namespace gui::client_chat
+
+namespace gui::client_chat
+{
+void clear(void);
+void refresh_timings(void);
+void print(const std::string& string);
+} // namespace gui::client_chat
diff --git a/game/client/gui/crosshair.cc b/game/client/gui/crosshair.cc index 29b5fe5..b5650a9 100644 --- a/game/client/gui/crosshair.cc +++ b/game/client/gui/crosshair.cc @@ -1,43 +1,43 @@ -#include "client/pch.hh" - -#include "client/gui/crosshair.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -static resource_ptr<TextureGUI> texture; - -void gui::crosshair::init(void) -{ - texture = resource::load<TextureGUI>( - "textures/gui/hud_crosshair.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T | TEXTURE_GUI_LOAD_VFLIP); - - if(texture == nullptr) { - spdlog::critical("crosshair: texture load failed"); - std::terminate(); - } -} - -void gui::crosshair::shutdown(void) -{ - texture = nullptr; -} - -void gui::crosshair::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - auto scaled_width = math::max<int>(texture->size.x, globals::gui_scale * texture->size.x / 2); - auto scaled_height = math::max<int>(texture->size.y, globals::gui_scale * texture->size.y / 2); - auto start = ImVec2( - static_cast<int>(0.5f * viewport->Size.x) - (scaled_width / 2), static_cast<int>(0.5f * viewport->Size.y) - (scaled_height / 2)); - auto end = ImVec2(start.x + scaled_width, start.y + scaled_height); - draw_list->AddImage(texture->handle, start, end); -} +#include "client/pch.hh"
+
+#include "client/gui/crosshair.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+static resource_ptr<TextureGUI> texture;
+
+void gui::crosshair::init(void)
+{
+ texture = resource::load<TextureGUI>("textures/gui/hud_crosshair.png",
+ TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T | TEXTURE_GUI_LOAD_VFLIP);
+
+ if(texture == nullptr) {
+ spdlog::critical("crosshair: texture load failed");
+ std::terminate();
+ }
+}
+
+void gui::crosshair::shutdown(void)
+{
+ texture = nullptr;
+}
+
+void gui::crosshair::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ auto scaled_width = math::max<int>(texture->size.x, globals::gui_scale * texture->size.x / 2);
+ auto scaled_height = math::max<int>(texture->size.y, globals::gui_scale * texture->size.y / 2);
+ auto start = ImVec2(static_cast<int>(0.5f * viewport->Size.x) - (scaled_width / 2),
+ static_cast<int>(0.5f * viewport->Size.y) - (scaled_height / 2));
+ auto end = ImVec2(start.x + scaled_width, start.y + scaled_height);
+ draw_list->AddImage(texture->handle, start, end);
+}
diff --git a/game/client/gui/crosshair.hh b/game/client/gui/crosshair.hh index 589727e..dca5725 100644 --- a/game/client/gui/crosshair.hh +++ b/game/client/gui/crosshair.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::crosshair -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::crosshair +#pragma once
+
+namespace gui::crosshair
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::crosshair
diff --git a/game/client/gui/direct_connection.cc b/game/client/gui/direct_connection.cc index 37372e2..dfbd05b 100644 --- a/game/client/gui/direct_connection.cc +++ b/game/client/gui/direct_connection.cc @@ -1,145 +1,145 @@ -#include "client/pch.hh" - -#include "client/gui/direct_connection.hh" - -#include "core/config/boolean.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_title; -static std::string str_connect; -static std::string str_cancel; - -static std::string str_hostname; -static std::string str_password; - -static std::string direct_hostname; -static std::string direct_password; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_DIRECT_CONNECTION) { - globals::gui_screen = GUI_PLAY_MENU; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_title = gui::language::resolve("direct_connection.title"); - str_connect = gui::language::resolve_gui("direct_connection.connect"); - str_cancel = gui::language::resolve_gui("direct_connection.cancel"); - - str_hostname = gui::language::resolve("direct_connection.hostname"); - str_password = gui::language::resolve("direct_connection.password"); -} - -static void connect_to_server(void) -{ - auto parts = utils::split(direct_hostname, ":"); - std::string parsed_hostname; - std::uint16_t parsed_port; - - if(!parts[0].empty()) { - parsed_hostname = parts[0]; - } - else { - parsed_hostname = std::string("localhost"); - } - - if(parts.size() >= 2) { - parsed_port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX); - } - else { - parsed_port = protocol::PORT; - } - - session::connect(parsed_hostname.c_str(), parsed_port, direct_password.c_str()); -} - -void gui::direct_connection::init(void) -{ - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); -} - -void gui::direct_connection::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.25f * viewport->Size.x, 0.20f * viewport->Size.y); - auto window_size = ImVec2(0.50f * viewport->Size.x, 0.80f * viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIDirectConnect", nullptr, WINDOW_FLAGS)) { - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 16.0f * globals::gui_scale)); - - ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank; - - if(client_game::streamer_mode.get_value()) { - // Hide server hostname to avoid things like - // followers flooding the server that is streamed online - hostname_flags |= ImGuiInputTextFlags_Password; - } - - auto avail_width = ImGui::GetContentRegionAvail().x; - - ImGui::PushItemWidth(avail_width); - - ImGui::InputText("###UIDirectConnect_hostname", &direct_hostname, hostname_flags); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_hostname.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::InputText("###UIDirectConnect_password", &direct_password, ImGuiInputTextFlags_Password); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_password.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::PopItemWidth(); - - ImGui::Dummy(ImVec2(0.0f, 4.0f * globals::gui_scale)); - - ImGui::BeginDisabled(utils::is_whitespace(direct_hostname)); - - if(ImGui::Button(str_connect.c_str(), ImVec2(avail_width, 0.0f))) { - connect_to_server(); - } - - ImGui::EndDisabled(); - - if(ImGui::Button(str_cancel.c_str(), ImVec2(avail_width, 0.0f))) { - globals::gui_screen = GUI_PLAY_MENU; - } - } - - ImGui::End(); -} +#include "client/pch.hh"
+
+#include "client/gui/direct_connection.hh"
+
+#include "core/config/boolean.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_title;
+static std::string str_connect;
+static std::string str_cancel;
+
+static std::string str_hostname;
+static std::string str_password;
+
+static std::string direct_hostname;
+static std::string direct_password;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_DIRECT_CONNECTION) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_title = gui::language::resolve("direct_connection.title");
+ str_connect = gui::language::resolve_gui("direct_connection.connect");
+ str_cancel = gui::language::resolve_gui("direct_connection.cancel");
+
+ str_hostname = gui::language::resolve("direct_connection.hostname");
+ str_password = gui::language::resolve("direct_connection.password");
+}
+
+static void connect_to_server(void)
+{
+ auto parts = utils::split(direct_hostname, ":");
+ std::string parsed_hostname;
+ std::uint16_t parsed_port;
+
+ if(!parts[0].empty()) {
+ parsed_hostname = parts[0];
+ }
+ else {
+ parsed_hostname = std::string("localhost");
+ }
+
+ if(parts.size() >= 2) {
+ parsed_port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX);
+ }
+ else {
+ parsed_port = protocol::PORT;
+ }
+
+ session::connect(parsed_hostname.c_str(), parsed_port, direct_password.c_str());
+}
+
+void gui::direct_connection::init(void)
+{
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void gui::direct_connection::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.25f * viewport->Size.x, 0.20f * viewport->Size.y);
+ auto window_size = ImVec2(0.50f * viewport->Size.x, 0.80f * viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIDirectConnect", nullptr, WINDOW_FLAGS)) {
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 16.0f * globals::gui_scale));
+
+ ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank;
+
+ if(client_game::streamer_mode.get_value()) {
+ // Hide server hostname to avoid things like
+ // followers flooding the server that is streamed online
+ hostname_flags |= ImGuiInputTextFlags_Password;
+ }
+
+ auto avail_width = ImGui::GetContentRegionAvail().x;
+
+ ImGui::PushItemWidth(avail_width);
+
+ ImGui::InputText("###UIDirectConnect_hostname", &direct_hostname, hostname_flags);
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_hostname.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::InputText("###UIDirectConnect_password", &direct_password, ImGuiInputTextFlags_Password);
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_password.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::PopItemWidth();
+
+ ImGui::Dummy(ImVec2(0.0f, 4.0f * globals::gui_scale));
+
+ ImGui::BeginDisabled(utils::is_whitespace(direct_hostname));
+
+ if(ImGui::Button(str_connect.c_str(), ImVec2(avail_width, 0.0f))) {
+ connect_to_server();
+ }
+
+ ImGui::EndDisabled();
+
+ if(ImGui::Button(str_cancel.c_str(), ImVec2(avail_width, 0.0f))) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ }
+ }
+
+ ImGui::End();
+}
diff --git a/game/client/gui/direct_connection.hh b/game/client/gui/direct_connection.hh index aa02d7c..c63156b 100644 --- a/game/client/gui/direct_connection.hh +++ b/game/client/gui/direct_connection.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::direct_connection -{ -void init(void); -void layout(void); -} // namespace gui::direct_connection +#pragma once
+
+namespace gui::direct_connection
+{
+void init(void);
+void layout(void);
+} // namespace gui::direct_connection
diff --git a/game/client/gui/gui_screen.hh b/game/client/gui/gui_screen.hh index 2eae310..c29f701 100644 --- a/game/client/gui/gui_screen.hh +++ b/game/client/gui/gui_screen.hh @@ -1,10 +1,10 @@ -#pragma once - -constexpr static unsigned int GUI_SCREEN_NONE = 0x0000U; -constexpr static unsigned int GUI_MAIN_MENU = 0x0001U; -constexpr static unsigned int GUI_PLAY_MENU = 0x0002U; -constexpr static unsigned int GUI_SETTINGS = 0x0003U; -constexpr static unsigned int GUI_PROGRESS_BAR = 0x0004U; -constexpr static unsigned int GUI_MESSAGE_BOX = 0x0005U; -constexpr static unsigned int GUI_CHAT = 0x0006U; -constexpr static unsigned int GUI_DIRECT_CONNECTION = 0x0007U; +#pragma once
+
+constexpr static unsigned int GUI_SCREEN_NONE = 0x0000U;
+constexpr static unsigned int GUI_MAIN_MENU = 0x0001U;
+constexpr static unsigned int GUI_PLAY_MENU = 0x0002U;
+constexpr static unsigned int GUI_SETTINGS = 0x0003U;
+constexpr static unsigned int GUI_PROGRESS_BAR = 0x0004U;
+constexpr static unsigned int GUI_MESSAGE_BOX = 0x0005U;
+constexpr static unsigned int GUI_CHAT = 0x0006U;
+constexpr static unsigned int GUI_DIRECT_CONNECTION = 0x0007U;
diff --git a/game/client/gui/hotbar.cc b/game/client/gui/hotbar.cc index ca058a3..806d82b 100644 --- a/game/client/gui/hotbar.cc +++ b/game/client/gui/hotbar.cc @@ -1,183 +1,183 @@ -#include "client/pch.hh" - -#include "client/gui/hotbar.hh" - -#include "core/io/config_map.hh" - -#include "core/resource/resource.hh" - -#include "shared/world/item_registry.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/settings.hh" -#include "client/gui/status_lines.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -constexpr static float ITEM_SIZE = 20.0f; -constexpr static float ITEM_PADDING = 2.0f; -constexpr static float SELECTOR_PADDING = 1.0f; -constexpr static float HOTBAR_PADDING = 2.0f; - -unsigned int gui::hotbar::active_slot = 0U; -item_id gui::hotbar::slots[HOTBAR_SIZE]; - -static config::KeyBind hotbar_keys[HOTBAR_SIZE]; - -static resource_ptr<TextureGUI> hotbar_background; -static resource_ptr<TextureGUI> hotbar_selector; - -static ImU32 get_color_alpha(ImGuiCol style_color, float alpha) -{ - const auto& color = ImGui::GetStyleColorVec4(style_color); - return ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, alpha)); -} - -static void update_hotbar_item(void) -{ - if(gui::hotbar::slots[gui::hotbar::active_slot] == NULL_ITEM_ID) { - gui::status_lines::unset(gui::STATUS_HOTBAR); - return; - } - - if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - gui::status_lines::set(gui::STATUS_HOTBAR, info->name, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f); - return; - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.action == GLFW_PRESS) && !globals::gui_screen) { - for(unsigned int i = 0U; i < HOTBAR_SIZE; ++i) { - if(hotbar_keys[i].equals(event.key)) { - gui::hotbar::active_slot = i; - update_hotbar_item(); - break; - } - } - } -} - -static void on_glfw_scroll(const io::GlfwScrollEvent& event) -{ - if(!globals::gui_screen) { - if(event.dy < 0.0) { - gui::hotbar::next_slot(); - return; - } - - if(event.dy > 0.0) { - gui::hotbar::prev_slot(); - return; - } - } -} - -void gui::hotbar::init(void) -{ - hotbar_keys[0].set_key(GLFW_KEY_1); - hotbar_keys[1].set_key(GLFW_KEY_2); - hotbar_keys[2].set_key(GLFW_KEY_3); - hotbar_keys[3].set_key(GLFW_KEY_4); - hotbar_keys[4].set_key(GLFW_KEY_5); - hotbar_keys[5].set_key(GLFW_KEY_6); - hotbar_keys[6].set_key(GLFW_KEY_7); - hotbar_keys[7].set_key(GLFW_KEY_8); - hotbar_keys[8].set_key(GLFW_KEY_9); - - globals::client_config.add_value("hotbar.key.0", hotbar_keys[0]); - globals::client_config.add_value("hotbar.key.1", hotbar_keys[1]); - globals::client_config.add_value("hotbar.key.3", hotbar_keys[2]); - globals::client_config.add_value("hotbar.key.4", hotbar_keys[3]); - globals::client_config.add_value("hotbar.key.5", hotbar_keys[4]); - globals::client_config.add_value("hotbar.key.6", hotbar_keys[5]); - globals::client_config.add_value("hotbar.key.7", hotbar_keys[6]); - globals::client_config.add_value("hotbar.key.8", hotbar_keys[7]); - globals::client_config.add_value("hotbar.key.9", hotbar_keys[8]); - - settings::add_keybind(10, hotbar_keys[0], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.0"); - settings::add_keybind(11, hotbar_keys[1], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.1"); - settings::add_keybind(12, hotbar_keys[2], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.2"); - settings::add_keybind(13, hotbar_keys[3], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.3"); - settings::add_keybind(14, hotbar_keys[4], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.4"); - settings::add_keybind(15, hotbar_keys[5], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.5"); - settings::add_keybind(16, hotbar_keys[6], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.6"); - settings::add_keybind(17, hotbar_keys[7], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.7"); - settings::add_keybind(18, hotbar_keys[8], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.8"); - - hotbar_background = resource::load<TextureGUI>("textures/gui/hud_hotbar.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - hotbar_selector = resource::load<TextureGUI>("textures/gui/hud_selector.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>(); -} - -void gui::hotbar::shutdown(void) -{ - hotbar_background = nullptr; - hotbar_selector = nullptr; -} - -void gui::hotbar::layout(void) -{ - auto& style = ImGui::GetStyle(); - - auto item_size = ITEM_SIZE * globals::gui_scale; - auto hotbar_width = HOTBAR_SIZE * item_size; - auto hotbar_padding = HOTBAR_PADDING * globals::gui_scale; - - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - // Draw the hotbar background image - auto background_start = ImVec2(0.5f * viewport->Size.x - 0.5f * hotbar_width, viewport->Size.y - item_size - hotbar_padding); - auto background_end = ImVec2(background_start.x + hotbar_width, background_start.y + item_size); - draw_list->AddImage(hotbar_background->handle, background_start, background_end); - - // Draw the hotbar selector image - auto selector_padding_a = SELECTOR_PADDING * globals::gui_scale; - auto selector_padding_b = SELECTOR_PADDING * globals::gui_scale * 2.0f; - auto selector_start = ImVec2( - background_start.x + gui::hotbar::active_slot * item_size - selector_padding_a, background_start.y - selector_padding_a); - auto selector_end = ImVec2(selector_start.x + item_size + selector_padding_b, selector_start.y + item_size + selector_padding_b); - draw_list->AddImage(hotbar_selector->handle, selector_start, selector_end); - - // Figure out item texture padding values - auto item_padding_a = ITEM_PADDING * globals::gui_scale; - auto item_padding_b = ITEM_PADDING * globals::gui_scale * 2.0f; - - // Draw individual item textures in the hotbar - for(std::size_t i = 0; i < HOTBAR_SIZE; ++i) { - const auto info = world::item_registry::find(gui::hotbar::slots[i]); - - if((info == nullptr) || (info->cached_texture == nullptr)) { - // There's either no item in the slot - // or the item doesn't have a texture - continue; - } - - const auto item_start = ImVec2(background_start.x + i * item_size + item_padding_a, background_start.y + item_padding_a); - const auto item_end = ImVec2(item_start.x + item_size - item_padding_b, item_start.y + item_size - item_padding_b); - draw_list->AddImage(info->cached_texture->handle, item_start, item_end); - } -} - -void gui::hotbar::next_slot(void) -{ - gui::hotbar::active_slot += 1U; - gui::hotbar::active_slot %= HOTBAR_SIZE; - update_hotbar_item(); -} - -void gui::hotbar::prev_slot(void) -{ - gui::hotbar::active_slot += HOTBAR_SIZE - 1U; - gui::hotbar::active_slot %= HOTBAR_SIZE; - update_hotbar_item(); -} +#include "client/pch.hh"
+
+#include "client/gui/hotbar.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/resource/resource.hh"
+
+#include "shared/world/item_registry.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/settings.hh"
+#include "client/gui/status_lines.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+constexpr static float ITEM_SIZE = 20.0f;
+constexpr static float ITEM_PADDING = 2.0f;
+constexpr static float SELECTOR_PADDING = 1.0f;
+constexpr static float HOTBAR_PADDING = 2.0f;
+
+unsigned int gui::hotbar::active_slot = 0U;
+item_id gui::hotbar::slots[HOTBAR_SIZE];
+
+static config::KeyBind hotbar_keys[HOTBAR_SIZE];
+
+static resource_ptr<TextureGUI> hotbar_background;
+static resource_ptr<TextureGUI> hotbar_selector;
+
+static ImU32 get_color_alpha(ImGuiCol style_color, float alpha)
+{
+ const auto& color = ImGui::GetStyleColorVec4(style_color);
+ return ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, alpha));
+}
+
+static void update_hotbar_item(void)
+{
+ if(gui::hotbar::slots[gui::hotbar::active_slot] == NULL_ITEM_ID) {
+ gui::status_lines::unset(gui::STATUS_HOTBAR);
+ return;
+ }
+
+ if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) {
+ gui::status_lines::set(gui::STATUS_HOTBAR, info->name, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f);
+ return;
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.action == GLFW_PRESS) && !globals::gui_screen) {
+ for(unsigned int i = 0U; i < HOTBAR_SIZE; ++i) {
+ if(hotbar_keys[i].equals(event.key)) {
+ gui::hotbar::active_slot = i;
+ update_hotbar_item();
+ break;
+ }
+ }
+ }
+}
+
+static void on_glfw_scroll(const io::GlfwScrollEvent& event)
+{
+ if(!globals::gui_screen) {
+ if(event.dy < 0.0) {
+ gui::hotbar::next_slot();
+ return;
+ }
+
+ if(event.dy > 0.0) {
+ gui::hotbar::prev_slot();
+ return;
+ }
+ }
+}
+
+void gui::hotbar::init(void)
+{
+ hotbar_keys[0].set_key(GLFW_KEY_1);
+ hotbar_keys[1].set_key(GLFW_KEY_2);
+ hotbar_keys[2].set_key(GLFW_KEY_3);
+ hotbar_keys[3].set_key(GLFW_KEY_4);
+ hotbar_keys[4].set_key(GLFW_KEY_5);
+ hotbar_keys[5].set_key(GLFW_KEY_6);
+ hotbar_keys[6].set_key(GLFW_KEY_7);
+ hotbar_keys[7].set_key(GLFW_KEY_8);
+ hotbar_keys[8].set_key(GLFW_KEY_9);
+
+ globals::client_config.add_value("hotbar.key.0", hotbar_keys[0]);
+ globals::client_config.add_value("hotbar.key.1", hotbar_keys[1]);
+ globals::client_config.add_value("hotbar.key.3", hotbar_keys[2]);
+ globals::client_config.add_value("hotbar.key.4", hotbar_keys[3]);
+ globals::client_config.add_value("hotbar.key.5", hotbar_keys[4]);
+ globals::client_config.add_value("hotbar.key.6", hotbar_keys[5]);
+ globals::client_config.add_value("hotbar.key.7", hotbar_keys[6]);
+ globals::client_config.add_value("hotbar.key.8", hotbar_keys[7]);
+ globals::client_config.add_value("hotbar.key.9", hotbar_keys[8]);
+
+ settings::add_keybind(10, hotbar_keys[0], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.0");
+ settings::add_keybind(11, hotbar_keys[1], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.1");
+ settings::add_keybind(12, hotbar_keys[2], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.2");
+ settings::add_keybind(13, hotbar_keys[3], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.3");
+ settings::add_keybind(14, hotbar_keys[4], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.4");
+ settings::add_keybind(15, hotbar_keys[5], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.5");
+ settings::add_keybind(16, hotbar_keys[6], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.6");
+ settings::add_keybind(17, hotbar_keys[7], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.7");
+ settings::add_keybind(18, hotbar_keys[8], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.8");
+
+ hotbar_background = resource::load<TextureGUI>("textures/gui/hud_hotbar.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+ hotbar_selector = resource::load<TextureGUI>("textures/gui/hud_selector.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>();
+}
+
+void gui::hotbar::shutdown(void)
+{
+ hotbar_background = nullptr;
+ hotbar_selector = nullptr;
+}
+
+void gui::hotbar::layout(void)
+{
+ auto& style = ImGui::GetStyle();
+
+ auto item_size = ITEM_SIZE * globals::gui_scale;
+ auto hotbar_width = HOTBAR_SIZE * item_size;
+ auto hotbar_padding = HOTBAR_PADDING * globals::gui_scale;
+
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ // Draw the hotbar background image
+ auto background_start = ImVec2(0.5f * viewport->Size.x - 0.5f * hotbar_width, viewport->Size.y - item_size - hotbar_padding);
+ auto background_end = ImVec2(background_start.x + hotbar_width, background_start.y + item_size);
+ draw_list->AddImage(hotbar_background->handle, background_start, background_end);
+
+ // Draw the hotbar selector image
+ auto selector_padding_a = SELECTOR_PADDING * globals::gui_scale;
+ auto selector_padding_b = SELECTOR_PADDING * globals::gui_scale * 2.0f;
+ auto selector_start = ImVec2(background_start.x + gui::hotbar::active_slot * item_size - selector_padding_a,
+ background_start.y - selector_padding_a);
+ auto selector_end = ImVec2(selector_start.x + item_size + selector_padding_b, selector_start.y + item_size + selector_padding_b);
+ draw_list->AddImage(hotbar_selector->handle, selector_start, selector_end);
+
+ // Figure out item texture padding values
+ auto item_padding_a = ITEM_PADDING * globals::gui_scale;
+ auto item_padding_b = ITEM_PADDING * globals::gui_scale * 2.0f;
+
+ // Draw individual item textures in the hotbar
+ for(std::size_t i = 0; i < HOTBAR_SIZE; ++i) {
+ const auto info = world::item_registry::find(gui::hotbar::slots[i]);
+
+ if((info == nullptr) || (info->cached_texture == nullptr)) {
+ // There's either no item in the slot
+ // or the item doesn't have a texture
+ continue;
+ }
+
+ const auto item_start = ImVec2(background_start.x + i * item_size + item_padding_a, background_start.y + item_padding_a);
+ const auto item_end = ImVec2(item_start.x + item_size - item_padding_b, item_start.y + item_size - item_padding_b);
+ draw_list->AddImage(info->cached_texture->handle, item_start, item_end);
+ }
+}
+
+void gui::hotbar::next_slot(void)
+{
+ gui::hotbar::active_slot += 1U;
+ gui::hotbar::active_slot %= HOTBAR_SIZE;
+ update_hotbar_item();
+}
+
+void gui::hotbar::prev_slot(void)
+{
+ gui::hotbar::active_slot += HOTBAR_SIZE - 1U;
+ gui::hotbar::active_slot %= HOTBAR_SIZE;
+ update_hotbar_item();
+}
diff --git a/game/client/gui/hotbar.hh b/game/client/gui/hotbar.hh index 85d75aa..88ce791 100644 --- a/game/client/gui/hotbar.hh +++ b/game/client/gui/hotbar.hh @@ -1,27 +1,27 @@ -#pragma once - -#include "shared/types.hh" - -// TODO: design an inventory system and an item -// registry and integrate the hotbar into that system - -constexpr static unsigned int HOTBAR_SIZE = 9U; - -namespace gui::hotbar -{ -extern unsigned int active_slot; -extern item_id slots[HOTBAR_SIZE]; -} // namespace gui::hotbar - -namespace gui::hotbar -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::hotbar - -namespace gui::hotbar -{ -void next_slot(void); -void prev_slot(void); -} // namespace gui::hotbar +#pragma once
+
+#include "shared/types.hh"
+
+// TODO: design an inventory system and an item
+// registry and integrate the hotbar into that system
+
+constexpr static unsigned int HOTBAR_SIZE = 9U;
+
+namespace gui::hotbar
+{
+extern unsigned int active_slot;
+extern item_id slots[HOTBAR_SIZE];
+} // namespace gui::hotbar
+
+namespace gui::hotbar
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::hotbar
+
+namespace gui::hotbar
+{
+void next_slot(void);
+void prev_slot(void);
+} // namespace gui::hotbar
diff --git a/game/client/gui/imdraw_ext.cc b/game/client/gui/imdraw_ext.cc index 832f284..c3d40c9 100644 --- a/game/client/gui/imdraw_ext.cc +++ b/game/client/gui/imdraw_ext.cc @@ -1,34 +1,34 @@ -#include "client/pch.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/globals.hh" - -void gui::imdraw_ext::text_shadow( - const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list) -{ - imdraw_ext::text_shadow(text, position, text_color, shadow_color, font, draw_list, font->LegacySize); -} - -void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size) -{ - const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale); - draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size()); - draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size()); -} - -void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float wrap_width) -{ - imdraw_ext::text_shadow_w(text, position, text_color, shadow_color, font, draw_list, font->LegacySize, wrap_width); -} - -void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size, float wrap_width) -{ - const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale); - draw_list->AddText( - font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size(), wrap_width); - draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size(), wrap_width); -} +#include "client/pch.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/globals.hh"
+
+void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list)
+{
+ imdraw_ext::text_shadow(text, position, text_color, shadow_color, font, draw_list, font->LegacySize);
+}
+
+void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size)
+{
+ const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale);
+ draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size());
+ draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size());
+}
+
+void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float wrap_width)
+{
+ imdraw_ext::text_shadow_w(text, position, text_color, shadow_color, font, draw_list, font->LegacySize, wrap_width);
+}
+
+void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size, float wrap_width)
+{
+ const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale);
+ draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size(),
+ wrap_width);
+ draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size(), wrap_width);
+}
diff --git a/game/client/gui/imdraw_ext.hh b/game/client/gui/imdraw_ext.hh index a7e1503..e1475b2 100644 --- a/game/client/gui/imdraw_ext.hh +++ b/game/client/gui/imdraw_ext.hh @@ -1,17 +1,17 @@ -#pragma once - -namespace gui::imdraw_ext -{ -void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list); -void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list, - float font_size); -} // namespace gui::imdraw_ext - -namespace gui::imdraw_ext -{ -void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float wrap_width); -void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size, float wrap_width); -} // namespace gui::imdraw_ext +#pragma once
+
+namespace gui::imdraw_ext
+{
+void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list);
+void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list,
+ float font_size);
+} // namespace gui::imdraw_ext
+
+namespace gui::imdraw_ext
+{
+void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float wrap_width);
+void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size, float wrap_width);
+} // namespace gui::imdraw_ext
diff --git a/game/client/gui/language.cc b/game/client/gui/language.cc index f63dd99..57a43fe 100644 --- a/game/client/gui/language.cc +++ b/game/client/gui/language.cc @@ -1,201 +1,201 @@ -#include "client/pch.hh" - -#include "client/gui/language.hh" - -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "client/gui/settings.hh" - -#include "client/globals.hh" - -constexpr static std::string_view DEFAULT_LANGUAGE = "en_US"; - -// Available languages are kept in a special manifest file which -// is essentially a key-value map of semi-IETF-compliant language tags -// and the language's endonym; after reading the manifest, the translation -// system knows what language it can load and will act accordingly -constexpr static std::string_view MANIFEST_PATH = "lang/manifest.json"; - -static gui::LanguageManifest manifest; -static gui::LanguageIterator current_language; -static std::unordered_map<std::string, std::string> language_map; -static std::unordered_map<std::string, gui::LanguageIterator> ietf_map; -static config::String config_language(DEFAULT_LANGUAGE); - -static void send_language_event(gui::LanguageIterator new_language) -{ - gui::LanguageSetEvent event; - event.new_language = new_language; - globals::dispatcher.trigger(event); -} - -void gui::language::init(void) -{ - globals::client_config.add_value("language", config_language); - - settings::add_language_select(0, settings_location::GENERAL, "language"); - - auto file = PHYSFS_openRead(std::string(MANIFEST_PATH).c_str()); - - if(file == nullptr) { - spdlog::critical("language: {}: {}", MANIFEST_PATH, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto jsonv = json_parse_string(source.c_str()); - const auto json = json_value_get_object(jsonv); - const auto count = json_object_get_count(json); - - if((jsonv == nullptr) || (json == nullptr) || (count == 0)) { - spdlog::critical("language: {}: parse error", MANIFEST_PATH); - json_value_free(jsonv); - std::terminate(); - } - - for(std::size_t i = 0; i < count; ++i) { - const auto ietf = json_object_get_name(json, i); - const auto value = json_object_get_value_at(json, i); - const auto endonym = json_value_get_string(value); - - if(ietf && endonym) { - LanguageInfo info; - info.ietf = std::string(ietf); - info.endonym = std::string(endonym); - info.display = std::format("{} ({})", endonym, ietf); - manifest.push_back(info); - } - } - - for(auto it = manifest.cbegin(); it != manifest.cend(); ++it) { - ietf_map.emplace(it->ietf, it); - } - - json_value_free(jsonv); - - // This is temporary! This value will - // be overriden in init_late after the - // config system updates config_language - current_language = manifest.cend(); -} - -void gui::language::init_late(void) -{ - auto user_language = ietf_map.find(config_language.get_value()); - - if(user_language != ietf_map.cend()) { - gui::language::set(user_language->second); - return; - } - - auto fallback = ietf_map.find(std::string(DEFAULT_LANGUAGE)); - - if(fallback != ietf_map.cend()) { - gui::language::set(fallback->second); - return; - } - - spdlog::critical("language: we're doomed!"); - spdlog::critical("language: {} doesn't exist!", DEFAULT_LANGUAGE); - std::terminate(); -} - -void gui::language::set(LanguageIterator new_language) -{ - if(new_language != manifest.cend()) { - auto path = std::format("lang/lang.{}.json", new_language->ietf); - - auto file = PHYSFS_openRead(path.c_str()); - - if(file == nullptr) { - spdlog::warn("language: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - send_language_event(new_language); - return; - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto jsonv = json_parse_string(source.c_str()); - const auto json = json_value_get_object(jsonv); - const auto count = json_object_get_count(json); - - if((jsonv == nullptr) || (json == nullptr) || (count == 0)) { - spdlog::warn("language: {}: parse error", path); - send_language_event(new_language); - json_value_free(jsonv); - return; - } - - language_map.clear(); - - for(size_t i = 0; i < count; ++i) { - const auto key = json_object_get_name(json, i); - const auto value = json_object_get_value_at(json, i); - const auto value_str = json_value_get_string(value); - - if(key && value_str) { - language_map.emplace(key, value_str); - continue; - } - } - - json_value_free(jsonv); - - current_language = new_language; - config_language.set(new_language->ietf.c_str()); - } - - send_language_event(new_language); -} - -gui::LanguageIterator gui::language::get_current(void) -{ - return current_language; -} - -gui::LanguageIterator gui::language::find(std::string_view ietf) -{ - const auto it = ietf_map.find(std::string(ietf)); - if(it != ietf_map.cend()) { - return it->second; - } - else { - return manifest.cend(); - } -} - -gui::LanguageIterator gui::language::cbegin(void) -{ - return manifest.cbegin(); -} - -gui::LanguageIterator gui::language::cend(void) -{ - return manifest.cend(); -} - -std::string_view gui::language::resolve(std::string_view key) -{ - const auto it = language_map.find(std::string(key)); - - if(it != language_map.cend()) { - return it->second; - } - - return key; -} - -std::string gui::language::resolve_gui(std::string_view key) -{ - // We need window tags to retain their hierarchy when a language - // dynamically changes; ImGui allows to provide hidden unique identifiers - // to GUI primitives that have their name change dynamically, so we're using this - return std::format("{}###{}", gui::language::resolve(key), key); -} +#include "client/pch.hh"
+
+#include "client/gui/language.hh"
+
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "client/gui/settings.hh"
+
+#include "client/globals.hh"
+
+constexpr static std::string_view DEFAULT_LANGUAGE = "en_US";
+
+// Available languages are kept in a special manifest file which
+// is essentially a key-value map of semi-IETF-compliant language tags
+// and the language's endonym; after reading the manifest, the translation
+// system knows what language it can load and will act accordingly
+constexpr static std::string_view MANIFEST_PATH = "lang/manifest.json";
+
+static gui::LanguageManifest manifest;
+static gui::LanguageIterator current_language;
+static std::unordered_map<std::string, std::string> language_map;
+static std::unordered_map<std::string, gui::LanguageIterator> ietf_map;
+static config::String config_language(DEFAULT_LANGUAGE);
+
+static void send_language_event(gui::LanguageIterator new_language)
+{
+ gui::LanguageSetEvent event;
+ event.new_language = new_language;
+ globals::dispatcher.trigger(event);
+}
+
+void gui::language::init(void)
+{
+ globals::client_config.add_value("language", config_language);
+
+ settings::add_language_select(0, settings_location::GENERAL, "language");
+
+ auto file = PHYSFS_openRead(std::string(MANIFEST_PATH).c_str());
+
+ if(file == nullptr) {
+ spdlog::critical("language: {}: {}", MANIFEST_PATH, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto jsonv = json_parse_string(source.c_str());
+ const auto json = json_value_get_object(jsonv);
+ const auto count = json_object_get_count(json);
+
+ if((jsonv == nullptr) || (json == nullptr) || (count == 0)) {
+ spdlog::critical("language: {}: parse error", MANIFEST_PATH);
+ json_value_free(jsonv);
+ std::terminate();
+ }
+
+ for(std::size_t i = 0; i < count; ++i) {
+ const auto ietf = json_object_get_name(json, i);
+ const auto value = json_object_get_value_at(json, i);
+ const auto endonym = json_value_get_string(value);
+
+ if(ietf && endonym) {
+ LanguageInfo info;
+ info.ietf = std::string(ietf);
+ info.endonym = std::string(endonym);
+ info.display = std::format("{} ({})", endonym, ietf);
+ manifest.push_back(info);
+ }
+ }
+
+ for(auto it = manifest.cbegin(); it != manifest.cend(); ++it) {
+ ietf_map.emplace(it->ietf, it);
+ }
+
+ json_value_free(jsonv);
+
+ // This is temporary! This value will
+ // be overriden in init_late after the
+ // config system updates config_language
+ current_language = manifest.cend();
+}
+
+void gui::language::init_late(void)
+{
+ auto user_language = ietf_map.find(config_language.get_value());
+
+ if(user_language != ietf_map.cend()) {
+ gui::language::set(user_language->second);
+ return;
+ }
+
+ auto fallback = ietf_map.find(std::string(DEFAULT_LANGUAGE));
+
+ if(fallback != ietf_map.cend()) {
+ gui::language::set(fallback->second);
+ return;
+ }
+
+ spdlog::critical("language: we're doomed!");
+ spdlog::critical("language: {} doesn't exist!", DEFAULT_LANGUAGE);
+ std::terminate();
+}
+
+void gui::language::set(LanguageIterator new_language)
+{
+ if(new_language != manifest.cend()) {
+ auto path = std::format("lang/lang.{}.json", new_language->ietf);
+
+ auto file = PHYSFS_openRead(path.c_str());
+
+ if(file == nullptr) {
+ spdlog::warn("language: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ send_language_event(new_language);
+ return;
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto jsonv = json_parse_string(source.c_str());
+ const auto json = json_value_get_object(jsonv);
+ const auto count = json_object_get_count(json);
+
+ if((jsonv == nullptr) || (json == nullptr) || (count == 0)) {
+ spdlog::warn("language: {}: parse error", path);
+ send_language_event(new_language);
+ json_value_free(jsonv);
+ return;
+ }
+
+ language_map.clear();
+
+ for(size_t i = 0; i < count; ++i) {
+ const auto key = json_object_get_name(json, i);
+ const auto value = json_object_get_value_at(json, i);
+ const auto value_str = json_value_get_string(value);
+
+ if(key && value_str) {
+ language_map.emplace(key, value_str);
+ continue;
+ }
+ }
+
+ json_value_free(jsonv);
+
+ current_language = new_language;
+ config_language.set(new_language->ietf.c_str());
+ }
+
+ send_language_event(new_language);
+}
+
+gui::LanguageIterator gui::language::get_current(void)
+{
+ return current_language;
+}
+
+gui::LanguageIterator gui::language::find(std::string_view ietf)
+{
+ const auto it = ietf_map.find(std::string(ietf));
+ if(it != ietf_map.cend()) {
+ return it->second;
+ }
+ else {
+ return manifest.cend();
+ }
+}
+
+gui::LanguageIterator gui::language::cbegin(void)
+{
+ return manifest.cbegin();
+}
+
+gui::LanguageIterator gui::language::cend(void)
+{
+ return manifest.cend();
+}
+
+std::string_view gui::language::resolve(std::string_view key)
+{
+ const auto it = language_map.find(std::string(key));
+
+ if(it != language_map.cend()) {
+ return it->second;
+ }
+
+ return key;
+}
+
+std::string gui::language::resolve_gui(std::string_view key)
+{
+ // We need window tags to retain their hierarchy when a language
+ // dynamically changes; ImGui allows to provide hidden unique identifiers
+ // to GUI primitives that have their name change dynamically, so we're using this
+ return std::format("{}###{}", gui::language::resolve(key), key);
+}
diff --git a/game/client/gui/language.hh b/game/client/gui/language.hh index 90132d7..0628941 100644 --- a/game/client/gui/language.hh +++ b/game/client/gui/language.hh @@ -1,42 +1,42 @@ -#pragma once - -namespace gui -{ -struct LanguageInfo final { - std::string endonym; // Language's self-name - std::string display; // Display for the settings GUI - std::string ietf; // Semi-compliant language abbreviation -}; - -using LanguageManifest = std::vector<LanguageInfo>; -using LanguageIterator = LanguageManifest::const_iterator; - -struct LanguageSetEvent final { - LanguageIterator new_language; -}; -} // namespace gui - -namespace gui::language -{ -void init(void); -void init_late(void); -} // namespace gui::language - -namespace gui::language -{ -void set(LanguageIterator new_language); -} // namespace gui::language - -namespace gui::language -{ -LanguageIterator get_current(void); -LanguageIterator find(std::string_view ietf); -LanguageIterator cbegin(void); -LanguageIterator cend(void); -} // namespace gui::language - -namespace gui::language -{ -std::string_view resolve(std::string_view key); -std::string resolve_gui(std::string_view key); -} // namespace gui::language +#pragma once
+
+namespace gui
+{
+struct LanguageInfo final {
+ std::string endonym; // Language's self-name
+ std::string display; // Display for the settings GUI
+ std::string ietf; // Semi-compliant language abbreviation
+};
+
+using LanguageManifest = std::vector<LanguageInfo>;
+using LanguageIterator = LanguageManifest::const_iterator;
+
+struct LanguageSetEvent final {
+ LanguageIterator new_language;
+};
+} // namespace gui
+
+namespace gui::language
+{
+void init(void);
+void init_late(void);
+} // namespace gui::language
+
+namespace gui::language
+{
+void set(LanguageIterator new_language);
+} // namespace gui::language
+
+namespace gui::language
+{
+LanguageIterator get_current(void);
+LanguageIterator find(std::string_view ietf);
+LanguageIterator cbegin(void);
+LanguageIterator cend(void);
+} // namespace gui::language
+
+namespace gui::language
+{
+std::string_view resolve(std::string_view key);
+std::string resolve_gui(std::string_view key);
+} // namespace gui::language
diff --git a/game/client/gui/main_menu.cc b/game/client/gui/main_menu.cc index 3c68612..aa506d3 100644 --- a/game/client/gui/main_menu.cc +++ b/game/client/gui/main_menu.cc @@ -1,172 +1,172 @@ -#include "client/pch.hh" - -#include "client/gui/main_menu.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "core/version.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" -#include "client/gui/window_title.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_play; -static std::string str_resume; -static std::string str_settings; -static std::string str_leave; -static std::string str_quit; - -static resource_ptr<TextureGUI> title; -static float title_aspect; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(session::is_ingame() && (event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_SCREEN_NONE) { - globals::gui_screen = GUI_MAIN_MENU; - return; - } - - if(globals::gui_screen == GUI_MAIN_MENU) { - globals::gui_screen = GUI_SCREEN_NONE; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_play = gui::language::resolve_gui("main_menu.play"); - str_resume = gui::language::resolve_gui("main_menu.resume"); - str_settings = gui::language::resolve("main_menu.settings"); - str_leave = gui::language::resolve("main_menu.leave"); - str_quit = gui::language::resolve("main_menu.quit"); -} - -void gui::main_menu::init(void) -{ - title = resource::load<TextureGUI>("textures/gui/menu_title.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - - if(title == nullptr) { - spdlog::critical("main_menu: texture load failed"); - std::terminate(); - } - - if(title->size.x > title->size.y) { - title_aspect = static_cast<float>(title->size.x) / static_cast<float>(title->size.y); - } - else { - title_aspect = static_cast<float>(title->size.y) / static_cast<float>(title->size.x); - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); -} - -void gui::main_menu::shutdown(void) -{ - title = nullptr; -} - -void gui::main_menu::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.15f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###main_menu", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 2.0f * globals::gui_scale)); - - if(session::is_ingame()) { - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - } - else { - auto reference_height = 0.225f * window_size.y; - auto image_width = math::min(window_size.x, reference_height * title_aspect); - auto image_height = image_width / title_aspect; - ImGui::SetCursorPosX(0.5f * (window_size.x - image_width)); - ImGui::Image(title->handle, ImVec2(image_width, image_height)); - } - - ImGui::Dummy(ImVec2(0.0f, 24.0f * globals::gui_scale)); - - const float button_width = 240.0f * globals::gui_scale; - const float button_xpos = 0.5f * (window_size.x - button_width); - - if(session::is_ingame()) { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_resume.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_SCREEN_NONE; - } - - ImGui::Spacing(); - } - else { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_play.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_PLAY_MENU; - } - - ImGui::Spacing(); - } - - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_settings.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_SETTINGS; - } - - ImGui::Spacing(); - - if(session::is_ingame()) { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_leave.c_str(), ImVec2(button_width, 0.0f))) { - session::disconnect("protocol.client_disconnect"); - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - } - - ImGui::Spacing(); - } - else { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_quit.c_str(), ImVec2(button_width, 0.0f))) { - glfwSetWindowShouldClose(globals::window, true); - } - - ImGui::Spacing(); - } - - if(!session::is_ingame()) { - const auto& padding = ImGui::GetStyle().FramePadding; - const auto& spacing = ImGui::GetStyle().ItemSpacing; - - ImGui::PushFont(globals::font_unscii8, 4.0f); - ImGui::SetCursorScreenPos(ImVec2(padding.x + spacing.x, window_size.y - ImGui::GetFontSize() - padding.y - spacing.y)); - ImGui::Text("Voxelius %*s", version::semver.size(), version::semver.data()); // string_view is not always null-terminated - ImGui::PopFont(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} +#include "client/pch.hh"
+
+#include "client/gui/main_menu.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/version.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+#include "client/gui/window_title.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_play;
+static std::string str_resume;
+static std::string str_settings;
+static std::string str_leave;
+static std::string str_quit;
+
+static resource_ptr<TextureGUI> title;
+static float title_aspect;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(session::is_ingame() && (event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_SCREEN_NONE) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ return;
+ }
+
+ if(globals::gui_screen == GUI_MAIN_MENU) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_play = gui::language::resolve_gui("main_menu.play");
+ str_resume = gui::language::resolve_gui("main_menu.resume");
+ str_settings = gui::language::resolve("main_menu.settings");
+ str_leave = gui::language::resolve("main_menu.leave");
+ str_quit = gui::language::resolve("main_menu.quit");
+}
+
+void gui::main_menu::init(void)
+{
+ title = resource::load<TextureGUI>("textures/gui/menu_title.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+
+ if(title == nullptr) {
+ spdlog::critical("main_menu: texture load failed");
+ std::terminate();
+ }
+
+ if(title->size.x > title->size.y) {
+ title_aspect = static_cast<float>(title->size.x) / static_cast<float>(title->size.y);
+ }
+ else {
+ title_aspect = static_cast<float>(title->size.y) / static_cast<float>(title->size.x);
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void gui::main_menu::shutdown(void)
+{
+ title = nullptr;
+}
+
+void gui::main_menu::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.15f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###main_menu", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 2.0f * globals::gui_scale));
+
+ if(session::is_ingame()) {
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+ }
+ else {
+ auto reference_height = 0.225f * window_size.y;
+ auto image_width = math::min(window_size.x, reference_height * title_aspect);
+ auto image_height = image_width / title_aspect;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - image_width));
+ ImGui::Image(title->handle, ImVec2(image_width, image_height));
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, 24.0f * globals::gui_scale));
+
+ const float button_width = 240.0f * globals::gui_scale;
+ const float button_xpos = 0.5f * (window_size.x - button_width);
+
+ if(session::is_ingame()) {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_resume.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ }
+
+ ImGui::Spacing();
+ }
+ else {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_play.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ }
+
+ ImGui::Spacing();
+ }
+
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_settings.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_SETTINGS;
+ }
+
+ ImGui::Spacing();
+
+ if(session::is_ingame()) {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_leave.c_str(), ImVec2(button_width, 0.0f))) {
+ session::disconnect("protocol.client_disconnect");
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ }
+
+ ImGui::Spacing();
+ }
+ else {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_quit.c_str(), ImVec2(button_width, 0.0f))) {
+ glfwSetWindowShouldClose(globals::window, true);
+ }
+
+ ImGui::Spacing();
+ }
+
+ if(!session::is_ingame()) {
+ const auto& padding = ImGui::GetStyle().FramePadding;
+ const auto& spacing = ImGui::GetStyle().ItemSpacing;
+
+ ImGui::PushFont(globals::font_unscii8, 4.0f);
+ ImGui::SetCursorScreenPos(ImVec2(padding.x + spacing.x, window_size.y - ImGui::GetFontSize() - padding.y - spacing.y));
+ ImGui::Text("Voxelius %*s", version::semver.size(), version::semver.data()); // string_view is not always null-terminated
+ ImGui::PopFont();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
diff --git a/game/client/gui/main_menu.hh b/game/client/gui/main_menu.hh index 205f078..8e30e38 100644 --- a/game/client/gui/main_menu.hh +++ b/game/client/gui/main_menu.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::main_menu -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::main_menu +#pragma once
+
+namespace gui::main_menu
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::main_menu
diff --git a/game/client/gui/message_box.cc b/game/client/gui/message_box.cc index 59e2d33..b7f109a 100644 --- a/game/client/gui/message_box.cc +++ b/game/client/gui/message_box.cc @@ -1,95 +1,95 @@ -#include "client/pch.hh" - -#include "client/gui/message_box.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -struct Button final { - gui::message_box_action action; - std::string str_title; -}; - -static std::string str_title; -static std::string str_subtitle; -static std::vector<Button> buttons; - -void gui::message_box::init(void) -{ - str_title = std::string(); - str_subtitle = std::string(); - buttons.clear(); -} - -void gui::message_box::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale)); - - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale)); - - if(!str_subtitle.empty()) { - const float subtitle_width = ImGui::CalcTextSize(str_subtitle.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - subtitle_width)); - ImGui::TextUnformatted(str_subtitle.c_str()); - } - - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - - for(const auto& button : buttons) { - const float button_width = 0.8f * ImGui::CalcItemWidth(); - ImGui::SetCursorPosX(0.5f * (window_size.x - button_width)); - - if(ImGui::Button(button.str_title.c_str(), ImVec2(button_width, 0.0f))) { - if(button.action) { - button.action(); - } - } - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::message_box::reset(void) -{ - str_title.clear(); - str_subtitle.clear(); - buttons.clear(); -} - -void gui::message_box::set_title(std::string_view title) -{ - str_title = gui::language::resolve(title); -} - -void gui::message_box::set_subtitle(std::string_view subtitle) -{ - str_subtitle = gui::language::resolve(subtitle); -} - -void gui::message_box::add_button(std::string_view text, const message_box_action& action) -{ - Button button = {}; - button.str_title = std::format("{}###MessageBox_Button{}", gui::language::resolve(text), buttons.size()); - button.action = action; - - buttons.push_back(button); -} +#include "client/pch.hh"
+
+#include "client/gui/message_box.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+struct Button final {
+ gui::message_box_action action;
+ std::string str_title;
+};
+
+static std::string str_title;
+static std::string str_subtitle;
+static std::vector<Button> buttons;
+
+void gui::message_box::init(void)
+{
+ str_title = std::string();
+ str_subtitle = std::string();
+ buttons.clear();
+}
+
+void gui::message_box::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale));
+
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale));
+
+ if(!str_subtitle.empty()) {
+ const float subtitle_width = ImGui::CalcTextSize(str_subtitle.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - subtitle_width));
+ ImGui::TextUnformatted(str_subtitle.c_str());
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+
+ for(const auto& button : buttons) {
+ const float button_width = 0.8f * ImGui::CalcItemWidth();
+ ImGui::SetCursorPosX(0.5f * (window_size.x - button_width));
+
+ if(ImGui::Button(button.str_title.c_str(), ImVec2(button_width, 0.0f))) {
+ if(button.action) {
+ button.action();
+ }
+ }
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::message_box::reset(void)
+{
+ str_title.clear();
+ str_subtitle.clear();
+ buttons.clear();
+}
+
+void gui::message_box::set_title(std::string_view title)
+{
+ str_title = gui::language::resolve(title);
+}
+
+void gui::message_box::set_subtitle(std::string_view subtitle)
+{
+ str_subtitle = gui::language::resolve(subtitle);
+}
+
+void gui::message_box::add_button(std::string_view text, const message_box_action& action)
+{
+ Button button = {};
+ button.str_title = std::format("{}###MessageBox_Button{}", gui::language::resolve(text), buttons.size());
+ button.action = action;
+
+ buttons.push_back(button);
+}
diff --git a/game/client/gui/message_box.hh b/game/client/gui/message_box.hh index 74a6fbf..06a3b8b 100644 --- a/game/client/gui/message_box.hh +++ b/game/client/gui/message_box.hh @@ -1,20 +1,20 @@ -#pragma once - -namespace gui -{ -using message_box_action = void (*)(void); -} // namespace gui - -namespace gui::message_box -{ -void init(void); -void layout(void); -void reset(void); -} // namespace gui::message_box - -namespace gui::message_box -{ -void set_title(std::string_view title); -void set_subtitle(std::string_view subtitle); -void add_button(std::string_view text, const message_box_action& action); -} // namespace gui::message_box +#pragma once
+
+namespace gui
+{
+using message_box_action = void (*)(void);
+} // namespace gui
+
+namespace gui::message_box
+{
+void init(void);
+void layout(void);
+void reset(void);
+} // namespace gui::message_box
+
+namespace gui::message_box
+{
+void set_title(std::string_view title);
+void set_subtitle(std::string_view subtitle);
+void add_button(std::string_view text, const message_box_action& action);
+} // namespace gui::message_box
diff --git a/game/client/gui/metrics.cc b/game/client/gui/metrics.cc index 57a6319..ef47880 100644 --- a/game/client/gui/metrics.cc +++ b/game/client/gui/metrics.cc @@ -1,103 +1,103 @@ -#include "client/pch.hh" - -#include "client/gui/metrics.hh" - -#include "core/version.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 "shared/coord.hh" - -#include "client/entity/camera.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs - | ImGuiWindowFlags_NoNav; - -static std::basic_string<GLubyte> r_version; -static std::basic_string<GLubyte> r_renderer; - -void gui::metrics::init(void) -{ - r_version = std::basic_string<GLubyte>(glGetString(GL_VERSION)); - r_renderer = std::basic_string<GLubyte>(glGetString(GL_RENDERER)); -} - -void gui::metrics::layout(void) -{ - if(!session::is_ingame()) { - // Sanity check; we are checking this - // in client_game before calling layout - // on HUD-ish GUI systems but still - return; - } - - auto draw_list = ImGui::GetForegroundDrawList(); - - // FIXME: maybe use style colors instead of hardcoding? - auto text_color = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - auto shadow_color = ImGui::GetColorU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f)); - - auto font_size = 4.0f; - auto position = ImVec2(8.0f, 8.0f); - auto y_step = 1.5f * globals::gui_scale * font_size; - - // Draw version - auto version_line = std::format("Voxelius {} [{}]", version::semver, version::commit); - gui::imdraw_ext::text_shadow(version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += 1.5f * y_step; - - // Draw client-side window framerate metrics - auto window_framerate = 1.0f / globals::window_frametime_avg; - auto window_frametime = 1000.0f * globals::window_frametime_avg; - auto window_fps_line = std::format("{:.02f} FPS [{:.02f} ms]", window_framerate, window_frametime); - gui::imdraw_ext::text_shadow(window_fps_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw world rendering metrics - auto drawcall_line = std::format("World: {} DC / {} TRI", globals::num_drawcalls, globals::num_triangles); - gui::imdraw_ext::text_shadow(drawcall_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw OpenGL version string - auto r_version_line = std::format("GL_VERSION: {}", reinterpret_cast<const char*>(r_version.c_str())); - gui::imdraw_ext::text_shadow(r_version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw OpenGL renderer string - auto r_renderer_line = std::format("GL_RENDERER: {}", reinterpret_cast<const char*>(r_renderer.c_str())); - gui::imdraw_ext::text_shadow(r_renderer_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += 1.5f * y_step; - - const auto& head = globals::dimension->entities.get<entity::Head>(globals::player); - const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player); - const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player); - - // Draw player voxel position - auto voxel_position = coord::to_voxel(transform.chunk, transform.local); - auto voxel_line = std::format("voxel: [{} {} {}]", voxel_position.x, voxel_position.y, voxel_position.z); - gui::imdraw_ext::text_shadow(voxel_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw player world position - auto world_line = std::format("world: [{} {} {}] [{:.03f} {:.03f} {:.03f}]", transform.chunk.x, transform.chunk.y, transform.chunk.z, - transform.local.x, transform.local.y, transform.local.z); - gui::imdraw_ext::text_shadow(world_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw player look angles - auto angles = glm::degrees(transform.angles + head.angles); - auto angle_line = std::format("angle: [{: .03f} {: .03f} {: .03f}]", angles[0], angles[1], angles[2]); - gui::imdraw_ext::text_shadow(angle_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; -} +#include "client/pch.hh"
+
+#include "client/gui/metrics.hh"
+
+#include "core/version.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 "shared/coord.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
+ | ImGuiWindowFlags_NoNav;
+
+static std::basic_string<GLubyte> r_version;
+static std::basic_string<GLubyte> r_renderer;
+
+void gui::metrics::init(void)
+{
+ r_version = std::basic_string<GLubyte>(glGetString(GL_VERSION));
+ r_renderer = std::basic_string<GLubyte>(glGetString(GL_RENDERER));
+}
+
+void gui::metrics::layout(void)
+{
+ if(!session::is_ingame()) {
+ // Sanity check; we are checking this
+ // in client_game before calling layout
+ // on HUD-ish GUI systems but still
+ return;
+ }
+
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ // FIXME: maybe use style colors instead of hardcoding?
+ auto text_color = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
+ auto shadow_color = ImGui::GetColorU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
+
+ auto font_size = 4.0f;
+ auto position = ImVec2(8.0f, 8.0f);
+ auto y_step = 1.5f * globals::gui_scale * font_size;
+
+ // Draw version
+ auto version_line = std::format("Voxelius {} [{}]", version::semver, version::commit);
+ gui::imdraw_ext::text_shadow(version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += 1.5f * y_step;
+
+ // Draw client-side window framerate metrics
+ auto window_framerate = 1.0f / globals::window_frametime_avg;
+ auto window_frametime = 1000.0f * globals::window_frametime_avg;
+ auto window_fps_line = std::format("{:.02f} FPS [{:.02f} ms]", window_framerate, window_frametime);
+ gui::imdraw_ext::text_shadow(window_fps_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw world rendering metrics
+ auto drawcall_line = std::format("World: {} DC / {} TRI", globals::num_drawcalls, globals::num_triangles);
+ gui::imdraw_ext::text_shadow(drawcall_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw OpenGL version string
+ auto r_version_line = std::format("GL_VERSION: {}", reinterpret_cast<const char*>(r_version.c_str()));
+ gui::imdraw_ext::text_shadow(r_version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw OpenGL renderer string
+ auto r_renderer_line = std::format("GL_RENDERER: {}", reinterpret_cast<const char*>(r_renderer.c_str()));
+ gui::imdraw_ext::text_shadow(r_renderer_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += 1.5f * y_step;
+
+ const auto& head = globals::dimension->entities.get<entity::Head>(globals::player);
+ const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player);
+ const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player);
+
+ // Draw player voxel position
+ auto voxel_position = coord::to_voxel(transform.chunk, transform.local);
+ auto voxel_line = std::format("voxel: [{} {} {}]", voxel_position.x, voxel_position.y, voxel_position.z);
+ gui::imdraw_ext::text_shadow(voxel_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw player world position
+ auto world_line = std::format("world: [{} {} {}] [{:.03f} {:.03f} {:.03f}]", transform.chunk.x, transform.chunk.y, transform.chunk.z,
+ transform.local.x, transform.local.y, transform.local.z);
+ gui::imdraw_ext::text_shadow(world_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw player look angles
+ auto angles = glm::degrees(transform.angles + head.angles);
+ auto angle_line = std::format("angle: [{: .03f} {: .03f} {: .03f}]", angles[0], angles[1], angles[2]);
+ gui::imdraw_ext::text_shadow(angle_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+}
diff --git a/game/client/gui/metrics.hh b/game/client/gui/metrics.hh index 4898332..6bec5cb 100644 --- a/game/client/gui/metrics.hh +++ b/game/client/gui/metrics.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::metrics -{ -void init(void); -void layout(void); -} // namespace gui::metrics +#pragma once
+
+namespace gui::metrics
+{
+void init(void);
+void layout(void);
+} // namespace gui::metrics
diff --git a/game/client/gui/play_menu.cc b/game/client/gui/play_menu.cc index 2951bf2..5b9887e 100644 --- a/game/client/gui/play_menu.cc +++ b/game/client/gui/play_menu.cc @@ -1,564 +1,564 @@ -#include "client/pch.hh" - -#include "client/gui/play_menu.hh" - -#include "core/config/boolean.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/gui/bother.hh" -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static std::string_view DEFAULT_SERVER_NAME = "Voxelius Server"; -constexpr static std::string_view SERVERS_TXT = "servers.txt"; -constexpr static std::string_view WARNING_TOAST = "[!]"; - -constexpr static std::size_t MAX_SERVER_ITEM_NAME = 24; - -enum class item_status : unsigned int { - UNKNOWN = 0x0000U, - PINGING = 0x0001U, - REACHED = 0x0002U, - FAILURE = 0x0003U, -}; - -struct ServerStatusItem final { - std::string name; - std::string password; - std::string hostname; - std::uint16_t port; - - // Things pulled from bother events - std::uint32_t protocol_version; - std::uint16_t num_players; - std::uint16_t max_players; - std::string motd; - - // Unique identifier that monotonically - // grows with each new server added and - // doesn't reset with each server removed - unsigned int identity; - - item_status status; -}; - -static std::string str_tab_servers; - -static std::string str_join; -static std::string str_connect; -static std::string str_add; -static std::string str_edit; -static std::string str_remove; -static std::string str_refresh; - -static std::string str_status_init; -static std::string str_status_ping; -static std::string str_status_fail; - -static std::string str_outdated_client; -static std::string str_outdated_server; - -static std::string input_itemname; -static std::string input_hostname; -static std::string input_password; - -static unsigned int next_identity; -static std::deque<ServerStatusItem*> servers_deque; -static ServerStatusItem* selected_server; -static bool editing_server; -static bool adding_server; -static bool needs_focus; - -static void parse_hostname(ServerStatusItem* item, const std::string& hostname) -{ - auto parts = utils::split(hostname, ":"); - - if(!parts[0].empty()) { - item->hostname = parts[0]; - } - else { - item->hostname = std::string("localhost"); - } - - if(parts.size() >= 2) { - item->port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX); - } - else { - item->port = protocol::PORT; - } -} - -static void add_new_server(void) -{ - auto item = new ServerStatusItem(); - item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; - item->max_players = UINT16_MAX; - item->num_players = UINT16_MAX; - item->identity = next_identity; - item->status = item_status::UNKNOWN; - - next_identity += 1U; - - input_itemname = DEFAULT_SERVER_NAME; - input_hostname = std::string(); - input_password = std::string(); - - servers_deque.push_back(item); - selected_server = item; - editing_server = true; - adding_server = true; - needs_focus = true; -} - -static void edit_selected_server(void) -{ - input_itemname = selected_server->name; - - if(selected_server->port != protocol::PORT) { - input_hostname = std::format("{}:{}", selected_server->hostname, selected_server->port); - } - else { - input_hostname = selected_server->hostname; - } - - input_password = selected_server->password; - - editing_server = true; - needs_focus = true; -} - -static void remove_selected_server(void) -{ - gui::bother::cancel(selected_server->identity); - - for(auto it = servers_deque.cbegin(); it != servers_deque.cend(); ++it) { - if(selected_server == (*it)) { - delete selected_server; - selected_server = nullptr; - servers_deque.erase(it); - return; - } - } -} - -static void join_selected_server(void) -{ - if(!session::peer) { - session::connect(selected_server->hostname.c_str(), selected_server->port, selected_server->password.c_str()); - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_PLAY_MENU) { - if(editing_server) { - if(adding_server) { - remove_selected_server(); - } - else { - input_itemname.clear(); - input_hostname.clear(); - input_password.clear(); - editing_server = false; - adding_server = false; - return; - } - } - - globals::gui_screen = GUI_MAIN_MENU; - selected_server = nullptr; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_tab_servers = gui::language::resolve_gui("play_menu.tab.servers"); - - str_join = gui::language::resolve_gui("play_menu.join"); - str_connect = gui::language::resolve_gui("play_menu.connect"); - str_add = gui::language::resolve_gui("play_menu.add"); - str_edit = gui::language::resolve_gui("play_menu.edit"); - str_remove = gui::language::resolve_gui("play_menu.remove"); - str_refresh = gui::language::resolve_gui("play_menu.refresh"); - - str_status_init = gui::language::resolve("play_menu.status.init"); - str_status_ping = gui::language::resolve("play_menu.status.ping"); - str_status_fail = gui::language::resolve("play_menu.status.fail"); - - str_outdated_client = gui::language::resolve("play_menu.outdated_client"); - str_outdated_server = gui::language::resolve("play_menu.outdated_server"); -} - -static void on_bother_response(const gui::BotherResponseEvent& event) -{ - for(auto item : servers_deque) { - if(item->identity == event.identity) { - if(event.is_server_unreachable) { - item->protocol_version = 0U; - item->num_players = UINT16_MAX; - item->max_players = UINT16_MAX; - item->motd = str_status_fail; - item->status = item_status::FAILURE; - } - else { - item->protocol_version = event.protocol_version; - item->num_players = event.num_players; - item->max_players = event.max_players; - item->motd = event.motd; - item->status = item_status::REACHED; - } - - break; - } - } -} - -static void layout_server_item(ServerStatusItem* item) -{ - // Preserve the cursor at which we draw stuff - const ImVec2& cursor = ImGui::GetCursorScreenPos(); - const ImVec2& padding = ImGui::GetStyle().FramePadding; - const ImVec2& spacing = ImGui::GetStyle().ItemSpacing; - - const float item_width = ImGui::GetContentRegionAvail().x; - const float line_height = ImGui::GetTextLineHeightWithSpacing(); - const std::string sid = std::format("###play_menu.servers.{}", static_cast<void*>(item)); - if(ImGui::Selectable(sid.c_str(), (item == selected_server), 0, ImVec2(0.0, 2.0f * (line_height + padding.y + spacing.y)))) { - selected_server = item; - editing_server = false; - } - - if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - // Double clicked - join the selected server - join_selected_server(); - } - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - if(item == selected_server) { - const ImVec2 start = ImVec2(cursor.x, cursor.y); - const ImVec2 end = ImVec2(start.x + item_width, start.y + 2.0f * (line_height + padding.y + spacing.y)); - draw_list->AddRect(start, end, ImGui::GetColorU32(ImGuiCol_Text), 0.0f, 0, globals::gui_scale); - } - - const ImVec2 name_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y); - draw_list->AddText(name_pos, ImGui::GetColorU32(ImGuiCol_Text), item->name.c_str(), item->name.c_str() + item->name.size()); - - if(item->status == item_status::REACHED) { - auto stats = std::format("{}/{}", item->num_players, item->max_players); - auto stats_width = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()).x; - auto stats_pos = ImVec2(cursor.x + item_width - stats_width - padding.x, cursor.y + padding.y); - draw_list->AddText(stats_pos, ImGui::GetColorU32(ImGuiCol_TextDisabled), stats.c_str(), stats.c_str() + stats.size()); - - if(item->protocol_version != protocol::VERSION) { - auto warning_size = ImGui::CalcTextSize(WARNING_TOAST.data(), WARNING_TOAST.data() + WARNING_TOAST.size()); - auto warning_pos = ImVec2(stats_pos.x - warning_size.x - padding.x - 4.0f * globals::gui_scale, cursor.y + padding.y); - auto warning_end = ImVec2(warning_pos.x + warning_size.x, warning_pos.y + warning_size.y); - draw_list->AddText(warning_pos, ImGui::GetColorU32(ImGuiCol_DragDropTarget), WARNING_TOAST.data(), - WARNING_TOAST.data() + WARNING_TOAST.size()); - - if(ImGui::IsMouseHoveringRect(warning_pos, warning_end)) { - ImGui::BeginTooltip(); - - if(item->protocol_version < protocol::VERSION) { - ImGui::TextUnformatted(str_outdated_server.c_str(), str_outdated_server.c_str() + str_outdated_server.size()); - } - else { - ImGui::TextUnformatted(str_outdated_client.c_str(), str_outdated_client.c_str() + str_outdated_client.size()); - } - - ImGui::EndTooltip(); - } - } - } - - ImU32 motd_color = {}; - const std::string* motd_text; - - switch(item->status) { - case item_status::UNKNOWN: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &str_status_init; - break; - case item_status::PINGING: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &str_status_ping; - break; - case item_status::REACHED: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &item->motd; - break; - default: - motd_color = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered); - motd_text = &str_status_fail; - break; - } - - const ImVec2 motd_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y + line_height); - draw_list->AddText(motd_pos, motd_color, motd_text->c_str(), motd_text->c_str() + motd_text->size()); -} - -static void layout_server_edit(ServerStatusItem* item) -{ - if(needs_focus) { - ImGui::SetKeyboardFocusHere(); - needs_focus = false; - } - - ImGui::SetNextItemWidth(-0.25f * ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_itemname", &input_itemname); - ImGui::SameLine(); - - const bool ignore_input = utils::is_whitespace(input_itemname) || input_hostname.empty(); - - ImGui::BeginDisabled(ignore_input); - - if(ImGui::Button("OK###play_menu.servers.submit_input", ImVec2(-1.0f, 0.0f)) - || (!ignore_input && ImGui::IsKeyPressed(ImGuiKey_Enter))) { - parse_hostname(item, input_hostname); - item->password = input_password; - item->name = input_itemname.substr(0, MAX_SERVER_ITEM_NAME); - item->status = item_status::UNKNOWN; - editing_server = false; - adding_server = false; - - input_itemname.clear(); - input_hostname.clear(); - - gui::bother::cancel(item->identity); - } - - ImGui::EndDisabled(); - - ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank; - - if(client_game::streamer_mode.get_value()) { - // Hide server hostname to avoid things like - // followers flooding the server that is streamed online - hostname_flags |= ImGuiInputTextFlags_Password; - } - - ImGui::SetNextItemWidth(-0.50f * ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_hostname", &input_hostname, hostname_flags); - ImGui::SameLine(); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_password", &input_password, ImGuiInputTextFlags_Password); -} - -static void layout_servers(void) -{ - if(ImGui::BeginListBox("###play_menu.servers.listbox", ImVec2(-1.0f, -1.0f))) { - for(ServerStatusItem* item : servers_deque) { - if(editing_server && item == selected_server) { - layout_server_edit(item); - } - else { - layout_server_item(item); - } - } - - ImGui::EndListBox(); - } -} - -static void layout_servers_buttons(void) -{ - auto avail_width = ImGui::GetContentRegionAvail().x; - - // Can only join when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_join.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) { - join_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only connect directly when not editing anything - ImGui::BeginDisabled(editing_server); - - if(ImGui::Button(str_connect.c_str(), ImVec2(-1.00f, 0.0f))) { - globals::gui_screen = GUI_DIRECT_CONNECTION; - } - - ImGui::EndDisabled(); - - // Can only add when not editing anything - ImGui::BeginDisabled(editing_server); - - if(ImGui::Button(str_add.c_str(), ImVec2(-0.75f * avail_width, 0.0f))) { - add_new_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only edit when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_edit.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) { - edit_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only remove when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_remove.c_str(), ImVec2(-0.25f * avail_width, 0.0f))) { - remove_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - if(ImGui::Button(str_refresh.c_str(), ImVec2(-1.0f, 0.0f))) { - for(ServerStatusItem* item : servers_deque) { - if(item->status != item_status::PINGING) { - if(!editing_server || item != selected_server) { - item->status = item_status::UNKNOWN; - gui::bother::cancel(item->identity); - } - } - } - } -} - -void gui::play_menu::init(void) -{ - if(auto file = PHYSFS_openRead(std::string(SERVERS_TXT).c_str())) { - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto stream = std::istringstream(source); - auto line = std::string(); - - while(std::getline(stream, line)) { - auto parts = utils::split(line, "%"); - - auto item = new ServerStatusItem(); - item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; - item->max_players = UINT16_MAX; - item->num_players = UINT16_MAX; - item->identity = next_identity; - item->status = item_status::UNKNOWN; - - next_identity += 1U; - - parse_hostname(item, parts[0]); - - if(parts.size() >= 2) { - item->password = parts[1]; - } - else { - item->password = std::string(); - } - - if(parts.size() >= 3) { - item->name = parts[2].substr(0, MAX_SERVER_ITEM_NAME); - } - else { - item->name = DEFAULT_SERVER_NAME; - } - - servers_deque.push_back(item); - } - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); - globals::dispatcher.sink<BotherResponseEvent>().connect<&on_bother_response>(); -} - -void gui::play_menu::shutdown(void) -{ - std::ostringstream stream; - - for(const auto item : servers_deque) { - stream << std::format("{}:{}%{}%{}", item->hostname, item->port, item->password, item->name) << std::endl; - } - - if(auto file = PHYSFS_openWrite(std::string(SERVERS_TXT).c_str())) { - auto source = stream.str(); - PHYSFS_writeBytes(file, source.data(), source.size()); - PHYSFS_close(file); - } - - for(auto item : servers_deque) - delete item; - servers_deque.clear(); -} - -void gui::play_menu::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f); - const auto window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###play_menu", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale)); - - if(ImGui::BeginTabBar("###play_menu.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::TabItemButton("<<")) { - globals::gui_screen = GUI_MAIN_MENU; - selected_server = nullptr; - editing_server = false; - } - - if(ImGui::BeginTabItem(str_tab_servers.c_str())) { - if(ImGui::BeginChild("###play_menu.servers.child", ImVec2(0.0f, -2.0f * ImGui::GetFrameHeightWithSpacing()))) { - layout_servers(); - } - - ImGui::EndChild(); - - layout_servers_buttons(); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::play_menu::update_late(void) -{ - for(auto item : servers_deque) { - if(item->status == item_status::UNKNOWN) { - gui::bother::ping(item->identity, item->hostname.c_str(), item->port); - item->status = item_status::PINGING; - continue; - } - } -} +#include "client/pch.hh"
+
+#include "client/gui/play_menu.hh"
+
+#include "core/config/boolean.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/gui/bother.hh"
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static std::string_view DEFAULT_SERVER_NAME = "Voxelius Server";
+constexpr static std::string_view SERVERS_TXT = "servers.txt";
+constexpr static std::string_view WARNING_TOAST = "[!]";
+
+constexpr static std::size_t MAX_SERVER_ITEM_NAME = 24;
+
+enum class item_status : unsigned int {
+ UNKNOWN = 0x0000U,
+ PINGING = 0x0001U,
+ REACHED = 0x0002U,
+ FAILURE = 0x0003U,
+};
+
+struct ServerStatusItem final {
+ std::string name;
+ std::string password;
+ std::string hostname;
+ std::uint16_t port;
+
+ // Things pulled from bother events
+ std::uint32_t protocol_version;
+ std::uint16_t num_players;
+ std::uint16_t max_players;
+ std::string motd;
+
+ // Unique identifier that monotonically
+ // grows with each new server added and
+ // doesn't reset with each server removed
+ unsigned int identity;
+
+ item_status status;
+};
+
+static std::string str_tab_servers;
+
+static std::string str_join;
+static std::string str_connect;
+static std::string str_add;
+static std::string str_edit;
+static std::string str_remove;
+static std::string str_refresh;
+
+static std::string str_status_init;
+static std::string str_status_ping;
+static std::string str_status_fail;
+
+static std::string str_outdated_client;
+static std::string str_outdated_server;
+
+static std::string input_itemname;
+static std::string input_hostname;
+static std::string input_password;
+
+static unsigned int next_identity;
+static std::deque<ServerStatusItem*> servers_deque;
+static ServerStatusItem* selected_server;
+static bool editing_server;
+static bool adding_server;
+static bool needs_focus;
+
+static void parse_hostname(ServerStatusItem* item, const std::string& hostname)
+{
+ auto parts = utils::split(hostname, ":");
+
+ if(!parts[0].empty()) {
+ item->hostname = parts[0];
+ }
+ else {
+ item->hostname = std::string("localhost");
+ }
+
+ if(parts.size() >= 2) {
+ item->port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX);
+ }
+ else {
+ item->port = protocol::PORT;
+ }
+}
+
+static void add_new_server(void)
+{
+ auto item = new ServerStatusItem();
+ item->port = protocol::PORT;
+ item->protocol_version = protocol::VERSION;
+ item->max_players = UINT16_MAX;
+ item->num_players = UINT16_MAX;
+ item->identity = next_identity;
+ item->status = item_status::UNKNOWN;
+
+ next_identity += 1U;
+
+ input_itemname = DEFAULT_SERVER_NAME;
+ input_hostname = std::string();
+ input_password = std::string();
+
+ servers_deque.push_back(item);
+ selected_server = item;
+ editing_server = true;
+ adding_server = true;
+ needs_focus = true;
+}
+
+static void edit_selected_server(void)
+{
+ input_itemname = selected_server->name;
+
+ if(selected_server->port != protocol::PORT) {
+ input_hostname = std::format("{}:{}", selected_server->hostname, selected_server->port);
+ }
+ else {
+ input_hostname = selected_server->hostname;
+ }
+
+ input_password = selected_server->password;
+
+ editing_server = true;
+ needs_focus = true;
+}
+
+static void remove_selected_server(void)
+{
+ gui::bother::cancel(selected_server->identity);
+
+ for(auto it = servers_deque.cbegin(); it != servers_deque.cend(); ++it) {
+ if(selected_server == (*it)) {
+ delete selected_server;
+ selected_server = nullptr;
+ servers_deque.erase(it);
+ return;
+ }
+ }
+}
+
+static void join_selected_server(void)
+{
+ if(!session::peer) {
+ session::connect(selected_server->hostname.c_str(), selected_server->port, selected_server->password.c_str());
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_PLAY_MENU) {
+ if(editing_server) {
+ if(adding_server) {
+ remove_selected_server();
+ }
+ else {
+ input_itemname.clear();
+ input_hostname.clear();
+ input_password.clear();
+ editing_server = false;
+ adding_server = false;
+ return;
+ }
+ }
+
+ globals::gui_screen = GUI_MAIN_MENU;
+ selected_server = nullptr;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_tab_servers = gui::language::resolve_gui("play_menu.tab.servers");
+
+ str_join = gui::language::resolve_gui("play_menu.join");
+ str_connect = gui::language::resolve_gui("play_menu.connect");
+ str_add = gui::language::resolve_gui("play_menu.add");
+ str_edit = gui::language::resolve_gui("play_menu.edit");
+ str_remove = gui::language::resolve_gui("play_menu.remove");
+ str_refresh = gui::language::resolve_gui("play_menu.refresh");
+
+ str_status_init = gui::language::resolve("play_menu.status.init");
+ str_status_ping = gui::language::resolve("play_menu.status.ping");
+ str_status_fail = gui::language::resolve("play_menu.status.fail");
+
+ str_outdated_client = gui::language::resolve("play_menu.outdated_client");
+ str_outdated_server = gui::language::resolve("play_menu.outdated_server");
+}
+
+static void on_bother_response(const gui::BotherResponseEvent& event)
+{
+ for(auto item : servers_deque) {
+ if(item->identity == event.identity) {
+ if(event.is_server_unreachable) {
+ item->protocol_version = 0U;
+ item->num_players = UINT16_MAX;
+ item->max_players = UINT16_MAX;
+ item->motd = str_status_fail;
+ item->status = item_status::FAILURE;
+ }
+ else {
+ item->protocol_version = event.protocol_version;
+ item->num_players = event.num_players;
+ item->max_players = event.max_players;
+ item->motd = event.motd;
+ item->status = item_status::REACHED;
+ }
+
+ break;
+ }
+ }
+}
+
+static void layout_server_item(ServerStatusItem* item)
+{
+ // Preserve the cursor at which we draw stuff
+ const ImVec2& cursor = ImGui::GetCursorScreenPos();
+ const ImVec2& padding = ImGui::GetStyle().FramePadding;
+ const ImVec2& spacing = ImGui::GetStyle().ItemSpacing;
+
+ const float item_width = ImGui::GetContentRegionAvail().x;
+ const float line_height = ImGui::GetTextLineHeightWithSpacing();
+ const std::string sid = std::format("###play_menu.servers.{}", static_cast<void*>(item));
+ if(ImGui::Selectable(sid.c_str(), (item == selected_server), 0, ImVec2(0.0, 2.0f * (line_height + padding.y + spacing.y)))) {
+ selected_server = item;
+ editing_server = false;
+ }
+
+ if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
+ // Double clicked - join the selected server
+ join_selected_server();
+ }
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ if(item == selected_server) {
+ const ImVec2 start = ImVec2(cursor.x, cursor.y);
+ const ImVec2 end = ImVec2(start.x + item_width, start.y + 2.0f * (line_height + padding.y + spacing.y));
+ draw_list->AddRect(start, end, ImGui::GetColorU32(ImGuiCol_Text), 0.0f, 0, globals::gui_scale);
+ }
+
+ const ImVec2 name_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y);
+ draw_list->AddText(name_pos, ImGui::GetColorU32(ImGuiCol_Text), item->name.c_str(), item->name.c_str() + item->name.size());
+
+ if(item->status == item_status::REACHED) {
+ auto stats = std::format("{}/{}", item->num_players, item->max_players);
+ auto stats_width = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()).x;
+ auto stats_pos = ImVec2(cursor.x + item_width - stats_width - padding.x, cursor.y + padding.y);
+ draw_list->AddText(stats_pos, ImGui::GetColorU32(ImGuiCol_TextDisabled), stats.c_str(), stats.c_str() + stats.size());
+
+ if(item->protocol_version != protocol::VERSION) {
+ auto warning_size = ImGui::CalcTextSize(WARNING_TOAST.data(), WARNING_TOAST.data() + WARNING_TOAST.size());
+ auto warning_pos = ImVec2(stats_pos.x - warning_size.x - padding.x - 4.0f * globals::gui_scale, cursor.y + padding.y);
+ auto warning_end = ImVec2(warning_pos.x + warning_size.x, warning_pos.y + warning_size.y);
+ draw_list->AddText(warning_pos, ImGui::GetColorU32(ImGuiCol_DragDropTarget), WARNING_TOAST.data(),
+ WARNING_TOAST.data() + WARNING_TOAST.size());
+
+ if(ImGui::IsMouseHoveringRect(warning_pos, warning_end)) {
+ ImGui::BeginTooltip();
+
+ if(item->protocol_version < protocol::VERSION) {
+ ImGui::TextUnformatted(str_outdated_server.c_str(), str_outdated_server.c_str() + str_outdated_server.size());
+ }
+ else {
+ ImGui::TextUnformatted(str_outdated_client.c_str(), str_outdated_client.c_str() + str_outdated_client.size());
+ }
+
+ ImGui::EndTooltip();
+ }
+ }
+ }
+
+ ImU32 motd_color = {};
+ const std::string* motd_text;
+
+ switch(item->status) {
+ case item_status::UNKNOWN:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &str_status_init;
+ break;
+ case item_status::PINGING:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &str_status_ping;
+ break;
+ case item_status::REACHED:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &item->motd;
+ break;
+ default:
+ motd_color = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered);
+ motd_text = &str_status_fail;
+ break;
+ }
+
+ const ImVec2 motd_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y + line_height);
+ draw_list->AddText(motd_pos, motd_color, motd_text->c_str(), motd_text->c_str() + motd_text->size());
+}
+
+static void layout_server_edit(ServerStatusItem* item)
+{
+ if(needs_focus) {
+ ImGui::SetKeyboardFocusHere();
+ needs_focus = false;
+ }
+
+ ImGui::SetNextItemWidth(-0.25f * ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_itemname", &input_itemname);
+ ImGui::SameLine();
+
+ const bool ignore_input = utils::is_whitespace(input_itemname) || input_hostname.empty();
+
+ ImGui::BeginDisabled(ignore_input);
+
+ if(ImGui::Button("OK###play_menu.servers.submit_input", ImVec2(-1.0f, 0.0f))
+ || (!ignore_input && ImGui::IsKeyPressed(ImGuiKey_Enter))) {
+ parse_hostname(item, input_hostname);
+ item->password = input_password;
+ item->name = input_itemname.substr(0, MAX_SERVER_ITEM_NAME);
+ item->status = item_status::UNKNOWN;
+ editing_server = false;
+ adding_server = false;
+
+ input_itemname.clear();
+ input_hostname.clear();
+
+ gui::bother::cancel(item->identity);
+ }
+
+ ImGui::EndDisabled();
+
+ ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank;
+
+ if(client_game::streamer_mode.get_value()) {
+ // Hide server hostname to avoid things like
+ // followers flooding the server that is streamed online
+ hostname_flags |= ImGuiInputTextFlags_Password;
+ }
+
+ ImGui::SetNextItemWidth(-0.50f * ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_hostname", &input_hostname, hostname_flags);
+ ImGui::SameLine();
+
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_password", &input_password, ImGuiInputTextFlags_Password);
+}
+
+static void layout_servers(void)
+{
+ if(ImGui::BeginListBox("###play_menu.servers.listbox", ImVec2(-1.0f, -1.0f))) {
+ for(ServerStatusItem* item : servers_deque) {
+ if(editing_server && item == selected_server) {
+ layout_server_edit(item);
+ }
+ else {
+ layout_server_item(item);
+ }
+ }
+
+ ImGui::EndListBox();
+ }
+}
+
+static void layout_servers_buttons(void)
+{
+ auto avail_width = ImGui::GetContentRegionAvail().x;
+
+ // Can only join when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_join.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) {
+ join_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only connect directly when not editing anything
+ ImGui::BeginDisabled(editing_server);
+
+ if(ImGui::Button(str_connect.c_str(), ImVec2(-1.00f, 0.0f))) {
+ globals::gui_screen = GUI_DIRECT_CONNECTION;
+ }
+
+ ImGui::EndDisabled();
+
+ // Can only add when not editing anything
+ ImGui::BeginDisabled(editing_server);
+
+ if(ImGui::Button(str_add.c_str(), ImVec2(-0.75f * avail_width, 0.0f))) {
+ add_new_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only edit when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_edit.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) {
+ edit_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only remove when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_remove.c_str(), ImVec2(-0.25f * avail_width, 0.0f))) {
+ remove_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ if(ImGui::Button(str_refresh.c_str(), ImVec2(-1.0f, 0.0f))) {
+ for(ServerStatusItem* item : servers_deque) {
+ if(item->status != item_status::PINGING) {
+ if(!editing_server || item != selected_server) {
+ item->status = item_status::UNKNOWN;
+ gui::bother::cancel(item->identity);
+ }
+ }
+ }
+ }
+}
+
+void gui::play_menu::init(void)
+{
+ if(auto file = PHYSFS_openRead(std::string(SERVERS_TXT).c_str())) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto stream = std::istringstream(source);
+ auto line = std::string();
+
+ while(std::getline(stream, line)) {
+ auto parts = utils::split(line, "%");
+
+ auto item = new ServerStatusItem();
+ item->port = protocol::PORT;
+ item->protocol_version = protocol::VERSION;
+ item->max_players = UINT16_MAX;
+ item->num_players = UINT16_MAX;
+ item->identity = next_identity;
+ item->status = item_status::UNKNOWN;
+
+ next_identity += 1U;
+
+ parse_hostname(item, parts[0]);
+
+ if(parts.size() >= 2) {
+ item->password = parts[1];
+ }
+ else {
+ item->password = std::string();
+ }
+
+ if(parts.size() >= 3) {
+ item->name = parts[2].substr(0, MAX_SERVER_ITEM_NAME);
+ }
+ else {
+ item->name = DEFAULT_SERVER_NAME;
+ }
+
+ servers_deque.push_back(item);
+ }
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+ globals::dispatcher.sink<BotherResponseEvent>().connect<&on_bother_response>();
+}
+
+void gui::play_menu::shutdown(void)
+{
+ std::ostringstream stream;
+
+ for(const auto item : servers_deque) {
+ stream << std::format("{}:{}%{}%{}", item->hostname, item->port, item->password, item->name) << std::endl;
+ }
+
+ if(auto file = PHYSFS_openWrite(std::string(SERVERS_TXT).c_str())) {
+ auto source = stream.str();
+ PHYSFS_writeBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+ }
+
+ for(auto item : servers_deque)
+ delete item;
+ servers_deque.clear();
+}
+
+void gui::play_menu::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f);
+ const auto window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###play_menu", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale));
+
+ if(ImGui::BeginTabBar("###play_menu.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::TabItemButton("<<")) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ selected_server = nullptr;
+ editing_server = false;
+ }
+
+ if(ImGui::BeginTabItem(str_tab_servers.c_str())) {
+ if(ImGui::BeginChild("###play_menu.servers.child", ImVec2(0.0f, -2.0f * ImGui::GetFrameHeightWithSpacing()))) {
+ layout_servers();
+ }
+
+ ImGui::EndChild();
+
+ layout_servers_buttons();
+
+ ImGui::EndTabItem();
+ }
+
+ ImGui::EndTabBar();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::play_menu::update_late(void)
+{
+ for(auto item : servers_deque) {
+ if(item->status == item_status::UNKNOWN) {
+ gui::bother::ping(item->identity, item->hostname.c_str(), item->port);
+ item->status = item_status::PINGING;
+ continue;
+ }
+ }
+}
diff --git a/game/client/gui/play_menu.hh b/game/client/gui/play_menu.hh index 1b1f003..15f7c89 100644 --- a/game/client/gui/play_menu.hh +++ b/game/client/gui/play_menu.hh @@ -1,9 +1,9 @@ -#pragma once - -namespace gui::play_menu -{ -void init(void); -void shutdown(void); -void layout(void); -void update_late(void); -} // namespace gui::play_menu +#pragma once
+
+namespace gui::play_menu
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+void update_late(void);
+} // namespace gui::play_menu
diff --git a/game/client/gui/progress_bar.cc b/game/client/gui/progress_bar.cc index 8be2f8c..9266ce1 100644 --- a/game/client/gui/progress_bar.cc +++ b/game/client/gui/progress_bar.cc @@ -1,111 +1,111 @@ -#include "client/pch.hh" - -#include "client/gui/progress_bar.hh" - -#include "core/math/constexpr.hh" - -#include "client/gui/language.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_title; -static std::string str_button; -static gui::progress_bar_action button_action; - -void gui::progress_bar::init(void) -{ - str_title = "Loading"; - str_button = std::string(); - button_action = nullptr; -} - -void gui::progress_bar::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale)); - - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale)); - - const ImVec2 cursor = ImGui::GetCursorPos(); - - const std::size_t num_bars = 32; - const float spinner_width = 0.8f * ImGui::CalcItemWidth(); - const float bar_width = spinner_width / static_cast<float>(num_bars); - const float bar_height = 0.5f * ImGui::GetFrameHeight(); - - const float base_xpos = window_start.x + 0.5f * (window_size.x - spinner_width) + 0.5f; - const float base_ypos = window_start.y + cursor.y; - const float phase = 2.0f * ImGui::GetTime(); - - const ImVec4& background = ImGui::GetStyleColorVec4(ImGuiCol_Button); - const ImVec4& foreground = ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram); - - for(std::size_t i = 0; i < num_bars; ++i) { - const float sinval = std::sin(M_PI * static_cast<float>(i) / static_cast<float>(num_bars) - phase); - const float modifier = std::exp(-8.0f * (0.5f + 0.5f * sinval)); - - ImVec4 color = {}; - color.x = math::lerp(background.x, foreground.x, modifier); - color.y = math::lerp(background.y, foreground.y, modifier); - color.z = math::lerp(background.z, foreground.z, modifier); - color.w = math::lerp(background.w, foreground.w, modifier); - - const ImVec2 start = ImVec2(base_xpos + bar_width * i, base_ypos); - const ImVec2 end = ImVec2(start.x + bar_width, start.y + bar_height); - ImGui::GetWindowDrawList()->AddRectFilled(start, end, ImGui::GetColorU32(color)); - } - - // The NewLine call tricks ImGui into correctly padding the - // next widget that comes after the progress_bar spinner; this - // is needed to ensure the button is located in the correct place - ImGui::NewLine(); - - if(!str_button.empty()) { - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - - const float button_width = 0.8f * ImGui::CalcItemWidth(); - ImGui::SetCursorPosX(0.5f * (window_size.x - button_width)); - - if(ImGui::Button(str_button.c_str(), ImVec2(button_width, 0.0f))) { - if(button_action) { - button_action(); - } - } - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::progress_bar::reset(void) -{ - str_title.clear(); - str_button.clear(); - button_action = nullptr; -} - -void gui::progress_bar::set_title(std::string_view title) -{ - str_title = gui::language::resolve(title); -} - -void gui::progress_bar::set_button(std::string_view text, const progress_bar_action& action) -{ - str_button = std::format("{}###ProgressBar_Button", gui::language::resolve(text)); - button_action = action; -} +#include "client/pch.hh"
+
+#include "client/gui/progress_bar.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/gui/language.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_title;
+static std::string str_button;
+static gui::progress_bar_action button_action;
+
+void gui::progress_bar::init(void)
+{
+ str_title = "Loading";
+ str_button = std::string();
+ button_action = nullptr;
+}
+
+void gui::progress_bar::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale));
+
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale));
+
+ const ImVec2 cursor = ImGui::GetCursorPos();
+
+ const std::size_t num_bars = 32;
+ const float spinner_width = 0.8f * ImGui::CalcItemWidth();
+ const float bar_width = spinner_width / static_cast<float>(num_bars);
+ const float bar_height = 0.5f * ImGui::GetFrameHeight();
+
+ const float base_xpos = window_start.x + 0.5f * (window_size.x - spinner_width) + 0.5f;
+ const float base_ypos = window_start.y + cursor.y;
+ const float phase = 2.0f * ImGui::GetTime();
+
+ const ImVec4& background = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+ const ImVec4& foreground = ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram);
+
+ for(std::size_t i = 0; i < num_bars; ++i) {
+ const float sinval = std::sin(M_PI * static_cast<float>(i) / static_cast<float>(num_bars) - phase);
+ const float modifier = std::exp(-8.0f * (0.5f + 0.5f * sinval));
+
+ ImVec4 color = {};
+ color.x = math::lerp(background.x, foreground.x, modifier);
+ color.y = math::lerp(background.y, foreground.y, modifier);
+ color.z = math::lerp(background.z, foreground.z, modifier);
+ color.w = math::lerp(background.w, foreground.w, modifier);
+
+ const ImVec2 start = ImVec2(base_xpos + bar_width * i, base_ypos);
+ const ImVec2 end = ImVec2(start.x + bar_width, start.y + bar_height);
+ ImGui::GetWindowDrawList()->AddRectFilled(start, end, ImGui::GetColorU32(color));
+ }
+
+ // The NewLine call tricks ImGui into correctly padding the
+ // next widget that comes after the progress_bar spinner; this
+ // is needed to ensure the button is located in the correct place
+ ImGui::NewLine();
+
+ if(!str_button.empty()) {
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+
+ const float button_width = 0.8f * ImGui::CalcItemWidth();
+ ImGui::SetCursorPosX(0.5f * (window_size.x - button_width));
+
+ if(ImGui::Button(str_button.c_str(), ImVec2(button_width, 0.0f))) {
+ if(button_action) {
+ button_action();
+ }
+ }
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::progress_bar::reset(void)
+{
+ str_title.clear();
+ str_button.clear();
+ button_action = nullptr;
+}
+
+void gui::progress_bar::set_title(std::string_view title)
+{
+ str_title = gui::language::resolve(title);
+}
+
+void gui::progress_bar::set_button(std::string_view text, const progress_bar_action& action)
+{
+ str_button = std::format("{}###ProgressBar_Button", gui::language::resolve(text));
+ button_action = action;
+}
diff --git a/game/client/gui/progress_bar.hh b/game/client/gui/progress_bar.hh index 7a0581d..5f2e601 100644 --- a/game/client/gui/progress_bar.hh +++ b/game/client/gui/progress_bar.hh @@ -1,19 +1,19 @@ -#pragma once - -namespace gui -{ -using progress_bar_action = void (*)(void); -} // namespace gui - -namespace gui::progress_bar -{ -void init(void); -void layout(void); -} // namespace gui::progress_bar - -namespace gui::progress_bar -{ -void reset(void); -void set_title(std::string_view title); -void set_button(std::string_view text, const progress_bar_action& action); -} // namespace gui::progress_bar +#pragma once
+
+namespace gui
+{
+using progress_bar_action = void (*)(void);
+} // namespace gui
+
+namespace gui::progress_bar
+{
+void init(void);
+void layout(void);
+} // namespace gui::progress_bar
+
+namespace gui::progress_bar
+{
+void reset(void);
+void set_title(std::string_view title);
+void set_button(std::string_view text, const progress_bar_action& action);
+} // namespace gui::progress_bar
diff --git a/game/client/gui/scoreboard.cc b/game/client/gui/scoreboard.cc index fabd753..f875ef2 100644 --- a/game/client/gui/scoreboard.cc +++ b/game/client/gui/scoreboard.cc @@ -1,103 +1,103 @@ -#include "client/pch.hh" - -#include "client/gui/scoreboard.hh" - -#include "core/io/config_map.hh" - -#include "shared/protocol.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/settings.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground; - -static config::KeyBind list_key(GLFW_KEY_TAB); - -static std::vector<std::string> usernames; -static float max_username_size; - -static void on_scoreboard_update_packet(const protocol::ScoreboardUpdate& packet) -{ - usernames = packet.names; - max_username_size = 0.0f; -} - -void gui::scoreboard::init(void) -{ - globals::client_config.add_value("scoreboard.key", list_key); - - settings::add_keybind(3, list_key, settings_location::KEYBOARD_MISC, "key.scoreboard"); - - globals::dispatcher.sink<protocol::ScoreboardUpdate>().connect<&on_scoreboard_update_packet>(); -} - -void gui::scoreboard::layout(void) -{ - if(globals::gui_screen == GUI_SCREEN_NONE && session::is_ingame() && glfwGetKey(globals::window, list_key.get_key()) == GLFW_PRESS) { - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, 0.0f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) { - ImGui::End(); - return; - } - - ImGui::PushFont(globals::font_unscii16, 8.0f); - - const auto& padding = ImGui::GetStyle().FramePadding; - const auto& spacing = ImGui::GetStyle().ItemSpacing; - auto font = globals::font_unscii8; - - // Figure out the maximum username size - for(const auto& username : usernames) { - const ImVec2 size = ImGui::CalcTextSize(username.c_str(), username.c_str() + username.size()); - - if(size.x > max_username_size) { - max_username_size = size.x; - } - } - - // Having a minimum size allows for - // generally better in-game visibility - const float true_size = math::max<float>(0.25f * window_size.x, max_username_size); - - // Figure out username rect dimensions - const float rect_start_x = 0.5f * window_size.x - 0.5f * true_size; - const float rect_start_y = 0.15f * window_size.y; - const float rect_size_x = 2.0f * padding.x + true_size; - const float rect_size_y = 2.0f * padding.y + 0.5f * ImGui::GetFontSize(); - - // const ImU32 border_col = ImGui::GetColorU32(ImGuiCol_Border, 1.00f); - const ImU32 rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, 0.80f); - const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text, 1.00f); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - // Slightly space apart individual rows - const float row_step_y = rect_size_y + 0.5f * spacing.y; - - for(std::size_t i = 0; i < usernames.size(); ++i) { - const ImVec2 rect_a = ImVec2(rect_start_x, rect_start_y + i * row_step_y); - const ImVec2 rect_b = ImVec2(rect_a.x + rect_size_x, rect_a.y + rect_size_y); - const ImVec2 text_pos = ImVec2(rect_a.x + padding.x, rect_a.y + padding.y); - - // draw_list->AddRect(rect_a, rect_b, border_col, 0.0f, ImDrawFlags_None, globals::gui_scale); - draw_list->AddRectFilled(rect_a, rect_b, rect_col, 0.0f, ImDrawFlags_None); - draw_list->AddText( - font, 0.5f * ImGui::GetFontSize(), text_pos, text_col, usernames[i].c_str(), usernames[i].c_str() + usernames[i].size()); - } - - ImGui::PopFont(); - ImGui::End(); - } -} +#include "client/pch.hh"
+
+#include "client/gui/scoreboard.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/settings.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
+ | ImGuiWindowFlags_NoBackground;
+
+static config::KeyBind list_key(GLFW_KEY_TAB);
+
+static std::vector<std::string> usernames;
+static float max_username_size;
+
+static void on_scoreboard_update_packet(const protocol::ScoreboardUpdate& packet)
+{
+ usernames = packet.names;
+ max_username_size = 0.0f;
+}
+
+void gui::scoreboard::init(void)
+{
+ globals::client_config.add_value("scoreboard.key", list_key);
+
+ settings::add_keybind(3, list_key, settings_location::KEYBOARD_MISC, "key.scoreboard");
+
+ globals::dispatcher.sink<protocol::ScoreboardUpdate>().connect<&on_scoreboard_update_packet>();
+}
+
+void gui::scoreboard::layout(void)
+{
+ if(globals::gui_screen == GUI_SCREEN_NONE && session::is_ingame() && glfwGetKey(globals::window, list_key.get_key()) == GLFW_PRESS) {
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, 0.0f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) {
+ ImGui::End();
+ return;
+ }
+
+ ImGui::PushFont(globals::font_unscii16, 8.0f);
+
+ const auto& padding = ImGui::GetStyle().FramePadding;
+ const auto& spacing = ImGui::GetStyle().ItemSpacing;
+ auto font = globals::font_unscii8;
+
+ // Figure out the maximum username size
+ for(const auto& username : usernames) {
+ const ImVec2 size = ImGui::CalcTextSize(username.c_str(), username.c_str() + username.size());
+
+ if(size.x > max_username_size) {
+ max_username_size = size.x;
+ }
+ }
+
+ // Having a minimum size allows for
+ // generally better in-game visibility
+ const float true_size = math::max<float>(0.25f * window_size.x, max_username_size);
+
+ // Figure out username rect dimensions
+ const float rect_start_x = 0.5f * window_size.x - 0.5f * true_size;
+ const float rect_start_y = 0.15f * window_size.y;
+ const float rect_size_x = 2.0f * padding.x + true_size;
+ const float rect_size_y = 2.0f * padding.y + 0.5f * ImGui::GetFontSize();
+
+ // const ImU32 border_col = ImGui::GetColorU32(ImGuiCol_Border, 1.00f);
+ const ImU32 rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, 0.80f);
+ const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text, 1.00f);
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ // Slightly space apart individual rows
+ const float row_step_y = rect_size_y + 0.5f * spacing.y;
+
+ for(std::size_t i = 0; i < usernames.size(); ++i) {
+ const ImVec2 rect_a = ImVec2(rect_start_x, rect_start_y + i * row_step_y);
+ const ImVec2 rect_b = ImVec2(rect_a.x + rect_size_x, rect_a.y + rect_size_y);
+ const ImVec2 text_pos = ImVec2(rect_a.x + padding.x, rect_a.y + padding.y);
+
+ // draw_list->AddRect(rect_a, rect_b, border_col, 0.0f, ImDrawFlags_None, globals::gui_scale);
+ draw_list->AddRectFilled(rect_a, rect_b, rect_col, 0.0f, ImDrawFlags_None);
+ draw_list->AddText(font, 0.5f * ImGui::GetFontSize(), text_pos, text_col, usernames[i].c_str(),
+ usernames[i].c_str() + usernames[i].size());
+ }
+
+ ImGui::PopFont();
+ ImGui::End();
+ }
+}
diff --git a/game/client/gui/scoreboard.hh b/game/client/gui/scoreboard.hh index 320e185..5f1c59d 100644 --- a/game/client/gui/scoreboard.hh +++ b/game/client/gui/scoreboard.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::scoreboard -{ -void init(void); -void layout(void); -} // namespace gui::scoreboard +#pragma once
+
+namespace gui::scoreboard
+{
+void init(void);
+void layout(void);
+} // namespace gui::scoreboard
diff --git a/game/client/gui/settings.cc b/game/client/gui/settings.cc index 693f0d6..db26d8c 100644 --- a/game/client/gui/settings.cc +++ b/game/client/gui/settings.cc @@ -1,1069 +1,1069 @@ -#include "client/pch.hh" - -#include "client/gui/settings.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.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/language.hh" - -#include "client/io/gamepad.hh" -#include "client/io/glfw.hh" - -#include "client/const.hh" -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static unsigned int NUM_LOCATIONS = static_cast<unsigned int>(settings_location::COUNT); - -enum class setting_type : unsigned int { - CHECKBOX = 0x0000U, ///< config::Boolean - INPUT_INT = 0x0001U, ///< config::Number<int> - INPUT_FLOAT = 0x0002U, ///< config::Number<float> - INPUT_UINT = 0x0003U, ///< config::Number<unsigned int> - INPUT_STRING = 0x0004U, ///< config::String - SLIDER_INT = 0x0005U, ///< config::Number<int> - SLIDER_FLOAT = 0x0006U, ///< config::Number<float> - SLIDER_UINT = 0x0007U, ///< config::Number<unsigned int> - STEPPER_INT = 0x0008U, ///< config::Number<int> - STEPPER_UINT = 0x0009U, ///< config::Number<unsigned int> - KEYBIND = 0x000AU, ///< config::KeyBind - GAMEPAD_AXIS = 0x000BU, ///< config::GamepadAxis - GAMEPAD_BUTTON = 0x000CU, ///< config::GamepadButton - LANGUAGE_SELECT = 0x000DU, ///< config::String internally -}; - -class SettingValue { -public: - virtual ~SettingValue(void) = default; - virtual void layout(void) const = 0; - void layout_tooltip(void) const; - void layout_label(void) const; - -public: - setting_type type; - std::string tooltip; - std::string title; - std::string name; - bool has_tooltip; - int priority; -}; - -class SettingValueWID : public SettingValue { -public: - virtual ~SettingValueWID(void) = default; - -public: - std::string wid; -}; - -class SettingValue_CheckBox final : public SettingValue { -public: - virtual ~SettingValue_CheckBox(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - config::Boolean* value; - std::string wids[2]; -}; - -class SettingValue_InputInt final : public SettingValueWID { -public: - virtual ~SettingValue_InputInt(void) = default; - virtual void layout(void) const override; - -public: - config::Int* value; -}; - -class SettingValue_InputFloat final : public SettingValueWID { -public: - virtual ~SettingValue_InputFloat(void) = default; - virtual void layout(void) const override; - -public: - std::string format; - config::Float* value; -}; - -class SettingValue_InputUnsigned final : public SettingValueWID { -public: - virtual ~SettingValue_InputUnsigned(void) = default; - virtual void layout(void) const override; - -public: - config::Unsigned* value; -}; - -class SettingValue_InputString final : public SettingValueWID { -public: - virtual ~SettingValue_InputString(void) = default; - virtual void layout(void) const override; - -public: - config::String* value; - bool allow_whitespace; -}; - -class SettingValue_SliderInt final : public SettingValueWID { -public: - virtual ~SettingValue_SliderInt(void) = default; - virtual void layout(void) const override; - -public: - config::Int* value; -}; - -class SettingValue_SliderFloat final : public SettingValueWID { -public: - virtual ~SettingValue_SliderFloat(void) = default; - virtual void layout(void) const override; - -public: - std::string format; - config::Float* value; -}; - -class SettingValue_SliderUnsigned final : public SettingValueWID { -public: - virtual ~SettingValue_SliderUnsigned(void) = default; - virtual void layout(void) const override; - -public: - config::Unsigned* value; -}; - -class SettingValue_StepperInt final : public SettingValue { -public: - virtual ~SettingValue_StepperInt(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::vector<std::string> wids; - config::Int* value; -}; - -class SettingValue_StepperUnsigned final : public SettingValue { -public: - virtual ~SettingValue_StepperUnsigned(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::vector<std::string> wids; - config::Unsigned* value; -}; - -class SettingValue_KeyBind final : public SettingValue { -public: - virtual ~SettingValue_KeyBind(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - config::KeyBind* value; -}; - -class SettingValue_GamepadAxis final : public SettingValue { -public: - virtual ~SettingValue_GamepadAxis(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - std::string wid_checkbox; - config::GamepadAxis* value; -}; - -class SettingValue_GamepadButton final : public SettingValue { -public: - virtual ~SettingValue_GamepadButton(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - config::GamepadButton* value; -}; - -class SettingValue_Language final : public SettingValueWID { -public: - virtual ~SettingValue_Language(void) = default; - virtual void layout(void) const override; -}; - -static std::string str_checkbox_false; -static std::string str_checkbox_true; - -static std::string str_tab_general; -static std::string str_tab_input; -static std::string str_tab_video; -static std::string str_tab_sound; - -static std::string str_input_keyboard; -static std::string str_input_gamepad; -static std::string str_input_mouse; - -static std::string str_keyboard_movement; -static std::string str_keyboard_gameplay; -static std::string str_keyboard_misc; - -static std::string str_gamepad_movement; -static std::string str_gamepad_gameplay; -static std::string str_gamepad_misc; - -static std::string str_gamepad_axis_prefix; -static std::string str_gamepad_button_prefix; -static std::string str_gamepad_checkbox_tooltip; - -static std::string str_video_gui; - -static std::string str_sound_levels; - -static std::vector<SettingValue*> values_all; -static std::vector<SettingValue*> values[NUM_LOCATIONS]; - -void SettingValue::layout_tooltip(void) const -{ - if(has_tooltip) { - ImGui::SameLine(); - ImGui::TextDisabled("[?]"); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(tooltip.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - } -} - -void SettingValue::layout_label(void) const -{ - ImGui::SameLine(); - ImGui::TextUnformatted(title.c_str()); -} - -void SettingValue_CheckBox::refresh_wids(void) -{ - wids[0] = std::format("{}###{}", str_checkbox_false, static_cast<void*>(value)); - wids[1] = std::format("{}###{}", str_checkbox_true, static_cast<void*>(value)); -} - -void SettingValue_CheckBox::layout(void) const -{ - const auto& wid = value->get_value() ? wids[1] : wids[0]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - value->set_value(!value->get_value()); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputInt::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::InputInt(wid.c_str(), ¤t_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputFloat::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::InputFloat(wid.c_str(), ¤t_value, 0.0f, 0.0f, format.c_str())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputUnsigned::layout(void) const -{ - auto current_value = static_cast<std::uint32_t>(value->get_value()); - - if(ImGui::InputScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputString::layout(void) const -{ - ImGuiInputTextFlags flags; - std::string current_value(value->get_value()); - - if(allow_whitespace) { - flags = ImGuiInputTextFlags_AllowTabInput; - } - else { - flags = 0; - } - - if(ImGui::InputText(wid.c_str(), ¤t_value, flags)) { - value->set(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderInt::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::SliderInt(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderFloat::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::SliderFloat(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value(), format.c_str())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderUnsigned::layout(void) const -{ - auto current_value = static_cast<std::uint32_t>(value->get_value()); - auto min_value = static_cast<std::uint32_t>(value->get_min_value()); - auto max_value = static_cast<std::uint32_t>(value->get_max_value()); - - if(ImGui::SliderScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value, &min_value, &max_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperInt::layout(void) const -{ - auto current_value = value->get_value(); - auto min_value = value->get_min_value(); - auto max_value = value->get_max_value(); - - auto current_wid = current_value - min_value; - - if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - current_value += 1; - } - - if(current_value > max_value) { - value->set_value(min_value); - } - else { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperInt::refresh_wids(void) -{ - for(std::size_t i = 0; i < wids.size(); ++i) { - auto key = std::format("settings.value.{}.{}", name, i); - wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value)); - } -} - -void SettingValue_StepperUnsigned::layout(void) const -{ - auto current_value = value->get_value(); - auto min_value = value->get_min_value(); - auto max_value = value->get_max_value(); - - auto current_wid = current_value - min_value; - - if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - current_value += 1U; - } - - if(current_value > max_value) { - value->set_value(min_value); - } - else { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperUnsigned::refresh_wids(void) -{ - for(std::size_t i = 0; i < wids.size(); ++i) { - auto key = std::format("settings.value.{}.{}", name, i); - wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value)); - } -} - -void SettingValue_KeyBind::layout(void) const -{ - const auto is_active = ((globals::gui_keybind_ptr == value) && !globals::gui_gamepad_axis_ptr && !globals::gui_gamepad_button_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_keybind_ptr = value; - } - - layout_label(); -} - -void SettingValue_KeyBind::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value)); -} - -void SettingValue_GamepadAxis::layout(void) const -{ - const auto is_active = ((globals::gui_gamepad_axis_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_button_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - auto is_inverted = value->is_inverted(); - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x, 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_gamepad_axis_ptr = value; - } - - ImGui::SameLine(); - - if(ImGui::Checkbox(wid_checkbox.c_str(), &is_inverted)) { - value->set_inverted(is_inverted); - } - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_gamepad_checkbox_tooltip.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - layout_label(); -} - -void SettingValue_GamepadAxis::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get_name(), static_cast<const void*>(value)); - wid_checkbox = std::format("###CHECKBOX_{}", static_cast<const void*>(value)); -} - -void SettingValue_GamepadButton::layout(void) const -{ - const auto is_active = ((globals::gui_gamepad_button_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_axis_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_gamepad_button_ptr = value; - } - - layout_label(); -} - -void SettingValue_GamepadButton::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value)); -} - -void SettingValue_Language::layout(void) const -{ - auto current_language = gui::language::get_current(); - - if(ImGui::BeginCombo(wid.c_str(), current_language->endonym.c_str())) { - for(auto it = gui::language::cbegin(); it != gui::language::cend(); ++it) { - if(ImGui::Selectable(it->display.c_str(), it == current_language)) { - gui::language::set(it); - continue; - } - } - - ImGui::EndCombo(); - } - - layout_label(); - layout_tooltip(); -} - -static void refresh_input_wids(void) -{ - for(SettingValue* value : values_all) { - if(value->type == setting_type::KEYBIND) { - auto keybind = static_cast<SettingValue_KeyBind*>(value); - keybind->refresh_wids(); - continue; - } - - if(value->type == setting_type::GAMEPAD_AXIS) { - auto gamepad_axis = static_cast<SettingValue_GamepadAxis*>(value); - gamepad_axis->refresh_wids(); - continue; - } - - if(value->type == setting_type::GAMEPAD_BUTTON) { - auto gamepad_button = static_cast<SettingValue_GamepadButton*>(value); - gamepad_button->refresh_wids(); - } - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.action == GLFW_PRESS) && (event.key != DEBUG_KEY)) { - if(globals::gui_keybind_ptr || globals::gui_gamepad_axis_ptr || globals::gui_gamepad_button_ptr) { - if(event.key == GLFW_KEY_ESCAPE) { - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_keybind_ptr = nullptr; - globals::gui_gamepad_axis_ptr = nullptr; - globals::gui_gamepad_button_ptr = nullptr; - - return; - } - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_keybind_ptr->set_key(event.key); - globals::gui_keybind_ptr = nullptr; - - refresh_input_wids(); - - return; - } - - if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_SETTINGS)) { - globals::gui_screen = GUI_MAIN_MENU; - return; - } - } -} - -static void on_gamepad_axis(const io::GamepadAxisEvent& event) -{ - if(globals::gui_gamepad_axis_ptr) { - auto& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_gamepad_axis_ptr->set_axis(event.axis); - globals::gui_gamepad_axis_ptr = nullptr; - - refresh_input_wids(); - - return; - } -} - -static void on_gamepad_button(const io::GamepadButtonEvent& event) -{ - if(globals::gui_gamepad_button_ptr) { - auto& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_gamepad_button_ptr->set_button(event.button); - globals::gui_gamepad_button_ptr = nullptr; - - refresh_input_wids(); - - return; - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_checkbox_false = gui::language::resolve("settings.checkbox.false"); - str_checkbox_true = gui::language::resolve("settings.checkbox.true"); - - str_tab_general = gui::language::resolve("settings.tab.general"); - str_tab_input = gui::language::resolve("settings.tab.input"); - str_tab_video = gui::language::resolve("settings.tab.video"); - str_tab_sound = gui::language::resolve("settings.tab.sound"); - - str_input_keyboard = gui::language::resolve("settings.input.keyboard"); - str_input_gamepad = gui::language::resolve("settings.input.gamepad"); - str_input_mouse = gui::language::resolve("settings.input.mouse"); - - str_keyboard_movement = gui::language::resolve("settings.keyboard.movement"); - str_keyboard_gameplay = gui::language::resolve("settings.keyboard.gameplay"); - str_keyboard_misc = gui::language::resolve("settings.keyboard.misc"); - - str_gamepad_movement = gui::language::resolve("settings.gamepad.movement"); - str_gamepad_gameplay = gui::language::resolve("settings.gamepad.gameplay"); - str_gamepad_misc = gui::language::resolve("settings.gamepad.misc"); - - str_gamepad_axis_prefix = gui::language::resolve("settings.gamepad.axis"); - str_gamepad_button_prefix = gui::language::resolve("settings.gamepad.button"); - str_gamepad_checkbox_tooltip = gui::language::resolve("settings.gamepad.checkbox_tooltip"); - - str_video_gui = gui::language::resolve("settings.video.gui"); - - str_sound_levels = gui::language::resolve("settings.sound.levels"); - - for(SettingValue* value : values_all) { - if(value->type == setting_type::CHECKBOX) { - auto checkbox = static_cast<SettingValue_CheckBox*>(value); - checkbox->refresh_wids(); - } - - if(value->type == setting_type::STEPPER_INT) { - auto stepper = static_cast<SettingValue_StepperInt*>(value); - stepper->refresh_wids(); - } - - if(value->type == setting_type::STEPPER_UINT) { - auto stepper = static_cast<SettingValue_StepperUnsigned*>(value); - stepper->refresh_wids(); - } - - value->title = gui::language::resolve(std::format("settings.value.{}", value->name).c_str()); - - if(value->has_tooltip) { - value->tooltip = gui::language::resolve(std::format("settings.tooltip.{}", value->name).c_str()); - } - } -} - -static void layout_values(settings_location location) -{ - ImGui::PushItemWidth(ImGui::CalcItemWidth() * 0.70f); - - for(const SettingValue* value : values[static_cast<unsigned int>(location)]) { - value->layout(); - } - - ImGui::PopItemWidth(); -} - -static void layout_general(void) -{ - if(ImGui::BeginChild("###settings.general.child")) { - layout_values(settings_location::GENERAL); - } - - ImGui::EndChild(); -} - -static void layout_input_keyboard(void) -{ - if(ImGui::BeginChild("###settings.input.keyboard.child")) { - ImGui::SeparatorText(str_keyboard_movement.c_str()); - layout_values(settings_location::KEYBOARD_MOVEMENT); - ImGui::SeparatorText(str_keyboard_gameplay.c_str()); - layout_values(settings_location::KEYBOARD_GAMEPLAY); - ImGui::SeparatorText(str_keyboard_misc.c_str()); - layout_values(settings_location::KEYBOARD_MISC); - } - - ImGui::EndChild(); -} - -static void layout_input_gamepad(void) -{ - if(ImGui::BeginChild("###settings.input.gamepad.child")) { - layout_values(settings_location::GAMEPAD); - ImGui::SeparatorText(str_gamepad_movement.c_str()); - layout_values(settings_location::GAMEPAD_MOVEMENT); - ImGui::SeparatorText(str_gamepad_gameplay.c_str()); - layout_values(settings_location::GAMEPAD_GAMEPLAY); - ImGui::SeparatorText(str_gamepad_misc.c_str()); - layout_values(settings_location::GAMEPAD_MISC); - } - - ImGui::EndChild(); -} - -static void layout_input_mouse(void) -{ - if(ImGui::BeginChild("###settings.input.mouse.child")) { - layout_values(settings_location::MOUSE); - } - - ImGui::EndChild(); -} - -static void layout_input(void) -{ - if(ImGui::BeginTabBar("###settings.input.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::BeginTabItem(str_input_keyboard.c_str())) { - layout_input_keyboard(); - ImGui::EndTabItem(); - } - - if(io::gamepad::available) { - if(ImGui::BeginTabItem(str_input_gamepad.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_input_gamepad(); - ImGui::EndTabItem(); - } - } - - if(ImGui::BeginTabItem(str_input_mouse.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_input_mouse(); - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } -} - -static void layout_video(void) -{ - if(ImGui::BeginChild("###settings.video.child")) { - layout_values(settings_location::VIDEO); - ImGui::SeparatorText(str_video_gui.c_str()); - layout_values(settings_location::VIDEO_GUI); - } - - ImGui::EndChild(); -} - -static void layout_sound(void) -{ - if(ImGui::BeginChild("###settings.sound.child")) { - layout_values(settings_location::SOUND); - ImGui::SeparatorText(str_sound_levels.c_str()); - layout_values(settings_location::SOUND_LEVELS); - } - - ImGui::EndChild(); -} - -void settings::init(void) -{ - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GamepadAxisEvent>().connect<&on_gamepad_axis>(); - globals::dispatcher.sink<io::GamepadButtonEvent>().connect<&on_gamepad_button>(); - globals::dispatcher.sink<gui::LanguageSetEvent>().connect<&on_language_set>(); -} - -void settings::init_late(void) -{ - for(std::size_t i = 0; i < NUM_LOCATIONS; ++i) { - std::sort(values[i].begin(), values[i].end(), [](const SettingValue* a, const SettingValue* b) { - return a->priority < b->priority; - }); - } - - refresh_input_wids(); -} - -void settings::shutdown(void) -{ - for(const SettingValue* value : values_all) - delete value; - for(std::size_t i = 0; i < NUM_LOCATIONS; values[i++].clear()) - ; - values_all.clear(); -} - -void settings::layout(void) -{ - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const ImVec2 window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f); - const ImVec2 window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###settings", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale)); - - if(ImGui::BeginTabBar("###settings.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::TabItemButton("<<")) { - globals::gui_screen = GUI_MAIN_MENU; - globals::gui_keybind_ptr = nullptr; - } - - if(ImGui::BeginTabItem(str_tab_general.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_general(); - ImGui::EndTabItem(); - } - - if(ImGui::BeginTabItem(str_tab_input.c_str())) { - layout_input(); - ImGui::EndTabItem(); - } - - if(ImGui::BeginTabItem(str_tab_video.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_video(); - ImGui::EndTabItem(); - } - - if(globals::sound_ctx && globals::sound_dev) { - if(ImGui::BeginTabItem(str_tab_sound.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_sound(); - ImGui::EndTabItem(); - } - } - - ImGui::EndTabBar(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void settings::add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_CheckBox; - setting_value->type = setting_type::CHECKBOX; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_InputInt; - setting_value->type = setting_type::INPUT_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input( - int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, std::string_view fmt) -{ - auto setting_value = new SettingValue_InputFloat; - setting_value->type = setting_type::INPUT_FLOAT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->format = fmt; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_InputUnsigned; - setting_value->type = setting_type::INPUT_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input( - int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace) -{ - auto setting_value = new SettingValue_InputString; - setting_value->type = setting_type::INPUT_STRING; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->allow_whitespace = allow_whitespace; - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_SliderInt; - setting_value->type = setting_type::SLIDER_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider( - int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, std::string_view fmt) -{ - auto setting_value = new SettingValue_SliderFloat; - setting_value->type = setting_type::SLIDER_FLOAT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->format = fmt; - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_SliderUnsigned; - setting_value->type = setting_type::SLIDER_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_StepperInt; - setting_value->type = setting_type::STEPPER_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1); - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_StepperUnsigned; - setting_value->type = setting_type::STEPPER_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1); - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_KeyBind; - setting_value->type = setting_type::KEYBIND; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_GamepadAxis; - setting_value->type = setting_type::GAMEPAD_AXIS; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_GamepadButton; - setting_value->type = setting_type::GAMEPAD_BUTTON; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_language_select(int priority, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_Language; - setting_value->type = setting_type::LANGUAGE_SELECT; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} +#include "client/pch.hh"
+
+#include "client/gui/settings.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.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/language.hh"
+
+#include "client/io/gamepad.hh"
+#include "client/io/glfw.hh"
+
+#include "client/const.hh"
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static unsigned int NUM_LOCATIONS = static_cast<unsigned int>(settings_location::COUNT);
+
+enum class setting_type : unsigned int {
+ CHECKBOX = 0x0000U, ///< config::Boolean
+ INPUT_INT = 0x0001U, ///< config::Number<int>
+ INPUT_FLOAT = 0x0002U, ///< config::Number<float>
+ INPUT_UINT = 0x0003U, ///< config::Number<unsigned int>
+ INPUT_STRING = 0x0004U, ///< config::String
+ SLIDER_INT = 0x0005U, ///< config::Number<int>
+ SLIDER_FLOAT = 0x0006U, ///< config::Number<float>
+ SLIDER_UINT = 0x0007U, ///< config::Number<unsigned int>
+ STEPPER_INT = 0x0008U, ///< config::Number<int>
+ STEPPER_UINT = 0x0009U, ///< config::Number<unsigned int>
+ KEYBIND = 0x000AU, ///< config::KeyBind
+ GAMEPAD_AXIS = 0x000BU, ///< config::GamepadAxis
+ GAMEPAD_BUTTON = 0x000CU, ///< config::GamepadButton
+ LANGUAGE_SELECT = 0x000DU, ///< config::String internally
+};
+
+class SettingValue {
+public:
+ virtual ~SettingValue(void) = default;
+ virtual void layout(void) const = 0;
+ void layout_tooltip(void) const;
+ void layout_label(void) const;
+
+public:
+ setting_type type;
+ std::string tooltip;
+ std::string title;
+ std::string name;
+ bool has_tooltip;
+ int priority;
+};
+
+class SettingValueWID : public SettingValue {
+public:
+ virtual ~SettingValueWID(void) = default;
+
+public:
+ std::string wid;
+};
+
+class SettingValue_CheckBox final : public SettingValue {
+public:
+ virtual ~SettingValue_CheckBox(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ config::Boolean* value;
+ std::string wids[2];
+};
+
+class SettingValue_InputInt final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputInt(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Int* value;
+};
+
+class SettingValue_InputFloat final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputFloat(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ std::string format;
+ config::Float* value;
+};
+
+class SettingValue_InputUnsigned final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputUnsigned(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Unsigned* value;
+};
+
+class SettingValue_InputString final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputString(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::String* value;
+ bool allow_whitespace;
+};
+
+class SettingValue_SliderInt final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderInt(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Int* value;
+};
+
+class SettingValue_SliderFloat final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderFloat(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ std::string format;
+ config::Float* value;
+};
+
+class SettingValue_SliderUnsigned final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderUnsigned(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Unsigned* value;
+};
+
+class SettingValue_StepperInt final : public SettingValue {
+public:
+ virtual ~SettingValue_StepperInt(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::vector<std::string> wids;
+ config::Int* value;
+};
+
+class SettingValue_StepperUnsigned final : public SettingValue {
+public:
+ virtual ~SettingValue_StepperUnsigned(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::vector<std::string> wids;
+ config::Unsigned* value;
+};
+
+class SettingValue_KeyBind final : public SettingValue {
+public:
+ virtual ~SettingValue_KeyBind(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ config::KeyBind* value;
+};
+
+class SettingValue_GamepadAxis final : public SettingValue {
+public:
+ virtual ~SettingValue_GamepadAxis(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ std::string wid_checkbox;
+ config::GamepadAxis* value;
+};
+
+class SettingValue_GamepadButton final : public SettingValue {
+public:
+ virtual ~SettingValue_GamepadButton(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ config::GamepadButton* value;
+};
+
+class SettingValue_Language final : public SettingValueWID {
+public:
+ virtual ~SettingValue_Language(void) = default;
+ virtual void layout(void) const override;
+};
+
+static std::string str_checkbox_false;
+static std::string str_checkbox_true;
+
+static std::string str_tab_general;
+static std::string str_tab_input;
+static std::string str_tab_video;
+static std::string str_tab_sound;
+
+static std::string str_input_keyboard;
+static std::string str_input_gamepad;
+static std::string str_input_mouse;
+
+static std::string str_keyboard_movement;
+static std::string str_keyboard_gameplay;
+static std::string str_keyboard_misc;
+
+static std::string str_gamepad_movement;
+static std::string str_gamepad_gameplay;
+static std::string str_gamepad_misc;
+
+static std::string str_gamepad_axis_prefix;
+static std::string str_gamepad_button_prefix;
+static std::string str_gamepad_checkbox_tooltip;
+
+static std::string str_video_gui;
+
+static std::string str_sound_levels;
+
+static std::vector<SettingValue*> values_all;
+static std::vector<SettingValue*> values[NUM_LOCATIONS];
+
+void SettingValue::layout_tooltip(void) const
+{
+ if(has_tooltip) {
+ ImGui::SameLine();
+ ImGui::TextDisabled("[?]");
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(tooltip.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ }
+}
+
+void SettingValue::layout_label(void) const
+{
+ ImGui::SameLine();
+ ImGui::TextUnformatted(title.c_str());
+}
+
+void SettingValue_CheckBox::refresh_wids(void)
+{
+ wids[0] = std::format("{}###{}", str_checkbox_false, static_cast<void*>(value));
+ wids[1] = std::format("{}###{}", str_checkbox_true, static_cast<void*>(value));
+}
+
+void SettingValue_CheckBox::layout(void) const
+{
+ const auto& wid = value->get_value() ? wids[1] : wids[0];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ value->set_value(!value->get_value());
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputInt::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::InputInt(wid.c_str(), ¤t_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputFloat::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::InputFloat(wid.c_str(), ¤t_value, 0.0f, 0.0f, format.c_str())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputUnsigned::layout(void) const
+{
+ auto current_value = static_cast<std::uint32_t>(value->get_value());
+
+ if(ImGui::InputScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputString::layout(void) const
+{
+ ImGuiInputTextFlags flags;
+ std::string current_value(value->get_value());
+
+ if(allow_whitespace) {
+ flags = ImGuiInputTextFlags_AllowTabInput;
+ }
+ else {
+ flags = 0;
+ }
+
+ if(ImGui::InputText(wid.c_str(), ¤t_value, flags)) {
+ value->set(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderInt::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::SliderInt(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderFloat::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::SliderFloat(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value(), format.c_str())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderUnsigned::layout(void) const
+{
+ auto current_value = static_cast<std::uint32_t>(value->get_value());
+ auto min_value = static_cast<std::uint32_t>(value->get_min_value());
+ auto max_value = static_cast<std::uint32_t>(value->get_max_value());
+
+ if(ImGui::SliderScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value, &min_value, &max_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperInt::layout(void) const
+{
+ auto current_value = value->get_value();
+ auto min_value = value->get_min_value();
+ auto max_value = value->get_max_value();
+
+ auto current_wid = current_value - min_value;
+
+ if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ current_value += 1;
+ }
+
+ if(current_value > max_value) {
+ value->set_value(min_value);
+ }
+ else {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperInt::refresh_wids(void)
+{
+ for(std::size_t i = 0; i < wids.size(); ++i) {
+ auto key = std::format("settings.value.{}.{}", name, i);
+ wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value));
+ }
+}
+
+void SettingValue_StepperUnsigned::layout(void) const
+{
+ auto current_value = value->get_value();
+ auto min_value = value->get_min_value();
+ auto max_value = value->get_max_value();
+
+ auto current_wid = current_value - min_value;
+
+ if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ current_value += 1U;
+ }
+
+ if(current_value > max_value) {
+ value->set_value(min_value);
+ }
+ else {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperUnsigned::refresh_wids(void)
+{
+ for(std::size_t i = 0; i < wids.size(); ++i) {
+ auto key = std::format("settings.value.{}.{}", name, i);
+ wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value));
+ }
+}
+
+void SettingValue_KeyBind::layout(void) const
+{
+ const auto is_active = ((globals::gui_keybind_ptr == value) && !globals::gui_gamepad_axis_ptr && !globals::gui_gamepad_button_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_keybind_ptr = value;
+ }
+
+ layout_label();
+}
+
+void SettingValue_KeyBind::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value));
+}
+
+void SettingValue_GamepadAxis::layout(void) const
+{
+ const auto is_active = ((globals::gui_gamepad_axis_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_button_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+ auto is_inverted = value->is_inverted();
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x, 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_gamepad_axis_ptr = value;
+ }
+
+ ImGui::SameLine();
+
+ if(ImGui::Checkbox(wid_checkbox.c_str(), &is_inverted)) {
+ value->set_inverted(is_inverted);
+ }
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_gamepad_checkbox_tooltip.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ layout_label();
+}
+
+void SettingValue_GamepadAxis::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get_name(), static_cast<const void*>(value));
+ wid_checkbox = std::format("###CHECKBOX_{}", static_cast<const void*>(value));
+}
+
+void SettingValue_GamepadButton::layout(void) const
+{
+ const auto is_active = ((globals::gui_gamepad_button_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_axis_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_gamepad_button_ptr = value;
+ }
+
+ layout_label();
+}
+
+void SettingValue_GamepadButton::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value));
+}
+
+void SettingValue_Language::layout(void) const
+{
+ auto current_language = gui::language::get_current();
+
+ if(ImGui::BeginCombo(wid.c_str(), current_language->endonym.c_str())) {
+ for(auto it = gui::language::cbegin(); it != gui::language::cend(); ++it) {
+ if(ImGui::Selectable(it->display.c_str(), it == current_language)) {
+ gui::language::set(it);
+ continue;
+ }
+ }
+
+ ImGui::EndCombo();
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+static void refresh_input_wids(void)
+{
+ for(SettingValue* value : values_all) {
+ if(value->type == setting_type::KEYBIND) {
+ auto keybind = static_cast<SettingValue_KeyBind*>(value);
+ keybind->refresh_wids();
+ continue;
+ }
+
+ if(value->type == setting_type::GAMEPAD_AXIS) {
+ auto gamepad_axis = static_cast<SettingValue_GamepadAxis*>(value);
+ gamepad_axis->refresh_wids();
+ continue;
+ }
+
+ if(value->type == setting_type::GAMEPAD_BUTTON) {
+ auto gamepad_button = static_cast<SettingValue_GamepadButton*>(value);
+ gamepad_button->refresh_wids();
+ }
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.action == GLFW_PRESS) && (event.key != DEBUG_KEY)) {
+ if(globals::gui_keybind_ptr || globals::gui_gamepad_axis_ptr || globals::gui_gamepad_button_ptr) {
+ if(event.key == GLFW_KEY_ESCAPE) {
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_keybind_ptr = nullptr;
+ globals::gui_gamepad_axis_ptr = nullptr;
+ globals::gui_gamepad_button_ptr = nullptr;
+
+ return;
+ }
+
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_keybind_ptr->set_key(event.key);
+ globals::gui_keybind_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+
+ if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_SETTINGS)) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ return;
+ }
+ }
+}
+
+static void on_gamepad_axis(const io::GamepadAxisEvent& event)
+{
+ if(globals::gui_gamepad_axis_ptr) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_gamepad_axis_ptr->set_axis(event.axis);
+ globals::gui_gamepad_axis_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+}
+
+static void on_gamepad_button(const io::GamepadButtonEvent& event)
+{
+ if(globals::gui_gamepad_button_ptr) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_gamepad_button_ptr->set_button(event.button);
+ globals::gui_gamepad_button_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_checkbox_false = gui::language::resolve("settings.checkbox.false");
+ str_checkbox_true = gui::language::resolve("settings.checkbox.true");
+
+ str_tab_general = gui::language::resolve("settings.tab.general");
+ str_tab_input = gui::language::resolve("settings.tab.input");
+ str_tab_video = gui::language::resolve("settings.tab.video");
+ str_tab_sound = gui::language::resolve("settings.tab.sound");
+
+ str_input_keyboard = gui::language::resolve("settings.input.keyboard");
+ str_input_gamepad = gui::language::resolve("settings.input.gamepad");
+ str_input_mouse = gui::language::resolve("settings.input.mouse");
+
+ str_keyboard_movement = gui::language::resolve("settings.keyboard.movement");
+ str_keyboard_gameplay = gui::language::resolve("settings.keyboard.gameplay");
+ str_keyboard_misc = gui::language::resolve("settings.keyboard.misc");
+
+ str_gamepad_movement = gui::language::resolve("settings.gamepad.movement");
+ str_gamepad_gameplay = gui::language::resolve("settings.gamepad.gameplay");
+ str_gamepad_misc = gui::language::resolve("settings.gamepad.misc");
+
+ str_gamepad_axis_prefix = gui::language::resolve("settings.gamepad.axis");
+ str_gamepad_button_prefix = gui::language::resolve("settings.gamepad.button");
+ str_gamepad_checkbox_tooltip = gui::language::resolve("settings.gamepad.checkbox_tooltip");
+
+ str_video_gui = gui::language::resolve("settings.video.gui");
+
+ str_sound_levels = gui::language::resolve("settings.sound.levels");
+
+ for(SettingValue* value : values_all) {
+ if(value->type == setting_type::CHECKBOX) {
+ auto checkbox = static_cast<SettingValue_CheckBox*>(value);
+ checkbox->refresh_wids();
+ }
+
+ if(value->type == setting_type::STEPPER_INT) {
+ auto stepper = static_cast<SettingValue_StepperInt*>(value);
+ stepper->refresh_wids();
+ }
+
+ if(value->type == setting_type::STEPPER_UINT) {
+ auto stepper = static_cast<SettingValue_StepperUnsigned*>(value);
+ stepper->refresh_wids();
+ }
+
+ value->title = gui::language::resolve(std::format("settings.value.{}", value->name).c_str());
+
+ if(value->has_tooltip) {
+ value->tooltip = gui::language::resolve(std::format("settings.tooltip.{}", value->name).c_str());
+ }
+ }
+}
+
+static void layout_values(settings_location location)
+{
+ ImGui::PushItemWidth(ImGui::CalcItemWidth() * 0.70f);
+
+ for(const SettingValue* value : values[static_cast<unsigned int>(location)]) {
+ value->layout();
+ }
+
+ ImGui::PopItemWidth();
+}
+
+static void layout_general(void)
+{
+ if(ImGui::BeginChild("###settings.general.child")) {
+ layout_values(settings_location::GENERAL);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_keyboard(void)
+{
+ if(ImGui::BeginChild("###settings.input.keyboard.child")) {
+ ImGui::SeparatorText(str_keyboard_movement.c_str());
+ layout_values(settings_location::KEYBOARD_MOVEMENT);
+ ImGui::SeparatorText(str_keyboard_gameplay.c_str());
+ layout_values(settings_location::KEYBOARD_GAMEPLAY);
+ ImGui::SeparatorText(str_keyboard_misc.c_str());
+ layout_values(settings_location::KEYBOARD_MISC);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_gamepad(void)
+{
+ if(ImGui::BeginChild("###settings.input.gamepad.child")) {
+ layout_values(settings_location::GAMEPAD);
+ ImGui::SeparatorText(str_gamepad_movement.c_str());
+ layout_values(settings_location::GAMEPAD_MOVEMENT);
+ ImGui::SeparatorText(str_gamepad_gameplay.c_str());
+ layout_values(settings_location::GAMEPAD_GAMEPLAY);
+ ImGui::SeparatorText(str_gamepad_misc.c_str());
+ layout_values(settings_location::GAMEPAD_MISC);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_mouse(void)
+{
+ if(ImGui::BeginChild("###settings.input.mouse.child")) {
+ layout_values(settings_location::MOUSE);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input(void)
+{
+ if(ImGui::BeginTabBar("###settings.input.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::BeginTabItem(str_input_keyboard.c_str())) {
+ layout_input_keyboard();
+ ImGui::EndTabItem();
+ }
+
+ if(io::gamepad::available) {
+ if(ImGui::BeginTabItem(str_input_gamepad.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_input_gamepad();
+ ImGui::EndTabItem();
+ }
+ }
+
+ if(ImGui::BeginTabItem(str_input_mouse.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_input_mouse();
+ ImGui::EndTabItem();
+ }
+
+ ImGui::EndTabBar();
+ }
+}
+
+static void layout_video(void)
+{
+ if(ImGui::BeginChild("###settings.video.child")) {
+ layout_values(settings_location::VIDEO);
+ ImGui::SeparatorText(str_video_gui.c_str());
+ layout_values(settings_location::VIDEO_GUI);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_sound(void)
+{
+ if(ImGui::BeginChild("###settings.sound.child")) {
+ layout_values(settings_location::SOUND);
+ ImGui::SeparatorText(str_sound_levels.c_str());
+ layout_values(settings_location::SOUND_LEVELS);
+ }
+
+ ImGui::EndChild();
+}
+
+void settings::init(void)
+{
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GamepadAxisEvent>().connect<&on_gamepad_axis>();
+ globals::dispatcher.sink<io::GamepadButtonEvent>().connect<&on_gamepad_button>();
+ globals::dispatcher.sink<gui::LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void settings::init_late(void)
+{
+ for(std::size_t i = 0; i < NUM_LOCATIONS; ++i) {
+ std::sort(values[i].begin(), values[i].end(), [](const SettingValue* a, const SettingValue* b) {
+ return a->priority < b->priority;
+ });
+ }
+
+ refresh_input_wids();
+}
+
+void settings::shutdown(void)
+{
+ for(const SettingValue* value : values_all)
+ delete value;
+ for(std::size_t i = 0; i < NUM_LOCATIONS; values[i++].clear())
+ ;
+ values_all.clear();
+}
+
+void settings::layout(void)
+{
+ const ImGuiViewport* viewport = ImGui::GetMainViewport();
+ const ImVec2 window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f);
+ const ImVec2 window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###settings", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale));
+
+ if(ImGui::BeginTabBar("###settings.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::TabItemButton("<<")) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ globals::gui_keybind_ptr = nullptr;
+ }
+
+ if(ImGui::BeginTabItem(str_tab_general.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_general();
+ ImGui::EndTabItem();
+ }
+
+ if(ImGui::BeginTabItem(str_tab_input.c_str())) {
+ layout_input();
+ ImGui::EndTabItem();
+ }
+
+ if(ImGui::BeginTabItem(str_tab_video.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_video();
+ ImGui::EndTabItem();
+ }
+
+ if(globals::sound_ctx && globals::sound_dev) {
+ if(ImGui::BeginTabItem(str_tab_sound.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_sound();
+ ImGui::EndTabItem();
+ }
+ }
+
+ ImGui::EndTabBar();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void settings::add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_CheckBox;
+ setting_value->type = setting_type::CHECKBOX;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_InputInt;
+ setting_value->type = setting_type::INPUT_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt)
+{
+ auto setting_value = new SettingValue_InputFloat;
+ setting_value->type = setting_type::INPUT_FLOAT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->format = fmt;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_InputUnsigned;
+ setting_value->type = setting_type::INPUT_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip,
+ bool allow_whitespace)
+{
+ auto setting_value = new SettingValue_InputString;
+ setting_value->type = setting_type::INPUT_STRING;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->allow_whitespace = allow_whitespace;
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_SliderInt;
+ setting_value->type = setting_type::SLIDER_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt)
+{
+ auto setting_value = new SettingValue_SliderFloat;
+ setting_value->type = setting_type::SLIDER_FLOAT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->format = fmt;
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_SliderUnsigned;
+ setting_value->type = setting_type::SLIDER_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_StepperInt;
+ setting_value->type = setting_type::STEPPER_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1);
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_StepperUnsigned;
+ setting_value->type = setting_type::STEPPER_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1);
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_KeyBind;
+ setting_value->type = setting_type::KEYBIND;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_GamepadAxis;
+ setting_value->type = setting_type::GAMEPAD_AXIS;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_GamepadButton;
+ setting_value->type = setting_type::GAMEPAD_BUTTON;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_language_select(int priority, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_Language;
+ setting_value->type = setting_type::LANGUAGE_SELECT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
diff --git a/game/client/gui/settings.hh b/game/client/gui/settings.hh index efb8ca4..472a61d 100644 --- a/game/client/gui/settings.hh +++ b/game/client/gui/settings.hh @@ -1,90 +1,90 @@ -#pragma once - -namespace config -{ -class Boolean; -class String; -} // namespace config - -namespace config -{ -class Int; -class Float; -class Unsigned; -} // namespace config - -namespace config -{ -class KeyBind; -class GamepadAxis; -class GamepadButton; -} // namespace config - -enum class settings_location : unsigned int { - GENERAL = 0x0000U, - KEYBOARD_MOVEMENT = 0x0001U, - KEYBOARD_GAMEPLAY = 0x0002U, - KEYBOARD_MISC = 0x0003U, - GAMEPAD = 0x0004U, - GAMEPAD_MOVEMENT = 0x0005U, - GAMEPAD_GAMEPLAY = 0x0006U, - GAMEPAD_MISC = 0x0007U, - MOUSE = 0x0008U, - VIDEO = 0x0009U, - VIDEO_GUI = 0x000AU, - SOUND = 0x000BU, - SOUND_LEVELS = 0x000CU, - COUNT = 0x000DU, -}; - -namespace settings -{ -void init(void); -void init_late(void); -void shutdown(void); -void layout(void); -} // namespace settings - -namespace settings -{ -void add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, - std::string_view fmt = "%.3f"); -void add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -void add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace); -} // namespace settings - -namespace settings -{ -void add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, - std::string_view format = "%.3f"); -void add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name); -} // namespace settings - -namespace settings -{ -void add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name); -void add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name); -} // namespace settings - -namespace settings -{ -void add_language_select(int priority, settings_location location, std::string_view name); -} // namespace settings +#pragma once
+
+namespace config
+{
+class Boolean;
+class String;
+} // namespace config
+
+namespace config
+{
+class Int;
+class Float;
+class Unsigned;
+} // namespace config
+
+namespace config
+{
+class KeyBind;
+class GamepadAxis;
+class GamepadButton;
+} // namespace config
+
+enum class settings_location : unsigned int {
+ GENERAL = 0x0000U,
+ KEYBOARD_MOVEMENT = 0x0001U,
+ KEYBOARD_GAMEPLAY = 0x0002U,
+ KEYBOARD_MISC = 0x0003U,
+ GAMEPAD = 0x0004U,
+ GAMEPAD_MOVEMENT = 0x0005U,
+ GAMEPAD_GAMEPLAY = 0x0006U,
+ GAMEPAD_MISC = 0x0007U,
+ MOUSE = 0x0008U,
+ VIDEO = 0x0009U,
+ VIDEO_GUI = 0x000AU,
+ SOUND = 0x000BU,
+ SOUND_LEVELS = 0x000CU,
+ COUNT = 0x000DU,
+};
+
+namespace settings
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void layout(void);
+} // namespace settings
+
+namespace settings
+{
+void add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt = "%.3f");
+void add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+void add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace);
+} // namespace settings
+
+namespace settings
+{
+void add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view format = "%.3f");
+void add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name);
+} // namespace settings
+
+namespace settings
+{
+void add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name);
+void add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name);
+} // namespace settings
+
+namespace settings
+{
+void add_language_select(int priority, settings_location location, std::string_view name);
+} // namespace settings
diff --git a/game/client/gui/splash.cc b/game/client/gui/splash.cc index 440df49..9990103 100644 --- a/game/client/gui/splash.cc +++ b/game/client/gui/splash.cc @@ -1,178 +1,178 @@ -#include "client/pch.hh" - -#include "client/gui/splash.hh" - -#include "core/io/cmdline.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/epoch.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -constexpr static int SPLASH_COUNT = 4; -constexpr static std::size_t DELAY_MICROSECONDS = 2000000; -constexpr static std::string_view SPLASH_PATH = "textures/gui/client_splash.png"; - -static resource_ptr<TextureGUI> texture; -static float texture_aspect; -static float texture_alpha; - -static std::uint64_t end_time; -static std::string current_text; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - end_time = UINT64_C(0); -} - -static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event) -{ - end_time = UINT64_C(0); -} - -static void on_glfw_scroll(const io::GlfwScrollEvent& event) -{ - end_time = UINT64_C(0); -} - -void gui::client_splash::init(void) -{ - if(io::cmdline::contains("nosplash")) { - texture = nullptr; - texture_aspect = 0.0f; - texture_alpha = 0.0f; - return; - } - - std::random_device randev; - std::uniform_int_distribution<int> dist(0, SPLASH_COUNT - 1); - - texture = resource::load<TextureGUI>(SPLASH_PATH, TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - texture_aspect = 0.0f; - texture_alpha = 0.0f; - - if(texture) { - if(texture->size.x > texture->size.y) { - texture_aspect = static_cast<float>(texture->size.x) / static_cast<float>(texture->size.y); - } - else { - texture_aspect = static_cast<float>(texture->size.y) / static_cast<float>(texture->size.x); - } - - texture_alpha = 1.0f; - } -} - -void gui::client_splash::init_late(void) -{ - if(!texture) { - // We don't have to waste time - // rendering the missing client_splash texture - return; - } - - end_time = utils::unix_microseconds() + DELAY_MICROSECONDS; - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>(); - - current_text = gui::language::resolve("splash.skip_prompt"); - - while(!glfwWindowShouldClose(globals::window)) { - const std::uint64_t curtime = utils::unix_microseconds(); - const std::uint64_t remains = end_time - curtime; - - if(curtime >= end_time) { - break; - } - - texture_alpha = math::smoothstep(0.25f, 0.6f, static_cast<float>(remains) / static_cast<float>(DELAY_MICROSECONDS)); - - gui::client_splash::render(); - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().disconnect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwMouseButtonEvent>().disconnect<&on_glfw_mouse_button>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().disconnect<&on_glfw_scroll>(); - - texture = nullptr; - texture_aspect = 0.0f; - texture_alpha = 0.0f; - end_time = UINT64_C(0); -} - -void gui::client_splash::render(void) -{ - if(!texture) { - // We don't have to waste time - // rendering the missing client_splash texture - return; - } - - // The client_splash is rendered outside the main - // render loop, so we have to manually begin - // and render both window and ImGui frames - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - glDisable(GL_DEPTH_TEST); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, globals::width, globals::height); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.0f, 0.0f); - auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###client_splash", nullptr, WINDOW_FLAGS)) { - const float image_width = 0.60f * viewport->Size.x; - const float image_height = image_width / texture_aspect; - const ImVec2 image_size = ImVec2(image_width, image_height); - - const float image_x = 0.5f * (viewport->Size.x - image_width); - const float image_y = 0.5f * (viewport->Size.y - image_height); - const ImVec2 image_pos = ImVec2(image_x, image_y); - - if(!current_text.empty()) { - ImGui::PushFont(globals::font_unscii8, 16.0f); - ImGui::SetCursorPos(ImVec2(16.0f, 16.0f)); - ImGui::TextDisabled("%s", current_text.c_str()); - ImGui::PopFont(); - } - - const ImVec2 uv_a = ImVec2(0.0f, 0.0f); - const ImVec2 uv_b = ImVec2(1.0f, 1.0f); - const ImVec4 tint = ImVec4(1.0f, 1.0f, 1.0f, texture_alpha); - - ImGui::SetCursorPos(image_pos); - ImGui::ImageWithBg(texture->handle, image_size, uv_a, uv_b, ImVec4(0.0f, 0.0f, 0.0f, 0.0f), tint); - } - - ImGui::End(); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(globals::window); - - glfwPollEvents(); -} +#include "client/pch.hh"
+
+#include "client/gui/splash.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+constexpr static int SPLASH_COUNT = 4;
+constexpr static std::size_t DELAY_MICROSECONDS = 2000000;
+constexpr static std::string_view SPLASH_PATH = "textures/gui/client_splash.png";
+
+static resource_ptr<TextureGUI> texture;
+static float texture_aspect;
+static float texture_alpha;
+
+static std::uint64_t end_time;
+static std::string current_text;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+static void on_glfw_scroll(const io::GlfwScrollEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+void gui::client_splash::init(void)
+{
+ if(io::cmdline::contains("nosplash")) {
+ texture = nullptr;
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+ return;
+ }
+
+ std::random_device randev;
+ std::uniform_int_distribution<int> dist(0, SPLASH_COUNT - 1);
+
+ texture = resource::load<TextureGUI>(SPLASH_PATH, TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+
+ if(texture) {
+ if(texture->size.x > texture->size.y) {
+ texture_aspect = static_cast<float>(texture->size.x) / static_cast<float>(texture->size.y);
+ }
+ else {
+ texture_aspect = static_cast<float>(texture->size.y) / static_cast<float>(texture->size.x);
+ }
+
+ texture_alpha = 1.0f;
+ }
+}
+
+void gui::client_splash::init_late(void)
+{
+ if(!texture) {
+ // We don't have to waste time
+ // rendering the missing client_splash texture
+ return;
+ }
+
+ end_time = utils::unix_microseconds() + DELAY_MICROSECONDS;
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>();
+
+ current_text = gui::language::resolve("splash.skip_prompt");
+
+ while(!glfwWindowShouldClose(globals::window)) {
+ const std::uint64_t curtime = utils::unix_microseconds();
+ const std::uint64_t remains = end_time - curtime;
+
+ if(curtime >= end_time) {
+ break;
+ }
+
+ texture_alpha = math::smoothstep(0.25f, 0.6f, static_cast<float>(remains) / static_cast<float>(DELAY_MICROSECONDS));
+
+ gui::client_splash::render();
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().disconnect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwMouseButtonEvent>().disconnect<&on_glfw_mouse_button>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().disconnect<&on_glfw_scroll>();
+
+ texture = nullptr;
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+ end_time = UINT64_C(0);
+}
+
+void gui::client_splash::render(void)
+{
+ if(!texture) {
+ // We don't have to waste time
+ // rendering the missing client_splash texture
+ return;
+ }
+
+ // The client_splash is rendered outside the main
+ // render loop, so we have to manually begin
+ // and render both window and ImGui frames
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ glDisable(GL_DEPTH_TEST);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glViewport(0, 0, globals::width, globals::height);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.0f, 0.0f);
+ auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###client_splash", nullptr, WINDOW_FLAGS)) {
+ const float image_width = 0.60f * viewport->Size.x;
+ const float image_height = image_width / texture_aspect;
+ const ImVec2 image_size = ImVec2(image_width, image_height);
+
+ const float image_x = 0.5f * (viewport->Size.x - image_width);
+ const float image_y = 0.5f * (viewport->Size.y - image_height);
+ const ImVec2 image_pos = ImVec2(image_x, image_y);
+
+ if(!current_text.empty()) {
+ ImGui::PushFont(globals::font_unscii8, 16.0f);
+ ImGui::SetCursorPos(ImVec2(16.0f, 16.0f));
+ ImGui::TextDisabled("%s", current_text.c_str());
+ ImGui::PopFont();
+ }
+
+ const ImVec2 uv_a = ImVec2(0.0f, 0.0f);
+ const ImVec2 uv_b = ImVec2(1.0f, 1.0f);
+ const ImVec4 tint = ImVec4(1.0f, 1.0f, 1.0f, texture_alpha);
+
+ ImGui::SetCursorPos(image_pos);
+ ImGui::ImageWithBg(texture->handle, image_size, uv_a, uv_b, ImVec4(0.0f, 0.0f, 0.0f, 0.0f), tint);
+ }
+
+ ImGui::End();
+
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ glfwSwapBuffers(globals::window);
+
+ glfwPollEvents();
+}
diff --git a/game/client/gui/splash.hh b/game/client/gui/splash.hh index 3ce63e4..a28a9ad 100644 --- a/game/client/gui/splash.hh +++ b/game/client/gui/splash.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::client_splash -{ -void init(void); -void init_late(void); -void render(void); -} // namespace gui::client_splash +#pragma once
+
+namespace gui::client_splash
+{
+void init(void);
+void init_late(void);
+void render(void);
+} // namespace gui::client_splash
diff --git a/game/client/gui/status_lines.cc b/game/client/gui/status_lines.cc index 9d9ac4c..d1a919a 100644 --- a/game/client/gui/status_lines.cc +++ b/game/client/gui/status_lines.cc @@ -1,84 +1,84 @@ -#include "client/pch.hh" - -#include "client/gui/status_lines.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/globals.hh" - -static float line_offsets[gui::STATUS_COUNT]; -static ImFont* line_fonts[gui::STATUS_COUNT]; -static float line_sizes[gui::STATUS_COUNT]; - -static ImVec4 line_text_colors[gui::STATUS_COUNT]; -static ImVec4 line_shadow_colors[gui::STATUS_COUNT]; -static std::string line_strings[gui::STATUS_COUNT]; -static std::uint64_t line_spawns[gui::STATUS_COUNT]; -static float line_fadeouts[gui::STATUS_COUNT]; - -void gui::status_lines::init(void) -{ - for(unsigned int i = 0U; i < STATUS_COUNT; ++i) { - line_text_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_shadow_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_strings[i] = std::string(); - line_spawns[i] = UINT64_MAX; - line_fadeouts[i] = 0.0f; - } -} - -void gui::status_lines::init_late(void) -{ - line_offsets[STATUS_DEBUG] = 64.0f; - line_offsets[STATUS_HOTBAR] = 40.0f; -} - -void gui::status_lines::layout(void) -{ - line_fonts[STATUS_DEBUG] = globals::font_unscii8; - line_sizes[STATUS_DEBUG] = 4.0f; - - line_fonts[STATUS_HOTBAR] = globals::font_unscii16; - line_sizes[STATUS_HOTBAR] = 8.0f; - - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - for(unsigned int i = 0U; i < STATUS_COUNT; ++i) { - auto offset = line_offsets[i] * globals::gui_scale; - auto& text = line_strings[i]; - auto* font = line_fonts[i]; - - auto size = font->CalcTextSizeA(line_sizes[i] * globals::gui_scale, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.size()); - auto pos = ImVec2(0.5f * (viewport->Size.x - size.x), viewport->Size.y - offset); - - auto spawn = line_spawns[i]; - auto fadeout = line_fadeouts[i]; - auto alpha = std::exp(-1.0f * std::pow(1.0e-6f * static_cast<float>(globals::curtime - spawn) / fadeout, 10.0f)); - - auto& color = line_text_colors[i]; - auto& shadow = line_shadow_colors[i]; - auto color_U32 = ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, color.w * alpha)); - auto shadow_U32 = ImGui::GetColorU32(ImVec4(shadow.x, shadow.y, shadow.z, color.w * alpha)); - - gui::imdraw_ext::text_shadow(text, pos, color_U32, shadow_U32, font, draw_list, line_sizes[i]); - } -} - -void gui::status_lines::set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout) -{ - line_text_colors[line] = ImVec4(color.x, color.y, color.z, color.w); - line_shadow_colors[line] = ImVec4(color.x * 0.1f, color.y * 0.1f, color.z * 0.1f, color.w); - line_strings[line] = std::string(text); - line_spawns[line] = globals::curtime; - line_fadeouts[line] = fadeout; -} - -void gui::status_lines::unset(unsigned int line) -{ - line_text_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_shadow_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_strings[line] = std::string(); - line_spawns[line] = UINT64_C(0); - line_fadeouts[line] = 0.0f; -} +#include "client/pch.hh"
+
+#include "client/gui/status_lines.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/globals.hh"
+
+static float line_offsets[gui::STATUS_COUNT];
+static ImFont* line_fonts[gui::STATUS_COUNT];
+static float line_sizes[gui::STATUS_COUNT];
+
+static ImVec4 line_text_colors[gui::STATUS_COUNT];
+static ImVec4 line_shadow_colors[gui::STATUS_COUNT];
+static std::string line_strings[gui::STATUS_COUNT];
+static std::uint64_t line_spawns[gui::STATUS_COUNT];
+static float line_fadeouts[gui::STATUS_COUNT];
+
+void gui::status_lines::init(void)
+{
+ for(unsigned int i = 0U; i < STATUS_COUNT; ++i) {
+ line_text_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_shadow_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_strings[i] = std::string();
+ line_spawns[i] = UINT64_MAX;
+ line_fadeouts[i] = 0.0f;
+ }
+}
+
+void gui::status_lines::init_late(void)
+{
+ line_offsets[STATUS_DEBUG] = 64.0f;
+ line_offsets[STATUS_HOTBAR] = 40.0f;
+}
+
+void gui::status_lines::layout(void)
+{
+ line_fonts[STATUS_DEBUG] = globals::font_unscii8;
+ line_sizes[STATUS_DEBUG] = 4.0f;
+
+ line_fonts[STATUS_HOTBAR] = globals::font_unscii16;
+ line_sizes[STATUS_HOTBAR] = 8.0f;
+
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ for(unsigned int i = 0U; i < STATUS_COUNT; ++i) {
+ auto offset = line_offsets[i] * globals::gui_scale;
+ auto& text = line_strings[i];
+ auto* font = line_fonts[i];
+
+ auto size = font->CalcTextSizeA(line_sizes[i] * globals::gui_scale, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.size());
+ auto pos = ImVec2(0.5f * (viewport->Size.x - size.x), viewport->Size.y - offset);
+
+ auto spawn = line_spawns[i];
+ auto fadeout = line_fadeouts[i];
+ auto alpha = std::exp(-1.0f * std::pow(1.0e-6f * static_cast<float>(globals::curtime - spawn) / fadeout, 10.0f));
+
+ auto& color = line_text_colors[i];
+ auto& shadow = line_shadow_colors[i];
+ auto color_U32 = ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, color.w * alpha));
+ auto shadow_U32 = ImGui::GetColorU32(ImVec4(shadow.x, shadow.y, shadow.z, color.w * alpha));
+
+ gui::imdraw_ext::text_shadow(text, pos, color_U32, shadow_U32, font, draw_list, line_sizes[i]);
+ }
+}
+
+void gui::status_lines::set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout)
+{
+ line_text_colors[line] = ImVec4(color.x, color.y, color.z, color.w);
+ line_shadow_colors[line] = ImVec4(color.x * 0.1f, color.y * 0.1f, color.z * 0.1f, color.w);
+ line_strings[line] = std::string(text);
+ line_spawns[line] = globals::curtime;
+ line_fadeouts[line] = fadeout;
+}
+
+void gui::status_lines::unset(unsigned int line)
+{
+ line_text_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_shadow_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_strings[line] = std::string();
+ line_spawns[line] = UINT64_C(0);
+ line_fadeouts[line] = 0.0f;
+}
diff --git a/game/client/gui/status_lines.hh b/game/client/gui/status_lines.hh index c86e248..7245d68 100644 --- a/game/client/gui/status_lines.hh +++ b/game/client/gui/status_lines.hh @@ -1,21 +1,21 @@ -#pragma once - -namespace gui -{ -constexpr static unsigned int STATUS_DEBUG = 0x0000; // generic debug line -constexpr static unsigned int STATUS_HOTBAR = 0x0001; // hotbar item line -constexpr static unsigned int STATUS_COUNT = 0x0002; -} // namespace gui - -namespace gui::status_lines -{ -void init(void); -void init_late(void); -void layout(void); -} // namespace gui::status_lines - -namespace gui::status_lines -{ -void set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout); -void unset(unsigned int line); -} // namespace gui::status_lines +#pragma once
+
+namespace gui
+{
+constexpr static unsigned int STATUS_DEBUG = 0x0000; // generic debug line
+constexpr static unsigned int STATUS_HOTBAR = 0x0001; // hotbar item line
+constexpr static unsigned int STATUS_COUNT = 0x0002;
+} // namespace gui
+
+namespace gui::status_lines
+{
+void init(void);
+void init_late(void);
+void layout(void);
+} // namespace gui::status_lines
+
+namespace gui::status_lines
+{
+void set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout);
+void unset(unsigned int line);
+} // namespace gui::status_lines
diff --git a/game/client/gui/window_title.cc b/game/client/gui/window_title.cc index 6f46668..6e2387c 100644 --- a/game/client/gui/window_title.cc +++ b/game/client/gui/window_title.cc @@ -1,23 +1,23 @@ -#include "client/pch.hh" - -#include "client/gui/window_title.hh" - -#include "core/version.hh" - -#include "shared/splash.hh" - -#include "client/globals.hh" - -void gui::window_title::update(void) -{ - std::string title; - - if(globals::sound_ctx && globals::sound_dev) { - title = std::format("Voxelius {}: {}", version::semver, splash::get()); - } - else { - title = std::format("Voxelius {}: {} [NOSOUND]", version::semver, splash::get()); - } - - glfwSetWindowTitle(globals::window, title.c_str()); -} +#include "client/pch.hh"
+
+#include "client/gui/window_title.hh"
+
+#include "core/version.hh"
+
+#include "shared/splash.hh"
+
+#include "client/globals.hh"
+
+void gui::window_title::update(void)
+{
+ std::string title;
+
+ if(globals::sound_ctx && globals::sound_dev) {
+ title = std::format("Voxelius {}: {}", version::semver, splash::get());
+ }
+ else {
+ title = std::format("Voxelius {}: {} [NOSOUND]", version::semver, splash::get());
+ }
+
+ glfwSetWindowTitle(globals::window, title.c_str());
+}
diff --git a/game/client/gui/window_title.hh b/game/client/gui/window_title.hh index af1ab7c..86e6a86 100644 --- a/game/client/gui/window_title.hh +++ b/game/client/gui/window_title.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace gui::window_title -{ -void update(void); -} // namespace gui::window_title +#pragma once
+
+namespace gui::window_title
+{
+void update(void);
+} // namespace gui::window_title
diff --git a/game/client/io/gamepad.cc b/game/client/io/gamepad.cc index 3a71920..1407996 100644 --- a/game/client/io/gamepad.cc +++ b/game/client/io/gamepad.cc @@ -1,183 +1,183 @@ -#include "client/pch.hh" - -#include "client/io/gamepad.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" -#include "core/math/constexpr.hh" - -#include "client/gui/settings.hh" -#include "client/io/glfw.hh" - -#include "client/globals.hh" -#include "client/toggles.hh" - -constexpr static int INVALID_GAMEPAD_ID = INT_MAX; -constexpr static std::size_t NUM_AXES = static_cast<std::size_t>(GLFW_GAMEPAD_AXIS_LAST + 1); -constexpr static std::size_t NUM_BUTTONS = static_cast<std::size_t>(GLFW_GAMEPAD_BUTTON_LAST + 1); -constexpr static float GAMEPAD_AXIS_EVENT_THRESHOLD = 0.5f; - -static int active_gamepad_id; - -bool io::gamepad::available = false; -config::Float io::gamepad::deadzone(0.00f, 0.00f, 0.66f); -config::Boolean io::gamepad::active(false); -GLFWgamepadstate io::gamepad::state; -GLFWgamepadstate io::gamepad::last_state; - -static void on_toggle_enable(const ToggleEnabledEvent& event) -{ - if(event.type == TOGGLE_USE_GAMEPAD) { - io::gamepad::active.set_value(true); - return; - } -} - -static void on_toggle_disable(const ToggleDisabledEvent& event) -{ - if(event.type == TOGGLE_USE_GAMEPAD) { - io::gamepad::active.set_value(false); - return; - } -} - -static void on_glfw_joystick_event(const io::GlfwJoystickEvent& event) -{ - if((event.event_type == GLFW_CONNECTED) && glfwJoystickIsGamepad(event.joystick_id) && (active_gamepad_id == INVALID_GAMEPAD_ID)) { - io::gamepad::available = true; - - active_gamepad_id = event.joystick_id; - - for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) { - // empty - } - - for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) { - // empty - } - - spdlog::info("gamepad: detected gamepad: {}", glfwGetGamepadName(event.joystick_id)); - - return; - } - - if((event.event_type == GLFW_DISCONNECTED) && (active_gamepad_id == event.joystick_id)) { - io::gamepad::available = false; - - active_gamepad_id = INVALID_GAMEPAD_ID; - - for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) { - // empty - } - - for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) { - // empty - } - - spdlog::warn("gamepad: disconnected"); - - return; - } -} - -void io::gamepad::init(void) -{ - io::gamepad::available = false; - - active_gamepad_id = INVALID_GAMEPAD_ID; - - globals::client_config.add_value("gamepad.deadzone", io::gamepad::deadzone); - globals::client_config.add_value("gamepad.active", io::gamepad::active); - - settings::add_checkbox(0, io::gamepad::active, settings_location::GAMEPAD, "gamepad.active", true); - settings::add_slider(1, io::gamepad::deadzone, settings_location::GAMEPAD, "gamepad.deadzone", true, "%.03f"); - - auto mappings_path = io::cmdline::get_cstr("gpmap", "misc/gamecontrollerdb.txt"); - auto mappings_file = PHYSFS_openRead(mappings_path); - - if(mappings_file) { - spdlog::info("gamepad: using mappings from {}", mappings_path); - auto mappings_string = std::string(PHYSFS_fileLength(mappings_file), char(0x00)); - PHYSFS_readBytes(mappings_file, mappings_string.data(), mappings_string.size()); - glfwUpdateGamepadMappings(mappings_string.c_str()); - PHYSFS_close(mappings_file); - } - - for(int joystick = 0; joystick <= GLFW_JOYSTICK_LAST; joystick += 1) { - if(glfwJoystickIsGamepad(joystick)) { - io::gamepad::available = true; - - active_gamepad_id = joystick; - - for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) { - // empty - } - - for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) { - // empty - } - - spdlog::info("gamepad: detected gamepad: {}", glfwGetGamepadName(joystick)); - - break; - } - } - - for(int i = 0; i < NUM_AXES; io::gamepad::state.axes[i++] = 0.0f) { - // empty - } - - for(int i = 0; i < NUM_BUTTONS; io::gamepad::state.buttons[i++] = GLFW_RELEASE) { - // empty - } - - globals::dispatcher.sink<ToggleEnabledEvent>().connect<&on_toggle_enable>(); - globals::dispatcher.sink<ToggleDisabledEvent>().connect<&on_toggle_disable>(); - globals::dispatcher.sink<GlfwJoystickEvent>().connect<&on_glfw_joystick_event>(); -} - -void io::gamepad::update_late(void) -{ - if(active_gamepad_id == INVALID_GAMEPAD_ID) { - // No active gamepad found - return; - } - - if(glfwGetGamepadState(active_gamepad_id, &io::gamepad::state)) { - for(int i = 0; i < NUM_AXES; ++i) { - if((math::abs(io::gamepad::state.axes[i]) > GAMEPAD_AXIS_EVENT_THRESHOLD) - && (math::abs(io::gamepad::last_state.axes[i]) <= GAMEPAD_AXIS_EVENT_THRESHOLD)) { - GamepadAxisEvent event; - event.action = GLFW_PRESS; - event.axis = i; - globals::dispatcher.enqueue(event); - continue; - } - - if((math::abs(io::gamepad::state.axes[i]) <= GAMEPAD_AXIS_EVENT_THRESHOLD) - && (math::abs(io::gamepad::last_state.axes[i]) > GAMEPAD_AXIS_EVENT_THRESHOLD)) { - GamepadAxisEvent event; - event.action = GLFW_RELEASE; - event.axis = i; - globals::dispatcher.enqueue(event); - continue; - } - } - - for(int i = 0; i < NUM_BUTTONS; ++i) { - if(io::gamepad::state.buttons[i] == io::gamepad::last_state.buttons[i]) { - // Nothing happens - continue; - } - - GamepadButtonEvent event; - event.action = io::gamepad::state.buttons[i]; - event.button = i; - globals::dispatcher.enqueue(event); - } - } - - io::gamepad::last_state = io::gamepad::state; -} +#include "client/pch.hh"
+
+#include "client/io/gamepad.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+#include "core/math/constexpr.hh"
+
+#include "client/gui/settings.hh"
+#include "client/io/glfw.hh"
+
+#include "client/globals.hh"
+#include "client/toggles.hh"
+
+constexpr static int INVALID_GAMEPAD_ID = INT_MAX;
+constexpr static std::size_t NUM_AXES = static_cast<std::size_t>(GLFW_GAMEPAD_AXIS_LAST + 1);
+constexpr static std::size_t NUM_BUTTONS = static_cast<std::size_t>(GLFW_GAMEPAD_BUTTON_LAST + 1);
+constexpr static float GAMEPAD_AXIS_EVENT_THRESHOLD = 0.5f;
+
+static int active_gamepad_id;
+
+bool io::gamepad::available = false;
+config::Float io::gamepad::deadzone(0.00f, 0.00f, 0.66f);
+config::Boolean io::gamepad::active(false);
+GLFWgamepadstate io::gamepad::state;
+GLFWgamepadstate io::gamepad::last_state;
+
+static void on_toggle_enable(const ToggleEnabledEvent& event)
+{
+ if(event.type == TOGGLE_USE_GAMEPAD) {
+ io::gamepad::active.set_value(true);
+ return;
+ }
+}
+
+static void on_toggle_disable(const ToggleDisabledEvent& event)
+{
+ if(event.type == TOGGLE_USE_GAMEPAD) {
+ io::gamepad::active.set_value(false);
+ return;
+ }
+}
+
+static void on_glfw_joystick_event(const io::GlfwJoystickEvent& event)
+{
+ if((event.event_type == GLFW_CONNECTED) && glfwJoystickIsGamepad(event.joystick_id) && (active_gamepad_id == INVALID_GAMEPAD_ID)) {
+ io::gamepad::available = true;
+
+ active_gamepad_id = event.joystick_id;
+
+ for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) {
+ // empty
+ }
+
+ for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) {
+ // empty
+ }
+
+ spdlog::info("gamepad: detected gamepad: {}", glfwGetGamepadName(event.joystick_id));
+
+ return;
+ }
+
+ if((event.event_type == GLFW_DISCONNECTED) && (active_gamepad_id == event.joystick_id)) {
+ io::gamepad::available = false;
+
+ active_gamepad_id = INVALID_GAMEPAD_ID;
+
+ for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) {
+ // empty
+ }
+
+ for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) {
+ // empty
+ }
+
+ spdlog::warn("gamepad: disconnected");
+
+ return;
+ }
+}
+
+void io::gamepad::init(void)
+{
+ io::gamepad::available = false;
+
+ active_gamepad_id = INVALID_GAMEPAD_ID;
+
+ globals::client_config.add_value("gamepad.deadzone", io::gamepad::deadzone);
+ globals::client_config.add_value("gamepad.active", io::gamepad::active);
+
+ settings::add_checkbox(0, io::gamepad::active, settings_location::GAMEPAD, "gamepad.active", true);
+ settings::add_slider(1, io::gamepad::deadzone, settings_location::GAMEPAD, "gamepad.deadzone", true, "%.03f");
+
+ auto mappings_path = io::cmdline::get_cstr("gpmap", "misc/gamecontrollerdb.txt");
+ auto mappings_file = PHYSFS_openRead(mappings_path);
+
+ if(mappings_file) {
+ spdlog::info("gamepad: using mappings from {}", mappings_path);
+ auto mappings_string = std::string(PHYSFS_fileLength(mappings_file), char(0x00));
+ PHYSFS_readBytes(mappings_file, mappings_string.data(), mappings_string.size());
+ glfwUpdateGamepadMappings(mappings_string.c_str());
+ PHYSFS_close(mappings_file);
+ }
+
+ for(int joystick = 0; joystick <= GLFW_JOYSTICK_LAST; joystick += 1) {
+ if(glfwJoystickIsGamepad(joystick)) {
+ io::gamepad::available = true;
+
+ active_gamepad_id = joystick;
+
+ for(int i = 0; i < NUM_AXES; io::gamepad::last_state.axes[i++] = 0.0f) {
+ // empty
+ }
+
+ for(int i = 0; i < NUM_BUTTONS; io::gamepad::last_state.buttons[i++] = GLFW_RELEASE) {
+ // empty
+ }
+
+ spdlog::info("gamepad: detected gamepad: {}", glfwGetGamepadName(joystick));
+
+ break;
+ }
+ }
+
+ for(int i = 0; i < NUM_AXES; io::gamepad::state.axes[i++] = 0.0f) {
+ // empty
+ }
+
+ for(int i = 0; i < NUM_BUTTONS; io::gamepad::state.buttons[i++] = GLFW_RELEASE) {
+ // empty
+ }
+
+ globals::dispatcher.sink<ToggleEnabledEvent>().connect<&on_toggle_enable>();
+ globals::dispatcher.sink<ToggleDisabledEvent>().connect<&on_toggle_disable>();
+ globals::dispatcher.sink<GlfwJoystickEvent>().connect<&on_glfw_joystick_event>();
+}
+
+void io::gamepad::update_late(void)
+{
+ if(active_gamepad_id == INVALID_GAMEPAD_ID) {
+ // No active gamepad found
+ return;
+ }
+
+ if(glfwGetGamepadState(active_gamepad_id, &io::gamepad::state)) {
+ for(int i = 0; i < NUM_AXES; ++i) {
+ if((math::abs(io::gamepad::state.axes[i]) > GAMEPAD_AXIS_EVENT_THRESHOLD)
+ && (math::abs(io::gamepad::last_state.axes[i]) <= GAMEPAD_AXIS_EVENT_THRESHOLD)) {
+ GamepadAxisEvent event;
+ event.action = GLFW_PRESS;
+ event.axis = i;
+ globals::dispatcher.enqueue(event);
+ continue;
+ }
+
+ if((math::abs(io::gamepad::state.axes[i]) <= GAMEPAD_AXIS_EVENT_THRESHOLD)
+ && (math::abs(io::gamepad::last_state.axes[i]) > GAMEPAD_AXIS_EVENT_THRESHOLD)) {
+ GamepadAxisEvent event;
+ event.action = GLFW_RELEASE;
+ event.axis = i;
+ globals::dispatcher.enqueue(event);
+ continue;
+ }
+ }
+
+ for(int i = 0; i < NUM_BUTTONS; ++i) {
+ if(io::gamepad::state.buttons[i] == io::gamepad::last_state.buttons[i]) {
+ // Nothing happens
+ continue;
+ }
+
+ GamepadButtonEvent event;
+ event.action = io::gamepad::state.buttons[i];
+ event.button = i;
+ globals::dispatcher.enqueue(event);
+ }
+ }
+
+ io::gamepad::last_state = io::gamepad::state;
+}
diff --git a/game/client/io/gamepad.hh b/game/client/io/gamepad.hh index 9c56894..ff38127 100644 --- a/game/client/io/gamepad.hh +++ b/game/client/io/gamepad.hh @@ -1,50 +1,50 @@ -#pragma once - -namespace io -{ -constexpr static int INVALID_GAMEPAD_AXIS = INT_MAX; -constexpr static int INVALID_GAMEPAD_BUTTON = INT_MAX; -} // namespace io - -namespace config -{ -class Boolean; -class Float; -} // namespace config - -struct GLFWgamepadstate; - -namespace io::gamepad -{ -extern bool available; -extern config::Float deadzone; -extern config::Boolean active; -extern GLFWgamepadstate state; -extern GLFWgamepadstate last_state; -} // namespace io::gamepad - -namespace io::gamepad -{ -void init(void); -void update_late(void); -} // namespace io::gamepad - -namespace io -{ -// This simulates buttons using axes. When an axis -// value exceeds 1.5 times the deadzone, the event is -// queued with a GLFW_PRESS action, when it falls back -// below the threshold, the event is queued with GLFW_RELEASE action -struct GamepadAxisEvent final { - int action; - int axis; -}; - -// This smears GLFW event sugar over gamepad polling -// system. Whenever it detects a state change, the event -// is queued with an appropriate action -struct GamepadButtonEvent final { - int action; - int button; -}; -} // namespace io +#pragma once
+
+namespace io
+{
+constexpr static int INVALID_GAMEPAD_AXIS = INT_MAX;
+constexpr static int INVALID_GAMEPAD_BUTTON = INT_MAX;
+} // namespace io
+
+namespace config
+{
+class Boolean;
+class Float;
+} // namespace config
+
+struct GLFWgamepadstate;
+
+namespace io::gamepad
+{
+extern bool available;
+extern config::Float deadzone;
+extern config::Boolean active;
+extern GLFWgamepadstate state;
+extern GLFWgamepadstate last_state;
+} // namespace io::gamepad
+
+namespace io::gamepad
+{
+void init(void);
+void update_late(void);
+} // namespace io::gamepad
+
+namespace io
+{
+// This simulates buttons using axes. When an axis
+// value exceeds 1.5 times the deadzone, the event is
+// queued with a GLFW_PRESS action, when it falls back
+// below the threshold, the event is queued with GLFW_RELEASE action
+struct GamepadAxisEvent final {
+ int action;
+ int axis;
+};
+
+// This smears GLFW event sugar over gamepad polling
+// system. Whenever it detects a state change, the event
+// is queued with an appropriate action
+struct GamepadButtonEvent final {
+ int action;
+ int button;
+};
+} // namespace io
diff --git a/game/client/io/glfw.hh b/game/client/io/glfw.hh index ea3f4a7..bbd767a 100644 --- a/game/client/io/glfw.hh +++ b/game/client/io/glfw.hh @@ -1,40 +1,40 @@ -#ifndef CLIENTFW -#define CLIENTFW 1 -#pragma once - -namespace io -{ -struct GlfwCursorPosEvent final { - glm::fvec2 pos; -}; - -struct GlfwFramebufferSizeEvent final { - glm::ivec2 size; - float aspect; -}; - -struct GlfwJoystickEvent final { - int joystick_id; - int event_type; -}; - -struct GlfwKeyEvent final { - int key { GLFW_KEY_UNKNOWN }; - int scancode; - int action; - int mods; -}; - -struct GlfwMouseButtonEvent final { - int button { GLFW_KEY_UNKNOWN }; - int action; - int mods; -}; - -struct GlfwScrollEvent final { - float dx; - float dy; -}; -} // namespace io - -#endif // CLIENTFW +#ifndef CLIENTFW
+#define CLIENTFW 1
+#pragma once
+
+namespace io
+{
+struct GlfwCursorPosEvent final {
+ glm::fvec2 pos;
+};
+
+struct GlfwFramebufferSizeEvent final {
+ glm::ivec2 size;
+ float aspect;
+};
+
+struct GlfwJoystickEvent final {
+ int joystick_id;
+ int event_type;
+};
+
+struct GlfwKeyEvent final {
+ int key { GLFW_KEY_UNKNOWN };
+ int scancode;
+ int action;
+ int mods;
+};
+
+struct GlfwMouseButtonEvent final {
+ int button { GLFW_KEY_UNKNOWN };
+ int action;
+ int mods;
+};
+
+struct GlfwScrollEvent final {
+ float dx;
+ float dy;
+};
+} // namespace io
+
+#endif // CLIENTFW
diff --git a/game/client/main.cc b/game/client/main.cc index a1185e2..e20fd5c 100644 --- a/game/client/main.cc +++ b/game/client/main.cc @@ -1,449 +1,449 @@ -#include "client/pch.hh" - -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" - -#include "core/resource/image.hh" -#include "core/resource/resource.hh" - -#include "core/utils/epoch.hh" - -#include "core/threading.hh" -#include "core/version.hh" - -#include "shared/game.hh" -#include "shared/splash.hh" - -#include "client/gui/window_title.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/sound_effect.hh" -#include "client/resource/texture_gui.hh" - -#include "client/const.hh" -#include "client/game.hh" -#include "client/globals.hh" - -#if defined(_WIN32) -extern "C" __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; -extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -#endif - -static void on_glfw_error(int code, const char* message) -{ - spdlog::error("glfw: {}", message); -} - -static void on_glfw_char(GLFWwindow* window, unsigned int codepoint) -{ - ImGui_ImplGlfw_CharCallback(window, codepoint); -} - -static void on_glfw_cursor_enter(GLFWwindow* window, int entered) -{ - ImGui_ImplGlfw_CursorEnterCallback(window, entered); -} - -static void on_glfw_cursor_pos(GLFWwindow* window, double xpos, double ypos) -{ - io::GlfwCursorPosEvent event; - event.pos.x = static_cast<float>(xpos); - event.pos.y = static_cast<float>(ypos); - globals::dispatcher.trigger(event); - - ImGui_ImplGlfw_CursorPosCallback(window, xpos, ypos); -} - -static void on_glfw_framebuffer_size(GLFWwindow* window, int width, int height) -{ - if(glfwGetWindowAttrib(window, GLFW_ICONIFIED)) { - // Don't do anything if the window was just - // iconified (minimized); as it turns out minimized - // windows on WIN32 seem to be forced into 0x0 - return; - } - - globals::width = width; - globals::height = height; - globals::aspect = static_cast<float>(width) / static_cast<float>(height); - - io::GlfwFramebufferSizeEvent fb_event; - fb_event.size.x = globals::width; - fb_event.size.y = globals::height; - fb_event.aspect = globals::aspect; - globals::dispatcher.trigger(fb_event); -} - -static void on_glfw_key(GLFWwindow* window, int key, int scancode, int action, int mods) -{ - io::GlfwKeyEvent event; - event.key = key; - event.scancode = scancode; - event.action = action; - event.mods = mods; - globals::dispatcher.trigger(event); - - ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods); -} - -static void on_glfw_joystick(int joystick_id, int event_type) -{ - io::GlfwJoystickEvent event; - event.joystick_id = joystick_id; - event.event_type = event_type; - globals::dispatcher.trigger(event); -} - -static void on_glfw_monitor_event(GLFWmonitor* monitor, int event) -{ - ImGui_ImplGlfw_MonitorCallback(monitor, event); -} - -static void on_glfw_mouse_button(GLFWwindow* window, int button, int action, int mods) -{ - io::GlfwMouseButtonEvent event; - event.button = button; - event.action = action; - event.mods = mods; - globals::dispatcher.trigger(event); - - ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods); -} - -static void on_glfw_scroll(GLFWwindow* window, double dx, double dy) -{ - io::GlfwScrollEvent event; - event.dx = static_cast<float>(dx); - event.dy = static_cast<float>(dy); - globals::dispatcher.trigger(event); - - ImGui_ImplGlfw_ScrollCallback(window, dx, dy); -} - -static void on_glfw_window_focus(GLFWwindow* window, int focused) -{ - ImGui_ImplGlfw_WindowFocusCallback(window, focused); -} - -static void GLAD_API_PTR on_opengl_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, - const void* param) -{ - spdlog::info("opengl: {}", reinterpret_cast<const char*>(message)); -} - -static void on_termination_signal(int) -{ - spdlog::warn("client: received termination signal"); - glfwSetWindowShouldClose(globals::window, true); -} - -int main(int argc, char** argv) -{ - io::cmdline::create(argc, argv); - -#if defined(_WIN32) -#if defined(NDEBUG) - if(GetConsoleWindow() && !io::cmdline::contains("debug")) { - // Hide the console window on release builds - // unless explicitly specified to preserve it instead - FreeConsole(); - } -#else - if(GetConsoleWindow() && io::cmdline::contains("nodebug")) { - // Hide the console window on debug builds when - // explicitly specified by the user to hide it - FreeConsole(); - } -#endif -#endif - - shared_game::init(argc, argv); - - spdlog::info("Voxelius Client {}", version::semver); - - glfwSetErrorCallback(&on_glfw_error); - -#if defined(__unix__) - // Wayland constantly throws random bullshit at me - // when I'm dealing with pretty much anything cross-platform - // on pretty much any kind of UNIX and Linux distribution - glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); -#endif - - if(!glfwInit()) { - spdlog::critical("glfw: init failed"); - std::terminate(); - } - - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_SAMPLES, 0); - - globals::window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, "Client", nullptr, nullptr); - - if(!globals::window) { - spdlog::critical("glfw: failed to open a window"); - std::terminate(); - } - - std::signal(SIGINT, &on_termination_signal); - std::signal(SIGTERM, &on_termination_signal); - - glfwMakeContextCurrent(globals::window); - glfwSwapInterval(1); - - if(!gladLoadGL(&glfwGetProcAddress)) { - spdlog::critical("glad: failed to load function pointers"); - std::terminate(); - } - - if(GLAD_GL_KHR_debug) { - if(!io::cmdline::contains("nodebug")) { - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(&on_opengl_message, nullptr); - - // NVIDIA drivers tend to spam quote-unquote "useful" - // information about buffer usage into the debug callback - static const std::uint32_t ignore_nvidia_131185 = 131185; - glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 1, &ignore_nvidia_131185, GL_FALSE); - } - else { - spdlog::warn("glad: nodebug command line parameter found"); - spdlog::warn("glad: OpenGL errors will not be logged"); - } - } - else { - spdlog::warn("glad: KHR_debug extension not supported"); - spdlog::warn("glad: OpenGL errors will not be logged"); - } - - spdlog::info("opengl: version: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION))); - spdlog::info("opengl: renderer: {}", reinterpret_cast<const char*>(glGetString(GL_RENDERER))); - - Image::register_resource(); - TextureGUI::register_resource(); - SoundEffect::register_resource(); - - glDisable(GL_MULTISAMPLE); - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(globals::window, false); - ImGui_ImplOpenGL3_Init(nullptr); - - // The UI is scaled against a resolution defined by BASE_WIDTH and BASE_HEIGHT - // constants. However, UI scale of 1 doesn't look that good, so the window size is - // limited to a resolution that allows at least UI scale of 2 and is defined by MIN_WIDTH and MIN_HEIGHT. - glfwSetWindowSizeLimits(globals::window, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE); - - glfwSetCharCallback(globals::window, &on_glfw_char); - glfwSetCursorEnterCallback(globals::window, &on_glfw_cursor_enter); - glfwSetCursorPosCallback(globals::window, &on_glfw_cursor_pos); - glfwSetFramebufferSizeCallback(globals::window, &on_glfw_framebuffer_size); - glfwSetKeyCallback(globals::window, &on_glfw_key); - glfwSetMouseButtonCallback(globals::window, &on_glfw_mouse_button); - glfwSetScrollCallback(globals::window, &on_glfw_scroll); - glfwSetWindowFocusCallback(globals::window, &on_glfw_window_focus); - - glfwSetJoystickCallback(&on_glfw_joystick); - glfwSetMonitorCallback(&on_glfw_monitor_event); - - if(auto image = resource::load<Image>("textures/gui/window_icon.png")) { - GLFWimage icon_image; - icon_image.width = image->size.x; - icon_image.height = image->size.y; - icon_image.pixels = reinterpret_cast<unsigned char*>(image->pixels); - glfwSetWindowIcon(globals::window, 1, &icon_image); - } - - if(io::cmdline::contains("nosound")) { - spdlog::warn("client: sound disabled [per command line]"); - globals::sound_dev = nullptr; - globals::sound_ctx = nullptr; - } - else { - if(!saladLoadALdefault()) { - spdlog::warn("client: sound disabled [openal loading failed]"); - globals::sound_dev = nullptr; - globals::sound_ctx = nullptr; - } - else { - globals::sound_dev = alcOpenDevice(nullptr); - - if(globals::sound_dev == nullptr) { - spdlog::warn("client: sound disabled [no device]"); - globals::sound_ctx = nullptr; - } - else { - spdlog::info("sound: {}", reinterpret_cast<const char*>(alcGetString(globals::sound_dev, ALC_DEVICE_SPECIFIER))); - - globals::sound_ctx = alcCreateContext(globals::sound_dev, nullptr); - - if(globals::sound_ctx == nullptr) { - spdlog::warn("client: sound disabled [context creation failed]"); - alcCloseDevice(globals::sound_dev); - globals::sound_dev = nullptr; - } - else { - alcMakeContextCurrent(globals::sound_ctx); - } - } - } - } - - splash::init_client(); - - gui::window_title::update(); - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::fixed_frametime = 0.0f; - globals::fixed_frametime_avg = 0.0f; - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_framecount = 0; - - globals::curtime = utils::unix_microseconds(); - - globals::window_frametime = 0.0f; - globals::window_frametime_avg = 0.0f; - globals::window_frametime_us = 0; - globals::window_framecount = 0; - - int vmode_width = DEFAULT_WIDTH; - int vmode_height = DEFAULT_HEIGHT; - - if(auto vmode = io::cmdline::get_cstr("mode")) { - std::sscanf(vmode, "%dx%d", &vmode_width, &vmode_height); - vmode_height = math::max(vmode_height, MIN_HEIGHT); - vmode_width = math::max(vmode_width, MIN_WIDTH); - } - - glfwSetWindowSize(globals::window, vmode_width, vmode_height); - - client_game::init(); - - int wwidth, wheight; - glfwGetFramebufferSize(globals::window, &wwidth, &wheight); - on_glfw_framebuffer_size(globals::window, wwidth, wheight); - - threading::init(); - - globals::client_config.load_file("client.conf"); - globals::client_config.load_cmdline(); - - client_game::init_late(); - - auto last_curtime = globals::curtime; - - while(!glfwWindowShouldClose(globals::window)) { - globals::curtime = utils::unix_microseconds(); - - globals::window_frametime_us = globals::curtime - last_curtime; - globals::window_frametime = static_cast<float>(globals::window_frametime_us) / 1000000.0f; - globals::window_frametime_avg += globals::window_frametime; - globals::window_frametime_avg *= 0.5f; - - if(globals::fixed_frametime_us == UINT64_MAX) { - globals::fixed_framecount = 0; - globals::fixed_accumulator = 0; - } - else { - globals::fixed_accumulator += globals::window_frametime_us; - globals::fixed_framecount = globals::fixed_accumulator / globals::fixed_frametime_us; - globals::fixed_accumulator %= globals::fixed_frametime_us; - } - - globals::num_drawcalls = 0; - globals::num_triangles = 0; - - last_curtime = globals::curtime; - - for(std::uint64_t i = 0; i < globals::fixed_framecount; ++i) - client_game::fixed_update(); - client_game::update(); - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - glDisable(GL_BLEND); - - glDisable(GL_DEPTH_TEST); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, globals::width, globals::height); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - // Make sure there is no stray program object - // being bound to the context. Usually third-party - // overlay software (such as RivaTuner) injects itself - // into the rendering loop and binds internal objects, - // which creates an incomprehensible visual mess - glUseProgram(0); - - client_game::render(); - - client_game::layout(); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(globals::window); - - for(std::uint64_t i = 0; i < globals::fixed_framecount; ++i) - client_game::fixed_update_late(); - client_game::update_late(); - - glfwPollEvents(); - - // EnTT provides two ways of dispatching events: - // queued and immediate. When glfwPollEvents() is - // called, immediate events are triggered across - // the application, whilst queued ones are triggered - // later by calling entt::dispatcher::update() - globals::dispatcher.update(); - - globals::window_framecount += 1; - - resource::soft_cleanup(); - - threading::update(); - } - - client_game::shutdown(); - - resource::hard_cleanup(); - - spdlog::info("client: shutdown after {} frames", globals::window_framecount); - spdlog::info("client: average framerate: {:.03f} FPS", 1.0f / globals::window_frametime_avg); - spdlog::info("client: average frametime: {:.03f} ms", 1000.0f * globals::window_frametime_avg); - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - if(globals::sound_ctx) { - alcMakeContextCurrent(nullptr); - alcDestroyContext(globals::sound_ctx); - alcCloseDevice(globals::sound_dev); - } - - glfwDestroyWindow(globals::window); - glfwTerminate(); - - globals::client_config.save_file("client.conf"); - - threading::shutdown(); - - shared_game::shutdown(); - - return EXIT_SUCCESS; -} +#include "client/pch.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "core/threading.hh"
+#include "core/version.hh"
+
+#include "shared/game.hh"
+#include "shared/splash.hh"
+
+#include "client/gui/window_title.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/sound_effect.hh"
+#include "client/resource/texture_gui.hh"
+
+#include "client/const.hh"
+#include "client/game.hh"
+#include "client/globals.hh"
+
+#if defined(_WIN32)
+extern "C" __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
+extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
+#endif
+
+static void on_glfw_error(int code, const char* message)
+{
+ spdlog::error("glfw: {}", message);
+}
+
+static void on_glfw_char(GLFWwindow* window, unsigned int codepoint)
+{
+ ImGui_ImplGlfw_CharCallback(window, codepoint);
+}
+
+static void on_glfw_cursor_enter(GLFWwindow* window, int entered)
+{
+ ImGui_ImplGlfw_CursorEnterCallback(window, entered);
+}
+
+static void on_glfw_cursor_pos(GLFWwindow* window, double xpos, double ypos)
+{
+ io::GlfwCursorPosEvent event;
+ event.pos.x = static_cast<float>(xpos);
+ event.pos.y = static_cast<float>(ypos);
+ globals::dispatcher.trigger(event);
+
+ ImGui_ImplGlfw_CursorPosCallback(window, xpos, ypos);
+}
+
+static void on_glfw_framebuffer_size(GLFWwindow* window, int width, int height)
+{
+ if(glfwGetWindowAttrib(window, GLFW_ICONIFIED)) {
+ // Don't do anything if the window was just
+ // iconified (minimized); as it turns out minimized
+ // windows on WIN32 seem to be forced into 0x0
+ return;
+ }
+
+ globals::width = width;
+ globals::height = height;
+ globals::aspect = static_cast<float>(width) / static_cast<float>(height);
+
+ io::GlfwFramebufferSizeEvent fb_event;
+ fb_event.size.x = globals::width;
+ fb_event.size.y = globals::height;
+ fb_event.aspect = globals::aspect;
+ globals::dispatcher.trigger(fb_event);
+}
+
+static void on_glfw_key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ io::GlfwKeyEvent event;
+ event.key = key;
+ event.scancode = scancode;
+ event.action = action;
+ event.mods = mods;
+ globals::dispatcher.trigger(event);
+
+ ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods);
+}
+
+static void on_glfw_joystick(int joystick_id, int event_type)
+{
+ io::GlfwJoystickEvent event;
+ event.joystick_id = joystick_id;
+ event.event_type = event_type;
+ globals::dispatcher.trigger(event);
+}
+
+static void on_glfw_monitor_event(GLFWmonitor* monitor, int event)
+{
+ ImGui_ImplGlfw_MonitorCallback(monitor, event);
+}
+
+static void on_glfw_mouse_button(GLFWwindow* window, int button, int action, int mods)
+{
+ io::GlfwMouseButtonEvent event;
+ event.button = button;
+ event.action = action;
+ event.mods = mods;
+ globals::dispatcher.trigger(event);
+
+ ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods);
+}
+
+static void on_glfw_scroll(GLFWwindow* window, double dx, double dy)
+{
+ io::GlfwScrollEvent event;
+ event.dx = static_cast<float>(dx);
+ event.dy = static_cast<float>(dy);
+ globals::dispatcher.trigger(event);
+
+ ImGui_ImplGlfw_ScrollCallback(window, dx, dy);
+}
+
+static void on_glfw_window_focus(GLFWwindow* window, int focused)
+{
+ ImGui_ImplGlfw_WindowFocusCallback(window, focused);
+}
+
+static void GLAD_API_PTR on_opengl_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message,
+ const void* param)
+{
+ spdlog::info("opengl: {}", reinterpret_cast<const char*>(message));
+}
+
+static void on_termination_signal(int)
+{
+ spdlog::warn("client: received termination signal");
+ glfwSetWindowShouldClose(globals::window, true);
+}
+
+int main(int argc, char** argv)
+{
+ io::cmdline::create(argc, argv);
+
+#if defined(_WIN32)
+#if defined(NDEBUG)
+ if(GetConsoleWindow() && !io::cmdline::contains("debug")) {
+ // Hide the console window on release builds
+ // unless explicitly specified to preserve it instead
+ FreeConsole();
+ }
+#else
+ if(GetConsoleWindow() && io::cmdline::contains("nodebug")) {
+ // Hide the console window on debug builds when
+ // explicitly specified by the user to hide it
+ FreeConsole();
+ }
+#endif
+#endif
+
+ shared_game::init(argc, argv);
+
+ spdlog::info("Voxelius Client {}", version::semver);
+
+ glfwSetErrorCallback(&on_glfw_error);
+
+#if defined(__unix__)
+ // Wayland constantly throws random bullshit at me
+ // when I'm dealing with pretty much anything cross-platform
+ // on pretty much any kind of UNIX and Linux distribution
+ glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
+#endif
+
+ if(!glfwInit()) {
+ spdlog::critical("glfw: init failed");
+ std::terminate();
+ }
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+ glfwWindowHint(GLFW_SAMPLES, 0);
+
+ globals::window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, "Client", nullptr, nullptr);
+
+ if(!globals::window) {
+ spdlog::critical("glfw: failed to open a window");
+ std::terminate();
+ }
+
+ std::signal(SIGINT, &on_termination_signal);
+ std::signal(SIGTERM, &on_termination_signal);
+
+ glfwMakeContextCurrent(globals::window);
+ glfwSwapInterval(1);
+
+ if(!gladLoadGL(&glfwGetProcAddress)) {
+ spdlog::critical("glad: failed to load function pointers");
+ std::terminate();
+ }
+
+ if(GLAD_GL_KHR_debug) {
+ if(!io::cmdline::contains("nodebug")) {
+ glEnable(GL_DEBUG_OUTPUT);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ glDebugMessageCallback(&on_opengl_message, nullptr);
+
+ // NVIDIA drivers tend to spam quote-unquote "useful"
+ // information about buffer usage into the debug callback
+ static const std::uint32_t ignore_nvidia_131185 = 131185;
+ glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 1, &ignore_nvidia_131185, GL_FALSE);
+ }
+ else {
+ spdlog::warn("glad: nodebug command line parameter found");
+ spdlog::warn("glad: OpenGL errors will not be logged");
+ }
+ }
+ else {
+ spdlog::warn("glad: KHR_debug extension not supported");
+ spdlog::warn("glad: OpenGL errors will not be logged");
+ }
+
+ spdlog::info("opengl: version: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
+ spdlog::info("opengl: renderer: {}", reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
+
+ Image::register_resource();
+ TextureGUI::register_resource();
+ SoundEffect::register_resource();
+
+ glDisable(GL_MULTISAMPLE);
+
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGui::StyleColorsDark();
+ ImGui_ImplGlfw_InitForOpenGL(globals::window, false);
+ ImGui_ImplOpenGL3_Init(nullptr);
+
+ // The UI is scaled against a resolution defined by BASE_WIDTH and BASE_HEIGHT
+ // constants. However, UI scale of 1 doesn't look that good, so the window size is
+ // limited to a resolution that allows at least UI scale of 2 and is defined by MIN_WIDTH and MIN_HEIGHT.
+ glfwSetWindowSizeLimits(globals::window, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
+
+ glfwSetCharCallback(globals::window, &on_glfw_char);
+ glfwSetCursorEnterCallback(globals::window, &on_glfw_cursor_enter);
+ glfwSetCursorPosCallback(globals::window, &on_glfw_cursor_pos);
+ glfwSetFramebufferSizeCallback(globals::window, &on_glfw_framebuffer_size);
+ glfwSetKeyCallback(globals::window, &on_glfw_key);
+ glfwSetMouseButtonCallback(globals::window, &on_glfw_mouse_button);
+ glfwSetScrollCallback(globals::window, &on_glfw_scroll);
+ glfwSetWindowFocusCallback(globals::window, &on_glfw_window_focus);
+
+ glfwSetJoystickCallback(&on_glfw_joystick);
+ glfwSetMonitorCallback(&on_glfw_monitor_event);
+
+ if(auto image = resource::load<Image>("textures/gui/window_icon.png")) {
+ GLFWimage icon_image;
+ icon_image.width = image->size.x;
+ icon_image.height = image->size.y;
+ icon_image.pixels = reinterpret_cast<unsigned char*>(image->pixels);
+ glfwSetWindowIcon(globals::window, 1, &icon_image);
+ }
+
+ if(io::cmdline::contains("nosound")) {
+ spdlog::warn("client: sound disabled [per command line]");
+ globals::sound_dev = nullptr;
+ globals::sound_ctx = nullptr;
+ }
+ else {
+ if(!saladLoadALdefault()) {
+ spdlog::warn("client: sound disabled [openal loading failed]");
+ globals::sound_dev = nullptr;
+ globals::sound_ctx = nullptr;
+ }
+ else {
+ globals::sound_dev = alcOpenDevice(nullptr);
+
+ if(globals::sound_dev == nullptr) {
+ spdlog::warn("client: sound disabled [no device]");
+ globals::sound_ctx = nullptr;
+ }
+ else {
+ spdlog::info("sound: {}", reinterpret_cast<const char*>(alcGetString(globals::sound_dev, ALC_DEVICE_SPECIFIER)));
+
+ globals::sound_ctx = alcCreateContext(globals::sound_dev, nullptr);
+
+ if(globals::sound_ctx == nullptr) {
+ spdlog::warn("client: sound disabled [context creation failed]");
+ alcCloseDevice(globals::sound_dev);
+ globals::sound_dev = nullptr;
+ }
+ else {
+ alcMakeContextCurrent(globals::sound_ctx);
+ }
+ }
+ }
+ }
+
+ splash::init_client();
+
+ gui::window_title::update();
+
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_frametime_avg = 0.0f;
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_framecount = 0;
+
+ globals::curtime = utils::unix_microseconds();
+
+ globals::window_frametime = 0.0f;
+ globals::window_frametime_avg = 0.0f;
+ globals::window_frametime_us = 0;
+ globals::window_framecount = 0;
+
+ int vmode_width = DEFAULT_WIDTH;
+ int vmode_height = DEFAULT_HEIGHT;
+
+ if(auto vmode = io::cmdline::get_cstr("mode")) {
+ std::sscanf(vmode, "%dx%d", &vmode_width, &vmode_height);
+ vmode_height = math::max(vmode_height, MIN_HEIGHT);
+ vmode_width = math::max(vmode_width, MIN_WIDTH);
+ }
+
+ glfwSetWindowSize(globals::window, vmode_width, vmode_height);
+
+ client_game::init();
+
+ int wwidth, wheight;
+ glfwGetFramebufferSize(globals::window, &wwidth, &wheight);
+ on_glfw_framebuffer_size(globals::window, wwidth, wheight);
+
+ threading::init();
+
+ globals::client_config.load_file("client.conf");
+ globals::client_config.load_cmdline();
+
+ client_game::init_late();
+
+ auto last_curtime = globals::curtime;
+
+ while(!glfwWindowShouldClose(globals::window)) {
+ globals::curtime = utils::unix_microseconds();
+
+ globals::window_frametime_us = globals::curtime - last_curtime;
+ globals::window_frametime = static_cast<float>(globals::window_frametime_us) / 1000000.0f;
+ globals::window_frametime_avg += globals::window_frametime;
+ globals::window_frametime_avg *= 0.5f;
+
+ if(globals::fixed_frametime_us == UINT64_MAX) {
+ globals::fixed_framecount = 0;
+ globals::fixed_accumulator = 0;
+ }
+ else {
+ globals::fixed_accumulator += globals::window_frametime_us;
+ globals::fixed_framecount = globals::fixed_accumulator / globals::fixed_frametime_us;
+ globals::fixed_accumulator %= globals::fixed_frametime_us;
+ }
+
+ globals::num_drawcalls = 0;
+ globals::num_triangles = 0;
+
+ last_curtime = globals::curtime;
+
+ for(std::uint64_t i = 0; i < globals::fixed_framecount; ++i)
+ client_game::fixed_update();
+ client_game::update();
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ glDisable(GL_BLEND);
+
+ glDisable(GL_DEPTH_TEST);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glViewport(0, 0, globals::width, globals::height);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Make sure there is no stray program object
+ // being bound to the context. Usually third-party
+ // overlay software (such as RivaTuner) injects itself
+ // into the rendering loop and binds internal objects,
+ // which creates an incomprehensible visual mess
+ glUseProgram(0);
+
+ client_game::render();
+
+ client_game::layout();
+
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ glfwSwapBuffers(globals::window);
+
+ for(std::uint64_t i = 0; i < globals::fixed_framecount; ++i)
+ client_game::fixed_update_late();
+ client_game::update_late();
+
+ glfwPollEvents();
+
+ // EnTT provides two ways of dispatching events:
+ // queued and immediate. When glfwPollEvents() is
+ // called, immediate events are triggered across
+ // the application, whilst queued ones are triggered
+ // later by calling entt::dispatcher::update()
+ globals::dispatcher.update();
+
+ globals::window_framecount += 1;
+
+ resource::soft_cleanup();
+
+ threading::update();
+ }
+
+ client_game::shutdown();
+
+ resource::hard_cleanup();
+
+ spdlog::info("client: shutdown after {} frames", globals::window_framecount);
+ spdlog::info("client: average framerate: {:.03f} FPS", 1.0f / globals::window_frametime_avg);
+ spdlog::info("client: average frametime: {:.03f} ms", 1000.0f * globals::window_frametime_avg);
+
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+ ImGui::DestroyContext();
+
+ if(globals::sound_ctx) {
+ alcMakeContextCurrent(nullptr);
+ alcDestroyContext(globals::sound_ctx);
+ alcCloseDevice(globals::sound_dev);
+ }
+
+ glfwDestroyWindow(globals::window);
+ glfwTerminate();
+
+ globals::client_config.save_file("client.conf");
+
+ threading::shutdown();
+
+ shared_game::shutdown();
+
+ return EXIT_SUCCESS;
+}
diff --git a/game/client/pch.hh b/game/client/pch.hh index 3832081..e6d5681 100644 --- a/game/client/pch.hh +++ b/game/client/pch.hh @@ -1,28 +1,28 @@ -#pragma once - -#include <shared/pch.hh> - -#include <AL/al.h> -#include <AL/alc.h> -#include <AL/salad.h> - -#include <dr_mp3.h> -#include <dr_wav.h> - -#include <GLFW/glfw3.h> - -#include <glad/gl.h> - -#include <imgui.h> -#include <imgui_impl_glfw.h> -#include <imgui_impl_opengl3.h> -#include <imgui_stdlib.h> - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#endif - -#if defined(__unix__) -#include <dlfcn.h> -#endif +#pragma once
+
+#include <shared/pch.hh>
+
+#include <AL/al.h>
+#include <AL/alc.h>
+#include <AL/salad.h>
+
+#include <dr_mp3.h>
+#include <dr_wav.h>
+
+#include <GLFW/glfw3.h>
+
+#include <glad/gl.h>
+
+#include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <imgui_stdlib.h>
+
+#if defined(_WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#if defined(__unix__)
+#include <dlfcn.h>
+#endif
diff --git a/game/client/program.cc b/game/client/program.cc index e35716d..9eba02f 100644 --- a/game/client/program.cc +++ b/game/client/program.cc @@ -1,224 +1,224 @@ -#include "client/pch.hh" - -#include "client/program.hh" - -#include "core/utils/string.hh" - -// This fills up the array of source lines and figures out -// which lines are to be dynamically resolved as variant macros -static void parse_source(std::string_view source, std::vector<std::string>& out_lines, std::vector<GL_VariedMacro>& out_variants) -{ - std::string line; - std::istringstream stream = std::istringstream(std::string(source)); - unsigned long line_number = 0UL; - - out_lines.clear(); - out_variants.clear(); - - while(std::getline(stream, line)) { - unsigned int macro_index = {}; - char macro_name[128] = {}; - - if(std::sscanf(line.c_str(), " # pragma variant [ %u ] %127[^, \"\t\r\n]", ¯o_index, ¯o_name) == 2) { - if(out_variants.size() <= macro_index) { - out_variants.resize(macro_index + 1U); - } - - out_variants[macro_index].name = macro_name; - out_variants[macro_index].line = line_number; - out_variants[macro_index].value = std::numeric_limits<unsigned int>::max(); - - out_lines.push_back(std::string()); - line_number += 1UL; - } - else { - out_lines.push_back(line); - line_number += 1UL; - } - } -} - -static GLuint compile_shader(std::string_view path, const char* source, GLenum shader_stage) -{ - GLuint shader = glCreateShader(shader_stage); - glShaderSource(shader, 1, &source, nullptr); - glCompileShader(shader); - - GLint info_log_length; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length); - - if(info_log_length >= 2) { - std::basic_string<GLchar> info_log; - info_log.resize(info_log_length); - glGetShaderInfoLog(shader, info_log_length, nullptr, info_log.data()); - spdlog::info("gl_program: {}: shader information:", path); - spdlog::info(info_log); - } - - GLint compile_status; - glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); - - if(!compile_status) { - glDeleteShader(shader); - return 0; - } - - return shader; -} - -bool GL_Program::setup(std::string_view vpath, std::string_view fpath) -{ - destroy(); - - vert_path = std::string(vpath); - frag_path = std::string(fpath); - - auto vfile = PHYSFS_openRead(vert_path.c_str()); - - if(vfile == nullptr) { - spdlog::warn("gl_program: {}: {}", vpath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - auto vsource = std::string(PHYSFS_fileLength(vfile), char(0x00)); - PHYSFS_readBytes(vfile, vsource.data(), vsource.size()); - PHYSFS_close(vfile); - - auto ffile = PHYSFS_openRead(frag_path.c_str()); - - if(ffile == nullptr) { - spdlog::warn("gl_program: {}: {}", fpath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - return false; - } - - auto fsource = std::string(PHYSFS_fileLength(ffile), char(0x00)); - PHYSFS_readBytes(ffile, fsource.data(), fsource.size()); - PHYSFS_close(ffile); - - parse_source(vsource.c_str(), vert_source, vert_variants); - parse_source(fsource.c_str(), frag_source, frag_variants); - - needs_update = true; - handle = 0; - - return true; -} - -bool GL_Program::update(void) -{ - if(!needs_update) { - // The program is already up to - // date with the internal state - return true; - } - - for(const auto& macro : vert_variants) - vert_source[macro.line] = std::format("#define {} {}", macro.name, macro.value); - for(const auto& macro : frag_variants) - frag_source[macro.line] = std::format("#define {} {}", macro.name, macro.value); - - std::string vsource(utils::join(vert_source, "\r\n")); - std::string fsource(utils::join(frag_source, "\r\n")); - - GLuint vert = compile_shader(vert_path.c_str(), vsource.c_str(), GL_VERTEX_SHADER); - GLuint frag = compile_shader(frag_path.c_str(), fsource.c_str(), GL_FRAGMENT_SHADER); - - if(!vert || !frag) { - // needs_update = false; - glDeleteShader(frag); - glDeleteShader(vert); - return false; - } - - handle = glCreateProgram(); - glAttachShader(handle, vert); - glAttachShader(handle, frag); - glLinkProgram(handle); - - GLint info_log_length; - glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &info_log_length); - - if(info_log_length >= 2) { - std::basic_string<GLchar> info_log; - info_log.resize(info_log_length); - glGetProgramInfoLog(handle, info_log_length, nullptr, info_log.data()); - spdlog::info("gl_program: [{}; {}]: program information", vert, frag); - spdlog::info(info_log); - } - - glDeleteShader(frag); - glDeleteShader(vert); - - GLint link_status; - glGetProgramiv(handle, GL_LINK_STATUS, &link_status); - - if(!link_status) { - // needs_update = false; - glDeleteProgram(handle); - return false; - } - - for(auto& uniform : uniforms) { - // NOTE: GL seems to silently ignore invalid uniform - // locations (-1); should we write something into logs about this? - uniform.location = glGetUniformLocation(handle, uniform.name.c_str()); - } - - needs_update = false; - return true; -} - -void GL_Program::destroy(void) -{ - if(handle) { - glDeleteProgram(handle); - handle = 0; - } - - uniforms.clear(); - - frag_variants.clear(); - frag_source.clear(); - frag_path = std::string(); - - vert_variants.clear(); - vert_source.clear(); - vert_path = std::string(); - - needs_update = false; -} - -std::size_t GL_Program::add_uniform(std::string_view name) -{ - for(std::size_t i = 0; i < uniforms.size(); ++i) { - if(0 == uniforms[i].name.compare(name)) { - return i; - } - } - - const std::size_t index = uniforms.size(); - uniforms.push_back(GL_Uniform()); - uniforms[index].location = -1; - uniforms[index].name = name; - return index; -} - -void GL_Program::set_variant_vert(unsigned int variant, unsigned int value) -{ - if(variant < vert_variants.size()) { - if(value != vert_variants[variant].value) { - vert_variants[variant].value = value; - needs_update = true; - } - } -} - -void GL_Program::set_variant_frag(unsigned int variant, unsigned int value) -{ - if(variant < frag_variants.size()) { - if(value != frag_variants[variant].value) { - frag_variants[variant].value = value; - needs_update = true; - } - } -} +#include "client/pch.hh"
+
+#include "client/program.hh"
+
+#include "core/utils/string.hh"
+
+// This fills up the array of source lines and figures out
+// which lines are to be dynamically resolved as variant macros
+static void parse_source(std::string_view source, std::vector<std::string>& out_lines, std::vector<GL_VariedMacro>& out_variants)
+{
+ std::string line;
+ std::istringstream stream = std::istringstream(std::string(source));
+ unsigned long line_number = 0UL;
+
+ out_lines.clear();
+ out_variants.clear();
+
+ while(std::getline(stream, line)) {
+ unsigned int macro_index = {};
+ char macro_name[128] = {};
+
+ if(std::sscanf(line.c_str(), " # pragma variant [ %u ] %127[^, \"\t\r\n]", ¯o_index, ¯o_name) == 2) {
+ if(out_variants.size() <= macro_index) {
+ out_variants.resize(macro_index + 1U);
+ }
+
+ out_variants[macro_index].name = macro_name;
+ out_variants[macro_index].line = line_number;
+ out_variants[macro_index].value = std::numeric_limits<unsigned int>::max();
+
+ out_lines.push_back(std::string());
+ line_number += 1UL;
+ }
+ else {
+ out_lines.push_back(line);
+ line_number += 1UL;
+ }
+ }
+}
+
+static GLuint compile_shader(std::string_view path, const char* source, GLenum shader_stage)
+{
+ GLuint shader = glCreateShader(shader_stage);
+ glShaderSource(shader, 1, &source, nullptr);
+ glCompileShader(shader);
+
+ GLint info_log_length;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if(info_log_length >= 2) {
+ std::basic_string<GLchar> info_log;
+ info_log.resize(info_log_length);
+ glGetShaderInfoLog(shader, info_log_length, nullptr, info_log.data());
+ spdlog::info("gl_program: {}: shader information:", path);
+ spdlog::info(info_log);
+ }
+
+ GLint compile_status;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
+
+ if(!compile_status) {
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+bool GL_Program::setup(std::string_view vpath, std::string_view fpath)
+{
+ destroy();
+
+ vert_path = std::string(vpath);
+ frag_path = std::string(fpath);
+
+ auto vfile = PHYSFS_openRead(vert_path.c_str());
+
+ if(vfile == nullptr) {
+ spdlog::warn("gl_program: {}: {}", vpath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ auto vsource = std::string(PHYSFS_fileLength(vfile), char(0x00));
+ PHYSFS_readBytes(vfile, vsource.data(), vsource.size());
+ PHYSFS_close(vfile);
+
+ auto ffile = PHYSFS_openRead(frag_path.c_str());
+
+ if(ffile == nullptr) {
+ spdlog::warn("gl_program: {}: {}", fpath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return false;
+ }
+
+ auto fsource = std::string(PHYSFS_fileLength(ffile), char(0x00));
+ PHYSFS_readBytes(ffile, fsource.data(), fsource.size());
+ PHYSFS_close(ffile);
+
+ parse_source(vsource.c_str(), vert_source, vert_variants);
+ parse_source(fsource.c_str(), frag_source, frag_variants);
+
+ needs_update = true;
+ handle = 0;
+
+ return true;
+}
+
+bool GL_Program::update(void)
+{
+ if(!needs_update) {
+ // The program is already up to
+ // date with the internal state
+ return true;
+ }
+
+ for(const auto& macro : vert_variants)
+ vert_source[macro.line] = std::format("#define {} {}", macro.name, macro.value);
+ for(const auto& macro : frag_variants)
+ frag_source[macro.line] = std::format("#define {} {}", macro.name, macro.value);
+
+ std::string vsource(utils::join(vert_source, "\r\n"));
+ std::string fsource(utils::join(frag_source, "\r\n"));
+
+ GLuint vert = compile_shader(vert_path.c_str(), vsource.c_str(), GL_VERTEX_SHADER);
+ GLuint frag = compile_shader(frag_path.c_str(), fsource.c_str(), GL_FRAGMENT_SHADER);
+
+ if(!vert || !frag) {
+ // needs_update = false;
+ glDeleteShader(frag);
+ glDeleteShader(vert);
+ return false;
+ }
+
+ handle = glCreateProgram();
+ glAttachShader(handle, vert);
+ glAttachShader(handle, frag);
+ glLinkProgram(handle);
+
+ GLint info_log_length;
+ glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ if(info_log_length >= 2) {
+ std::basic_string<GLchar> info_log;
+ info_log.resize(info_log_length);
+ glGetProgramInfoLog(handle, info_log_length, nullptr, info_log.data());
+ spdlog::info("gl_program: [{}; {}]: program information", vert, frag);
+ spdlog::info(info_log);
+ }
+
+ glDeleteShader(frag);
+ glDeleteShader(vert);
+
+ GLint link_status;
+ glGetProgramiv(handle, GL_LINK_STATUS, &link_status);
+
+ if(!link_status) {
+ // needs_update = false;
+ glDeleteProgram(handle);
+ return false;
+ }
+
+ for(auto& uniform : uniforms) {
+ // NOTE: GL seems to silently ignore invalid uniform
+ // locations (-1); should we write something into logs about this?
+ uniform.location = glGetUniformLocation(handle, uniform.name.c_str());
+ }
+
+ needs_update = false;
+ return true;
+}
+
+void GL_Program::destroy(void)
+{
+ if(handle) {
+ glDeleteProgram(handle);
+ handle = 0;
+ }
+
+ uniforms.clear();
+
+ frag_variants.clear();
+ frag_source.clear();
+ frag_path = std::string();
+
+ vert_variants.clear();
+ vert_source.clear();
+ vert_path = std::string();
+
+ needs_update = false;
+}
+
+std::size_t GL_Program::add_uniform(std::string_view name)
+{
+ for(std::size_t i = 0; i < uniforms.size(); ++i) {
+ if(0 == uniforms[i].name.compare(name)) {
+ return i;
+ }
+ }
+
+ const std::size_t index = uniforms.size();
+ uniforms.push_back(GL_Uniform());
+ uniforms[index].location = -1;
+ uniforms[index].name = name;
+ return index;
+}
+
+void GL_Program::set_variant_vert(unsigned int variant, unsigned int value)
+{
+ if(variant < vert_variants.size()) {
+ if(value != vert_variants[variant].value) {
+ vert_variants[variant].value = value;
+ needs_update = true;
+ }
+ }
+}
+
+void GL_Program::set_variant_frag(unsigned int variant, unsigned int value)
+{
+ if(variant < frag_variants.size()) {
+ if(value != frag_variants[variant].value) {
+ frag_variants[variant].value = value;
+ needs_update = true;
+ }
+ }
+}
diff --git a/game/client/program.hh b/game/client/program.hh index af78513..bf35417 100644 --- a/game/client/program.hh +++ b/game/client/program.hh @@ -1,34 +1,34 @@ -#pragma once - -struct GL_VariedMacro final { - std::string name; - unsigned long line; - unsigned int value; -}; - -struct GL_Uniform final { - std::string name; - GLint location; -}; - -class GL_Program final { -public: - bool setup(std::string_view vpath, std::string_view fpath); - void destroy(void); - bool update(void); - - std::size_t add_uniform(std::string_view name); - void set_variant_vert(unsigned int variant, unsigned int value); - void set_variant_frag(unsigned int variant, unsigned int value); - -public: - std::string vert_path; - std::string frag_path; - std::vector<std::string> vert_source; - std::vector<std::string> frag_source; - std::vector<GL_VariedMacro> vert_variants; - std::vector<GL_VariedMacro> frag_variants; - std::vector<GL_Uniform> uniforms; - bool needs_update; - GLuint handle; -}; +#pragma once
+
+struct GL_VariedMacro final {
+ std::string name;
+ unsigned long line;
+ unsigned int value;
+};
+
+struct GL_Uniform final {
+ std::string name;
+ GLint location;
+};
+
+class GL_Program final {
+public:
+ bool setup(std::string_view vpath, std::string_view fpath);
+ void destroy(void);
+ bool update(void);
+
+ std::size_t add_uniform(std::string_view name);
+ void set_variant_vert(unsigned int variant, unsigned int value);
+ void set_variant_frag(unsigned int variant, unsigned int value);
+
+public:
+ std::string vert_path;
+ std::string frag_path;
+ std::vector<std::string> vert_source;
+ std::vector<std::string> frag_source;
+ std::vector<GL_VariedMacro> vert_variants;
+ std::vector<GL_VariedMacro> frag_variants;
+ std::vector<GL_Uniform> uniforms;
+ bool needs_update;
+ GLuint handle;
+};
diff --git a/game/client/receive.cc b/game/client/receive.cc index a253911..d6dcac0 100644 --- a/game/client/receive.cc +++ b/game/client/receive.cc @@ -1,192 +1,192 @@ -#include "client/pch.hh" - -#include "client/receive.hh" - -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/protocol.hh" - -#include "client/entity/factory.hh" - -#include "client/gui/chat.hh" -#include "client/gui/gui_screen.hh" -#include "client/gui/message_box.hh" -#include "client/gui/window_title.hh" - -#include "client/sound/sound.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -static bool synchronize_entity_id(world::Dimension* dimension, entt::entity entity) -{ - if(dimension->entities.valid(entity)) { - // Entity ID already exists - return true; - } - - auto created = dimension->entities.create(entity); - - if(created == entity) { - // Synchronized successfully - return true; - } - - session::disconnect("protocol.entity_id_desync"); - spdlog::critical("receive: entity desync: network {} resolved as client {}", static_cast<std::uint64_t>(entity), - static_cast<std::uint64_t>(created)); - - gui::message_box::reset(); - gui::message_box::set_title("disconnected.disconnected"); - gui::message_box::set_subtitle("protocol.entity_id_desync"); - gui::message_box::add_button("disconnected.back", [](void) { - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - }); - - globals::gui_screen = GUI_MESSAGE_BOX; - - return false; -} - -static void on_dimension_info_packet(const protocol::DimensionInfo& packet) -{ - if(session::peer) { - if(globals::dimension) { - delete globals::dimension; - globals::dimension = nullptr; - globals::player = entt::null; - } - - globals::dimension = new world::Dimension(packet.name.c_str(), packet.gravity); - } -} - -static void on_chunk_voxels_packet(const protocol::ChunkVoxels& packet) -{ - if(session::peer && globals::dimension) { - auto chunk = globals::dimension->create_chunk(packet.chunk); - chunk->set_voxels(packet.voxels); - - world::ChunkUpdateEvent event; - event.dimension = globals::dimension; - event.cpos = packet.chunk; - event.chunk = chunk; - - globals::dispatcher.trigger(event); - - return; - } -} - -static void on_entity_head_packet(const protocol::EntityHead& packet) -{ - if(session::peer && globals::dimension) { - if(synchronize_entity_id(globals::dimension, packet.entity)) { - auto& component = globals::dimension->entities.get_or_emplace<entity::Head>(packet.entity); - auto& prevcomp = globals::dimension->entities.get_or_emplace<entity::client::HeadPrev>(packet.entity); - - // Store the previous component state - prevcomp.angles = component.angles; - prevcomp.offset = component.offset; - - // Assign the new component state - component.angles = packet.angles; - } - } -} - -static void on_entity_transform_packet(const protocol::EntityTransform& packet) -{ - if(session::peer && globals::dimension) { - if(synchronize_entity_id(globals::dimension, packet.entity)) { - auto& component = globals::dimension->entities.get_or_emplace<entity::Transform>(packet.entity); - auto& prevcomp = globals::dimension->entities.get_or_emplace<entity::client::TransformPrev>(packet.entity); - - // Store the previous component state - prevcomp.angles = component.angles; - prevcomp.chunk = component.chunk; - prevcomp.local = component.local; - - // Assign the new component state - component.angles = packet.angles; - component.chunk = packet.chunk; - component.local = packet.local; - } - } -} - -static void on_entity_velocity_packet(const protocol::EntityVelocity& packet) -{ - if(session::peer && globals::dimension) { - if(synchronize_entity_id(globals::dimension, packet.entity)) { - auto& component = globals::dimension->entities.get_or_emplace<entity::Velocity>(packet.entity); - component.value = packet.value; - } - } -} - -static void on_entity_player_packet(const protocol::EntityPlayer& packet) -{ - if(session::peer && globals::dimension) { - if(synchronize_entity_id(globals::dimension, packet.entity)) { - entity::client::create_player(globals::dimension, packet.entity); - } - } -} - -static void on_spawn_player_packet(const protocol::SpawnPlayer& packet) -{ - if(session::peer && globals::dimension) { - if(synchronize_entity_id(globals::dimension, packet.entity)) { - entity::client::create_player(globals::dimension, packet.entity); - - globals::player = packet.entity; - globals::gui_screen = GUI_SCREEN_NONE; - - gui::client_chat::refresh_timings(); - - gui::window_title::update(); - } - } -} - -static void on_remove_entity_packet(const protocol::RemoveEntity& packet) -{ - if(globals::dimension) { - if(packet.entity == globals::player) { - globals::player = entt::null; - } - - globals::dimension->entities.destroy(packet.entity); - } -} - -static void on_generic_sound_packet(const protocol::GenericSound& packet) -{ - sound::play_generic(packet.sound.c_str(), packet.looping, packet.pitch); -} - -static void on_entity_sound_packet(const protocol::EntitySound& packet) -{ - sound::play_entity(packet.entity, packet.sound.c_str(), packet.looping, packet.pitch); -} - -void client_receive::init(void) -{ - globals::dispatcher.sink<protocol::DimensionInfo>().connect<&on_dimension_info_packet>(); - globals::dispatcher.sink<protocol::ChunkVoxels>().connect<&on_chunk_voxels_packet>(); - globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>(); - globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>(); - globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>(); - globals::dispatcher.sink<protocol::EntityPlayer>().connect<&on_entity_player_packet>(); - globals::dispatcher.sink<protocol::SpawnPlayer>().connect<&on_spawn_player_packet>(); - globals::dispatcher.sink<protocol::RemoveEntity>().connect<&on_remove_entity_packet>(); - globals::dispatcher.sink<protocol::GenericSound>().connect<&on_generic_sound_packet>(); - globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>(); -} +#include "client/pch.hh"
+
+#include "client/receive.hh"
+
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/entity/factory.hh"
+
+#include "client/gui/chat.hh"
+#include "client/gui/gui_screen.hh"
+#include "client/gui/message_box.hh"
+#include "client/gui/window_title.hh"
+
+#include "client/sound/sound.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+static bool synchronize_entity_id(world::Dimension* dimension, entt::entity entity)
+{
+ if(dimension->entities.valid(entity)) {
+ // Entity ID already exists
+ return true;
+ }
+
+ auto created = dimension->entities.create(entity);
+
+ if(created == entity) {
+ // Synchronized successfully
+ return true;
+ }
+
+ session::disconnect("protocol.entity_id_desync");
+ spdlog::critical("receive: entity desync: network {} resolved as client {}", static_cast<std::uint64_t>(entity),
+ static_cast<std::uint64_t>(created));
+
+ gui::message_box::reset();
+ gui::message_box::set_title("disconnected.disconnected");
+ gui::message_box::set_subtitle("protocol.entity_id_desync");
+ gui::message_box::add_button("disconnected.back", [](void) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ });
+
+ globals::gui_screen = GUI_MESSAGE_BOX;
+
+ return false;
+}
+
+static void on_dimension_info_packet(const protocol::DimensionInfo& packet)
+{
+ if(session::peer) {
+ if(globals::dimension) {
+ delete globals::dimension;
+ globals::dimension = nullptr;
+ globals::player = entt::null;
+ }
+
+ globals::dimension = new world::Dimension(packet.name.c_str(), packet.gravity);
+ }
+}
+
+static void on_chunk_voxels_packet(const protocol::ChunkVoxels& packet)
+{
+ if(session::peer && globals::dimension) {
+ auto chunk = globals::dimension->create_chunk(packet.chunk);
+ chunk->set_voxels(packet.voxels);
+
+ world::ChunkUpdateEvent event;
+ event.dimension = globals::dimension;
+ event.cpos = packet.chunk;
+ event.chunk = chunk;
+
+ globals::dispatcher.trigger(event);
+
+ return;
+ }
+}
+
+static void on_entity_head_packet(const protocol::EntityHead& packet)
+{
+ if(session::peer && globals::dimension) {
+ if(synchronize_entity_id(globals::dimension, packet.entity)) {
+ auto& component = globals::dimension->entities.get_or_emplace<entity::Head>(packet.entity);
+ auto& prevcomp = globals::dimension->entities.get_or_emplace<entity::client::HeadPrev>(packet.entity);
+
+ // Store the previous component state
+ prevcomp.angles = component.angles;
+ prevcomp.offset = component.offset;
+
+ // Assign the new component state
+ component.angles = packet.angles;
+ }
+ }
+}
+
+static void on_entity_transform_packet(const protocol::EntityTransform& packet)
+{
+ if(session::peer && globals::dimension) {
+ if(synchronize_entity_id(globals::dimension, packet.entity)) {
+ auto& component = globals::dimension->entities.get_or_emplace<entity::Transform>(packet.entity);
+ auto& prevcomp = globals::dimension->entities.get_or_emplace<entity::client::TransformPrev>(packet.entity);
+
+ // Store the previous component state
+ prevcomp.angles = component.angles;
+ prevcomp.chunk = component.chunk;
+ prevcomp.local = component.local;
+
+ // Assign the new component state
+ component.angles = packet.angles;
+ component.chunk = packet.chunk;
+ component.local = packet.local;
+ }
+ }
+}
+
+static void on_entity_velocity_packet(const protocol::EntityVelocity& packet)
+{
+ if(session::peer && globals::dimension) {
+ if(synchronize_entity_id(globals::dimension, packet.entity)) {
+ auto& component = globals::dimension->entities.get_or_emplace<entity::Velocity>(packet.entity);
+ component.value = packet.value;
+ }
+ }
+}
+
+static void on_entity_player_packet(const protocol::EntityPlayer& packet)
+{
+ if(session::peer && globals::dimension) {
+ if(synchronize_entity_id(globals::dimension, packet.entity)) {
+ entity::client::create_player(globals::dimension, packet.entity);
+ }
+ }
+}
+
+static void on_spawn_player_packet(const protocol::SpawnPlayer& packet)
+{
+ if(session::peer && globals::dimension) {
+ if(synchronize_entity_id(globals::dimension, packet.entity)) {
+ entity::client::create_player(globals::dimension, packet.entity);
+
+ globals::player = packet.entity;
+ globals::gui_screen = GUI_SCREEN_NONE;
+
+ gui::client_chat::refresh_timings();
+
+ gui::window_title::update();
+ }
+ }
+}
+
+static void on_remove_entity_packet(const protocol::RemoveEntity& packet)
+{
+ if(globals::dimension) {
+ if(packet.entity == globals::player) {
+ globals::player = entt::null;
+ }
+
+ globals::dimension->entities.destroy(packet.entity);
+ }
+}
+
+static void on_generic_sound_packet(const protocol::GenericSound& packet)
+{
+ sound::play_generic(packet.sound.c_str(), packet.looping, packet.pitch);
+}
+
+static void on_entity_sound_packet(const protocol::EntitySound& packet)
+{
+ sound::play_entity(packet.entity, packet.sound.c_str(), packet.looping, packet.pitch);
+}
+
+void client_receive::init(void)
+{
+ globals::dispatcher.sink<protocol::DimensionInfo>().connect<&on_dimension_info_packet>();
+ globals::dispatcher.sink<protocol::ChunkVoxels>().connect<&on_chunk_voxels_packet>();
+ globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>();
+ globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>();
+ globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>();
+ globals::dispatcher.sink<protocol::EntityPlayer>().connect<&on_entity_player_packet>();
+ globals::dispatcher.sink<protocol::SpawnPlayer>().connect<&on_spawn_player_packet>();
+ globals::dispatcher.sink<protocol::RemoveEntity>().connect<&on_remove_entity_packet>();
+ globals::dispatcher.sink<protocol::GenericSound>().connect<&on_generic_sound_packet>();
+ globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>();
+}
diff --git a/game/client/receive.hh b/game/client/receive.hh index d675392..d958581 100644 --- a/game/client/receive.hh +++ b/game/client/receive.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace client_receive -{ -void init(void); -} // namespace client_receive +#pragma once
+
+namespace client_receive
+{
+void init(void);
+} // namespace client_receive
diff --git a/game/client/resource/sound_effect.cc b/game/client/resource/sound_effect.cc index 4fb3e82..d6cc7c5 100644 --- a/game/client/resource/sound_effect.cc +++ b/game/client/resource/sound_effect.cc @@ -1,90 +1,90 @@ -#include "client/pch.hh" - -#include "client/resource/sound_effect.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/physfs.hh" - -#include "client/globals.hh" - -static std::size_t drwav_read_physfs(void* file, void* output, std::size_t count) -{ - return static_cast<std::size_t>(PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(file), output, count)); -} - -static drwav_bool32 drwav_seek_physfs(void* file, int offset, drwav_seek_origin origin) -{ - if(origin == drwav_seek_origin_current) { - return PHYSFS_seek(reinterpret_cast<PHYSFS_File*>(file), PHYSFS_tell(reinterpret_cast<PHYSFS_File*>(file)) + offset); - } - else { - return PHYSFS_seek(reinterpret_cast<PHYSFS_File*>(file), offset); - } -} - -static const void* sound_effect_load_func(const char* name, std::uint32_t flags) -{ - assert(name); - - if(globals::sound_ctx == nullptr) { - // Sound is disabled - return nullptr; - } - - auto file = PHYSFS_openRead(name); - - if(file == nullptr) { - spdlog::warn("sfx: {}: {}", name, utils::physfs_error()); - return nullptr; - } - - drwav wav_info; - - if(!drwav_init(&wav_info, &drwav_read_physfs, &drwav_seek_physfs, file, nullptr)) { - spdlog::warn("sfx: {}: drwav_init failed", name); - PHYSFS_close(file); - return nullptr; - } - - if(wav_info.channels != 1) { - spdlog::warn("sfx: {}: only mono sound files are allowed", name); - drwav_uninit(&wav_info); - PHYSFS_close(file); - return nullptr; - } - - auto samples = new ALshort[wav_info.totalPCMFrameCount]; - auto count = drwav_read_pcm_frames_s16(&wav_info, wav_info.totalPCMFrameCount, reinterpret_cast<drwav_int16*>(samples)); - auto sample_rate = static_cast<ALsizei>(wav_info.sampleRate); - auto length = static_cast<ALsizei>(count * sizeof(ALshort)); - - drwav_uninit(&wav_info); - PHYSFS_close(file); - - auto new_resource = new SoundEffect(); - new_resource->name = std::string(name); - - alGenBuffers(1, &new_resource->buffer); - alBufferData(new_resource->buffer, AL_FORMAT_MONO16, samples, length, sample_rate); - - delete[] samples; - - return new_resource; -} - -static void sound_effect_free_func(const void* resource) -{ - assert(resource); - - auto sound_effect = reinterpret_cast<const SoundEffect*>(resource); - - alDeleteBuffers(1, &sound_effect->buffer); - - delete sound_effect; -} - -void SoundEffect::register_resource(void) -{ - resource::register_loader<SoundEffect>(&sound_effect_load_func, &sound_effect_free_func); -} +#include "client/pch.hh"
+
+#include "client/resource/sound_effect.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/physfs.hh"
+
+#include "client/globals.hh"
+
+static std::size_t drwav_read_physfs(void* file, void* output, std::size_t count)
+{
+ return static_cast<std::size_t>(PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(file), output, count));
+}
+
+static drwav_bool32 drwav_seek_physfs(void* file, int offset, drwav_seek_origin origin)
+{
+ if(origin == drwav_seek_origin_current) {
+ return PHYSFS_seek(reinterpret_cast<PHYSFS_File*>(file), PHYSFS_tell(reinterpret_cast<PHYSFS_File*>(file)) + offset);
+ }
+ else {
+ return PHYSFS_seek(reinterpret_cast<PHYSFS_File*>(file), offset);
+ }
+}
+
+static const void* sound_effect_load_func(const char* name, std::uint32_t flags)
+{
+ assert(name);
+
+ if(globals::sound_ctx == nullptr) {
+ // Sound is disabled
+ return nullptr;
+ }
+
+ auto file = PHYSFS_openRead(name);
+
+ if(file == nullptr) {
+ spdlog::warn("sfx: {}: {}", name, utils::physfs_error());
+ return nullptr;
+ }
+
+ drwav wav_info;
+
+ if(!drwav_init(&wav_info, &drwav_read_physfs, &drwav_seek_physfs, file, nullptr)) {
+ spdlog::warn("sfx: {}: drwav_init failed", name);
+ PHYSFS_close(file);
+ return nullptr;
+ }
+
+ if(wav_info.channels != 1) {
+ spdlog::warn("sfx: {}: only mono sound files are allowed", name);
+ drwav_uninit(&wav_info);
+ PHYSFS_close(file);
+ return nullptr;
+ }
+
+ auto samples = new ALshort[wav_info.totalPCMFrameCount];
+ auto count = drwav_read_pcm_frames_s16(&wav_info, wav_info.totalPCMFrameCount, reinterpret_cast<drwav_int16*>(samples));
+ auto sample_rate = static_cast<ALsizei>(wav_info.sampleRate);
+ auto length = static_cast<ALsizei>(count * sizeof(ALshort));
+
+ drwav_uninit(&wav_info);
+ PHYSFS_close(file);
+
+ auto new_resource = new SoundEffect();
+ new_resource->name = std::string(name);
+
+ alGenBuffers(1, &new_resource->buffer);
+ alBufferData(new_resource->buffer, AL_FORMAT_MONO16, samples, length, sample_rate);
+
+ delete[] samples;
+
+ return new_resource;
+}
+
+static void sound_effect_free_func(const void* resource)
+{
+ assert(resource);
+
+ auto sound_effect = reinterpret_cast<const SoundEffect*>(resource);
+
+ alDeleteBuffers(1, &sound_effect->buffer);
+
+ delete sound_effect;
+}
+
+void SoundEffect::register_resource(void)
+{
+ resource::register_loader<SoundEffect>(&sound_effect_load_func, &sound_effect_free_func);
+}
diff --git a/game/client/resource/sound_effect.hh b/game/client/resource/sound_effect.hh index f2db33b..ab68ed2 100644 --- a/game/client/resource/sound_effect.hh +++ b/game/client/resource/sound_effect.hh @@ -1,8 +1,8 @@ -#pragma once - -struct SoundEffect final { - static void register_resource(void); - - std::string name; - ALuint buffer; -}; +#pragma once
+
+struct SoundEffect final {
+ static void register_resource(void);
+
+ std::string name;
+ ALuint buffer;
+};
diff --git a/game/client/resource/texture_gui.cc b/game/client/resource/texture_gui.cc index beb2f54..be8a6ed 100644 --- a/game/client/resource/texture_gui.cc +++ b/game/client/resource/texture_gui.cc @@ -1,83 +1,83 @@ -#include "client/pch.hh" - -#include "client/resource/texture_gui.hh" - -#include "core/resource/image.hh" -#include "core/resource/resource.hh" - -static const void* texture_gui_load_func(const char* name, std::uint32_t flags) -{ - assert(name); - - unsigned int image_load_flags = 0U; - - if(flags & TEXTURE_GUI_LOAD_VFLIP) { - image_load_flags |= IMAGE_LOAD_FLIP; - } - - if(flags & TEXTURE_GUI_LOAD_GRAYSCALE) { - image_load_flags |= IMAGE_LOAD_GRAY; - } - - if(auto image = resource::load<Image>(name, image_load_flags)) { - GLuint gl_texture; - - glGenTextures(1, &gl_texture); - glBindTexture(GL_TEXTURE_2D, gl_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image->size.x, image->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); - - if(flags & TEXTURE_GUI_LOAD_CLAMP_S) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - } - - if(flags & TEXTURE_GUI_LOAD_CLAMP_T) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - } - - if(flags & TEXTURE_GUI_LOAD_LINEAR_MAG) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - - if(flags & TEXTURE_GUI_LOAD_LINEAR_MIN) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } - - auto new_resource = new TextureGUI(); - new_resource->handle = static_cast<ImTextureID>(gl_texture); - new_resource->size.x = image->size.x; - new_resource->size.y = image->size.y; - - return new_resource; - } - - return nullptr; -} - -static void texture_gui_free_func(const void* resource) -{ - assert(resource); - - auto texture_gui = reinterpret_cast<const TextureGUI*>(resource); - auto gl_texture = static_cast<GLuint>(texture_gui->handle); - - glDeleteTextures(1, &gl_texture); - - delete texture_gui; -} - -void TextureGUI::register_resource(void) -{ - resource::register_loader<TextureGUI>(&texture_gui_load_func, &texture_gui_free_func); -} +#include "client/pch.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+static const void* texture_gui_load_func(const char* name, std::uint32_t flags)
+{
+ assert(name);
+
+ unsigned int image_load_flags = 0U;
+
+ if(flags & TEXTURE_GUI_LOAD_VFLIP) {
+ image_load_flags |= IMAGE_LOAD_FLIP;
+ }
+
+ if(flags & TEXTURE_GUI_LOAD_GRAYSCALE) {
+ image_load_flags |= IMAGE_LOAD_GRAY;
+ }
+
+ if(auto image = resource::load<Image>(name, image_load_flags)) {
+ GLuint gl_texture;
+
+ glGenTextures(1, &gl_texture);
+ glBindTexture(GL_TEXTURE_2D, gl_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image->size.x, image->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);
+
+ if(flags & TEXTURE_GUI_LOAD_CLAMP_S) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ }
+ else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ }
+
+ if(flags & TEXTURE_GUI_LOAD_CLAMP_T) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ if(flags & TEXTURE_GUI_LOAD_LINEAR_MAG) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+ else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+
+ if(flags & TEXTURE_GUI_LOAD_LINEAR_MIN) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+ else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ }
+
+ auto new_resource = new TextureGUI();
+ new_resource->handle = static_cast<ImTextureID>(gl_texture);
+ new_resource->size.x = image->size.x;
+ new_resource->size.y = image->size.y;
+
+ return new_resource;
+ }
+
+ return nullptr;
+}
+
+static void texture_gui_free_func(const void* resource)
+{
+ assert(resource);
+
+ auto texture_gui = reinterpret_cast<const TextureGUI*>(resource);
+ auto gl_texture = static_cast<GLuint>(texture_gui->handle);
+
+ glDeleteTextures(1, &gl_texture);
+
+ delete texture_gui;
+}
+
+void TextureGUI::register_resource(void)
+{
+ resource::register_loader<TextureGUI>(&texture_gui_load_func, &texture_gui_free_func);
+}
diff --git a/game/client/resource/texture_gui.hh b/game/client/resource/texture_gui.hh index 2d42c83..9ce0535 100644 --- a/game/client/resource/texture_gui.hh +++ b/game/client/resource/texture_gui.hh @@ -1,15 +1,15 @@ -#pragma once - -constexpr static unsigned int TEXTURE_GUI_LOAD_CLAMP_S = 0x0001; -constexpr static unsigned int TEXTURE_GUI_LOAD_CLAMP_T = 0x0002; -constexpr static unsigned int TEXTURE_GUI_LOAD_LINEAR_MAG = 0x0004; -constexpr static unsigned int TEXTURE_GUI_LOAD_LINEAR_MIN = 0x0008; -constexpr static unsigned int TEXTURE_GUI_LOAD_VFLIP = 0x0010; -constexpr static unsigned int TEXTURE_GUI_LOAD_GRAYSCALE = 0x0020; - -struct TextureGUI final { - static void register_resource(void); - - ImTextureID handle; - glm::ivec2 size; -}; +#pragma once
+
+constexpr static unsigned int TEXTURE_GUI_LOAD_CLAMP_S = 0x0001;
+constexpr static unsigned int TEXTURE_GUI_LOAD_CLAMP_T = 0x0002;
+constexpr static unsigned int TEXTURE_GUI_LOAD_LINEAR_MAG = 0x0004;
+constexpr static unsigned int TEXTURE_GUI_LOAD_LINEAR_MIN = 0x0008;
+constexpr static unsigned int TEXTURE_GUI_LOAD_VFLIP = 0x0010;
+constexpr static unsigned int TEXTURE_GUI_LOAD_GRAYSCALE = 0x0020;
+
+struct TextureGUI final {
+ static void register_resource(void);
+
+ ImTextureID handle;
+ glm::ivec2 size;
+};
diff --git a/game/client/screenshot.cc b/game/client/screenshot.cc index 9b573ef..87ab5e7 100644 --- a/game/client/screenshot.cc +++ b/game/client/screenshot.cc @@ -1,86 +1,86 @@ -#include "client/pch.hh" - -#include "client/screenshot.hh" - -#include "core/io/config_map.hh" - -#include "core/utils/epoch.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/chat.hh" -#include "client/gui/language.hh" -#include "client/gui/settings.hh" - -#include "client/io/glfw.hh" - -#include "client/globals.hh" -#include "client/toggles.hh" - -static config::KeyBind screenshot_key(GLFW_KEY_F2); - -static void stbi_png_physfs_callback(void* context, void* data, int size) -{ - PHYSFS_writeBytes(reinterpret_cast<PHYSFS_File*>(context), data, size); -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(!globals::gui_keybind_ptr && !toggles::is_sequence_await) { - if(screenshot_key.equals(event.key) && (event.action == GLFW_PRESS)) { - screenshot::take(); - return; - } - } -} - -void screenshot::init(void) -{ - globals::client_config.add_value("screenshot.key", screenshot_key); - - settings::add_keybind(0, screenshot_key, settings_location::KEYBOARD_MISC, "key.screenshot"); - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); -} - -void screenshot::take(void) -{ - auto stride = 3 * globals::width; - auto length = 3 * globals::width * globals::height; - auto pixels = new std::byte[length]; - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - GLint old_pack_alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &old_pack_alignment); - - // The window can be of any size, including irregular - // values such as, say 641x480, while there is a default - // alignment value of sorts that might result in a corrupted - // image; we set GL_PACK_ALIGNMENT to 1, enabling byte-alignment - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - glReadPixels(0, 0, globals::width, globals::height, GL_RGB, GL_UNSIGNED_BYTE, pixels); - - // Restore the old pack alignment value - glPixelStorei(GL_PACK_ALIGNMENT, old_pack_alignment); - - const auto directory = std::string("screenshots"); - const auto filename = std::format("{}.png", utils::unix_microseconds()); - const auto filepath = std::format("{}/{}", directory, filename); - - PHYSFS_mkdir(directory.c_str()); - - if(auto file = PHYSFS_openWrite(filepath.c_str())) { - stbi_flip_vertically_on_write(true); - stbi_write_png_to_func(&stbi_png_physfs_callback, file, globals::width, globals::height, 3, pixels, stride); - - spdlog::info("screenshot: wrote {}", filepath); - - gui::client_chat::print(std::format("{} {}", gui::language::resolve("chat.screenshot_message"), filename)); - - PHYSFS_close(file); - } - - delete[] pixels; -} +#include "client/pch.hh"
+
+#include "client/screenshot.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/chat.hh"
+#include "client/gui/language.hh"
+#include "client/gui/settings.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/globals.hh"
+#include "client/toggles.hh"
+
+static config::KeyBind screenshot_key(GLFW_KEY_F2);
+
+static void stbi_png_physfs_callback(void* context, void* data, int size)
+{
+ PHYSFS_writeBytes(reinterpret_cast<PHYSFS_File*>(context), data, size);
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(!globals::gui_keybind_ptr && !toggles::is_sequence_await) {
+ if(screenshot_key.equals(event.key) && (event.action == GLFW_PRESS)) {
+ screenshot::take();
+ return;
+ }
+ }
+}
+
+void screenshot::init(void)
+{
+ globals::client_config.add_value("screenshot.key", screenshot_key);
+
+ settings::add_keybind(0, screenshot_key, settings_location::KEYBOARD_MISC, "key.screenshot");
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+}
+
+void screenshot::take(void)
+{
+ auto stride = 3 * globals::width;
+ auto length = 3 * globals::width * globals::height;
+ auto pixels = new std::byte[length];
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ GLint old_pack_alignment;
+ glGetIntegerv(GL_PACK_ALIGNMENT, &old_pack_alignment);
+
+ // The window can be of any size, including irregular
+ // values such as, say 641x480, while there is a default
+ // alignment value of sorts that might result in a corrupted
+ // image; we set GL_PACK_ALIGNMENT to 1, enabling byte-alignment
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+
+ glReadPixels(0, 0, globals::width, globals::height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+
+ // Restore the old pack alignment value
+ glPixelStorei(GL_PACK_ALIGNMENT, old_pack_alignment);
+
+ const auto directory = std::string("screenshots");
+ const auto filename = std::format("{}.png", utils::unix_microseconds());
+ const auto filepath = std::format("{}/{}", directory, filename);
+
+ PHYSFS_mkdir(directory.c_str());
+
+ if(auto file = PHYSFS_openWrite(filepath.c_str())) {
+ stbi_flip_vertically_on_write(true);
+ stbi_write_png_to_func(&stbi_png_physfs_callback, file, globals::width, globals::height, 3, pixels, stride);
+
+ spdlog::info("screenshot: wrote {}", filepath);
+
+ gui::client_chat::print(std::format("{} {}", gui::language::resolve("chat.screenshot_message"), filename));
+
+ PHYSFS_close(file);
+ }
+
+ delete[] pixels;
+}
diff --git a/game/client/screenshot.hh b/game/client/screenshot.hh index fecbd2f..b76507e 100644 --- a/game/client/screenshot.hh +++ b/game/client/screenshot.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace screenshot -{ -void init(void); -void take(void); -} // namespace screenshot +#pragma once
+
+namespace screenshot
+{
+void init(void);
+void take(void);
+} // namespace screenshot
diff --git a/game/client/session.cc b/game/client/session.cc index 96bd28a..7e0d36c 100644 --- a/game/client/session.cc +++ b/game/client/session.cc @@ -1,307 +1,307 @@ -#include "client/pch.hh" - -#include "client/session.hh" - -#include "core/config/string.hh" - -#include "core/math/crc64.hh" - -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/item_registry.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "client/entity/camera.hh" - -#include "client/gui/chat.hh" -#include "client/gui/gui_screen.hh" -#include "client/gui/message_box.hh" -#include "client/gui/progress_bar.hh" -#include "client/gui/window_title.hh" - -#include "client/world/chunk_visibility.hh" - -#include "client/game.hh" -#include "client/globals.hh" - -ENetPeer* session::peer = nullptr; -std::uint16_t session::client_index = UINT16_MAX; -std::uint64_t session::client_identity = UINT64_MAX; - -static std::uint64_t server_password_hash = UINT64_MAX; - -static void set_fixed_tickrate(std::uint16_t tickrate) -{ - globals::fixed_frametime_us = 1000000U / math::clamp<std::uint64_t>(tickrate, 10U, 300U); - globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f; - globals::fixed_accumulator = 0; -} - -static void on_login_response_packet(const protocol::LoginResponse& packet) -{ - spdlog::info("session: assigned client_index={}", packet.client_index); - spdlog::info("session: assigned client_identity={}", packet.client_identity); - spdlog::info("session: server ticks at {} TPS", packet.server_tickrate); - - session::client_index = packet.client_index; - session::client_identity = packet.client_identity; - - set_fixed_tickrate(packet.server_tickrate); - - gui::progress_bar::set_title("connecting.loading_world"); -} - -static void on_disconnect_packet(const protocol::Disconnect& packet) -{ - enet_peer_disconnect(session::peer, 0); - - spdlog::info("session: disconnected: {}", packet.reason); - - gui::client_chat::clear(); - - session::peer = nullptr; - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = UINT64_MAX; - - delete globals::dimension; - globals::player = entt::null; - globals::dimension = nullptr; - - gui::message_box::reset(); - gui::message_box::set_title("disconnected.disconnected"); - gui::message_box::set_subtitle(packet.reason.c_str()); - gui::message_box::add_button("disconnected.back", [](void) { - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - }); - - globals::gui_screen = GUI_MESSAGE_BOX; -} - -static void on_set_voxel_packet(const protocol::SetVoxel& packet) -{ - auto cpos = coord::to_chunk(packet.vpos); - auto lpos = coord::to_local(packet.vpos); - auto index = coord::to_index(lpos); - - if(auto chunk = globals::dimension->find_chunk(cpos)) { - if(chunk->get_voxel(index) != packet.voxel) { - chunk->set_voxel(packet.voxel, index); - - world::ChunkUpdateEvent event; - event.dimension = globals::dimension; - event.chunk = chunk; - event.cpos = cpos; - - // Send a generic ChunkUpdate event to shake - // up the mesher; directly calling world::set_voxel - // here would result in a networked feedback loop - // caused by event handler below tripping - globals::dispatcher.trigger(event); - } - } -} - -// NOTE: [session] is a good place for this since [receive] -// handles entity data sent by the server and [session] handles -// everything else network related that is not player movement -static void on_voxel_set(const world::VoxelSetEvent& event) -{ - if(session::peer) { - // Propagate changes to the server - // FIXME: should we also validate things here or wait for the server to do so - protocol::SetVoxel packet; - packet.vpos = coord::to_voxel(event.cpos, event.lpos); - packet.voxel = event.voxel; - - protocol::send(session::peer, protocol::encode(packet)); - } -} - -void session::init(void) -{ - session::peer = nullptr; - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = UINT64_MAX; - - globals::dispatcher.sink<protocol::LoginResponse>().connect<&on_login_response_packet>(); - globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>(); - globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>(); - - globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void session::shutdown(void) -{ - session::disconnect("protocol.client_shutdown"); - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; -} - -void session::invalidate(void) -{ - if(session::peer) { - enet_peer_reset(session::peer); - - gui::message_box::reset(); - gui::message_box::set_title("disconnected.disconnected"); - gui::message_box::set_subtitle("enet.peer_connection_timeout"); - gui::message_box::add_button("disconnected.back", [](void) { - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - }); - - globals::gui_screen = GUI_MESSAGE_BOX; - } - - gui::client_chat::clear(); - - session::peer = nullptr; - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = UINT64_MAX; - - delete globals::dimension; - globals::player = entt::null; - globals::dimension = nullptr; -} - -void session::connect(std::string_view host, std::uint16_t port, std::string_view password) -{ - ENetAddress address; - enet_address_set_host(&address, std::string(host).c_str()); - address.port = port; - - session::peer = enet_host_connect(globals::client_host, &address, 1, 0); - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = math::crc64(password.data(), password.size()); - - if(!session::peer) { - server_password_hash = UINT64_MAX; - - gui::message_box::reset(); - gui::message_box::set_title("disconnected.disconnected"); - gui::message_box::set_subtitle("enet.peer_connection_failed"); - gui::message_box::add_button("disconnected.back", [](void) { - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - }); - - globals::gui_screen = GUI_MESSAGE_BOX; - - return; - } - - gui::progress_bar::reset(); - gui::progress_bar::set_title("connecting.connecting"); - gui::progress_bar::set_button("connecting.cancel_button", [](void) { - enet_peer_disconnect(session::peer, 0); - - session::peer = nullptr; - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = UINT64_MAX; - - delete globals::dimension; - globals::player = entt::null; - globals::dimension = nullptr; - - globals::gui_screen = GUI_PLAY_MENU; - }); - - globals::gui_screen = GUI_PROGRESS_BAR; -} - -void session::disconnect(std::string_view reason) -{ - if(session::peer) { - protocol::Disconnect packet; - packet.reason = std::string(reason); - - protocol::send(session::peer, protocol::encode(packet)); - - enet_host_flush(globals::client_host); - enet_host_service(globals::client_host, nullptr, 50); - enet_peer_reset(session::peer); - - session::peer = nullptr; - session::client_index = UINT16_MAX; - session::client_identity = UINT64_MAX; - - globals::fixed_frametime_us = UINT64_MAX; - globals::fixed_frametime = 0.0f; - globals::fixed_accumulator = 0; - - server_password_hash = UINT64_MAX; - - delete globals::dimension; - globals::player = entt::null; - globals::dimension = nullptr; - - gui::client_chat::clear(); - } -} - -void session::send_login_request(void) -{ - protocol::LoginRequest packet; - packet.version = protocol::VERSION; - packet.voxel_registry_checksum = world::voxel_registry::calculate_checksum(); - packet.item_registry_checksum = world::item_registry::calculate_checksum(); - packet.password_hash = server_password_hash; - packet.username = client_game::username.get(); - - protocol::send(session::peer, protocol::encode(packet)); - - server_password_hash = UINT64_MAX; - - gui::progress_bar::set_title("connecting.logging_in"); - globals::gui_screen = GUI_PROGRESS_BAR; -} - -bool session::is_ingame(void) -{ - if(globals::dimension) { - return globals::dimension->entities.valid(globals::player); - } - else { - return false; - } -} +#include "client/pch.hh"
+
+#include "client/session.hh"
+
+#include "core/config/string.hh"
+
+#include "core/math/crc64.hh"
+
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/item_registry.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/gui/chat.hh"
+#include "client/gui/gui_screen.hh"
+#include "client/gui/message_box.hh"
+#include "client/gui/progress_bar.hh"
+#include "client/gui/window_title.hh"
+
+#include "client/world/chunk_visibility.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+
+ENetPeer* session::peer = nullptr;
+std::uint16_t session::client_index = UINT16_MAX;
+std::uint64_t session::client_identity = UINT64_MAX;
+
+static std::uint64_t server_password_hash = UINT64_MAX;
+
+static void set_fixed_tickrate(std::uint16_t tickrate)
+{
+ globals::fixed_frametime_us = 1000000U / math::clamp<std::uint64_t>(tickrate, 10U, 300U);
+ globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f;
+ globals::fixed_accumulator = 0;
+}
+
+static void on_login_response_packet(const protocol::LoginResponse& packet)
+{
+ spdlog::info("session: assigned client_index={}", packet.client_index);
+ spdlog::info("session: assigned client_identity={}", packet.client_identity);
+ spdlog::info("session: server ticks at {} TPS", packet.server_tickrate);
+
+ session::client_index = packet.client_index;
+ session::client_identity = packet.client_identity;
+
+ set_fixed_tickrate(packet.server_tickrate);
+
+ gui::progress_bar::set_title("connecting.loading_world");
+}
+
+static void on_disconnect_packet(const protocol::Disconnect& packet)
+{
+ enet_peer_disconnect(session::peer, 0);
+
+ spdlog::info("session: disconnected: {}", packet.reason);
+
+ gui::client_chat::clear();
+
+ session::peer = nullptr;
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = UINT64_MAX;
+
+ delete globals::dimension;
+ globals::player = entt::null;
+ globals::dimension = nullptr;
+
+ gui::message_box::reset();
+ gui::message_box::set_title("disconnected.disconnected");
+ gui::message_box::set_subtitle(packet.reason.c_str());
+ gui::message_box::add_button("disconnected.back", [](void) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ });
+
+ globals::gui_screen = GUI_MESSAGE_BOX;
+}
+
+static void on_set_voxel_packet(const protocol::SetVoxel& packet)
+{
+ auto cpos = coord::to_chunk(packet.vpos);
+ auto lpos = coord::to_local(packet.vpos);
+ auto index = coord::to_index(lpos);
+
+ if(auto chunk = globals::dimension->find_chunk(cpos)) {
+ if(chunk->get_voxel(index) != packet.voxel) {
+ chunk->set_voxel(packet.voxel, index);
+
+ world::ChunkUpdateEvent event;
+ event.dimension = globals::dimension;
+ event.chunk = chunk;
+ event.cpos = cpos;
+
+ // Send a generic ChunkUpdate event to shake
+ // up the mesher; directly calling world::set_voxel
+ // here would result in a networked feedback loop
+ // caused by event handler below tripping
+ globals::dispatcher.trigger(event);
+ }
+ }
+}
+
+// NOTE: [session] is a good place for this since [receive]
+// handles entity data sent by the server and [session] handles
+// everything else network related that is not player movement
+static void on_voxel_set(const world::VoxelSetEvent& event)
+{
+ if(session::peer) {
+ // Propagate changes to the server
+ // FIXME: should we also validate things here or wait for the server to do so
+ protocol::SetVoxel packet;
+ packet.vpos = coord::to_voxel(event.cpos, event.lpos);
+ packet.voxel = event.voxel;
+
+ protocol::send(session::peer, protocol::encode(packet));
+ }
+}
+
+void session::init(void)
+{
+ session::peer = nullptr;
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = UINT64_MAX;
+
+ globals::dispatcher.sink<protocol::LoginResponse>().connect<&on_login_response_packet>();
+ globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>();
+ globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>();
+
+ globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void session::shutdown(void)
+{
+ session::disconnect("protocol.client_shutdown");
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+}
+
+void session::invalidate(void)
+{
+ if(session::peer) {
+ enet_peer_reset(session::peer);
+
+ gui::message_box::reset();
+ gui::message_box::set_title("disconnected.disconnected");
+ gui::message_box::set_subtitle("enet.peer_connection_timeout");
+ gui::message_box::add_button("disconnected.back", [](void) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ });
+
+ globals::gui_screen = GUI_MESSAGE_BOX;
+ }
+
+ gui::client_chat::clear();
+
+ session::peer = nullptr;
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = UINT64_MAX;
+
+ delete globals::dimension;
+ globals::player = entt::null;
+ globals::dimension = nullptr;
+}
+
+void session::connect(std::string_view host, std::uint16_t port, std::string_view password)
+{
+ ENetAddress address;
+ enet_address_set_host(&address, std::string(host).c_str());
+ address.port = port;
+
+ session::peer = enet_host_connect(globals::client_host, &address, 1, 0);
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = math::crc64(password.data(), password.size());
+
+ if(!session::peer) {
+ server_password_hash = UINT64_MAX;
+
+ gui::message_box::reset();
+ gui::message_box::set_title("disconnected.disconnected");
+ gui::message_box::set_subtitle("enet.peer_connection_failed");
+ gui::message_box::add_button("disconnected.back", [](void) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ });
+
+ globals::gui_screen = GUI_MESSAGE_BOX;
+
+ return;
+ }
+
+ gui::progress_bar::reset();
+ gui::progress_bar::set_title("connecting.connecting");
+ gui::progress_bar::set_button("connecting.cancel_button", [](void) {
+ enet_peer_disconnect(session::peer, 0);
+
+ session::peer = nullptr;
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = UINT64_MAX;
+
+ delete globals::dimension;
+ globals::player = entt::null;
+ globals::dimension = nullptr;
+
+ globals::gui_screen = GUI_PLAY_MENU;
+ });
+
+ globals::gui_screen = GUI_PROGRESS_BAR;
+}
+
+void session::disconnect(std::string_view reason)
+{
+ if(session::peer) {
+ protocol::Disconnect packet;
+ packet.reason = std::string(reason);
+
+ protocol::send(session::peer, protocol::encode(packet));
+
+ enet_host_flush(globals::client_host);
+ enet_host_service(globals::client_host, nullptr, 50);
+ enet_peer_reset(session::peer);
+
+ session::peer = nullptr;
+ session::client_index = UINT16_MAX;
+ session::client_identity = UINT64_MAX;
+
+ globals::fixed_frametime_us = UINT64_MAX;
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_accumulator = 0;
+
+ server_password_hash = UINT64_MAX;
+
+ delete globals::dimension;
+ globals::player = entt::null;
+ globals::dimension = nullptr;
+
+ gui::client_chat::clear();
+ }
+}
+
+void session::send_login_request(void)
+{
+ protocol::LoginRequest packet;
+ packet.version = protocol::VERSION;
+ packet.voxel_registry_checksum = world::voxel_registry::calculate_checksum();
+ packet.item_registry_checksum = world::item_registry::calculate_checksum();
+ packet.password_hash = server_password_hash;
+ packet.username = client_game::username.get();
+
+ protocol::send(session::peer, protocol::encode(packet));
+
+ server_password_hash = UINT64_MAX;
+
+ gui::progress_bar::set_title("connecting.logging_in");
+ globals::gui_screen = GUI_PROGRESS_BAR;
+}
+
+bool session::is_ingame(void)
+{
+ if(globals::dimension) {
+ return globals::dimension->entities.valid(globals::player);
+ }
+ else {
+ return false;
+ }
+}
diff --git a/game/client/session.hh b/game/client/session.hh index f61d62d..8f4353e 100644 --- a/game/client/session.hh +++ b/game/client/session.hh @@ -1,27 +1,27 @@ -#pragma once - -namespace session -{ -extern ENetPeer* peer; -extern std::uint16_t client_index; -extern std::uint64_t client_identity; -} // namespace session - -namespace session -{ -void init(void); -void shutdown(void); -void invalidate(void); -} // namespace session - -namespace session -{ -void connect(std::string_view hostname, std::uint16_t port, std::string_view password); -void disconnect(std::string_view reason); -void send_login_request(void); -} // namespace session - -namespace session -{ -bool is_ingame(void); -} // namespace session +#pragma once
+
+namespace session
+{
+extern ENetPeer* peer;
+extern std::uint16_t client_index;
+extern std::uint64_t client_identity;
+} // namespace session
+
+namespace session
+{
+void init(void);
+void shutdown(void);
+void invalidate(void);
+} // namespace session
+
+namespace session
+{
+void connect(std::string_view hostname, std::uint16_t port, std::string_view password);
+void disconnect(std::string_view reason);
+void send_login_request(void);
+} // namespace session
+
+namespace session
+{
+bool is_ingame(void);
+} // namespace session
diff --git a/game/client/sound/sound.cc b/game/client/sound/sound.cc index e77289c..403bd3e 100644 --- a/game/client/sound/sound.cc +++ b/game/client/sound/sound.cc @@ -1,211 +1,211 @@ -#include "client/pch.hh" - -#include "client/sound/sound.hh" - -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "client/entity/camera.hh" -#include "client/entity/sound_emitter.hh" - -#include "client/gui/settings.hh" - -#include "client/resource/sound_effect.hh" - -#include "client/const.hh" -#include "client/globals.hh" -#include "client/session.hh" - -config::Float sound::volume_master(100.0f, 0.0f, 100.0f); -config::Float sound::volume_effects(100.0f, 0.0f, 100.0f); -config::Float sound::volume_music(100.0f, 0.0f, 100.0f); -config::Float sound::volume_ui(100.0f, 0.0f, 100.0f); - -static ALuint generic_source; -static ALuint player_source; -static ALuint ui_source; - -static resource_ptr<SoundEffect> sfx_generic; -static resource_ptr<SoundEffect> sfx_player; -static resource_ptr<SoundEffect> sfx_ui; - -void sound::init_config(void) -{ - globals::client_config.add_value("sound.volume_master", sound::volume_master); - globals::client_config.add_value("sound.volume_effects", sound::volume_effects); - globals::client_config.add_value("sound.volume_music", sound::volume_music); - globals::client_config.add_value("sound.volume_ui", sound::volume_ui); - - settings::add_slider(1, sound::volume_master, settings_location::SOUND, "sound.volume_master", false, "%.0f%%"); - - settings::add_slider(0, sound::volume_effects, settings_location::SOUND_LEVELS, "sound.volume_effects", false, "%.0f%%"); - settings::add_slider(1, sound::volume_music, settings_location::SOUND_LEVELS, "sound.volume_music", false, "%.0f%%"); - settings::add_slider(2, sound::volume_ui, settings_location::SOUND_LEVELS, "sound.volume_ui", false, "%.0f%%"); -} - -void sound::init(void) -{ - alGenSources(1, &generic_source); - alSourcei(generic_source, AL_SOURCE_RELATIVE, AL_TRUE); - alSource3f(generic_source, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(generic_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alGenSources(1, &player_source); - alSourcei(player_source, AL_SOURCE_RELATIVE, AL_TRUE); - alSource3f(player_source, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(player_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alGenSources(1, &ui_source); - alSourcei(ui_source, AL_SOURCE_RELATIVE, AL_TRUE); - alSource3f(ui_source, AL_POSITION, 0.0f, 0.0f, 0.0f); - alSource3f(ui_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - sfx_generic = nullptr; - sfx_player = nullptr; - sfx_ui = nullptr; -} - -void sound::init_late(void) -{ -} - -void sound::shutdown(void) -{ - sfx_ui = nullptr; - sfx_player = nullptr; - sfx_generic = nullptr; - - alDeleteBuffers(1, &ui_source); - alDeleteSources(1, &generic_source); - alDeleteSources(1, &player_source); -} - -void sound::update(void) -{ - auto effects_gain = math::clamp(0.01f * sound::volume_effects.get_value(), 0.0f, 1.0f); - alSourcef(generic_source, AL_GAIN, effects_gain); - alSourcef(player_source, AL_GAIN, effects_gain); - - auto ui_gain = math::clamp(0.01f * sound::volume_ui.get_value(), 0.0f, 1.0f); - alSourcef(ui_source, AL_GAIN, ui_gain); -} - -void sound::play_generic(std::string_view sound, bool looping, float pitch) -{ - if(sound.size()) { - sound::play_generic(resource::load<SoundEffect>(sound), looping, pitch); - } - else { - sound::play_generic(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch); - } -} - -void sound::play_entity(entt::entity entity, std::string_view sound, bool looping, float pitch) -{ - if(sound.size()) { - sound::play_entity(entity, resource::load<SoundEffect>(sound), looping, pitch); - } - else { - sound::play_entity(entity, static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch); - } -} - -void sound::play_player(std::string_view sound, bool looping, float pitch) -{ - if(sound.size()) { - sound::play_player(resource::load<SoundEffect>(sound), looping, pitch); - } - else { - sound::play_player(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch); - } -} - -void sound::play_ui(std::string_view sound, bool looping, float pitch) -{ - if(sound.size()) { - sound::play_ui(resource::load<SoundEffect>(sound), looping, pitch); - } - else { - sound::play_ui(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch); - } -} - -void sound::play_generic(resource_ptr<SoundEffect> sound, bool looping, float pitch) -{ - alSourceRewind(generic_source); - - sfx_generic = sound; - - if(sfx_generic) { - alSourcei(generic_source, AL_BUFFER, sfx_generic->buffer); - alSourcei(generic_source, AL_LOOPING, looping); - alSourcef(generic_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH)); - alSourcePlay(generic_source); - } -} - -void sound::play_entity(entt::entity entity, resource_ptr<SoundEffect> sound, bool looping, float pitch) -{ - if(globals::dimension && globals::dimension->entities.valid(entity)) { - if(auto emitter = globals::dimension->entities.try_get<entity::SoundEmitter>(entity)) { - alSourceRewind(emitter->source); - - emitter->sound = sound; - - if(emitter->sound) { - alSourcei(emitter->source, AL_BUFFER, emitter->sound->buffer); - alSourcei(emitter->source, AL_LOOPING, looping); - alSourcef(emitter->source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH)); - alSourcePlay(emitter->source); - } - } - } -} - -void sound::play_player(resource_ptr<SoundEffect> sound, bool looping, float pitch) -{ - if(sound && session::is_ingame()) { - protocol::EntitySound packet; - packet.entity = globals::player; - packet.sound = sound->name; - packet.looping = looping; - packet.pitch = pitch; - - protocol::send(session::peer, protocol::encode(packet)); - } - - alSourceRewind(player_source); - - sfx_player = sound; - - if(sfx_player) { - alSourcei(player_source, AL_BUFFER, sfx_player->buffer); - alSourcei(player_source, AL_LOOPING, looping); - alSourcef(player_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH)); - alSourcePlay(player_source); - } -} - -void sound::play_ui(resource_ptr<SoundEffect> sound, bool looping, float pitch) -{ - alSourceRewind(ui_source); - - sfx_ui = sound; - - if(sfx_ui) { - alSourcei(ui_source, AL_BUFFER, sfx_ui->buffer); - alSourcei(ui_source, AL_LOOPING, looping); - alSourcef(ui_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH)); - alSourcePlay(ui_source); - } -} +#include "client/pch.hh"
+
+#include "client/sound/sound.hh"
+
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "client/entity/camera.hh"
+#include "client/entity/sound_emitter.hh"
+
+#include "client/gui/settings.hh"
+
+#include "client/resource/sound_effect.hh"
+
+#include "client/const.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+config::Float sound::volume_master(100.0f, 0.0f, 100.0f);
+config::Float sound::volume_effects(100.0f, 0.0f, 100.0f);
+config::Float sound::volume_music(100.0f, 0.0f, 100.0f);
+config::Float sound::volume_ui(100.0f, 0.0f, 100.0f);
+
+static ALuint generic_source;
+static ALuint player_source;
+static ALuint ui_source;
+
+static resource_ptr<SoundEffect> sfx_generic;
+static resource_ptr<SoundEffect> sfx_player;
+static resource_ptr<SoundEffect> sfx_ui;
+
+void sound::init_config(void)
+{
+ globals::client_config.add_value("sound.volume_master", sound::volume_master);
+ globals::client_config.add_value("sound.volume_effects", sound::volume_effects);
+ globals::client_config.add_value("sound.volume_music", sound::volume_music);
+ globals::client_config.add_value("sound.volume_ui", sound::volume_ui);
+
+ settings::add_slider(1, sound::volume_master, settings_location::SOUND, "sound.volume_master", false, "%.0f%%");
+
+ settings::add_slider(0, sound::volume_effects, settings_location::SOUND_LEVELS, "sound.volume_effects", false, "%.0f%%");
+ settings::add_slider(1, sound::volume_music, settings_location::SOUND_LEVELS, "sound.volume_music", false, "%.0f%%");
+ settings::add_slider(2, sound::volume_ui, settings_location::SOUND_LEVELS, "sound.volume_ui", false, "%.0f%%");
+}
+
+void sound::init(void)
+{
+ alGenSources(1, &generic_source);
+ alSourcei(generic_source, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSource3f(generic_source, AL_POSITION, 0.0f, 0.0f, 0.0f);
+ alSource3f(generic_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+
+ alGenSources(1, &player_source);
+ alSourcei(player_source, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSource3f(player_source, AL_POSITION, 0.0f, 0.0f, 0.0f);
+ alSource3f(player_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+
+ alGenSources(1, &ui_source);
+ alSourcei(ui_source, AL_SOURCE_RELATIVE, AL_TRUE);
+ alSource3f(ui_source, AL_POSITION, 0.0f, 0.0f, 0.0f);
+ alSource3f(ui_source, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
+
+ sfx_generic = nullptr;
+ sfx_player = nullptr;
+ sfx_ui = nullptr;
+}
+
+void sound::init_late(void)
+{
+}
+
+void sound::shutdown(void)
+{
+ sfx_ui = nullptr;
+ sfx_player = nullptr;
+ sfx_generic = nullptr;
+
+ alDeleteBuffers(1, &ui_source);
+ alDeleteSources(1, &generic_source);
+ alDeleteSources(1, &player_source);
+}
+
+void sound::update(void)
+{
+ auto effects_gain = math::clamp(0.01f * sound::volume_effects.get_value(), 0.0f, 1.0f);
+ alSourcef(generic_source, AL_GAIN, effects_gain);
+ alSourcef(player_source, AL_GAIN, effects_gain);
+
+ auto ui_gain = math::clamp(0.01f * sound::volume_ui.get_value(), 0.0f, 1.0f);
+ alSourcef(ui_source, AL_GAIN, ui_gain);
+}
+
+void sound::play_generic(std::string_view sound, bool looping, float pitch)
+{
+ if(sound.size()) {
+ sound::play_generic(resource::load<SoundEffect>(sound), looping, pitch);
+ }
+ else {
+ sound::play_generic(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch);
+ }
+}
+
+void sound::play_entity(entt::entity entity, std::string_view sound, bool looping, float pitch)
+{
+ if(sound.size()) {
+ sound::play_entity(entity, resource::load<SoundEffect>(sound), looping, pitch);
+ }
+ else {
+ sound::play_entity(entity, static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch);
+ }
+}
+
+void sound::play_player(std::string_view sound, bool looping, float pitch)
+{
+ if(sound.size()) {
+ sound::play_player(resource::load<SoundEffect>(sound), looping, pitch);
+ }
+ else {
+ sound::play_player(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch);
+ }
+}
+
+void sound::play_ui(std::string_view sound, bool looping, float pitch)
+{
+ if(sound.size()) {
+ sound::play_ui(resource::load<SoundEffect>(sound), looping, pitch);
+ }
+ else {
+ sound::play_ui(static_cast<resource_ptr<SoundEffect>>(nullptr), looping, pitch);
+ }
+}
+
+void sound::play_generic(resource_ptr<SoundEffect> sound, bool looping, float pitch)
+{
+ alSourceRewind(generic_source);
+
+ sfx_generic = sound;
+
+ if(sfx_generic) {
+ alSourcei(generic_source, AL_BUFFER, sfx_generic->buffer);
+ alSourcei(generic_source, AL_LOOPING, looping);
+ alSourcef(generic_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH));
+ alSourcePlay(generic_source);
+ }
+}
+
+void sound::play_entity(entt::entity entity, resource_ptr<SoundEffect> sound, bool looping, float pitch)
+{
+ if(globals::dimension && globals::dimension->entities.valid(entity)) {
+ if(auto emitter = globals::dimension->entities.try_get<entity::SoundEmitter>(entity)) {
+ alSourceRewind(emitter->source);
+
+ emitter->sound = sound;
+
+ if(emitter->sound) {
+ alSourcei(emitter->source, AL_BUFFER, emitter->sound->buffer);
+ alSourcei(emitter->source, AL_LOOPING, looping);
+ alSourcef(emitter->source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH));
+ alSourcePlay(emitter->source);
+ }
+ }
+ }
+}
+
+void sound::play_player(resource_ptr<SoundEffect> sound, bool looping, float pitch)
+{
+ if(sound && session::is_ingame()) {
+ protocol::EntitySound packet;
+ packet.entity = globals::player;
+ packet.sound = sound->name;
+ packet.looping = looping;
+ packet.pitch = pitch;
+
+ protocol::send(session::peer, protocol::encode(packet));
+ }
+
+ alSourceRewind(player_source);
+
+ sfx_player = sound;
+
+ if(sfx_player) {
+ alSourcei(player_source, AL_BUFFER, sfx_player->buffer);
+ alSourcei(player_source, AL_LOOPING, looping);
+ alSourcef(player_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH));
+ alSourcePlay(player_source);
+ }
+}
+
+void sound::play_ui(resource_ptr<SoundEffect> sound, bool looping, float pitch)
+{
+ alSourceRewind(ui_source);
+
+ sfx_ui = sound;
+
+ if(sfx_ui) {
+ alSourcei(ui_source, AL_BUFFER, sfx_ui->buffer);
+ alSourcei(ui_source, AL_LOOPING, looping);
+ alSourcef(ui_source, AL_PITCH, math::clamp(pitch, MIN_PITCH, MAX_PITCH));
+ alSourcePlay(ui_source);
+ }
+}
diff --git a/game/client/sound/sound.hh b/game/client/sound/sound.hh index d96d0c4..c794845 100644 --- a/game/client/sound/sound.hh +++ b/game/client/sound/sound.hh @@ -1,43 +1,43 @@ -#pragma once - -#include "core/resource/resource.hh" - -namespace config -{ -class Float; -} // namespace config - -struct SoundEffect; - -namespace sound -{ -extern config::Float volume_master; -extern config::Float volume_effects; -extern config::Float volume_music; -extern config::Float volume_ui; -} // namespace sound - -namespace sound -{ -void init_config(void); -void init(void); -void init_late(void); -void shutdown(void); -void update(void); -} // namespace sound - -namespace sound -{ -void play_generic(std::string_view sound, bool looping, float pitch); -void play_entity(entt::entity entity, std::string_view sound, bool looping, float pitch); -void play_player(std::string_view sound, bool looping, float pitch); -void play_ui(std::string_view sound, bool looping, float pitch); -} // namespace sound - -namespace sound -{ -void play_generic(resource_ptr<SoundEffect> sound, bool looping, float pitch); -void play_entity(entt::entity entity, resource_ptr<SoundEffect> sound, bool looping, float pitch); -void play_player(resource_ptr<SoundEffect> sound, bool looping, float pitch); -void play_ui(resource_ptr<SoundEffect> sound, bool looping, float pitch); -} // namespace sound +#pragma once
+
+#include "core/resource/resource.hh"
+
+namespace config
+{
+class Float;
+} // namespace config
+
+struct SoundEffect;
+
+namespace sound
+{
+extern config::Float volume_master;
+extern config::Float volume_effects;
+extern config::Float volume_music;
+extern config::Float volume_ui;
+} // namespace sound
+
+namespace sound
+{
+void init_config(void);
+void init(void);
+void init_late(void);
+void shutdown(void);
+void update(void);
+} // namespace sound
+
+namespace sound
+{
+void play_generic(std::string_view sound, bool looping, float pitch);
+void play_entity(entt::entity entity, std::string_view sound, bool looping, float pitch);
+void play_player(std::string_view sound, bool looping, float pitch);
+void play_ui(std::string_view sound, bool looping, float pitch);
+} // namespace sound
+
+namespace sound
+{
+void play_generic(resource_ptr<SoundEffect> sound, bool looping, float pitch);
+void play_entity(entt::entity entity, resource_ptr<SoundEffect> sound, bool looping, float pitch);
+void play_player(resource_ptr<SoundEffect> sound, bool looping, float pitch);
+void play_ui(resource_ptr<SoundEffect> sound, bool looping, float pitch);
+} // namespace sound
diff --git a/game/client/toggles.cc b/game/client/toggles.cc index 833e099..34d03c7 100644 --- a/game/client/toggles.cc +++ b/game/client/toggles.cc @@ -1,157 +1,157 @@ -#include "client/pch.hh" - -#include "client/toggles.hh" - -#include "core/io/config_map.hh" - -#include "client/gui/chat.hh" -#include "client/gui/language.hh" - -#include "client/io/gamepad.hh" -#include "client/io/glfw.hh" - -#include "client/const.hh" -#include "client/globals.hh" - -struct ToggleInfo final { - std::string_view description; - int glfw_keycode; - bool is_enabled; -}; - -bool toggles::is_sequence_await = false; - -static ToggleInfo toggle_infos[TOGGLE_COUNT]; - -static void print_toggle_state(const ToggleInfo& info) -{ - if(info.description.size()) { - if(info.is_enabled) { - gui::client_chat::print(std::format("[toggles] {} ON", info.description)); - } - else { - gui::client_chat::print(std::format("[toggles] {} OFF", info.description)); - } - } -} - -static void toggle_value(ToggleInfo& info, toggle_type type) -{ - if(info.is_enabled) { - info.is_enabled = false; - globals::dispatcher.trigger(ToggleDisabledEvent { type }); - } - else { - info.is_enabled = true; - globals::dispatcher.trigger(ToggleEnabledEvent { type }); - } - - print_toggle_state(info); -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(globals::gui_keybind_ptr) { - // The UI keybind subsystem has the authority - // over debug toggles and it hogs the input keys - return; - } - - if(event.key == DEBUG_KEY) { - if(event.action == GLFW_PRESS) { - toggles::is_sequence_await = true; - ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - return; - } - - if(event.action == GLFW_RELEASE) { - toggles::is_sequence_await = false; - ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - return; - } - } - - if((event.action == GLFW_PRESS) && toggles::is_sequence_await) { - if(event.key == GLFW_KEY_L) { - // This causes the language subsystem - // to re-parse the JSON file essentially - // causing the game to soft-reload language - gui::language::set(gui::language::get_current()); - return; - } - - for(toggle_type i = 0; i < TOGGLE_COUNT; ++i) { - if(event.key == toggle_infos[i].glfw_keycode) { - toggle_value(toggle_infos[i], i); - return; - } - } - } -} - -void toggles::init(void) -{ - toggle_infos[TOGGLE_WIREFRAME].description = "wireframe"; - toggle_infos[TOGGLE_WIREFRAME].glfw_keycode = GLFW_KEY_Z; - toggle_infos[TOGGLE_WIREFRAME].is_enabled = false; - - toggle_infos[TOGGLE_FULLBRIGHT].description = "fullbright"; - toggle_infos[TOGGLE_FULLBRIGHT].glfw_keycode = GLFW_KEY_J; - toggle_infos[TOGGLE_FULLBRIGHT].is_enabled = false; - - toggle_infos[TOGGLE_CHUNK_AABB].description = "chunk Borders"; - toggle_infos[TOGGLE_CHUNK_AABB].glfw_keycode = GLFW_KEY_G; - toggle_infos[TOGGLE_CHUNK_AABB].is_enabled = false; - - toggle_infos[TOGGLE_METRICS_UI].description = std::string_view(); - toggle_infos[TOGGLE_METRICS_UI].glfw_keycode = GLFW_KEY_V; - toggle_infos[TOGGLE_METRICS_UI].is_enabled = false; - - toggle_infos[TOGGLE_USE_GAMEPAD].description = "gamepad input"; - toggle_infos[TOGGLE_USE_GAMEPAD].glfw_keycode = GLFW_KEY_P; - toggle_infos[TOGGLE_USE_GAMEPAD].is_enabled = false; - - toggle_infos[TOGGLE_PM_FLIGHT].description = "flight mode"; - toggle_infos[TOGGLE_PM_FLIGHT].glfw_keycode = GLFW_KEY_F; - toggle_infos[TOGGLE_PM_FLIGHT].is_enabled = false; - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); -} - -void toggles::init_late(void) -{ - for(toggle_type i = 0; i < TOGGLE_COUNT; ++i) { - if(toggle_infos[i].is_enabled) { - globals::dispatcher.trigger(ToggleEnabledEvent { i }); - } - else { - globals::dispatcher.trigger(ToggleDisabledEvent { i }); - } - } -} - -bool toggles::get(toggle_type type) -{ - if(type < TOGGLE_COUNT) { - return toggle_infos[type].is_enabled; - } - else { - return false; - } -} - -void toggles::set(toggle_type type, bool value) -{ - if(type < TOGGLE_COUNT) { - if(value) { - toggle_infos[type].is_enabled = true; - globals::dispatcher.trigger(ToggleEnabledEvent { type }); - } - else { - toggle_infos[type].is_enabled = false; - globals::dispatcher.trigger(ToggleDisabledEvent { type }); - } - - print_toggle_state(toggle_infos[type]); - } -} +#include "client/pch.hh"
+
+#include "client/toggles.hh"
+
+#include "core/io/config_map.hh"
+
+#include "client/gui/chat.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/gamepad.hh"
+#include "client/io/glfw.hh"
+
+#include "client/const.hh"
+#include "client/globals.hh"
+
+struct ToggleInfo final {
+ std::string_view description;
+ int glfw_keycode;
+ bool is_enabled;
+};
+
+bool toggles::is_sequence_await = false;
+
+static ToggleInfo toggle_infos[TOGGLE_COUNT];
+
+static void print_toggle_state(const ToggleInfo& info)
+{
+ if(info.description.size()) {
+ if(info.is_enabled) {
+ gui::client_chat::print(std::format("[toggles] {} ON", info.description));
+ }
+ else {
+ gui::client_chat::print(std::format("[toggles] {} OFF", info.description));
+ }
+ }
+}
+
+static void toggle_value(ToggleInfo& info, toggle_type type)
+{
+ if(info.is_enabled) {
+ info.is_enabled = false;
+ globals::dispatcher.trigger(ToggleDisabledEvent { type });
+ }
+ else {
+ info.is_enabled = true;
+ globals::dispatcher.trigger(ToggleEnabledEvent { type });
+ }
+
+ print_toggle_state(info);
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(globals::gui_keybind_ptr) {
+ // The UI keybind subsystem has the authority
+ // over debug toggles and it hogs the input keys
+ return;
+ }
+
+ if(event.key == DEBUG_KEY) {
+ if(event.action == GLFW_PRESS) {
+ toggles::is_sequence_await = true;
+ ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ return;
+ }
+
+ if(event.action == GLFW_RELEASE) {
+ toggles::is_sequence_await = false;
+ ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ return;
+ }
+ }
+
+ if((event.action == GLFW_PRESS) && toggles::is_sequence_await) {
+ if(event.key == GLFW_KEY_L) {
+ // This causes the language subsystem
+ // to re-parse the JSON file essentially
+ // causing the game to soft-reload language
+ gui::language::set(gui::language::get_current());
+ return;
+ }
+
+ for(toggle_type i = 0; i < TOGGLE_COUNT; ++i) {
+ if(event.key == toggle_infos[i].glfw_keycode) {
+ toggle_value(toggle_infos[i], i);
+ return;
+ }
+ }
+ }
+}
+
+void toggles::init(void)
+{
+ toggle_infos[TOGGLE_WIREFRAME].description = "wireframe";
+ toggle_infos[TOGGLE_WIREFRAME].glfw_keycode = GLFW_KEY_Z;
+ toggle_infos[TOGGLE_WIREFRAME].is_enabled = false;
+
+ toggle_infos[TOGGLE_FULLBRIGHT].description = "fullbright";
+ toggle_infos[TOGGLE_FULLBRIGHT].glfw_keycode = GLFW_KEY_J;
+ toggle_infos[TOGGLE_FULLBRIGHT].is_enabled = false;
+
+ toggle_infos[TOGGLE_CHUNK_AABB].description = "chunk Borders";
+ toggle_infos[TOGGLE_CHUNK_AABB].glfw_keycode = GLFW_KEY_G;
+ toggle_infos[TOGGLE_CHUNK_AABB].is_enabled = false;
+
+ toggle_infos[TOGGLE_METRICS_UI].description = std::string_view();
+ toggle_infos[TOGGLE_METRICS_UI].glfw_keycode = GLFW_KEY_V;
+ toggle_infos[TOGGLE_METRICS_UI].is_enabled = false;
+
+ toggle_infos[TOGGLE_USE_GAMEPAD].description = "gamepad input";
+ toggle_infos[TOGGLE_USE_GAMEPAD].glfw_keycode = GLFW_KEY_P;
+ toggle_infos[TOGGLE_USE_GAMEPAD].is_enabled = false;
+
+ toggle_infos[TOGGLE_PM_FLIGHT].description = "flight mode";
+ toggle_infos[TOGGLE_PM_FLIGHT].glfw_keycode = GLFW_KEY_F;
+ toggle_infos[TOGGLE_PM_FLIGHT].is_enabled = false;
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+}
+
+void toggles::init_late(void)
+{
+ for(toggle_type i = 0; i < TOGGLE_COUNT; ++i) {
+ if(toggle_infos[i].is_enabled) {
+ globals::dispatcher.trigger(ToggleEnabledEvent { i });
+ }
+ else {
+ globals::dispatcher.trigger(ToggleDisabledEvent { i });
+ }
+ }
+}
+
+bool toggles::get(toggle_type type)
+{
+ if(type < TOGGLE_COUNT) {
+ return toggle_infos[type].is_enabled;
+ }
+ else {
+ return false;
+ }
+}
+
+void toggles::set(toggle_type type, bool value)
+{
+ if(type < TOGGLE_COUNT) {
+ if(value) {
+ toggle_infos[type].is_enabled = true;
+ globals::dispatcher.trigger(ToggleEnabledEvent { type });
+ }
+ else {
+ toggle_infos[type].is_enabled = false;
+ globals::dispatcher.trigger(ToggleDisabledEvent { type });
+ }
+
+ print_toggle_state(toggle_infos[type]);
+ }
+}
diff --git a/game/client/toggles.hh b/game/client/toggles.hh index d051a92..8ef780e 100644 --- a/game/client/toggles.hh +++ b/game/client/toggles.hh @@ -1,35 +1,35 @@ -#pragma once - -using toggle_type = unsigned int; -constexpr static toggle_type TOGGLE_WIREFRAME = 0x0000U; // Render things in wireframe mode -constexpr static toggle_type TOGGLE_FULLBRIGHT = 0x0001U; // Render things without lighting -constexpr static toggle_type TOGGLE_CHUNK_AABB = 0x0002U; // Render chunk bounding boxes -constexpr static toggle_type TOGGLE_METRICS_UI = 0x0003U; // Render debug metrics overlay -constexpr static toggle_type TOGGLE_USE_GAMEPAD = 0x0004U; // Use gamepad for player movement -constexpr static toggle_type TOGGLE_PM_FLIGHT = 0x0005U; // Enable flight for player movement -constexpr static std::size_t TOGGLE_COUNT = 0x0006U; - -struct ToggleEnabledEvent final { - toggle_type type; -}; - -struct ToggleDisabledEvent final { - toggle_type type; -}; - -namespace toggles -{ -// The value is true whenever the debug -// toggles manager awaits for a sequenced key -// to be pressed. During this no input should -// be processed by any other gameplay system -extern bool is_sequence_await; -} // namespace toggles - -namespace toggles -{ -void init(void); -void init_late(void); -bool get(toggle_type type); -void set(toggle_type type, bool value); -} // namespace toggles +#pragma once
+
+using toggle_type = unsigned int;
+constexpr static toggle_type TOGGLE_WIREFRAME = 0x0000U; // Render things in wireframe mode
+constexpr static toggle_type TOGGLE_FULLBRIGHT = 0x0001U; // Render things without lighting
+constexpr static toggle_type TOGGLE_CHUNK_AABB = 0x0002U; // Render chunk bounding boxes
+constexpr static toggle_type TOGGLE_METRICS_UI = 0x0003U; // Render debug metrics overlay
+constexpr static toggle_type TOGGLE_USE_GAMEPAD = 0x0004U; // Use gamepad for player movement
+constexpr static toggle_type TOGGLE_PM_FLIGHT = 0x0005U; // Enable flight for player movement
+constexpr static std::size_t TOGGLE_COUNT = 0x0006U;
+
+struct ToggleEnabledEvent final {
+ toggle_type type;
+};
+
+struct ToggleDisabledEvent final {
+ toggle_type type;
+};
+
+namespace toggles
+{
+// The value is true whenever the debug
+// toggles manager awaits for a sequenced key
+// to be pressed. During this no input should
+// be processed by any other gameplay system
+extern bool is_sequence_await;
+} // namespace toggles
+
+namespace toggles
+{
+void init(void);
+void init_late(void);
+bool get(toggle_type type);
+void set(toggle_type type, bool value);
+} // namespace toggles
diff --git a/game/client/world/chunk_mesher.cc b/game/client/world/chunk_mesher.cc index e2f1e10..a8ee817 100644 --- a/game/client/world/chunk_mesher.cc +++ b/game/client/world/chunk_mesher.cc @@ -1,484 +1,484 @@ -#include "client/pch.hh" - -#include "client/world/chunk_mesher.hh" - -#include "core/math/crc64.hh" - -#include "core/threading.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" - -#include "client/world/chunk_quad.hh" -#include "client/world/voxel_atlas.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -using QuadBuilder = std::vector<world::ChunkQuad>; - -using CachedChunkCoord = unsigned short; -constexpr static CachedChunkCoord CPOS_ITSELF = 0x0000; -constexpr static CachedChunkCoord CPOS_NORTH = 0x0001; -constexpr static CachedChunkCoord CPOS_SOUTH = 0x0002; -constexpr static CachedChunkCoord CPOS_EAST = 0x0003; -constexpr static CachedChunkCoord CPOS_WEST = 0x0004; -constexpr static CachedChunkCoord CPOS_TOP = 0x0005; -constexpr static CachedChunkCoord CPOS_BOTTOM = 0x0006; -constexpr static const size_t NUM_CACHED_CPOS = 7; - -static const CachedChunkCoord get_cached_cpos(const chunk_pos& pivot, const chunk_pos& cpos) -{ - static const CachedChunkCoord nx[3] = { CPOS_WEST, 0, CPOS_EAST }; - static const CachedChunkCoord ny[3] = { CPOS_BOTTOM, 0, CPOS_TOP }; - static const CachedChunkCoord nz[3] = { CPOS_NORTH, 0, CPOS_SOUTH }; - - if(pivot != cpos) { - chunk_pos delta = pivot - cpos; - delta[0] = math::clamp<std::int64_t>(delta[0], -1, 1); - delta[1] = math::clamp<std::int64_t>(delta[1], -1, 1); - delta[2] = math::clamp<std::int64_t>(delta[2], -1, 1); - - if(delta[0]) { - return nx[delta[0] + 1]; - } - else if(delta[1]) { - return ny[delta[1] + 1]; - } - else { - return nz[delta[2] + 1]; - } - } - - return CPOS_ITSELF; -} - -static world::voxel_facing get_facing(world::voxel_face face, world::voxel_type type) -{ - if(type == world::voxel_type::CROSS) { - switch(face) { - case world::voxel_face::CROSS_NESW: - return world::voxel_facing::NESW; - case world::voxel_face::CROSS_NWSE: - return world::voxel_facing::NWSE; - default: - return world::voxel_facing::NORTH; - } - } - - switch(face) { - case world::voxel_face::CUBE_NORTH: - return world::voxel_facing::NORTH; - case world::voxel_face::CUBE_SOUTH: - return world::voxel_facing::SOUTH; - case world::voxel_face::CUBE_EAST: - return world::voxel_facing::EAST; - case world::voxel_face::CUBE_WEST: - return world::voxel_facing::WEST; - case world::voxel_face::CUBE_TOP: - return world::voxel_facing::UP; - case world::voxel_face::CUBE_BOTTOM: - return world::voxel_facing::DOWN; - default: - return world::voxel_facing::NORTH; - } -} - -class GL_MeshingTask final : public Task { -public: - explicit GL_MeshingTask(entt::entity entity, const chunk_pos& cpos); - virtual ~GL_MeshingTask(void) = default; - virtual void process(void) override; - virtual void finalize(void) override; - -private: - bool vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const; - void push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face); - void push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face, - std::size_t entropy); - void make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis, std::size_t entropy); - void cache_chunk(const chunk_pos& cpos); - -private: - std::array<world::VoxelStorage, NUM_CACHED_CPOS> m_cache; - std::vector<QuadBuilder> m_quads_b; // blending - std::vector<QuadBuilder> m_quads_s; // solid - entt::entity m_entity; - chunk_pos m_cpos; -}; - -GL_MeshingTask::GL_MeshingTask(entt::entity entity, const chunk_pos& cpos) -{ - m_entity = entity; - m_cpos = cpos; - - cache_chunk(m_cpos); - cache_chunk(m_cpos + DIR_NORTH<chunk_pos::value_type>); - cache_chunk(m_cpos + DIR_SOUTH<chunk_pos::value_type>); - cache_chunk(m_cpos + DIR_EAST<chunk_pos::value_type>); - cache_chunk(m_cpos + DIR_WEST<chunk_pos::value_type>); - cache_chunk(m_cpos + DIR_DOWN<chunk_pos::value_type>); - cache_chunk(m_cpos + DIR_UP<chunk_pos::value_type>); -} - -void GL_MeshingTask::process(void) -{ - m_quads_b.resize(world::voxel_atlas::plane_count()); - m_quads_s.resize(world::voxel_atlas::plane_count()); - - const auto& voxels = m_cache.at(CPOS_ITSELF); - - for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { - if(m_status == task_status::CANCELLED) { - m_quads_b.clear(); - m_quads_s.clear(); - return; - } - - const auto voxel = voxels[i]; - const auto lpos = coord::to_local(i); - - const auto info = world::voxel_registry::find(voxel); - - if(info == nullptr) { - // Either a NULL_VOXEL_ID or something went - // horribly wrong and we don't what this is - continue; - } - - world::voxel_vis vis = 0; - - if(vis_test(voxel, info, lpos + DIR_NORTH<local_pos::value_type>)) { - vis |= world::VIS_NORTH; - } - - if(vis_test(voxel, info, lpos + DIR_SOUTH<local_pos::value_type>)) { - vis |= world::VIS_SOUTH; - } - - if(vis_test(voxel, info, lpos + DIR_EAST<local_pos::value_type>)) { - vis |= world::VIS_EAST; - } - - if(vis_test(voxel, info, lpos + DIR_WEST<local_pos::value_type>)) { - vis |= world::VIS_WEST; - } - - if(vis_test(voxel, info, lpos + DIR_UP<local_pos::value_type>)) { - vis |= world::VIS_UP; - } - - if(vis_test(voxel, info, lpos + DIR_DOWN<local_pos::value_type>)) { - vis |= world::VIS_DOWN; - } - - const auto vpos = coord::to_voxel(m_cpos, lpos); - const auto entropy_src = vpos[0] * vpos[1] * vpos[2]; - const auto entropy = math::crc64(&entropy_src, sizeof(entropy_src)); - - // FIXME: handle different voxel types - make_cube(voxel, info, lpos, vis, entropy); - } -} - -void GL_MeshingTask::finalize(void) -{ - if(!globals::dimension || !globals::dimension->chunks.valid(m_entity)) { - // We either disconnected or something - // else happened that invalidated the entity - return; - } - - auto& component = globals::dimension->chunks.emplace_or_replace<world::ChunkMesh>(m_entity); - - const std::size_t plane_count_nb = m_quads_s.size(); - const std::size_t plane_count_b = m_quads_b.size(); - - bool has_no_submeshes_b = true; - bool has_no_submeshes_nb = true; - - component.quad_nb.resize(plane_count_nb); - component.quad_b.resize(plane_count_b); - - for(std::size_t plane = 0; plane < plane_count_nb; ++plane) { - auto& builder = m_quads_s[plane]; - auto& buffer = component.quad_nb[plane]; - - if(builder.empty()) { - if(buffer.handle) { - glDeleteBuffers(1, &buffer.handle); - buffer.handle = 0; - buffer.size = 0; - } - } - else { - if(!buffer.handle) { - glGenBuffers(1, &buffer.handle); - } - - glBindBuffer(GL_ARRAY_BUFFER, buffer.handle); - glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW); - buffer.size = builder.size(); - has_no_submeshes_nb = false; - } - } - - for(std::size_t plane = 0; plane < plane_count_b; ++plane) { - auto& builder = m_quads_b[plane]; - auto& buffer = component.quad_b[plane]; - - if(builder.empty()) { - if(buffer.handle) { - glDeleteBuffers(1, &buffer.handle); - buffer.handle = 0; - buffer.size = 0; - } - } - else { - if(!buffer.handle) { - glGenBuffers(1, &buffer.handle); - } - - glBindBuffer(GL_ARRAY_BUFFER, buffer.handle); - glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW); - buffer.size = builder.size(); - has_no_submeshes_b = false; - } - } - - if(has_no_submeshes_b && has_no_submeshes_nb) { - globals::dimension->chunks.remove<world::ChunkMesh>(m_entity); - } -} - -bool GL_MeshingTask::vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const -{ - const auto pvpos = coord::to_voxel(m_cpos, lpos); - const auto pcpos = coord::to_chunk(pvpos); - const auto plpos = coord::to_local(pvpos); - const auto index = coord::to_index(plpos); - - const auto cached_cpos = get_cached_cpos(m_cpos, pcpos); - const auto& voxels = m_cache.at(cached_cpos); - const auto neighbour = voxels[index]; - - bool result; - - if(neighbour == NULL_VOXEL_ID) { - result = true; - } - else if(neighbour == voxel) { - result = false; - } - else if(auto neighbour_info = world::voxel_registry::find(neighbour)) { - if(neighbour_info->blending != info->blending) { - // Voxel types that use blending are semi-transparent; - // this means they're rendered using a different setup - // and they must have visible faces with opaque voxels - result = neighbour_info->blending; - } - else { - result = false; - } - } - else { - result = false; - } - - return result; -} - -void GL_MeshingTask::push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face) -{ - const world::voxel_facing facing = get_facing(face, info->type); - const world::VoxelTexture& vtex = info->textures[static_cast<std::size_t>(face)]; - - if(info->blending) { - m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size())); - } - else { - m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size())); - } -} - -void GL_MeshingTask::push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face, - std::size_t entropy) -{ - const world::voxel_facing facing = get_facing(face, info->type); - const world::VoxelTexture& vtex = info->textures[static_cast<std::size_t>(face)]; - const std::size_t entropy_mod = entropy % vtex.paths.size(); - - if(info->blending) { - m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0)); - } - else { - m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0)); - } -} - -void GL_MeshingTask::make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis, - std::size_t entropy) -{ - const glm::fvec3 fpos = glm::fvec3(lpos); - const glm::fvec2 fsize = glm::fvec2(1.0f, 1.0f); - - if(info->animated) { - if(vis & world::VIS_NORTH) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_NORTH); - } - - if(vis & world::VIS_SOUTH) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_SOUTH); - } - - if(vis & world::VIS_EAST) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_EAST); - } - - if(vis & world::VIS_WEST) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_WEST); - } - - if(vis & world::VIS_UP) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_TOP); - } - - if(vis & world::VIS_DOWN) { - push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM); - } - } - else { - if(vis & world::VIS_NORTH) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_NORTH, entropy); - } - - if(vis & world::VIS_SOUTH) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_SOUTH, entropy); - } - - if(vis & world::VIS_EAST) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_EAST, entropy); - } - - if(vis & world::VIS_WEST) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_WEST, entropy); - } - - if(vis & world::VIS_UP) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_TOP, entropy); - } - - if(vis & world::VIS_DOWN) { - push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM, entropy); - } - } -} - -void GL_MeshingTask::cache_chunk(const chunk_pos& cpos) -{ - const auto index = get_cached_cpos(m_cpos, cpos); - - if(const auto chunk = globals::dimension->find_chunk(cpos)) { - m_cache[index] = chunk->get_voxels(); - return; - } -} - -// Bogus internal flag component -struct NeedsMeshingComponent final {}; - -static void on_chunk_create(const world::ChunkCreateEvent& event) -{ - const std::array<chunk_pos, 6> neighbours = { - event.cpos + DIR_NORTH<chunk_pos::value_type>, - event.cpos + DIR_SOUTH<chunk_pos::value_type>, - event.cpos + DIR_EAST<chunk_pos::value_type>, - event.cpos + DIR_WEST<chunk_pos::value_type>, - event.cpos + DIR_UP<chunk_pos::value_type>, - event.cpos + DIR_DOWN<chunk_pos::value_type>, - }; - - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); - - for(const chunk_pos& cpos : neighbours) { - if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); - continue; - } - } -} - -static void on_chunk_update(const world::ChunkUpdateEvent& event) -{ - const std::array<chunk_pos, 6> neighbours = { - event.cpos + DIR_NORTH<chunk_pos::value_type>, - event.cpos + DIR_SOUTH<chunk_pos::value_type>, - event.cpos + DIR_EAST<chunk_pos::value_type>, - event.cpos + DIR_WEST<chunk_pos::value_type>, - event.cpos + DIR_UP<chunk_pos::value_type>, - event.cpos + DIR_DOWN<chunk_pos::value_type>, - }; - - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); - - for(const chunk_pos& cpos : neighbours) { - if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); - continue; - } - } -} - -static void on_voxel_set(const world::VoxelSetEvent& event) -{ - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity()); - - std::vector<chunk_pos> neighbours; - - for(int dim = 0; dim < 3; dim += 1) { - chunk_pos offset = chunk_pos(0, 0, 0); - offset[dim] = 1; - - if(event.lpos[dim] == 0) { - neighbours.push_back(event.cpos - offset); - continue; - } - - if(event.lpos[dim] == (CHUNK_SIZE - 1)) { - neighbours.push_back(event.cpos + offset); - continue; - } - } - - for(const chunk_pos& cpos : neighbours) { - if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) { - globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity()); - continue; - } - } -} - -void world::chunk_mesher::init(void) -{ - globals::dispatcher.sink<ChunkCreateEvent>().connect<&on_chunk_create>(); - globals::dispatcher.sink<ChunkUpdateEvent>().connect<&on_chunk_update>(); - globals::dispatcher.sink<VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void world::chunk_mesher::shutdown(void) -{ -} - -void world::chunk_mesher::update(void) -{ - if(session::is_ingame()) { - const auto group = globals::dimension->chunks.group<NeedsMeshingComponent>(entt::get<ChunkComponent>); - for(const auto [entity, chunk] : group.each()) { - globals::dimension->chunks.remove<NeedsMeshingComponent>(entity); - threading::submit<GL_MeshingTask>(entity, chunk.cpos); - } - } -} +#include "client/pch.hh"
+
+#include "client/world/chunk_mesher.hh"
+
+#include "core/math/crc64.hh"
+
+#include "core/threading.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+
+#include "client/world/chunk_quad.hh"
+#include "client/world/voxel_atlas.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+using QuadBuilder = std::vector<world::ChunkQuad>;
+
+using CachedChunkCoord = unsigned short;
+constexpr static CachedChunkCoord CPOS_ITSELF = 0x0000;
+constexpr static CachedChunkCoord CPOS_NORTH = 0x0001;
+constexpr static CachedChunkCoord CPOS_SOUTH = 0x0002;
+constexpr static CachedChunkCoord CPOS_EAST = 0x0003;
+constexpr static CachedChunkCoord CPOS_WEST = 0x0004;
+constexpr static CachedChunkCoord CPOS_TOP = 0x0005;
+constexpr static CachedChunkCoord CPOS_BOTTOM = 0x0006;
+constexpr static const size_t NUM_CACHED_CPOS = 7;
+
+static const CachedChunkCoord get_cached_cpos(const chunk_pos& pivot, const chunk_pos& cpos)
+{
+ static const CachedChunkCoord nx[3] = { CPOS_WEST, 0, CPOS_EAST };
+ static const CachedChunkCoord ny[3] = { CPOS_BOTTOM, 0, CPOS_TOP };
+ static const CachedChunkCoord nz[3] = { CPOS_NORTH, 0, CPOS_SOUTH };
+
+ if(pivot != cpos) {
+ chunk_pos delta = pivot - cpos;
+ delta[0] = math::clamp<std::int64_t>(delta[0], -1, 1);
+ delta[1] = math::clamp<std::int64_t>(delta[1], -1, 1);
+ delta[2] = math::clamp<std::int64_t>(delta[2], -1, 1);
+
+ if(delta[0]) {
+ return nx[delta[0] + 1];
+ }
+ else if(delta[1]) {
+ return ny[delta[1] + 1];
+ }
+ else {
+ return nz[delta[2] + 1];
+ }
+ }
+
+ return CPOS_ITSELF;
+}
+
+static world::voxel_facing get_facing(world::voxel_face face, world::voxel_type type)
+{
+ if(type == world::voxel_type::CROSS) {
+ switch(face) {
+ case world::voxel_face::CROSS_NESW:
+ return world::voxel_facing::NESW;
+ case world::voxel_face::CROSS_NWSE:
+ return world::voxel_facing::NWSE;
+ default:
+ return world::voxel_facing::NORTH;
+ }
+ }
+
+ switch(face) {
+ case world::voxel_face::CUBE_NORTH:
+ return world::voxel_facing::NORTH;
+ case world::voxel_face::CUBE_SOUTH:
+ return world::voxel_facing::SOUTH;
+ case world::voxel_face::CUBE_EAST:
+ return world::voxel_facing::EAST;
+ case world::voxel_face::CUBE_WEST:
+ return world::voxel_facing::WEST;
+ case world::voxel_face::CUBE_TOP:
+ return world::voxel_facing::UP;
+ case world::voxel_face::CUBE_BOTTOM:
+ return world::voxel_facing::DOWN;
+ default:
+ return world::voxel_facing::NORTH;
+ }
+}
+
+class GL_MeshingTask final : public Task {
+public:
+ explicit GL_MeshingTask(entt::entity entity, const chunk_pos& cpos);
+ virtual ~GL_MeshingTask(void) = default;
+ virtual void process(void) override;
+ virtual void finalize(void) override;
+
+private:
+ bool vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const;
+ void push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face);
+ void push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face,
+ std::size_t entropy);
+ void make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis, std::size_t entropy);
+ void cache_chunk(const chunk_pos& cpos);
+
+private:
+ std::array<world::VoxelStorage, NUM_CACHED_CPOS> m_cache;
+ std::vector<QuadBuilder> m_quads_b; // blending
+ std::vector<QuadBuilder> m_quads_s; // solid
+ entt::entity m_entity;
+ chunk_pos m_cpos;
+};
+
+GL_MeshingTask::GL_MeshingTask(entt::entity entity, const chunk_pos& cpos)
+{
+ m_entity = entity;
+ m_cpos = cpos;
+
+ cache_chunk(m_cpos);
+ cache_chunk(m_cpos + DIR_NORTH<chunk_pos::value_type>);
+ cache_chunk(m_cpos + DIR_SOUTH<chunk_pos::value_type>);
+ cache_chunk(m_cpos + DIR_EAST<chunk_pos::value_type>);
+ cache_chunk(m_cpos + DIR_WEST<chunk_pos::value_type>);
+ cache_chunk(m_cpos + DIR_DOWN<chunk_pos::value_type>);
+ cache_chunk(m_cpos + DIR_UP<chunk_pos::value_type>);
+}
+
+void GL_MeshingTask::process(void)
+{
+ m_quads_b.resize(world::voxel_atlas::plane_count());
+ m_quads_s.resize(world::voxel_atlas::plane_count());
+
+ const auto& voxels = m_cache.at(CPOS_ITSELF);
+
+ for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) {
+ if(m_status == task_status::CANCELLED) {
+ m_quads_b.clear();
+ m_quads_s.clear();
+ return;
+ }
+
+ const auto voxel = voxels[i];
+ const auto lpos = coord::to_local(i);
+
+ const auto info = world::voxel_registry::find(voxel);
+
+ if(info == nullptr) {
+ // Either a NULL_VOXEL_ID or something went
+ // horribly wrong and we don't what this is
+ continue;
+ }
+
+ world::voxel_vis vis = 0;
+
+ if(vis_test(voxel, info, lpos + DIR_NORTH<local_pos::value_type>)) {
+ vis |= world::VIS_NORTH;
+ }
+
+ if(vis_test(voxel, info, lpos + DIR_SOUTH<local_pos::value_type>)) {
+ vis |= world::VIS_SOUTH;
+ }
+
+ if(vis_test(voxel, info, lpos + DIR_EAST<local_pos::value_type>)) {
+ vis |= world::VIS_EAST;
+ }
+
+ if(vis_test(voxel, info, lpos + DIR_WEST<local_pos::value_type>)) {
+ vis |= world::VIS_WEST;
+ }
+
+ if(vis_test(voxel, info, lpos + DIR_UP<local_pos::value_type>)) {
+ vis |= world::VIS_UP;
+ }
+
+ if(vis_test(voxel, info, lpos + DIR_DOWN<local_pos::value_type>)) {
+ vis |= world::VIS_DOWN;
+ }
+
+ const auto vpos = coord::to_voxel(m_cpos, lpos);
+ const auto entropy_src = vpos[0] * vpos[1] * vpos[2];
+ const auto entropy = math::crc64(&entropy_src, sizeof(entropy_src));
+
+ // FIXME: handle different voxel types
+ make_cube(voxel, info, lpos, vis, entropy);
+ }
+}
+
+void GL_MeshingTask::finalize(void)
+{
+ if(!globals::dimension || !globals::dimension->chunks.valid(m_entity)) {
+ // We either disconnected or something
+ // else happened that invalidated the entity
+ return;
+ }
+
+ auto& component = globals::dimension->chunks.emplace_or_replace<world::ChunkMesh>(m_entity);
+
+ const std::size_t plane_count_nb = m_quads_s.size();
+ const std::size_t plane_count_b = m_quads_b.size();
+
+ bool has_no_submeshes_b = true;
+ bool has_no_submeshes_nb = true;
+
+ component.quad_nb.resize(plane_count_nb);
+ component.quad_b.resize(plane_count_b);
+
+ for(std::size_t plane = 0; plane < plane_count_nb; ++plane) {
+ auto& builder = m_quads_s[plane];
+ auto& buffer = component.quad_nb[plane];
+
+ if(builder.empty()) {
+ if(buffer.handle) {
+ glDeleteBuffers(1, &buffer.handle);
+ buffer.handle = 0;
+ buffer.size = 0;
+ }
+ }
+ else {
+ if(!buffer.handle) {
+ glGenBuffers(1, &buffer.handle);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, buffer.handle);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW);
+ buffer.size = builder.size();
+ has_no_submeshes_nb = false;
+ }
+ }
+
+ for(std::size_t plane = 0; plane < plane_count_b; ++plane) {
+ auto& builder = m_quads_b[plane];
+ auto& buffer = component.quad_b[plane];
+
+ if(builder.empty()) {
+ if(buffer.handle) {
+ glDeleteBuffers(1, &buffer.handle);
+ buffer.handle = 0;
+ buffer.size = 0;
+ }
+ }
+ else {
+ if(!buffer.handle) {
+ glGenBuffers(1, &buffer.handle);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, buffer.handle);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(world::ChunkQuad) * builder.size(), builder.data(), GL_STATIC_DRAW);
+ buffer.size = builder.size();
+ has_no_submeshes_b = false;
+ }
+ }
+
+ if(has_no_submeshes_b && has_no_submeshes_nb) {
+ globals::dimension->chunks.remove<world::ChunkMesh>(m_entity);
+ }
+}
+
+bool GL_MeshingTask::vis_test(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos) const
+{
+ const auto pvpos = coord::to_voxel(m_cpos, lpos);
+ const auto pcpos = coord::to_chunk(pvpos);
+ const auto plpos = coord::to_local(pvpos);
+ const auto index = coord::to_index(plpos);
+
+ const auto cached_cpos = get_cached_cpos(m_cpos, pcpos);
+ const auto& voxels = m_cache.at(cached_cpos);
+ const auto neighbour = voxels[index];
+
+ bool result;
+
+ if(neighbour == NULL_VOXEL_ID) {
+ result = true;
+ }
+ else if(neighbour == voxel) {
+ result = false;
+ }
+ else if(auto neighbour_info = world::voxel_registry::find(neighbour)) {
+ if(neighbour_info->blending != info->blending) {
+ // Voxel types that use blending are semi-transparent;
+ // this means they're rendered using a different setup
+ // and they must have visible faces with opaque voxels
+ result = neighbour_info->blending;
+ }
+ else {
+ result = false;
+ }
+ }
+ else {
+ result = false;
+ }
+
+ return result;
+}
+
+void GL_MeshingTask::push_quad_a(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face)
+{
+ const world::voxel_facing facing = get_facing(face, info->type);
+ const world::VoxelTexture& vtex = info->textures[static_cast<std::size_t>(face)];
+
+ if(info->blending) {
+ m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size()));
+ }
+ else {
+ m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset, vtex.paths.size()));
+ }
+}
+
+void GL_MeshingTask::push_quad_v(const world::VoxelInfo* info, const glm::fvec3& pos, const glm::fvec2& size, world::voxel_face face,
+ std::size_t entropy)
+{
+ const world::voxel_facing facing = get_facing(face, info->type);
+ const world::VoxelTexture& vtex = info->textures[static_cast<std::size_t>(face)];
+ const std::size_t entropy_mod = entropy % vtex.paths.size();
+
+ if(info->blending) {
+ m_quads_b[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0));
+ }
+ else {
+ m_quads_s[vtex.cached_plane].push_back(make_chunk_quad(pos, size, facing, vtex.cached_offset + entropy_mod, 0));
+ }
+}
+
+void GL_MeshingTask::make_cube(voxel_id voxel, const world::VoxelInfo* info, const local_pos& lpos, world::voxel_vis vis,
+ std::size_t entropy)
+{
+ const glm::fvec3 fpos = glm::fvec3(lpos);
+ const glm::fvec2 fsize = glm::fvec2(1.0f, 1.0f);
+
+ if(info->animated) {
+ if(vis & world::VIS_NORTH) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_NORTH);
+ }
+
+ if(vis & world::VIS_SOUTH) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_SOUTH);
+ }
+
+ if(vis & world::VIS_EAST) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_EAST);
+ }
+
+ if(vis & world::VIS_WEST) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_WEST);
+ }
+
+ if(vis & world::VIS_UP) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_TOP);
+ }
+
+ if(vis & world::VIS_DOWN) {
+ push_quad_a(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM);
+ }
+ }
+ else {
+ if(vis & world::VIS_NORTH) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_NORTH, entropy);
+ }
+
+ if(vis & world::VIS_SOUTH) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_SOUTH, entropy);
+ }
+
+ if(vis & world::VIS_EAST) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_EAST, entropy);
+ }
+
+ if(vis & world::VIS_WEST) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_WEST, entropy);
+ }
+
+ if(vis & world::VIS_UP) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_TOP, entropy);
+ }
+
+ if(vis & world::VIS_DOWN) {
+ push_quad_v(info, fpos, fsize, world::voxel_face::CUBE_BOTTOM, entropy);
+ }
+ }
+}
+
+void GL_MeshingTask::cache_chunk(const chunk_pos& cpos)
+{
+ const auto index = get_cached_cpos(m_cpos, cpos);
+
+ if(const auto chunk = globals::dimension->find_chunk(cpos)) {
+ m_cache[index] = chunk->get_voxels();
+ return;
+ }
+}
+
+// Bogus internal flag component
+struct NeedsMeshingComponent final {};
+
+static void on_chunk_create(const world::ChunkCreateEvent& event)
+{
+ const std::array<chunk_pos, 6> neighbours = {
+ event.cpos + DIR_NORTH<chunk_pos::value_type>,
+ event.cpos + DIR_SOUTH<chunk_pos::value_type>,
+ event.cpos + DIR_EAST<chunk_pos::value_type>,
+ event.cpos + DIR_WEST<chunk_pos::value_type>,
+ event.cpos + DIR_UP<chunk_pos::value_type>,
+ event.cpos + DIR_DOWN<chunk_pos::value_type>,
+ };
+
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity());
+
+ for(const chunk_pos& cpos : neighbours) {
+ if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) {
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity());
+ continue;
+ }
+ }
+}
+
+static void on_chunk_update(const world::ChunkUpdateEvent& event)
+{
+ const std::array<chunk_pos, 6> neighbours = {
+ event.cpos + DIR_NORTH<chunk_pos::value_type>,
+ event.cpos + DIR_SOUTH<chunk_pos::value_type>,
+ event.cpos + DIR_EAST<chunk_pos::value_type>,
+ event.cpos + DIR_WEST<chunk_pos::value_type>,
+ event.cpos + DIR_UP<chunk_pos::value_type>,
+ event.cpos + DIR_DOWN<chunk_pos::value_type>,
+ };
+
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity());
+
+ for(const chunk_pos& cpos : neighbours) {
+ if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) {
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity());
+ continue;
+ }
+ }
+}
+
+static void on_voxel_set(const world::VoxelSetEvent& event)
+{
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(event.chunk->get_entity());
+
+ std::vector<chunk_pos> neighbours;
+
+ for(int dim = 0; dim < 3; dim += 1) {
+ chunk_pos offset = chunk_pos(0, 0, 0);
+ offset[dim] = 1;
+
+ if(event.lpos[dim] == 0) {
+ neighbours.push_back(event.cpos - offset);
+ continue;
+ }
+
+ if(event.lpos[dim] == (CHUNK_SIZE - 1)) {
+ neighbours.push_back(event.cpos + offset);
+ continue;
+ }
+ }
+
+ for(const chunk_pos& cpos : neighbours) {
+ if(const world::Chunk* chunk = globals::dimension->find_chunk(cpos)) {
+ globals::dimension->chunks.emplace_or_replace<NeedsMeshingComponent>(chunk->get_entity());
+ continue;
+ }
+ }
+}
+
+void world::chunk_mesher::init(void)
+{
+ globals::dispatcher.sink<ChunkCreateEvent>().connect<&on_chunk_create>();
+ globals::dispatcher.sink<ChunkUpdateEvent>().connect<&on_chunk_update>();
+ globals::dispatcher.sink<VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void world::chunk_mesher::shutdown(void)
+{
+}
+
+void world::chunk_mesher::update(void)
+{
+ if(session::is_ingame()) {
+ const auto group = globals::dimension->chunks.group<NeedsMeshingComponent>(entt::get<ChunkComponent>);
+ for(const auto [entity, chunk] : group.each()) {
+ globals::dimension->chunks.remove<NeedsMeshingComponent>(entity);
+ threading::submit<GL_MeshingTask>(entity, chunk.cpos);
+ }
+ }
+}
diff --git a/game/client/world/chunk_mesher.hh b/game/client/world/chunk_mesher.hh index cb0c7c5..1ef5035 100644 --- a/game/client/world/chunk_mesher.hh +++ b/game/client/world/chunk_mesher.hh @@ -1,18 +1,18 @@ -#pragma once - -#include "client/world/chunk_vbo.hh" - -namespace world -{ -struct ChunkMesh final { - std::vector<ChunkVBO> quad_nb; - std::vector<ChunkVBO> quad_b; -}; -} // namespace world - -namespace world::chunk_mesher -{ -void init(void); -void shutdown(void); -void update(void); -} // namespace world::chunk_mesher +#pragma once
+
+#include "client/world/chunk_vbo.hh"
+
+namespace world
+{
+struct ChunkMesh final {
+ std::vector<ChunkVBO> quad_nb;
+ std::vector<ChunkVBO> quad_b;
+};
+} // namespace world
+
+namespace world::chunk_mesher
+{
+void init(void);
+void shutdown(void);
+void update(void);
+} // namespace world::chunk_mesher
diff --git a/game/client/world/chunk_quad.hh b/game/client/world/chunk_quad.hh index d160b7e..c15bb7a 100644 --- a/game/client/world/chunk_quad.hh +++ b/game/client/world/chunk_quad.hh @@ -1,41 +1,41 @@ -#pragma once - -#include "core/math/constexpr.hh" - -#include "shared/world/voxel_registry.hh" - -namespace world -{ -// [0] XXXXXXXXYYYYYYYYZZZZZZZZWWWWHHHH -// [1] FFFFTTTTTTTTTTTAAAAA------------ -using ChunkQuad = std::array<std::uint32_t, 2>; -} // namespace world - -namespace world -{ -constexpr inline static ChunkQuad make_chunk_quad(const glm::fvec3& position, const glm::fvec2& size, voxel_facing facing, - std::size_t texture, std::size_t frames) -{ - ChunkQuad result = {}; - result[0] = 0x00000000; - result[1] = 0x00000000; - - // [0] XXXXXXXXYYYYYYYYZZZZZZZZ-------- - result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.x * 16.0f)) << 24U; - result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.y * 16.0f)) << 16U; - result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.z * 16.0f)) << 8U; - - // [0] ------------------------WWWWHHHH - result[0] |= (0x0000000FU & static_cast<std::uint32_t>(size.x * 16.0f - 1.0f)) << 4U; - result[0] |= (0x0000000FU & static_cast<std::uint32_t>(size.y * 16.0f - 1.0f)); - - // [1] FFFF---------------------------- - result[1] |= (0x0000000FU & static_cast<std::uint32_t>(facing)) << 28U; - - // [1] ----TTTTTTTTTTTAAAAA------------ - result[1] |= (0x000007FFU & static_cast<std::uint32_t>(texture)) << 17U; - result[1] |= (0x0000001FU & static_cast<std::uint32_t>(frames)) << 12U; - - return result; -} -} // namespace world +#pragma once
+
+#include "core/math/constexpr.hh"
+
+#include "shared/world/voxel_registry.hh"
+
+namespace world
+{
+// [0] XXXXXXXXYYYYYYYYZZZZZZZZWWWWHHHH
+// [1] FFFFTTTTTTTTTTTAAAAA------------
+using ChunkQuad = std::array<std::uint32_t, 2>;
+} // namespace world
+
+namespace world
+{
+constexpr inline static ChunkQuad make_chunk_quad(const glm::fvec3& position, const glm::fvec2& size, voxel_facing facing,
+ std::size_t texture, std::size_t frames)
+{
+ ChunkQuad result = {};
+ result[0] = 0x00000000;
+ result[1] = 0x00000000;
+
+ // [0] XXXXXXXXYYYYYYYYZZZZZZZZ--------
+ result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.x * 16.0f)) << 24U;
+ result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.y * 16.0f)) << 16U;
+ result[0] |= (0x000000FFU & static_cast<std::uint32_t>(position.z * 16.0f)) << 8U;
+
+ // [0] ------------------------WWWWHHHH
+ result[0] |= (0x0000000FU & static_cast<std::uint32_t>(size.x * 16.0f - 1.0f)) << 4U;
+ result[0] |= (0x0000000FU & static_cast<std::uint32_t>(size.y * 16.0f - 1.0f));
+
+ // [1] FFFF----------------------------
+ result[1] |= (0x0000000FU & static_cast<std::uint32_t>(facing)) << 28U;
+
+ // [1] ----TTTTTTTTTTTAAAAA------------
+ result[1] |= (0x000007FFU & static_cast<std::uint32_t>(texture)) << 17U;
+ result[1] |= (0x0000001FU & static_cast<std::uint32_t>(frames)) << 12U;
+
+ return result;
+}
+} // namespace world
diff --git a/game/client/world/chunk_renderer.cc b/game/client/world/chunk_renderer.cc index 23ce4ec..1b195d7 100644 --- a/game/client/world/chunk_renderer.cc +++ b/game/client/world/chunk_renderer.cc @@ -1,204 +1,204 @@ -#include "client/pch.hh" - -#include "client/world/chunk_renderer.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" - -#include "client/entity/camera.hh" - -#include "client/gui/settings.hh" - -#include "client/world/chunk_mesher.hh" -#include "client/world/chunk_quad.hh" -#include "client/world/outline.hh" -#include "client/world/skybox.hh" -#include "client/world/voxel_anims.hh" -#include "client/world/voxel_atlas.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/program.hh" -#include "client/toggles.hh" - -// ONLY TOUCH THESE IF THE RESPECTIVE SHADER -// VARIANT MACRO DECLARATIONS LAYOUT CHANGED AS WELL -constexpr static unsigned int WORLD_CURVATURE = 0U; -constexpr static unsigned int WORLD_FOG = 1U; - -static config::Boolean depth_sort_chunks(true); - -static GL_Program quad_program; -static std::size_t u_quad_vproj_matrix; -static std::size_t u_quad_world_position; -static std::size_t u_quad_timings; -static std::size_t u_quad_fog_color; -static std::size_t u_quad_view_distance; -static std::size_t u_quad_textures; -static GLuint quad_vaobj; -static GLuint quad_vbo; - -void world::chunk_renderer::init(void) -{ - globals::client_config.add_value("chunk_renderer.depth_sort_chunks", depth_sort_chunks); - - settings::add_checkbox(5, depth_sort_chunks, settings_location::VIDEO, "chunk_renderer.depth_sort_chunks", false); - - if(!quad_program.setup("shaders/chunk_quad.vert", "shaders/chunk_quad.frag")) { - spdlog::critical("chunk_renderer: quad_program: setup failed"); - std::terminate(); - } - - u_quad_vproj_matrix = quad_program.add_uniform("u_ViewProjMatrix"); - u_quad_world_position = quad_program.add_uniform("u_WorldPosition"); - u_quad_timings = quad_program.add_uniform("u_Timings"); - u_quad_fog_color = quad_program.add_uniform("u_FogColor"); - u_quad_view_distance = quad_program.add_uniform("u_ViewDistance"); - u_quad_textures = quad_program.add_uniform("u_Textures"); - - const glm::fvec3 vertices[4] = { - glm::fvec3(1.0f, 0.0f, 1.0f), - glm::fvec3(1.0f, 0.0f, 0.0f), - glm::fvec3(0.0f, 0.0f, 1.0f), - glm::fvec3(0.0f, 0.0f, 0.0f), - }; - - glGenVertexArrays(1, &quad_vaobj); - glBindVertexArray(quad_vaobj); - - glGenBuffers(1, &quad_vbo); - glBindBuffer(GL_ARRAY_BUFFER, quad_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribDivisor(0, 0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr); -} - -void world::chunk_renderer::shutdown(void) -{ - glDeleteBuffers(1, &quad_vbo); - glDeleteVertexArrays(1, &quad_vaobj); - quad_program.destroy(); -} - -void world::chunk_renderer::render(void) -{ - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - glLineWidth(1.0f); - - if(toggles::get(TOGGLE_WIREFRAME)) { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - else { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - - quad_program.set_variant_vert(WORLD_CURVATURE, client_game::world_curvature.get_value()); - quad_program.set_variant_vert(WORLD_FOG, client_game::fog_mode.get_value()); - quad_program.set_variant_frag(WORLD_FOG, client_game::fog_mode.get_value()); - - if(!quad_program.update()) { - spdlog::critical("chunk_renderer: quad_program: update failed"); - quad_program.destroy(); - std::terminate(); - } - - GLuint timings[3]; - timings[0] = globals::window_frametime; - timings[1] = globals::window_frametime_avg; - timings[2] = world::voxel_anims::frame; - - const auto group = globals::dimension->chunks.group<ChunkComponent>(entt::get<world::ChunkMesh>); - - if(depth_sort_chunks.get_value()) { - // FIXME: speed! sorting every frame doesn't look - // like a good idea. Can we store the group elsewhere and - // still have all the up-to-date chunk things inside? - group.sort([](entt::entity ea, entt::entity eb) { - const auto dir_a = globals::dimension->chunks.get<ChunkComponent>(ea).cpos - entity::camera::position_chunk; - const auto dir_b = globals::dimension->chunks.get<ChunkComponent>(eb).cpos - entity::camera::position_chunk; - - const auto da = dir_a[0] * dir_a[0] + dir_a[1] * dir_a[1] + dir_a[2] * dir_a[2]; - const auto db = dir_b[0] * dir_b[0] + dir_b[1] * dir_b[1] + dir_b[2] * dir_b[2]; - - return da > db; - }); - } - - for(std::size_t plane_id = 0; plane_id < world::voxel_atlas::plane_count(); ++plane_id) { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D_ARRAY, world::voxel_atlas::plane_texture(plane_id)); - - glBindVertexArray(quad_vaobj); - - glUseProgram(quad_program.handle); - glUniformMatrix4fv(quad_program.uniforms[u_quad_vproj_matrix].location, 1, false, glm::value_ptr(entity::camera::matrix)); - glUniform3uiv(quad_program.uniforms[u_quad_timings].location, 1, timings); - glUniform4fv(quad_program.uniforms[u_quad_fog_color].location, 1, glm::value_ptr(world::skybox::fog_color)); - glUniform1f(quad_program.uniforms[u_quad_view_distance].location, entity::camera::view_distance.get_value() * CHUNK_SIZE); - glUniform1i(quad_program.uniforms[u_quad_textures].location, 0); // GL_TEXTURE0 - - glDisable(GL_BLEND); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glFrontFace(GL_CCW); - - for(const auto [entity, chunk, mesh] : group.each()) { - if(plane_id < mesh.quad_nb.size() && mesh.quad_nb[plane_id].handle && mesh.quad_nb[plane_id].size) { - const auto wpos = coord::to_fvec3(chunk.cpos - entity::camera::position_chunk); - glUniform3fv(quad_program.uniforms[u_quad_world_position].location, 1, glm::value_ptr(wpos)); - - glBindBuffer(GL_ARRAY_BUFFER, mesh.quad_nb[plane_id].handle); - - glEnableVertexAttribArray(1); - glVertexAttribDivisor(1, 1); - glVertexAttribIPointer(1, 2, GL_UNSIGNED_INT, sizeof(ChunkQuad), nullptr); - - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, mesh.quad_nb[plane_id].size); - - globals::num_drawcalls += 1; - globals::num_triangles += 2 * mesh.quad_nb[plane_id].size; - } - } - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - for(const auto [entity, chunk, mesh] : group.each()) { - if(plane_id < mesh.quad_b.size() && mesh.quad_b[plane_id].handle && mesh.quad_b[plane_id].size) { - const auto wpos = coord::to_fvec3(chunk.cpos - entity::camera::position_chunk); - glUniform3fv(quad_program.uniforms[u_quad_world_position].location, 1, glm::value_ptr(wpos)); - - glBindBuffer(GL_ARRAY_BUFFER, mesh.quad_b[plane_id].handle); - - glEnableVertexAttribArray(1); - glVertexAttribDivisor(1, 1); - glVertexAttribIPointer(1, 2, GL_UNSIGNED_INT, sizeof(ChunkQuad), nullptr); - - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, mesh.quad_b[plane_id].size); - - globals::num_drawcalls += 1; - globals::num_triangles += 2 * mesh.quad_b[plane_id].size; - } - } - } - - if(toggles::get(TOGGLE_CHUNK_AABB)) { - world::outline::prepare(); - - for(const auto [entity, chunk, mesh] : group.each()) { - const auto size = glm::fvec3(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE); - world::outline::cube(chunk.cpos, glm::fvec3(0.0f, 0.0f, 0.0f), size, 1.0f, glm::fvec4(1.0f, 1.0f, 0.0f, 1.0f)); - } - } -} +#include "client/pch.hh"
+
+#include "client/world/chunk_renderer.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/gui/settings.hh"
+
+#include "client/world/chunk_mesher.hh"
+#include "client/world/chunk_quad.hh"
+#include "client/world/outline.hh"
+#include "client/world/skybox.hh"
+#include "client/world/voxel_anims.hh"
+#include "client/world/voxel_atlas.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/program.hh"
+#include "client/toggles.hh"
+
+// ONLY TOUCH THESE IF THE RESPECTIVE SHADER
+// VARIANT MACRO DECLARATIONS LAYOUT CHANGED AS WELL
+constexpr static unsigned int WORLD_CURVATURE = 0U;
+constexpr static unsigned int WORLD_FOG = 1U;
+
+static config::Boolean depth_sort_chunks(true);
+
+static GL_Program quad_program;
+static std::size_t u_quad_vproj_matrix;
+static std::size_t u_quad_world_position;
+static std::size_t u_quad_timings;
+static std::size_t u_quad_fog_color;
+static std::size_t u_quad_view_distance;
+static std::size_t u_quad_textures;
+static GLuint quad_vaobj;
+static GLuint quad_vbo;
+
+void world::chunk_renderer::init(void)
+{
+ globals::client_config.add_value("chunk_renderer.depth_sort_chunks", depth_sort_chunks);
+
+ settings::add_checkbox(5, depth_sort_chunks, settings_location::VIDEO, "chunk_renderer.depth_sort_chunks", false);
+
+ if(!quad_program.setup("shaders/chunk_quad.vert", "shaders/chunk_quad.frag")) {
+ spdlog::critical("chunk_renderer: quad_program: setup failed");
+ std::terminate();
+ }
+
+ u_quad_vproj_matrix = quad_program.add_uniform("u_ViewProjMatrix");
+ u_quad_world_position = quad_program.add_uniform("u_WorldPosition");
+ u_quad_timings = quad_program.add_uniform("u_Timings");
+ u_quad_fog_color = quad_program.add_uniform("u_FogColor");
+ u_quad_view_distance = quad_program.add_uniform("u_ViewDistance");
+ u_quad_textures = quad_program.add_uniform("u_Textures");
+
+ const glm::fvec3 vertices[4] = {
+ glm::fvec3(1.0f, 0.0f, 1.0f),
+ glm::fvec3(1.0f, 0.0f, 0.0f),
+ glm::fvec3(0.0f, 0.0f, 1.0f),
+ glm::fvec3(0.0f, 0.0f, 0.0f),
+ };
+
+ glGenVertexArrays(1, &quad_vaobj);
+ glBindVertexArray(quad_vaobj);
+
+ glGenBuffers(1, &quad_vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(0);
+ glVertexAttribDivisor(0, 0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr);
+}
+
+void world::chunk_renderer::shutdown(void)
+{
+ glDeleteBuffers(1, &quad_vbo);
+ glDeleteVertexArrays(1, &quad_vaobj);
+ quad_program.destroy();
+}
+
+void world::chunk_renderer::render(void)
+{
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LEQUAL);
+ glLineWidth(1.0f);
+
+ if(toggles::get(TOGGLE_WIREFRAME)) {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ }
+ else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+
+ quad_program.set_variant_vert(WORLD_CURVATURE, client_game::world_curvature.get_value());
+ quad_program.set_variant_vert(WORLD_FOG, client_game::fog_mode.get_value());
+ quad_program.set_variant_frag(WORLD_FOG, client_game::fog_mode.get_value());
+
+ if(!quad_program.update()) {
+ spdlog::critical("chunk_renderer: quad_program: update failed");
+ quad_program.destroy();
+ std::terminate();
+ }
+
+ GLuint timings[3];
+ timings[0] = globals::window_frametime;
+ timings[1] = globals::window_frametime_avg;
+ timings[2] = world::voxel_anims::frame;
+
+ const auto group = globals::dimension->chunks.group<ChunkComponent>(entt::get<world::ChunkMesh>);
+
+ if(depth_sort_chunks.get_value()) {
+ // FIXME: speed! sorting every frame doesn't look
+ // like a good idea. Can we store the group elsewhere and
+ // still have all the up-to-date chunk things inside?
+ group.sort([](entt::entity ea, entt::entity eb) {
+ const auto dir_a = globals::dimension->chunks.get<ChunkComponent>(ea).cpos - entity::camera::position_chunk;
+ const auto dir_b = globals::dimension->chunks.get<ChunkComponent>(eb).cpos - entity::camera::position_chunk;
+
+ const auto da = dir_a[0] * dir_a[0] + dir_a[1] * dir_a[1] + dir_a[2] * dir_a[2];
+ const auto db = dir_b[0] * dir_b[0] + dir_b[1] * dir_b[1] + dir_b[2] * dir_b[2];
+
+ return da > db;
+ });
+ }
+
+ for(std::size_t plane_id = 0; plane_id < world::voxel_atlas::plane_count(); ++plane_id) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, world::voxel_atlas::plane_texture(plane_id));
+
+ glBindVertexArray(quad_vaobj);
+
+ glUseProgram(quad_program.handle);
+ glUniformMatrix4fv(quad_program.uniforms[u_quad_vproj_matrix].location, 1, false, glm::value_ptr(entity::camera::matrix));
+ glUniform3uiv(quad_program.uniforms[u_quad_timings].location, 1, timings);
+ glUniform4fv(quad_program.uniforms[u_quad_fog_color].location, 1, glm::value_ptr(world::skybox::fog_color));
+ glUniform1f(quad_program.uniforms[u_quad_view_distance].location, entity::camera::view_distance.get_value() * CHUNK_SIZE);
+ glUniform1i(quad_program.uniforms[u_quad_textures].location, 0); // GL_TEXTURE0
+
+ glDisable(GL_BLEND);
+
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ glFrontFace(GL_CCW);
+
+ for(const auto [entity, chunk, mesh] : group.each()) {
+ if(plane_id < mesh.quad_nb.size() && mesh.quad_nb[plane_id].handle && mesh.quad_nb[plane_id].size) {
+ const auto wpos = coord::to_fvec3(chunk.cpos - entity::camera::position_chunk);
+ glUniform3fv(quad_program.uniforms[u_quad_world_position].location, 1, glm::value_ptr(wpos));
+
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.quad_nb[plane_id].handle);
+
+ glEnableVertexAttribArray(1);
+ glVertexAttribDivisor(1, 1);
+ glVertexAttribIPointer(1, 2, GL_UNSIGNED_INT, sizeof(ChunkQuad), nullptr);
+
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, mesh.quad_nb[plane_id].size);
+
+ globals::num_drawcalls += 1;
+ globals::num_triangles += 2 * mesh.quad_nb[plane_id].size;
+ }
+ }
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ for(const auto [entity, chunk, mesh] : group.each()) {
+ if(plane_id < mesh.quad_b.size() && mesh.quad_b[plane_id].handle && mesh.quad_b[plane_id].size) {
+ const auto wpos = coord::to_fvec3(chunk.cpos - entity::camera::position_chunk);
+ glUniform3fv(quad_program.uniforms[u_quad_world_position].location, 1, glm::value_ptr(wpos));
+
+ glBindBuffer(GL_ARRAY_BUFFER, mesh.quad_b[plane_id].handle);
+
+ glEnableVertexAttribArray(1);
+ glVertexAttribDivisor(1, 1);
+ glVertexAttribIPointer(1, 2, GL_UNSIGNED_INT, sizeof(ChunkQuad), nullptr);
+
+ glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, mesh.quad_b[plane_id].size);
+
+ globals::num_drawcalls += 1;
+ globals::num_triangles += 2 * mesh.quad_b[plane_id].size;
+ }
+ }
+ }
+
+ if(toggles::get(TOGGLE_CHUNK_AABB)) {
+ world::outline::prepare();
+
+ for(const auto [entity, chunk, mesh] : group.each()) {
+ const auto size = glm::fvec3(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE);
+ world::outline::cube(chunk.cpos, glm::fvec3(0.0f, 0.0f, 0.0f), size, 1.0f, glm::fvec4(1.0f, 1.0f, 0.0f, 1.0f));
+ }
+ }
+}
diff --git a/game/client/world/chunk_renderer.hh b/game/client/world/chunk_renderer.hh index 2b73225..88b381f 100644 --- a/game/client/world/chunk_renderer.hh +++ b/game/client/world/chunk_renderer.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace world::chunk_renderer -{ -void init(void); -void shutdown(void); -void render(void); -} // namespace world::chunk_renderer +#pragma once
+
+namespace world::chunk_renderer
+{
+void init(void);
+void shutdown(void);
+void render(void);
+} // namespace world::chunk_renderer
diff --git a/game/client/world/chunk_vbo.hh b/game/client/world/chunk_vbo.hh index 175b34f..9fedf0d 100644 --- a/game/client/world/chunk_vbo.hh +++ b/game/client/world/chunk_vbo.hh @@ -1,22 +1,22 @@ -#pragma once - -namespace world -{ -class ChunkVBO final { -public: - std::size_t size; - GLuint handle; - -public: - inline ~ChunkVBO(void) - { - // The ChunkVBO structure is meant to be a part - // of the ChunkMesh component within the EnTT registry; - // When the registry is cleaned or a chunk is removed, components - // are expected to be safely disposed of so we need a destructor; - if(handle) { - glDeleteBuffers(1, &handle); - } - } -}; -} // namespace world +#pragma once
+
+namespace world
+{
+class ChunkVBO final {
+public:
+ std::size_t size;
+ GLuint handle;
+
+public:
+ inline ~ChunkVBO(void)
+ {
+ // The ChunkVBO structure is meant to be a part
+ // of the ChunkMesh component within the EnTT registry;
+ // When the registry is cleaned or a chunk is removed, components
+ // are expected to be safely disposed of so we need a destructor;
+ if(handle) {
+ glDeleteBuffers(1, &handle);
+ }
+ }
+};
+} // namespace world
diff --git a/game/client/world/chunk_visibility.cc b/game/client/world/chunk_visibility.cc index 871c04b..e8828ee 100644 --- a/game/client/world/chunk_visibility.cc +++ b/game/client/world/chunk_visibility.cc @@ -1,90 +1,90 @@ -#include "client/pch.hh" - -#include "client/world/chunk_visibility.hh" - -#include "core/config/number.hh" - -#include "core/math/vectors.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/chunk_aabb.hh" -#include "shared/world/dimension.hh" - -#include "shared/protocol.hh" - -#include "client/entity/camera.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -// Sending a somewhat large amount of network packets -// can easily overwhelm both client, server and the network -// channel created between the two. To prevent this from happening -// we throttle the client's ever increasing itch for new chunks -constexpr static unsigned int MAX_CHUNKS_REQUESTS_PER_FRAME = 16U; - -static world::ChunkAABB current_view_box; -static world::ChunkAABB previous_view_box; -static std::vector<chunk_pos> requests; - -static void update_requests(void) -{ - requests.clear(); - - for(auto cx = current_view_box.min.x; cx != current_view_box.max.x; cx += 1) - for(auto cy = current_view_box.min.y; cy != current_view_box.max.y; cy += 1) - for(auto cz = current_view_box.min.z; cz != current_view_box.max.z; cz += 1) { - auto cpos = chunk_pos(cx, cy, cz); - - if(!globals::dimension->find_chunk(cpos)) { - requests.push_back(cpos); - } - } - - std::sort(requests.begin(), requests.end(), [](const chunk_pos& cpos_a, const chunk_pos& cpos_b) { - auto da = math::distance2(cpos_a, entity::camera::position_chunk); - auto db = math::distance2(cpos_b, entity::camera::position_chunk); - return da > db; - }); -} - -void world::chunk_visibility::update_late(void) -{ - current_view_box.min = entity::camera::position_chunk - static_cast<chunk_pos::value_type>(entity::camera::view_distance.get_value()); - current_view_box.max = entity::camera::position_chunk + static_cast<chunk_pos::value_type>(entity::camera::view_distance.get_value()); - - if(!session::is_ingame()) { - // This makes sure the previous view box - // is always different from the current one - previous_view_box.min = chunk_pos(INT32_MIN, INT32_MIN, INT32_MIN); - previous_view_box.max = chunk_pos(INT32_MAX, INT32_MAX, INT32_MAX); - return; - } - - if((current_view_box.min != previous_view_box.min) || (current_view_box.max != previous_view_box.max)) { - update_requests(); - } - - for(unsigned int i = 0U; i < MAX_CHUNKS_REQUESTS_PER_FRAME; ++i) { - if(requests.empty()) { - // Done sending requests - break; - } - - protocol::RequestChunk packet; - packet.cpos = requests.back(); - protocol::send(session::peer, protocol::encode(packet)); - - requests.pop_back(); - } - - auto view = globals::dimension->chunks.view<ChunkComponent>(); - - for(const auto [entity, chunk] : view.each()) { - if(!current_view_box.contains(chunk.cpos)) { - globals::dimension->remove_chunk(entity); - } - } - - previous_view_box = current_view_box; -} +#include "client/pch.hh"
+
+#include "client/world/chunk_visibility.hh"
+
+#include "core/config/number.hh"
+
+#include "core/math/vectors.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/chunk_aabb.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+// Sending a somewhat large amount of network packets
+// can easily overwhelm both client, server and the network
+// channel created between the two. To prevent this from happening
+// we throttle the client's ever increasing itch for new chunks
+constexpr static unsigned int MAX_CHUNKS_REQUESTS_PER_FRAME = 16U;
+
+static world::ChunkAABB current_view_box;
+static world::ChunkAABB previous_view_box;
+static std::vector<chunk_pos> requests;
+
+static void update_requests(void)
+{
+ requests.clear();
+
+ for(auto cx = current_view_box.min.x; cx != current_view_box.max.x; cx += 1)
+ for(auto cy = current_view_box.min.y; cy != current_view_box.max.y; cy += 1)
+ for(auto cz = current_view_box.min.z; cz != current_view_box.max.z; cz += 1) {
+ auto cpos = chunk_pos(cx, cy, cz);
+
+ if(!globals::dimension->find_chunk(cpos)) {
+ requests.push_back(cpos);
+ }
+ }
+
+ std::sort(requests.begin(), requests.end(), [](const chunk_pos& cpos_a, const chunk_pos& cpos_b) {
+ auto da = math::distance2(cpos_a, entity::camera::position_chunk);
+ auto db = math::distance2(cpos_b, entity::camera::position_chunk);
+ return da > db;
+ });
+}
+
+void world::chunk_visibility::update_late(void)
+{
+ current_view_box.min = entity::camera::position_chunk - static_cast<chunk_pos::value_type>(entity::camera::view_distance.get_value());
+ current_view_box.max = entity::camera::position_chunk + static_cast<chunk_pos::value_type>(entity::camera::view_distance.get_value());
+
+ if(!session::is_ingame()) {
+ // This makes sure the previous view box
+ // is always different from the current one
+ previous_view_box.min = chunk_pos(INT32_MIN, INT32_MIN, INT32_MIN);
+ previous_view_box.max = chunk_pos(INT32_MAX, INT32_MAX, INT32_MAX);
+ return;
+ }
+
+ if((current_view_box.min != previous_view_box.min) || (current_view_box.max != previous_view_box.max)) {
+ update_requests();
+ }
+
+ for(unsigned int i = 0U; i < MAX_CHUNKS_REQUESTS_PER_FRAME; ++i) {
+ if(requests.empty()) {
+ // Done sending requests
+ break;
+ }
+
+ protocol::RequestChunk packet;
+ packet.cpos = requests.back();
+ protocol::send(session::peer, protocol::encode(packet));
+
+ requests.pop_back();
+ }
+
+ auto view = globals::dimension->chunks.view<ChunkComponent>();
+
+ for(const auto [entity, chunk] : view.each()) {
+ if(!current_view_box.contains(chunk.cpos)) {
+ globals::dimension->remove_chunk(entity);
+ }
+ }
+
+ previous_view_box = current_view_box;
+}
diff --git a/game/client/world/chunk_visibility.hh b/game/client/world/chunk_visibility.hh index 8d1f3cd..e9b2d89 100644 --- a/game/client/world/chunk_visibility.hh +++ b/game/client/world/chunk_visibility.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace world::chunk_visibility -{ -void update_late(void); -} // namespace world::chunk_visibility +#pragma once
+
+namespace world::chunk_visibility
+{
+void update_late(void);
+} // namespace world::chunk_visibility
diff --git a/game/client/world/outline.cc b/game/client/world/outline.cc index 396d297..62f9624 100644 --- a/game/client/world/outline.cc +++ b/game/client/world/outline.cc @@ -1,150 +1,150 @@ -#include "client/pch.hh" - -#include "client/world/outline.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" - -#include "shared/coord.hh" - -#include "client/entity/camera.hh" - -#include "client/const.hh" -#include "client/game.hh" -#include "client/program.hh" - -// ONLY TOUCH THESE IF THE RESPECTIVE SHADER -// VARIANT MACRO DECLARATIONS LAYOUT CHANGED AS WELL -constexpr static unsigned int WORLD_CURVATURE = 0U; - -static GL_Program program; -static std::size_t u_vpmatrix; -static std::size_t u_worldpos; -static std::size_t u_viewdist; -static std::size_t u_modulate; -static std::size_t u_scale; - -static GLuint vaobj; -static GLuint cube_vbo; -static GLuint line_vbo; - -void world::outline::init(void) -{ - if(!program.setup("shaders/outline.vert", "shaders/outline.frag")) { - spdlog::critical("outline: program setup failed"); - std::terminate(); - } - - u_vpmatrix = program.add_uniform("u_ViewProjMatrix"); - u_worldpos = program.add_uniform("u_WorldPosition"); - u_viewdist = program.add_uniform("u_ViewDistance"); - u_modulate = program.add_uniform("u_Modulate"); - u_scale = program.add_uniform("u_Scale"); - - const glm::fvec3 cube_vertices[24] = { - glm::fvec3(0.0f, 0.0f, 0.0f), - glm::fvec3(0.0f, 1.0f, 0.0f), - glm::fvec3(0.0f, 1.0f, 0.0f), - glm::fvec3(1.0f, 1.0f, 0.0f), - glm::fvec3(1.0f, 1.0f, 0.0f), - glm::fvec3(1.0f, 0.0f, 0.0f), - glm::fvec3(1.0f, 0.0f, 0.0f), - glm::fvec3(0.0f, 0.0f, 0.0f), - - glm::fvec3(0.0f, 0.0f, 1.0f), - glm::fvec3(0.0f, 1.0f, 1.0f), - glm::fvec3(0.0f, 1.0f, 1.0f), - glm::fvec3(1.0f, 1.0f, 1.0f), - glm::fvec3(1.0f, 1.0f, 1.0f), - glm::fvec3(1.0f, 0.0f, 1.0f), - glm::fvec3(1.0f, 0.0f, 1.0f), - glm::fvec3(0.0f, 0.0f, 1.0f), - - glm::fvec3(0.0f, 0.0f, 0.0f), - glm::fvec3(0.0f, 0.0f, 1.0f), - glm::fvec3(0.0f, 1.0f, 0.0f), - glm::fvec3(0.0f, 1.0f, 1.0f), - glm::fvec3(1.0f, 0.0f, 0.0f), - glm::fvec3(1.0f, 0.0f, 1.0f), - glm::fvec3(1.0f, 1.0f, 0.0f), - glm::fvec3(1.0f, 1.0f, 1.0f), - }; - - glGenBuffers(1, &cube_vbo); - glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW); - - const glm::fvec3 line_vertices[2] = { - glm::fvec3(0.0f, 0.0f, 0.0f), - glm::fvec3(1.0f, 1.0f, 1.0f), - }; - - glGenBuffers(1, &line_vbo); - glBindBuffer(GL_ARRAY_BUFFER, line_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(line_vertices), line_vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &vaobj); - - glBindVertexArray(vaobj); - glEnableVertexAttribArray(0); - glVertexAttribDivisor(0, 0); -} - -void world::outline::shutdown(void) -{ - glDeleteVertexArrays(1, &vaobj); - glDeleteBuffers(1, &line_vbo); - glDeleteBuffers(1, &cube_vbo); - program.destroy(); -} - -void world::outline::prepare(void) -{ - program.set_variant_vert(WORLD_CURVATURE, client_game::world_curvature.get_value()); - - if(!program.update()) { - spdlog::critical("outline_renderer: program update failed"); - std::terminate(); - } - - glDisable(GL_CULL_FACE); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - glUseProgram(program.handle); - glUniformMatrix4fv(program.uniforms[u_vpmatrix].location, 1, false, glm::value_ptr(entity::camera::matrix)); - glUniform1f(program.uniforms[u_viewdist].location, CHUNK_SIZE * entity::camera::view_distance.get_value()); - - glBindVertexArray(vaobj); - glEnableVertexAttribArray(0); - glVertexAttribDivisor(0, 0); -} - -void world::outline::cube(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color) -{ - auto patch_cpos = cpos - entity::camera::position_chunk; - - glLineWidth(thickness); - - glUniform3fv(program.uniforms[u_worldpos].location, 1, glm::value_ptr(coord::to_fvec3(patch_cpos, fpos))); - glUniform4fv(program.uniforms[u_modulate].location, 1, glm::value_ptr(color)); - glUniform3fv(program.uniforms[u_scale].location, 1, glm::value_ptr(size)); - - glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); - glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr); - glDrawArrays(GL_LINES, 0, 24); -} - -void world::outline::line(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color) -{ - auto patch_cpos = cpos - entity::camera::position_chunk; - - glLineWidth(thickness); - - glUniform3fv(program.uniforms[u_worldpos].location, 1, glm::value_ptr(coord::to_fvec3(patch_cpos, fpos))); - glUniform4fv(program.uniforms[u_modulate].location, 1, glm::value_ptr(color)); - glUniform3fv(program.uniforms[u_scale].location, 1, glm::value_ptr(size)); - - glBindBuffer(GL_ARRAY_BUFFER, line_vbo); - glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr); - glDrawArrays(GL_LINES, 0, 2); -} +#include "client/pch.hh"
+
+#include "client/world/outline.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+
+#include "shared/coord.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/const.hh"
+#include "client/game.hh"
+#include "client/program.hh"
+
+// ONLY TOUCH THESE IF THE RESPECTIVE SHADER
+// VARIANT MACRO DECLARATIONS LAYOUT CHANGED AS WELL
+constexpr static unsigned int WORLD_CURVATURE = 0U;
+
+static GL_Program program;
+static std::size_t u_vpmatrix;
+static std::size_t u_worldpos;
+static std::size_t u_viewdist;
+static std::size_t u_modulate;
+static std::size_t u_scale;
+
+static GLuint vaobj;
+static GLuint cube_vbo;
+static GLuint line_vbo;
+
+void world::outline::init(void)
+{
+ if(!program.setup("shaders/outline.vert", "shaders/outline.frag")) {
+ spdlog::critical("outline: program setup failed");
+ std::terminate();
+ }
+
+ u_vpmatrix = program.add_uniform("u_ViewProjMatrix");
+ u_worldpos = program.add_uniform("u_WorldPosition");
+ u_viewdist = program.add_uniform("u_ViewDistance");
+ u_modulate = program.add_uniform("u_Modulate");
+ u_scale = program.add_uniform("u_Scale");
+
+ const glm::fvec3 cube_vertices[24] = {
+ glm::fvec3(0.0f, 0.0f, 0.0f),
+ glm::fvec3(0.0f, 1.0f, 0.0f),
+ glm::fvec3(0.0f, 1.0f, 0.0f),
+ glm::fvec3(1.0f, 1.0f, 0.0f),
+ glm::fvec3(1.0f, 1.0f, 0.0f),
+ glm::fvec3(1.0f, 0.0f, 0.0f),
+ glm::fvec3(1.0f, 0.0f, 0.0f),
+ glm::fvec3(0.0f, 0.0f, 0.0f),
+
+ glm::fvec3(0.0f, 0.0f, 1.0f),
+ glm::fvec3(0.0f, 1.0f, 1.0f),
+ glm::fvec3(0.0f, 1.0f, 1.0f),
+ glm::fvec3(1.0f, 1.0f, 1.0f),
+ glm::fvec3(1.0f, 1.0f, 1.0f),
+ glm::fvec3(1.0f, 0.0f, 1.0f),
+ glm::fvec3(1.0f, 0.0f, 1.0f),
+ glm::fvec3(0.0f, 0.0f, 1.0f),
+
+ glm::fvec3(0.0f, 0.0f, 0.0f),
+ glm::fvec3(0.0f, 0.0f, 1.0f),
+ glm::fvec3(0.0f, 1.0f, 0.0f),
+ glm::fvec3(0.0f, 1.0f, 1.0f),
+ glm::fvec3(1.0f, 0.0f, 0.0f),
+ glm::fvec3(1.0f, 0.0f, 1.0f),
+ glm::fvec3(1.0f, 1.0f, 0.0f),
+ glm::fvec3(1.0f, 1.0f, 1.0f),
+ };
+
+ glGenBuffers(1, &cube_vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW);
+
+ const glm::fvec3 line_vertices[2] = {
+ glm::fvec3(0.0f, 0.0f, 0.0f),
+ glm::fvec3(1.0f, 1.0f, 1.0f),
+ };
+
+ glGenBuffers(1, &line_vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, line_vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(line_vertices), line_vertices, GL_STATIC_DRAW);
+
+ glGenVertexArrays(1, &vaobj);
+
+ glBindVertexArray(vaobj);
+ glEnableVertexAttribArray(0);
+ glVertexAttribDivisor(0, 0);
+}
+
+void world::outline::shutdown(void)
+{
+ glDeleteVertexArrays(1, &vaobj);
+ glDeleteBuffers(1, &line_vbo);
+ glDeleteBuffers(1, &cube_vbo);
+ program.destroy();
+}
+
+void world::outline::prepare(void)
+{
+ program.set_variant_vert(WORLD_CURVATURE, client_game::world_curvature.get_value());
+
+ if(!program.update()) {
+ spdlog::critical("outline_renderer: program update failed");
+ std::terminate();
+ }
+
+ glDisable(GL_CULL_FACE);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
+ glUseProgram(program.handle);
+ glUniformMatrix4fv(program.uniforms[u_vpmatrix].location, 1, false, glm::value_ptr(entity::camera::matrix));
+ glUniform1f(program.uniforms[u_viewdist].location, CHUNK_SIZE * entity::camera::view_distance.get_value());
+
+ glBindVertexArray(vaobj);
+ glEnableVertexAttribArray(0);
+ glVertexAttribDivisor(0, 0);
+}
+
+void world::outline::cube(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color)
+{
+ auto patch_cpos = cpos - entity::camera::position_chunk;
+
+ glLineWidth(thickness);
+
+ glUniform3fv(program.uniforms[u_worldpos].location, 1, glm::value_ptr(coord::to_fvec3(patch_cpos, fpos)));
+ glUniform4fv(program.uniforms[u_modulate].location, 1, glm::value_ptr(color));
+ glUniform3fv(program.uniforms[u_scale].location, 1, glm::value_ptr(size));
+
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbo);
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr);
+ glDrawArrays(GL_LINES, 0, 24);
+}
+
+void world::outline::line(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color)
+{
+ auto patch_cpos = cpos - entity::camera::position_chunk;
+
+ glLineWidth(thickness);
+
+ glUniform3fv(program.uniforms[u_worldpos].location, 1, glm::value_ptr(coord::to_fvec3(patch_cpos, fpos)));
+ glUniform4fv(program.uniforms[u_modulate].location, 1, glm::value_ptr(color));
+ glUniform3fv(program.uniforms[u_scale].location, 1, glm::value_ptr(size));
+
+ glBindBuffer(GL_ARRAY_BUFFER, line_vbo);
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(glm::fvec3), nullptr);
+ glDrawArrays(GL_LINES, 0, 2);
+}
diff --git a/game/client/world/outline.hh b/game/client/world/outline.hh index 2456a32..bd69ecc 100644 --- a/game/client/world/outline.hh +++ b/game/client/world/outline.hh @@ -1,16 +1,16 @@ -#pragma once - -#include "shared/types.hh" - -namespace world::outline -{ -void init(void); -void shutdown(void); -void prepare(void); -} // namespace world::outline - -namespace world::outline -{ -void cube(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color); -void line(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color); -} // namespace world::outline +#pragma once
+
+#include "shared/types.hh"
+
+namespace world::outline
+{
+void init(void);
+void shutdown(void);
+void prepare(void);
+} // namespace world::outline
+
+namespace world::outline
+{
+void cube(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color);
+void line(const chunk_pos& cpos, const glm::fvec3& fpos, const glm::fvec3& size, float thickness, const glm::fvec4& color);
+} // namespace world::outline
diff --git a/game/client/world/player_target.cc b/game/client/world/player_target.cc index a398eed..f0550c0 100644 --- a/game/client/world/player_target.cc +++ b/game/client/world/player_target.cc @@ -1,69 +1,69 @@ -#include "client/pch.hh" - -#include "client/world/player_target.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/ray_dda.hh" - -#include "shared/coord.hh" - -#include "client/entity/camera.hh" -#include "client/world/outline.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static float MAX_REACH = 16.0f; - -voxel_id world::player_target::voxel; -voxel_pos world::player_target::coord; -voxel_pos world::player_target::normal; -const world::VoxelInfo* world::player_target::info; - -void world::player_target::init(void) -{ - world::player_target::voxel = NULL_VOXEL_ID; - world::player_target::coord = voxel_pos(); - world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; -} - -void world::player_target::update(void) -{ - if(session::is_ingame()) { - RayDDA ray(globals::dimension, entity::camera::position_chunk, entity::camera::position_local, entity::camera::direction); - - do { - world::player_target::voxel = ray.step(); - - if(world::player_target::voxel != NULL_VOXEL_ID) { - world::player_target::coord = ray.vpos; - world::player_target::normal = ray.vnormal; - world::player_target::info = world::voxel_registry::find(world::player_target::voxel); - break; - } - - world::player_target::coord = voxel_pos(); - world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; - } while(ray.distance < MAX_REACH); - } - else { - world::player_target::voxel = NULL_VOXEL_ID; - world::player_target::coord = voxel_pos(); - world::player_target::normal = voxel_pos(); - world::player_target::info = nullptr; - } -} - -void world::player_target::render(void) -{ - if((world::player_target::voxel != NULL_VOXEL_ID) && !client_game::hide_hud) { - auto cpos = coord::to_chunk(world::player_target::coord); - auto fpos = coord::to_local(world::player_target::coord); - - world::outline::prepare(); - world::outline::cube(cpos, glm::fvec3(fpos), glm::fvec3(1.0f), 2.0f, glm::fvec4(0.0f, 0.0f, 0.0f, 1.0f)); - } -} +#include "client/pch.hh"
+
+#include "client/world/player_target.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/ray_dda.hh"
+
+#include "shared/coord.hh"
+
+#include "client/entity/camera.hh"
+#include "client/world/outline.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static float MAX_REACH = 16.0f;
+
+voxel_id world::player_target::voxel;
+voxel_pos world::player_target::coord;
+voxel_pos world::player_target::normal;
+const world::VoxelInfo* world::player_target::info;
+
+void world::player_target::init(void)
+{
+ world::player_target::voxel = NULL_VOXEL_ID;
+ world::player_target::coord = voxel_pos();
+ world::player_target::normal = voxel_pos();
+ world::player_target::info = nullptr;
+}
+
+void world::player_target::update(void)
+{
+ if(session::is_ingame()) {
+ RayDDA ray(globals::dimension, entity::camera::position_chunk, entity::camera::position_local, entity::camera::direction);
+
+ do {
+ world::player_target::voxel = ray.step();
+
+ if(world::player_target::voxel != NULL_VOXEL_ID) {
+ world::player_target::coord = ray.vpos;
+ world::player_target::normal = ray.vnormal;
+ world::player_target::info = world::voxel_registry::find(world::player_target::voxel);
+ break;
+ }
+
+ world::player_target::coord = voxel_pos();
+ world::player_target::normal = voxel_pos();
+ world::player_target::info = nullptr;
+ } while(ray.distance < MAX_REACH);
+ }
+ else {
+ world::player_target::voxel = NULL_VOXEL_ID;
+ world::player_target::coord = voxel_pos();
+ world::player_target::normal = voxel_pos();
+ world::player_target::info = nullptr;
+ }
+}
+
+void world::player_target::render(void)
+{
+ if((world::player_target::voxel != NULL_VOXEL_ID) && !client_game::hide_hud) {
+ auto cpos = coord::to_chunk(world::player_target::coord);
+ auto fpos = coord::to_local(world::player_target::coord);
+
+ world::outline::prepare();
+ world::outline::cube(cpos, glm::fvec3(fpos), glm::fvec3(1.0f), 2.0f, glm::fvec4(0.0f, 0.0f, 0.0f, 1.0f));
+ }
+}
diff --git a/game/client/world/player_target.hh b/game/client/world/player_target.hh index f137a1b..f0db9be 100644 --- a/game/client/world/player_target.hh +++ b/game/client/world/player_target.hh @@ -1,18 +1,18 @@ -#pragma once - -#include "shared/world/voxel_registry.hh" - -namespace world::player_target -{ -extern voxel_id voxel; -extern voxel_pos coord; -extern voxel_pos normal; -extern const VoxelInfo* info; -} // namespace world::player_target - -namespace world::player_target -{ -void init(void); -void update(void); -void render(void); -} // namespace world::player_target +#pragma once
+
+#include "shared/world/voxel_registry.hh"
+
+namespace world::player_target
+{
+extern voxel_id voxel;
+extern voxel_pos coord;
+extern voxel_pos normal;
+extern const VoxelInfo* info;
+} // namespace world::player_target
+
+namespace world::player_target
+{
+void init(void);
+void update(void);
+void render(void);
+} // namespace world::player_target
diff --git a/game/client/world/skybox.cc b/game/client/world/skybox.cc index 5e52fa4..cfff141 100644 --- a/game/client/world/skybox.cc +++ b/game/client/world/skybox.cc @@ -1,11 +1,11 @@ -#include "client/pch.hh" - -#include "client/world/skybox.hh" - -glm::fvec3 world::skybox::fog_color; - -void world::skybox::init(void) -{ - // https://convertingcolors.com/hex-color-B1F3FF.html - world::skybox::fog_color = glm::fvec3(0.690f, 0.950f, 1.000f); -} +#include "client/pch.hh"
+
+#include "client/world/skybox.hh"
+
+glm::fvec3 world::skybox::fog_color;
+
+void world::skybox::init(void)
+{
+ // https://convertingcolors.com/hex-color-B1F3FF.html
+ world::skybox::fog_color = glm::fvec3(0.690f, 0.950f, 1.000f);
+}
diff --git a/game/client/world/skybox.hh b/game/client/world/skybox.hh index 40113cd..39a514b 100644 --- a/game/client/world/skybox.hh +++ b/game/client/world/skybox.hh @@ -1,11 +1,11 @@ -#pragma once - -namespace world::skybox -{ -extern glm::fvec3 fog_color; -} // namespace world::skybox - -namespace world::skybox -{ -void init(void); -} // namespace world::skybox +#pragma once
+
+namespace world::skybox
+{
+extern glm::fvec3 fog_color;
+} // namespace world::skybox
+
+namespace world::skybox
+{
+void init(void);
+} // namespace world::skybox
diff --git a/game/client/world/voxel_anims.cc b/game/client/world/voxel_anims.cc index e4d9cf6..185d463 100644 --- a/game/client/world/voxel_anims.cc +++ b/game/client/world/voxel_anims.cc @@ -1,33 +1,33 @@ -#include "client/pch.hh" - -#include "client/world/voxel_anims.hh" - -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "client/globals.hh" - -static config::Unsigned base_framerate(16U, 1U, 16U); - -std::uint64_t world::voxel_anims::nextframe = 0U; -std::uint32_t world::voxel_anims::frame = 0U; - -void world::voxel_anims::init(void) -{ - globals::client_config.add_value("voxel_anims.base_framerate", base_framerate); - - world::voxel_anims::nextframe = 0U; - world::voxel_anims::frame = 0U; -} - -void world::voxel_anims::update(void) -{ - if(globals::curtime >= world::voxel_anims::nextframe) { - world::voxel_anims::nextframe = globals::curtime - + static_cast<std::uint64_t>(1000000.0 / static_cast<float>(base_framerate.get_value())); - world::voxel_anims::frame += 1U; - } -} +#include "client/pch.hh"
+
+#include "client/world/voxel_anims.hh"
+
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/globals.hh"
+
+static config::Unsigned base_framerate(16U, 1U, 16U);
+
+std::uint64_t world::voxel_anims::nextframe = 0U;
+std::uint32_t world::voxel_anims::frame = 0U;
+
+void world::voxel_anims::init(void)
+{
+ globals::client_config.add_value("voxel_anims.base_framerate", base_framerate);
+
+ world::voxel_anims::nextframe = 0U;
+ world::voxel_anims::frame = 0U;
+}
+
+void world::voxel_anims::update(void)
+{
+ if(globals::curtime >= world::voxel_anims::nextframe) {
+ world::voxel_anims::nextframe = globals::curtime
+ + static_cast<std::uint64_t>(1000000.0 / static_cast<float>(base_framerate.get_value()));
+ world::voxel_anims::frame += 1U;
+ }
+}
diff --git a/game/client/world/voxel_anims.hh b/game/client/world/voxel_anims.hh index 0d8a0d0..e4fe73e 100644 --- a/game/client/world/voxel_anims.hh +++ b/game/client/world/voxel_anims.hh @@ -1,13 +1,13 @@ -#pragma once - -namespace world::voxel_anims -{ -extern std::uint64_t nextframe; -extern std::uint32_t frame; -} // namespace world::voxel_anims - -namespace world::voxel_anims -{ -void init(void); -void update(void); -} // namespace world::voxel_anims +#pragma once
+
+namespace world::voxel_anims
+{
+extern std::uint64_t nextframe;
+extern std::uint32_t frame;
+} // namespace world::voxel_anims
+
+namespace world::voxel_anims
+{
+void init(void);
+void update(void);
+} // namespace world::voxel_anims
diff --git a/game/client/world/voxel_atlas.cc b/game/client/world/voxel_atlas.cc index 512a06a..a01db12 100644 --- a/game/client/world/voxel_atlas.cc +++ b/game/client/world/voxel_atlas.cc @@ -1,185 +1,185 @@ -#include "client/pch.hh" - -#include "client/world/voxel_atlas.hh" - -#include "core/math/constexpr.hh" -#include "core/math/crc64.hh" - -#include "core/resource/image.hh" -#include "core/resource/resource.hh" - -struct AtlasPlane final { - std::unordered_map<std::size_t, std::size_t> lookup; - std::vector<world::AtlasStrip> strips; - std::size_t layer_count_max; - std::size_t layer_count; - std::size_t plane_id; - GLuint gl_texture; -}; - -static int atlas_width; -static int atlas_height; -static std::size_t atlas_count; -static std::vector<AtlasPlane> planes; - -// Certain animated and varied voxels just double their -// textures (see the "default" texture part in VoxelInfoBuilder::build) -// so there could either be six UNIQUE atlas strips or only one -// https://crypto.stackexchange.com/questions/55162/best-way-to-hash-two-values-into-one -static std::size_t vector_hash(const std::vector<std::string>& strings) -{ - std::size_t source = 0; - for(const std::string& str : strings) - source += math::crc64(str); - return math::crc64(&source, sizeof(source)); -} - -static void plane_setup(AtlasPlane& plane) -{ - glGenTextures(1, &plane.gl_texture); - glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, atlas_width, atlas_height, plane.layer_count_max, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT); -} - -static world::AtlasStrip* plane_lookup(AtlasPlane& plane, std::size_t hash_value) -{ - const auto it = plane.lookup.find(hash_value); - - if(it != plane.lookup.cend()) { - return &plane.strips[it->second]; - } - - return nullptr; -} - -static world::AtlasStrip* plane_new_strip(AtlasPlane& plane, const std::vector<std::string>& paths, std::size_t hash_value) -{ - world::AtlasStrip strip = {}; - strip.offset = plane.layer_count; - strip.plane = plane.plane_id; - - glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture); - - for(std::size_t i = 0; i < paths.size(); ++i) { - if(auto image = resource::load<Image>(paths[i].c_str(), IMAGE_LOAD_FLIP)) { - if((image->size.x != atlas_width) || (image->size.y != atlas_height)) { - spdlog::warn("atlas: {}: size mismatch", paths[i]); - continue; - } - - const std::size_t offset = strip.offset + i; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, offset, image->size.x, image->size.y, 1, GL_RGBA, GL_UNSIGNED_BYTE, - image->pixels); - } - } - - plane.layer_count += paths.size(); - - const std::size_t index = plane.strips.size(); - plane.lookup.emplace(hash_value, index); - plane.strips.push_back(std::move(strip)); - return &plane.strips[index]; -} - -void world::voxel_atlas::create(int width, int height, std::size_t count) -{ - GLint max_plane_layers; - - atlas_width = 1 << math::log2(width); - atlas_height = 1 << math::log2(height); - - // Clipping this at OpenGL 4.5 limit of 2048 is important due to - // how voxel quad meshes are packed in memory: each texture index is - // confined in 11 bits so having bigger atlas planes makes no sense; - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max_plane_layers); - max_plane_layers = math::clamp(max_plane_layers, 256, 2048); - - for(long i = count; i > 0L; i -= max_plane_layers) { - AtlasPlane plane = {}; - plane.plane_id = planes.size(); - plane.layer_count_max = math::min<std::size_t>(max_plane_layers, i); - plane.layer_count = 0; - - const std::size_t save_id = plane.plane_id; - planes.push_back(std::move(plane)); - plane_setup(planes[save_id]); - } - - spdlog::debug("voxel_atlas: count={}", count); - spdlog::debug("voxel_atlas: atlas_size=[{}x{}]", atlas_width, atlas_height); - spdlog::debug("voxel_atlas: max_plane_layers={}", max_plane_layers); -} - -void world::voxel_atlas::destroy(void) -{ - for(const AtlasPlane& plane : planes) - glDeleteTextures(1, &plane.gl_texture); - atlas_width = 0; - atlas_height = 0; - planes.clear(); -} - -std::size_t world::voxel_atlas::plane_count(void) -{ - return planes.size(); -} - -GLuint world::voxel_atlas::plane_texture(std::size_t plane_id) -{ - if(plane_id < planes.size()) { - return planes[plane_id].gl_texture; - } - else { - return 0; - } -} - -void world::voxel_atlas::generate_mipmaps(void) -{ - for(const AtlasPlane& plane : planes) { - glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture); - glGenerateMipmap(GL_TEXTURE_2D_ARRAY); - } -} - -world::AtlasStrip* world::voxel_atlas::find_or_load(const std::vector<std::string>& paths) -{ - const std::size_t hash_value = vector_hash(paths); - - for(AtlasPlane& plane : planes) { - if(AtlasStrip* strip = plane_lookup(plane, hash_value)) { - return strip; - } - - continue; - } - - for(AtlasPlane& plane : planes) { - if((plane.layer_count + paths.size()) <= plane.layer_count_max) { - return plane_new_strip(plane, paths, hash_value); - } - - continue; - } - - return nullptr; -} - -world::AtlasStrip* world::voxel_atlas::find(const std::vector<std::string>& paths) -{ - const std::size_t hash_value = vector_hash(paths); - - for(AtlasPlane& plane : planes) { - if(AtlasStrip* strip = plane_lookup(plane, hash_value)) { - return strip; - } - - continue; - } - - return nullptr; -} +#include "client/pch.hh"
+
+#include "client/world/voxel_atlas.hh"
+
+#include "core/math/constexpr.hh"
+#include "core/math/crc64.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+struct AtlasPlane final {
+ std::unordered_map<std::size_t, std::size_t> lookup;
+ std::vector<world::AtlasStrip> strips;
+ std::size_t layer_count_max;
+ std::size_t layer_count;
+ std::size_t plane_id;
+ GLuint gl_texture;
+};
+
+static int atlas_width;
+static int atlas_height;
+static std::size_t atlas_count;
+static std::vector<AtlasPlane> planes;
+
+// Certain animated and varied voxels just double their
+// textures (see the "default" texture part in VoxelInfoBuilder::build)
+// so there could either be six UNIQUE atlas strips or only one
+// https://crypto.stackexchange.com/questions/55162/best-way-to-hash-two-values-into-one
+static std::size_t vector_hash(const std::vector<std::string>& strings)
+{
+ std::size_t source = 0;
+ for(const std::string& str : strings)
+ source += math::crc64(str);
+ return math::crc64(&source, sizeof(source));
+}
+
+static void plane_setup(AtlasPlane& plane)
+{
+ glGenTextures(1, &plane.gl_texture);
+ glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture);
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, atlas_width, atlas_height, plane.layer_count_max, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
+}
+
+static world::AtlasStrip* plane_lookup(AtlasPlane& plane, std::size_t hash_value)
+{
+ const auto it = plane.lookup.find(hash_value);
+
+ if(it != plane.lookup.cend()) {
+ return &plane.strips[it->second];
+ }
+
+ return nullptr;
+}
+
+static world::AtlasStrip* plane_new_strip(AtlasPlane& plane, const std::vector<std::string>& paths, std::size_t hash_value)
+{
+ world::AtlasStrip strip = {};
+ strip.offset = plane.layer_count;
+ strip.plane = plane.plane_id;
+
+ glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture);
+
+ for(std::size_t i = 0; i < paths.size(); ++i) {
+ if(auto image = resource::load<Image>(paths[i].c_str(), IMAGE_LOAD_FLIP)) {
+ if((image->size.x != atlas_width) || (image->size.y != atlas_height)) {
+ spdlog::warn("atlas: {}: size mismatch", paths[i]);
+ continue;
+ }
+
+ const std::size_t offset = strip.offset + i;
+ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, offset, image->size.x, image->size.y, 1, GL_RGBA, GL_UNSIGNED_BYTE,
+ image->pixels);
+ }
+ }
+
+ plane.layer_count += paths.size();
+
+ const std::size_t index = plane.strips.size();
+ plane.lookup.emplace(hash_value, index);
+ plane.strips.push_back(std::move(strip));
+ return &plane.strips[index];
+}
+
+void world::voxel_atlas::create(int width, int height, std::size_t count)
+{
+ GLint max_plane_layers;
+
+ atlas_width = 1 << math::log2(width);
+ atlas_height = 1 << math::log2(height);
+
+ // Clipping this at OpenGL 4.5 limit of 2048 is important due to
+ // how voxel quad meshes are packed in memory: each texture index is
+ // confined in 11 bits so having bigger atlas planes makes no sense;
+ glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max_plane_layers);
+ max_plane_layers = math::clamp(max_plane_layers, 256, 2048);
+
+ for(long i = count; i > 0L; i -= max_plane_layers) {
+ AtlasPlane plane = {};
+ plane.plane_id = planes.size();
+ plane.layer_count_max = math::min<std::size_t>(max_plane_layers, i);
+ plane.layer_count = 0;
+
+ const std::size_t save_id = plane.plane_id;
+ planes.push_back(std::move(plane));
+ plane_setup(planes[save_id]);
+ }
+
+ spdlog::debug("voxel_atlas: count={}", count);
+ spdlog::debug("voxel_atlas: atlas_size=[{}x{}]", atlas_width, atlas_height);
+ spdlog::debug("voxel_atlas: max_plane_layers={}", max_plane_layers);
+}
+
+void world::voxel_atlas::destroy(void)
+{
+ for(const AtlasPlane& plane : planes)
+ glDeleteTextures(1, &plane.gl_texture);
+ atlas_width = 0;
+ atlas_height = 0;
+ planes.clear();
+}
+
+std::size_t world::voxel_atlas::plane_count(void)
+{
+ return planes.size();
+}
+
+GLuint world::voxel_atlas::plane_texture(std::size_t plane_id)
+{
+ if(plane_id < planes.size()) {
+ return planes[plane_id].gl_texture;
+ }
+ else {
+ return 0;
+ }
+}
+
+void world::voxel_atlas::generate_mipmaps(void)
+{
+ for(const AtlasPlane& plane : planes) {
+ glBindTexture(GL_TEXTURE_2D_ARRAY, plane.gl_texture);
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+ }
+}
+
+world::AtlasStrip* world::voxel_atlas::find_or_load(const std::vector<std::string>& paths)
+{
+ const std::size_t hash_value = vector_hash(paths);
+
+ for(AtlasPlane& plane : planes) {
+ if(AtlasStrip* strip = plane_lookup(plane, hash_value)) {
+ return strip;
+ }
+
+ continue;
+ }
+
+ for(AtlasPlane& plane : planes) {
+ if((plane.layer_count + paths.size()) <= plane.layer_count_max) {
+ return plane_new_strip(plane, paths, hash_value);
+ }
+
+ continue;
+ }
+
+ return nullptr;
+}
+
+world::AtlasStrip* world::voxel_atlas::find(const std::vector<std::string>& paths)
+{
+ const std::size_t hash_value = vector_hash(paths);
+
+ for(AtlasPlane& plane : planes) {
+ if(AtlasStrip* strip = plane_lookup(plane, hash_value)) {
+ return strip;
+ }
+
+ continue;
+ }
+
+ return nullptr;
+}
diff --git a/game/client/world/voxel_atlas.hh b/game/client/world/voxel_atlas.hh index 70e8a1e..4ae6381 100644 --- a/game/client/world/voxel_atlas.hh +++ b/game/client/world/voxel_atlas.hh @@ -1,28 +1,28 @@ -#pragma once - -namespace world -{ -struct AtlasStrip final { - std::size_t offset; - std::size_t plane; -}; -} // namespace world - -namespace world::voxel_atlas -{ -void create(int width, int height, std::size_t count); -void destroy(void); -} // namespace world::voxel_atlas - -namespace world::voxel_atlas -{ -std::size_t plane_count(void); -GLuint plane_texture(std::size_t plane_id); -void generate_mipmaps(void); -} // namespace world::voxel_atlas - -namespace world::voxel_atlas -{ -AtlasStrip* find_or_load(const std::vector<std::string>& paths); -AtlasStrip* find(const std::vector<std::string>& paths); -} // namespace world::voxel_atlas +#pragma once
+
+namespace world
+{
+struct AtlasStrip final {
+ std::size_t offset;
+ std::size_t plane;
+};
+} // namespace world
+
+namespace world::voxel_atlas
+{
+void create(int width, int height, std::size_t count);
+void destroy(void);
+} // namespace world::voxel_atlas
+
+namespace world::voxel_atlas
+{
+std::size_t plane_count(void);
+GLuint plane_texture(std::size_t plane_id);
+void generate_mipmaps(void);
+} // namespace world::voxel_atlas
+
+namespace world::voxel_atlas
+{
+AtlasStrip* find_or_load(const std::vector<std::string>& paths);
+AtlasStrip* find(const std::vector<std::string>& paths);
+} // namespace world::voxel_atlas
diff --git a/game/client/world/voxel_sounds.cc b/game/client/world/voxel_sounds.cc index 71ea1fc..481e615 100644 --- a/game/client/world/voxel_sounds.cc +++ b/game/client/world/voxel_sounds.cc @@ -1,86 +1,86 @@ -#include "client/pch.hh" - -#include "client/world/voxel_sounds.hh" - -#include "client/resource/sound_effect.hh" - -constexpr static std::size_t NUM_SURFACES = static_cast<std::size_t>(world::voxel_surface::COUNT); - -static std::vector<resource_ptr<SoundEffect>> footsteps_sounds[NUM_SURFACES]; -static std::mt19937_64 randomizer; - -static void add_footsteps_effect(world::voxel_surface surface, std::string_view name) -{ - if(auto effect = resource::load<SoundEffect>(name)) { - auto surface_index = static_cast<std::size_t>(surface); - footsteps_sounds[surface_index].push_back(effect); - } -} - -static resource_ptr<SoundEffect> get_footsteps_effect(world::voxel_surface surface) -{ - auto surface_index = static_cast<std::size_t>(surface); - - if(surface_index >= NUM_SURFACES) { - // Surface index out of range - return nullptr; - } - - const auto& sounds = footsteps_sounds[surface_index]; - - if(sounds.empty()) { - // No sounds for this surface - return nullptr; - } - - auto dist = std::uniform_int_distribution<std::size_t>(0, sounds.size() - 1); - return sounds.at(dist(randomizer)); -} - -void world::voxel_sounds::init(void) -{ - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default1.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default2.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default3.wav"); - add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default4.wav"); - - add_footsteps_effect(voxel_surface::DIRT, "sounds/surface/dirt1.wav"); - - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass1.wav"); - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass2.wav"); - add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass3.wav"); - - add_footsteps_effect(voxel_surface::GRAVEL, "sounds/surface/gravel1.wav"); - - add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand1.wav"); - add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand2.wav"); - - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood1.wav"); - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood2.wav"); - add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood3.wav"); -} - -void world::voxel_sounds::shutdown(void) -{ - for(std::size_t i = 0; i < NUM_SURFACES; ++i) { - footsteps_sounds[i].clear(); - } -} - -resource_ptr<SoundEffect> world::voxel_sounds::get_footsteps(voxel_surface surface) -{ - if(auto effect = get_footsteps_effect(surface)) { - return effect; - } - - if(auto effect = get_footsteps_effect(voxel_surface::DEFAULT)) { - return effect; - } - - return nullptr; -} - -resource_ptr<SoundEffect> world::voxel_sounds::get_placebreak(voxel_surface surface) -{ - return nullptr; -} +#include "client/pch.hh"
+
+#include "client/world/voxel_sounds.hh"
+
+#include "client/resource/sound_effect.hh"
+
+constexpr static std::size_t NUM_SURFACES = static_cast<std::size_t>(world::voxel_surface::COUNT);
+
+static std::vector<resource_ptr<SoundEffect>> footsteps_sounds[NUM_SURFACES];
+static std::mt19937_64 randomizer;
+
+static void add_footsteps_effect(world::voxel_surface surface, std::string_view name)
+{
+ if(auto effect = resource::load<SoundEffect>(name)) {
+ auto surface_index = static_cast<std::size_t>(surface);
+ footsteps_sounds[surface_index].push_back(effect);
+ }
+}
+
+static resource_ptr<SoundEffect> get_footsteps_effect(world::voxel_surface surface)
+{
+ auto surface_index = static_cast<std::size_t>(surface);
+
+ if(surface_index >= NUM_SURFACES) {
+ // Surface index out of range
+ return nullptr;
+ }
+
+ const auto& sounds = footsteps_sounds[surface_index];
+
+ if(sounds.empty()) {
+ // No sounds for this surface
+ return nullptr;
+ }
+
+ auto dist = std::uniform_int_distribution<std::size_t>(0, sounds.size() - 1);
+ return sounds.at(dist(randomizer));
+}
+
+void world::voxel_sounds::init(void)
+{
+ add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default1.wav");
+ add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default2.wav");
+ add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default3.wav");
+ add_footsteps_effect(voxel_surface::DEFAULT, "sounds/surface/default4.wav");
+
+ add_footsteps_effect(voxel_surface::DIRT, "sounds/surface/dirt1.wav");
+
+ add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass1.wav");
+ add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass2.wav");
+ add_footsteps_effect(voxel_surface::GRASS, "sounds/surface/grass3.wav");
+
+ add_footsteps_effect(voxel_surface::GRAVEL, "sounds/surface/gravel1.wav");
+
+ add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand1.wav");
+ add_footsteps_effect(voxel_surface::SAND, "sounds/surface/sand2.wav");
+
+ add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood1.wav");
+ add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood2.wav");
+ add_footsteps_effect(voxel_surface::WOOD, "sounds/surface/wood3.wav");
+}
+
+void world::voxel_sounds::shutdown(void)
+{
+ for(std::size_t i = 0; i < NUM_SURFACES; ++i) {
+ footsteps_sounds[i].clear();
+ }
+}
+
+resource_ptr<SoundEffect> world::voxel_sounds::get_footsteps(voxel_surface surface)
+{
+ if(auto effect = get_footsteps_effect(surface)) {
+ return effect;
+ }
+
+ if(auto effect = get_footsteps_effect(voxel_surface::DEFAULT)) {
+ return effect;
+ }
+
+ return nullptr;
+}
+
+resource_ptr<SoundEffect> world::voxel_sounds::get_placebreak(voxel_surface surface)
+{
+ return nullptr;
+}
diff --git a/game/client/world/voxel_sounds.hh b/game/client/world/voxel_sounds.hh index b09b475..09f5e2e 100644 --- a/game/client/world/voxel_sounds.hh +++ b/game/client/world/voxel_sounds.hh @@ -1,19 +1,19 @@ -#pragma once - -#include "core/resource/resource.hh" - -#include "shared/world/voxel_registry.hh" - -struct SoundEffect; - -namespace world::voxel_sounds -{ -void init(void); -void shutdown(void); -} // namespace world::voxel_sounds - -namespace world::voxel_sounds -{ -resource_ptr<SoundEffect> get_footsteps(voxel_surface surface); -resource_ptr<SoundEffect> get_placebreak(voxel_surface surface); -} // namespace world::voxel_sounds +#pragma once
+
+#include "core/resource/resource.hh"
+
+#include "shared/world/voxel_registry.hh"
+
+struct SoundEffect;
+
+namespace world::voxel_sounds
+{
+void init(void);
+void shutdown(void);
+} // namespace world::voxel_sounds
+
+namespace world::voxel_sounds
+{
+resource_ptr<SoundEffect> get_footsteps(voxel_surface surface);
+resource_ptr<SoundEffect> get_placebreak(voxel_surface surface);
+} // namespace world::voxel_sounds
diff --git a/game/server/chat.cc b/game/server/chat.cc index a0ceba8..fadfaa2 100644 --- a/game/server/chat.cc +++ b/game/server/chat.cc @@ -1,55 +1,55 @@ -#include "server/pch.hh" - -#include "server/chat.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" -#include "shared/protocol.hh" - -static void on_chat_message_packet(const protocol::ChatMessage& packet) -{ - if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) { - if(auto session = sessions::find(packet.peer)) { - server_chat::broadcast(packet.message.c_str(), session->client_username.c_str()); - } - else { - server_chat::broadcast(packet.message.c_str(), packet.sender.c_str()); - } - } -} - -void server_chat::init(void) -{ - globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>(); -} - -void server_chat::broadcast(std::string_view message) -{ - server_chat::broadcast(message, "server"); -} - -void server_chat::broadcast(std::string_view message, std::string_view sender) -{ - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.message = message; - packet.sender = sender; - - protocol::broadcast(globals::server_host, protocol::encode(packet)); - - spdlog::info("<{}> {}", sender, message); -} - -void server_chat::send(Session* session, std::string_view message) -{ - server_chat::send(session, message, "server"); -} - -void server_chat::send(Session* session, std::string_view message, std::string_view sender) -{ - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.message = message; - packet.sender = sender; - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} +#include "server/pch.hh"
+
+#include "server/chat.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+#include "shared/protocol.hh"
+
+static void on_chat_message_packet(const protocol::ChatMessage& packet)
+{
+ if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) {
+ if(auto session = sessions::find(packet.peer)) {
+ server_chat::broadcast(packet.message.c_str(), session->client_username.c_str());
+ }
+ else {
+ server_chat::broadcast(packet.message.c_str(), packet.sender.c_str());
+ }
+ }
+}
+
+void server_chat::init(void)
+{
+ globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>();
+}
+
+void server_chat::broadcast(std::string_view message)
+{
+ server_chat::broadcast(message, "server");
+}
+
+void server_chat::broadcast(std::string_view message, std::string_view sender)
+{
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.message = message;
+ packet.sender = sender;
+
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+
+ spdlog::info("<{}> {}", sender, message);
+}
+
+void server_chat::send(Session* session, std::string_view message)
+{
+ server_chat::send(session, message, "server");
+}
+
+void server_chat::send(Session* session, std::string_view message, std::string_view sender)
+{
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.message = message;
+ packet.sender = sender;
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
diff --git a/game/server/chat.hh b/game/server/chat.hh index 1b3e11b..76ddbbb 100644 --- a/game/server/chat.hh +++ b/game/server/chat.hh @@ -1,12 +1,12 @@ -#pragma once - -struct Session; - -namespace server_chat -{ -void init(void); -void broadcast(std::string_view message); -void broadcast(std::string_view message, std::string_view sender); -void send(Session* session, std::string_view message); -void send(Session* session, std::string_view message, std::string_view sender); -} // namespace server_chat +#pragma once
+
+struct Session;
+
+namespace server_chat
+{
+void init(void);
+void broadcast(std::string_view message);
+void broadcast(std::string_view message, std::string_view sender);
+void send(Session* session, std::string_view message);
+void send(Session* session, std::string_view message, std::string_view sender);
+} // namespace server_chat
diff --git a/game/server/game.cc b/game/server/game.cc index 441a2cb..8624670 100644 --- a/game/server/game.cc +++ b/game/server/game.cc @@ -1,156 +1,156 @@ -#include "server/pch.hh" - -#include "server/game.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" -#include "core/math/crc64.hh" - -#include "core/utils/epoch.hh" - -#include "shared/entity/collision.hh" -#include "shared/entity/gravity.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/stasis.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/game_items.hh" -#include "shared/game_voxels.hh" -#include "shared/protocol.hh" -#include "shared/splash.hh" - -#include "server/world/universe.hh" -#include "server/world/unloader.hh" -#include "server/world/worldgen.hh" - -#include "server/chat.hh" -#include "server/globals.hh" -#include "server/receive.hh" -#include "server/sessions.hh" -#include "server/status.hh" -#include "server/whitelist.hh" - -config::Unsigned server_game::view_distance(4U, 4U, 32U); - -std::uint64_t server_game::password_hash = UINT64_MAX; - -static config::Number<enet_uint16> listen_port(protocol::PORT, 1024U, UINT16_MAX); -static config::Unsigned status_peers(2U, 1U, 16U); -static config::String password_string(""); - -void server_game::init(void) -{ - globals::server_config.add_value("game.listen_port", listen_port); - globals::server_config.add_value("game.status_peers", status_peers); - globals::server_config.add_value("game.password", password_string); - globals::server_config.add_value("game.view_distance", server_game::view_distance); - - sessions::init(); - - whitelist::init(); - - splash::init_server(); - - status::init(); - - server_chat::init(); - server_recieve::init(); - - world::worldgen::init(); - - world::unloader::init(); - world::universe::init(); -} - -void server_game::init_late(void) -{ - server_game::password_hash = math::crc64(password_string.get_value()); - - sessions::init_late(); - - whitelist::init_late(); - - ENetAddress address; - address.host = ENET_HOST_ANY; - address.port = listen_port.get_value(); - - globals::server_host = enet_host_create(&address, sessions::max_players.get_value() + status_peers.get_value(), 1, 0, 0); - - if(!globals::server_host) { - spdlog::critical("game: unable to setup an ENet host"); - std::terminate(); - } - - spdlog::info("game: host: {} player + {} status peers", sessions::max_players.get_value(), status_peers.get_value()); - spdlog::info("game: host: listening on UDP port {}", address.port); - - game_voxels::populate(); - game_items::populate(); - - world::unloader::init_late(); - world::universe::init_late(); - - sessions::init_post_universe(); -} - -void server_game::shutdown(void) -{ - protocol::Disconnect packet; - packet.reason = "protocol.server_shutdown"; - protocol::broadcast(globals::server_host, protocol::encode(packet)); - - whitelist::shutdown(); - - sessions::shutdown(); - - enet_host_flush(globals::server_host); - enet_host_service(globals::server_host, nullptr, 500); - enet_host_destroy(globals::server_host); - - world::universe::shutdown(); -} - -void server_game::fixed_update(void) -{ - // FIXME: threading - for(auto dimension : globals::dimensions) { - entity::Collision::fixed_update(dimension.second); - entity::Velocity::fixed_update(dimension.second); - entity::Transform::fixed_update(dimension.second); - entity::Gravity::fixed_update(dimension.second); - entity::Stasis::fixed_update(dimension.second); - } -} - -void server_game::fixed_update_late(void) -{ - ENetEvent enet_event; - - while(0 < enet_host_service(globals::server_host, &enet_event, 0)) { - if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) { - sessions::destroy(sessions::find(enet_event.peer)); - sessions::refresh_scoreboard(); - continue; - } - - if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) { - protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer); - enet_packet_destroy(enet_event.packet); - continue; - } - } - - // FIXME: threading - for(auto dimension : globals::dimensions) { - world::unloader::fixed_update_late(dimension.second); - } -} +#include "server/pch.hh"
+
+#include "server/game.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+#include "core/math/crc64.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "shared/entity/collision.hh"
+#include "shared/entity/gravity.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/stasis.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/game_items.hh"
+#include "shared/game_voxels.hh"
+#include "shared/protocol.hh"
+#include "shared/splash.hh"
+
+#include "server/world/universe.hh"
+#include "server/world/unloader.hh"
+#include "server/world/worldgen.hh"
+
+#include "server/chat.hh"
+#include "server/globals.hh"
+#include "server/receive.hh"
+#include "server/sessions.hh"
+#include "server/status.hh"
+#include "server/whitelist.hh"
+
+config::Unsigned server_game::view_distance(4U, 4U, 32U);
+
+std::uint64_t server_game::password_hash = UINT64_MAX;
+
+static config::Number<enet_uint16> listen_port(protocol::PORT, 1024U, UINT16_MAX);
+static config::Unsigned status_peers(2U, 1U, 16U);
+static config::String password_string("");
+
+void server_game::init(void)
+{
+ globals::server_config.add_value("game.listen_port", listen_port);
+ globals::server_config.add_value("game.status_peers", status_peers);
+ globals::server_config.add_value("game.password", password_string);
+ globals::server_config.add_value("game.view_distance", server_game::view_distance);
+
+ sessions::init();
+
+ whitelist::init();
+
+ splash::init_server();
+
+ status::init();
+
+ server_chat::init();
+ server_recieve::init();
+
+ world::worldgen::init();
+
+ world::unloader::init();
+ world::universe::init();
+}
+
+void server_game::init_late(void)
+{
+ server_game::password_hash = math::crc64(password_string.get_value());
+
+ sessions::init_late();
+
+ whitelist::init_late();
+
+ ENetAddress address;
+ address.host = ENET_HOST_ANY;
+ address.port = listen_port.get_value();
+
+ globals::server_host = enet_host_create(&address, sessions::max_players.get_value() + status_peers.get_value(), 1, 0, 0);
+
+ if(!globals::server_host) {
+ spdlog::critical("game: unable to setup an ENet host");
+ std::terminate();
+ }
+
+ spdlog::info("game: host: {} player + {} status peers", sessions::max_players.get_value(), status_peers.get_value());
+ spdlog::info("game: host: listening on UDP port {}", address.port);
+
+ game_voxels::populate();
+ game_items::populate();
+
+ world::unloader::init_late();
+ world::universe::init_late();
+
+ sessions::init_post_universe();
+}
+
+void server_game::shutdown(void)
+{
+ protocol::Disconnect packet;
+ packet.reason = "protocol.server_shutdown";
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+
+ whitelist::shutdown();
+
+ sessions::shutdown();
+
+ enet_host_flush(globals::server_host);
+ enet_host_service(globals::server_host, nullptr, 500);
+ enet_host_destroy(globals::server_host);
+
+ world::universe::shutdown();
+}
+
+void server_game::fixed_update(void)
+{
+ // FIXME: threading
+ for(auto dimension : globals::dimensions) {
+ entity::Collision::fixed_update(dimension.second);
+ entity::Velocity::fixed_update(dimension.second);
+ entity::Transform::fixed_update(dimension.second);
+ entity::Gravity::fixed_update(dimension.second);
+ entity::Stasis::fixed_update(dimension.second);
+ }
+}
+
+void server_game::fixed_update_late(void)
+{
+ ENetEvent enet_event;
+
+ while(0 < enet_host_service(globals::server_host, &enet_event, 0)) {
+ if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) {
+ sessions::destroy(sessions::find(enet_event.peer));
+ sessions::refresh_scoreboard();
+ continue;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) {
+ protocol::decode(globals::dispatcher, enet_event.packet, enet_event.peer);
+ enet_packet_destroy(enet_event.packet);
+ continue;
+ }
+ }
+
+ // FIXME: threading
+ for(auto dimension : globals::dimensions) {
+ world::unloader::fixed_update_late(dimension.second);
+ }
+}
diff --git a/game/server/game.hh b/game/server/game.hh index 1dbe4b8..99ba3d6 100644 --- a/game/server/game.hh +++ b/game/server/game.hh @@ -1,25 +1,25 @@ -#pragma once - -namespace config -{ -class Unsigned; -} // namespace config - -namespace server_game -{ -extern config::Unsigned view_distance; -} // namespace server_game - -namespace server_game -{ -extern std::uint64_t password_hash; -} // namespace server_game - -namespace server_game -{ -void init(void); -void init_late(void); -void shutdown(void); -void fixed_update(void); -void fixed_update_late(void); -} // namespace server_game +#pragma once
+
+namespace config
+{
+class Unsigned;
+} // namespace config
+
+namespace server_game
+{
+extern config::Unsigned view_distance;
+} // namespace server_game
+
+namespace server_game
+{
+extern std::uint64_t password_hash;
+} // namespace server_game
+
+namespace server_game
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void fixed_update(void);
+void fixed_update_late(void);
+} // namespace server_game
diff --git a/game/server/globals.cc b/game/server/globals.cc index 7d79e4d..9e1875d 100644 --- a/game/server/globals.cc +++ b/game/server/globals.cc @@ -1,18 +1,18 @@ -#include "server/pch.hh" - -#include "server/globals.hh" - -#include "core/io/config_map.hh" - -#include "shared/protocol.hh" - -io::ConfigMap globals::server_config; - -ENetHost* globals::server_host; - -bool globals::is_running; -unsigned int globals::tickrate; -std::uint64_t globals::tickrate_dt; - -world::Dimension* globals::spawn_dimension; -std::unordered_map<std::string, world::Dimension*> globals::dimensions; +#include "server/pch.hh"
+
+#include "server/globals.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/protocol.hh"
+
+io::ConfigMap globals::server_config;
+
+ENetHost* globals::server_host;
+
+bool globals::is_running;
+unsigned int globals::tickrate;
+std::uint64_t globals::tickrate_dt;
+
+world::Dimension* globals::spawn_dimension;
+std::unordered_map<std::string, world::Dimension*> globals::dimensions;
diff --git a/game/server/globals.hh b/game/server/globals.hh index b684d3b..e9aa174 100644 --- a/game/server/globals.hh +++ b/game/server/globals.hh @@ -1,27 +1,27 @@ -#pragma once - -#include "shared/globals.hh" - -namespace io -{ -class ConfigMap; -} // namespace io - -namespace world -{ -class Dimension; -} // namespace world - -namespace globals -{ -extern io::ConfigMap server_config; - -extern ENetHost* server_host; - -extern bool is_running; -extern unsigned int tickrate; -extern std::uint64_t tickrate_dt; - -extern world::Dimension* spawn_dimension; -extern std::unordered_map<std::string, world::Dimension*> dimensions; -} // namespace globals +#pragma once
+
+#include "shared/globals.hh"
+
+namespace io
+{
+class ConfigMap;
+} // namespace io
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace globals
+{
+extern io::ConfigMap server_config;
+
+extern ENetHost* server_host;
+
+extern bool is_running;
+extern unsigned int tickrate;
+extern std::uint64_t tickrate_dt;
+
+extern world::Dimension* spawn_dimension;
+extern std::unordered_map<std::string, world::Dimension*> dimensions;
+} // namespace globals
diff --git a/game/server/main.cc b/game/server/main.cc index 9d7026f..3316407 100644 --- a/game/server/main.cc +++ b/game/server/main.cc @@ -1,108 +1,108 @@ -#include "server/pch.hh" - -#include "core/config/number.hh" - -#include "core/io/cmdline.hh" -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/image.hh" -#include "core/resource/resource.hh" - -#include "core/utils/epoch.hh" - -#include "core/threading.hh" -#include "core/version.hh" - -#include "shared/game.hh" -#include "shared/protocol.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -static config::Unsigned server_tickrate(protocol::TICKRATE, 10U, 300U); - -static void on_termination_signal(int) -{ - spdlog::warn("server: received termination signal"); - globals::is_running = false; -} - -int main(int argc, char** argv) -{ - io::cmdline::create(argc, argv); - - shared_game::init(argc, argv); - - spdlog::info("Voxelius Server {}", version::semver); - - globals::fixed_frametime = 0.0f; - globals::fixed_frametime_avg = 0.0f; - globals::fixed_frametime_us = 0; - globals::fixed_framecount = 0; - - globals::curtime = utils::unix_microseconds(); - - globals::is_running = true; - - std::signal(SIGINT, &on_termination_signal); - std::signal(SIGTERM, &on_termination_signal); - - Image::register_resource(); - - server_game::init(); - - threading::init(); - - globals::server_config.add_value("server.tickrate", server_tickrate); - globals::server_config.load_file("server.conf"); - globals::server_config.load_cmdline(); - - globals::tickrate = server_tickrate.get_value(); - globals::tickrate_dt = static_cast<std::uint64_t>(1000000.0f / static_cast<float>(globals::tickrate)); - - server_game::init_late(); - - std::uint64_t last_curtime = globals::curtime; - - while(globals::is_running) { - globals::curtime = utils::unix_microseconds(); - - globals::fixed_frametime_us = globals::curtime - last_curtime; - globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f; - globals::fixed_frametime_avg += globals::fixed_frametime; - globals::fixed_frametime_avg *= 0.5f; - - last_curtime = globals::curtime; - - server_game::fixed_update(); - server_game::fixed_update_late(); - - globals::dispatcher.update(); - - globals::fixed_framecount += 1; - - std::this_thread::sleep_for(std::chrono::microseconds(globals::tickrate_dt)); - - resource::soft_cleanup(); - - threading::update(); - } - - server_game::shutdown(); - - resource::hard_cleanup(); - - threading::shutdown(); - - spdlog::info("server: shutdown after {} frames", globals::fixed_framecount); - spdlog::info("server: average framerate: {:.03f} TPS", 1.0f / globals::fixed_frametime_avg); - spdlog::info("server: average frametime: {:.03f} MSPT", 1000.0f * globals::fixed_frametime_avg); - - globals::server_config.save_file("server.conf"); - - shared_game::shutdown(); - - return EXIT_SUCCESS; -} +#include "server/pch.hh"
+
+#include "core/config/number.hh"
+
+#include "core/io/cmdline.hh"
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/image.hh"
+#include "core/resource/resource.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "core/threading.hh"
+#include "core/version.hh"
+
+#include "shared/game.hh"
+#include "shared/protocol.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+static config::Unsigned server_tickrate(protocol::TICKRATE, 10U, 300U);
+
+static void on_termination_signal(int)
+{
+ spdlog::warn("server: received termination signal");
+ globals::is_running = false;
+}
+
+int main(int argc, char** argv)
+{
+ io::cmdline::create(argc, argv);
+
+ shared_game::init(argc, argv);
+
+ spdlog::info("Voxelius Server {}", version::semver);
+
+ globals::fixed_frametime = 0.0f;
+ globals::fixed_frametime_avg = 0.0f;
+ globals::fixed_frametime_us = 0;
+ globals::fixed_framecount = 0;
+
+ globals::curtime = utils::unix_microseconds();
+
+ globals::is_running = true;
+
+ std::signal(SIGINT, &on_termination_signal);
+ std::signal(SIGTERM, &on_termination_signal);
+
+ Image::register_resource();
+
+ server_game::init();
+
+ threading::init();
+
+ globals::server_config.add_value("server.tickrate", server_tickrate);
+ globals::server_config.load_file("server.conf");
+ globals::server_config.load_cmdline();
+
+ globals::tickrate = server_tickrate.get_value();
+ globals::tickrate_dt = static_cast<std::uint64_t>(1000000.0f / static_cast<float>(globals::tickrate));
+
+ server_game::init_late();
+
+ std::uint64_t last_curtime = globals::curtime;
+
+ while(globals::is_running) {
+ globals::curtime = utils::unix_microseconds();
+
+ globals::fixed_frametime_us = globals::curtime - last_curtime;
+ globals::fixed_frametime = static_cast<float>(globals::fixed_frametime_us) / 1000000.0f;
+ globals::fixed_frametime_avg += globals::fixed_frametime;
+ globals::fixed_frametime_avg *= 0.5f;
+
+ last_curtime = globals::curtime;
+
+ server_game::fixed_update();
+ server_game::fixed_update_late();
+
+ globals::dispatcher.update();
+
+ globals::fixed_framecount += 1;
+
+ std::this_thread::sleep_for(std::chrono::microseconds(globals::tickrate_dt));
+
+ resource::soft_cleanup();
+
+ threading::update();
+ }
+
+ server_game::shutdown();
+
+ resource::hard_cleanup();
+
+ threading::shutdown();
+
+ spdlog::info("server: shutdown after {} frames", globals::fixed_framecount);
+ spdlog::info("server: average framerate: {:.03f} TPS", 1.0f / globals::fixed_frametime_avg);
+ spdlog::info("server: average frametime: {:.03f} MSPT", 1000.0f * globals::fixed_frametime_avg);
+
+ globals::server_config.save_file("server.conf");
+
+ shared_game::shutdown();
+
+ return EXIT_SUCCESS;
+}
diff --git a/game/server/pch.hh b/game/server/pch.hh index 6f42c17..e90ebbd 100644 --- a/game/server/pch.hh +++ b/game/server/pch.hh @@ -1,3 +1,3 @@ -#pragma once - -#include <shared/pch.hh> +#pragma once
+
+#include <shared/pch.hh>
diff --git a/game/server/receive.cc b/game/server/receive.cc index 5c56872..75ac3a6 100644 --- a/game/server/receive.cc +++ b/game/server/receive.cc @@ -1,174 +1,174 @@ -#include "server/pch.hh" - -#include "server/receive.hh" - -#include "core/config/number.hh" - -#include "shared/entity/head.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/chunk_aabb.hh" -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "server/world/inhabited.hh" -#include "server/world/universe.hh" -#include "server/world/worldgen.hh" - -#include "server/game.hh" -#include "server/globals.hh" -#include "server/sessions.hh" - -static void on_entity_transform_packet(const protocol::EntityTransform& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Transform>(session->player_entity); - component.angles = packet.angles; - component.chunk = packet.chunk; - component.local = packet.local; - - protocol::EntityTransform response; - response.entity = session->player_entity; - response.angles = component.angles; - response.chunk = component.chunk; - response.local = component.local; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -static void on_entity_velocity_packet(const protocol::EntityVelocity& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Velocity>(session->player_entity); - component.value = packet.value; - - protocol::EntityVelocity response; - response.entity = session->player_entity; - response.value = component.value; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -static void on_entity_head_packet(const protocol::EntityHead& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && session->dimension->entities.valid(session->player_entity)) { - auto& component = session->dimension->entities.emplace_or_replace<entity::Head>(session->player_entity); - component.angles = packet.angles; - - protocol::EntityHead response; - response.entity = session->player_entity; - response.angles = component.angles; - - // Propagate changes to the rest of the world - // except the peer that has sent the packet in the first place - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - } - } -} - -static void on_set_voxel_packet(const protocol::SetVoxel& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(session->dimension && !session->dimension->set_voxel(packet.voxel, packet.vpos)) { - auto cpos = coord::to_chunk(packet.vpos); - auto lpos = coord::to_local(packet.vpos); - auto index = coord::to_index(lpos); - - if(world::worldgen::is_generating(session->dimension, cpos)) { - // The chunk is currently being generated; - // ignore all requests from players to build there - return; - } - - auto chunk = session->dimension->find_chunk(cpos); - - if(chunk == nullptr) { - // The chunk is not loaded, so we must - // ignore any requests from players to build there - return; - } - - chunk->set_voxel(packet.voxel, index); - - session->dimension->chunks.emplace_or_replace<world::Inhabited>(chunk->get_entity()); - - protocol::SetVoxel response; - response.vpos = packet.vpos; - response.voxel = packet.voxel; - sessions::broadcast(session->dimension, protocol::encode(response), session->peer); - - return; - } - } -} - -static void on_request_chunk_packet(const protocol::RequestChunk& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) { - // De-spawned sessions cannot request - // chunks from the server; that's cheating!!! - return; - } - - if(auto transform = session->dimension->entities.try_get<entity::Transform>(session->player_entity)) { - world::ChunkAABB view_box; - view_box.min = transform->chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - view_box.max = transform->chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - - if(view_box.contains(packet.cpos)) { - if(auto chunk = world::universe::load_chunk(session->dimension, packet.cpos)) { - protocol::ChunkVoxels response; - response.chunk = packet.cpos; - response.voxels = chunk->get_voxels(); - protocol::send(packet.peer, protocol::encode(response)); - } - else { - world::worldgen::request_chunk(session, packet.cpos); - } - } - } - } -} - -static void on_entity_sound_packet(const protocol::EntitySound& packet) -{ - if(auto session = sessions::find(packet.peer)) { - if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) { - // De-spawned sessions cannot play sounds - return; - } - - protocol::EntitySound response; - response.entity = session->player_entity; - response.sound = packet.sound; - response.looping = packet.looping; - response.pitch = packet.pitch; - - sessions::broadcast(session->dimension, protocol::encode(response), packet.peer); - } -} - -void server_recieve::init(void) -{ - globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>(); - globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>(); - globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>(); - globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>(); - globals::dispatcher.sink<protocol::RequestChunk>().connect<&on_request_chunk_packet>(); - globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>(); -} +#include "server/pch.hh"
+
+#include "server/receive.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/entity/head.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/chunk_aabb.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/universe.hh"
+#include "server/world/worldgen.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static void on_entity_transform_packet(const protocol::EntityTransform& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Transform>(session->player_entity);
+ component.angles = packet.angles;
+ component.chunk = packet.chunk;
+ component.local = packet.local;
+
+ protocol::EntityTransform response;
+ response.entity = session->player_entity;
+ response.angles = component.angles;
+ response.chunk = component.chunk;
+ response.local = component.local;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+static void on_entity_velocity_packet(const protocol::EntityVelocity& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Velocity>(session->player_entity);
+ component.value = packet.value;
+
+ protocol::EntityVelocity response;
+ response.entity = session->player_entity;
+ response.value = component.value;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+static void on_entity_head_packet(const protocol::EntityHead& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && session->dimension->entities.valid(session->player_entity)) {
+ auto& component = session->dimension->entities.emplace_or_replace<entity::Head>(session->player_entity);
+ component.angles = packet.angles;
+
+ protocol::EntityHead response;
+ response.entity = session->player_entity;
+ response.angles = component.angles;
+
+ // Propagate changes to the rest of the world
+ // except the peer that has sent the packet in the first place
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+ }
+ }
+}
+
+static void on_set_voxel_packet(const protocol::SetVoxel& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(session->dimension && !session->dimension->set_voxel(packet.voxel, packet.vpos)) {
+ auto cpos = coord::to_chunk(packet.vpos);
+ auto lpos = coord::to_local(packet.vpos);
+ auto index = coord::to_index(lpos);
+
+ if(world::worldgen::is_generating(session->dimension, cpos)) {
+ // The chunk is currently being generated;
+ // ignore all requests from players to build there
+ return;
+ }
+
+ auto chunk = session->dimension->find_chunk(cpos);
+
+ if(chunk == nullptr) {
+ // The chunk is not loaded, so we must
+ // ignore any requests from players to build there
+ return;
+ }
+
+ chunk->set_voxel(packet.voxel, index);
+
+ session->dimension->chunks.emplace_or_replace<world::Inhabited>(chunk->get_entity());
+
+ protocol::SetVoxel response;
+ response.vpos = packet.vpos;
+ response.voxel = packet.voxel;
+ sessions::broadcast(session->dimension, protocol::encode(response), session->peer);
+
+ return;
+ }
+ }
+}
+
+static void on_request_chunk_packet(const protocol::RequestChunk& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) {
+ // De-spawned sessions cannot request
+ // chunks from the server; that's cheating!!!
+ return;
+ }
+
+ if(auto transform = session->dimension->entities.try_get<entity::Transform>(session->player_entity)) {
+ world::ChunkAABB view_box;
+ view_box.min = transform->chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ view_box.max = transform->chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+
+ if(view_box.contains(packet.cpos)) {
+ if(auto chunk = world::universe::load_chunk(session->dimension, packet.cpos)) {
+ protocol::ChunkVoxels response;
+ response.chunk = packet.cpos;
+ response.voxels = chunk->get_voxels();
+ protocol::send(packet.peer, protocol::encode(response));
+ }
+ else {
+ world::worldgen::request_chunk(session, packet.cpos);
+ }
+ }
+ }
+ }
+}
+
+static void on_entity_sound_packet(const protocol::EntitySound& packet)
+{
+ if(auto session = sessions::find(packet.peer)) {
+ if(!session->dimension || !session->dimension->entities.valid(session->player_entity)) {
+ // De-spawned sessions cannot play sounds
+ return;
+ }
+
+ protocol::EntitySound response;
+ response.entity = session->player_entity;
+ response.sound = packet.sound;
+ response.looping = packet.looping;
+ response.pitch = packet.pitch;
+
+ sessions::broadcast(session->dimension, protocol::encode(response), packet.peer);
+ }
+}
+
+void server_recieve::init(void)
+{
+ globals::dispatcher.sink<protocol::EntityTransform>().connect<&on_entity_transform_packet>();
+ globals::dispatcher.sink<protocol::EntityVelocity>().connect<&on_entity_velocity_packet>();
+ globals::dispatcher.sink<protocol::EntityHead>().connect<&on_entity_head_packet>();
+ globals::dispatcher.sink<protocol::SetVoxel>().connect<&on_set_voxel_packet>();
+ globals::dispatcher.sink<protocol::RequestChunk>().connect<&on_request_chunk_packet>();
+ globals::dispatcher.sink<protocol::EntitySound>().connect<&on_entity_sound_packet>();
+}
diff --git a/game/server/receive.hh b/game/server/receive.hh index 4150226..a8d531e 100644 --- a/game/server/receive.hh +++ b/game/server/receive.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace server_recieve -{ -void init(void); -} // namespace server_recieve +#pragma once
+
+namespace server_recieve
+{
+void init(void);
+} // namespace server_recieve
diff --git a/game/server/sessions.cc b/game/server/sessions.cc index 0bddcfe..c06ec3e 100644 --- a/game/server/sessions.cc +++ b/game/server/sessions.cc @@ -1,431 +1,431 @@ -#include "server/pch.hh" - -#include "server/sessions.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" -#include "core/math/crc64.hh" - -#include "core/utils/string.hh" - -#include "shared/entity/factory.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" -#include "shared/world/item_registry.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" -#include "shared/protocol.hh" - -#include "server/game.hh" -#include "server/globals.hh" -#include "server/whitelist.hh" - -class DimensionListener final { -public: - explicit DimensionListener(world::Dimension* dimension); - void on_destroy_entity(const entt::registry& registry, entt::entity entity); - -private: - world::Dimension* dimension; -}; - -config::Unsigned sessions::max_players(8U, 1U, 128U); -unsigned int sessions::num_players = 0U; - -static emhash8::HashMap<std::string, Session*> username_map; -static emhash8::HashMap<std::uint64_t, Session*> identity_map; -static std::vector<DimensionListener> dimension_listeners; -static std::vector<Session> sessions_vector; - -static void on_login_request_packet(const protocol::LoginRequest& packet) -{ - if(packet.version > protocol::VERSION) { - protocol::Disconnect response; - response.reason = "protocol.outdated_server"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(packet.version < protocol::VERSION) { - protocol::Disconnect response; - response.reason = "protocol.outdated_client"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - // FIXME: calculate voxel registry checksum ahead of time - // instead of figuring it out every time a new player connects - if(packet.voxel_registry_checksum != world::voxel_registry::calculate_checksum()) { - protocol::Disconnect response; - response.reason = "protocol.voxel_registry_checksum"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(packet.item_registry_checksum != world::item_registry::calculate_checksum()) { - protocol::Disconnect response; - response.reason = "protocol.item_registry_checksum"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - // Don't assign new usernames and just kick the player if - // an another client using the same username is already connected - // and playing; since we have a whitelist, adding "(1)" isn't feasible anymore - if(username_map.contains(packet.username)) { - protocol::Disconnect response; - response.reason = "protocol.username_taken"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(whitelist::enabled.get_value()) { - if(!whitelist::contains(packet.username.c_str())) { - protocol::Disconnect response; - response.reason = "protocol.not_whitelisted"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(!whitelist::matches(packet.username.c_str(), packet.password_hash)) { - protocol::Disconnect response; - response.reason = "protocol.password_incorrect"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - } - else if(packet.password_hash != server_game::password_hash) { - protocol::Disconnect response; - response.reason = "protocol.password_incorrect"; - protocol::send(packet.peer, protocol::encode(response)); - return; - } - - if(Session* session = sessions::create(packet.peer, packet.username.c_str())) { - protocol::LoginResponse response; - response.client_index = session->client_index; - response.client_identity = session->client_identity; - response.server_tickrate = globals::tickrate; - protocol::send(packet.peer, protocol::encode(response)); - - protocol::DimensionInfo dim_info; - dim_info.name = globals::spawn_dimension->get_name(); - dim_info.gravity = globals::spawn_dimension->get_gravity(); - protocol::send(packet.peer, protocol::encode(dim_info)); - - spdlog::info("sessions: {} [{}] logged in with client_index={} in {}", session->client_username, session->client_identity, - session->client_index, globals::spawn_dimension->get_name()); - - // FIXME: only send entities that are present within the current - // player's view bounding box; this also would mean we're not sending - // anything here and just straight up spawing the player and await them - // to receive all the chunks and entites they feel like requesting - for(auto entity : globals::spawn_dimension->entities.view<entt::entity>()) { - if(const auto head = globals::spawn_dimension->entities.try_get<entity::Head>(entity)) { - protocol::EntityHead head_packet; - head_packet.entity = entity; - head_packet.angles = head->angles; - protocol::send(session->peer, protocol::encode(head_packet)); - } - - if(const auto transform = globals::spawn_dimension->entities.try_get<entity::Transform>(entity)) { - protocol::EntityTransform transform_packet; - transform_packet.entity = entity; - transform_packet.angles = transform->angles; - transform_packet.chunk = transform->chunk; - transform_packet.local = transform->local; - protocol::send(session->peer, protocol::encode(transform_packet)); - } - - if(const auto velocity = globals::spawn_dimension->entities.try_get<entity::Velocity>(entity)) { - protocol::EntityVelocity velocity_packet; - velocity_packet.entity = entity; - velocity_packet.value = velocity->value; - protocol::send(session->peer, protocol::encode(velocity_packet)); - } - - if(globals::spawn_dimension->entities.all_of<entity::Player>(entity)) { - protocol::EntityPlayer player_packet; - player_packet.entity = entity; - protocol::send(session->peer, protocol::encode(player_packet)); - } - } - - session->dimension = globals::spawn_dimension; - session->player_entity = globals::spawn_dimension->entities.create(); - entity::shared::create_player(globals::spawn_dimension, session->player_entity); - - const auto& head = globals::spawn_dimension->entities.get<entity::Head>(session->player_entity); - const auto& transform = globals::spawn_dimension->entities.get<entity::Transform>(session->player_entity); - const auto& velocity = globals::spawn_dimension->entities.get<entity::Velocity>(session->player_entity); - - protocol::EntityHead head_packet; - head_packet.entity = session->player_entity; - head_packet.angles = head.angles; - - protocol::EntityTransform transform_packet; - transform_packet.entity = session->player_entity; - transform_packet.angles = transform.angles; - transform_packet.chunk = transform.chunk; - transform_packet.local = transform.local; - - protocol::EntityVelocity velocity_packet; - velocity_packet.entity = session->player_entity; - velocity_packet.value = velocity.value; - - protocol::EntityPlayer player_packet; - player_packet.entity = session->player_entity; - - protocol::broadcast(globals::server_host, protocol::encode(head_packet)); - protocol::broadcast(globals::server_host, protocol::encode(transform_packet)); - protocol::broadcast(globals::server_host, protocol::encode(velocity_packet)); - protocol::broadcast(globals::server_host, protocol::encode(player_packet)); - - protocol::SpawnPlayer spawn_packet; - spawn_packet.entity = session->player_entity; - - // SpawnPlayer serves a different purpose compared to EntityPlayer - // The latter is used to construct entities (as in "attach a component") - // whilst the SpawnPlayer packet is used to notify client-side that the - // entity identifier in the packet is to be treated as the local player entity - protocol::send(session->peer, protocol::encode(spawn_packet)); - - protocol::ChatMessage message; - message.type = protocol::ChatMessage::PLAYER_JOIN; - message.sender = session->client_username; - message.message = std::string(); - - protocol::broadcast(globals::server_host, protocol::encode(message)); - - sessions::refresh_scoreboard(); - - return; - } - - protocol::Disconnect response; - response.reason = "protocol.server_full"; - protocol::send(packet.peer, protocol::encode(response)); -} - -static void on_disconnect_packet(const protocol::Disconnect& packet) -{ - if(Session* session = sessions::find(packet.peer)) { - protocol::ChatMessage message; - message.type = protocol::ChatMessage::PLAYER_LEAVE; - message.sender = session->client_username; - message.message = packet.reason; - - protocol::broadcast(globals::server_host, protocol::encode(message), session->peer); - - spdlog::info("{} disconnected ({})", session->client_username, packet.reason); - - sessions::destroy(session); - sessions::refresh_scoreboard(); - } -} - -// NOTE: [sessions] is a good place for this since [receive] -// handles entity data sent by players and [sessions] handles -// everything else network related that is not player movement -static void on_voxel_set(const world::VoxelSetEvent& event) -{ - protocol::SetVoxel packet; - packet.vpos = coord::to_voxel(event.cpos, event.lpos); - packet.voxel = event.voxel; - packet.flags = 0U; // UNDONE - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} - -DimensionListener::DimensionListener(world::Dimension* dimension) -{ - this->dimension = dimension; -} - -void DimensionListener::on_destroy_entity(const entt::registry& registry, entt::entity entity) -{ - protocol::RemoveEntity packet; - packet.entity = entity; - sessions::broadcast(dimension, protocol::encode(packet)); -} - -void sessions::init(void) -{ - globals::server_config.add_value("sessions.max_players", sessions::max_players); - - globals::dispatcher.sink<protocol::LoginRequest>().connect<&on_login_request_packet>(); - globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>(); - - globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void sessions::init_late(void) -{ - sessions::num_players = 0U; - - username_map.clear(); - identity_map.clear(); - sessions_vector.resize(sessions::max_players.get_value(), Session()); - - for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) { - sessions_vector[i].client_index = UINT16_MAX; - sessions_vector[i].client_identity = UINT64_MAX; - sessions_vector[i].client_username = std::string(); - sessions_vector[i].player_entity = entt::null; - sessions_vector[i].peer = nullptr; - } -} - -void sessions::init_post_universe(void) -{ - for(auto& dimension : globals::dimensions) { - dimension_listeners.push_back(DimensionListener(dimension.second)); - dimension.second->entities.on_destroy<entt::entity>().connect<&DimensionListener::on_destroy_entity>(dimension_listeners.back()); - } -} - -void sessions::shutdown(void) -{ - username_map.clear(); - identity_map.clear(); - sessions_vector.clear(); - dimension_listeners.clear(); -} - -Session* sessions::create(ENetPeer* peer, std::string_view client_username) -{ - for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) { - if(!sessions_vector[i].peer) { - std::uint64_t client_identity = math::crc64(client_username.data(), client_username.size()); - - sessions_vector[i].client_index = i; - sessions_vector[i].client_identity = client_identity; - sessions_vector[i].client_username = client_username; - sessions_vector[i].player_entity = entt::null; - sessions_vector[i].peer = peer; - - username_map[std::string(client_username)] = &sessions_vector[i]; - identity_map[client_identity] = &sessions_vector[i]; - - peer->data = &sessions_vector[i]; - - sessions::num_players += 1U; - - return &sessions_vector[i]; - } - } - - return nullptr; -} - -Session* sessions::find(std::string_view client_username) -{ - const auto it = username_map.find(std::string(client_username)); - if(it != username_map.cend()) { - return it->second; - } - else { - return nullptr; - } -} - -Session* sessions::find(std::uint16_t client_index) -{ - if(client_index < sessions_vector.size()) { - if(!sessions_vector[client_index].peer) { - return nullptr; - } - else { - return &sessions_vector[client_index]; - } - } - - return nullptr; -} - -Session* sessions::find(std::uint64_t client_identity) -{ - const auto it = identity_map.find(client_identity); - - if(it != identity_map.cend()) { - return it->second; - } - else { - return nullptr; - } -} - -Session* sessions::find(ENetPeer* peer) -{ - if(peer != nullptr) { - return reinterpret_cast<Session*>(peer->data); - } - else { - return nullptr; - } -} - -void sessions::destroy(Session* session) -{ - if(session) { - if(session->peer) { - // Make sure we don't leave a mark - session->peer->data = nullptr; - } - - if(session->dimension) { - session->dimension->entities.destroy(session->player_entity); - } - - username_map.erase(session->client_username); - identity_map.erase(session->client_identity); - - session->client_index = UINT16_MAX; - session->client_identity = UINT64_MAX; - session->client_username = std::string(); - session->player_entity = entt::null; - session->peer = nullptr; - - sessions::num_players -= 1U; - } -} - -void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet) -{ - for(const auto& session : sessions_vector) { - if(session.peer && (session.dimension == dimension)) { - enet_peer_send(session.peer, protocol::CHANNEL, packet); - } - } -} - -void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except) -{ - for(const auto& session : sessions_vector) { - if(session.peer && (session.peer != except)) { - enet_peer_send(session.peer, protocol::CHANNEL, packet); - } - } -} - -void sessions::refresh_scoreboard(void) -{ - protocol::ScoreboardUpdate packet; - - for(std::size_t i = 0; i < sessions::max_players.get_value(); ++i) { - if(sessions_vector[i].peer) { - packet.names.push_back(sessions_vector[i].client_username); - } - } - - protocol::broadcast(globals::server_host, protocol::encode(packet)); -} +#include "server/pch.hh"
+
+#include "server/sessions.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+#include "core/math/crc64.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/entity/factory.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+#include "shared/world/item_registry.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+#include "shared/protocol.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+#include "server/whitelist.hh"
+
+class DimensionListener final {
+public:
+ explicit DimensionListener(world::Dimension* dimension);
+ void on_destroy_entity(const entt::registry& registry, entt::entity entity);
+
+private:
+ world::Dimension* dimension;
+};
+
+config::Unsigned sessions::max_players(8U, 1U, 128U);
+unsigned int sessions::num_players = 0U;
+
+static emhash8::HashMap<std::string, Session*> username_map;
+static emhash8::HashMap<std::uint64_t, Session*> identity_map;
+static std::vector<DimensionListener> dimension_listeners;
+static std::vector<Session> sessions_vector;
+
+static void on_login_request_packet(const protocol::LoginRequest& packet)
+{
+ if(packet.version > protocol::VERSION) {
+ protocol::Disconnect response;
+ response.reason = "protocol.outdated_server";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(packet.version < protocol::VERSION) {
+ protocol::Disconnect response;
+ response.reason = "protocol.outdated_client";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ // FIXME: calculate voxel registry checksum ahead of time
+ // instead of figuring it out every time a new player connects
+ if(packet.voxel_registry_checksum != world::voxel_registry::calculate_checksum()) {
+ protocol::Disconnect response;
+ response.reason = "protocol.voxel_registry_checksum";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(packet.item_registry_checksum != world::item_registry::calculate_checksum()) {
+ protocol::Disconnect response;
+ response.reason = "protocol.item_registry_checksum";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ // Don't assign new usernames and just kick the player if
+ // an another client using the same username is already connected
+ // and playing; since we have a whitelist, adding "(1)" isn't feasible anymore
+ if(username_map.contains(packet.username)) {
+ protocol::Disconnect response;
+ response.reason = "protocol.username_taken";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(whitelist::enabled.get_value()) {
+ if(!whitelist::contains(packet.username.c_str())) {
+ protocol::Disconnect response;
+ response.reason = "protocol.not_whitelisted";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(!whitelist::matches(packet.username.c_str(), packet.password_hash)) {
+ protocol::Disconnect response;
+ response.reason = "protocol.password_incorrect";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+ }
+ else if(packet.password_hash != server_game::password_hash) {
+ protocol::Disconnect response;
+ response.reason = "protocol.password_incorrect";
+ protocol::send(packet.peer, protocol::encode(response));
+ return;
+ }
+
+ if(Session* session = sessions::create(packet.peer, packet.username.c_str())) {
+ protocol::LoginResponse response;
+ response.client_index = session->client_index;
+ response.client_identity = session->client_identity;
+ response.server_tickrate = globals::tickrate;
+ protocol::send(packet.peer, protocol::encode(response));
+
+ protocol::DimensionInfo dim_info;
+ dim_info.name = globals::spawn_dimension->get_name();
+ dim_info.gravity = globals::spawn_dimension->get_gravity();
+ protocol::send(packet.peer, protocol::encode(dim_info));
+
+ spdlog::info("sessions: {} [{}] logged in with client_index={} in {}", session->client_username, session->client_identity,
+ session->client_index, globals::spawn_dimension->get_name());
+
+ // FIXME: only send entities that are present within the current
+ // player's view bounding box; this also would mean we're not sending
+ // anything here and just straight up spawing the player and await them
+ // to receive all the chunks and entites they feel like requesting
+ for(auto entity : globals::spawn_dimension->entities.view<entt::entity>()) {
+ if(const auto head = globals::spawn_dimension->entities.try_get<entity::Head>(entity)) {
+ protocol::EntityHead head_packet;
+ head_packet.entity = entity;
+ head_packet.angles = head->angles;
+ protocol::send(session->peer, protocol::encode(head_packet));
+ }
+
+ if(const auto transform = globals::spawn_dimension->entities.try_get<entity::Transform>(entity)) {
+ protocol::EntityTransform transform_packet;
+ transform_packet.entity = entity;
+ transform_packet.angles = transform->angles;
+ transform_packet.chunk = transform->chunk;
+ transform_packet.local = transform->local;
+ protocol::send(session->peer, protocol::encode(transform_packet));
+ }
+
+ if(const auto velocity = globals::spawn_dimension->entities.try_get<entity::Velocity>(entity)) {
+ protocol::EntityVelocity velocity_packet;
+ velocity_packet.entity = entity;
+ velocity_packet.value = velocity->value;
+ protocol::send(session->peer, protocol::encode(velocity_packet));
+ }
+
+ if(globals::spawn_dimension->entities.all_of<entity::Player>(entity)) {
+ protocol::EntityPlayer player_packet;
+ player_packet.entity = entity;
+ protocol::send(session->peer, protocol::encode(player_packet));
+ }
+ }
+
+ session->dimension = globals::spawn_dimension;
+ session->player_entity = globals::spawn_dimension->entities.create();
+ entity::shared::create_player(globals::spawn_dimension, session->player_entity);
+
+ const auto& head = globals::spawn_dimension->entities.get<entity::Head>(session->player_entity);
+ const auto& transform = globals::spawn_dimension->entities.get<entity::Transform>(session->player_entity);
+ const auto& velocity = globals::spawn_dimension->entities.get<entity::Velocity>(session->player_entity);
+
+ protocol::EntityHead head_packet;
+ head_packet.entity = session->player_entity;
+ head_packet.angles = head.angles;
+
+ protocol::EntityTransform transform_packet;
+ transform_packet.entity = session->player_entity;
+ transform_packet.angles = transform.angles;
+ transform_packet.chunk = transform.chunk;
+ transform_packet.local = transform.local;
+
+ protocol::EntityVelocity velocity_packet;
+ velocity_packet.entity = session->player_entity;
+ velocity_packet.value = velocity.value;
+
+ protocol::EntityPlayer player_packet;
+ player_packet.entity = session->player_entity;
+
+ protocol::broadcast(globals::server_host, protocol::encode(head_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(transform_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(velocity_packet));
+ protocol::broadcast(globals::server_host, protocol::encode(player_packet));
+
+ protocol::SpawnPlayer spawn_packet;
+ spawn_packet.entity = session->player_entity;
+
+ // SpawnPlayer serves a different purpose compared to EntityPlayer
+ // The latter is used to construct entities (as in "attach a component")
+ // whilst the SpawnPlayer packet is used to notify client-side that the
+ // entity identifier in the packet is to be treated as the local player entity
+ protocol::send(session->peer, protocol::encode(spawn_packet));
+
+ protocol::ChatMessage message;
+ message.type = protocol::ChatMessage::PLAYER_JOIN;
+ message.sender = session->client_username;
+ message.message = std::string();
+
+ protocol::broadcast(globals::server_host, protocol::encode(message));
+
+ sessions::refresh_scoreboard();
+
+ return;
+ }
+
+ protocol::Disconnect response;
+ response.reason = "protocol.server_full";
+ protocol::send(packet.peer, protocol::encode(response));
+}
+
+static void on_disconnect_packet(const protocol::Disconnect& packet)
+{
+ if(Session* session = sessions::find(packet.peer)) {
+ protocol::ChatMessage message;
+ message.type = protocol::ChatMessage::PLAYER_LEAVE;
+ message.sender = session->client_username;
+ message.message = packet.reason;
+
+ protocol::broadcast(globals::server_host, protocol::encode(message), session->peer);
+
+ spdlog::info("{} disconnected ({})", session->client_username, packet.reason);
+
+ sessions::destroy(session);
+ sessions::refresh_scoreboard();
+ }
+}
+
+// NOTE: [sessions] is a good place for this since [receive]
+// handles entity data sent by players and [sessions] handles
+// everything else network related that is not player movement
+static void on_voxel_set(const world::VoxelSetEvent& event)
+{
+ protocol::SetVoxel packet;
+ packet.vpos = coord::to_voxel(event.cpos, event.lpos);
+ packet.voxel = event.voxel;
+ packet.flags = 0U; // UNDONE
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
+
+DimensionListener::DimensionListener(world::Dimension* dimension)
+{
+ this->dimension = dimension;
+}
+
+void DimensionListener::on_destroy_entity(const entt::registry& registry, entt::entity entity)
+{
+ protocol::RemoveEntity packet;
+ packet.entity = entity;
+ sessions::broadcast(dimension, protocol::encode(packet));
+}
+
+void sessions::init(void)
+{
+ globals::server_config.add_value("sessions.max_players", sessions::max_players);
+
+ globals::dispatcher.sink<protocol::LoginRequest>().connect<&on_login_request_packet>();
+ globals::dispatcher.sink<protocol::Disconnect>().connect<&on_disconnect_packet>();
+
+ globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void sessions::init_late(void)
+{
+ sessions::num_players = 0U;
+
+ username_map.clear();
+ identity_map.clear();
+ sessions_vector.resize(sessions::max_players.get_value(), Session());
+
+ for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) {
+ sessions_vector[i].client_index = UINT16_MAX;
+ sessions_vector[i].client_identity = UINT64_MAX;
+ sessions_vector[i].client_username = std::string();
+ sessions_vector[i].player_entity = entt::null;
+ sessions_vector[i].peer = nullptr;
+ }
+}
+
+void sessions::init_post_universe(void)
+{
+ for(auto& dimension : globals::dimensions) {
+ dimension_listeners.push_back(DimensionListener(dimension.second));
+ dimension.second->entities.on_destroy<entt::entity>().connect<&DimensionListener::on_destroy_entity>(dimension_listeners.back());
+ }
+}
+
+void sessions::shutdown(void)
+{
+ username_map.clear();
+ identity_map.clear();
+ sessions_vector.clear();
+ dimension_listeners.clear();
+}
+
+Session* sessions::create(ENetPeer* peer, std::string_view client_username)
+{
+ for(unsigned int i = 0U; i < sessions::max_players.get_value(); ++i) {
+ if(!sessions_vector[i].peer) {
+ std::uint64_t client_identity = math::crc64(client_username.data(), client_username.size());
+
+ sessions_vector[i].client_index = i;
+ sessions_vector[i].client_identity = client_identity;
+ sessions_vector[i].client_username = client_username;
+ sessions_vector[i].player_entity = entt::null;
+ sessions_vector[i].peer = peer;
+
+ username_map[std::string(client_username)] = &sessions_vector[i];
+ identity_map[client_identity] = &sessions_vector[i];
+
+ peer->data = &sessions_vector[i];
+
+ sessions::num_players += 1U;
+
+ return &sessions_vector[i];
+ }
+ }
+
+ return nullptr;
+}
+
+Session* sessions::find(std::string_view client_username)
+{
+ const auto it = username_map.find(std::string(client_username));
+ if(it != username_map.cend()) {
+ return it->second;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+Session* sessions::find(std::uint16_t client_index)
+{
+ if(client_index < sessions_vector.size()) {
+ if(!sessions_vector[client_index].peer) {
+ return nullptr;
+ }
+ else {
+ return &sessions_vector[client_index];
+ }
+ }
+
+ return nullptr;
+}
+
+Session* sessions::find(std::uint64_t client_identity)
+{
+ const auto it = identity_map.find(client_identity);
+
+ if(it != identity_map.cend()) {
+ return it->second;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+Session* sessions::find(ENetPeer* peer)
+{
+ if(peer != nullptr) {
+ return reinterpret_cast<Session*>(peer->data);
+ }
+ else {
+ return nullptr;
+ }
+}
+
+void sessions::destroy(Session* session)
+{
+ if(session) {
+ if(session->peer) {
+ // Make sure we don't leave a mark
+ session->peer->data = nullptr;
+ }
+
+ if(session->dimension) {
+ session->dimension->entities.destroy(session->player_entity);
+ }
+
+ username_map.erase(session->client_username);
+ identity_map.erase(session->client_identity);
+
+ session->client_index = UINT16_MAX;
+ session->client_identity = UINT64_MAX;
+ session->client_username = std::string();
+ session->player_entity = entt::null;
+ session->peer = nullptr;
+
+ sessions::num_players -= 1U;
+ }
+}
+
+void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet)
+{
+ for(const auto& session : sessions_vector) {
+ if(session.peer && (session.dimension == dimension)) {
+ enet_peer_send(session.peer, protocol::CHANNEL, packet);
+ }
+ }
+}
+
+void sessions::broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except)
+{
+ for(const auto& session : sessions_vector) {
+ if(session.peer && (session.peer != except)) {
+ enet_peer_send(session.peer, protocol::CHANNEL, packet);
+ }
+ }
+}
+
+void sessions::refresh_scoreboard(void)
+{
+ protocol::ScoreboardUpdate packet;
+
+ for(std::size_t i = 0; i < sessions::max_players.get_value(); ++i) {
+ if(sessions_vector[i].peer) {
+ packet.names.push_back(sessions_vector[i].client_username);
+ }
+ }
+
+ protocol::broadcast(globals::server_host, protocol::encode(packet));
+}
diff --git a/game/server/sessions.hh b/game/server/sessions.hh index 656b76d..14d50e1 100644 --- a/game/server/sessions.hh +++ b/game/server/sessions.hh @@ -1,55 +1,55 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace config -{ -class Unsigned; -} // namespace config - -struct Session final { - std::uint16_t client_index; - std::uint64_t client_identity; - std::string client_username; - entt::entity player_entity; - world::Dimension* dimension; - ENetPeer* peer; -}; - -namespace sessions -{ -extern config::Unsigned max_players; -extern unsigned int num_players; -} // namespace sessions - -namespace sessions -{ -void init(void); -void init_late(void); -void init_post_universe(void); -void shutdown(void); -} // namespace sessions - -namespace sessions -{ -Session* create(ENetPeer* peer, std::string_view client_username); -Session* find(std::string_view client_username); -Session* find(std::uint16_t client_index); -Session* find(std::uint64_t client_identity); -Session* find(ENetPeer* peer); -void destroy(Session* session); -} // namespace sessions - -namespace sessions -{ -void broadcast(const world::Dimension* dimension, ENetPacket* packet); -void broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except); -} // namespace sessions - -namespace sessions -{ -void refresh_scoreboard(void); -} // namespace sessions +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace config
+{
+class Unsigned;
+} // namespace config
+
+struct Session final {
+ std::uint16_t client_index;
+ std::uint64_t client_identity;
+ std::string client_username;
+ entt::entity player_entity;
+ world::Dimension* dimension;
+ ENetPeer* peer;
+};
+
+namespace sessions
+{
+extern config::Unsigned max_players;
+extern unsigned int num_players;
+} // namespace sessions
+
+namespace sessions
+{
+void init(void);
+void init_late(void);
+void init_post_universe(void);
+void shutdown(void);
+} // namespace sessions
+
+namespace sessions
+{
+Session* create(ENetPeer* peer, std::string_view client_username);
+Session* find(std::string_view client_username);
+Session* find(std::uint16_t client_index);
+Session* find(std::uint64_t client_identity);
+Session* find(ENetPeer* peer);
+void destroy(Session* session);
+} // namespace sessions
+
+namespace sessions
+{
+void broadcast(const world::Dimension* dimension, ENetPacket* packet);
+void broadcast(const world::Dimension* dimension, ENetPacket* packet, ENetPeer* except);
+} // namespace sessions
+
+namespace sessions
+{
+void refresh_scoreboard(void);
+} // namespace sessions
diff --git a/game/server/status.cc b/game/server/status.cc index ed73b23..ba1d59d 100644 --- a/game/server/status.cc +++ b/game/server/status.cc @@ -1,26 +1,26 @@ -#include "server/pch.hh" - -#include "server/status.hh" - -#include "core/config/number.hh" - -#include "shared/protocol.hh" -#include "shared/splash.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" - -static void on_status_request_packet(const protocol::StatusRequest& packet) -{ - protocol::StatusResponse response; - response.version = protocol::VERSION; - response.max_players = sessions::max_players.get_value(); - response.num_players = sessions::num_players; - response.motd = splash::get(); - protocol::send(packet.peer, protocol::encode(response)); -} - -void status::init(void) -{ - globals::dispatcher.sink<protocol::StatusRequest>().connect<&on_status_request_packet>(); -} +#include "server/pch.hh"
+
+#include "server/status.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/protocol.hh"
+#include "shared/splash.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static void on_status_request_packet(const protocol::StatusRequest& packet)
+{
+ protocol::StatusResponse response;
+ response.version = protocol::VERSION;
+ response.max_players = sessions::max_players.get_value();
+ response.num_players = sessions::num_players;
+ response.motd = splash::get();
+ protocol::send(packet.peer, protocol::encode(response));
+}
+
+void status::init(void)
+{
+ globals::dispatcher.sink<protocol::StatusRequest>().connect<&on_status_request_packet>();
+}
diff --git a/game/server/status.hh b/game/server/status.hh index 35370a0..d298827 100644 --- a/game/server/status.hh +++ b/game/server/status.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace status -{ -void init(void); -} // namespace status +#pragma once
+
+namespace status
+{
+void init(void);
+} // namespace status
diff --git a/game/server/whitelist.cc b/game/server/whitelist.cc index 0807c19..053809e 100644 --- a/game/server/whitelist.cc +++ b/game/server/whitelist.cc @@ -1,101 +1,101 @@ -#include "server/pch.hh" - -#include "server/whitelist.hh" - -#include "core/config/boolean.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/math/crc64.hh" - -#include "core/utils/string.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -constexpr static std::string_view DEFAULT_FILENAME = "whitelist.txt"; -constexpr static char SEPARATOR_CHAR = ':'; - -config::Boolean whitelist::enabled(false); -config::String whitelist::filename(DEFAULT_FILENAME); - -static emhash8::HashMap<std::string, std::uint64_t> whitelist_map; - -void whitelist::init(void) -{ - globals::server_config.add_value("whitelist.enabled", whitelist::enabled); - globals::server_config.add_value("whitelist.filename", whitelist::filename); -} - -void whitelist::init_late(void) -{ - whitelist_map.clear(); - - if(!whitelist::enabled.get_value()) { - // Not enabled, shouldn't - // even bother with parsing - // the whitelist file - return; - } - - if(utils::is_whitespace(whitelist::filename.get_value())) { - spdlog::warn("whitelist: enabled but filename is empty, using default ({})", DEFAULT_FILENAME); - whitelist::filename.set(DEFAULT_FILENAME); - } - - PHYSFS_File* file = PHYSFS_openRead(whitelist::filename.c_str()); - - if(file == nullptr) { - spdlog::warn("whitelist: {}: {}", whitelist::filename.get(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - whitelist::enabled.set_value(false); - return; - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - std::istringstream stream(source); - std::string line; - - while(std::getline(stream, line)) { - const auto location = line.find_last_of(SEPARATOR_CHAR); - - if(location == std::string::npos) { - // Entries that don't define a password field default - // to the global server password; this allows easier adding - // of guest accounts which can later be edited to use a better password - whitelist_map[line] = server_game::password_hash; - } - else { - const auto username = line.substr(0, location); - const auto password = line.substr(location + 1); - whitelist_map[username] = math::crc64(password); - } - } - - PHYSFS_close(file); -} - -void whitelist::shutdown(void) -{ - // UNDONE: implement saving -} - -bool whitelist::contains(std::string_view username) -{ - return whitelist_map.contains(std::string(username)); -} - -bool whitelist::matches(std::string_view username, std::uint64_t password_hash) -{ - const auto it = whitelist_map.find(std::string(username)); - - if(it == whitelist_map.cend()) { - // Not whitelisted, no match - return false; - } - - return it->second == password_hash; -} +#include "server/pch.hh"
+
+#include "server/whitelist.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/crc64.hh"
+
+#include "core/utils/string.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+constexpr static std::string_view DEFAULT_FILENAME = "whitelist.txt";
+constexpr static char SEPARATOR_CHAR = ':';
+
+config::Boolean whitelist::enabled(false);
+config::String whitelist::filename(DEFAULT_FILENAME);
+
+static emhash8::HashMap<std::string, std::uint64_t> whitelist_map;
+
+void whitelist::init(void)
+{
+ globals::server_config.add_value("whitelist.enabled", whitelist::enabled);
+ globals::server_config.add_value("whitelist.filename", whitelist::filename);
+}
+
+void whitelist::init_late(void)
+{
+ whitelist_map.clear();
+
+ if(!whitelist::enabled.get_value()) {
+ // Not enabled, shouldn't
+ // even bother with parsing
+ // the whitelist file
+ return;
+ }
+
+ if(utils::is_whitespace(whitelist::filename.get_value())) {
+ spdlog::warn("whitelist: enabled but filename is empty, using default ({})", DEFAULT_FILENAME);
+ whitelist::filename.set(DEFAULT_FILENAME);
+ }
+
+ PHYSFS_File* file = PHYSFS_openRead(whitelist::filename.c_str());
+
+ if(file == nullptr) {
+ spdlog::warn("whitelist: {}: {}", whitelist::filename.get(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ whitelist::enabled.set_value(false);
+ return;
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::istringstream stream(source);
+ std::string line;
+
+ while(std::getline(stream, line)) {
+ const auto location = line.find_last_of(SEPARATOR_CHAR);
+
+ if(location == std::string::npos) {
+ // Entries that don't define a password field default
+ // to the global server password; this allows easier adding
+ // of guest accounts which can later be edited to use a better password
+ whitelist_map[line] = server_game::password_hash;
+ }
+ else {
+ const auto username = line.substr(0, location);
+ const auto password = line.substr(location + 1);
+ whitelist_map[username] = math::crc64(password);
+ }
+ }
+
+ PHYSFS_close(file);
+}
+
+void whitelist::shutdown(void)
+{
+ // UNDONE: implement saving
+}
+
+bool whitelist::contains(std::string_view username)
+{
+ return whitelist_map.contains(std::string(username));
+}
+
+bool whitelist::matches(std::string_view username, std::uint64_t password_hash)
+{
+ const auto it = whitelist_map.find(std::string(username));
+
+ if(it == whitelist_map.cend()) {
+ // Not whitelisted, no match
+ return false;
+ }
+
+ return it->second == password_hash;
+}
diff --git a/game/server/whitelist.hh b/game/server/whitelist.hh index 4695d16..990b15a 100644 --- a/game/server/whitelist.hh +++ b/game/server/whitelist.hh @@ -1,26 +1,26 @@ -#pragma once - -namespace config -{ -class Boolean; -class String; -} // namespace config - -namespace whitelist -{ -extern config::Boolean enabled; -extern config::String filename; -} // namespace whitelist - -namespace whitelist -{ -void init(void); -void init_late(void); -void shutdown(void); -} // namespace whitelist - -namespace whitelist -{ -bool contains(std::string_view username); -bool matches(std::string_view username, std::uint64_t password_hash); -} // namespace whitelist +#pragma once
+
+namespace config
+{
+class Boolean;
+class String;
+} // namespace config
+
+namespace whitelist
+{
+extern config::Boolean enabled;
+extern config::String filename;
+} // namespace whitelist
+
+namespace whitelist
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+} // namespace whitelist
+
+namespace whitelist
+{
+bool contains(std::string_view username);
+bool matches(std::string_view username, std::uint64_t password_hash);
+} // namespace whitelist
diff --git a/game/server/world/inhabited.hh b/game/server/world/inhabited.hh index 57008e9..5eba9ef 100644 --- a/game/server/world/inhabited.hh +++ b/game/server/world/inhabited.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace world -{ -struct Inhabited final {}; -} // namespace world +#pragma once
+
+namespace world
+{
+struct Inhabited final {};
+} // namespace world
diff --git a/game/server/world/overworld.cc b/game/server/world/overworld.cc index fca3edf..eb801de 100644 --- a/game/server/world/overworld.cc +++ b/game/server/world/overworld.cc @@ -1,383 +1,383 @@ -#include "server/pch.hh" - -#include "server/world/overworld.hh" - -#include "core/math/vectors.hh" - -#include "shared/world/voxel_storage.hh" - -#include "shared/coord.hh" -#include "shared/game_voxels.hh" - -// FIXME: load these from a file -static void compute_tree_feature(unsigned int height, world::Feature& feature, voxel_id log_voxel, voxel_id leaves_voxel) -{ - // Ensure the tree height is too small - height = math::max<unsigned int>(height, 4U); - - // Put down a single piece of dirt - feature.push_back({ voxel_pos(0, -1, 0), game_voxels::dirt, true }); - - // Generate tree stem - for(unsigned int i = 0; i < height; ++i) { - feature.push_back({ voxel_pos(0, i, 0), log_voxel, true }); - } - - auto leaves_start = height - 3U; - auto leaves_thick_end = height - 2U; - auto leaves_thin_end = height - 1U; - - // Generate the thin 3x3 layer of leaves that - // starts from leaves_start and ends at leaves_thin_end - for(unsigned int i = leaves_start; i <= leaves_thin_end; ++i) { - feature.push_back({ local_pos(-1, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +1), leaves_voxel, false }); - } - - // Generate the tree cap; a 3x3 patch of leaves - // that is slapped right on top of the thin 3x3 layer - feature.push_back({ local_pos(-1, height, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+0, height, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+1, height, +0), leaves_voxel, false }); - - // Generate the thin 5x5 layer of leaves that - // starts from leaves_start and ends at leaves_thin_end - for(unsigned int i = leaves_start; i <= leaves_thick_end; ++i) { - feature.push_back({ local_pos(-1, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(-1, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(-2, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+0, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+1, i, +2), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, -1), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, -2), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +0), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +1), leaves_voxel, false }); - feature.push_back({ local_pos(+2, i, +2), leaves_voxel, false }); - } -} - -world::Overworld::Overworld(std::string_view name) : Dimension(name, -30.0f) -{ - m_bottommost_chunk.set_limits(-64, -4); - m_terrain_variation.set_limits(16, 256); - - compute_tree_feature(4U, m_feat_tree[0], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(5U, m_feat_tree[1], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(6U, m_feat_tree[2], game_voxels::oak_log, game_voxels::oak_leaves); - compute_tree_feature(8U, m_feat_tree[3], game_voxels::oak_log, game_voxels::oak_leaves); -} - -void world::Overworld::init(io::ConfigMap& config) -{ - m_terrain_variation.set_value(64); - m_bottommost_chunk.set_value(-4); - - config.add_value("overworld.terrain_variation", m_terrain_variation); - config.add_value("overworld.bottommost_chunk", m_bottommost_chunk); -} - -void world::Overworld::init_late(std::uint64_t global_seed) -{ - std::mt19937 twister(global_seed); - - m_fnl_variation = fnlCreateState(); - m_fnl_variation.seed = static_cast<int>(twister()); - m_fnl_variation.noise_type = FNL_NOISE_PERLIN; - m_fnl_variation.frequency = 0.001f; - - m_fnl_terrain = fnlCreateState(); - m_fnl_terrain.seed = static_cast<int>(twister()); - m_fnl_terrain.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_terrain.fractal_type = FNL_FRACTAL_FBM; - m_fnl_terrain.frequency = 0.005f; - m_fnl_terrain.octaves = 4; - - m_fnl_caves_a = fnlCreateState(); - m_fnl_caves_a.seed = static_cast<int>(twister()); - m_fnl_caves_a.noise_type = FNL_NOISE_PERLIN; - m_fnl_caves_a.fractal_type = FNL_FRACTAL_RIDGED; - m_fnl_caves_a.frequency = 0.0125f; - m_fnl_caves_a.octaves = 1; - - m_fnl_caves_b = fnlCreateState(); - m_fnl_caves_b.seed = static_cast<int>(twister()); - m_fnl_caves_b.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_caves_b.fractal_type = FNL_FRACTAL_RIDGED; - m_fnl_caves_b.frequency = 0.0125f; - m_fnl_caves_b.octaves = 1; - - m_fnl_nvdi = fnlCreateState(); - m_fnl_nvdi.seed = static_cast<int>(twister()); - m_fnl_nvdi.noise_type = FNL_NOISE_OPENSIMPLEX2S; - m_fnl_nvdi.frequency = 1.0f; - - m_metamap.clear(); -} - -bool world::Overworld::generate(const chunk_pos& cpos, VoxelStorage& voxels) -{ - if(cpos.y <= m_bottommost_chunk.get_value()) { - // If the player asks the generator - // to generate a lot of stuff below - // the surface, it will happily chew - // through all the server threads - return false; - } - - voxels.fill(NULL_VOXEL_ID); - - m_mutex.lock(); - generate_terrain(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_surface(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_caves(cpos, voxels); - m_mutex.unlock(); - - m_mutex.lock(); - generate_features(cpos, voxels); - m_mutex.unlock(); - - return true; -} - -bool world::Overworld::is_inside_cave(const voxel_pos& vpos) -{ - auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, vpos.y * 2.0f, vpos.z); - auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, vpos.y * 2.0f, vpos.z); - return (noise_a > 0.95f) && (noise_b > 0.85f); -} - -bool world::Overworld::is_inside_terrain(const voxel_pos& vpos) -{ - auto variation_noise = fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z); - auto variation = m_terrain_variation.get_value() * (1.0f - (variation_noise * variation_noise)); - auto noise = variation * fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z) - vpos.y; - return noise > 0.0f; -} - -const world::Overworld_Metadata& world::Overworld::get_or_create_metadata(const chunk_pos_xz& cpos) -{ - auto it = m_metamap.find(cpos); - - if(it != m_metamap.cend()) { - // Metadata is present - return it->second; - } - - auto& metadata = m_metamap.insert_or_assign(cpos, Overworld_Metadata()).first->second; - metadata.entropy.fill(std::numeric_limits<std::uint64_t>::max()); - metadata.heightmap.fill(std::numeric_limits<voxel_pos::value_type>::min()); - - auto twister = std::mt19937_64(std::hash<chunk_pos_xz>()(cpos)); - auto variation = m_terrain_variation.get_value(); - - // Generator might need some randomness - // that depends on 2D coordinates, so we - // generate this entropy ahead of time - for(int i = 0; i < CHUNK_AREA; ++i) { - metadata.entropy[i] = twister(); - } - - // Generate speculative heightmap; - // Cave generation might have issues with placing - // surface features such as trees but I genuinely don't give a shit - for(int lx = 0; lx < CHUNK_SIZE; lx += 1) { - for(int lz = 0; lz < CHUNK_SIZE; lz += 1) { - auto hdx = static_cast<std::size_t>(lx + lz * CHUNK_SIZE); - auto vpos = coord::to_voxel(chunk_pos(cpos.x, 0, cpos.y), local_pos(lx, 0, lz)); - - for(vpos.y = variation; vpos.y >= -variation; vpos.y -= 1) { - if(is_inside_terrain(vpos)) { - metadata.heightmap[hdx] = vpos.y; - break; - } - } - } - } - - auto nvdi_value = 0.5f + 0.5f * fnlGetNoise2D(&m_fnl_nvdi, cpos.x, cpos.y); - auto tree_density = (nvdi_value >= 0.33f) ? math::floor<unsigned int>(nvdi_value * 4.0f) : 0U; - - for(unsigned int i = 0U; i < tree_density; ++i) { - auto lpos = local_pos((twister() % CHUNK_SIZE), (twister() % OW_NUM_TREES), (twister() % CHUNK_SIZE)); - auto is_unique = true; - - for(const auto& check_lpos : metadata.trees) { - if(math::distance2(check_lpos, lpos) <= 9) { - is_unique = false; - break; - } - } - - if(is_unique) { - metadata.trees.push_back(lpos); - } - } - - return metadata; -} - -void world::Overworld::generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - - if(vpos.y > variation) { - voxels[i] = NULL_VOXEL_ID; - continue; - } - - if(vpos.y < -variation) { - voxels[i] = game_voxels::stone; - continue; - } - - if(is_inside_terrain(vpos)) { - voxels[i] = game_voxels::stone; - continue; - } - } -} - -void world::Overworld::generate_surface(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - auto hdx = static_cast<std::size_t>(lpos.x + lpos.z * CHUNK_SIZE); - - if((vpos.y > variation) || (vpos.y < -variation)) { - // Speculative optimization - continue; - } - - if(voxels[i] == NULL_VOXEL_ID) { - // Surface voxel checks only apply for solid voxels; - // it's kind of obvious you can't replace air with grass - continue; - } - - unsigned int depth = 0U; - - for(unsigned int dy = 0U; dy < 5U; dy += 1U) { - auto d_lpos = local_pos(lpos.x, lpos.y + dy + 1, lpos.z); - auto d_vpos = coord::to_voxel(cpos, d_lpos); - auto d_index = coord::to_index(d_lpos); - - if(d_lpos.y >= CHUNK_SIZE) { - if(!is_inside_terrain(d_vpos)) { - break; - } - - depth += 1U; - } - else { - if(voxels[d_index] == NULL_VOXEL_ID) { - break; - } - - depth += 1U; - } - } - - if(depth < 5U) { - if(depth == 0U) { - voxels[i] = game_voxels::grass; - } - else { - voxels[i] = game_voxels::dirt; - } - } - } -} - -void world::Overworld::generate_caves(const chunk_pos& cpos, VoxelStorage& voxels) -{ - auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z)); - auto variation = m_terrain_variation.get_value(); - - for(unsigned long i = 0U; i < CHUNK_VOLUME; ++i) { - auto lpos = coord::to_local(i); - auto vpos = coord::to_voxel(cpos, lpos); - - if(vpos.y > variation) { - // Speculative optimization - there's no solid - // terrain above variation to carve caves out from - continue; - } - - if(is_inside_cave(vpos)) { - voxels[i] = NULL_VOXEL_ID; - continue; - } - } -} - -void world::Overworld::generate_features(const chunk_pos& cpos, VoxelStorage& voxels) -{ - const chunk_pos_xz tree_chunks[] = { - chunk_pos_xz(cpos.x - 0, cpos.z - 1), - chunk_pos_xz(cpos.x - 1, cpos.z - 1), - chunk_pos_xz(cpos.x - 1, cpos.z + 0), - chunk_pos_xz(cpos.x - 1, cpos.z + 1), - chunk_pos_xz(cpos.x + 0, cpos.z + 0), - chunk_pos_xz(cpos.x + 0, cpos.z + 1), - chunk_pos_xz(cpos.x + 1, cpos.z - 1), - chunk_pos_xz(cpos.x + 1, cpos.z + 0), - chunk_pos_xz(cpos.x + 1, cpos.z + 1), - }; - - for(unsigned int i = 0U; i < math::array_size(tree_chunks); ++i) { - const auto& cpos_xz = tree_chunks[i]; - const auto& metadata = get_or_create_metadata(cpos_xz); - - for(const auto& tree_info : metadata.trees) { - auto hdx = static_cast<std::size_t>(tree_info.x + tree_info.z * CHUNK_SIZE); - auto height = metadata.heightmap[hdx]; - - if(height == std::numeric_limits<voxel_pos::value_type>::min()) { - // What happened? Cave happened - continue; - } - - auto cpos_xyz = chunk_pos(cpos_xz.x, 0, cpos_xz.y); - auto lpos_xyz = local_pos(tree_info.x, 0, tree_info.z); - - auto vpos = coord::to_voxel(cpos_xyz, lpos_xyz); - vpos.y = height; - - if(is_inside_cave(vpos)) { - // Cave is in the way - continue; - } - - m_feat_tree[tree_info.y].place(vpos + DIR_UP<voxel_pos::value_type>, cpos, voxels); - } - } -} +#include "server/pch.hh"
+
+#include "server/world/overworld.hh"
+
+#include "core/math/vectors.hh"
+
+#include "shared/world/voxel_storage.hh"
+
+#include "shared/coord.hh"
+#include "shared/game_voxels.hh"
+
+// FIXME: load these from a file
+static void compute_tree_feature(unsigned int height, world::Feature& feature, voxel_id log_voxel, voxel_id leaves_voxel)
+{
+ // Ensure the tree height is too small
+ height = math::max<unsigned int>(height, 4U);
+
+ // Put down a single piece of dirt
+ feature.push_back({ voxel_pos(0, -1, 0), game_voxels::dirt, true });
+
+ // Generate tree stem
+ for(unsigned int i = 0; i < height; ++i) {
+ feature.push_back({ voxel_pos(0, i, 0), log_voxel, true });
+ }
+
+ auto leaves_start = height - 3U;
+ auto leaves_thick_end = height - 2U;
+ auto leaves_thin_end = height - 1U;
+
+ // Generate the thin 3x3 layer of leaves that
+ // starts from leaves_start and ends at leaves_thin_end
+ for(unsigned int i = leaves_start; i <= leaves_thin_end; ++i) {
+ feature.push_back({ local_pos(-1, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +1), leaves_voxel, false });
+ }
+
+ // Generate the tree cap; a 3x3 patch of leaves
+ // that is slapped right on top of the thin 3x3 layer
+ feature.push_back({ local_pos(-1, height, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, height, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, height, +0), leaves_voxel, false });
+
+ // Generate the thin 5x5 layer of leaves that
+ // starts from leaves_start and ends at leaves_thin_end
+ for(unsigned int i = leaves_start; i <= leaves_thick_end; ++i) {
+ feature.push_back({ local_pos(-1, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(-1, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(-2, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+0, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+1, i, +2), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, -1), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, -2), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +0), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +1), leaves_voxel, false });
+ feature.push_back({ local_pos(+2, i, +2), leaves_voxel, false });
+ }
+}
+
+world::Overworld::Overworld(std::string_view name) : Dimension(name, -30.0f)
+{
+ m_bottommost_chunk.set_limits(-64, -4);
+ m_terrain_variation.set_limits(16, 256);
+
+ compute_tree_feature(4U, m_feat_tree[0], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(5U, m_feat_tree[1], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(6U, m_feat_tree[2], game_voxels::oak_log, game_voxels::oak_leaves);
+ compute_tree_feature(8U, m_feat_tree[3], game_voxels::oak_log, game_voxels::oak_leaves);
+}
+
+void world::Overworld::init(io::ConfigMap& config)
+{
+ m_terrain_variation.set_value(64);
+ m_bottommost_chunk.set_value(-4);
+
+ config.add_value("overworld.terrain_variation", m_terrain_variation);
+ config.add_value("overworld.bottommost_chunk", m_bottommost_chunk);
+}
+
+void world::Overworld::init_late(std::uint64_t global_seed)
+{
+ std::mt19937 twister(global_seed);
+
+ m_fnl_variation = fnlCreateState();
+ m_fnl_variation.seed = static_cast<int>(twister());
+ m_fnl_variation.noise_type = FNL_NOISE_PERLIN;
+ m_fnl_variation.frequency = 0.001f;
+
+ m_fnl_terrain = fnlCreateState();
+ m_fnl_terrain.seed = static_cast<int>(twister());
+ m_fnl_terrain.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_terrain.fractal_type = FNL_FRACTAL_FBM;
+ m_fnl_terrain.frequency = 0.005f;
+ m_fnl_terrain.octaves = 4;
+
+ m_fnl_caves_a = fnlCreateState();
+ m_fnl_caves_a.seed = static_cast<int>(twister());
+ m_fnl_caves_a.noise_type = FNL_NOISE_PERLIN;
+ m_fnl_caves_a.fractal_type = FNL_FRACTAL_RIDGED;
+ m_fnl_caves_a.frequency = 0.0125f;
+ m_fnl_caves_a.octaves = 1;
+
+ m_fnl_caves_b = fnlCreateState();
+ m_fnl_caves_b.seed = static_cast<int>(twister());
+ m_fnl_caves_b.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_caves_b.fractal_type = FNL_FRACTAL_RIDGED;
+ m_fnl_caves_b.frequency = 0.0125f;
+ m_fnl_caves_b.octaves = 1;
+
+ m_fnl_nvdi = fnlCreateState();
+ m_fnl_nvdi.seed = static_cast<int>(twister());
+ m_fnl_nvdi.noise_type = FNL_NOISE_OPENSIMPLEX2S;
+ m_fnl_nvdi.frequency = 1.0f;
+
+ m_metamap.clear();
+}
+
+bool world::Overworld::generate(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ if(cpos.y <= m_bottommost_chunk.get_value()) {
+ // If the player asks the generator
+ // to generate a lot of stuff below
+ // the surface, it will happily chew
+ // through all the server threads
+ return false;
+ }
+
+ voxels.fill(NULL_VOXEL_ID);
+
+ m_mutex.lock();
+ generate_terrain(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_surface(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_caves(cpos, voxels);
+ m_mutex.unlock();
+
+ m_mutex.lock();
+ generate_features(cpos, voxels);
+ m_mutex.unlock();
+
+ return true;
+}
+
+bool world::Overworld::is_inside_cave(const voxel_pos& vpos)
+{
+ auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, vpos.y * 2.0f, vpos.z);
+ auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, vpos.y * 2.0f, vpos.z);
+ return (noise_a > 0.95f) && (noise_b > 0.85f);
+}
+
+bool world::Overworld::is_inside_terrain(const voxel_pos& vpos)
+{
+ auto variation_noise = fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z);
+ auto variation = m_terrain_variation.get_value() * (1.0f - (variation_noise * variation_noise));
+ auto noise = variation * fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z) - vpos.y;
+ return noise > 0.0f;
+}
+
+const world::Overworld_Metadata& world::Overworld::get_or_create_metadata(const chunk_pos_xz& cpos)
+{
+ auto it = m_metamap.find(cpos);
+
+ if(it != m_metamap.cend()) {
+ // Metadata is present
+ return it->second;
+ }
+
+ auto& metadata = m_metamap.insert_or_assign(cpos, Overworld_Metadata()).first->second;
+ metadata.entropy.fill(std::numeric_limits<std::uint64_t>::max());
+ metadata.heightmap.fill(std::numeric_limits<voxel_pos::value_type>::min());
+
+ auto twister = std::mt19937_64(std::hash<chunk_pos_xz>()(cpos));
+ auto variation = m_terrain_variation.get_value();
+
+ // Generator might need some randomness
+ // that depends on 2D coordinates, so we
+ // generate this entropy ahead of time
+ for(int i = 0; i < CHUNK_AREA; ++i) {
+ metadata.entropy[i] = twister();
+ }
+
+ // Generate speculative heightmap;
+ // Cave generation might have issues with placing
+ // surface features such as trees but I genuinely don't give a shit
+ for(int lx = 0; lx < CHUNK_SIZE; lx += 1) {
+ for(int lz = 0; lz < CHUNK_SIZE; lz += 1) {
+ auto hdx = static_cast<std::size_t>(lx + lz * CHUNK_SIZE);
+ auto vpos = coord::to_voxel(chunk_pos(cpos.x, 0, cpos.y), local_pos(lx, 0, lz));
+
+ for(vpos.y = variation; vpos.y >= -variation; vpos.y -= 1) {
+ if(is_inside_terrain(vpos)) {
+ metadata.heightmap[hdx] = vpos.y;
+ break;
+ }
+ }
+ }
+ }
+
+ auto nvdi_value = 0.5f + 0.5f * fnlGetNoise2D(&m_fnl_nvdi, cpos.x, cpos.y);
+ auto tree_density = (nvdi_value >= 0.33f) ? math::floor<unsigned int>(nvdi_value * 4.0f) : 0U;
+
+ for(unsigned int i = 0U; i < tree_density; ++i) {
+ auto lpos = local_pos((twister() % CHUNK_SIZE), (twister() % OW_NUM_TREES), (twister() % CHUNK_SIZE));
+ auto is_unique = true;
+
+ for(const auto& check_lpos : metadata.trees) {
+ if(math::distance2(check_lpos, lpos) <= 9) {
+ is_unique = false;
+ break;
+ }
+ }
+
+ if(is_unique) {
+ metadata.trees.push_back(lpos);
+ }
+ }
+
+ return metadata;
+}
+
+void world::Overworld::generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+
+ if(vpos.y > variation) {
+ voxels[i] = NULL_VOXEL_ID;
+ continue;
+ }
+
+ if(vpos.y < -variation) {
+ voxels[i] = game_voxels::stone;
+ continue;
+ }
+
+ if(is_inside_terrain(vpos)) {
+ voxels[i] = game_voxels::stone;
+ continue;
+ }
+ }
+}
+
+void world::Overworld::generate_surface(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+ auto hdx = static_cast<std::size_t>(lpos.x + lpos.z * CHUNK_SIZE);
+
+ if((vpos.y > variation) || (vpos.y < -variation)) {
+ // Speculative optimization
+ continue;
+ }
+
+ if(voxels[i] == NULL_VOXEL_ID) {
+ // Surface voxel checks only apply for solid voxels;
+ // it's kind of obvious you can't replace air with grass
+ continue;
+ }
+
+ unsigned int depth = 0U;
+
+ for(unsigned int dy = 0U; dy < 5U; dy += 1U) {
+ auto d_lpos = local_pos(lpos.x, lpos.y + dy + 1, lpos.z);
+ auto d_vpos = coord::to_voxel(cpos, d_lpos);
+ auto d_index = coord::to_index(d_lpos);
+
+ if(d_lpos.y >= CHUNK_SIZE) {
+ if(!is_inside_terrain(d_vpos)) {
+ break;
+ }
+
+ depth += 1U;
+ }
+ else {
+ if(voxels[d_index] == NULL_VOXEL_ID) {
+ break;
+ }
+
+ depth += 1U;
+ }
+ }
+
+ if(depth < 5U) {
+ if(depth == 0U) {
+ voxels[i] = game_voxels::grass;
+ }
+ else {
+ voxels[i] = game_voxels::dirt;
+ }
+ }
+ }
+}
+
+void world::Overworld::generate_caves(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ auto& metadata = get_or_create_metadata(chunk_pos_xz(cpos.x, cpos.z));
+ auto variation = m_terrain_variation.get_value();
+
+ for(unsigned long i = 0U; i < CHUNK_VOLUME; ++i) {
+ auto lpos = coord::to_local(i);
+ auto vpos = coord::to_voxel(cpos, lpos);
+
+ if(vpos.y > variation) {
+ // Speculative optimization - there's no solid
+ // terrain above variation to carve caves out from
+ continue;
+ }
+
+ if(is_inside_cave(vpos)) {
+ voxels[i] = NULL_VOXEL_ID;
+ continue;
+ }
+ }
+}
+
+void world::Overworld::generate_features(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ const chunk_pos_xz tree_chunks[] = {
+ chunk_pos_xz(cpos.x - 0, cpos.z - 1),
+ chunk_pos_xz(cpos.x - 1, cpos.z - 1),
+ chunk_pos_xz(cpos.x - 1, cpos.z + 0),
+ chunk_pos_xz(cpos.x - 1, cpos.z + 1),
+ chunk_pos_xz(cpos.x + 0, cpos.z + 0),
+ chunk_pos_xz(cpos.x + 0, cpos.z + 1),
+ chunk_pos_xz(cpos.x + 1, cpos.z - 1),
+ chunk_pos_xz(cpos.x + 1, cpos.z + 0),
+ chunk_pos_xz(cpos.x + 1, cpos.z + 1),
+ };
+
+ for(unsigned int i = 0U; i < math::array_size(tree_chunks); ++i) {
+ const auto& cpos_xz = tree_chunks[i];
+ const auto& metadata = get_or_create_metadata(cpos_xz);
+
+ for(const auto& tree_info : metadata.trees) {
+ auto hdx = static_cast<std::size_t>(tree_info.x + tree_info.z * CHUNK_SIZE);
+ auto height = metadata.heightmap[hdx];
+
+ if(height == std::numeric_limits<voxel_pos::value_type>::min()) {
+ // What happened? Cave happened
+ continue;
+ }
+
+ auto cpos_xyz = chunk_pos(cpos_xz.x, 0, cpos_xz.y);
+ auto lpos_xyz = local_pos(tree_info.x, 0, tree_info.z);
+
+ auto vpos = coord::to_voxel(cpos_xyz, lpos_xyz);
+ vpos.y = height;
+
+ if(is_inside_cave(vpos)) {
+ // Cave is in the way
+ continue;
+ }
+
+ m_feat_tree[tree_info.y].place(vpos + DIR_UP<voxel_pos::value_type>, cpos, voxels);
+ }
+ }
+}
diff --git a/game/server/world/overworld.hh b/game/server/world/overworld.hh index f3fc8cf..a8112cf 100644 --- a/game/server/world/overworld.hh +++ b/game/server/world/overworld.hh @@ -1,68 +1,68 @@ -#pragma once - -#include "core/config/number.hh" - -#include "core/io/config_map.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/feature.hh" - -#include "shared/const.hh" - -constexpr static unsigned int OW_NUM_TREES = 4U; - -namespace world -{ -struct Overworld_Metadata final { - world::dimension_entropy_map entropy; - world::dimension_height_map heightmap; - std::vector<local_pos> trees; -}; -} // namespace world - -namespace world -{ -class Overworld final : public Dimension { -public: - explicit Overworld(std::string_view name); - virtual ~Overworld(void) = default; - -public: - virtual void init(io::ConfigMap& config) override; - virtual void init_late(std::uint64_t global_seed) override; - virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels) override; - -private: - bool is_inside_cave(const voxel_pos& vpos); - bool is_inside_terrain(const voxel_pos& vpos); - -private: - const Overworld_Metadata& get_or_create_metadata(const chunk_pos_xz& cpos); - -private: - void generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_surface(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_caves(const chunk_pos& cpos, VoxelStorage& voxels); - void generate_features(const chunk_pos& cpos, VoxelStorage& voxels); - -private: - config::Int m_terrain_variation; - config::Int m_bottommost_chunk; - -private: - emhash8::HashMap<chunk_pos_xz, Overworld_Metadata> m_metamap; - -private: - fnl_state m_fnl_variation; - fnl_state m_fnl_terrain; - fnl_state m_fnl_caves_a; - fnl_state m_fnl_caves_b; - fnl_state m_fnl_nvdi; - -private: - Feature m_feat_tree[OW_NUM_TREES]; - -private: - std::mutex m_mutex; -}; -} // namespace world +#pragma once
+
+#include "core/config/number.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/feature.hh"
+
+#include "shared/const.hh"
+
+constexpr static unsigned int OW_NUM_TREES = 4U;
+
+namespace world
+{
+struct Overworld_Metadata final {
+ world::dimension_entropy_map entropy;
+ world::dimension_height_map heightmap;
+ std::vector<local_pos> trees;
+};
+} // namespace world
+
+namespace world
+{
+class Overworld final : public Dimension {
+public:
+ explicit Overworld(std::string_view name);
+ virtual ~Overworld(void) = default;
+
+public:
+ virtual void init(io::ConfigMap& config) override;
+ virtual void init_late(std::uint64_t global_seed) override;
+ virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels) override;
+
+private:
+ bool is_inside_cave(const voxel_pos& vpos);
+ bool is_inside_terrain(const voxel_pos& vpos);
+
+private:
+ const Overworld_Metadata& get_or_create_metadata(const chunk_pos_xz& cpos);
+
+private:
+ void generate_terrain(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_surface(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_caves(const chunk_pos& cpos, VoxelStorage& voxels);
+ void generate_features(const chunk_pos& cpos, VoxelStorage& voxels);
+
+private:
+ config::Int m_terrain_variation;
+ config::Int m_bottommost_chunk;
+
+private:
+ emhash8::HashMap<chunk_pos_xz, Overworld_Metadata> m_metamap;
+
+private:
+ fnl_state m_fnl_variation;
+ fnl_state m_fnl_terrain;
+ fnl_state m_fnl_caves_a;
+ fnl_state m_fnl_caves_b;
+ fnl_state m_fnl_nvdi;
+
+private:
+ Feature m_feat_tree[OW_NUM_TREES];
+
+private:
+ std::mutex m_mutex;
+};
+} // namespace world
diff --git a/game/server/world/universe.cc b/game/server/world/universe.cc index 278d0a9..2dd6053 100644 --- a/game/server/world/universe.cc +++ b/game/server/world/universe.cc @@ -1,221 +1,221 @@ -#include "server/pch.hh" - -#include "server/world/universe.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/buffer.hh" -#include "core/io/config_map.hh" - -#include "core/utils/epoch.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "server/world/inhabited.hh" -#include "server/world/overworld.hh" - -#include "server/globals.hh" - -struct DimensionMetadata final { - std::string config_path; - std::string zvox_dir; - io::ConfigMap config; -}; - -static config::String universe_name("save"); - -static io::ConfigMap universe_config; -static config::Unsigned64 universe_config_seed; -static config::String universe_spawn_dimension("world"); - -static std::string universe_config_path; -static std::unordered_map<world::Dimension*, DimensionMetadata*> metadata_map; - -static std::string make_chunk_filename(const DimensionMetadata* metadata, const chunk_pos& cpos) -{ - const auto unsigned_x = static_cast<std::uint32_t>(cpos.x); - const auto unsigned_y = static_cast<std::uint32_t>(cpos.y); - const auto unsigned_z = static_cast<std::uint32_t>(cpos.z); - return std::format("{}/{:08X}-{:08X}-{:08X}.zvox", metadata->zvox_dir, unsigned_x, unsigned_y, unsigned_z); -} - -static void add_new_dimension(world::Dimension* dimension) -{ - if(globals::dimensions.count(std::string(dimension->get_name()))) { - spdlog::critical("universe: dimension named {} already exists", dimension->get_name()); - std::terminate(); - } - - auto dimension_dir = std::format("{}/{}", universe_name.get(), dimension->get_name()); - - if(!PHYSFS_mkdir(dimension_dir.c_str())) { - spdlog::critical("universe: {}: {}", dimension_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - auto metadata = new DimensionMetadata; - metadata->config_path = std::format("{}/dimension.conf", dimension_dir); - metadata->zvox_dir = std::format("{}/chunk", dimension_dir); - - if(!PHYSFS_mkdir(metadata->zvox_dir.c_str())) { - spdlog::critical("universe: {}: {}", metadata->zvox_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - globals::dimensions.insert_or_assign(std::string(dimension->get_name()), dimension); - - auto& mapped_metadata = metadata_map.insert_or_assign(dimension, metadata).first->second; - - dimension->init(mapped_metadata->config); - - mapped_metadata->config.load_file(mapped_metadata->config_path.c_str()); - - dimension->init_late(universe_config_seed.get_value()); -} - -static void internal_save_chunk(const DimensionMetadata* metadata, const world::Dimension* dimension, const chunk_pos& cpos, - const world::Chunk* chunk) -{ - auto path = make_chunk_filename(metadata, cpos); - - io::WriteBuffer buffer; - chunk->get_voxels().serialize(buffer); - - if(auto file = buffer.to_file(path.c_str())) { - PHYSFS_close(file); - return; - } -} - -void world::universe::init(void) -{ - // If the world is newly created, the seed will - // be chosed based on the current system's view on UNIX time - universe_config_seed.set_value(utils::unix_microseconds()); - - // We're going to read files from directory named with - // the value of this config value. Since config is also - // read from command line, the [--universe <name>] parameter still works - globals::server_config.add_value("universe", universe_name); - - universe_config.add_value("global_seed", universe_config_seed); - universe_config.add_value("spawn_dimension", universe_spawn_dimension); -} - -void world::universe::init_late(void) -{ - const auto universe_dir = std::string(universe_name.get()); - - if(!PHYSFS_mkdir(universe_dir.c_str())) { - spdlog::critical("universe: {}: {}", universe_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - universe_config_path = std::format("{}/universe.conf", universe_dir); - universe_config.load_file(universe_config_path.c_str()); - - add_new_dimension(new Overworld("world")); - - // UNDONE: lua scripts to setup dimensions - if(globals::dimensions.empty()) { - spdlog::critical("universe: no dimensions"); - std::terminate(); - } - - auto spawn_dimension = globals::dimensions.find(universe_spawn_dimension.get_value()); - - if(spawn_dimension == globals::dimensions.cend()) { - spdlog::critical("universe: {} is not a valid dimension name", universe_spawn_dimension.get()); - std::terminate(); - } - - globals::spawn_dimension = spawn_dimension->second; -} - -void world::universe::shutdown(void) -{ - for(const auto metadata : metadata_map) { - metadata.second->config.save_file(metadata.second->config_path.c_str()); - delete metadata.second; - } - - metadata_map.clear(); - - for(const auto dimension : globals::dimensions) { - world::universe::save_all_chunks(dimension.second); - delete dimension.second; - } - - globals::dimensions.clear(); - globals::spawn_dimension = nullptr; - - universe_config.save_file(universe_config_path.c_str()); -} - -world::Chunk* world::universe::load_chunk(Dimension* dimension, const chunk_pos& cpos) -{ - if(auto chunk = dimension->find_chunk(cpos)) { - // Just return the existing chunk which is - // most probable to be up to date compared to - // whatever the hell is currently stored on disk - return chunk; - } - - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // The dimension is for sure a weird one - return nullptr; - } - - if(auto file = PHYSFS_openRead(make_chunk_filename(metadata->second, cpos).c_str())) { - VoxelStorage voxels; - io::ReadBuffer buffer(file); - voxels.deserialize(buffer); - - PHYSFS_close(file); - - auto chunk = dimension->create_chunk(cpos); - chunk->set_voxels(voxels); - - // Make sure we're going to save it later - dimension->chunks.emplace_or_replace<Inhabited>(chunk->get_entity()); - - return chunk; - } - - return nullptr; -} - -void world::universe::save_chunk(Dimension* dimension, const chunk_pos& cpos) -{ - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // Cannot save a chunk in a dimension - // that doesn't have a metadata struct - return; - } - - if(auto chunk = dimension->find_chunk(cpos)) { - internal_save_chunk(metadata->second, dimension, cpos, chunk); - } -} - -void world::universe::save_all_chunks(Dimension* dimension) -{ - auto group = dimension->chunks.group(entt::get<ChunkComponent, Inhabited>); - auto metadata = metadata_map.find(dimension); - - if(metadata == metadata_map.cend()) { - // Cannot save a chunk in a dimension - // that doesn't have a metadata struct - return; - } - - for(auto [entity, chunk] : group.each()) { - internal_save_chunk(metadata->second, dimension, chunk.cpos, chunk.chunk); - } -} +#include "server/pch.hh"
+
+#include "server/world/universe.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/buffer.hh"
+#include "core/io/config_map.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/overworld.hh"
+
+#include "server/globals.hh"
+
+struct DimensionMetadata final {
+ std::string config_path;
+ std::string zvox_dir;
+ io::ConfigMap config;
+};
+
+static config::String universe_name("save");
+
+static io::ConfigMap universe_config;
+static config::Unsigned64 universe_config_seed;
+static config::String universe_spawn_dimension("world");
+
+static std::string universe_config_path;
+static std::unordered_map<world::Dimension*, DimensionMetadata*> metadata_map;
+
+static std::string make_chunk_filename(const DimensionMetadata* metadata, const chunk_pos& cpos)
+{
+ const auto unsigned_x = static_cast<std::uint32_t>(cpos.x);
+ const auto unsigned_y = static_cast<std::uint32_t>(cpos.y);
+ const auto unsigned_z = static_cast<std::uint32_t>(cpos.z);
+ return std::format("{}/{:08X}-{:08X}-{:08X}.zvox", metadata->zvox_dir, unsigned_x, unsigned_y, unsigned_z);
+}
+
+static void add_new_dimension(world::Dimension* dimension)
+{
+ if(globals::dimensions.count(std::string(dimension->get_name()))) {
+ spdlog::critical("universe: dimension named {} already exists", dimension->get_name());
+ std::terminate();
+ }
+
+ auto dimension_dir = std::format("{}/{}", universe_name.get(), dimension->get_name());
+
+ if(!PHYSFS_mkdir(dimension_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", dimension_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ auto metadata = new DimensionMetadata;
+ metadata->config_path = std::format("{}/dimension.conf", dimension_dir);
+ metadata->zvox_dir = std::format("{}/chunk", dimension_dir);
+
+ if(!PHYSFS_mkdir(metadata->zvox_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", metadata->zvox_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ globals::dimensions.insert_or_assign(std::string(dimension->get_name()), dimension);
+
+ auto& mapped_metadata = metadata_map.insert_or_assign(dimension, metadata).first->second;
+
+ dimension->init(mapped_metadata->config);
+
+ mapped_metadata->config.load_file(mapped_metadata->config_path.c_str());
+
+ dimension->init_late(universe_config_seed.get_value());
+}
+
+static void internal_save_chunk(const DimensionMetadata* metadata, const world::Dimension* dimension, const chunk_pos& cpos,
+ const world::Chunk* chunk)
+{
+ auto path = make_chunk_filename(metadata, cpos);
+
+ io::WriteBuffer buffer;
+ chunk->get_voxels().serialize(buffer);
+
+ if(auto file = buffer.to_file(path.c_str())) {
+ PHYSFS_close(file);
+ return;
+ }
+}
+
+void world::universe::init(void)
+{
+ // If the world is newly created, the seed will
+ // be chosed based on the current system's view on UNIX time
+ universe_config_seed.set_value(utils::unix_microseconds());
+
+ // We're going to read files from directory named with
+ // the value of this config value. Since config is also
+ // read from command line, the [--universe <name>] parameter still works
+ globals::server_config.add_value("universe", universe_name);
+
+ universe_config.add_value("global_seed", universe_config_seed);
+ universe_config.add_value("spawn_dimension", universe_spawn_dimension);
+}
+
+void world::universe::init_late(void)
+{
+ const auto universe_dir = std::string(universe_name.get());
+
+ if(!PHYSFS_mkdir(universe_dir.c_str())) {
+ spdlog::critical("universe: {}: {}", universe_dir, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ universe_config_path = std::format("{}/universe.conf", universe_dir);
+ universe_config.load_file(universe_config_path.c_str());
+
+ add_new_dimension(new Overworld("world"));
+
+ // UNDONE: lua scripts to setup dimensions
+ if(globals::dimensions.empty()) {
+ spdlog::critical("universe: no dimensions");
+ std::terminate();
+ }
+
+ auto spawn_dimension = globals::dimensions.find(universe_spawn_dimension.get_value());
+
+ if(spawn_dimension == globals::dimensions.cend()) {
+ spdlog::critical("universe: {} is not a valid dimension name", universe_spawn_dimension.get());
+ std::terminate();
+ }
+
+ globals::spawn_dimension = spawn_dimension->second;
+}
+
+void world::universe::shutdown(void)
+{
+ for(const auto metadata : metadata_map) {
+ metadata.second->config.save_file(metadata.second->config_path.c_str());
+ delete metadata.second;
+ }
+
+ metadata_map.clear();
+
+ for(const auto dimension : globals::dimensions) {
+ world::universe::save_all_chunks(dimension.second);
+ delete dimension.second;
+ }
+
+ globals::dimensions.clear();
+ globals::spawn_dimension = nullptr;
+
+ universe_config.save_file(universe_config_path.c_str());
+}
+
+world::Chunk* world::universe::load_chunk(Dimension* dimension, const chunk_pos& cpos)
+{
+ if(auto chunk = dimension->find_chunk(cpos)) {
+ // Just return the existing chunk which is
+ // most probable to be up to date compared to
+ // whatever the hell is currently stored on disk
+ return chunk;
+ }
+
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // The dimension is for sure a weird one
+ return nullptr;
+ }
+
+ if(auto file = PHYSFS_openRead(make_chunk_filename(metadata->second, cpos).c_str())) {
+ VoxelStorage voxels;
+ io::ReadBuffer buffer(file);
+ voxels.deserialize(buffer);
+
+ PHYSFS_close(file);
+
+ auto chunk = dimension->create_chunk(cpos);
+ chunk->set_voxels(voxels);
+
+ // Make sure we're going to save it later
+ dimension->chunks.emplace_or_replace<Inhabited>(chunk->get_entity());
+
+ return chunk;
+ }
+
+ return nullptr;
+}
+
+void world::universe::save_chunk(Dimension* dimension, const chunk_pos& cpos)
+{
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // Cannot save a chunk in a dimension
+ // that doesn't have a metadata struct
+ return;
+ }
+
+ if(auto chunk = dimension->find_chunk(cpos)) {
+ internal_save_chunk(metadata->second, dimension, cpos, chunk);
+ }
+}
+
+void world::universe::save_all_chunks(Dimension* dimension)
+{
+ auto group = dimension->chunks.group(entt::get<ChunkComponent, Inhabited>);
+ auto metadata = metadata_map.find(dimension);
+
+ if(metadata == metadata_map.cend()) {
+ // Cannot save a chunk in a dimension
+ // that doesn't have a metadata struct
+ return;
+ }
+
+ for(auto [entity, chunk] : group.each()) {
+ internal_save_chunk(metadata->second, dimension, chunk.cpos, chunk.chunk);
+ }
+}
diff --git a/game/server/world/universe.hh b/game/server/world/universe.hh index 285911e..966ac70 100644 --- a/game/server/world/universe.hh +++ b/game/server/world/universe.hh @@ -1,25 +1,25 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Chunk; -class Dimension; -} // namespace world - -class Session; - -namespace world::universe -{ -void init(void); -void init_late(void); -void shutdown(void); -} // namespace world::universe - -namespace world::universe -{ -Chunk* load_chunk(Dimension* dimension, const chunk_pos& cpos); -void save_chunk(Dimension* dimension, const chunk_pos& cpos); -void save_all_chunks(Dimension* dimension); -} // namespace world::universe +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Chunk;
+class Dimension;
+} // namespace world
+
+class Session;
+
+namespace world::universe
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+} // namespace world::universe
+
+namespace world::universe
+{
+Chunk* load_chunk(Dimension* dimension, const chunk_pos& cpos);
+void save_chunk(Dimension* dimension, const chunk_pos& cpos);
+void save_all_chunks(Dimension* dimension);
+} // namespace world::universe
diff --git a/game/server/world/unloader.cc b/game/server/world/unloader.cc index 4a3f4e1..371a96f 100644 --- a/game/server/world/unloader.cc +++ b/game/server/world/unloader.cc @@ -1,78 +1,78 @@ -#include "server/pch.hh" - -#include "server/world/unloader.hh" - -#include "core/config/number.hh" - -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/chunk_aabb.hh" -#include "shared/world/dimension.hh" - -#include "server/world/inhabited.hh" -#include "server/world/universe.hh" - -#include "server/game.hh" -#include "server/globals.hh" - -static void on_chunk_update(const world::ChunkUpdateEvent& event) -{ - event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity()); -} - -static void on_voxel_set(const world::VoxelSetEvent& event) -{ - event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity()); -} - -void world::unloader::init(void) -{ - globals::dispatcher.sink<world::ChunkUpdateEvent>().connect<&on_chunk_update>(); - globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>(); -} - -void world::unloader::init_late(void) -{ -} - -void world::unloader::fixed_update_late(Dimension* dimension) -{ - auto group = dimension->entities.group(entt::get<entity::Player, entity::Transform>); - auto boxes = std::vector<ChunkAABB>(); - - for(const auto [entity, transform] : group.each()) { - ChunkAABB aabb; - aabb.min = transform.chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - aabb.max = transform.chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value()); - boxes.push_back(aabb); - } - - auto view = dimension->chunks.view<ChunkComponent>(); - auto chunk_in_view = false; - - for(const auto [entity, chunk] : view.each()) { - chunk_in_view = false; - - for(const auto& aabb : boxes) { - if(aabb.contains(chunk.cpos)) { - chunk_in_view = true; - break; - } - } - - if(chunk_in_view) { - // The chunk is within view box of at least - // a single player; we shouldn't unload it now - continue; - } - - if(dimension->chunks.any_of<Inhabited>(entity)) { - // Only store inhabited chunks on disk - world::universe::save_chunk(dimension, chunk.cpos); - } - - dimension->remove_chunk(entity); - } -} +#include "server/pch.hh"
+
+#include "server/world/unloader.hh"
+
+#include "core/config/number.hh"
+
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/chunk_aabb.hh"
+#include "shared/world/dimension.hh"
+
+#include "server/world/inhabited.hh"
+#include "server/world/universe.hh"
+
+#include "server/game.hh"
+#include "server/globals.hh"
+
+static void on_chunk_update(const world::ChunkUpdateEvent& event)
+{
+ event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity());
+}
+
+static void on_voxel_set(const world::VoxelSetEvent& event)
+{
+ event.dimension->chunks.emplace_or_replace<world::Inhabited>(event.chunk->get_entity());
+}
+
+void world::unloader::init(void)
+{
+ globals::dispatcher.sink<world::ChunkUpdateEvent>().connect<&on_chunk_update>();
+ globals::dispatcher.sink<world::VoxelSetEvent>().connect<&on_voxel_set>();
+}
+
+void world::unloader::init_late(void)
+{
+}
+
+void world::unloader::fixed_update_late(Dimension* dimension)
+{
+ auto group = dimension->entities.group(entt::get<entity::Player, entity::Transform>);
+ auto boxes = std::vector<ChunkAABB>();
+
+ for(const auto [entity, transform] : group.each()) {
+ ChunkAABB aabb;
+ aabb.min = transform.chunk - static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ aabb.max = transform.chunk + static_cast<chunk_pos::value_type>(server_game::view_distance.get_value());
+ boxes.push_back(aabb);
+ }
+
+ auto view = dimension->chunks.view<ChunkComponent>();
+ auto chunk_in_view = false;
+
+ for(const auto [entity, chunk] : view.each()) {
+ chunk_in_view = false;
+
+ for(const auto& aabb : boxes) {
+ if(aabb.contains(chunk.cpos)) {
+ chunk_in_view = true;
+ break;
+ }
+ }
+
+ if(chunk_in_view) {
+ // The chunk is within view box of at least
+ // a single player; we shouldn't unload it now
+ continue;
+ }
+
+ if(dimension->chunks.any_of<Inhabited>(entity)) {
+ // Only store inhabited chunks on disk
+ world::universe::save_chunk(dimension, chunk.cpos);
+ }
+
+ dimension->remove_chunk(entity);
+ }
+}
diff --git a/game/server/world/unloader.hh b/game/server/world/unloader.hh index 9682de6..a5b1da1 100644 --- a/game/server/world/unloader.hh +++ b/game/server/world/unloader.hh @@ -1,13 +1,13 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace world::unloader -{ -void init(void); -void init_late(void); -void fixed_update_late(Dimension* dimension); -} // namespace world::unloader +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace world::unloader
+{
+void init(void);
+void init_late(void);
+void fixed_update_late(Dimension* dimension);
+} // namespace world::unloader
diff --git a/game/server/world/worldgen.cc b/game/server/world/worldgen.cc index 8b02b52..3d8154c 100644 --- a/game/server/world/worldgen.cc +++ b/game/server/world/worldgen.cc @@ -1,151 +1,151 @@ -#include "server/pch.hh" - -#include "server/world/worldgen.hh" - -#include "core/io/cmdline.hh" - -#include "core/threading.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "shared/protocol.hh" - -#include "server/world/inhabited.hh" - -#include "server/globals.hh" -#include "server/sessions.hh" - -static bool aggressive_caching; - -static emhash8::HashMap<world::Dimension*, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>> active_tasks; - -class WorldgenTask final : public Task { -public: - explicit WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos); - virtual ~WorldgenTask(void) = default; - virtual void process(void) override; - virtual void finalize(void) override; - -private: - world::Dimension* m_dimension; - world::VoxelStorage m_voxels; - chunk_pos m_cpos; -}; - -WorldgenTask::WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos) -{ - m_dimension = dimension; - m_voxels.fill(rand()); // trolling - m_cpos = cpos; -} - -void WorldgenTask::process(void) -{ - if(!m_dimension->generate(m_cpos, m_voxels)) { - set_status(task_status::CANCELLED); - } -} - -void WorldgenTask::finalize(void) -{ - auto dim_tasks = active_tasks.find(m_dimension); - - if(dim_tasks == active_tasks.cend()) { - // Normally this should never happen but - // one can never be sure about anything - // when that anything is threaded out - return; - } - - auto it = dim_tasks->second.find(m_cpos); - - if(it == dim_tasks->second.cend()) { - // Normally this should never happen but - // one can never be sure about anything - // when that anything is threaded out - return; - } - - auto chunk = m_dimension->create_chunk(m_cpos); - chunk->set_voxels(m_voxels); - - if(aggressive_caching) { - // Marking the chunk with InhabitedComponent makes - // it so that it is saved regardles of whether it was - // modified by players or not. This isn't particularly - // good for server-side disk usage but it might improve performance - m_dimension->chunks.emplace<world::Inhabited>(chunk->get_entity()); - } - - protocol::ChunkVoxels response; - response.voxels = m_voxels; - response.chunk = m_cpos; - - auto packet = protocol::encode(response); - - for(auto session : it->second) { - if(session->peer) { - // Respond with the voxels to every session - // that has requested this specific chunk for this dimension - enet_peer_send(session->peer, protocol::CHANNEL, packet); - } - } - - dim_tasks->second.erase(it); - - if(dim_tasks->second.empty()) { - // There are no more requests - // to generate a chunk for that - // dimension, at least for now - active_tasks.erase(dim_tasks); - } -} - -void world::worldgen::init(void) -{ - aggressive_caching = io::cmdline::contains("aggressive-caching"); -} - -bool world::worldgen::is_generating(Dimension* dimension, const chunk_pos& cpos) -{ - auto dim_tasks = active_tasks.find(dimension); - - if(dim_tasks == active_tasks.cend()) { - // No tasks for this dimension - return false; - } - - auto it = dim_tasks->second.find(cpos); - - if(it == dim_tasks->second.cend()) { - // Not generating this chunk - return false; - } - - return true; -} - -void world::worldgen::request_chunk(Session* session, const chunk_pos& cpos) -{ - if(session->dimension) { - auto dim_tasks = active_tasks.find(session->dimension); - - if(dim_tasks == active_tasks.cend()) { - dim_tasks = active_tasks.emplace(session->dimension, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>()).first; - } - - auto it = dim_tasks->second.find(cpos); - - if(it == dim_tasks->second.cend()) { - auto& sessions = dim_tasks->second.insert_or_assign(cpos, std::unordered_set<Session*>()).first->second; - sessions.insert(session); - - threading::submit<WorldgenTask>(session->dimension, cpos); - - return; - } - - it->second.insert(session); - } -} +#include "server/pch.hh"
+
+#include "server/world/worldgen.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/threading.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/protocol.hh"
+
+#include "server/world/inhabited.hh"
+
+#include "server/globals.hh"
+#include "server/sessions.hh"
+
+static bool aggressive_caching;
+
+static emhash8::HashMap<world::Dimension*, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>> active_tasks;
+
+class WorldgenTask final : public Task {
+public:
+ explicit WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos);
+ virtual ~WorldgenTask(void) = default;
+ virtual void process(void) override;
+ virtual void finalize(void) override;
+
+private:
+ world::Dimension* m_dimension;
+ world::VoxelStorage m_voxels;
+ chunk_pos m_cpos;
+};
+
+WorldgenTask::WorldgenTask(world::Dimension* dimension, const chunk_pos& cpos)
+{
+ m_dimension = dimension;
+ m_voxels.fill(rand()); // trolling
+ m_cpos = cpos;
+}
+
+void WorldgenTask::process(void)
+{
+ if(!m_dimension->generate(m_cpos, m_voxels)) {
+ set_status(task_status::CANCELLED);
+ }
+}
+
+void WorldgenTask::finalize(void)
+{
+ auto dim_tasks = active_tasks.find(m_dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ // Normally this should never happen but
+ // one can never be sure about anything
+ // when that anything is threaded out
+ return;
+ }
+
+ auto it = dim_tasks->second.find(m_cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ // Normally this should never happen but
+ // one can never be sure about anything
+ // when that anything is threaded out
+ return;
+ }
+
+ auto chunk = m_dimension->create_chunk(m_cpos);
+ chunk->set_voxels(m_voxels);
+
+ if(aggressive_caching) {
+ // Marking the chunk with InhabitedComponent makes
+ // it so that it is saved regardles of whether it was
+ // modified by players or not. This isn't particularly
+ // good for server-side disk usage but it might improve performance
+ m_dimension->chunks.emplace<world::Inhabited>(chunk->get_entity());
+ }
+
+ protocol::ChunkVoxels response;
+ response.voxels = m_voxels;
+ response.chunk = m_cpos;
+
+ auto packet = protocol::encode(response);
+
+ for(auto session : it->second) {
+ if(session->peer) {
+ // Respond with the voxels to every session
+ // that has requested this specific chunk for this dimension
+ enet_peer_send(session->peer, protocol::CHANNEL, packet);
+ }
+ }
+
+ dim_tasks->second.erase(it);
+
+ if(dim_tasks->second.empty()) {
+ // There are no more requests
+ // to generate a chunk for that
+ // dimension, at least for now
+ active_tasks.erase(dim_tasks);
+ }
+}
+
+void world::worldgen::init(void)
+{
+ aggressive_caching = io::cmdline::contains("aggressive-caching");
+}
+
+bool world::worldgen::is_generating(Dimension* dimension, const chunk_pos& cpos)
+{
+ auto dim_tasks = active_tasks.find(dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ // No tasks for this dimension
+ return false;
+ }
+
+ auto it = dim_tasks->second.find(cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ // Not generating this chunk
+ return false;
+ }
+
+ return true;
+}
+
+void world::worldgen::request_chunk(Session* session, const chunk_pos& cpos)
+{
+ if(session->dimension) {
+ auto dim_tasks = active_tasks.find(session->dimension);
+
+ if(dim_tasks == active_tasks.cend()) {
+ dim_tasks = active_tasks.emplace(session->dimension, emhash8::HashMap<chunk_pos, std::unordered_set<Session*>>()).first;
+ }
+
+ auto it = dim_tasks->second.find(cpos);
+
+ if(it == dim_tasks->second.cend()) {
+ auto& sessions = dim_tasks->second.insert_or_assign(cpos, std::unordered_set<Session*>()).first->second;
+ sessions.insert(session);
+
+ threading::submit<WorldgenTask>(session->dimension, cpos);
+
+ return;
+ }
+
+ it->second.insert(session);
+ }
+}
diff --git a/game/server/world/worldgen.hh b/game/server/world/worldgen.hh index 8ffec36..eeb3e19 100644 --- a/game/server/world/worldgen.hh +++ b/game/server/world/worldgen.hh @@ -1,21 +1,21 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Dimension; -} // namespace world - -class Session; - -namespace world::worldgen -{ -void init(void); -} // namespace world::worldgen - -namespace world::worldgen -{ -bool is_generating(Dimension* dimension, const chunk_pos& cpos); -void request_chunk(Session* session, const chunk_pos& cpos); -} // namespace world::worldgen +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+class Session;
+
+namespace world::worldgen
+{
+void init(void);
+} // namespace world::worldgen
+
+namespace world::worldgen
+{
+bool is_generating(Dimension* dimension, const chunk_pos& cpos);
+void request_chunk(Session* session, const chunk_pos& cpos);
+} // namespace world::worldgen
diff --git a/game/shared/const.hh b/game/shared/const.hh index 187962a..6851ff7 100644 --- a/game/shared/const.hh +++ b/game/shared/const.hh @@ -1,43 +1,43 @@ -#pragma once - -#include "core/math/constexpr.hh" - -constexpr static unsigned int CHUNK_SIZE = 16; -constexpr static unsigned int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; -constexpr static unsigned int CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; -constexpr static unsigned int CHUNK_BITSHIFT = math::log2(CHUNK_SIZE); - -template<typename T> -constexpr static glm::vec<3, T> DIR_NORTH = glm::vec<3, T>(0, 0, +1); -template<typename T> -constexpr static glm::vec<3, T> DIR_SOUTH = glm::vec<3, T>(0, 0, -1); -template<typename T> -constexpr static glm::vec<3, T> DIR_EAST = glm::vec<3, T>(-1, 0, 0); -template<typename T> -constexpr static glm::vec<3, T> DIR_WEST = glm::vec<3, T>(+1, 0, 0); -template<typename T> -constexpr static glm::vec<3, T> DIR_DOWN = glm::vec<3, T>(0, -1, 0); -template<typename T> -constexpr static glm::vec<3, T> DIR_UP = glm::vec<3, T>(0, +1, 0); - -template<typename T> -constexpr static glm::vec<3, T> DIR_FORWARD = glm::vec<3, T>(0, 0, +1); -template<typename T> -constexpr static glm::vec<3, T> DIR_BACK = glm::vec<3, T>(0, 0, -1); -template<typename T> -constexpr static glm::vec<3, T> DIR_LEFT = glm::vec<3, T>(-1, 0, 0); -template<typename T> -constexpr static glm::vec<3, T> DIR_RIGHT = glm::vec<3, T>(+1, 0, 0); - -template<typename T> -constexpr static glm::vec<3, T> UNIT_X = glm::vec<3, T>(1, 0, 0); -template<typename T> -constexpr static glm::vec<3, T> UNIT_Y = glm::vec<3, T>(0, 1, 0); -template<typename T> -constexpr static glm::vec<3, T> UNIT_Z = glm::vec<3, T>(0, 0, 1); - -template<typename T> -constexpr static glm::vec<2, T> ZERO_VEC2 = glm::vec<2, T>(0, 0); - -template<typename T> -constexpr static glm::vec<3, T> ZERO_VEC3 = glm::vec<3, T>(0, 0, 0); +#pragma once
+
+#include "core/math/constexpr.hh"
+
+constexpr static unsigned int CHUNK_SIZE = 16;
+constexpr static unsigned int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE;
+constexpr static unsigned int CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
+constexpr static unsigned int CHUNK_BITSHIFT = math::log2(CHUNK_SIZE);
+
+template<typename T>
+constexpr static glm::vec<3, T> DIR_NORTH = glm::vec<3, T>(0, 0, +1);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_SOUTH = glm::vec<3, T>(0, 0, -1);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_EAST = glm::vec<3, T>(-1, 0, 0);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_WEST = glm::vec<3, T>(+1, 0, 0);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_DOWN = glm::vec<3, T>(0, -1, 0);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_UP = glm::vec<3, T>(0, +1, 0);
+
+template<typename T>
+constexpr static glm::vec<3, T> DIR_FORWARD = glm::vec<3, T>(0, 0, +1);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_BACK = glm::vec<3, T>(0, 0, -1);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_LEFT = glm::vec<3, T>(-1, 0, 0);
+template<typename T>
+constexpr static glm::vec<3, T> DIR_RIGHT = glm::vec<3, T>(+1, 0, 0);
+
+template<typename T>
+constexpr static glm::vec<3, T> UNIT_X = glm::vec<3, T>(1, 0, 0);
+template<typename T>
+constexpr static glm::vec<3, T> UNIT_Y = glm::vec<3, T>(0, 1, 0);
+template<typename T>
+constexpr static glm::vec<3, T> UNIT_Z = glm::vec<3, T>(0, 0, 1);
+
+template<typename T>
+constexpr static glm::vec<2, T> ZERO_VEC2 = glm::vec<2, T>(0, 0);
+
+template<typename T>
+constexpr static glm::vec<3, T> ZERO_VEC3 = glm::vec<3, T>(0, 0, 0);
diff --git a/game/shared/coord.hh b/game/shared/coord.hh index 9d9be18..9f9afc7 100644 --- a/game/shared/coord.hh +++ b/game/shared/coord.hh @@ -1,145 +1,145 @@ -#pragma once - -#include "shared/const.hh" -#include "shared/types.hh" - -namespace coord -{ -constexpr chunk_pos to_chunk(const voxel_pos& vpos); -} // namespace coord - -namespace coord -{ -constexpr local_pos to_local(const voxel_pos& vpos); -constexpr local_pos to_local(const glm::fvec3& fvec); -constexpr local_pos to_local(std::size_t index); -} // namespace coord - -namespace coord -{ -constexpr voxel_pos to_voxel(const chunk_pos& cpos, const local_pos& lpos); -constexpr voxel_pos to_voxel(const chunk_pos& cpos, const glm::fvec3& fvec); -} // namespace coord - -namespace coord -{ -constexpr std::size_t to_index(const local_pos& lpos); -} // namespace coord - -namespace coord -{ -constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const chunk_pos& cpos, const glm::fvec3& fvec); -constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos); -constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos, const glm::fvec3& fvec); -} // namespace coord - -namespace coord -{ -constexpr glm::fvec3 to_fvec3(const chunk_pos& cpos); -constexpr glm::fvec3 to_fvec3(const chunk_pos& cpos, const glm::fvec3& fpos); -} // namespace coord - -inline constexpr chunk_pos coord::to_chunk(const voxel_pos& vpos) -{ - return chunk_pos { - static_cast<chunk_pos::value_type>(vpos.x >> CHUNK_BITSHIFT), - static_cast<chunk_pos::value_type>(vpos.y >> CHUNK_BITSHIFT), - static_cast<chunk_pos::value_type>(vpos.z >> CHUNK_BITSHIFT), - }; -} - -inline constexpr local_pos coord::to_local(const voxel_pos& vpos) -{ - return local_pos { - static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.x, CHUNK_SIZE)), - static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.y, CHUNK_SIZE)), - static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.z, CHUNK_SIZE)), - }; -} - -inline constexpr local_pos coord::to_local(const glm::fvec3& fvec) -{ - return local_pos { - static_cast<local_pos::value_type>(fvec.x), - static_cast<local_pos::value_type>(fvec.y), - static_cast<local_pos::value_type>(fvec.z), - }; -} - -inline constexpr local_pos coord::to_local(std::size_t index) -{ - return local_pos { - static_cast<local_pos::value_type>((index % CHUNK_SIZE)), - static_cast<local_pos::value_type>((index / CHUNK_SIZE) / CHUNK_SIZE), - static_cast<local_pos::value_type>((index / CHUNK_SIZE) % CHUNK_SIZE), - }; -} - -inline constexpr voxel_pos coord::to_voxel(const chunk_pos& cpos, const local_pos& lpos) -{ - return voxel_pos { - lpos.x + (static_cast<voxel_pos::value_type>(cpos.x) << CHUNK_BITSHIFT), - lpos.y + (static_cast<voxel_pos::value_type>(cpos.y) << CHUNK_BITSHIFT), - lpos.z + (static_cast<voxel_pos::value_type>(cpos.z) << CHUNK_BITSHIFT), - }; -} - -inline constexpr voxel_pos coord::to_voxel(const chunk_pos& cpos, const glm::fvec3& fvec) -{ - return voxel_pos { - static_cast<voxel_pos::value_type>(fvec.x) + (static_cast<voxel_pos::value_type>(cpos.x) << CHUNK_BITSHIFT), - static_cast<voxel_pos::value_type>(fvec.y) + (static_cast<voxel_pos::value_type>(cpos.y) << CHUNK_BITSHIFT), - static_cast<voxel_pos::value_type>(fvec.z) + (static_cast<voxel_pos::value_type>(cpos.z) << CHUNK_BITSHIFT), - }; -} - -inline constexpr std::size_t coord::to_index(const local_pos& lpos) -{ - return static_cast<std::size_t>((lpos.y * CHUNK_SIZE + lpos.z) * CHUNK_SIZE + lpos.x); -} - -inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const chunk_pos& cpos, const glm::fvec3& fvec) -{ - return glm::fvec3 { - static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + fvec.x, - static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + fvec.y, - static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + fvec.z, - }; -} - -inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos) -{ - return glm::fvec3 { - static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) - pivot_fvec.x, - static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) - pivot_fvec.y, - static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) - pivot_fvec.z, - }; -} - -inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos, - const glm::fvec3& fvec) -{ - return glm::fvec3 { - static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + (fvec.x - pivot_fvec.x), - static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + (fvec.y - pivot_fvec.y), - static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + (fvec.z - pivot_fvec.z), - }; -} - -inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos& cpos) -{ - return glm::fvec3 { - static_cast<float>(cpos.x << CHUNK_BITSHIFT), - static_cast<float>(cpos.y << CHUNK_BITSHIFT), - static_cast<float>(cpos.z << CHUNK_BITSHIFT), - }; -} - -inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos& cpos, const glm::fvec3& fpos) -{ - return glm::fvec3 { - fpos.x + static_cast<float>(cpos.x << CHUNK_BITSHIFT), - fpos.y + static_cast<float>(cpos.y << CHUNK_BITSHIFT), - fpos.z + static_cast<float>(cpos.z << CHUNK_BITSHIFT), - }; -} +#pragma once
+
+#include "shared/const.hh"
+#include "shared/types.hh"
+
+namespace coord
+{
+constexpr chunk_pos to_chunk(const voxel_pos& vpos);
+} // namespace coord
+
+namespace coord
+{
+constexpr local_pos to_local(const voxel_pos& vpos);
+constexpr local_pos to_local(const glm::fvec3& fvec);
+constexpr local_pos to_local(std::size_t index);
+} // namespace coord
+
+namespace coord
+{
+constexpr voxel_pos to_voxel(const chunk_pos& cpos, const local_pos& lpos);
+constexpr voxel_pos to_voxel(const chunk_pos& cpos, const glm::fvec3& fvec);
+} // namespace coord
+
+namespace coord
+{
+constexpr std::size_t to_index(const local_pos& lpos);
+} // namespace coord
+
+namespace coord
+{
+constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const chunk_pos& cpos, const glm::fvec3& fvec);
+constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos);
+constexpr glm::fvec3 to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos, const glm::fvec3& fvec);
+} // namespace coord
+
+namespace coord
+{
+constexpr glm::fvec3 to_fvec3(const chunk_pos& cpos);
+constexpr glm::fvec3 to_fvec3(const chunk_pos& cpos, const glm::fvec3& fpos);
+} // namespace coord
+
+inline constexpr chunk_pos coord::to_chunk(const voxel_pos& vpos)
+{
+ return chunk_pos {
+ static_cast<chunk_pos::value_type>(vpos.x >> CHUNK_BITSHIFT),
+ static_cast<chunk_pos::value_type>(vpos.y >> CHUNK_BITSHIFT),
+ static_cast<chunk_pos::value_type>(vpos.z >> CHUNK_BITSHIFT),
+ };
+}
+
+inline constexpr local_pos coord::to_local(const voxel_pos& vpos)
+{
+ return local_pos {
+ static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.x, CHUNK_SIZE)),
+ static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.y, CHUNK_SIZE)),
+ static_cast<local_pos::value_type>(math::mod_signed<voxel_pos::value_type>(vpos.z, CHUNK_SIZE)),
+ };
+}
+
+inline constexpr local_pos coord::to_local(const glm::fvec3& fvec)
+{
+ return local_pos {
+ static_cast<local_pos::value_type>(fvec.x),
+ static_cast<local_pos::value_type>(fvec.y),
+ static_cast<local_pos::value_type>(fvec.z),
+ };
+}
+
+inline constexpr local_pos coord::to_local(std::size_t index)
+{
+ return local_pos {
+ static_cast<local_pos::value_type>((index % CHUNK_SIZE)),
+ static_cast<local_pos::value_type>((index / CHUNK_SIZE) / CHUNK_SIZE),
+ static_cast<local_pos::value_type>((index / CHUNK_SIZE) % CHUNK_SIZE),
+ };
+}
+
+inline constexpr voxel_pos coord::to_voxel(const chunk_pos& cpos, const local_pos& lpos)
+{
+ return voxel_pos {
+ lpos.x + (static_cast<voxel_pos::value_type>(cpos.x) << CHUNK_BITSHIFT),
+ lpos.y + (static_cast<voxel_pos::value_type>(cpos.y) << CHUNK_BITSHIFT),
+ lpos.z + (static_cast<voxel_pos::value_type>(cpos.z) << CHUNK_BITSHIFT),
+ };
+}
+
+inline constexpr voxel_pos coord::to_voxel(const chunk_pos& cpos, const glm::fvec3& fvec)
+{
+ return voxel_pos {
+ static_cast<voxel_pos::value_type>(fvec.x) + (static_cast<voxel_pos::value_type>(cpos.x) << CHUNK_BITSHIFT),
+ static_cast<voxel_pos::value_type>(fvec.y) + (static_cast<voxel_pos::value_type>(cpos.y) << CHUNK_BITSHIFT),
+ static_cast<voxel_pos::value_type>(fvec.z) + (static_cast<voxel_pos::value_type>(cpos.z) << CHUNK_BITSHIFT),
+ };
+}
+
+inline constexpr std::size_t coord::to_index(const local_pos& lpos)
+{
+ return static_cast<std::size_t>((lpos.y * CHUNK_SIZE + lpos.z) * CHUNK_SIZE + lpos.x);
+}
+
+inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const chunk_pos& cpos, const glm::fvec3& fvec)
+{
+ return glm::fvec3 {
+ static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + fvec.x,
+ static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + fvec.y,
+ static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + fvec.z,
+ };
+}
+
+inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos)
+{
+ return glm::fvec3 {
+ static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) - pivot_fvec.x,
+ static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) - pivot_fvec.y,
+ static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) - pivot_fvec.z,
+ };
+}
+
+inline constexpr glm::fvec3 coord::to_relative(const chunk_pos& pivot_cpos, const glm::fvec3& pivot_fvec, const chunk_pos& cpos,
+ const glm::fvec3& fvec)
+{
+ return glm::fvec3 {
+ static_cast<float>((cpos.x - pivot_cpos.x) << CHUNK_BITSHIFT) + (fvec.x - pivot_fvec.x),
+ static_cast<float>((cpos.y - pivot_cpos.y) << CHUNK_BITSHIFT) + (fvec.y - pivot_fvec.y),
+ static_cast<float>((cpos.z - pivot_cpos.z) << CHUNK_BITSHIFT) + (fvec.z - pivot_fvec.z),
+ };
+}
+
+inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos& cpos)
+{
+ return glm::fvec3 {
+ static_cast<float>(cpos.x << CHUNK_BITSHIFT),
+ static_cast<float>(cpos.y << CHUNK_BITSHIFT),
+ static_cast<float>(cpos.z << CHUNK_BITSHIFT),
+ };
+}
+
+inline constexpr glm::fvec3 coord::to_fvec3(const chunk_pos& cpos, const glm::fvec3& fpos)
+{
+ return glm::fvec3 {
+ fpos.x + static_cast<float>(cpos.x << CHUNK_BITSHIFT),
+ fpos.y + static_cast<float>(cpos.y << CHUNK_BITSHIFT),
+ fpos.z + static_cast<float>(cpos.z << CHUNK_BITSHIFT),
+ };
+}
diff --git a/game/shared/entity/collision.cc b/game/shared/entity/collision.cc index 4346b68..dbe7d30 100644 --- a/game/shared/entity/collision.cc +++ b/game/shared/entity/collision.cc @@ -1,175 +1,175 @@ -#include "shared/pch.hh" - -#include "shared/entity/collision.hh" - -#include "core/math/constexpr.hh" - -#include "shared/entity/gravity.hh" -#include "shared/entity/grounded.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" -#include "shared/world/voxel_registry.hh" - -#include "shared/coord.hh" -#include "shared/globals.hh" - -static int vgrid_collide(const world::Dimension* dimension, int d, entity::Collision& collision, entity::Transform& transform, - entity::Velocity& velocity, world::voxel_surface& touch_surface) -{ - const auto move = globals::fixed_frametime * velocity.value[d]; - const auto move_sign = math::sign<int>(move); - - const auto& ref_aabb = collision.aabb; - const auto current_aabb = ref_aabb.push(transform.local); - - auto next_aabb = math::AABB(current_aabb); - next_aabb.min[d] += move; - next_aabb.max[d] += move; - - local_pos lpos_min; - lpos_min.x = math::floor<local_pos::value_type>(next_aabb.min.x); - lpos_min.y = math::floor<local_pos::value_type>(next_aabb.min.y); - lpos_min.z = math::floor<local_pos::value_type>(next_aabb.min.z); - - local_pos lpos_max; - lpos_max.x = math::ceil<local_pos::value_type>(next_aabb.max.x); - lpos_max.y = math::ceil<local_pos::value_type>(next_aabb.max.y); - lpos_max.z = math::ceil<local_pos::value_type>(next_aabb.max.z); - - // Other axes - const int u = (d + 1) % 3; - const int v = (d + 2) % 3; - - local_pos::value_type ddir; - local_pos::value_type dmin; - local_pos::value_type dmax; - - if(move < 0.0f) { - ddir = local_pos::value_type(+1); - dmin = lpos_min[d]; - dmax = lpos_max[d]; - } - else { - ddir = local_pos::value_type(-1); - dmin = lpos_max[d]; - dmax = lpos_min[d]; - } - - world::voxel_touch latch_touch = world::voxel_touch::NOTHING; - glm::fvec3 latch_values = glm::fvec3(0.0f, 0.0f, 0.0f); - world::voxel_surface latch_surface = world::voxel_surface::UNKNOWN; - math::AABB latch_vbox; - - for(auto i = dmin; i != dmax; i += ddir) { - for(auto j = lpos_min[u]; j < lpos_max[u]; ++j) - for(auto k = lpos_min[v]; k < lpos_max[v]; ++k) { - local_pos lpos; - lpos[d] = i; - lpos[u] = j; - lpos[v] = k; - - const auto vpos = coord::to_voxel(transform.chunk, lpos); - const auto info = world::voxel_registry::find(dimension->get_voxel(vpos)); - - if(info == nullptr) { - // Don't collide with something - // that we assume to be nothing - continue; - } - - math::AABB vbox; - vbox.min = glm::fvec3(lpos); - vbox.max = glm::fvec3(lpos) + 1.0f; - - if(!next_aabb.intersect(vbox)) { - // No intersection between the voxel - // and the entity's collision hull - continue; - } - - if(info->touch_type == world::voxel_touch::SOLID) { - // Solid touch type makes a collision - // response whenever it is encountered - velocity.value[d] = 0.0f; - touch_surface = info->surface; - return move_sign; - } - - // In case of other touch types, they - // are latched and the last ever touch - // type is then responded to - if(info->touch_type != world::voxel_touch::NOTHING) { - latch_touch = info->touch_type; - latch_values = info->touch_values; - latch_surface = info->surface; - latch_vbox = vbox; - continue; - } - } - } - - if(latch_touch != world::voxel_touch::NOTHING) { - if(latch_touch == world::voxel_touch::BOUNCE) { - const auto move_distance = math::abs(current_aabb.min[d] - next_aabb.min[d]); - const auto threshold = 2.0f * globals::fixed_frametime; - - if(move_distance > threshold) { - velocity.value[d] *= -latch_values[d]; - } - else { - velocity.value[d] = 0.0f; - } - - touch_surface = latch_surface; - - return move_sign; - } - - if(latch_touch == world::voxel_touch::SINK) { - velocity.value[d] *= latch_values[d]; - touch_surface = latch_surface; - return move_sign; - } - } - - return 0; -} - -void entity::Collision::fixed_update(world::Dimension* dimension) -{ - // FIXME: this isn't particularly accurate considering - // some voxels might be passable and some other voxels - // might apply some slowing factor; what I might do in the - // future is to add a specific value to the voxel registry - // entries that would specify the amount of force we apply - // to prevent player movement inside a specific voxel, plus - // we shouldn't treat all voxels as full cubes if we want - // to support slabs, stairs and non-full liquid voxels in the future - - auto group = dimension->entities.group<entity::Collision>(entt::get<entity::Transform, entity::Velocity>); - - for(auto [entity, collision, transform, velocity] : group.each()) { - auto surface = world::voxel_surface::UNKNOWN; - auto vertical_move = vgrid_collide(dimension, 1, collision, transform, velocity, surface); - - if(dimension->entities.any_of<entity::Gravity>(entity)) { - if(vertical_move == math::sign<int>(dimension->get_gravity())) { - dimension->entities.emplace_or_replace<entity::Grounded>(entity, entity::Grounded { surface }); - } - else { - dimension->entities.remove<entity::Grounded>(entity); - } - } - else { - // The entity cannot be grounded because the component - // setup of said entity should not let it comprehend the - // concept of resting on the ground (it flies around) - dimension->entities.remove<entity::Grounded>(entity); - } - - vgrid_collide(dimension, 0, collision, transform, velocity, surface); - vgrid_collide(dimension, 2, collision, transform, velocity, surface); - } -} +#include "shared/pch.hh"
+
+#include "shared/entity/collision.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "shared/entity/gravity.hh"
+#include "shared/entity/grounded.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+#include "shared/world/voxel_registry.hh"
+
+#include "shared/coord.hh"
+#include "shared/globals.hh"
+
+static int vgrid_collide(const world::Dimension* dimension, int d, entity::Collision& collision, entity::Transform& transform,
+ entity::Velocity& velocity, world::voxel_surface& touch_surface)
+{
+ const auto move = globals::fixed_frametime * velocity.value[d];
+ const auto move_sign = math::sign<int>(move);
+
+ const auto& ref_aabb = collision.aabb;
+ const auto current_aabb = ref_aabb.push(transform.local);
+
+ auto next_aabb = math::AABB(current_aabb);
+ next_aabb.min[d] += move;
+ next_aabb.max[d] += move;
+
+ local_pos lpos_min;
+ lpos_min.x = math::floor<local_pos::value_type>(next_aabb.min.x);
+ lpos_min.y = math::floor<local_pos::value_type>(next_aabb.min.y);
+ lpos_min.z = math::floor<local_pos::value_type>(next_aabb.min.z);
+
+ local_pos lpos_max;
+ lpos_max.x = math::ceil<local_pos::value_type>(next_aabb.max.x);
+ lpos_max.y = math::ceil<local_pos::value_type>(next_aabb.max.y);
+ lpos_max.z = math::ceil<local_pos::value_type>(next_aabb.max.z);
+
+ // Other axes
+ const int u = (d + 1) % 3;
+ const int v = (d + 2) % 3;
+
+ local_pos::value_type ddir;
+ local_pos::value_type dmin;
+ local_pos::value_type dmax;
+
+ if(move < 0.0f) {
+ ddir = local_pos::value_type(+1);
+ dmin = lpos_min[d];
+ dmax = lpos_max[d];
+ }
+ else {
+ ddir = local_pos::value_type(-1);
+ dmin = lpos_max[d];
+ dmax = lpos_min[d];
+ }
+
+ world::voxel_touch latch_touch = world::voxel_touch::NOTHING;
+ glm::fvec3 latch_values = glm::fvec3(0.0f, 0.0f, 0.0f);
+ world::voxel_surface latch_surface = world::voxel_surface::UNKNOWN;
+ math::AABB latch_vbox;
+
+ for(auto i = dmin; i != dmax; i += ddir) {
+ for(auto j = lpos_min[u]; j < lpos_max[u]; ++j)
+ for(auto k = lpos_min[v]; k < lpos_max[v]; ++k) {
+ local_pos lpos;
+ lpos[d] = i;
+ lpos[u] = j;
+ lpos[v] = k;
+
+ const auto vpos = coord::to_voxel(transform.chunk, lpos);
+ const auto info = world::voxel_registry::find(dimension->get_voxel(vpos));
+
+ if(info == nullptr) {
+ // Don't collide with something
+ // that we assume to be nothing
+ continue;
+ }
+
+ math::AABB vbox;
+ vbox.min = glm::fvec3(lpos);
+ vbox.max = glm::fvec3(lpos) + 1.0f;
+
+ if(!next_aabb.intersect(vbox)) {
+ // No intersection between the voxel
+ // and the entity's collision hull
+ continue;
+ }
+
+ if(info->touch_type == world::voxel_touch::SOLID) {
+ // Solid touch type makes a collision
+ // response whenever it is encountered
+ velocity.value[d] = 0.0f;
+ touch_surface = info->surface;
+ return move_sign;
+ }
+
+ // In case of other touch types, they
+ // are latched and the last ever touch
+ // type is then responded to
+ if(info->touch_type != world::voxel_touch::NOTHING) {
+ latch_touch = info->touch_type;
+ latch_values = info->touch_values;
+ latch_surface = info->surface;
+ latch_vbox = vbox;
+ continue;
+ }
+ }
+ }
+
+ if(latch_touch != world::voxel_touch::NOTHING) {
+ if(latch_touch == world::voxel_touch::BOUNCE) {
+ const auto move_distance = math::abs(current_aabb.min[d] - next_aabb.min[d]);
+ const auto threshold = 2.0f * globals::fixed_frametime;
+
+ if(move_distance > threshold) {
+ velocity.value[d] *= -latch_values[d];
+ }
+ else {
+ velocity.value[d] = 0.0f;
+ }
+
+ touch_surface = latch_surface;
+
+ return move_sign;
+ }
+
+ if(latch_touch == world::voxel_touch::SINK) {
+ velocity.value[d] *= latch_values[d];
+ touch_surface = latch_surface;
+ return move_sign;
+ }
+ }
+
+ return 0;
+}
+
+void entity::Collision::fixed_update(world::Dimension* dimension)
+{
+ // FIXME: this isn't particularly accurate considering
+ // some voxels might be passable and some other voxels
+ // might apply some slowing factor; what I might do in the
+ // future is to add a specific value to the voxel registry
+ // entries that would specify the amount of force we apply
+ // to prevent player movement inside a specific voxel, plus
+ // we shouldn't treat all voxels as full cubes if we want
+ // to support slabs, stairs and non-full liquid voxels in the future
+
+ auto group = dimension->entities.group<entity::Collision>(entt::get<entity::Transform, entity::Velocity>);
+
+ for(auto [entity, collision, transform, velocity] : group.each()) {
+ auto surface = world::voxel_surface::UNKNOWN;
+ auto vertical_move = vgrid_collide(dimension, 1, collision, transform, velocity, surface);
+
+ if(dimension->entities.any_of<entity::Gravity>(entity)) {
+ if(vertical_move == math::sign<int>(dimension->get_gravity())) {
+ dimension->entities.emplace_or_replace<entity::Grounded>(entity, entity::Grounded { surface });
+ }
+ else {
+ dimension->entities.remove<entity::Grounded>(entity);
+ }
+ }
+ else {
+ // The entity cannot be grounded because the component
+ // setup of said entity should not let it comprehend the
+ // concept of resting on the ground (it flies around)
+ dimension->entities.remove<entity::Grounded>(entity);
+ }
+
+ vgrid_collide(dimension, 0, collision, transform, velocity, surface);
+ vgrid_collide(dimension, 2, collision, transform, velocity, surface);
+ }
+}
diff --git a/game/shared/entity/collision.hh b/game/shared/entity/collision.hh index 33ddaac..ca94665 100644 --- a/game/shared/entity/collision.hh +++ b/game/shared/entity/collision.hh @@ -1,21 +1,21 @@ -#pragma once - -#include "core/math/aabb.hh" - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity -{ -struct Collision final { - math::AABB aabb; - -public: - // NOTE: entity::Collision::fixed_update must be called - // before entity::Transform::fixed_update and entity::Velocity::fixed_update - // because both transform and velocity may be updated internally - static void fixed_update(world::Dimension* dimension); -}; -} // namespace entity +#pragma once
+
+#include "core/math/aabb.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity
+{
+struct Collision final {
+ math::AABB aabb;
+
+public:
+ // NOTE: entity::Collision::fixed_update must be called
+ // before entity::Transform::fixed_update and entity::Velocity::fixed_update
+ // because both transform and velocity may be updated internally
+ static void fixed_update(world::Dimension* dimension);
+};
+} // namespace entity
diff --git a/game/shared/entity/factory.cc b/game/shared/entity/factory.cc index 619a418..ba95b73 100644 --- a/game/shared/entity/factory.cc +++ b/game/shared/entity/factory.cc @@ -1,37 +1,37 @@ -#include "shared/pch.hh" - -#include "shared/entity/factory.hh" - -#include "shared/entity/collision.hh" -#include "shared/entity/gravity.hh" -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/globals.hh" - -void entity::shared::create_player(world::Dimension* dimension, entt::entity entity) -{ - spdlog::debug("factory[{}]: assigning player components to {}", dimension->get_name(), static_cast<std::uint64_t>(entity)); - - auto& collision = dimension->entities.emplace_or_replace<entity::Collision>(entity); - collision.aabb.min = glm::fvec3(-0.4f, -1.6f, -0.4f); - collision.aabb.max = glm::fvec3(+0.4f, +0.2f, +0.4f); - - auto& head = dimension->entities.emplace_or_replace<entity::Head>(entity); - head.angles = glm::fvec3(0.0f, 0.0f, 0.0f); - head.offset = glm::fvec3(0.0f, 0.0f, 0.0f); - - dimension->entities.emplace_or_replace<entity::Player>(entity); - - auto& transform = dimension->entities.emplace_or_replace<entity::Transform>(entity); - transform.chunk = chunk_pos(0, 2, 0); - transform.local = glm::fvec3(0.0f, 0.0f, 0.0f); - transform.angles = glm::fvec3(0.0f, 0.0f, 0.0f); - - auto& velocity = dimension->entities.emplace_or_replace<entity::Velocity>(entity); - velocity.value = glm::fvec3(0.0f, 0.0f, 0.0f); -} +#include "shared/pch.hh"
+
+#include "shared/entity/factory.hh"
+
+#include "shared/entity/collision.hh"
+#include "shared/entity/gravity.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/globals.hh"
+
+void entity::shared::create_player(world::Dimension* dimension, entt::entity entity)
+{
+ spdlog::debug("factory[{}]: assigning player components to {}", dimension->get_name(), static_cast<std::uint64_t>(entity));
+
+ auto& collision = dimension->entities.emplace_or_replace<entity::Collision>(entity);
+ collision.aabb.min = glm::fvec3(-0.4f, -1.6f, -0.4f);
+ collision.aabb.max = glm::fvec3(+0.4f, +0.2f, +0.4f);
+
+ auto& head = dimension->entities.emplace_or_replace<entity::Head>(entity);
+ head.angles = glm::fvec3(0.0f, 0.0f, 0.0f);
+ head.offset = glm::fvec3(0.0f, 0.0f, 0.0f);
+
+ dimension->entities.emplace_or_replace<entity::Player>(entity);
+
+ auto& transform = dimension->entities.emplace_or_replace<entity::Transform>(entity);
+ transform.chunk = chunk_pos(0, 2, 0);
+ transform.local = glm::fvec3(0.0f, 0.0f, 0.0f);
+ transform.angles = glm::fvec3(0.0f, 0.0f, 0.0f);
+
+ auto& velocity = dimension->entities.emplace_or_replace<entity::Velocity>(entity);
+ velocity.value = glm::fvec3(0.0f, 0.0f, 0.0f);
+}
diff --git a/game/shared/entity/factory.hh b/game/shared/entity/factory.hh index e709060..be33045 100644 --- a/game/shared/entity/factory.hh +++ b/game/shared/entity/factory.hh @@ -1,11 +1,11 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity::shared -{ -void create_player(world::Dimension* dimension, entt::entity entity); -} // namespace entity::shared +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity::shared
+{
+void create_player(world::Dimension* dimension, entt::entity entity);
+} // namespace entity::shared
diff --git a/game/shared/entity/gravity.cc b/game/shared/entity/gravity.cc index f1708aa..09bc379 100644 --- a/game/shared/entity/gravity.cc +++ b/game/shared/entity/gravity.cc @@ -1,20 +1,20 @@ -#include "shared/pch.hh" - -#include "shared/entity/gravity.hh" - -#include "shared/entity/stasis.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/globals.hh" - -void entity::Gravity::fixed_update(world::Dimension* dimension) -{ - auto fixed_acceleration = globals::fixed_frametime * dimension->get_gravity(); - auto group = dimension->entities.group<entity::Gravity>(entt::get<entity::Velocity>, entt::exclude<entity::Stasis>); - - for(auto [entity, velocity] : group.each()) { - velocity.value.y += fixed_acceleration; - } -} +#include "shared/pch.hh"
+
+#include "shared/entity/gravity.hh"
+
+#include "shared/entity/stasis.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/globals.hh"
+
+void entity::Gravity::fixed_update(world::Dimension* dimension)
+{
+ auto fixed_acceleration = globals::fixed_frametime * dimension->get_gravity();
+ auto group = dimension->entities.group<entity::Gravity>(entt::get<entity::Velocity>, entt::exclude<entity::Stasis>);
+
+ for(auto [entity, velocity] : group.each()) {
+ velocity.value.y += fixed_acceleration;
+ }
+}
diff --git a/game/shared/entity/gravity.hh b/game/shared/entity/gravity.hh index 21f4582..2cbcbbc 100644 --- a/game/shared/entity/gravity.hh +++ b/game/shared/entity/gravity.hh @@ -1,14 +1,14 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity -{ -struct Gravity final { -public: - static void fixed_update(world::Dimension* dimension); -}; -} // namespace entity +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity
+{
+struct Gravity final {
+public:
+ static void fixed_update(world::Dimension* dimension);
+};
+} // namespace entity
diff --git a/game/shared/entity/grounded.hh b/game/shared/entity/grounded.hh index 22340db..e86e3a8 100644 --- a/game/shared/entity/grounded.hh +++ b/game/shared/entity/grounded.hh @@ -1,16 +1,16 @@ -#ifndef SHARED_ENTITY_GROUNDED -#define SHARED_ENTITY_GROUNDED 1 -#pragma once - -#include "shared/world/voxel_registry.hh" - -namespace entity -{ -// Assigned to entities which are grounded -// according to the collision and gravity system -struct Grounded final { - world::voxel_surface surface; -}; -} // namespace entity - -#endif // SHARED_ENTITY_GROUNDED +#ifndef SHARED_ENTITY_GROUNDED
+#define SHARED_ENTITY_GROUNDED 1
+#pragma once
+
+#include "shared/world/voxel_registry.hh"
+
+namespace entity
+{
+// Assigned to entities which are grounded
+// according to the collision and gravity system
+struct Grounded final {
+ world::voxel_surface surface;
+};
+} // namespace entity
+
+#endif // SHARED_ENTITY_GROUNDED
diff --git a/game/shared/entity/head.hh b/game/shared/entity/head.hh index 3f5d6d1..15ce7e5 100644 --- a/game/shared/entity/head.hh +++ b/game/shared/entity/head.hh @@ -1,16 +1,16 @@ -#pragma once - -namespace entity -{ -struct Head { - glm::fvec3 angles; - glm::fvec3 offset; -}; -} // namespace entity - -namespace entity::client -{ -// Client-side only - interpolated and previous head -struct HeadIntr final : public Head {}; -struct HeadPrev final : public Head {}; -} // namespace entity::client +#pragma once
+
+namespace entity
+{
+struct Head {
+ glm::fvec3 angles;
+ glm::fvec3 offset;
+};
+} // namespace entity
+
+namespace entity::client
+{
+// Client-side only - interpolated and previous head
+struct HeadIntr final : public Head {};
+struct HeadPrev final : public Head {};
+} // namespace entity::client
diff --git a/game/shared/entity/player.hh b/game/shared/entity/player.hh index 4bd0217..abffd85 100644 --- a/game/shared/entity/player.hh +++ b/game/shared/entity/player.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace entity -{ -struct Player final {}; -} // namespace entity +#pragma once
+
+namespace entity
+{
+struct Player final {};
+} // namespace entity
diff --git a/game/shared/entity/stasis.cc b/game/shared/entity/stasis.cc index 3b86294..eab8744 100644 --- a/game/shared/entity/stasis.cc +++ b/game/shared/entity/stasis.cc @@ -1,21 +1,21 @@ -#include "shared/pch.hh" - -#include "shared/entity/stasis.hh" - -#include "shared/entity/transform.hh" - -#include "shared/world/dimension.hh" - -void entity::Stasis::fixed_update(world::Dimension* dimension) -{ - auto view = dimension->entities.view<entity::Transform>(); - - for(auto [entity, transform] : view.each()) { - if(dimension->find_chunk(transform.chunk)) { - dimension->entities.remove<entity::Stasis>(entity); - } - else { - dimension->entities.emplace_or_replace<entity::Stasis>(entity); - } - } -} +#include "shared/pch.hh"
+
+#include "shared/entity/stasis.hh"
+
+#include "shared/entity/transform.hh"
+
+#include "shared/world/dimension.hh"
+
+void entity::Stasis::fixed_update(world::Dimension* dimension)
+{
+ auto view = dimension->entities.view<entity::Transform>();
+
+ for(auto [entity, transform] : view.each()) {
+ if(dimension->find_chunk(transform.chunk)) {
+ dimension->entities.remove<entity::Stasis>(entity);
+ }
+ else {
+ dimension->entities.emplace_or_replace<entity::Stasis>(entity);
+ }
+ }
+}
diff --git a/game/shared/entity/stasis.hh b/game/shared/entity/stasis.hh index e6cb9e4..cb364ce 100644 --- a/game/shared/entity/stasis.hh +++ b/game/shared/entity/stasis.hh @@ -1,16 +1,16 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity -{ -// Attached to entities with transform values -// out of bounds in a specific dimension -struct Stasis final { -public: - static void fixed_update(world::Dimension* dimension); -}; -} // namespace entity +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity
+{
+// Attached to entities with transform values
+// out of bounds in a specific dimension
+struct Stasis final {
+public:
+ static void fixed_update(world::Dimension* dimension);
+};
+} // namespace entity
diff --git a/game/shared/entity/transform.cc b/game/shared/entity/transform.cc index 379ddd5..0339b9e 100644 --- a/game/shared/entity/transform.cc +++ b/game/shared/entity/transform.cc @@ -1,33 +1,33 @@ -#include "shared/pch.hh" - -#include "shared/entity/transform.hh" - -#include "shared/world/dimension.hh" - -#include "shared/const.hh" - -constexpr inline static void update_component(unsigned int dim, entity::Transform& component) -{ - if(component.local[dim] >= CHUNK_SIZE) { - component.local[dim] -= CHUNK_SIZE; - component.chunk[dim] += 1; - return; - } - - if(component.local[dim] < 0.0f) { - component.local[dim] += CHUNK_SIZE; - component.chunk[dim] -= 1; - return; - } -} - -void entity::Transform::fixed_update(world::Dimension* dimension) -{ - auto view = dimension->entities.view<entity::Transform>(); - - for(auto [entity, transform] : view.each()) { - update_component(0U, transform); - update_component(1U, transform); - update_component(2U, transform); - } -} +#include "shared/pch.hh"
+
+#include "shared/entity/transform.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/const.hh"
+
+constexpr inline static void update_component(unsigned int dim, entity::Transform& component)
+{
+ if(component.local[dim] >= CHUNK_SIZE) {
+ component.local[dim] -= CHUNK_SIZE;
+ component.chunk[dim] += 1;
+ return;
+ }
+
+ if(component.local[dim] < 0.0f) {
+ component.local[dim] += CHUNK_SIZE;
+ component.chunk[dim] -= 1;
+ return;
+ }
+}
+
+void entity::Transform::fixed_update(world::Dimension* dimension)
+{
+ auto view = dimension->entities.view<entity::Transform>();
+
+ for(auto [entity, transform] : view.each()) {
+ update_component(0U, transform);
+ update_component(1U, transform);
+ update_component(2U, transform);
+ }
+}
diff --git a/game/shared/entity/transform.hh b/game/shared/entity/transform.hh index 2b357f8..f7db095 100644 --- a/game/shared/entity/transform.hh +++ b/game/shared/entity/transform.hh @@ -1,30 +1,30 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity -{ -struct Transform { - chunk_pos chunk; - glm::fvec3 local; - glm::fvec3 angles; - -public: - // Updates entity::Transform values so that - // the local translation field is always within - // local coodrinates; [floating-point precision] - static void fixed_update(world::Dimension* dimension); -}; -} // namespace entity - -namespace entity::client -{ -// Client-side only - interpolated and previous transform -struct TransformIntr final : public Transform {}; -struct TransformPrev final : public Transform {}; -} // namespace entity::client +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity
+{
+struct Transform {
+ chunk_pos chunk;
+ glm::fvec3 local;
+ glm::fvec3 angles;
+
+public:
+ // Updates entity::Transform values so that
+ // the local translation field is always within
+ // local coodrinates; [floating-point precision]
+ static void fixed_update(world::Dimension* dimension);
+};
+} // namespace entity
+
+namespace entity::client
+{
+// Client-side only - interpolated and previous transform
+struct TransformIntr final : public Transform {};
+struct TransformPrev final : public Transform {};
+} // namespace entity::client
diff --git a/game/shared/entity/velocity.cc b/game/shared/entity/velocity.cc index 86df445..85ad53b 100644 --- a/game/shared/entity/velocity.cc +++ b/game/shared/entity/velocity.cc @@ -1,19 +1,19 @@ -#include "shared/pch.hh" - -#include "shared/entity/velocity.hh" - -#include "shared/entity/stasis.hh" -#include "shared/entity/transform.hh" - -#include "shared/world/dimension.hh" - -#include "shared/globals.hh" - -void entity::Velocity::fixed_update(world::Dimension* dimension) -{ - auto group = dimension->entities.group<entity::Velocity>(entt::get<entity::Transform>, entt::exclude<entity::Stasis>); - - for(auto [entity, velocity, transform] : group.each()) { - transform.local += velocity.value * globals::fixed_frametime; - } -} +#include "shared/pch.hh"
+
+#include "shared/entity/velocity.hh"
+
+#include "shared/entity/stasis.hh"
+#include "shared/entity/transform.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/globals.hh"
+
+void entity::Velocity::fixed_update(world::Dimension* dimension)
+{
+ auto group = dimension->entities.group<entity::Velocity>(entt::get<entity::Transform>, entt::exclude<entity::Stasis>);
+
+ for(auto [entity, velocity, transform] : group.each()) {
+ transform.local += velocity.value * globals::fixed_frametime;
+ }
+}
diff --git a/game/shared/entity/velocity.hh b/game/shared/entity/velocity.hh index c8a1c91..e57e555 100644 --- a/game/shared/entity/velocity.hh +++ b/game/shared/entity/velocity.hh @@ -1,19 +1,19 @@ -#pragma once - -namespace world -{ -class Dimension; -} // namespace world - -namespace entity -{ -struct Velocity final { - glm::fvec3 value; - -public: - // Updates entities entity::Transform values - // according to velocities multiplied by fixed_frametime. - // NOTE: This system was previously called inertial - static void fixed_update(world::Dimension* dimension); -}; -} // namespace entity +#pragma once
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace entity
+{
+struct Velocity final {
+ glm::fvec3 value;
+
+public:
+ // Updates entities entity::Transform values
+ // according to velocities multiplied by fixed_frametime.
+ // NOTE: This system was previously called inertial
+ static void fixed_update(world::Dimension* dimension);
+};
+} // namespace entity
diff --git a/game/shared/game.cc b/game/shared/game.cc index 94c2c1f..f7b9da4 100644 --- a/game/shared/game.cc +++ b/game/shared/game.cc @@ -1,123 +1,123 @@ -#include "shared/pch.hh" - -#include "shared/game.hh" - -#include "core/io/cmdline.hh" - -static std::filesystem::path get_gamepath(void) -{ - if(auto gamepath = io::cmdline::get_cstr("gamepath")) { - // Allow users and third-party launchers to override - // content location. Perhaps this would work to allow - // for a Minecraft-like versioning approach? - return std::filesystem::path(gamepath); - } - - return std::filesystem::current_path() / "data"; -} - -static std::filesystem::path get_userpath(void) -{ - if(auto userpath = io::cmdline::get_cstr("userpath")) { - // Allow users and third-party launchers to override - // user data location. Perhaps this would work to allow - // for a Minecraft-like versioning approach? - return std::filesystem::path(userpath); - } - - if(auto win_appdata = std::getenv("APPDATA")) { - // On pre-seven Windows systems it's just AppData - // On post-seven Windows systems it's AppData/Roaming - return std::filesystem::path(win_appdata) / "voxelius"; - } - - if(auto xdg_home = std::getenv("XDG_DATA_HOME")) { - // Systems with an active X11/Wayland session - // theoretically should have this defined, and - // it can be different from ${HOME} (I think). - return std::filesystem::path(xdg_home) / ".voxelius"; - } - - if(auto unix_home = std::getenv("HOME")) { - // Any spherical UNIX/UNIX-like system in vacuum - // has this defined for every single user process. - return std::filesystem::path(unix_home) / ".voxelius"; - } - - // Give up and save stuff into CWD - return std::filesystem::current_path(); -} - -void shared_game::init(int argc, char** argv) -{ - auto logger = spdlog::default_logger(); - auto& logger_sinks = logger->sinks(); - - logger_sinks.clear(); - logger_sinks.push_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>()); - logger_sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("voxelius.log", false)); - -#if defined(NDEBUG) - constexpr auto default_loglevel = spdlog::level::info; -#else - constexpr auto default_loglevel = spdlog::level::trace; -#endif - - if(io::cmdline::contains("quiet")) { - logger->set_level(spdlog::level::warn); - } - else if(io::cmdline::contains("verbose")) { - logger->set_level(spdlog::level::trace); - } - else { - logger->set_level(default_loglevel); - } - - logger->set_pattern("%H:%M:%S.%e %^[%L]%$ %v"); - logger->flush(); - - if(!PHYSFS_init(argv[0])) { - spdlog::critical("physfs: init failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - auto gamepath = get_gamepath(); - auto userpath = get_userpath(); - - spdlog::info("shared_game: set gamepath to {}", gamepath.string()); - spdlog::info("shared_game: set userpath to {}", userpath.string()); - - std::error_code ignore_error = {}; - std::filesystem::create_directories(gamepath, ignore_error); - std::filesystem::create_directories(userpath, ignore_error); - - if(!PHYSFS_mount(gamepath.string().c_str(), nullptr, false)) { - spdlog::critical("physfs: mount {} failed: {}", gamepath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - if(!PHYSFS_mount(userpath.string().c_str(), nullptr, false)) { - spdlog::critical("physfs: mount {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - if(!PHYSFS_setWriteDir(userpath.string().c_str())) { - spdlog::critical("physfs: setwritedir {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - if(enet_initialize()) { - spdlog::critical("enet: init failed"); - std::terminate(); - } -} - -void shared_game::shutdown(void) -{ - enet_deinitialize(); - - if(!PHYSFS_deinit()) { - spdlog::critical("physfs: deinit failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } -} +#include "shared/pch.hh"
+
+#include "shared/game.hh"
+
+#include "core/io/cmdline.hh"
+
+static std::filesystem::path get_gamepath(void)
+{
+ if(auto gamepath = io::cmdline::get_cstr("gamepath")) {
+ // Allow users and third-party launchers to override
+ // content location. Perhaps this would work to allow
+ // for a Minecraft-like versioning approach?
+ return std::filesystem::path(gamepath);
+ }
+
+ return std::filesystem::current_path() / "data";
+}
+
+static std::filesystem::path get_userpath(void)
+{
+ if(auto userpath = io::cmdline::get_cstr("userpath")) {
+ // Allow users and third-party launchers to override
+ // user data location. Perhaps this would work to allow
+ // for a Minecraft-like versioning approach?
+ return std::filesystem::path(userpath);
+ }
+
+ if(auto win_appdata = std::getenv("APPDATA")) {
+ // On pre-seven Windows systems it's just AppData
+ // On post-seven Windows systems it's AppData/Roaming
+ return std::filesystem::path(win_appdata) / "voxelius";
+ }
+
+ if(auto xdg_home = std::getenv("XDG_DATA_HOME")) {
+ // Systems with an active X11/Wayland session
+ // theoretically should have this defined, and
+ // it can be different from ${HOME} (I think).
+ return std::filesystem::path(xdg_home) / ".voxelius";
+ }
+
+ if(auto unix_home = std::getenv("HOME")) {
+ // Any spherical UNIX/UNIX-like system in vacuum
+ // has this defined for every single user process.
+ return std::filesystem::path(unix_home) / ".voxelius";
+ }
+
+ // Give up and save stuff into CWD
+ return std::filesystem::current_path();
+}
+
+void shared_game::init(int argc, char** argv)
+{
+ auto logger = spdlog::default_logger();
+ auto& logger_sinks = logger->sinks();
+
+ logger_sinks.clear();
+ logger_sinks.push_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
+ logger_sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("voxelius.log", false));
+
+#if defined(NDEBUG)
+ constexpr auto default_loglevel = spdlog::level::info;
+#else
+ constexpr auto default_loglevel = spdlog::level::trace;
+#endif
+
+ if(io::cmdline::contains("quiet")) {
+ logger->set_level(spdlog::level::warn);
+ }
+ else if(io::cmdline::contains("verbose")) {
+ logger->set_level(spdlog::level::trace);
+ }
+ else {
+ logger->set_level(default_loglevel);
+ }
+
+ logger->set_pattern("%H:%M:%S.%e %^[%L]%$ %v");
+ logger->flush();
+
+ if(!PHYSFS_init(argv[0])) {
+ spdlog::critical("physfs: init failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ auto gamepath = get_gamepath();
+ auto userpath = get_userpath();
+
+ spdlog::info("shared_game: set gamepath to {}", gamepath.string());
+ spdlog::info("shared_game: set userpath to {}", userpath.string());
+
+ std::error_code ignore_error = {};
+ std::filesystem::create_directories(gamepath, ignore_error);
+ std::filesystem::create_directories(userpath, ignore_error);
+
+ if(!PHYSFS_mount(gamepath.string().c_str(), nullptr, false)) {
+ spdlog::critical("physfs: mount {} failed: {}", gamepath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ if(!PHYSFS_mount(userpath.string().c_str(), nullptr, false)) {
+ spdlog::critical("physfs: mount {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ if(!PHYSFS_setWriteDir(userpath.string().c_str())) {
+ spdlog::critical("physfs: setwritedir {} failed: {}", userpath.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ if(enet_initialize()) {
+ spdlog::critical("enet: init failed");
+ std::terminate();
+ }
+}
+
+void shared_game::shutdown(void)
+{
+ enet_deinitialize();
+
+ if(!PHYSFS_deinit()) {
+ spdlog::critical("physfs: deinit failed: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+}
diff --git a/game/shared/game.hh b/game/shared/game.hh index 06c1f51..21286e8 100644 --- a/game/shared/game.hh +++ b/game/shared/game.hh @@ -1,11 +1,11 @@ -#ifndef SHARED_GAME -#define SHARED_GAME 1 -#pragma once - -namespace shared_game -{ -void init(int argc, char** argv); -void shutdown(void); -} // namespace shared_game - -#endif // SHARED_GAME +#ifndef SHARED_GAME
+#define SHARED_GAME 1
+#pragma once
+
+namespace shared_game
+{
+void init(int argc, char** argv);
+void shutdown(void);
+} // namespace shared_game
+
+#endif // SHARED_GAME
diff --git a/game/shared/game_items.cc b/game/shared/game_items.cc index 58f379f..6073117 100644 --- a/game/shared/game_items.cc +++ b/game/shared/game_items.cc @@ -1,69 +1,66 @@ -#include "shared/pch.hh" - -#include "shared/game_items.hh" - -#include "shared/world/item_registry.hh" - -#include "shared/game_voxels.hh" - -item_id game_items::stone = NULL_ITEM_ID; -item_id game_items::cobblestone = NULL_ITEM_ID; -item_id game_items::dirt = NULL_ITEM_ID; -item_id game_items::grass = NULL_ITEM_ID; -item_id game_items::oak_leaves = NULL_ITEM_ID; -item_id game_items::oak_planks = NULL_ITEM_ID; -item_id game_items::oak_log = NULL_ITEM_ID; -item_id game_items::glass = NULL_ITEM_ID; -item_id game_items::slime = NULL_ITEM_ID; -item_id game_items::mud = NULL_ITEM_ID; - -void game_items::populate(void) -{ - // Stone; a hardened slate rock - game_items::stone = - world::item_registry::construct("stone").set_texture("textures/item/stone.png").set_place_voxel(game_voxels::stone).build(); - - // Cobblestone; a bunch of small stones - game_items::cobblestone = - world::item_registry::construct("cobblestone") - .set_texture("textures/item/cobblestone.png") - .set_place_voxel(game_voxels::cobblestone) - .build(); - - // Dirt; it's very dirty - game_items::dirt = - world::item_registry::construct("dirt").set_texture("textures/item/dirt.png").set_place_voxel(game_voxels::dirt).build(); - - // Grass; literally just grassy dirt - game_items::grass = - world::item_registry::construct("grass").set_texture("textures/item/grass.png").set_place_voxel(game_voxels::grass).build(); - - // Oak leaves; they're bushy! - game_items::oak_leaves = - world::item_registry::construct("oak_leaves") - .set_texture("textures/item/oak_leaves.png") - .set_place_voxel(game_voxels::oak_leaves) - .build(); - - // Oak planks; watch for splinters! - game_items::oak_planks = - world::item_registry::construct("oak_planks") - .set_texture("textures/item/oak_planks.png") - .set_place_voxel(game_voxels::oak_planks) - .build(); - - // Oak log; a big wad of wood - game_items::oak_log = - world::item_registry::construct("oak_log").set_texture("textures/item/oak_log.png").set_place_voxel(game_voxels::oak_log).build(); - - // Glass; used for windowing - game_items::glass = - world::item_registry::construct("glass").set_texture("textures/item/glass.png").set_place_voxel(game_voxels::glass).build(); - - // Slime; it's bouncy! - game_items::slime = - world::item_registry::construct("slime").set_texture("textures/item/slime.png").set_place_voxel(game_voxels::slime).build(); - - // Mud; you sink in it! - game_items::mud = world::item_registry::construct("mud").set_texture("textures/item/mud.png").build(); -} +#include "shared/pch.hh"
+
+#include "shared/game_items.hh"
+
+#include "shared/world/item_registry.hh"
+
+#include "shared/game_voxels.hh"
+
+item_id game_items::stone = NULL_ITEM_ID;
+item_id game_items::cobblestone = NULL_ITEM_ID;
+item_id game_items::dirt = NULL_ITEM_ID;
+item_id game_items::grass = NULL_ITEM_ID;
+item_id game_items::oak_leaves = NULL_ITEM_ID;
+item_id game_items::oak_planks = NULL_ITEM_ID;
+item_id game_items::oak_log = NULL_ITEM_ID;
+item_id game_items::glass = NULL_ITEM_ID;
+item_id game_items::slime = NULL_ITEM_ID;
+item_id game_items::mud = NULL_ITEM_ID;
+
+void game_items::populate(void)
+{
+ // Stone; a hardened slate rock
+ game_items::stone =
+ world::item_registry::construct("stone").set_texture("textures/item/stone.png").set_place_voxel(game_voxels::stone).build();
+
+ // Cobblestone; a bunch of small stones
+ game_items::cobblestone = world::item_registry::construct("cobblestone")
+ .set_texture("textures/item/cobblestone.png")
+ .set_place_voxel(game_voxels::cobblestone)
+ .build();
+
+ // Dirt; it's very dirty
+ game_items::dirt =
+ world::item_registry::construct("dirt").set_texture("textures/item/dirt.png").set_place_voxel(game_voxels::dirt).build();
+
+ // Grass; literally just grassy dirt
+ game_items::grass =
+ world::item_registry::construct("grass").set_texture("textures/item/grass.png").set_place_voxel(game_voxels::grass).build();
+
+ // Oak leaves; they're bushy!
+ game_items::oak_leaves = world::item_registry::construct("oak_leaves")
+ .set_texture("textures/item/oak_leaves.png")
+ .set_place_voxel(game_voxels::oak_leaves)
+ .build();
+
+ // Oak planks; watch for splinters!
+ game_items::oak_planks = world::item_registry::construct("oak_planks")
+ .set_texture("textures/item/oak_planks.png")
+ .set_place_voxel(game_voxels::oak_planks)
+ .build();
+
+ // Oak log; a big wad of wood
+ game_items::oak_log =
+ world::item_registry::construct("oak_log").set_texture("textures/item/oak_log.png").set_place_voxel(game_voxels::oak_log).build();
+
+ // Glass; used for windowing
+ game_items::glass =
+ world::item_registry::construct("glass").set_texture("textures/item/glass.png").set_place_voxel(game_voxels::glass).build();
+
+ // Slime; it's bouncy!
+ game_items::slime =
+ world::item_registry::construct("slime").set_texture("textures/item/slime.png").set_place_voxel(game_voxels::slime).build();
+
+ // Mud; you sink in it!
+ game_items::mud = world::item_registry::construct("mud").set_texture("textures/item/mud.png").build();
+}
diff --git a/game/shared/game_items.hh b/game/shared/game_items.hh index 2b5c19a..099923f 100644 --- a/game/shared/game_items.hh +++ b/game/shared/game_items.hh @@ -1,26 +1,26 @@ -#ifndef SHARED_GAME_ITEMS -#define SHARED_GAME_ITEMS 1 -#pragma once - -#include "shared/types.hh" - -namespace game_items -{ -extern item_id stone; -extern item_id cobblestone; -extern item_id dirt; -extern item_id grass; -extern item_id oak_leaves; -extern item_id oak_planks; -extern item_id oak_log; -extern item_id glass; -extern item_id slime; -extern item_id mud; -} // namespace game_items - -namespace game_items -{ -void populate(void); -} // namespace game_items - -#endif // SHARED_GAME_ITEMS +#ifndef SHARED_GAME_ITEMS
+#define SHARED_GAME_ITEMS 1
+#pragma once
+
+#include "shared/types.hh"
+
+namespace game_items
+{
+extern item_id stone;
+extern item_id cobblestone;
+extern item_id dirt;
+extern item_id grass;
+extern item_id oak_leaves;
+extern item_id oak_planks;
+extern item_id oak_log;
+extern item_id glass;
+extern item_id slime;
+extern item_id mud;
+} // namespace game_items
+
+namespace game_items
+{
+void populate(void);
+} // namespace game_items
+
+#endif // SHARED_GAME_ITEMS
diff --git a/game/shared/game_voxels.cc b/game/shared/game_voxels.cc index 2d9655b..51de1a1 100644 --- a/game/shared/game_voxels.cc +++ b/game/shared/game_voxels.cc @@ -1,126 +1,119 @@ -#include "shared/pch.hh" - -#include "shared/game_voxels.hh" - -#include "shared/world/voxel_registry.hh" - -voxel_id game_voxels::cobblestone = NULL_VOXEL_ID; -voxel_id game_voxels::dirt = NULL_VOXEL_ID; -voxel_id game_voxels::grass = NULL_VOXEL_ID; -voxel_id game_voxels::stone = NULL_VOXEL_ID; -voxel_id game_voxels::vtest = NULL_VOXEL_ID; -voxel_id game_voxels::vtest_ck = NULL_VOXEL_ID; -voxel_id game_voxels::oak_leaves = NULL_VOXEL_ID; -voxel_id game_voxels::oak_planks = NULL_VOXEL_ID; -voxel_id game_voxels::oak_log = NULL_VOXEL_ID; -voxel_id game_voxels::glass = NULL_VOXEL_ID; -voxel_id game_voxels::slime = NULL_VOXEL_ID; -voxel_id game_voxels::mud = NULL_VOXEL_ID; - -void game_voxels::populate(void) -{ - // Stone; the backbone of the generated world - game_voxels::stone = - world::voxel_registry::construct("stone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/stone_01.png") - .add_texture_default("textures/voxel/stone_02.png") - .add_texture_default("textures/voxel/stone_03.png") - .add_texture_default("textures/voxel/stone_04.png") - .set_surface(world::voxel_surface::STONE) - .build(); - - // Cobblestone; should drop when a stone is broken, might also be present in surface features - game_voxels::cobblestone = - world::voxel_registry::construct("cobblestone", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/cobblestone_01.png") - .add_texture_default("textures/voxel/cobblestone_02.png") - .set_surface(world::voxel_surface::STONE) - .build(); - - // Dirt with a grass layer on top; the top layer of plains biome - game_voxels::grass = - world::voxel_registry::construct("grass", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/grass_side_01.png") - .add_texture_default("textures/voxel/grass_side_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_01.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_03.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_04.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_01.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_02.png") - .set_surface(world::voxel_surface::GRASS) - .build(); - - // Dirt; the under-surface layer of some biomes - game_voxels::dirt = - world::voxel_registry::construct("dirt", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/dirt_01.png") - .add_texture_default("textures/voxel/dirt_02.png") - .add_texture_default("textures/voxel/dirt_03.png") - .add_texture_default("textures/voxel/dirt_04.png") - .set_surface(world::voxel_surface::DIRT) - .build(); - - // VTest; a test voxel to ensure animations work - game_voxels::vtest = - world::voxel_registry::construct("vtest", world::voxel_type::CUBE, true, false) - .add_texture_default("textures/voxel/vtest_F1.png") - .add_texture_default("textures/voxel/vtest_F2.png") - .add_texture_default("textures/voxel/vtest_F3.png") - .add_texture_default("textures/voxel/vtest_F4.png") - .build(); - - // VTest-CK; a pure blue chromakey I used to make the game's logo - game_voxels::vtest_ck = - world::voxel_registry::construct("vtest_ck", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/chromakey.png") - .build(); - - // Oak leaves; greenery. TODO: add trees as surface features - game_voxels::oak_leaves = - world::voxel_registry::construct("oak_leaves", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_leaves.png") - .set_surface(world::voxel_surface::GRASS) - .build(); - - // Oak planks; the thing that comes out of oak logs - game_voxels::oak_planks = - world::voxel_registry::construct("oak_planks", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_planks_01.png") - .add_texture_default("textures/voxel/oak_planks_02.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Oak logs; greenery. TODO: add trees as surface features - game_voxels::oak_log = - world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false) - .add_texture_default("textures/voxel/oak_wood_01.png") - .add_texture_default("textures/voxel/oak_wood_02.png") - .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png") - .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png") - .set_surface(world::voxel_surface::WOOD) - .build(); - - // Glass; blend rendering test - game_voxels::glass = - world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true) - .add_texture_default("textures/voxel/glass_01.png") - .set_surface(world::voxel_surface::GLASS) - .build(); - - // Slime; it's bouncy! - game_voxels::slime = - world::voxel_registry::construct("slime", world::voxel_type::CUBE, false, true) - .set_touch(world::voxel_touch::BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f)) - .add_texture_default("textures/voxel/slime_01.png") - .build(); - - // Mud; you sink in it - game_voxels::mud = - world::voxel_registry::construct("mud", world::voxel_type::CUBE, false, false) - .set_touch(world::voxel_touch::SINK, glm::fvec3(0.50f, 0.75f, 0.50f)) - .add_texture_default("textures/voxel/mud_01.png") - .add_texture_default("textures/voxel/mud_02.png") - .set_surface(world::voxel_surface::DIRT) - .build(); -} +#include "shared/pch.hh"
+
+#include "shared/game_voxels.hh"
+
+#include "shared/world/voxel_registry.hh"
+
+voxel_id game_voxels::cobblestone = NULL_VOXEL_ID;
+voxel_id game_voxels::dirt = NULL_VOXEL_ID;
+voxel_id game_voxels::grass = NULL_VOXEL_ID;
+voxel_id game_voxels::stone = NULL_VOXEL_ID;
+voxel_id game_voxels::vtest = NULL_VOXEL_ID;
+voxel_id game_voxels::vtest_ck = NULL_VOXEL_ID;
+voxel_id game_voxels::oak_leaves = NULL_VOXEL_ID;
+voxel_id game_voxels::oak_planks = NULL_VOXEL_ID;
+voxel_id game_voxels::oak_log = NULL_VOXEL_ID;
+voxel_id game_voxels::glass = NULL_VOXEL_ID;
+voxel_id game_voxels::slime = NULL_VOXEL_ID;
+voxel_id game_voxels::mud = NULL_VOXEL_ID;
+
+void game_voxels::populate(void)
+{
+ // Stone; the backbone of the generated world
+ game_voxels::stone =
+ world::voxel_registry::construct("stone", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/stone_01.png")
+ .add_texture_default("textures/voxel/stone_02.png")
+ .add_texture_default("textures/voxel/stone_03.png")
+ .add_texture_default("textures/voxel/stone_04.png")
+ .set_surface(world::voxel_surface::STONE)
+ .build();
+
+ // Cobblestone; should drop when a stone is broken, might also be present in surface features
+ game_voxels::cobblestone =
+ world::voxel_registry::construct("cobblestone", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/cobblestone_01.png")
+ .add_texture_default("textures/voxel/cobblestone_02.png")
+ .set_surface(world::voxel_surface::STONE)
+ .build();
+
+ // Dirt with a grass layer on top; the top layer of plains biome
+ game_voxels::grass =
+ world::voxel_registry::construct("grass", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/grass_side_01.png")
+ .add_texture_default("textures/voxel/grass_side_02.png")
+ .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_01.png")
+ .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_02.png")
+ .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_03.png")
+ .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/dirt_04.png")
+ .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_01.png")
+ .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/grass_02.png")
+ .set_surface(world::voxel_surface::GRASS)
+ .build();
+
+ // Dirt; the under-surface layer of some biomes
+ game_voxels::dirt =
+ world::voxel_registry::construct("dirt", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/dirt_01.png")
+ .add_texture_default("textures/voxel/dirt_02.png")
+ .add_texture_default("textures/voxel/dirt_03.png")
+ .add_texture_default("textures/voxel/dirt_04.png")
+ .set_surface(world::voxel_surface::DIRT)
+ .build();
+
+ // VTest; a test voxel to ensure animations work
+ game_voxels::vtest = world::voxel_registry::construct("vtest", world::voxel_type::CUBE, true, false)
+ .add_texture_default("textures/voxel/vtest_F1.png")
+ .add_texture_default("textures/voxel/vtest_F2.png")
+ .add_texture_default("textures/voxel/vtest_F3.png")
+ .add_texture_default("textures/voxel/vtest_F4.png")
+ .build();
+
+ // VTest-CK; a pure blue chromakey I used to make the game's logo
+ game_voxels::vtest_ck = world::voxel_registry::construct("vtest_ck", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/chromakey.png")
+ .build();
+
+ // Oak leaves; greenery. TODO: add trees as surface features
+ game_voxels::oak_leaves = world::voxel_registry::construct("oak_leaves", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/oak_leaves.png")
+ .set_surface(world::voxel_surface::GRASS)
+ .build();
+
+ // Oak planks; the thing that comes out of oak logs
+ game_voxels::oak_planks = world::voxel_registry::construct("oak_planks", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/oak_planks_01.png")
+ .add_texture_default("textures/voxel/oak_planks_02.png")
+ .set_surface(world::voxel_surface::WOOD)
+ .build();
+
+ // Oak logs; greenery. TODO: add trees as surface features
+ game_voxels::oak_log =
+ world::voxel_registry::construct("oak_log", world::voxel_type::CUBE, false, false)
+ .add_texture_default("textures/voxel/oak_wood_01.png")
+ .add_texture_default("textures/voxel/oak_wood_02.png")
+ .add_texture(world::voxel_face::CUBE_BOTTOM, "textures/voxel/oak_wood_top.png")
+ .add_texture(world::voxel_face::CUBE_TOP, "textures/voxel/oak_wood_top.png")
+ .set_surface(world::voxel_surface::WOOD)
+ .build();
+
+ // Glass; blend rendering test
+ game_voxels::glass = world::voxel_registry::construct("glass", world::voxel_type::CUBE, false, true)
+ .add_texture_default("textures/voxel/glass_01.png")
+ .set_surface(world::voxel_surface::GLASS)
+ .build();
+
+ // Slime; it's bouncy!
+ game_voxels::slime = world::voxel_registry::construct("slime", world::voxel_type::CUBE, false, true)
+ .set_touch(world::voxel_touch::BOUNCE, glm::fvec3(0.00f, 0.60f, 0.00f))
+ .add_texture_default("textures/voxel/slime_01.png")
+ .build();
+
+ // Mud; you sink in it
+ game_voxels::mud = world::voxel_registry::construct("mud", world::voxel_type::CUBE, false, false)
+ .set_touch(world::voxel_touch::SINK, glm::fvec3(0.50f, 0.75f, 0.50f))
+ .add_texture_default("textures/voxel/mud_01.png")
+ .add_texture_default("textures/voxel/mud_02.png")
+ .set_surface(world::voxel_surface::DIRT)
+ .build();
+}
diff --git a/game/shared/game_voxels.hh b/game/shared/game_voxels.hh index e96e141..21102d0 100644 --- a/game/shared/game_voxels.hh +++ b/game/shared/game_voxels.hh @@ -1,28 +1,28 @@ -#ifndef SHARED_GAME_VOXELS -#define SHARED_GAME_VOXELS 1 -#pragma once - -#include "shared/types.hh" - -namespace game_voxels -{ -extern voxel_id cobblestone; -extern voxel_id dirt; -extern voxel_id grass; -extern voxel_id stone; -extern voxel_id vtest; -extern voxel_id vtest_ck; -extern voxel_id oak_leaves; -extern voxel_id oak_planks; -extern voxel_id oak_log; -extern voxel_id glass; -extern voxel_id slime; -extern voxel_id mud; -} // namespace game_voxels - -namespace game_voxels -{ -void populate(void); -} // namespace game_voxels - -#endif // SHARED_GAME_VOXELS +#ifndef SHARED_GAME_VOXELS
+#define SHARED_GAME_VOXELS 1
+#pragma once
+
+#include "shared/types.hh"
+
+namespace game_voxels
+{
+extern voxel_id cobblestone;
+extern voxel_id dirt;
+extern voxel_id grass;
+extern voxel_id stone;
+extern voxel_id vtest;
+extern voxel_id vtest_ck;
+extern voxel_id oak_leaves;
+extern voxel_id oak_planks;
+extern voxel_id oak_log;
+extern voxel_id glass;
+extern voxel_id slime;
+extern voxel_id mud;
+} // namespace game_voxels
+
+namespace game_voxels
+{
+void populate(void);
+} // namespace game_voxels
+
+#endif // SHARED_GAME_VOXELS
diff --git a/game/shared/globals.cc b/game/shared/globals.cc index 8552214..1651402 100644 --- a/game/shared/globals.cc +++ b/game/shared/globals.cc @@ -1,12 +1,12 @@ -#include "shared/pch.hh" - -#include "shared/globals.hh" - -entt::dispatcher globals::dispatcher; - -float globals::fixed_frametime; -float globals::fixed_frametime_avg; -std::uint64_t globals::fixed_frametime_us; -std::size_t globals::fixed_framecount; - -std::uint64_t globals::curtime; +#include "shared/pch.hh"
+
+#include "shared/globals.hh"
+
+entt::dispatcher globals::dispatcher;
+
+float globals::fixed_frametime;
+float globals::fixed_frametime_avg;
+std::uint64_t globals::fixed_frametime_us;
+std::size_t globals::fixed_framecount;
+
+std::uint64_t globals::curtime;
diff --git a/game/shared/globals.hh b/game/shared/globals.hh index 39a12a7..4a5afd1 100644 --- a/game/shared/globals.hh +++ b/game/shared/globals.hh @@ -1,19 +1,19 @@ -#pragma once - -namespace globals -{ -extern entt::dispatcher dispatcher; -} // namespace globals - -namespace globals -{ -extern float fixed_frametime; -extern float fixed_frametime_avg; -extern std::uint64_t fixed_frametime_us; -extern std::size_t fixed_framecount; -} // namespace globals - -namespace globals -{ -extern std::uint64_t curtime; -} // namespace globals +#pragma once
+
+namespace globals
+{
+extern entt::dispatcher dispatcher;
+} // namespace globals
+
+namespace globals
+{
+extern float fixed_frametime;
+extern float fixed_frametime_avg;
+extern std::uint64_t fixed_frametime_us;
+extern std::size_t fixed_framecount;
+} // namespace globals
+
+namespace globals
+{
+extern std::uint64_t curtime;
+} // namespace globals
diff --git a/game/shared/pch.hh b/game/shared/pch.hh index e978e0f..438f82a 100644 --- a/game/shared/pch.hh +++ b/game/shared/pch.hh @@ -1,19 +1,19 @@ -#pragma once - -#include <core/pch.hh> // inherit dependent includes from core.lib - -#include <csignal> - -#include <enet/enet.h> - -#include <entt/entity/registry.hpp> -#include <entt/signal/dispatcher.hpp> - -#include <fastnoiselite.h> - -#include <miniz.h> - -#include <parson.h> - -#include <spdlog/sinks/basic_file_sink.h> -#include <spdlog/sinks/stdout_color_sinks.h> +#pragma once
+
+#include <core/pch.hh> // inherit dependent includes from core.lib
+
+#include <csignal>
+
+#include <enet/enet.h>
+
+#include <entt/entity/registry.hpp>
+#include <entt/signal/dispatcher.hpp>
+
+#include <fastnoiselite.h>
+
+#include <miniz.h>
+
+#include <parson.h>
+
+#include <spdlog/sinks/basic_file_sink.h>
+#include <spdlog/sinks/stdout_color_sinks.h>
diff --git a/game/shared/protocol.cc b/game/shared/protocol.cc index 6c18658..576502e 100644 --- a/game/shared/protocol.cc +++ b/game/shared/protocol.cc @@ -1,510 +1,510 @@ -#include "shared/pch.hh" - -#include "shared/protocol.hh" - -#include "core/io/buffer.hh" - -#include "shared/entity/head.hh" -#include "shared/entity/player.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" - -#include "shared/globals.hh" - -static io::ReadBuffer read_buffer; -static io::WriteBuffer write_buffer; - -ENetPacket* protocol::encode(const protocol::StatusRequest& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::StatusRequest::ID); - write_buffer.write<std::uint32_t>(packet.version); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::StatusResponse& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::StatusResponse::ID); - write_buffer.write<std::uint32_t>(packet.version); - write_buffer.write<std::uint16_t>(packet.max_players); - write_buffer.write<std::uint16_t>(packet.num_players); - write_buffer.write<std::string_view>(packet.motd); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::LoginRequest& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::LoginRequest::ID); - write_buffer.write<std::uint32_t>(packet.version); - write_buffer.write<std::uint64_t>(packet.voxel_registry_checksum); - write_buffer.write<std::uint64_t>(packet.item_registry_checksum); - write_buffer.write<std::uint64_t>(packet.password_hash); - write_buffer.write<std::string_view>(packet.username.substr(0, protocol::MAX_USERNAME)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::LoginResponse& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::LoginResponse::ID); - write_buffer.write<std::uint16_t>(packet.client_index); - write_buffer.write<std::uint64_t>(packet.client_identity); - write_buffer.write<std::uint16_t>(packet.server_tickrate); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::Disconnect& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::Disconnect::ID); - write_buffer.write<std::string_view>(packet.reason); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::ChunkVoxels& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::ChunkVoxels::ID); - write_buffer.write<std::int32_t>(packet.chunk.x); - write_buffer.write<std::int32_t>(packet.chunk.y); - write_buffer.write<std::int32_t>(packet.chunk.z); - packet.voxels.serialize(write_buffer); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::EntityTransform& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::EntityTransform::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - write_buffer.write<std::int32_t>(packet.chunk.x); - write_buffer.write<std::int32_t>(packet.chunk.y); - write_buffer.write<std::int32_t>(packet.chunk.z); - write_buffer.write<float>(packet.local.x); - write_buffer.write<float>(packet.local.y); - write_buffer.write<float>(packet.local.z); - write_buffer.write<float>(packet.angles.x); - write_buffer.write<float>(packet.angles.y); - write_buffer.write<float>(packet.angles.z); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::EntityHead& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::EntityHead::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - write_buffer.write<float>(packet.angles.x); - write_buffer.write<float>(packet.angles.y); - write_buffer.write<float>(packet.angles.z); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::EntityVelocity& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::EntityVelocity::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - write_buffer.write<float>(packet.value.x); - write_buffer.write<float>(packet.value.y); - write_buffer.write<float>(packet.value.z); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::SpawnPlayer& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::SpawnPlayer::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::ChatMessage& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::ChatMessage::ID); - write_buffer.write<std::uint16_t>(packet.type); - write_buffer.write<std::string_view>(packet.sender.substr(0, protocol::MAX_USERNAME)); - write_buffer.write<std::string_view>(packet.message.substr(0, protocol::MAX_CHAT)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::SetVoxel& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::SetVoxel::ID); - write_buffer.write<std::int64_t>(packet.vpos.x); - write_buffer.write<std::int64_t>(packet.vpos.y); - write_buffer.write<std::int64_t>(packet.vpos.z); - write_buffer.write<std::uint16_t>(packet.voxel); - write_buffer.write<std::uint16_t>(packet.flags); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::RemoveEntity& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::RemoveEntity::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::EntityPlayer& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::EntityPlayer::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::ScoreboardUpdate& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::ScoreboardUpdate::ID); - write_buffer.write<std::uint16_t>(static_cast<std::uint16_t>(packet.names.size())); - for(const std::string& username : packet.names) - write_buffer.write<std::string_view>(username.substr(0, protocol::MAX_USERNAME)); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::RequestChunk& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::RequestChunk::ID); - write_buffer.write<std::int32_t>(packet.cpos.x); - write_buffer.write<std::int32_t>(packet.cpos.y); - write_buffer.write<std::int32_t>(packet.cpos.z); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::GenericSound& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::GenericSound::ID); - write_buffer.write<std::string_view>(packet.sound.substr(0, protocol::MAX_SOUNDNAME)); - write_buffer.write<std::uint8_t>(packet.looping); - write_buffer.write<float>(packet.pitch); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::EntitySound& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::EntitySound::ID); - write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity)); - write_buffer.write<std::string_view>(packet.sound.substr(0, protocol::MAX_SOUNDNAME)); - write_buffer.write<std::uint8_t>(packet.looping); - write_buffer.write<float>(packet.pitch); - return write_buffer.to_packet(flags); -} - -ENetPacket* protocol::encode(const protocol::DimensionInfo& packet, enet_uint32 flags) -{ - write_buffer.reset(); - write_buffer.write<std::uint16_t>(protocol::DimensionInfo::ID); - write_buffer.write<std::string_view>(packet.name); - write_buffer.write<float>(packet.gravity); - return write_buffer.to_packet(flags); -} - -void protocol::broadcast(ENetHost* host, ENetPacket* packet) -{ - if(packet) { - enet_host_broadcast(host, protocol::CHANNEL, packet); - } -} - -void protocol::broadcast(ENetHost* host, ENetPacket* packet, ENetPeer* except) -{ - if(packet) { - for(unsigned int i = 0U; i < host->peerCount; ++i) { - if(host->peers[i].state == ENET_PEER_STATE_CONNECTED) { - if(&host->peers[i] != except) { - enet_peer_send(&host->peers[i], protocol::CHANNEL, packet); - } - } - } - } -} - -void protocol::send(ENetPeer* peer, ENetPacket* packet) -{ - if(packet) { - enet_peer_send(peer, protocol::CHANNEL, packet); - } -} - -void protocol::decode(entt::dispatcher& dispatcher, const ENetPacket* packet, ENetPeer* peer) -{ - read_buffer.reset(packet); - - protocol::StatusRequest status_request; - protocol::StatusResponse status_response; - protocol::LoginRequest login_request; - protocol::LoginResponse login_response; - protocol::Disconnect disconnect; - protocol::ChunkVoxels chunk_voxels; - protocol::EntityTransform entity_transform; - protocol::EntityHead entity_head; - protocol::EntityVelocity entity_velocity; - protocol::SpawnPlayer spawn_player; - protocol::ChatMessage chat_message; - protocol::SetVoxel set_voxel; - protocol::RemoveEntity remove_entity; - protocol::EntityPlayer entity_player; - protocol::ScoreboardUpdate scoreboard_update; - protocol::RequestChunk request_chunk; - protocol::GenericSound generic_sound; - protocol::EntitySound entity_sound; - protocol::DimensionInfo dimension_info; - - auto id = read_buffer.read<std::uint16_t>(); - - switch(id) { - case protocol::StatusRequest::ID: - status_request.peer = peer; - status_request.version = read_buffer.read<std::uint32_t>(); - dispatcher.trigger(status_request); - break; - - case protocol::StatusResponse::ID: - status_response.peer = peer; - status_response.version = read_buffer.read<std::uint32_t>(); - status_response.max_players = read_buffer.read<std::uint16_t>(); - status_response.num_players = read_buffer.read<std::uint16_t>(); - status_response.motd = read_buffer.read<std::string>(); - dispatcher.trigger(status_response); - break; - - case protocol::LoginRequest::ID: - login_request.peer = peer; - login_request.version = read_buffer.read<std::uint32_t>(); - login_request.voxel_registry_checksum = read_buffer.read<std::uint64_t>(); - login_request.item_registry_checksum = read_buffer.read<std::uint64_t>(); - login_request.password_hash = read_buffer.read<std::uint64_t>(); - login_request.username = read_buffer.read<std::string>(); - dispatcher.trigger(login_request); - break; - - case protocol::LoginResponse::ID: - login_response.peer = peer; - login_response.client_index = read_buffer.read<std::uint16_t>(); - login_response.client_identity = read_buffer.read<std::uint64_t>(); - login_response.server_tickrate = read_buffer.read<std::uint16_t>(); - dispatcher.trigger(login_response); - break; - - case protocol::Disconnect::ID: - disconnect.peer = peer; - disconnect.reason = read_buffer.read<std::string>(); - dispatcher.trigger(disconnect); - break; - - case protocol::ChunkVoxels::ID: - chunk_voxels.peer = peer; - chunk_voxels.chunk.x = read_buffer.read<std::int32_t>(); - chunk_voxels.chunk.y = read_buffer.read<std::int32_t>(); - chunk_voxels.chunk.z = read_buffer.read<std::int32_t>(); - chunk_voxels.voxels.deserialize(read_buffer); - dispatcher.trigger(chunk_voxels); - break; - - case protocol::EntityTransform::ID: - entity_transform.peer = peer; - entity_transform.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - entity_transform.chunk.x = read_buffer.read<std::int32_t>(); - entity_transform.chunk.y = read_buffer.read<std::int32_t>(); - entity_transform.chunk.z = read_buffer.read<std::int32_t>(); - entity_transform.local.x = read_buffer.read<float>(); - entity_transform.local.y = read_buffer.read<float>(); - entity_transform.local.z = read_buffer.read<float>(); - entity_transform.angles.x = read_buffer.read<float>(); - entity_transform.angles.y = read_buffer.read<float>(); - entity_transform.angles.z = read_buffer.read<float>(); - dispatcher.trigger(entity_transform); - break; - - case protocol::EntityHead::ID: - entity_head.peer = peer; - entity_head.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - entity_head.angles[0] = read_buffer.read<float>(); - entity_head.angles[1] = read_buffer.read<float>(); - entity_head.angles[2] = read_buffer.read<float>(); - dispatcher.trigger(entity_head); - break; - - case protocol::EntityVelocity::ID: - entity_velocity.peer = peer; - entity_velocity.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - entity_velocity.value.x = read_buffer.read<float>(); - entity_velocity.value.y = read_buffer.read<float>(); - entity_velocity.value.z = read_buffer.read<float>(); - dispatcher.trigger(entity_velocity); - break; - - case protocol::SpawnPlayer::ID: - spawn_player.peer = peer; - spawn_player.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - dispatcher.trigger(spawn_player); - break; - - case protocol::ChatMessage::ID: - chat_message.peer = peer; - chat_message.type = read_buffer.read<std::uint16_t>(); - chat_message.sender = read_buffer.read<std::string>(); - chat_message.message = read_buffer.read<std::string>(); - dispatcher.trigger(chat_message); - break; - - case protocol::SetVoxel::ID: - set_voxel.peer = peer; - set_voxel.vpos.x = read_buffer.read<std::int64_t>(); - set_voxel.vpos.y = read_buffer.read<std::int64_t>(); - set_voxel.vpos.z = read_buffer.read<std::int64_t>(); - set_voxel.voxel = read_buffer.read<std::uint16_t>(); - set_voxel.flags = read_buffer.read<std::uint16_t>(); - dispatcher.trigger(set_voxel); - break; - - case protocol::RemoveEntity::ID: - remove_entity.peer = peer; - remove_entity.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - dispatcher.trigger(remove_entity); - break; - - case protocol::EntityPlayer::ID: - entity_player.peer = peer; - entity_player.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - dispatcher.trigger(entity_player); - break; - - case protocol::ScoreboardUpdate::ID: - scoreboard_update.peer = peer; - scoreboard_update.names.resize(read_buffer.read<std::uint16_t>()); - for(std::size_t i = 0; i < scoreboard_update.names.size(); ++i) - scoreboard_update.names[i] = read_buffer.read<std::string>(); - dispatcher.trigger(scoreboard_update); - break; - - case protocol::RequestChunk::ID: - request_chunk.peer = peer; - request_chunk.cpos.x = read_buffer.read<std::uint32_t>(); - request_chunk.cpos.y = read_buffer.read<std::uint32_t>(); - request_chunk.cpos.z = read_buffer.read<std::uint32_t>(); - dispatcher.trigger(request_chunk); - break; - - case protocol::GenericSound::ID: - generic_sound.peer = peer; - generic_sound.sound = read_buffer.read<std::string>(); - generic_sound.looping = read_buffer.read<std::uint8_t>(); - generic_sound.pitch = read_buffer.read<float>(); - dispatcher.trigger(generic_sound); - break; - - case protocol::EntitySound::ID: - entity_sound.peer = peer; - entity_sound.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>()); - entity_sound.sound = read_buffer.read<std::string>(); - entity_sound.looping = read_buffer.read<std::uint8_t>(); - entity_sound.pitch = read_buffer.read<float>(); - dispatcher.trigger(entity_sound); - break; - - case protocol::DimensionInfo::ID: - dimension_info.peer = peer; - dimension_info.name = read_buffer.read<std::string>(); - dimension_info.gravity = read_buffer.read<float>(); - dispatcher.trigger(dimension_info); - break; - } -} - -ENetPacket* protocol::utils::make_disconnect(std::string_view reason, enet_uint32 flags) -{ - protocol::Disconnect packet; - packet.reason = reason; - return protocol::encode(packet, flags); -} - -ENetPacket* protocol::utils::make_chat_message(std::string_view message, enet_uint32 flags) -{ - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.message = message; - return protocol::encode(packet, flags); -} - -ENetPacket* protocol::utils::make_chunk_voxels(world::Dimension* dimension, entt::entity entity, enet_uint32 flags) -{ - if(auto component = dimension->chunks.try_get<world::ChunkComponent>(entity)) { - protocol::ChunkVoxels packet; - packet.chunk = component->cpos; - packet.voxels = component->chunk->get_voxels(); - return protocol::encode(packet, flags); - } - - return nullptr; -} - -ENetPacket* protocol::utils::make_entity_head(world::Dimension* dimension, entt::entity entity, enet_uint32 flags) -{ - if(auto component = dimension->entities.try_get<entity::Head>(entity)) { - protocol::EntityHead packet; - packet.entity = entity; - packet.angles = component->angles; - return protocol::encode(packet, flags); - } - - return nullptr; -} - -ENetPacket* protocol::utils::make_entity_transform(world::Dimension* dimension, entt::entity entity, enet_uint32 flags) -{ - if(auto component = dimension->entities.try_get<entity::Transform>(entity)) { - protocol::EntityTransform packet; - packet.entity = entity; - packet.chunk = component->chunk; - packet.local = component->local; - packet.angles = component->angles; - return protocol::encode(packet, flags); - } - - return nullptr; -} - -ENetPacket* protocol::utils::make_entity_velocity(world::Dimension* dimension, entt::entity entity, enet_uint32 flags) -{ - if(auto component = dimension->entities.try_get<entity::Velocity>(entity)) { - protocol::EntityVelocity packet; - packet.entity = entity; - packet.value = component->value; - return protocol::encode(packet, flags); - } - - return nullptr; -} - -ENetPacket* protocol::utils::make_entity_player(world::Dimension* dimension, entt::entity entity, enet_uint32 flags) -{ - if(dimension->entities.any_of<entity::Player>(entity)) { - protocol::EntityPlayer packet; - packet.entity = entity; - return protocol::encode(packet, flags); - } - - return nullptr; -} - -ENetPacket* protocol::utils::make_dimension_info(const world::Dimension* dimension) -{ - protocol::DimensionInfo packet; - packet.name = dimension->get_name(); - packet.gravity = dimension->get_gravity(); - return protocol::encode(packet, ENET_PACKET_FLAG_RELIABLE); -} +#include "shared/pch.hh"
+
+#include "shared/protocol.hh"
+
+#include "core/io/buffer.hh"
+
+#include "shared/entity/head.hh"
+#include "shared/entity/player.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+
+#include "shared/globals.hh"
+
+static io::ReadBuffer read_buffer;
+static io::WriteBuffer write_buffer;
+
+ENetPacket* protocol::encode(const protocol::StatusRequest& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::StatusRequest::ID);
+ write_buffer.write<std::uint32_t>(packet.version);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::StatusResponse& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::StatusResponse::ID);
+ write_buffer.write<std::uint32_t>(packet.version);
+ write_buffer.write<std::uint16_t>(packet.max_players);
+ write_buffer.write<std::uint16_t>(packet.num_players);
+ write_buffer.write<std::string_view>(packet.motd);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::LoginRequest& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::LoginRequest::ID);
+ write_buffer.write<std::uint32_t>(packet.version);
+ write_buffer.write<std::uint64_t>(packet.voxel_registry_checksum);
+ write_buffer.write<std::uint64_t>(packet.item_registry_checksum);
+ write_buffer.write<std::uint64_t>(packet.password_hash);
+ write_buffer.write<std::string_view>(packet.username.substr(0, protocol::MAX_USERNAME));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::LoginResponse& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::LoginResponse::ID);
+ write_buffer.write<std::uint16_t>(packet.client_index);
+ write_buffer.write<std::uint64_t>(packet.client_identity);
+ write_buffer.write<std::uint16_t>(packet.server_tickrate);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::Disconnect& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::Disconnect::ID);
+ write_buffer.write<std::string_view>(packet.reason);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::ChunkVoxels& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::ChunkVoxels::ID);
+ write_buffer.write<std::int32_t>(packet.chunk.x);
+ write_buffer.write<std::int32_t>(packet.chunk.y);
+ write_buffer.write<std::int32_t>(packet.chunk.z);
+ packet.voxels.serialize(write_buffer);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::EntityTransform& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::EntityTransform::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ write_buffer.write<std::int32_t>(packet.chunk.x);
+ write_buffer.write<std::int32_t>(packet.chunk.y);
+ write_buffer.write<std::int32_t>(packet.chunk.z);
+ write_buffer.write<float>(packet.local.x);
+ write_buffer.write<float>(packet.local.y);
+ write_buffer.write<float>(packet.local.z);
+ write_buffer.write<float>(packet.angles.x);
+ write_buffer.write<float>(packet.angles.y);
+ write_buffer.write<float>(packet.angles.z);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::EntityHead& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::EntityHead::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ write_buffer.write<float>(packet.angles.x);
+ write_buffer.write<float>(packet.angles.y);
+ write_buffer.write<float>(packet.angles.z);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::EntityVelocity& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::EntityVelocity::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ write_buffer.write<float>(packet.value.x);
+ write_buffer.write<float>(packet.value.y);
+ write_buffer.write<float>(packet.value.z);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::SpawnPlayer& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::SpawnPlayer::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::ChatMessage& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::ChatMessage::ID);
+ write_buffer.write<std::uint16_t>(packet.type);
+ write_buffer.write<std::string_view>(packet.sender.substr(0, protocol::MAX_USERNAME));
+ write_buffer.write<std::string_view>(packet.message.substr(0, protocol::MAX_CHAT));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::SetVoxel& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::SetVoxel::ID);
+ write_buffer.write<std::int64_t>(packet.vpos.x);
+ write_buffer.write<std::int64_t>(packet.vpos.y);
+ write_buffer.write<std::int64_t>(packet.vpos.z);
+ write_buffer.write<std::uint16_t>(packet.voxel);
+ write_buffer.write<std::uint16_t>(packet.flags);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::RemoveEntity& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::RemoveEntity::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::EntityPlayer& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::EntityPlayer::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::ScoreboardUpdate& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::ScoreboardUpdate::ID);
+ write_buffer.write<std::uint16_t>(static_cast<std::uint16_t>(packet.names.size()));
+ for(const std::string& username : packet.names)
+ write_buffer.write<std::string_view>(username.substr(0, protocol::MAX_USERNAME));
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::RequestChunk& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::RequestChunk::ID);
+ write_buffer.write<std::int32_t>(packet.cpos.x);
+ write_buffer.write<std::int32_t>(packet.cpos.y);
+ write_buffer.write<std::int32_t>(packet.cpos.z);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::GenericSound& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::GenericSound::ID);
+ write_buffer.write<std::string_view>(packet.sound.substr(0, protocol::MAX_SOUNDNAME));
+ write_buffer.write<std::uint8_t>(packet.looping);
+ write_buffer.write<float>(packet.pitch);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::EntitySound& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::EntitySound::ID);
+ write_buffer.write<std::uint64_t>(static_cast<std::uint64_t>(packet.entity));
+ write_buffer.write<std::string_view>(packet.sound.substr(0, protocol::MAX_SOUNDNAME));
+ write_buffer.write<std::uint8_t>(packet.looping);
+ write_buffer.write<float>(packet.pitch);
+ return write_buffer.to_packet(flags);
+}
+
+ENetPacket* protocol::encode(const protocol::DimensionInfo& packet, enet_uint32 flags)
+{
+ write_buffer.reset();
+ write_buffer.write<std::uint16_t>(protocol::DimensionInfo::ID);
+ write_buffer.write<std::string_view>(packet.name);
+ write_buffer.write<float>(packet.gravity);
+ return write_buffer.to_packet(flags);
+}
+
+void protocol::broadcast(ENetHost* host, ENetPacket* packet)
+{
+ if(packet) {
+ enet_host_broadcast(host, protocol::CHANNEL, packet);
+ }
+}
+
+void protocol::broadcast(ENetHost* host, ENetPacket* packet, ENetPeer* except)
+{
+ if(packet) {
+ for(unsigned int i = 0U; i < host->peerCount; ++i) {
+ if(host->peers[i].state == ENET_PEER_STATE_CONNECTED) {
+ if(&host->peers[i] != except) {
+ enet_peer_send(&host->peers[i], protocol::CHANNEL, packet);
+ }
+ }
+ }
+ }
+}
+
+void protocol::send(ENetPeer* peer, ENetPacket* packet)
+{
+ if(packet) {
+ enet_peer_send(peer, protocol::CHANNEL, packet);
+ }
+}
+
+void protocol::decode(entt::dispatcher& dispatcher, const ENetPacket* packet, ENetPeer* peer)
+{
+ read_buffer.reset(packet);
+
+ protocol::StatusRequest status_request;
+ protocol::StatusResponse status_response;
+ protocol::LoginRequest login_request;
+ protocol::LoginResponse login_response;
+ protocol::Disconnect disconnect;
+ protocol::ChunkVoxels chunk_voxels;
+ protocol::EntityTransform entity_transform;
+ protocol::EntityHead entity_head;
+ protocol::EntityVelocity entity_velocity;
+ protocol::SpawnPlayer spawn_player;
+ protocol::ChatMessage chat_message;
+ protocol::SetVoxel set_voxel;
+ protocol::RemoveEntity remove_entity;
+ protocol::EntityPlayer entity_player;
+ protocol::ScoreboardUpdate scoreboard_update;
+ protocol::RequestChunk request_chunk;
+ protocol::GenericSound generic_sound;
+ protocol::EntitySound entity_sound;
+ protocol::DimensionInfo dimension_info;
+
+ auto id = read_buffer.read<std::uint16_t>();
+
+ switch(id) {
+ case protocol::StatusRequest::ID:
+ status_request.peer = peer;
+ status_request.version = read_buffer.read<std::uint32_t>();
+ dispatcher.trigger(status_request);
+ break;
+
+ case protocol::StatusResponse::ID:
+ status_response.peer = peer;
+ status_response.version = read_buffer.read<std::uint32_t>();
+ status_response.max_players = read_buffer.read<std::uint16_t>();
+ status_response.num_players = read_buffer.read<std::uint16_t>();
+ status_response.motd = read_buffer.read<std::string>();
+ dispatcher.trigger(status_response);
+ break;
+
+ case protocol::LoginRequest::ID:
+ login_request.peer = peer;
+ login_request.version = read_buffer.read<std::uint32_t>();
+ login_request.voxel_registry_checksum = read_buffer.read<std::uint64_t>();
+ login_request.item_registry_checksum = read_buffer.read<std::uint64_t>();
+ login_request.password_hash = read_buffer.read<std::uint64_t>();
+ login_request.username = read_buffer.read<std::string>();
+ dispatcher.trigger(login_request);
+ break;
+
+ case protocol::LoginResponse::ID:
+ login_response.peer = peer;
+ login_response.client_index = read_buffer.read<std::uint16_t>();
+ login_response.client_identity = read_buffer.read<std::uint64_t>();
+ login_response.server_tickrate = read_buffer.read<std::uint16_t>();
+ dispatcher.trigger(login_response);
+ break;
+
+ case protocol::Disconnect::ID:
+ disconnect.peer = peer;
+ disconnect.reason = read_buffer.read<std::string>();
+ dispatcher.trigger(disconnect);
+ break;
+
+ case protocol::ChunkVoxels::ID:
+ chunk_voxels.peer = peer;
+ chunk_voxels.chunk.x = read_buffer.read<std::int32_t>();
+ chunk_voxels.chunk.y = read_buffer.read<std::int32_t>();
+ chunk_voxels.chunk.z = read_buffer.read<std::int32_t>();
+ chunk_voxels.voxels.deserialize(read_buffer);
+ dispatcher.trigger(chunk_voxels);
+ break;
+
+ case protocol::EntityTransform::ID:
+ entity_transform.peer = peer;
+ entity_transform.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ entity_transform.chunk.x = read_buffer.read<std::int32_t>();
+ entity_transform.chunk.y = read_buffer.read<std::int32_t>();
+ entity_transform.chunk.z = read_buffer.read<std::int32_t>();
+ entity_transform.local.x = read_buffer.read<float>();
+ entity_transform.local.y = read_buffer.read<float>();
+ entity_transform.local.z = read_buffer.read<float>();
+ entity_transform.angles.x = read_buffer.read<float>();
+ entity_transform.angles.y = read_buffer.read<float>();
+ entity_transform.angles.z = read_buffer.read<float>();
+ dispatcher.trigger(entity_transform);
+ break;
+
+ case protocol::EntityHead::ID:
+ entity_head.peer = peer;
+ entity_head.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ entity_head.angles[0] = read_buffer.read<float>();
+ entity_head.angles[1] = read_buffer.read<float>();
+ entity_head.angles[2] = read_buffer.read<float>();
+ dispatcher.trigger(entity_head);
+ break;
+
+ case protocol::EntityVelocity::ID:
+ entity_velocity.peer = peer;
+ entity_velocity.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ entity_velocity.value.x = read_buffer.read<float>();
+ entity_velocity.value.y = read_buffer.read<float>();
+ entity_velocity.value.z = read_buffer.read<float>();
+ dispatcher.trigger(entity_velocity);
+ break;
+
+ case protocol::SpawnPlayer::ID:
+ spawn_player.peer = peer;
+ spawn_player.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ dispatcher.trigger(spawn_player);
+ break;
+
+ case protocol::ChatMessage::ID:
+ chat_message.peer = peer;
+ chat_message.type = read_buffer.read<std::uint16_t>();
+ chat_message.sender = read_buffer.read<std::string>();
+ chat_message.message = read_buffer.read<std::string>();
+ dispatcher.trigger(chat_message);
+ break;
+
+ case protocol::SetVoxel::ID:
+ set_voxel.peer = peer;
+ set_voxel.vpos.x = read_buffer.read<std::int64_t>();
+ set_voxel.vpos.y = read_buffer.read<std::int64_t>();
+ set_voxel.vpos.z = read_buffer.read<std::int64_t>();
+ set_voxel.voxel = read_buffer.read<std::uint16_t>();
+ set_voxel.flags = read_buffer.read<std::uint16_t>();
+ dispatcher.trigger(set_voxel);
+ break;
+
+ case protocol::RemoveEntity::ID:
+ remove_entity.peer = peer;
+ remove_entity.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ dispatcher.trigger(remove_entity);
+ break;
+
+ case protocol::EntityPlayer::ID:
+ entity_player.peer = peer;
+ entity_player.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ dispatcher.trigger(entity_player);
+ break;
+
+ case protocol::ScoreboardUpdate::ID:
+ scoreboard_update.peer = peer;
+ scoreboard_update.names.resize(read_buffer.read<std::uint16_t>());
+ for(std::size_t i = 0; i < scoreboard_update.names.size(); ++i)
+ scoreboard_update.names[i] = read_buffer.read<std::string>();
+ dispatcher.trigger(scoreboard_update);
+ break;
+
+ case protocol::RequestChunk::ID:
+ request_chunk.peer = peer;
+ request_chunk.cpos.x = read_buffer.read<std::uint32_t>();
+ request_chunk.cpos.y = read_buffer.read<std::uint32_t>();
+ request_chunk.cpos.z = read_buffer.read<std::uint32_t>();
+ dispatcher.trigger(request_chunk);
+ break;
+
+ case protocol::GenericSound::ID:
+ generic_sound.peer = peer;
+ generic_sound.sound = read_buffer.read<std::string>();
+ generic_sound.looping = read_buffer.read<std::uint8_t>();
+ generic_sound.pitch = read_buffer.read<float>();
+ dispatcher.trigger(generic_sound);
+ break;
+
+ case protocol::EntitySound::ID:
+ entity_sound.peer = peer;
+ entity_sound.entity = static_cast<entt::entity>(read_buffer.read<std::uint64_t>());
+ entity_sound.sound = read_buffer.read<std::string>();
+ entity_sound.looping = read_buffer.read<std::uint8_t>();
+ entity_sound.pitch = read_buffer.read<float>();
+ dispatcher.trigger(entity_sound);
+ break;
+
+ case protocol::DimensionInfo::ID:
+ dimension_info.peer = peer;
+ dimension_info.name = read_buffer.read<std::string>();
+ dimension_info.gravity = read_buffer.read<float>();
+ dispatcher.trigger(dimension_info);
+ break;
+ }
+}
+
+ENetPacket* protocol::utils::make_disconnect(std::string_view reason, enet_uint32 flags)
+{
+ protocol::Disconnect packet;
+ packet.reason = reason;
+ return protocol::encode(packet, flags);
+}
+
+ENetPacket* protocol::utils::make_chat_message(std::string_view message, enet_uint32 flags)
+{
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.message = message;
+ return protocol::encode(packet, flags);
+}
+
+ENetPacket* protocol::utils::make_chunk_voxels(world::Dimension* dimension, entt::entity entity, enet_uint32 flags)
+{
+ if(auto component = dimension->chunks.try_get<world::ChunkComponent>(entity)) {
+ protocol::ChunkVoxels packet;
+ packet.chunk = component->cpos;
+ packet.voxels = component->chunk->get_voxels();
+ return protocol::encode(packet, flags);
+ }
+
+ return nullptr;
+}
+
+ENetPacket* protocol::utils::make_entity_head(world::Dimension* dimension, entt::entity entity, enet_uint32 flags)
+{
+ if(auto component = dimension->entities.try_get<entity::Head>(entity)) {
+ protocol::EntityHead packet;
+ packet.entity = entity;
+ packet.angles = component->angles;
+ return protocol::encode(packet, flags);
+ }
+
+ return nullptr;
+}
+
+ENetPacket* protocol::utils::make_entity_transform(world::Dimension* dimension, entt::entity entity, enet_uint32 flags)
+{
+ if(auto component = dimension->entities.try_get<entity::Transform>(entity)) {
+ protocol::EntityTransform packet;
+ packet.entity = entity;
+ packet.chunk = component->chunk;
+ packet.local = component->local;
+ packet.angles = component->angles;
+ return protocol::encode(packet, flags);
+ }
+
+ return nullptr;
+}
+
+ENetPacket* protocol::utils::make_entity_velocity(world::Dimension* dimension, entt::entity entity, enet_uint32 flags)
+{
+ if(auto component = dimension->entities.try_get<entity::Velocity>(entity)) {
+ protocol::EntityVelocity packet;
+ packet.entity = entity;
+ packet.value = component->value;
+ return protocol::encode(packet, flags);
+ }
+
+ return nullptr;
+}
+
+ENetPacket* protocol::utils::make_entity_player(world::Dimension* dimension, entt::entity entity, enet_uint32 flags)
+{
+ if(dimension->entities.any_of<entity::Player>(entity)) {
+ protocol::EntityPlayer packet;
+ packet.entity = entity;
+ return protocol::encode(packet, flags);
+ }
+
+ return nullptr;
+}
+
+ENetPacket* protocol::utils::make_dimension_info(const world::Dimension* dimension)
+{
+ protocol::DimensionInfo packet;
+ packet.name = dimension->get_name();
+ packet.gravity = dimension->get_gravity();
+ return protocol::encode(packet, ENET_PACKET_FLAG_RELIABLE);
+}
diff --git a/game/shared/protocol.hh b/game/shared/protocol.hh index 21f9da9..3133275 100644 --- a/game/shared/protocol.hh +++ b/game/shared/protocol.hh @@ -1,212 +1,212 @@ -#pragma once - -#include "shared/world/chunk.hh" - -namespace world -{ -class Dimension; -} // namespace world - -namespace protocol -{ -constexpr static std::size_t MAX_CHAT = 16384; -constexpr static std::size_t MAX_USERNAME = 64; -constexpr static std::size_t MAX_SOUNDNAME = 1024; -constexpr static std::uint16_t TICKRATE = 60; -constexpr static std::uint16_t PORT = 43103; -constexpr static std::uint32_t VERSION = 15; -constexpr static std::uint8_t CHANNEL = 0; -} // namespace protocol - -namespace protocol -{ -template<std::uint16_t packet_id> -struct Base { - constexpr static std::uint16_t ID = packet_id; - virtual ~Base(void) = default; - ENetPeer* peer { nullptr }; -}; -} // namespace protocol - -namespace protocol -{ -struct StatusRequest; -struct StatusResponse; -struct LoginRequest; -struct LoginResponse; -struct Disconnect; -struct ChunkVoxels; -struct EntityTransform; -struct EntityHead; -struct EntityVelocity; -struct SpawnPlayer; -struct ChatMessage; -struct SetVoxel; -struct RemoveEntity; -struct EntityPlayer; -struct ScoreboardUpdate; -struct RequestChunk; -struct GenericSound; -struct EntitySound; -struct DimensionInfo; -} // namespace protocol - -namespace protocol -{ -ENetPacket* encode(const StatusRequest& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const StatusResponse& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const LoginRequest& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const LoginResponse& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const Disconnect& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const ChunkVoxels& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const EntityTransform& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const EntityHead& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const EntityVelocity& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const SpawnPlayer& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const ChatMessage& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const SetVoxel& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const RemoveEntity& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const EntityPlayer& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const ScoreboardUpdate& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const RequestChunk& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const GenericSound& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const EntitySound& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* encode(const DimensionInfo& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -} // namespace protocol - -namespace protocol -{ -void broadcast(ENetHost* host, ENetPacket* packet); -void broadcast(ENetHost* host, ENetPacket* packet, ENetPeer* except); -void send(ENetPeer* peer, ENetPacket* packet); -} // namespace protocol - -namespace protocol -{ -void decode(entt::dispatcher& dispatcher, const ENetPacket* packet, ENetPeer* peer); -} // namespace protocol - -namespace protocol::utils -{ -ENetPacket* make_disconnect(std::string_view reason, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* make_chat_message(std::string_view message, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -} // namespace protocol::utils - -namespace protocol::utils -{ -ENetPacket* make_chunk_voxels(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -} // namespace protocol::utils - -namespace protocol::utils -{ -ENetPacket* make_entity_head(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* make_entity_transform(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* make_entity_velocity(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* make_entity_player(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE); -ENetPacket* make_dimension_info(const world::Dimension* dimension); -} // namespace protocol::utils - -struct protocol::StatusRequest final : public protocol::Base<0x0000> { - std::uint32_t version; -}; - -struct protocol::StatusResponse final : public protocol::Base<0x0001> { - std::uint32_t version; - std::uint16_t max_players; - std::uint16_t num_players; - std::string motd; -}; - -struct protocol::LoginRequest final : public protocol::Base<0x0002> { - std::uint32_t version; - std::uint64_t voxel_registry_checksum; - std::uint64_t item_registry_checksum; - std::uint64_t password_hash; - std::string username; -}; - -struct protocol::LoginResponse final : public protocol::Base<0x0003> { - std::uint16_t client_index; - std::uint64_t client_identity; - std::uint16_t server_tickrate; -}; - -struct protocol::Disconnect final : public protocol::Base<0x0004> { - std::string reason; -}; - -struct protocol::ChunkVoxels final : public protocol::Base<0x0005> { - chunk_pos chunk; - world::VoxelStorage voxels; -}; - -struct protocol::EntityTransform final : public protocol::Base<0x0006> { - entt::entity entity; - chunk_pos chunk; - glm::fvec3 local; - glm::fvec3 angles; -}; - -struct protocol::EntityHead final : public protocol::Base<0x0007> { - entt::entity entity; - glm::fvec3 angles; -}; - -struct protocol::EntityVelocity final : public protocol::Base<0x0008> { - entt::entity entity; - glm::fvec3 value; -}; - -struct protocol::SpawnPlayer final : public protocol::Base<0x0009> { - entt::entity entity; -}; - -struct protocol::ChatMessage final : public protocol::Base<0x000A> { - constexpr static std::uint16_t TEXT_MESSAGE = 0x0000; - constexpr static std::uint16_t PLAYER_JOIN = 0x0001; - constexpr static std::uint16_t PLAYER_LEAVE = 0x0002; - - std::uint16_t type; - std::string sender; - std::string message; -}; - -struct protocol::SetVoxel final : public protocol::Base<0x000B> { - voxel_pos vpos; - voxel_id voxel; - std::uint16_t flags; -}; - -struct protocol::RemoveEntity final : public protocol::Base<0x000C> { - entt::entity entity; -}; - -struct protocol::EntityPlayer final : public protocol::Base<0x000D> { - entt::entity entity; -}; - -struct protocol::ScoreboardUpdate final : public protocol::Base<0x000E> { - std::vector<std::string> names; -}; - -struct protocol::RequestChunk final : public protocol::Base<0x000F> { - chunk_pos cpos; -}; - -struct protocol::GenericSound final : public protocol::Base<0x0010> { - std::string sound; - bool looping; - float pitch; -}; - -struct protocol::EntitySound final : public protocol::Base<0x0011> { - entt::entity entity; - std::string sound; - bool looping; - float pitch; -}; - -struct protocol::DimensionInfo final : public protocol::Base<0x0012> { - std::string name; - float gravity; -}; +#pragma once
+
+#include "shared/world/chunk.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace protocol
+{
+constexpr static std::size_t MAX_CHAT = 16384;
+constexpr static std::size_t MAX_USERNAME = 64;
+constexpr static std::size_t MAX_SOUNDNAME = 1024;
+constexpr static std::uint16_t TICKRATE = 60;
+constexpr static std::uint16_t PORT = 43103;
+constexpr static std::uint32_t VERSION = 15;
+constexpr static std::uint8_t CHANNEL = 0;
+} // namespace protocol
+
+namespace protocol
+{
+template<std::uint16_t packet_id>
+struct Base {
+ constexpr static std::uint16_t ID = packet_id;
+ virtual ~Base(void) = default;
+ ENetPeer* peer { nullptr };
+};
+} // namespace protocol
+
+namespace protocol
+{
+struct StatusRequest;
+struct StatusResponse;
+struct LoginRequest;
+struct LoginResponse;
+struct Disconnect;
+struct ChunkVoxels;
+struct EntityTransform;
+struct EntityHead;
+struct EntityVelocity;
+struct SpawnPlayer;
+struct ChatMessage;
+struct SetVoxel;
+struct RemoveEntity;
+struct EntityPlayer;
+struct ScoreboardUpdate;
+struct RequestChunk;
+struct GenericSound;
+struct EntitySound;
+struct DimensionInfo;
+} // namespace protocol
+
+namespace protocol
+{
+ENetPacket* encode(const StatusRequest& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const StatusResponse& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const LoginRequest& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const LoginResponse& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const Disconnect& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const ChunkVoxels& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const EntityTransform& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const EntityHead& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const EntityVelocity& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const SpawnPlayer& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const ChatMessage& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const SetVoxel& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const RemoveEntity& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const EntityPlayer& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const ScoreboardUpdate& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const RequestChunk& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const GenericSound& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const EntitySound& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* encode(const DimensionInfo& packet, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+} // namespace protocol
+
+namespace protocol
+{
+void broadcast(ENetHost* host, ENetPacket* packet);
+void broadcast(ENetHost* host, ENetPacket* packet, ENetPeer* except);
+void send(ENetPeer* peer, ENetPacket* packet);
+} // namespace protocol
+
+namespace protocol
+{
+void decode(entt::dispatcher& dispatcher, const ENetPacket* packet, ENetPeer* peer);
+} // namespace protocol
+
+namespace protocol::utils
+{
+ENetPacket* make_disconnect(std::string_view reason, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* make_chat_message(std::string_view message, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+} // namespace protocol::utils
+
+namespace protocol::utils
+{
+ENetPacket* make_chunk_voxels(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+} // namespace protocol::utils
+
+namespace protocol::utils
+{
+ENetPacket* make_entity_head(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* make_entity_transform(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* make_entity_velocity(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* make_entity_player(world::Dimension* dimension, entt::entity entity, enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE);
+ENetPacket* make_dimension_info(const world::Dimension* dimension);
+} // namespace protocol::utils
+
+struct protocol::StatusRequest final : public protocol::Base<0x0000> {
+ std::uint32_t version;
+};
+
+struct protocol::StatusResponse final : public protocol::Base<0x0001> {
+ std::uint32_t version;
+ std::uint16_t max_players;
+ std::uint16_t num_players;
+ std::string motd;
+};
+
+struct protocol::LoginRequest final : public protocol::Base<0x0002> {
+ std::uint32_t version;
+ std::uint64_t voxel_registry_checksum;
+ std::uint64_t item_registry_checksum;
+ std::uint64_t password_hash;
+ std::string username;
+};
+
+struct protocol::LoginResponse final : public protocol::Base<0x0003> {
+ std::uint16_t client_index;
+ std::uint64_t client_identity;
+ std::uint16_t server_tickrate;
+};
+
+struct protocol::Disconnect final : public protocol::Base<0x0004> {
+ std::string reason;
+};
+
+struct protocol::ChunkVoxels final : public protocol::Base<0x0005> {
+ chunk_pos chunk;
+ world::VoxelStorage voxels;
+};
+
+struct protocol::EntityTransform final : public protocol::Base<0x0006> {
+ entt::entity entity;
+ chunk_pos chunk;
+ glm::fvec3 local;
+ glm::fvec3 angles;
+};
+
+struct protocol::EntityHead final : public protocol::Base<0x0007> {
+ entt::entity entity;
+ glm::fvec3 angles;
+};
+
+struct protocol::EntityVelocity final : public protocol::Base<0x0008> {
+ entt::entity entity;
+ glm::fvec3 value;
+};
+
+struct protocol::SpawnPlayer final : public protocol::Base<0x0009> {
+ entt::entity entity;
+};
+
+struct protocol::ChatMessage final : public protocol::Base<0x000A> {
+ constexpr static std::uint16_t TEXT_MESSAGE = 0x0000;
+ constexpr static std::uint16_t PLAYER_JOIN = 0x0001;
+ constexpr static std::uint16_t PLAYER_LEAVE = 0x0002;
+
+ std::uint16_t type;
+ std::string sender;
+ std::string message;
+};
+
+struct protocol::SetVoxel final : public protocol::Base<0x000B> {
+ voxel_pos vpos;
+ voxel_id voxel;
+ std::uint16_t flags;
+};
+
+struct protocol::RemoveEntity final : public protocol::Base<0x000C> {
+ entt::entity entity;
+};
+
+struct protocol::EntityPlayer final : public protocol::Base<0x000D> {
+ entt::entity entity;
+};
+
+struct protocol::ScoreboardUpdate final : public protocol::Base<0x000E> {
+ std::vector<std::string> names;
+};
+
+struct protocol::RequestChunk final : public protocol::Base<0x000F> {
+ chunk_pos cpos;
+};
+
+struct protocol::GenericSound final : public protocol::Base<0x0010> {
+ std::string sound;
+ bool looping;
+ float pitch;
+};
+
+struct protocol::EntitySound final : public protocol::Base<0x0011> {
+ entt::entity entity;
+ std::string sound;
+ bool looping;
+ float pitch;
+};
+
+struct protocol::DimensionInfo final : public protocol::Base<0x0012> {
+ std::string name;
+ float gravity;
+};
diff --git a/game/shared/splash.cc b/game/shared/splash.cc index d5b0a6e..7515358 100644 --- a/game/shared/splash.cc +++ b/game/shared/splash.cc @@ -1,65 +1,65 @@ -#include "shared/pch.hh" - -#include "shared/splash.hh" - -constexpr static std::string_view SPLASHES_FILENAME_CLIENT = "misc/splashes_client.txt"; -constexpr static std::string_view SPLASHES_FILENAME_SERVER = "misc/splashes_server.txt"; -constexpr static std::size_t SPLASH_SERVER_MAX_LENGTH = 32; - -static std::mt19937_64 splash_random; -static std::vector<std::string> splash_lines; - -static std::string sanitize_line(const std::string& line) -{ - std::string result; - - for(auto chr : line) { - if(chr != '\r' && chr != '\n') { - result.push_back(chr); - } - } - - return result; -} - -static void splash_init_filename(std::string_view filename) -{ - if(auto file = PHYSFS_openRead(std::string(filename).c_str())) { - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - std::string line; - std::istringstream stream(source); - - while(std::getline(stream, line)) - splash_lines.push_back(sanitize_line(line)); - splash_random.seed(std::random_device()()); - } - else { - splash_lines.push_back(std::format("{}: {}", filename, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))); - splash_random.seed(std::random_device()()); - } -} - -void splash::init_client(void) -{ - splash_init_filename(SPLASHES_FILENAME_CLIENT); -} - -void splash::init_server(void) -{ - splash_init_filename(SPLASHES_FILENAME_SERVER); - - // Server browser GUI should be able to display - // these splash messages without text clipping over - for(int i = 0; i < splash_lines.size(); i++) { - splash_lines[i] = splash_lines[i].substr(0, SPLASH_SERVER_MAX_LENGTH); - } -} - -std::string_view splash::get(void) -{ - std::uniform_int_distribution<std::size_t> dist(0, splash_lines.size() - 1); - return splash_lines.at(dist(splash_random)); -} +#include "shared/pch.hh"
+
+#include "shared/splash.hh"
+
+constexpr static std::string_view SPLASHES_FILENAME_CLIENT = "misc/splashes_client.txt";
+constexpr static std::string_view SPLASHES_FILENAME_SERVER = "misc/splashes_server.txt";
+constexpr static std::size_t SPLASH_SERVER_MAX_LENGTH = 32;
+
+static std::mt19937_64 splash_random;
+static std::vector<std::string> splash_lines;
+
+static std::string sanitize_line(const std::string& line)
+{
+ std::string result;
+
+ for(auto chr : line) {
+ if(chr != '\r' && chr != '\n') {
+ result.push_back(chr);
+ }
+ }
+
+ return result;
+}
+
+static void splash_init_filename(std::string_view filename)
+{
+ if(auto file = PHYSFS_openRead(std::string(filename).c_str())) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::string line;
+ std::istringstream stream(source);
+
+ while(std::getline(stream, line))
+ splash_lines.push_back(sanitize_line(line));
+ splash_random.seed(std::random_device()());
+ }
+ else {
+ splash_lines.push_back(std::format("{}: {}", filename, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
+ splash_random.seed(std::random_device()());
+ }
+}
+
+void splash::init_client(void)
+{
+ splash_init_filename(SPLASHES_FILENAME_CLIENT);
+}
+
+void splash::init_server(void)
+{
+ splash_init_filename(SPLASHES_FILENAME_SERVER);
+
+ // Server browser GUI should be able to display
+ // these splash messages without text clipping over
+ for(int i = 0; i < splash_lines.size(); i++) {
+ splash_lines[i] = splash_lines[i].substr(0, SPLASH_SERVER_MAX_LENGTH);
+ }
+}
+
+std::string_view splash::get(void)
+{
+ std::uniform_int_distribution<std::size_t> dist(0, splash_lines.size() - 1);
+ return splash_lines.at(dist(splash_random));
+}
diff --git a/game/shared/splash.hh b/game/shared/splash.hh index be80cd6..2572d53 100644 --- a/game/shared/splash.hh +++ b/game/shared/splash.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace splash -{ -void init_client(void); -void init_server(void); -std::string_view get(void); -} // namespace splash +#pragma once
+
+namespace splash
+{
+void init_client(void);
+void init_server(void);
+std::string_view get(void);
+} // namespace splash
diff --git a/game/shared/types.hh b/game/shared/types.hh index 792ed0f..332eede 100644 --- a/game/shared/types.hh +++ b/game/shared/types.hh @@ -1,40 +1,40 @@ -#pragma once - -using item_id = std::uint32_t; -constexpr static item_id NULL_ITEM_ID = UINT32_C(0x00000000); -constexpr static item_id MAX_ITEM_ID = UINT32_C(0xFFFFFFFF); - -using voxel_id = std::uint16_t; -constexpr static voxel_id NULL_VOXEL_ID = UINT16_C(0x0000); -constexpr static voxel_id MAX_VOXEL_ID = UINT16_C(0xFFFF); - -using chunk_pos = glm::vec<3, std::int32_t>; -using local_pos = glm::vec<3, std::int16_t>; -using voxel_pos = glm::vec<3, std::int64_t>; - -using chunk_pos_xz = glm::vec<2, chunk_pos::value_type>; -using local_pos_xz = glm::vec<2, local_pos::value_type>; -using voxel_pos_xz = glm::vec<2, local_pos::value_type>; - -template<> -struct std::hash<chunk_pos> final { - constexpr inline std::size_t operator()(const chunk_pos& cpos) const - { - std::size_t value = 0; - value ^= cpos.x * 73856093; - value ^= cpos.y * 19349663; - value ^= cpos.z * 83492791; - return value; - } -}; - -template<> -struct std::hash<chunk_pos_xz> final { - constexpr inline std::size_t operator()(const chunk_pos_xz& cwpos) const - { - std::size_t value = 0; - value ^= cwpos.x * 73856093; - value ^= cwpos.y * 19349663; - return value; - } -}; +#pragma once
+
+using item_id = std::uint32_t;
+constexpr static item_id NULL_ITEM_ID = UINT32_C(0x00000000);
+constexpr static item_id MAX_ITEM_ID = UINT32_C(0xFFFFFFFF);
+
+using voxel_id = std::uint16_t;
+constexpr static voxel_id NULL_VOXEL_ID = UINT16_C(0x0000);
+constexpr static voxel_id MAX_VOXEL_ID = UINT16_C(0xFFFF);
+
+using chunk_pos = glm::vec<3, std::int32_t>;
+using local_pos = glm::vec<3, std::int16_t>;
+using voxel_pos = glm::vec<3, std::int64_t>;
+
+using chunk_pos_xz = glm::vec<2, chunk_pos::value_type>;
+using local_pos_xz = glm::vec<2, local_pos::value_type>;
+using voxel_pos_xz = glm::vec<2, local_pos::value_type>;
+
+template<>
+struct std::hash<chunk_pos> final {
+ constexpr inline std::size_t operator()(const chunk_pos& cpos) const
+ {
+ std::size_t value = 0;
+ value ^= cpos.x * 73856093;
+ value ^= cpos.y * 19349663;
+ value ^= cpos.z * 83492791;
+ return value;
+ }
+};
+
+template<>
+struct std::hash<chunk_pos_xz> final {
+ constexpr inline std::size_t operator()(const chunk_pos_xz& cwpos) const
+ {
+ std::size_t value = 0;
+ value ^= cwpos.x * 73856093;
+ value ^= cwpos.y * 19349663;
+ return value;
+ }
+};
diff --git a/game/shared/world/chunk.cc b/game/shared/world/chunk.cc index f689fc1..e59b68d 100644 --- a/game/shared/world/chunk.cc +++ b/game/shared/world/chunk.cc @@ -1,70 +1,70 @@ -#include "shared/pch.hh" - -#include "shared/world/chunk.hh" - -#include "shared/coord.hh" - -world::Chunk::Chunk(entt::entity entity, Dimension* dimension) -{ - m_entity = entity; - m_dimension = dimension; - m_voxels.fill(NULL_VOXEL_ID); - m_biome = BIOME_VOID; -} - -voxel_id world::Chunk::get_voxel(const local_pos& lpos) const -{ - return get_voxel(coord::to_index(lpos)); -} - -voxel_id world::Chunk::get_voxel(const std::size_t index) const -{ - if(index >= CHUNK_VOLUME) { - return NULL_VOXEL_ID; - } - else { - return m_voxels[index]; - } -} - -void world::Chunk::set_voxel(voxel_id voxel, const local_pos& lpos) -{ - set_voxel(voxel, coord::to_index(lpos)); -} - -void world::Chunk::set_voxel(voxel_id voxel, const std::size_t index) -{ - if(index < CHUNK_VOLUME) { - m_voxels[index] = voxel; - } -} - -const world::VoxelStorage& world::Chunk::get_voxels(void) const -{ - return m_voxels; -} - -void world::Chunk::set_voxels(const VoxelStorage& voxels) -{ - m_voxels = voxels; -} - -unsigned int world::Chunk::get_biome(void) const -{ - return m_biome; -} - -void world::Chunk::set_biome(unsigned int biome) -{ - m_biome = biome; -} - -entt::entity world::Chunk::get_entity(void) const -{ - return m_entity; -} - -world::Dimension* world::Chunk::get_dimension(void) const -{ - return m_dimension; -} +#include "shared/pch.hh"
+
+#include "shared/world/chunk.hh"
+
+#include "shared/coord.hh"
+
+world::Chunk::Chunk(entt::entity entity, Dimension* dimension)
+{
+ m_entity = entity;
+ m_dimension = dimension;
+ m_voxels.fill(NULL_VOXEL_ID);
+ m_biome = BIOME_VOID;
+}
+
+voxel_id world::Chunk::get_voxel(const local_pos& lpos) const
+{
+ return get_voxel(coord::to_index(lpos));
+}
+
+voxel_id world::Chunk::get_voxel(const std::size_t index) const
+{
+ if(index >= CHUNK_VOLUME) {
+ return NULL_VOXEL_ID;
+ }
+ else {
+ return m_voxels[index];
+ }
+}
+
+void world::Chunk::set_voxel(voxel_id voxel, const local_pos& lpos)
+{
+ set_voxel(voxel, coord::to_index(lpos));
+}
+
+void world::Chunk::set_voxel(voxel_id voxel, const std::size_t index)
+{
+ if(index < CHUNK_VOLUME) {
+ m_voxels[index] = voxel;
+ }
+}
+
+const world::VoxelStorage& world::Chunk::get_voxels(void) const
+{
+ return m_voxels;
+}
+
+void world::Chunk::set_voxels(const VoxelStorage& voxels)
+{
+ m_voxels = voxels;
+}
+
+unsigned int world::Chunk::get_biome(void) const
+{
+ return m_biome;
+}
+
+void world::Chunk::set_biome(unsigned int biome)
+{
+ m_biome = biome;
+}
+
+entt::entity world::Chunk::get_entity(void) const
+{
+ return m_entity;
+}
+
+world::Dimension* world::Chunk::get_dimension(void) const
+{
+ return m_dimension;
+}
diff --git a/game/shared/world/chunk.hh b/game/shared/world/chunk.hh index 439694c..c5bba12 100644 --- a/game/shared/world/chunk.hh +++ b/game/shared/world/chunk.hh @@ -1,42 +1,42 @@ -#pragma once - -#include "shared/world/voxel_storage.hh" - -#include "shared/types.hh" - -constexpr static unsigned int BIOME_VOID = 0U; - -namespace world -{ -class Dimension; -} // namespace world - -namespace world -{ -class Chunk final { -public: - explicit Chunk(entt::entity entity, Dimension* dimension); - virtual ~Chunk(void) = default; - - voxel_id get_voxel(const local_pos& lpos) const; - voxel_id get_voxel(const std::size_t index) const; - - void set_voxel(voxel_id voxel, const local_pos& lpos); - void set_voxel(voxel_id voxel, const std::size_t index); - - const VoxelStorage& get_voxels(void) const; - void set_voxels(const VoxelStorage& voxels); - - unsigned int get_biome(void) const; - void set_biome(unsigned int biome); - - entt::entity get_entity(void) const; - Dimension* get_dimension(void) const; - -private: - entt::entity m_entity; - Dimension* m_dimension; - VoxelStorage m_voxels; - unsigned int m_biome; -}; -} // namespace world +#pragma once
+
+#include "shared/world/voxel_storage.hh"
+
+#include "shared/types.hh"
+
+constexpr static unsigned int BIOME_VOID = 0U;
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace world
+{
+class Chunk final {
+public:
+ explicit Chunk(entt::entity entity, Dimension* dimension);
+ virtual ~Chunk(void) = default;
+
+ voxel_id get_voxel(const local_pos& lpos) const;
+ voxel_id get_voxel(const std::size_t index) const;
+
+ void set_voxel(voxel_id voxel, const local_pos& lpos);
+ void set_voxel(voxel_id voxel, const std::size_t index);
+
+ const VoxelStorage& get_voxels(void) const;
+ void set_voxels(const VoxelStorage& voxels);
+
+ unsigned int get_biome(void) const;
+ void set_biome(unsigned int biome);
+
+ entt::entity get_entity(void) const;
+ Dimension* get_dimension(void) const;
+
+private:
+ entt::entity m_entity;
+ Dimension* m_dimension;
+ VoxelStorage m_voxels;
+ unsigned int m_biome;
+};
+} // namespace world
diff --git a/game/shared/world/chunk_aabb.cc b/game/shared/world/chunk_aabb.cc index c0e540e..634b230 100644 --- a/game/shared/world/chunk_aabb.cc +++ b/game/shared/world/chunk_aabb.cc @@ -1,54 +1,54 @@ -#include "shared/pch.hh" - -#include "shared/world/chunk_aabb.hh" - -void world::ChunkAABB::set_bounds(const chunk_pos& min, const chunk_pos& max) -{ - this->min = min; - this->max = max; -} - -void world::ChunkAABB::set_offset(const chunk_pos& base, const chunk_pos& size) -{ - this->min = base; - this->max = base + size; -} - -bool world::ChunkAABB::contains(const chunk_pos& point) const -{ - auto result = true; - result = result && (point.x >= min.x) && (point.x <= max.x); - result = result && (point.y >= min.y) && (point.y <= max.y); - result = result && (point.z >= min.z) && (point.z <= max.z); - return result; -} - -bool world::ChunkAABB::intersect(const ChunkAABB& other_box) const -{ - auto result = true; - result = result && (min.x < other_box.max.x) && (max.x > other_box.min.x); - result = result && (min.y < other_box.max.y) && (max.y > other_box.min.y); - result = result && (min.z < other_box.max.z) && (max.z > other_box.min.z); - return result; -} - -world::ChunkAABB world::ChunkAABB::combine_with(const ChunkAABB& other_box) const -{ - ChunkAABB result; - result.set_bounds(min, other_box.max); - return result; -} - -world::ChunkAABB world::ChunkAABB::multiply_with(const ChunkAABB& other_box) const -{ - ChunkAABB result; - result.set_bounds(other_box.min, max); - return result; -} - -world::ChunkAABB world::ChunkAABB::push(const chunk_pos& vector) const -{ - ChunkAABB result; - result.set_bounds(min + vector, max + vector); - return result; -} +#include "shared/pch.hh"
+
+#include "shared/world/chunk_aabb.hh"
+
+void world::ChunkAABB::set_bounds(const chunk_pos& min, const chunk_pos& max)
+{
+ this->min = min;
+ this->max = max;
+}
+
+void world::ChunkAABB::set_offset(const chunk_pos& base, const chunk_pos& size)
+{
+ this->min = base;
+ this->max = base + size;
+}
+
+bool world::ChunkAABB::contains(const chunk_pos& point) const
+{
+ auto result = true;
+ result = result && (point.x >= min.x) && (point.x <= max.x);
+ result = result && (point.y >= min.y) && (point.y <= max.y);
+ result = result && (point.z >= min.z) && (point.z <= max.z);
+ return result;
+}
+
+bool world::ChunkAABB::intersect(const ChunkAABB& other_box) const
+{
+ auto result = true;
+ result = result && (min.x < other_box.max.x) && (max.x > other_box.min.x);
+ result = result && (min.y < other_box.max.y) && (max.y > other_box.min.y);
+ result = result && (min.z < other_box.max.z) && (max.z > other_box.min.z);
+ return result;
+}
+
+world::ChunkAABB world::ChunkAABB::combine_with(const ChunkAABB& other_box) const
+{
+ ChunkAABB result;
+ result.set_bounds(min, other_box.max);
+ return result;
+}
+
+world::ChunkAABB world::ChunkAABB::multiply_with(const ChunkAABB& other_box) const
+{
+ ChunkAABB result;
+ result.set_bounds(other_box.min, max);
+ return result;
+}
+
+world::ChunkAABB world::ChunkAABB::push(const chunk_pos& vector) const
+{
+ ChunkAABB result;
+ result.set_bounds(min + vector, max + vector);
+ return result;
+}
diff --git a/game/shared/world/chunk_aabb.hh b/game/shared/world/chunk_aabb.hh index bd4a0c5..a9e0205 100644 --- a/game/shared/world/chunk_aabb.hh +++ b/game/shared/world/chunk_aabb.hh @@ -1,33 +1,33 @@ -#ifndef SHARED_CHUNK_AABB -#define SHARED_CHUNK_AABB 1 -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class ChunkAABB final { -public: - ChunkAABB(void) = default; - virtual ~ChunkAABB(void) = default; - - void set_bounds(const chunk_pos& min, const chunk_pos& max); - void set_offset(const chunk_pos& base, const chunk_pos& size); - - const chunk_pos& get_min(void) const; - const chunk_pos& get_max(void) const; - - bool contains(const chunk_pos& point) const; - bool intersect(const ChunkAABB& other_box) const; - - ChunkAABB combine_with(const ChunkAABB& other_box) const; - ChunkAABB multiply_with(const ChunkAABB& other_box) const; - ChunkAABB push(const chunk_pos& vector) const; - -public: - chunk_pos min; - chunk_pos max; -}; -} // namespace world - -#endif // SHARED_CHUNK_AABB +#ifndef SHARED_CHUNK_AABB
+#define SHARED_CHUNK_AABB 1
+#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class ChunkAABB final {
+public:
+ ChunkAABB(void) = default;
+ virtual ~ChunkAABB(void) = default;
+
+ void set_bounds(const chunk_pos& min, const chunk_pos& max);
+ void set_offset(const chunk_pos& base, const chunk_pos& size);
+
+ const chunk_pos& get_min(void) const;
+ const chunk_pos& get_max(void) const;
+
+ bool contains(const chunk_pos& point) const;
+ bool intersect(const ChunkAABB& other_box) const;
+
+ ChunkAABB combine_with(const ChunkAABB& other_box) const;
+ ChunkAABB multiply_with(const ChunkAABB& other_box) const;
+ ChunkAABB push(const chunk_pos& vector) const;
+
+public:
+ chunk_pos min;
+ chunk_pos max;
+};
+} // namespace world
+
+#endif // SHARED_CHUNK_AABB
diff --git a/game/shared/world/dimension.cc b/game/shared/world/dimension.cc index 2d193fc..dd28449 100644 --- a/game/shared/world/dimension.cc +++ b/game/shared/world/dimension.cc @@ -1,173 +1,173 @@ -#include "shared/pch.hh" - -#include "shared/world/dimension.hh" - -#include "shared/world/chunk.hh" - -#include "shared/coord.hh" -#include "shared/globals.hh" - -world::Dimension::Dimension(std::string_view name, float gravity) -{ - m_name = name; - m_gravity = gravity; -} - -world::Dimension::~Dimension(void) -{ - for(const auto it : m_chunkmap) - delete it.second; - entities.clear(); - chunks.clear(); -} - -std::string_view world::Dimension::get_name(void) const -{ - return m_name; -} - -float world::Dimension::get_gravity(void) const -{ - return m_gravity; -} - -world::Chunk* world::Dimension::create_chunk(const chunk_pos& cpos) -{ - auto it = m_chunkmap.find(cpos); - - if(it != m_chunkmap.cend()) { - // Chunk already exists - return it->second; - } - - auto entity = chunks.create(); - auto chunk = new Chunk(entity, this); - - auto& component = chunks.emplace<ChunkComponent>(entity); - component.chunk = chunk; - component.cpos = cpos; - - ChunkCreateEvent event; - event.dimension = this; - event.chunk = chunk; - event.cpos = cpos; - - globals::dispatcher.trigger(event); - - return m_chunkmap.insert_or_assign(cpos, std::move(chunk)).first->second; -} - -world::Chunk* world::Dimension::find_chunk(entt::entity entity) const -{ - if(chunks.valid(entity)) { - return chunks.get<ChunkComponent>(entity).chunk; - } - else { - return nullptr; - } -} - -world::Chunk* world::Dimension::find_chunk(const chunk_pos& cpos) const -{ - auto it = m_chunkmap.find(cpos); - - if(it != m_chunkmap.cend()) { - return it->second; - } - else { - return nullptr; - } -} - -void world::Dimension::remove_chunk(entt::entity entity) -{ - if(chunks.valid(entity)) { - auto& component = chunks.get<ChunkComponent>(entity); - m_chunkmap.erase(component.cpos); - chunks.destroy(entity); - } -} - -void world::Dimension::remove_chunk(const chunk_pos& cpos) -{ - auto it = m_chunkmap.find(cpos); - - if(it != m_chunkmap.cend()) { - chunks.destroy(it->second->get_entity()); - m_chunkmap.erase(it); - } -} - -void world::Dimension::remove_chunk(Chunk* chunk) -{ - if(chunk) { - const auto& component = chunks.get<ChunkComponent>(chunk->get_entity()); - m_chunkmap.erase(component.cpos); - chunks.destroy(chunk->get_entity()); - } -} - -voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const -{ - auto cpos = coord::to_chunk(vpos); - auto lpos = coord::to_local(vpos); - - if(auto chunk = find_chunk(cpos)) { - return chunk->get_voxel(lpos); - } - else { - return NULL_VOXEL_ID; - } -} - -voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const -{ - // This allows accessing get_voxel with negative - // local coordinates that usually would result in an - // out-of-range values; this is useful for per-voxel update logic - return get_voxel(coord::to_voxel(cpos, lpos)); -} - -bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos) -{ - auto cpos = coord::to_chunk(vpos); - auto lpos = coord::to_local(vpos); - - if(auto chunk = find_chunk(cpos)) { - chunk->set_voxel(voxel, lpos); - - VoxelSetEvent event; - event.dimension = this; - event.cpos = cpos; - event.lpos = lpos; - event.voxel = voxel; - event.chunk = chunk; - - globals::dispatcher.trigger(event); - - return true; - } - - return false; -} - -bool world::Dimension::set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos) -{ - // This allows accessing set_voxel with negative - // local coordinates that usually would result in an - // out-of-range values; this is useful for per-voxel update logic - return set_voxel(voxel, coord::to_voxel(cpos, lpos)); -} - -void world::Dimension::init(io::ConfigMap& config) -{ -} - -void world::Dimension::init_late(std::uint64_t global_seed) -{ -} - -bool world::Dimension::generate(const chunk_pos& cpos, VoxelStorage& voxels) -{ - return false; -} +#include "shared/pch.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/world/chunk.hh"
+
+#include "shared/coord.hh"
+#include "shared/globals.hh"
+
+world::Dimension::Dimension(std::string_view name, float gravity)
+{
+ m_name = name;
+ m_gravity = gravity;
+}
+
+world::Dimension::~Dimension(void)
+{
+ for(const auto it : m_chunkmap)
+ delete it.second;
+ entities.clear();
+ chunks.clear();
+}
+
+std::string_view world::Dimension::get_name(void) const
+{
+ return m_name;
+}
+
+float world::Dimension::get_gravity(void) const
+{
+ return m_gravity;
+}
+
+world::Chunk* world::Dimension::create_chunk(const chunk_pos& cpos)
+{
+ auto it = m_chunkmap.find(cpos);
+
+ if(it != m_chunkmap.cend()) {
+ // Chunk already exists
+ return it->second;
+ }
+
+ auto entity = chunks.create();
+ auto chunk = new Chunk(entity, this);
+
+ auto& component = chunks.emplace<ChunkComponent>(entity);
+ component.chunk = chunk;
+ component.cpos = cpos;
+
+ ChunkCreateEvent event;
+ event.dimension = this;
+ event.chunk = chunk;
+ event.cpos = cpos;
+
+ globals::dispatcher.trigger(event);
+
+ return m_chunkmap.insert_or_assign(cpos, std::move(chunk)).first->second;
+}
+
+world::Chunk* world::Dimension::find_chunk(entt::entity entity) const
+{
+ if(chunks.valid(entity)) {
+ return chunks.get<ChunkComponent>(entity).chunk;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+world::Chunk* world::Dimension::find_chunk(const chunk_pos& cpos) const
+{
+ auto it = m_chunkmap.find(cpos);
+
+ if(it != m_chunkmap.cend()) {
+ return it->second;
+ }
+ else {
+ return nullptr;
+ }
+}
+
+void world::Dimension::remove_chunk(entt::entity entity)
+{
+ if(chunks.valid(entity)) {
+ auto& component = chunks.get<ChunkComponent>(entity);
+ m_chunkmap.erase(component.cpos);
+ chunks.destroy(entity);
+ }
+}
+
+void world::Dimension::remove_chunk(const chunk_pos& cpos)
+{
+ auto it = m_chunkmap.find(cpos);
+
+ if(it != m_chunkmap.cend()) {
+ chunks.destroy(it->second->get_entity());
+ m_chunkmap.erase(it);
+ }
+}
+
+void world::Dimension::remove_chunk(Chunk* chunk)
+{
+ if(chunk) {
+ const auto& component = chunks.get<ChunkComponent>(chunk->get_entity());
+ m_chunkmap.erase(component.cpos);
+ chunks.destroy(chunk->get_entity());
+ }
+}
+
+voxel_id world::Dimension::get_voxel(const voxel_pos& vpos) const
+{
+ auto cpos = coord::to_chunk(vpos);
+ auto lpos = coord::to_local(vpos);
+
+ if(auto chunk = find_chunk(cpos)) {
+ return chunk->get_voxel(lpos);
+ }
+ else {
+ return NULL_VOXEL_ID;
+ }
+}
+
+voxel_id world::Dimension::get_voxel(const chunk_pos& cpos, const local_pos& lpos) const
+{
+ // This allows accessing get_voxel with negative
+ // local coordinates that usually would result in an
+ // out-of-range values; this is useful for per-voxel update logic
+ return get_voxel(coord::to_voxel(cpos, lpos));
+}
+
+bool world::Dimension::set_voxel(voxel_id voxel, const voxel_pos& vpos)
+{
+ auto cpos = coord::to_chunk(vpos);
+ auto lpos = coord::to_local(vpos);
+
+ if(auto chunk = find_chunk(cpos)) {
+ chunk->set_voxel(voxel, lpos);
+
+ VoxelSetEvent event;
+ event.dimension = this;
+ event.cpos = cpos;
+ event.lpos = lpos;
+ event.voxel = voxel;
+ event.chunk = chunk;
+
+ globals::dispatcher.trigger(event);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool world::Dimension::set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos)
+{
+ // This allows accessing set_voxel with negative
+ // local coordinates that usually would result in an
+ // out-of-range values; this is useful for per-voxel update logic
+ return set_voxel(voxel, coord::to_voxel(cpos, lpos));
+}
+
+void world::Dimension::init(io::ConfigMap& config)
+{
+}
+
+void world::Dimension::init_late(std::uint64_t global_seed)
+{
+}
+
+bool world::Dimension::generate(const chunk_pos& cpos, VoxelStorage& voxels)
+{
+ return false;
+}
diff --git a/game/shared/world/dimension.hh b/game/shared/world/dimension.hh index 8b6691d..bf9bfe1 100644 --- a/game/shared/world/dimension.hh +++ b/game/shared/world/dimension.hh @@ -1,100 +1,100 @@ -#pragma once - -#include "shared/const.hh" -#include "shared/types.hh" - -namespace io -{ -class ConfigMap; -} // namespace io - -namespace world -{ -class Chunk; -class VoxelStorage; -} // namespace world - -namespace world -{ -using dimension_entropy_map = std::array<std::uint64_t, CHUNK_AREA>; -using dimension_height_map = std::array<voxel_pos::value_type, CHUNK_AREA>; -} // namespace world - -namespace world -{ -class Dimension { -public: - explicit Dimension(std::string_view name, float gravity); - virtual ~Dimension(void); - - std::string_view get_name(void) const; - float get_gravity(void) const; - -public: - Chunk* create_chunk(const chunk_pos& cpos); - Chunk* find_chunk(entt::entity entity) const; - Chunk* find_chunk(const chunk_pos& cpos) const; - - void remove_chunk(entt::entity entity); - void remove_chunk(const chunk_pos& cpos); - void remove_chunk(Chunk* chunk); - -public: - voxel_id get_voxel(const voxel_pos& vpos) const; - voxel_id get_voxel(const chunk_pos& cpos, const local_pos& lpos) const; - - bool set_voxel(voxel_id voxel, const voxel_pos& vpos); - bool set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos); - -public: - virtual void init(io::ConfigMap& config); - virtual void init_late(std::uint64_t global_seed); - virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels); - -public: - entt::registry chunks; - entt::registry entities; - -private: - std::string m_name; - emhash8::HashMap<chunk_pos, Chunk*> m_chunkmap; - float m_gravity; -}; -} // namespace world - -namespace world -{ -struct ChunkComponent final { - chunk_pos cpos; - Chunk* chunk; -}; -} // namespace world - -namespace world -{ -struct ChunkCreateEvent final { - Dimension* dimension; - chunk_pos cpos; - Chunk* chunk; -}; - -struct ChunkDestroyEvent final { - Dimension* dimension; - chunk_pos cpos; - Chunk* chunk; -}; - -struct ChunkUpdateEvent final { - Dimension* dimension; - chunk_pos cpos; - Chunk* chunk; -}; - -struct VoxelSetEvent final { - Dimension* dimension; - chunk_pos cpos; - local_pos lpos; - voxel_id voxel; - Chunk* chunk; -}; -} // namespace world +#pragma once
+
+#include "shared/const.hh"
+#include "shared/types.hh"
+
+namespace io
+{
+class ConfigMap;
+} // namespace io
+
+namespace world
+{
+class Chunk;
+class VoxelStorage;
+} // namespace world
+
+namespace world
+{
+using dimension_entropy_map = std::array<std::uint64_t, CHUNK_AREA>;
+using dimension_height_map = std::array<voxel_pos::value_type, CHUNK_AREA>;
+} // namespace world
+
+namespace world
+{
+class Dimension {
+public:
+ explicit Dimension(std::string_view name, float gravity);
+ virtual ~Dimension(void);
+
+ std::string_view get_name(void) const;
+ float get_gravity(void) const;
+
+public:
+ Chunk* create_chunk(const chunk_pos& cpos);
+ Chunk* find_chunk(entt::entity entity) const;
+ Chunk* find_chunk(const chunk_pos& cpos) const;
+
+ void remove_chunk(entt::entity entity);
+ void remove_chunk(const chunk_pos& cpos);
+ void remove_chunk(Chunk* chunk);
+
+public:
+ voxel_id get_voxel(const voxel_pos& vpos) const;
+ voxel_id get_voxel(const chunk_pos& cpos, const local_pos& lpos) const;
+
+ bool set_voxel(voxel_id voxel, const voxel_pos& vpos);
+ bool set_voxel(voxel_id voxel, const chunk_pos& cpos, const local_pos& lpos);
+
+public:
+ virtual void init(io::ConfigMap& config);
+ virtual void init_late(std::uint64_t global_seed);
+ virtual bool generate(const chunk_pos& cpos, VoxelStorage& voxels);
+
+public:
+ entt::registry chunks;
+ entt::registry entities;
+
+private:
+ std::string m_name;
+ emhash8::HashMap<chunk_pos, Chunk*> m_chunkmap;
+ float m_gravity;
+};
+} // namespace world
+
+namespace world
+{
+struct ChunkComponent final {
+ chunk_pos cpos;
+ Chunk* chunk;
+};
+} // namespace world
+
+namespace world
+{
+struct ChunkCreateEvent final {
+ Dimension* dimension;
+ chunk_pos cpos;
+ Chunk* chunk;
+};
+
+struct ChunkDestroyEvent final {
+ Dimension* dimension;
+ chunk_pos cpos;
+ Chunk* chunk;
+};
+
+struct ChunkUpdateEvent final {
+ Dimension* dimension;
+ chunk_pos cpos;
+ Chunk* chunk;
+};
+
+struct VoxelSetEvent final {
+ Dimension* dimension;
+ chunk_pos cpos;
+ local_pos lpos;
+ voxel_id voxel;
+ Chunk* chunk;
+};
+} // namespace world
diff --git a/game/shared/world/feature.cc b/game/shared/world/feature.cc index cbdc183..4212043 100644 --- a/game/shared/world/feature.cc +++ b/game/shared/world/feature.cc @@ -1,53 +1,53 @@ -#include "shared/pch.hh" - -#include "shared/world/feature.hh" - -#include "shared/world/chunk.hh" -#include "shared/world/dimension.hh" -#include "shared/world/voxel_storage.hh" - -#include "shared/coord.hh" - -void world::Feature::place(const voxel_pos& vpos, Dimension* dimension) const -{ - for(const auto [rpos, voxel, overwrite] : (*this)) { - auto it_vpos = vpos + rpos; - auto it_cpos = coord::to_chunk(it_vpos); - - if(auto chunk = dimension->create_chunk(it_cpos)) { - auto it_lpos = coord::to_local(it_vpos); - auto it_index = coord::to_index(it_lpos); - - if(chunk->get_voxel(it_index) && !overwrite) { - // There is something in the way - // and the called intentionally requested - // we do not force feature to overwrite voxels - continue; - } - - chunk->set_voxel(voxel, it_index); - } - } -} - -void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const -{ - for(const auto [rpos, voxel, overwrite] : (*this)) { - auto it_vpos = vpos + rpos; - auto it_cpos = coord::to_chunk(it_vpos); - - if(it_cpos == cpos) { - auto it_lpos = coord::to_local(it_vpos); - auto it_index = coord::to_index(it_lpos); - - if(voxels[it_index] && !overwrite) { - // There is something in the way - // and the called intentionally requested - // we do not force feature to overwrite voxels - continue; - } - - voxels[it_index] = voxel; - } - } -} +#include "shared/pch.hh"
+
+#include "shared/world/feature.hh"
+
+#include "shared/world/chunk.hh"
+#include "shared/world/dimension.hh"
+#include "shared/world/voxel_storage.hh"
+
+#include "shared/coord.hh"
+
+void world::Feature::place(const voxel_pos& vpos, Dimension* dimension) const
+{
+ for(const auto [rpos, voxel, overwrite] : (*this)) {
+ auto it_vpos = vpos + rpos;
+ auto it_cpos = coord::to_chunk(it_vpos);
+
+ if(auto chunk = dimension->create_chunk(it_cpos)) {
+ auto it_lpos = coord::to_local(it_vpos);
+ auto it_index = coord::to_index(it_lpos);
+
+ if(chunk->get_voxel(it_index) && !overwrite) {
+ // There is something in the way
+ // and the called intentionally requested
+ // we do not force feature to overwrite voxels
+ continue;
+ }
+
+ chunk->set_voxel(voxel, it_index);
+ }
+ }
+}
+
+void world::Feature::place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const
+{
+ for(const auto [rpos, voxel, overwrite] : (*this)) {
+ auto it_vpos = vpos + rpos;
+ auto it_cpos = coord::to_chunk(it_vpos);
+
+ if(it_cpos == cpos) {
+ auto it_lpos = coord::to_local(it_vpos);
+ auto it_index = coord::to_index(it_lpos);
+
+ if(voxels[it_index] && !overwrite) {
+ // There is something in the way
+ // and the called intentionally requested
+ // we do not force feature to overwrite voxels
+ continue;
+ }
+
+ voxels[it_index] = voxel;
+ }
+ }
+}
diff --git a/game/shared/world/feature.hh b/game/shared/world/feature.hh index 5008223..20a809b 100644 --- a/game/shared/world/feature.hh +++ b/game/shared/world/feature.hh @@ -1,22 +1,22 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Dimension; -class VoxelStorage; -} // namespace world - -namespace world -{ -class Feature final : public std::vector<std::tuple<voxel_pos, voxel_id, bool>> { -public: - Feature(void) = default; - virtual ~Feature(void) = default; - -public: - void place(const voxel_pos& vpos, Dimension* dimension) const; - void place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const; -}; -} // namespace world +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Dimension;
+class VoxelStorage;
+} // namespace world
+
+namespace world
+{
+class Feature final : public std::vector<std::tuple<voxel_pos, voxel_id, bool>> {
+public:
+ Feature(void) = default;
+ virtual ~Feature(void) = default;
+
+public:
+ void place(const voxel_pos& vpos, Dimension* dimension) const;
+ void place(const voxel_pos& vpos, const chunk_pos& cpos, VoxelStorage& voxels) const;
+};
+} // namespace world
diff --git a/game/shared/world/item_registry.cc b/game/shared/world/item_registry.cc index e0d30cc..d1b9ff4 100644 --- a/game/shared/world/item_registry.cc +++ b/game/shared/world/item_registry.cc @@ -1,106 +1,106 @@ -#include "shared/pch.hh" - -#include "shared/world/item_registry.hh" - -#include "core/math/crc64.hh" - -#include "shared/world/voxel_registry.hh" - -std::unordered_map<std::string, world::ItemInfoBuilder> world::item_registry::builders = {}; -std::unordered_map<std::string, item_id> world::item_registry::names = {}; -std::vector<std::shared_ptr<world::ItemInfo>> world::item_registry::items = {}; - -world::ItemInfoBuilder::ItemInfoBuilder(std::string_view name) -{ - prototype.name = name; - prototype.texture = std::string(); - prototype.place_voxel = NULL_VOXEL_ID; - prototype.cached_texture = nullptr; -} - -world::ItemInfoBuilder& world::ItemInfoBuilder::set_texture(std::string_view texture) -{ - prototype.texture = texture; - prototype.cached_texture = nullptr; - return *this; -} - -world::ItemInfoBuilder& world::ItemInfoBuilder::set_place_voxel(voxel_id place_voxel) -{ - prototype.place_voxel = place_voxel; - return *this; -} - -item_id world::ItemInfoBuilder::build(void) const -{ - const auto it = world::item_registry::names.find(prototype.name); - - if(it != world::item_registry::names.cend()) { - spdlog::warn("item_registry: cannot build {}: name already present", prototype.name); - return it->second; - } - - auto new_info = std::make_shared<ItemInfo>(); - new_info->name = prototype.name; - new_info->texture = prototype.texture; - new_info->place_voxel = prototype.place_voxel; - new_info->cached_texture = nullptr; - - world::item_registry::items.push_back(new_info); - world::item_registry::names.insert_or_assign(prototype.name, static_cast<item_id>(world::item_registry::items.size())); - - return static_cast<item_id>(world::item_registry::items.size()); -} - -world::ItemInfoBuilder& world::item_registry::construct(std::string_view name) -{ - const auto it = world::item_registry::builders.find(std::string(name)); - - if(it != world::item_registry::builders.cend()) { - return it->second; - } - else { - return world::item_registry::builders.emplace(std::string(name), ItemInfoBuilder(name)).first->second; - } -} - -world::ItemInfo* world::item_registry::find(std::string_view name) -{ - const auto it = world::item_registry::names.find(std::string(name)); - - if(it != world::item_registry::names.cend()) { - return world::item_registry::find(it->second); - } - else { - return nullptr; - } -} - -world::ItemInfo* world::item_registry::find(const item_id item) -{ - if((item != NULL_ITEM_ID) && (item <= world::item_registry::items.size())) { - return world::item_registry::items[item - 1].get(); - } - else { - return nullptr; - } -} - -void world::item_registry::purge(void) -{ - world::item_registry::builders.clear(); - world::item_registry::names.clear(); - world::item_registry::items.clear(); -} - -std::uint64_t world::item_registry::calculate_checksum(void) -{ - std::uint64_t result = 0; - - for(const auto& info : world::item_registry::items) { - result = math::crc64(info->name, result); - result += static_cast<std::uint64_t>(info->place_voxel); - } - - return result; -} +#include "shared/pch.hh"
+
+#include "shared/world/item_registry.hh"
+
+#include "core/math/crc64.hh"
+
+#include "shared/world/voxel_registry.hh"
+
+std::unordered_map<std::string, world::ItemInfoBuilder> world::item_registry::builders = {};
+std::unordered_map<std::string, item_id> world::item_registry::names = {};
+std::vector<std::shared_ptr<world::ItemInfo>> world::item_registry::items = {};
+
+world::ItemInfoBuilder::ItemInfoBuilder(std::string_view name)
+{
+ prototype.name = name;
+ prototype.texture = std::string();
+ prototype.place_voxel = NULL_VOXEL_ID;
+ prototype.cached_texture = nullptr;
+}
+
+world::ItemInfoBuilder& world::ItemInfoBuilder::set_texture(std::string_view texture)
+{
+ prototype.texture = texture;
+ prototype.cached_texture = nullptr;
+ return *this;
+}
+
+world::ItemInfoBuilder& world::ItemInfoBuilder::set_place_voxel(voxel_id place_voxel)
+{
+ prototype.place_voxel = place_voxel;
+ return *this;
+}
+
+item_id world::ItemInfoBuilder::build(void) const
+{
+ const auto it = world::item_registry::names.find(prototype.name);
+
+ if(it != world::item_registry::names.cend()) {
+ spdlog::warn("item_registry: cannot build {}: name already present", prototype.name);
+ return it->second;
+ }
+
+ auto new_info = std::make_shared<ItemInfo>();
+ new_info->name = prototype.name;
+ new_info->texture = prototype.texture;
+ new_info->place_voxel = prototype.place_voxel;
+ new_info->cached_texture = nullptr;
+
+ world::item_registry::items.push_back(new_info);
+ world::item_registry::names.insert_or_assign(prototype.name, static_cast<item_id>(world::item_registry::items.size()));
+
+ return static_cast<item_id>(world::item_registry::items.size());
+}
+
+world::ItemInfoBuilder& world::item_registry::construct(std::string_view name)
+{
+ const auto it = world::item_registry::builders.find(std::string(name));
+
+ if(it != world::item_registry::builders.cend()) {
+ return it->second;
+ }
+ else {
+ return world::item_registry::builders.emplace(std::string(name), ItemInfoBuilder(name)).first->second;
+ }
+}
+
+world::ItemInfo* world::item_registry::find(std::string_view name)
+{
+ const auto it = world::item_registry::names.find(std::string(name));
+
+ if(it != world::item_registry::names.cend()) {
+ return world::item_registry::find(it->second);
+ }
+ else {
+ return nullptr;
+ }
+}
+
+world::ItemInfo* world::item_registry::find(const item_id item)
+{
+ if((item != NULL_ITEM_ID) && (item <= world::item_registry::items.size())) {
+ return world::item_registry::items[item - 1].get();
+ }
+ else {
+ return nullptr;
+ }
+}
+
+void world::item_registry::purge(void)
+{
+ world::item_registry::builders.clear();
+ world::item_registry::names.clear();
+ world::item_registry::items.clear();
+}
+
+std::uint64_t world::item_registry::calculate_checksum(void)
+{
+ std::uint64_t result = 0;
+
+ for(const auto& info : world::item_registry::items) {
+ result = math::crc64(info->name, result);
+ result += static_cast<std::uint64_t>(info->place_voxel);
+ }
+
+ return result;
+}
diff --git a/game/shared/world/item_registry.hh b/game/shared/world/item_registry.hh index 74bd60a..c3e6cf9 100644 --- a/game/shared/world/item_registry.hh +++ b/game/shared/world/item_registry.hh @@ -1,64 +1,64 @@ -#pragma once - -#include "core/resource/resource.hh" - -#include "shared/types.hh" - -// This resource is only defined client-side and -// resource_ptr<TextureGUI> should remain set to null -// anywhere else in the shared and server code -struct TextureGUI; - -namespace world -{ -struct ItemInfo final { - std::string name; - std::string texture; - voxel_id place_voxel; - - resource_ptr<TextureGUI> cached_texture; // Client-side only -}; -} // namespace world - -namespace world -{ -class ItemInfoBuilder final { -public: - explicit ItemInfoBuilder(std::string_view name); - virtual ~ItemInfoBuilder(void) = default; - -public: - ItemInfoBuilder& set_texture(std::string_view texture); - ItemInfoBuilder& set_place_voxel(voxel_id place_voxel); - -public: - item_id build(void) const; - -private: - ItemInfo prototype; -}; -} // namespace world - -namespace world::item_registry -{ -extern std::unordered_map<std::string, ItemInfoBuilder> builders; -extern std::unordered_map<std::string, item_id> names; -extern std::vector<std::shared_ptr<ItemInfo>> items; -} // namespace world::item_registry - -namespace world::item_registry -{ -ItemInfoBuilder& construct(std::string_view name); -ItemInfo* find(std::string_view name); -ItemInfo* find(const item_id item); -} // namespace world::item_registry - -namespace world::item_registry -{ -void purge(void); -} // namespace world::item_registry - -namespace world::item_registry -{ -std::uint64_t calculate_checksum(void); -} // namespace world::item_registry +#pragma once
+
+#include "core/resource/resource.hh"
+
+#include "shared/types.hh"
+
+// This resource is only defined client-side and
+// resource_ptr<TextureGUI> should remain set to null
+// anywhere else in the shared and server code
+struct TextureGUI;
+
+namespace world
+{
+struct ItemInfo final {
+ std::string name;
+ std::string texture;
+ voxel_id place_voxel;
+
+ resource_ptr<TextureGUI> cached_texture; // Client-side only
+};
+} // namespace world
+
+namespace world
+{
+class ItemInfoBuilder final {
+public:
+ explicit ItemInfoBuilder(std::string_view name);
+ virtual ~ItemInfoBuilder(void) = default;
+
+public:
+ ItemInfoBuilder& set_texture(std::string_view texture);
+ ItemInfoBuilder& set_place_voxel(voxel_id place_voxel);
+
+public:
+ item_id build(void) const;
+
+private:
+ ItemInfo prototype;
+};
+} // namespace world
+
+namespace world::item_registry
+{
+extern std::unordered_map<std::string, ItemInfoBuilder> builders;
+extern std::unordered_map<std::string, item_id> names;
+extern std::vector<std::shared_ptr<ItemInfo>> items;
+} // namespace world::item_registry
+
+namespace world::item_registry
+{
+ItemInfoBuilder& construct(std::string_view name);
+ItemInfo* find(std::string_view name);
+ItemInfo* find(const item_id item);
+} // namespace world::item_registry
+
+namespace world::item_registry
+{
+void purge(void);
+} // namespace world::item_registry
+
+namespace world::item_registry
+{
+std::uint64_t calculate_checksum(void);
+} // namespace world::item_registry
diff --git a/game/shared/world/ray_dda.cc b/game/shared/world/ray_dda.cc index 9f85e6b..b337e9d 100644 --- a/game/shared/world/ray_dda.cc +++ b/game/shared/world/ray_dda.cc @@ -1,107 +1,107 @@ -#include "shared/pch.hh" - -#include "shared/world/ray_dda.hh" - -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" - -world::RayDDA::RayDDA( - const world::Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction) -{ - reset(dimension, start_chunk, start_fpos, direction); -} - -world::RayDDA::RayDDA( - const world::Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction) -{ - reset(dimension, start_chunk, start_fpos, direction); -} - -void world::RayDDA::reset( - const world::Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction) -{ - this->dimension = dimension; - this->start_chunk = start_chunk; - this->start_fpos = start_fpos; - this->direction = direction; - - this->delta_dist.x = direction.x ? math::abs(1.0f / direction.x) : std::numeric_limits<float>::max(); - this->delta_dist.y = direction.y ? math::abs(1.0f / direction.y) : std::numeric_limits<float>::max(); - this->delta_dist.z = direction.z ? math::abs(1.0f / direction.z) : std::numeric_limits<float>::max(); - - this->distance = 0.0f; - this->vpos = coord::to_voxel(start_chunk, start_fpos); - this->vnormal = voxel_pos(0, 0, 0); - - // Need this for initial direction calculations - auto lpos = coord::to_local(start_fpos); - - if(direction.x < 0.0f) { - this->side_dist.x = this->delta_dist.x * (start_fpos.x - lpos.x); - this->vstep.x = voxel_pos::value_type(-1); - } - else { - this->side_dist.x = this->delta_dist.x * (lpos.x + 1.0f - start_fpos.x); - this->vstep.x = voxel_pos::value_type(+1); - } - - if(direction.y < 0.0f) { - this->side_dist.y = this->delta_dist.y * (start_fpos.y - lpos.y); - this->vstep.y = voxel_pos::value_type(-1); - } - else { - this->side_dist.y = this->delta_dist.y * (lpos.y + 1.0f - start_fpos.y); - this->vstep.y = voxel_pos::value_type(+1); - } - - if(direction.z < 0.0f) { - this->side_dist.z = this->delta_dist.z * (start_fpos.z - lpos.z); - this->vstep.z = voxel_pos::value_type(-1); - } - else { - this->side_dist.z = this->delta_dist.z * (lpos.z + 1.0f - start_fpos.z); - this->vstep.z = voxel_pos::value_type(+1); - } -} - -void world::RayDDA::reset( - const world::Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction) -{ - reset(&dimension, start_chunk, start_fpos, direction); -} - -voxel_id world::RayDDA::step(void) -{ - if(side_dist.x < side_dist.z) { - if(side_dist.x < side_dist.y) { - vnormal = voxel_pos(-vstep.x, 0, 0); - distance = side_dist.x; - side_dist.x += delta_dist.x; - vpos.x += vstep.x; - } - else { - vnormal = voxel_pos(0, -vstep.y, 0); - distance = side_dist.y; - side_dist.y += delta_dist.y; - vpos.y += vstep.y; - } - } - else { - if(side_dist.z < side_dist.y) { - vnormal = voxel_pos(0, 0, -vstep.z); - distance = side_dist.z; - side_dist.z += delta_dist.z; - vpos.z += vstep.z; - } - else { - vnormal = voxel_pos(0, -vstep.y, 0); - distance = side_dist.y; - side_dist.y += delta_dist.y; - vpos.y += vstep.y; - } - } - - // This is slower than I want it to be - return dimension->get_voxel(vpos); -} +#include "shared/pch.hh"
+
+#include "shared/world/ray_dda.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+
+world::RayDDA::RayDDA(const world::Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos,
+ const glm::fvec3& direction)
+{
+ reset(dimension, start_chunk, start_fpos, direction);
+}
+
+world::RayDDA::RayDDA(const world::Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos,
+ const glm::fvec3& direction)
+{
+ reset(dimension, start_chunk, start_fpos, direction);
+}
+
+void world::RayDDA::reset(const world::Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos,
+ const glm::fvec3& direction)
+{
+ this->dimension = dimension;
+ this->start_chunk = start_chunk;
+ this->start_fpos = start_fpos;
+ this->direction = direction;
+
+ this->delta_dist.x = direction.x ? math::abs(1.0f / direction.x) : std::numeric_limits<float>::max();
+ this->delta_dist.y = direction.y ? math::abs(1.0f / direction.y) : std::numeric_limits<float>::max();
+ this->delta_dist.z = direction.z ? math::abs(1.0f / direction.z) : std::numeric_limits<float>::max();
+
+ this->distance = 0.0f;
+ this->vpos = coord::to_voxel(start_chunk, start_fpos);
+ this->vnormal = voxel_pos(0, 0, 0);
+
+ // Need this for initial direction calculations
+ auto lpos = coord::to_local(start_fpos);
+
+ if(direction.x < 0.0f) {
+ this->side_dist.x = this->delta_dist.x * (start_fpos.x - lpos.x);
+ this->vstep.x = voxel_pos::value_type(-1);
+ }
+ else {
+ this->side_dist.x = this->delta_dist.x * (lpos.x + 1.0f - start_fpos.x);
+ this->vstep.x = voxel_pos::value_type(+1);
+ }
+
+ if(direction.y < 0.0f) {
+ this->side_dist.y = this->delta_dist.y * (start_fpos.y - lpos.y);
+ this->vstep.y = voxel_pos::value_type(-1);
+ }
+ else {
+ this->side_dist.y = this->delta_dist.y * (lpos.y + 1.0f - start_fpos.y);
+ this->vstep.y = voxel_pos::value_type(+1);
+ }
+
+ if(direction.z < 0.0f) {
+ this->side_dist.z = this->delta_dist.z * (start_fpos.z - lpos.z);
+ this->vstep.z = voxel_pos::value_type(-1);
+ }
+ else {
+ this->side_dist.z = this->delta_dist.z * (lpos.z + 1.0f - start_fpos.z);
+ this->vstep.z = voxel_pos::value_type(+1);
+ }
+}
+
+void world::RayDDA::reset(const world::Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos,
+ const glm::fvec3& direction)
+{
+ reset(&dimension, start_chunk, start_fpos, direction);
+}
+
+voxel_id world::RayDDA::step(void)
+{
+ if(side_dist.x < side_dist.z) {
+ if(side_dist.x < side_dist.y) {
+ vnormal = voxel_pos(-vstep.x, 0, 0);
+ distance = side_dist.x;
+ side_dist.x += delta_dist.x;
+ vpos.x += vstep.x;
+ }
+ else {
+ vnormal = voxel_pos(0, -vstep.y, 0);
+ distance = side_dist.y;
+ side_dist.y += delta_dist.y;
+ vpos.y += vstep.y;
+ }
+ }
+ else {
+ if(side_dist.z < side_dist.y) {
+ vnormal = voxel_pos(0, 0, -vstep.z);
+ distance = side_dist.z;
+ side_dist.z += delta_dist.z;
+ vpos.z += vstep.z;
+ }
+ else {
+ vnormal = voxel_pos(0, -vstep.y, 0);
+ distance = side_dist.y;
+ side_dist.y += delta_dist.y;
+ vpos.y += vstep.y;
+ }
+ }
+
+ // This is slower than I want it to be
+ return dimension->get_voxel(vpos);
+}
diff --git a/game/shared/world/ray_dda.hh b/game/shared/world/ray_dda.hh index 927c74d..54bd48a 100644 --- a/game/shared/world/ray_dda.hh +++ b/game/shared/world/ray_dda.hh @@ -1,41 +1,41 @@ -#ifndef SHARED_RAY_DDA -#define SHARED_RAY_DDA 1 -#pragma once - -#include "shared/types.hh" - -namespace world -{ -class Dimension; -} // namespace world - -namespace world -{ -class RayDDA final { -public: - RayDDA(void) = default; - explicit RayDDA(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - explicit RayDDA(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - - void reset(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - void reset(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction); - - voxel_id step(void); - -public: - const Dimension* dimension; - chunk_pos start_chunk; - glm::fvec3 start_fpos; - glm::fvec3 direction; - - glm::fvec3 delta_dist; - glm::fvec3 side_dist; - voxel_pos vstep; - - double distance; - voxel_pos vnormal; - voxel_pos vpos; -}; -} // namespace world - -#endif // SHARED_RAY_DDA +#ifndef SHARED_RAY_DDA
+#define SHARED_RAY_DDA 1
+#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+class Dimension;
+} // namespace world
+
+namespace world
+{
+class RayDDA final {
+public:
+ RayDDA(void) = default;
+ explicit RayDDA(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction);
+ explicit RayDDA(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction);
+
+ void reset(const Dimension* dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction);
+ void reset(const Dimension& dimension, const chunk_pos& start_chunk, const glm::fvec3& start_fpos, const glm::fvec3& direction);
+
+ voxel_id step(void);
+
+public:
+ const Dimension* dimension;
+ chunk_pos start_chunk;
+ glm::fvec3 start_fpos;
+ glm::fvec3 direction;
+
+ glm::fvec3 delta_dist;
+ glm::fvec3 side_dist;
+ voxel_pos vstep;
+
+ double distance;
+ voxel_pos vnormal;
+ voxel_pos vpos;
+};
+} // namespace world
+
+#endif // SHARED_RAY_DDA
diff --git a/game/shared/world/voxel_registry.cc b/game/shared/world/voxel_registry.cc index 46737d6..b44845e 100644 --- a/game/shared/world/voxel_registry.cc +++ b/game/shared/world/voxel_registry.cc @@ -1,196 +1,196 @@ -#include "shared/pch.hh" - -#include "shared/world/voxel_registry.hh" - -#include "core/math/crc64.hh" - -std::unordered_map<std::string, world::VoxelInfoBuilder> world::voxel_registry::builders = {}; -std::unordered_map<std::string, voxel_id> world::voxel_registry::names = {}; -std::vector<std::shared_ptr<world::VoxelInfo>> world::voxel_registry::voxels = {}; - -world::VoxelInfoBuilder::VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending) -{ - prototype.name = name; - prototype.type = type; - prototype.animated = animated; - prototype.blending = blending; - - switch(type) { - case voxel_type::CUBE: - prototype.textures.resize(static_cast<std::size_t>(voxel_face::CUBE__NR)); - break; - case voxel_type::CROSS: - prototype.textures.resize(static_cast<std::size_t>(voxel_face::CROSS__NR)); - break; - case voxel_type::MODEL: - // Custom models should use a different texture - // resource management that is not a voxel atlas - // TODO: actually implement custom models lol - prototype.textures.resize(0); - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", name, static_cast<int>(type)); - std::terminate(); - } - - // Physics properties - prototype.touch_type = voxel_touch::SOLID; - prototype.touch_values = glm::fvec3(0.0f, 0.0f, 0.0f); - prototype.surface = voxel_surface::DEFAULT; - - // Things set in future by item_def - prototype.item_pick = NULL_ITEM_ID; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture_default(std::string_view texture) -{ - default_texture.paths.push_back(std::string(texture)); - return *this; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture(voxel_face face, std::string_view texture) -{ - const auto index = static_cast<std::size_t>(face); - prototype.textures[index].paths.push_back(std::string(texture)); - return *this; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_touch(voxel_touch type, const glm::fvec3& values) -{ - prototype.touch_type = type; - prototype.touch_values = values; - return *this; -} - -world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_surface(voxel_surface surface) -{ - prototype.surface = surface; - return *this; -} - -voxel_id world::VoxelInfoBuilder::build(void) const -{ - const auto it = world::voxel_registry::names.find(prototype.name); - - if(it != world::voxel_registry::names.cend()) { - spdlog::warn("voxel_registry: cannot build {}: name already present", prototype.name); - return it->second; - } - - std::size_t state_count; - - switch(prototype.type) { - case voxel_type::CUBE: - case voxel_type::CROSS: - case voxel_type::MODEL: - state_count = 1; - break; - default: - // Something really bad should happen if we end up here. - // The outside code would static_cast an int to VoxelType - // and possibly fuck a lot of things up to cause this - spdlog::critical("voxel_registry: {}: unknown voxel type {}", prototype.name, static_cast<int>(prototype.type)); - std::terminate(); - } - - if((world::voxel_registry::voxels.size() + state_count) >= MAX_VOXEL_ID) { - spdlog::critical("voxel_registry: voxel registry overflow"); - std::terminate(); - } - - auto new_info = std::make_shared<VoxelInfo>(); - new_info->name = prototype.name; - new_info->type = prototype.type; - new_info->animated = prototype.animated; - new_info->blending = prototype.blending; - - new_info->textures.resize(prototype.textures.size()); - - for(std::size_t i = 0; i < prototype.textures.size(); ++i) { - if(prototype.textures[i].paths.empty()) { - new_info->textures[i].paths = default_texture.paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - else { - new_info->textures[i].paths = prototype.textures[i].paths; - new_info->textures[i].cached_offset = SIZE_MAX; - new_info->textures[i].cached_plane = SIZE_MAX; - } - } - - // Physics properties - new_info->touch_type = prototype.touch_type; - new_info->touch_values = prototype.touch_values; - new_info->surface = prototype.surface; - - // Things set in future by item_def - new_info->item_pick = prototype.item_pick; - - // Base voxel identifier offset - new_info->base_voxel = world::voxel_registry::voxels.size() + 1; - - for(std::size_t i = 0; i < state_count; ++i) - world::voxel_registry::voxels.push_back(new_info); - world::voxel_registry::names.insert_or_assign(new_info->name, new_info->base_voxel); - - return new_info->base_voxel; -} - -world::VoxelInfoBuilder& world::voxel_registry::construct(std::string_view name, voxel_type type, bool animated, bool blending) -{ - const auto it = world::voxel_registry::builders.find(std::string(name)); - - if(it != world::voxel_registry::builders.cend()) { - return it->second; - } - else { - return world::voxel_registry::builders.emplace(std::string(name), VoxelInfoBuilder(name, type, animated, blending)).first->second; - } -} - -world::VoxelInfo* world::voxel_registry::find(std::string_view name) -{ - const auto it = world::voxel_registry::names.find(std::string(name)); - - if(it != world::voxel_registry::names.cend()) { - return world::voxel_registry::find(it->second); - } - else { - return nullptr; - } -} - -world::VoxelInfo* world::voxel_registry::find(const voxel_id voxel) -{ - if((voxel != NULL_VOXEL_ID) && (voxel <= world::voxel_registry::voxels.size())) { - return world::voxel_registry::voxels[voxel - 1].get(); - } - else { - return nullptr; - } -} - -void world::voxel_registry::purge(void) -{ - world::voxel_registry::builders.clear(); - world::voxel_registry::names.clear(); - world::voxel_registry::voxels.clear(); -} - -std::uint64_t world::voxel_registry::calculate_checksum(void) -{ - std::uint64_t result = 0; - - for(const std::shared_ptr<VoxelInfo>& info : world::voxel_registry::voxels) { - result = math::crc64(info->name, result); - result += static_cast<std::uint64_t>(info->type); - result += static_cast<std::uint64_t>(info->base_voxel); - result += info->blending ? 256 : 1; - } - - return result; -} +#include "shared/pch.hh"
+
+#include "shared/world/voxel_registry.hh"
+
+#include "core/math/crc64.hh"
+
+std::unordered_map<std::string, world::VoxelInfoBuilder> world::voxel_registry::builders = {};
+std::unordered_map<std::string, voxel_id> world::voxel_registry::names = {};
+std::vector<std::shared_ptr<world::VoxelInfo>> world::voxel_registry::voxels = {};
+
+world::VoxelInfoBuilder::VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending)
+{
+ prototype.name = name;
+ prototype.type = type;
+ prototype.animated = animated;
+ prototype.blending = blending;
+
+ switch(type) {
+ case voxel_type::CUBE:
+ prototype.textures.resize(static_cast<std::size_t>(voxel_face::CUBE__NR));
+ break;
+ case voxel_type::CROSS:
+ prototype.textures.resize(static_cast<std::size_t>(voxel_face::CROSS__NR));
+ break;
+ case voxel_type::MODEL:
+ // Custom models should use a different texture
+ // resource management that is not a voxel atlas
+ // TODO: actually implement custom models lol
+ prototype.textures.resize(0);
+ break;
+ default:
+ // Something really bad should happen if we end up here.
+ // The outside code would static_cast an int to VoxelType
+ // and possibly fuck a lot of things up to cause this
+ spdlog::critical("voxel_registry: {}: unknown voxel type {}", name, static_cast<int>(type));
+ std::terminate();
+ }
+
+ // Physics properties
+ prototype.touch_type = voxel_touch::SOLID;
+ prototype.touch_values = glm::fvec3(0.0f, 0.0f, 0.0f);
+ prototype.surface = voxel_surface::DEFAULT;
+
+ // Things set in future by item_def
+ prototype.item_pick = NULL_ITEM_ID;
+}
+
+world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture_default(std::string_view texture)
+{
+ default_texture.paths.push_back(std::string(texture));
+ return *this;
+}
+
+world::VoxelInfoBuilder& world::VoxelInfoBuilder::add_texture(voxel_face face, std::string_view texture)
+{
+ const auto index = static_cast<std::size_t>(face);
+ prototype.textures[index].paths.push_back(std::string(texture));
+ return *this;
+}
+
+world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_touch(voxel_touch type, const glm::fvec3& values)
+{
+ prototype.touch_type = type;
+ prototype.touch_values = values;
+ return *this;
+}
+
+world::VoxelInfoBuilder& world::VoxelInfoBuilder::set_surface(voxel_surface surface)
+{
+ prototype.surface = surface;
+ return *this;
+}
+
+voxel_id world::VoxelInfoBuilder::build(void) const
+{
+ const auto it = world::voxel_registry::names.find(prototype.name);
+
+ if(it != world::voxel_registry::names.cend()) {
+ spdlog::warn("voxel_registry: cannot build {}: name already present", prototype.name);
+ return it->second;
+ }
+
+ std::size_t state_count;
+
+ switch(prototype.type) {
+ case voxel_type::CUBE:
+ case voxel_type::CROSS:
+ case voxel_type::MODEL:
+ state_count = 1;
+ break;
+ default:
+ // Something really bad should happen if we end up here.
+ // The outside code would static_cast an int to VoxelType
+ // and possibly fuck a lot of things up to cause this
+ spdlog::critical("voxel_registry: {}: unknown voxel type {}", prototype.name, static_cast<int>(prototype.type));
+ std::terminate();
+ }
+
+ if((world::voxel_registry::voxels.size() + state_count) >= MAX_VOXEL_ID) {
+ spdlog::critical("voxel_registry: voxel registry overflow");
+ std::terminate();
+ }
+
+ auto new_info = std::make_shared<VoxelInfo>();
+ new_info->name = prototype.name;
+ new_info->type = prototype.type;
+ new_info->animated = prototype.animated;
+ new_info->blending = prototype.blending;
+
+ new_info->textures.resize(prototype.textures.size());
+
+ for(std::size_t i = 0; i < prototype.textures.size(); ++i) {
+ if(prototype.textures[i].paths.empty()) {
+ new_info->textures[i].paths = default_texture.paths;
+ new_info->textures[i].cached_offset = SIZE_MAX;
+ new_info->textures[i].cached_plane = SIZE_MAX;
+ }
+ else {
+ new_info->textures[i].paths = prototype.textures[i].paths;
+ new_info->textures[i].cached_offset = SIZE_MAX;
+ new_info->textures[i].cached_plane = SIZE_MAX;
+ }
+ }
+
+ // Physics properties
+ new_info->touch_type = prototype.touch_type;
+ new_info->touch_values = prototype.touch_values;
+ new_info->surface = prototype.surface;
+
+ // Things set in future by item_def
+ new_info->item_pick = prototype.item_pick;
+
+ // Base voxel identifier offset
+ new_info->base_voxel = world::voxel_registry::voxels.size() + 1;
+
+ for(std::size_t i = 0; i < state_count; ++i)
+ world::voxel_registry::voxels.push_back(new_info);
+ world::voxel_registry::names.insert_or_assign(new_info->name, new_info->base_voxel);
+
+ return new_info->base_voxel;
+}
+
+world::VoxelInfoBuilder& world::voxel_registry::construct(std::string_view name, voxel_type type, bool animated, bool blending)
+{
+ const auto it = world::voxel_registry::builders.find(std::string(name));
+
+ if(it != world::voxel_registry::builders.cend()) {
+ return it->second;
+ }
+ else {
+ return world::voxel_registry::builders.emplace(std::string(name), VoxelInfoBuilder(name, type, animated, blending)).first->second;
+ }
+}
+
+world::VoxelInfo* world::voxel_registry::find(std::string_view name)
+{
+ const auto it = world::voxel_registry::names.find(std::string(name));
+
+ if(it != world::voxel_registry::names.cend()) {
+ return world::voxel_registry::find(it->second);
+ }
+ else {
+ return nullptr;
+ }
+}
+
+world::VoxelInfo* world::voxel_registry::find(const voxel_id voxel)
+{
+ if((voxel != NULL_VOXEL_ID) && (voxel <= world::voxel_registry::voxels.size())) {
+ return world::voxel_registry::voxels[voxel - 1].get();
+ }
+ else {
+ return nullptr;
+ }
+}
+
+void world::voxel_registry::purge(void)
+{
+ world::voxel_registry::builders.clear();
+ world::voxel_registry::names.clear();
+ world::voxel_registry::voxels.clear();
+}
+
+std::uint64_t world::voxel_registry::calculate_checksum(void)
+{
+ std::uint64_t result = 0;
+
+ for(const std::shared_ptr<VoxelInfo>& info : world::voxel_registry::voxels) {
+ result = math::crc64(info->name, result);
+ result += static_cast<std::uint64_t>(info->type);
+ result += static_cast<std::uint64_t>(info->base_voxel);
+ result += info->blending ? 256 : 1;
+ }
+
+ return result;
+}
diff --git a/game/shared/world/voxel_registry.hh b/game/shared/world/voxel_registry.hh index 6ab8c97..a75f59f 100644 --- a/game/shared/world/voxel_registry.hh +++ b/game/shared/world/voxel_registry.hh @@ -1,149 +1,149 @@ -#pragma once - -#include "shared/types.hh" - -namespace world -{ -enum class voxel_face : unsigned short { - CUBE_NORTH = 0x0000, - CUBE_SOUTH = 0x0001, - CUBE_EAST = 0x0002, - CUBE_WEST = 0x0003, - CUBE_TOP = 0x0004, - CUBE_BOTTOM = 0x0005, - CUBE__NR = 0x0006, - - CROSS_NESW = 0x0000, - CROSS_NWSE = 0x0001, - CROSS__NR = 0x0002, -}; - -enum class voxel_type : unsigned short { - CUBE = 0x0000, - CROSS = 0x0001, // TODO - MODEL = 0x0002, // TODO -}; - -enum class voxel_facing : unsigned short { - NORTH = 0x0000, - SOUTH = 0x0001, - EAST = 0x0002, - WEST = 0x0003, - UP = 0x0004, - DOWN = 0x0005, - NESW = 0x0006, - NWSE = 0x0007, -}; - -enum class voxel_touch : unsigned short { - SOLID = 0x0000, // The entity is stopped in its tracks - BOUNCE = 0x0001, // The entity bounces back with some energy loss - SINK = 0x0002, // The entity phases/sinks through the voxel - NOTHING = 0xFFFF, -}; - -enum class voxel_surface : unsigned short { - DEFAULT = 0x0000, - STONE = 0x0001, - DIRT = 0x0002, - GLASS = 0x0003, - GRASS = 0x0004, - GRAVEL = 0x0005, - METAL = 0x0006, - SAND = 0x0007, - WOOD = 0x0008, - SLOSH = 0x0009, - COUNT = 0x000A, - UNKNOWN = 0xFFFF, -}; - -using voxel_vis = unsigned short; -constexpr static voxel_vis VIS_NORTH = 1 << static_cast<unsigned int>(voxel_facing::NORTH); -constexpr static voxel_vis VIS_SOUTH = 1 << static_cast<unsigned int>(voxel_facing::SOUTH); -constexpr static voxel_vis VIS_EAST = 1 << static_cast<unsigned int>(voxel_facing::EAST); -constexpr static voxel_vis VIS_WEST = 1 << static_cast<unsigned int>(voxel_facing::WEST); -constexpr static voxel_vis VIS_UP = 1 << static_cast<unsigned int>(voxel_facing::UP); -constexpr static voxel_vis VIS_DOWN = 1 << static_cast<unsigned int>(voxel_facing::DOWN); -} // namespace world - -namespace world -{ -struct VoxelTexture final { - std::vector<std::string> paths; - std::size_t cached_offset; // client-side only - std::size_t cached_plane; // client-side only -}; - -struct VoxelInfo final { - std::string name; - voxel_type type; - bool animated; - bool blending; - - std::vector<VoxelTexture> textures; - - // Physics properties go here - // TODO: player_move friction modifiers - // that would make the voxel very sticky or - // very slippery to walk on - voxel_touch touch_type; - glm::fvec3 touch_values; - voxel_surface surface; - - // Some voxel types might occupy multiple voxel_id - // values that reference to the exact same VoxelInfo - // structure; the actual numeric state is figured out by - // subtracting base_voxel from the checking voxel_id - voxel_id base_voxel; - - // These will be set by item_registry - // and by default set to NULL_ITEM_ID - item_id item_pick; -}; -} // namespace world - -namespace world -{ -class VoxelInfoBuilder final { -public: - explicit VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending); - virtual ~VoxelInfoBuilder(void) = default; - -public: - VoxelInfoBuilder& add_texture_default(std::string_view texture); - VoxelInfoBuilder& add_texture(voxel_face face, std::string_view texture); - VoxelInfoBuilder& set_touch(voxel_touch type, const glm::fvec3& values); - VoxelInfoBuilder& set_surface(voxel_surface surface); - -public: - voxel_id build(void) const; - -private: - VoxelTexture default_texture; - VoxelInfo prototype; -}; -} // namespace world - -namespace world::voxel_registry -{ -extern std::unordered_map<std::string, VoxelInfoBuilder> builders; -extern std::unordered_map<std::string, voxel_id> names; -extern std::vector<std::shared_ptr<VoxelInfo>> voxels; -} // namespace world::voxel_registry - -namespace world::voxel_registry -{ -VoxelInfoBuilder& construct(std::string_view name, voxel_type type, bool animated, bool blending); -VoxelInfo* find(std::string_view name); -VoxelInfo* find(const voxel_id voxel); -} // namespace world::voxel_registry - -namespace world::voxel_registry -{ -void purge(void); -} // namespace world::voxel_registry - -namespace world::voxel_registry -{ -std::uint64_t calculate_checksum(void); -} // namespace world::voxel_registry +#pragma once
+
+#include "shared/types.hh"
+
+namespace world
+{
+enum class voxel_face : unsigned short {
+ CUBE_NORTH = 0x0000,
+ CUBE_SOUTH = 0x0001,
+ CUBE_EAST = 0x0002,
+ CUBE_WEST = 0x0003,
+ CUBE_TOP = 0x0004,
+ CUBE_BOTTOM = 0x0005,
+ CUBE__NR = 0x0006,
+
+ CROSS_NESW = 0x0000,
+ CROSS_NWSE = 0x0001,
+ CROSS__NR = 0x0002,
+};
+
+enum class voxel_type : unsigned short {
+ CUBE = 0x0000,
+ CROSS = 0x0001, // TODO
+ MODEL = 0x0002, // TODO
+};
+
+enum class voxel_facing : unsigned short {
+ NORTH = 0x0000,
+ SOUTH = 0x0001,
+ EAST = 0x0002,
+ WEST = 0x0003,
+ UP = 0x0004,
+ DOWN = 0x0005,
+ NESW = 0x0006,
+ NWSE = 0x0007,
+};
+
+enum class voxel_touch : unsigned short {
+ SOLID = 0x0000, // The entity is stopped in its tracks
+ BOUNCE = 0x0001, // The entity bounces back with some energy loss
+ SINK = 0x0002, // The entity phases/sinks through the voxel
+ NOTHING = 0xFFFF,
+};
+
+enum class voxel_surface : unsigned short {
+ DEFAULT = 0x0000,
+ STONE = 0x0001,
+ DIRT = 0x0002,
+ GLASS = 0x0003,
+ GRASS = 0x0004,
+ GRAVEL = 0x0005,
+ METAL = 0x0006,
+ SAND = 0x0007,
+ WOOD = 0x0008,
+ SLOSH = 0x0009,
+ COUNT = 0x000A,
+ UNKNOWN = 0xFFFF,
+};
+
+using voxel_vis = unsigned short;
+constexpr static voxel_vis VIS_NORTH = 1 << static_cast<unsigned int>(voxel_facing::NORTH);
+constexpr static voxel_vis VIS_SOUTH = 1 << static_cast<unsigned int>(voxel_facing::SOUTH);
+constexpr static voxel_vis VIS_EAST = 1 << static_cast<unsigned int>(voxel_facing::EAST);
+constexpr static voxel_vis VIS_WEST = 1 << static_cast<unsigned int>(voxel_facing::WEST);
+constexpr static voxel_vis VIS_UP = 1 << static_cast<unsigned int>(voxel_facing::UP);
+constexpr static voxel_vis VIS_DOWN = 1 << static_cast<unsigned int>(voxel_facing::DOWN);
+} // namespace world
+
+namespace world
+{
+struct VoxelTexture final {
+ std::vector<std::string> paths;
+ std::size_t cached_offset; // client-side only
+ std::size_t cached_plane; // client-side only
+};
+
+struct VoxelInfo final {
+ std::string name;
+ voxel_type type;
+ bool animated;
+ bool blending;
+
+ std::vector<VoxelTexture> textures;
+
+ // Physics properties go here
+ // TODO: player_move friction modifiers
+ // that would make the voxel very sticky or
+ // very slippery to walk on
+ voxel_touch touch_type;
+ glm::fvec3 touch_values;
+ voxel_surface surface;
+
+ // Some voxel types might occupy multiple voxel_id
+ // values that reference to the exact same VoxelInfo
+ // structure; the actual numeric state is figured out by
+ // subtracting base_voxel from the checking voxel_id
+ voxel_id base_voxel;
+
+ // These will be set by item_registry
+ // and by default set to NULL_ITEM_ID
+ item_id item_pick;
+};
+} // namespace world
+
+namespace world
+{
+class VoxelInfoBuilder final {
+public:
+ explicit VoxelInfoBuilder(std::string_view name, voxel_type type, bool animated, bool blending);
+ virtual ~VoxelInfoBuilder(void) = default;
+
+public:
+ VoxelInfoBuilder& add_texture_default(std::string_view texture);
+ VoxelInfoBuilder& add_texture(voxel_face face, std::string_view texture);
+ VoxelInfoBuilder& set_touch(voxel_touch type, const glm::fvec3& values);
+ VoxelInfoBuilder& set_surface(voxel_surface surface);
+
+public:
+ voxel_id build(void) const;
+
+private:
+ VoxelTexture default_texture;
+ VoxelInfo prototype;
+};
+} // namespace world
+
+namespace world::voxel_registry
+{
+extern std::unordered_map<std::string, VoxelInfoBuilder> builders;
+extern std::unordered_map<std::string, voxel_id> names;
+extern std::vector<std::shared_ptr<VoxelInfo>> voxels;
+} // namespace world::voxel_registry
+
+namespace world::voxel_registry
+{
+VoxelInfoBuilder& construct(std::string_view name, voxel_type type, bool animated, bool blending);
+VoxelInfo* find(std::string_view name);
+VoxelInfo* find(const voxel_id voxel);
+} // namespace world::voxel_registry
+
+namespace world::voxel_registry
+{
+void purge(void);
+} // namespace world::voxel_registry
+
+namespace world::voxel_registry
+{
+std::uint64_t calculate_checksum(void);
+} // namespace world::voxel_registry
diff --git a/game/shared/world/voxel_storage.cc b/game/shared/world/voxel_storage.cc index 68e261c..75013b4 100644 --- a/game/shared/world/voxel_storage.cc +++ b/game/shared/world/voxel_storage.cc @@ -1,48 +1,48 @@ -#include "shared/pch.hh" - -#include "shared/world/voxel_storage.hh" - -#include "core/io/buffer.hh" - -void world::VoxelStorage::serialize(io::WriteBuffer& buffer) const -{ - auto bound = mz_compressBound(sizeof(VoxelStorage)); - auto zdata = std::vector<unsigned char>(bound); - - VoxelStorage net_storage; - - for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { - // Convert voxel indices into network byte order; - // We're going to compress them but we still want - // the order to be consistent across all the platforms - net_storage[i] = ENET_HOST_TO_NET_16(at(i)); - } - - mz_compress(zdata.data(), &bound, reinterpret_cast<unsigned char*>(net_storage.data()), sizeof(VoxelStorage)); - - buffer.write<std::uint64_t>(bound); - - // Write all the compressed data into the buffer - for(std::size_t i = 0; i < bound; buffer.write<std::uint8_t>(zdata[i++])) { - // empty - } -} - -void world::VoxelStorage::deserialize(io::ReadBuffer& buffer) -{ - auto size = static_cast<mz_ulong>(sizeof(VoxelStorage)); - auto bound = static_cast<mz_ulong>(buffer.read<std::uint64_t>()); - auto zdata = std::vector<unsigned char>(bound); - - // Read all the compressed data from the buffer - for(std::size_t i = 0; i < bound; zdata[i++] = buffer.read<std::uint8_t>()) { - // empty - } - - mz_uncompress(reinterpret_cast<unsigned char*>(data()), &size, zdata.data(), bound); - - for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) { - // Convert voxel indices back into the host byte order - at(i) = ENET_NET_TO_HOST_16(at(i)); - } -} +#include "shared/pch.hh"
+
+#include "shared/world/voxel_storage.hh"
+
+#include "core/io/buffer.hh"
+
+void world::VoxelStorage::serialize(io::WriteBuffer& buffer) const
+{
+ auto bound = mz_compressBound(sizeof(VoxelStorage));
+ auto zdata = std::vector<unsigned char>(bound);
+
+ VoxelStorage net_storage;
+
+ for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) {
+ // Convert voxel indices into network byte order;
+ // We're going to compress them but we still want
+ // the order to be consistent across all the platforms
+ net_storage[i] = ENET_HOST_TO_NET_16(at(i));
+ }
+
+ mz_compress(zdata.data(), &bound, reinterpret_cast<unsigned char*>(net_storage.data()), sizeof(VoxelStorage));
+
+ buffer.write<std::uint64_t>(bound);
+
+ // Write all the compressed data into the buffer
+ for(std::size_t i = 0; i < bound; buffer.write<std::uint8_t>(zdata[i++])) {
+ // empty
+ }
+}
+
+void world::VoxelStorage::deserialize(io::ReadBuffer& buffer)
+{
+ auto size = static_cast<mz_ulong>(sizeof(VoxelStorage));
+ auto bound = static_cast<mz_ulong>(buffer.read<std::uint64_t>());
+ auto zdata = std::vector<unsigned char>(bound);
+
+ // Read all the compressed data from the buffer
+ for(std::size_t i = 0; i < bound; zdata[i++] = buffer.read<std::uint8_t>()) {
+ // empty
+ }
+
+ mz_uncompress(reinterpret_cast<unsigned char*>(data()), &size, zdata.data(), bound);
+
+ for(std::size_t i = 0; i < CHUNK_VOLUME; ++i) {
+ // Convert voxel indices back into the host byte order
+ at(i) = ENET_NET_TO_HOST_16(at(i));
+ }
+}
diff --git a/game/shared/world/voxel_storage.hh b/game/shared/world/voxel_storage.hh index ac7f03d..98e85a3 100644 --- a/game/shared/world/voxel_storage.hh +++ b/game/shared/world/voxel_storage.hh @@ -1,20 +1,20 @@ -#pragma once - -#include "shared/const.hh" -#include "shared/types.hh" - -namespace io -{ -class ReadBuffer; -class WriteBuffer; -} // namespace io - -namespace world -{ -class VoxelStorage final : public std::array<voxel_id, CHUNK_VOLUME> { -public: - using std::array<voxel_id, CHUNK_VOLUME>::array; - void serialize(io::WriteBuffer& buffer) const; - void deserialize(io::ReadBuffer& buffer); -}; -} // namespace world +#pragma once
+
+#include "shared/const.hh"
+#include "shared/types.hh"
+
+namespace io
+{
+class ReadBuffer;
+class WriteBuffer;
+} // namespace io
+
+namespace world
+{
+class VoxelStorage final : public std::array<voxel_id, CHUNK_VOLUME> {
+public:
+ using std::array<voxel_id, CHUNK_VOLUME>::array;
+ void serialize(io::WriteBuffer& buffer) const;
+ void deserialize(io::ReadBuffer& buffer);
+};
+} // namespace world
|
