#pragma once // C++ #include <charconv> #include <cstdint> #include <ctime> #include <functional> #include <optional> #include <random> #include <span> #include <stdexcept> #include <string> #include <string_view> #include <type_traits> #include "BitStream.h" #include "NiPoint3.h" #include "dPlatforms.h" #include "Game.h" #include "Logger.h" enum eInventoryType : uint32_t; enum class eObjectBits : size_t; enum class eReplicaComponentType : uint32_t; /*! \file GeneralUtils.hpp \brief A namespace containing general utility functions */ //! The general utils namespace namespace GeneralUtils { //! Converts a plain ASCII string to a UTF-16 string /*! \param string The string to convert \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX); //! Converts a UTF-8 String to a UTF-16 string /*! \param string The string to convert \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX); namespace details { //! Internal, do not use bool _NextUTF8Char(std::string_view& slice, uint32_t& out); } //! Converts a UTF-16 string to a UTF-8 string /*! \param string The string to convert \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-8 representation of the string */ std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX); /** * Compares two basic strings but does so ignoring case sensitivity * \param a the first string to compare against the second string * \param b the second string to compare against the first string * @return if the two strings are equal */ bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b); // MARK: Bits // MARK: Bits //! Sets a bit on a numerical value template <typename T> inline void SetBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); const auto index = static_cast<size_t>(bits); if (index > (sizeof(T) * 8) - 1) { return; } value |= static_cast<T>(1) << index; } //! Clears a bit on a numerical value template <typename T> inline void ClearBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); const auto index = static_cast<size_t>(bits); if (index > (sizeof(T) * 8 - 1)) { return; } value &= ~(static_cast<T>(1) << index); } //! Sets a specific bit in a signed 64-bit integer /*! \param value The value to set the bit for \param index The index of the bit */ int64_t SetBit(int64_t value, const uint32_t index); //! Clears a specific bit in a signed 64-bit integer /*! \param value The value to clear the bit from \param index The index of the bit */ int64_t ClearBit(int64_t value, const uint32_t index); //! Checks a specific bit in a signed 64-bit integer /*! \parma value The value to check the bit in \param index The index of the bit \return Whether or not the bit is set */ bool CheckBit(int64_t value, const uint32_t index); bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to); std::u16string ReadWString(RakNet::BitStream& inStream); std::vector<std::wstring> SplitString(const std::wstring_view str, const wchar_t delimiter); std::vector<std::u16string> SplitString(const std::u16string_view str, const char16_t delimiter); std::vector<std::string> SplitString(const std::string_view str, const char delimiter); std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder); /** * Transparent string hasher - used to allow string_view key lookups for maps storing std::string keys * https://www.reddit.com/r/cpp_questions/comments/12xw3sn/find_stdstring_view_in_unordered_map_with/jhki225/ * https://godbolt.org/z/789xv8Eeq */ template <typename... Bases> struct overload : Bases... { using is_transparent = void; using Bases::operator() ... ; }; struct char_pointer_hash { auto operator()(const char* const ptr) const noexcept { return std::hash<std::string_view>{}(ptr); } }; using transparent_string_hash = overload< std::hash<std::string>, std::hash<std::string_view>, char_pointer_hash >; // Concept constraining to enum types template <typename T> concept Enum = std::is_enum_v<T>; // Concept constraining to numeric types template <typename T> concept Numeric = std::integral<T> || Enum<T> || std::floating_point<T>; // Concept trickery to enable parsing underlying numeric types template <Numeric T> struct numeric_parse { using type = T; }; // If an enum, present an alias to its underlying type for parsing template <Numeric T> requires Enum<T> struct numeric_parse<T> { using type = std::underlying_type_t<T>; }; // If a boolean, present an alias to an intermediate integral type for parsing template <Numeric T> requires std::same_as<T, bool> struct numeric_parse<T> { using type = uint8_t; }; // Shorthand type alias template <Numeric T> using numeric_parse_t = numeric_parse<T>::type; /** * For numeric values: Parses a string_view and returns an optional variable depending on the result. * @param str The string_view to be evaluated * @returns An std::optional containing the desired value if it is equivalent to the string */ template <Numeric T> [[nodiscard]] std::optional<T> TryParse(std::string_view str) { numeric_parse_t<T> result; while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); const char* const strEnd = str.data() + str.size(); const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result); const bool isParsed = parseEnd == strEnd && ec == std::errc{}; return isParsed ? static_cast<T>(result) : std::optional<T>{}; } #if !(__GNUC__ >= 11 || _MSC_VER >= 1924) // MacOS floating-point parse helper function specializations namespace details { template <std::floating_point T> [[nodiscard]] T _parse(const std::string_view str, size_t& parseNum); } /** * For floating-point values: Parses a string_view and returns an optional variable depending on the result. * Note that this function overload is only included for MacOS, as from_chars will fulfill its purpose otherwise. * @param str The string_view to be evaluated * @returns An std::optional containing the desired value if it is equivalent to the string */ template <std::floating_point T> [[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept try { while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); size_t parseNum; const T result = details::_parse<T>(str, parseNum); const bool isParsed = str.length() == parseNum; return isParsed ? result : std::optional<T>{}; } catch (...) { return std::nullopt; } #endif /** * The TryParse overload for handling NiPoint3 by passing 3 seperate string references * @param strX The string representing the X coordinate * @param strY The string representing the Y coordinate * @param strZ The string representing the Z coordinate * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template <typename T> [[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) { const auto x = TryParse<float>(strX); if (!x) return std::nullopt; const auto y = TryParse<float>(strY); if (!y) return std::nullopt; const auto z = TryParse<float>(strZ); return z ? std::make_optional<NiPoint3>(x.value(), y.value(), z.value()) : std::nullopt; } /** * The TryParse overload for handling NiPoint3 by passing a span of three strings * @param str The string vector representing the X, Y, and Z coordinates * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template <typename T> [[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) { return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt; } template <typename T> std::u16string to_u16string(const T value) { return GeneralUtils::ASCIIToUTF16(std::to_string(value)); } // From boost::hash_combine template <class T> constexpr void hash_combine(std::size_t& s, const T& v) { std::hash<T> h; s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2); } // MARK: Random Number Generation //! Generates a random number /*! \param min The minimum the generate from \param max The maximum to generate to */ template <typename T> inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) { // Make sure it is a numeric type static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); if constexpr (std::is_integral_v<T>) { // constexpr only necessary on first statement std::uniform_int_distribution<T> distribution(min, max); return distribution(Game::randomEngine); } else if (std::is_floating_point_v<T>) { std::uniform_real_distribution<T> distribution(min, max); return distribution(Game::randomEngine); } return T(); } /** * Casts the value of an enum entry to its underlying type * @param entry Enum entry to cast * @returns The enum entry's value in its underlying type */ template <Enum eType> constexpr std::underlying_type_t<eType> ToUnderlying(const eType entry) noexcept { return static_cast<std::underlying_type_t<eType>>(entry); } // on Windows we need to undef these or else they conflict with our numeric limits calls // DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS #ifdef _WIN32 #undef min #undef max #endif template <typename T> inline T GenerateRandomNumber() { // Make sure it is a numeric type static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max()); } }