This commit is contained in:
9
src/util/color.hpp
Normal file
9
src/util/color.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/ext/vector_float4.hpp>
|
||||
|
||||
namespace game::util::color
|
||||
{
|
||||
constexpr auto WHITE = glm::vec4(1.0f);
|
||||
constexpr auto GRAY = glm::vec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
}
|
||||
27
src/util/imgui.cpp
Normal file
27
src/util/imgui.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "imgui.hpp"
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
float row_widget_width_get(int count, float width)
|
||||
{
|
||||
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
|
||||
}
|
||||
|
||||
ImVec2 row_widget_size_get(int count, float width) { return ImVec2(row_widget_width_get(count, width), 0); }
|
||||
|
||||
float footer_height_get(int itemCount)
|
||||
{
|
||||
return ImGui::GetTextLineHeightWithSpacing() * itemCount + ImGui::GetStyle().WindowPadding.y +
|
||||
ImGui::GetStyle().ItemSpacing.y * (itemCount);
|
||||
}
|
||||
|
||||
ImVec2 footer_size_get(int itemCount)
|
||||
{
|
||||
return ImVec2(ImGui::GetContentRegionAvail().x, footer_height_get(itemCount));
|
||||
}
|
||||
|
||||
ImVec2 size_without_footer_get(int rowCount)
|
||||
{
|
||||
return ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_get(rowCount));
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,18 @@ using namespace glm;
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
float widget_width_with_row_get(int = 1, float = ImGui::GetContentRegionAvail().x);
|
||||
ImVec2 widget_size_with_row_get(int = 1, float = ImGui::GetContentRegionAvail().x);
|
||||
float row_widget_width_get(int = 1, float = ImGui::GetContentRegionAvail().x);
|
||||
ImVec2 row_widget_size_get(int = 1, float = ImGui::GetContentRegionAvail().x);
|
||||
|
||||
float footer_height_get(int = 1);
|
||||
ImVec2 footer_size_get(int = 1);
|
||||
ImVec2 size_without_footer_get(int = 1);
|
||||
|
||||
inline ImVec2 to_imvec2(vec2 value) { return ImVec2(value.x, value.y); }
|
||||
inline ImVec2 to_imvec2(ivec2 value) { return ImVec2(value.x, value.y); }
|
||||
inline vec2 to_vec2(ImVec2 value) { return vec2(value.x, value.y); }
|
||||
inline ivec2 to_ivec2(ImVec2 value) { return ivec2(value.x, value.y); }
|
||||
|
||||
inline ImVec4 to_imvec4(vec4 value) { return ImVec4(value.r, value.g, value.b, value.a); }
|
||||
inline vec4 to_vec4(ImVec4 value) { return vec4(value.x, value.y, value.z, value.w); }
|
||||
}
|
||||
}
|
||||
23
src/util/imgui/input_int_ex.cpp
Normal file
23
src/util/imgui/input_int_ex.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "input_int_ex.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
bool input_int_range(const char* label, int* value, int min, int max, int step, int stepFast,
|
||||
ImGuiInputTextFlags flags)
|
||||
{
|
||||
auto isChanged = ImGui::InputInt(label, value, step, stepFast, flags);
|
||||
|
||||
auto minMax = std::minmax(min, max);
|
||||
auto clamped = std::clamp(*value, minMax.first, minMax.second);
|
||||
|
||||
if (clamped != *value)
|
||||
{
|
||||
*value = clamped;
|
||||
isChanged = true;
|
||||
}
|
||||
|
||||
return isChanged;
|
||||
}
|
||||
}
|
||||
9
src/util/imgui/input_int_ex.hpp
Normal file
9
src/util/imgui/input_int_ex.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
bool input_int_range(const char* label, int* value, int min, int max, int step = 1, int stepFast = 100,
|
||||
ImGuiInputTextFlags flags = 0);
|
||||
}
|
||||
67
src/util/imgui/style.cpp
Normal file
67
src/util/imgui/style.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "style.hpp"
|
||||
|
||||
#include "../imgui.hpp"
|
||||
|
||||
namespace game::util::imgui::style
|
||||
{
|
||||
void rounding_set(float rounding)
|
||||
{
|
||||
auto& style = ImGui::GetStyle();
|
||||
style.WindowRounding = rounding;
|
||||
style.FrameRounding = rounding;
|
||||
style.GrabRounding = rounding;
|
||||
}
|
||||
|
||||
void color_set(glm::vec3 color)
|
||||
{
|
||||
static constexpr auto COLOR_BG_MULTIPLIER = 0.25f;
|
||||
static constexpr auto COLOR_BG_ALPHA = 0.90f;
|
||||
static constexpr auto COLOR_ACTIVE_MULTIPLIER = 1.30f;
|
||||
static constexpr auto COLOR_HOVERED_MULTIPLIER = 1.60f;
|
||||
static constexpr auto COLOR_ACCENT_MULTIPLIER = 2.0f;
|
||||
static constexpr auto COLOR_ACCENT_ACTIVE_MULTIPLIER = 2.25f;
|
||||
|
||||
auto& colors = ImGui::GetStyle().Colors;
|
||||
|
||||
auto colorNew = to_imvec4(glm::vec4(color, 1.0f));
|
||||
auto colorBg = to_imvec4(glm::vec4(color * COLOR_BG_MULTIPLIER, COLOR_BG_ALPHA));
|
||||
auto colorChildBg = to_imvec4(glm::vec4(color * COLOR_BG_MULTIPLIER, 0.0f));
|
||||
auto colorActive = to_imvec4(glm::vec4(color * COLOR_ACTIVE_MULTIPLIER, 1.0f));
|
||||
auto colorHovered = to_imvec4(glm::vec4(color * COLOR_HOVERED_MULTIPLIER, 1.0f));
|
||||
auto colorAccent = to_imvec4(glm::vec4(color * COLOR_ACCENT_MULTIPLIER, 1.0f));
|
||||
auto colorAccentActive = to_imvec4(glm::vec4(color * COLOR_ACCENT_ACTIVE_MULTIPLIER, 1.0f));
|
||||
|
||||
colors[ImGuiCol_Button] = colorNew;
|
||||
colors[ImGuiCol_FrameBg] = colorNew;
|
||||
colors[ImGuiCol_FrameBgHovered] = colorHovered;
|
||||
colors[ImGuiCol_FrameBgActive] = colorActive;
|
||||
colors[ImGuiCol_Header] = colorNew;
|
||||
colors[ImGuiCol_HeaderHovered] = colorHovered;
|
||||
colors[ImGuiCol_HeaderActive] = colorActive;
|
||||
colors[ImGuiCol_Tab] = colorNew;
|
||||
colors[ImGuiCol_TitleBg] = colorNew;
|
||||
colors[ImGuiCol_TitleBgCollapsed] = colorNew;
|
||||
colors[ImGuiCol_TitleBgActive] = colorNew;
|
||||
colors[ImGuiCol_WindowBg] = colorBg;
|
||||
colors[ImGuiCol_ChildBg] = colorChildBg;
|
||||
colors[ImGuiCol_PopupBg] = colorBg;
|
||||
|
||||
colors[ImGuiCol_ScrollbarBg] = colorNew;
|
||||
colors[ImGuiCol_ScrollbarGrab] = colorHovered;
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = colorHovered;
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = colorAccent;
|
||||
|
||||
colors[ImGuiCol_ButtonHovered] = colorHovered;
|
||||
colors[ImGuiCol_FrameBgHovered] = colorHovered;
|
||||
colors[ImGuiCol_TabHovered] = colorHovered;
|
||||
|
||||
colors[ImGuiCol_ButtonActive] = colorActive;
|
||||
colors[ImGuiCol_FrameBgActive] = colorActive;
|
||||
colors[ImGuiCol_TabActive] = colorActive;
|
||||
|
||||
colors[ImGuiCol_PlotHistogram] = colorAccent;
|
||||
colors[ImGuiCol_CheckMark] = colorAccent;
|
||||
colors[ImGuiCol_SliderGrab] = colorAccent;
|
||||
colors[ImGuiCol_SliderGrabActive] = colorAccentActive;
|
||||
}
|
||||
}
|
||||
10
src/util/imgui/style.hpp
Normal file
10
src/util/imgui/style.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::util::imgui::style
|
||||
{
|
||||
void rounding_set(float rounding = 10.0f);
|
||||
void color_set(glm::vec3 color);
|
||||
}
|
||||
61
src/util/imgui/widget.cpp
Normal file
61
src/util/imgui/widget.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "widget.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using game::resource::xml::SoundEntryCollection;
|
||||
|
||||
namespace game::util::imgui::widget
|
||||
{
|
||||
static SoundEntryCollection* hover{};
|
||||
static SoundEntryCollection* select{};
|
||||
|
||||
void sounds_set(SoundEntryCollection* _hover, SoundEntryCollection* _select)
|
||||
{
|
||||
hover = _hover;
|
||||
select = _select;
|
||||
}
|
||||
|
||||
void fx()
|
||||
{
|
||||
static constexpr auto FREQUENCY = 10.0f;
|
||||
static constexpr auto ALPHA_MIN = 1.0f;
|
||||
static constexpr auto ALPHA_MAX = 1.0f;
|
||||
static constexpr auto THICKNESS_MIN = 2.0f;
|
||||
static constexpr auto THICKNESS_MAX = 3.0f;
|
||||
|
||||
if (ImGui::IsItemActivated())
|
||||
if (select && !select->empty()) select->play();
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
auto storage = ImGui::GetStateStorage();
|
||||
auto id = ImGui::GetItemID();
|
||||
if (id == 0) return;
|
||||
bool wasHovered = storage->GetBool(id, false);
|
||||
|
||||
if (!wasHovered)
|
||||
{
|
||||
if (hover && !hover->empty()) hover->play();
|
||||
storage->SetBool(id, true);
|
||||
}
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto min = ImGui::GetItemRectMin();
|
||||
auto max = ImGui::GetItemRectMax();
|
||||
auto time = ImGui::GetTime();
|
||||
auto period = sinf(time * FREQUENCY);
|
||||
auto thickness = THICKNESS_MIN + (THICKNESS_MAX * period);
|
||||
auto colorBorder = ImGui::GetStyleColorVec4(ImGuiCol_CheckMark);
|
||||
colorBorder.w = ALPHA_MIN + (ALPHA_MAX * period);
|
||||
|
||||
ImGui::GetWindowDrawList()->AddRect(min, max, ImGui::GetColorU32(colorBorder), style.FrameRounding, 0, thickness);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto storage = ImGui::GetStateStorage();
|
||||
auto id = ImGui::GetItemID();
|
||||
if (id == 0) return;
|
||||
storage->SetBool(id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/util/imgui/widget.hpp
Normal file
20
src/util/imgui/widget.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../resource/xml/sound_entry.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::util::imgui::widget
|
||||
{
|
||||
void sounds_set(resource::xml::SoundEntryCollection* _hover, resource::xml::SoundEntryCollection* _select);
|
||||
void fx();
|
||||
}
|
||||
|
||||
#define WIDGET_FX_LAST_ITEM() ::game::util::imgui::widget::fx()
|
||||
#define WIDGET_FX(expr) \
|
||||
( \
|
||||
[&]() \
|
||||
{ \
|
||||
const auto _widget_result = (expr); \
|
||||
WIDGET_FX_LAST_ITEM(); \
|
||||
return _widget_result; \
|
||||
}())
|
||||
30
src/util/imgui/window_slide.cpp
Normal file
30
src/util/imgui/window_slide.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "window_slide.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
WindowSlide::WindowSlide(float duration, float initial) : value_(initial), duration_(duration) {}
|
||||
|
||||
void WindowSlide::update(bool isOpen, float deltaTime)
|
||||
{
|
||||
auto target = isOpen ? 1.0f : 0.0f;
|
||||
if (duration_ <= 0.0f)
|
||||
{
|
||||
value_ = target;
|
||||
return;
|
||||
}
|
||||
|
||||
auto step = deltaTime / duration_;
|
||||
if (value_ < target) value_ = std::min(target, value_ + step);
|
||||
if (value_ > target) value_ = std::max(target, value_ - step);
|
||||
value_ = std::clamp(value_, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float WindowSlide::value_get() const { return value_; }
|
||||
|
||||
float WindowSlide::eased_get(float power) const { return std::pow(value_, power); }
|
||||
|
||||
bool WindowSlide::is_visible() const { return value_ > 0.0f; }
|
||||
}
|
||||
19
src/util/imgui/window_slide.hpp
Normal file
19
src/util/imgui/window_slide.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
class WindowSlide
|
||||
{
|
||||
public:
|
||||
explicit WindowSlide(float duration = 0.125f, float initial = 1.0f);
|
||||
|
||||
void update(bool isOpen, float deltaTime);
|
||||
float value_get() const;
|
||||
float eased_get(float power = 2.0f) const;
|
||||
bool is_visible() const;
|
||||
|
||||
private:
|
||||
float value_{1.0f};
|
||||
float duration_{0.125f};
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "imgui_.h"
|
||||
|
||||
namespace game::util::imgui
|
||||
{
|
||||
float row_widget_width_get(int count, float width)
|
||||
{
|
||||
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
|
||||
}
|
||||
|
||||
ImVec2 widget_size_with_row_get(int count, float width) { return ImVec2(row_widget_width_get(count, width), 0); }
|
||||
}
|
||||
24
src/util/interact_type.hpp
Normal file
24
src/util/interact_type.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
namespace game
|
||||
{
|
||||
#define LIST \
|
||||
X(RUB, "Rub") \
|
||||
X(KISS, "Kiss") \
|
||||
X(SMACK, "Smack")
|
||||
|
||||
enum InteractType
|
||||
{
|
||||
#define X(symbol, string) symbol,
|
||||
LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
static constexpr const char* INTERACT_TYPE_STRINGS[] = {
|
||||
#define X(symbol, string) string,
|
||||
LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef LIST
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "math_.h"
|
||||
#include "math.hpp"
|
||||
#include "SDL3/SDL_rect.h"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include <ctime>
|
||||
@@ -7,25 +7,7 @@ using namespace glm;
|
||||
|
||||
namespace game::util::math
|
||||
{
|
||||
mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
vec2 scaleAbsolute = glm::abs(scale);
|
||||
vec2 scaleSign = glm::sign(scale);
|
||||
vec2 pivotScaled = pivot * scaleAbsolute;
|
||||
vec2 sizeScaled = size * scaleAbsolute;
|
||||
float handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
mat4 model(1.0f);
|
||||
model = glm::translate(model, vec3(position - pivotScaled, 0.0f));
|
||||
model = glm::translate(model, vec3(pivotScaled, 0.0f));
|
||||
model = glm::scale(model, vec3(scaleSign, 1.0f));
|
||||
model = glm::rotate(model, glm::radians(rotation) * handedness, vec3(0, 0, 1));
|
||||
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
|
||||
model = glm::scale(model, vec3(sizeScaled, 1.0f));
|
||||
return model;
|
||||
}
|
||||
|
||||
mat4 quad_model_parent_get(vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
static mat4 quad_model_local_get(vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
vec2 scaleSign = glm::sign(scale);
|
||||
vec2 scaleAbsolute = glm::abs(scale);
|
||||
@@ -37,17 +19,33 @@ namespace game::util::math
|
||||
local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1));
|
||||
local = glm::translate(local, vec3(-pivot, 0.0f));
|
||||
local = glm::scale(local, vec3(scaleAbsolute, 1.0f));
|
||||
return local;
|
||||
}
|
||||
|
||||
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local;
|
||||
mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
vec2 scaleAbsolute = glm::abs(scale);
|
||||
vec2 scaleSign = glm::sign(scale);
|
||||
vec2 pivotScaled = pivot * scaleAbsolute;
|
||||
vec2 sizeScaled = size * scaleAbsolute;
|
||||
|
||||
return glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f)) *
|
||||
quad_model_local_get(pivotScaled, scaleSign, rotation) * glm::scale(mat4(1.0f), vec3(sizeScaled, 1.0f));
|
||||
}
|
||||
|
||||
mat4 quad_model_no_size_get(vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * quad_model_local_get(pivot, scale, rotation);
|
||||
}
|
||||
|
||||
bool is_point_in_rect(ivec4 rect, ivec2 point) { return SDL_PointInRect((SDL_Point*)&point, (SDL_Rect*)&rect); }
|
||||
bool is_point_in_rectf(vec4 rect, vec2 point) { return SDL_PointInRectFloat((SDL_FPoint*)&point, (SDL_FRect*)&rect); }
|
||||
|
||||
float random() { return (float)rand() / RAND_MAX; }
|
||||
float random() { return (float)rand() / (float)RAND_MAX; }
|
||||
bool random_percent_roll(float percent) { return to_percent(random()) < percent; }
|
||||
float random_in_range(float min, float max) { return min + random() * (max - min); }
|
||||
float random_max(float max) { return random_in_range(0, max); }
|
||||
float random_roll(float value) { return random() * value; }
|
||||
bool random_bool() { return random() < 0.5f; };
|
||||
void random_seed_set() { srand(std::time(nullptr)); }
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ namespace game::util::math
|
||||
{
|
||||
glm::mat4 quad_model_get(glm::vec2 size, glm::vec2 position = {}, glm::vec2 pivot = {},
|
||||
glm::vec2 scale = glm::vec2(1.0f), float rotation = {});
|
||||
glm::mat4 quad_model_parent_get(glm::vec2 position = {}, glm::vec2 pivot = {}, glm::vec2 scale = glm::vec2(1.0f),
|
||||
float rotation = {});
|
||||
glm::mat4 quad_model_no_size_get(glm::vec2 position = {}, glm::vec2 pivot = {}, glm::vec2 scale = glm::vec2(1.0f),
|
||||
float rotation = {});
|
||||
|
||||
template <typename T> constexpr T to_percent(T value) { return value * 100.0f; }
|
||||
template <typename T> constexpr T to_unit(T value) { return value / 100.0f; }
|
||||
@@ -26,6 +26,7 @@ namespace game::util::math
|
||||
bool random_percent_roll(float percent);
|
||||
float random_roll(float value);
|
||||
float random_in_range(float min, float max);
|
||||
float random_max(float max);
|
||||
bool random_bool();
|
||||
void random_seed_set();
|
||||
}
|
||||
}
|
||||
12
src/util/measurement.hpp
Normal file
12
src/util/measurement.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace game::util::measurement
|
||||
{
|
||||
enum System
|
||||
{
|
||||
METRIC,
|
||||
IMPERIAL
|
||||
};
|
||||
|
||||
constexpr auto KG_TO_LB = 2.20462;
|
||||
}
|
||||
131
src/util/physfs.cpp
Normal file
131
src/util/physfs.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "physfs.hpp"
|
||||
|
||||
#include <physfs.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
namespace game::util::physfs
|
||||
{
|
||||
std::string path_normalize(std::string_view path)
|
||||
{
|
||||
const bool absolute = !path.empty() && path.front() == '/';
|
||||
std::vector<std::string> parts{};
|
||||
std::string segment{};
|
||||
|
||||
auto flush_segment = [&]()
|
||||
{
|
||||
if (segment.empty() || segment == ".")
|
||||
{
|
||||
segment.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (segment == "..")
|
||||
{
|
||||
if (!parts.empty() && parts.back() != "..")
|
||||
parts.pop_back();
|
||||
else if (!absolute)
|
||||
parts.emplace_back("..");
|
||||
}
|
||||
else
|
||||
{
|
||||
parts.emplace_back(std::move(segment));
|
||||
}
|
||||
|
||||
segment.clear();
|
||||
};
|
||||
|
||||
for (char ch : path)
|
||||
{
|
||||
if (ch == '/')
|
||||
{
|
||||
flush_segment();
|
||||
continue;
|
||||
}
|
||||
|
||||
segment.push_back(ch);
|
||||
}
|
||||
|
||||
flush_segment();
|
||||
|
||||
std::string pathNormalized{};
|
||||
if (absolute) pathNormalized.push_back('/');
|
||||
for (size_t i = 0; i < parts.size(); ++i)
|
||||
{
|
||||
if (i > 0) pathNormalized.push_back('/');
|
||||
pathNormalized.append(parts[i]);
|
||||
}
|
||||
|
||||
if (pathNormalized.empty() && absolute) return "/";
|
||||
return pathNormalized;
|
||||
}
|
||||
|
||||
std::string error_get() { return PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()); }
|
||||
|
||||
Archive::Archive(const std::filesystem::path& path, const std::string& mount)
|
||||
{
|
||||
if (!PHYSFS_mount(path.c_str(), mount.c_str(), 0))
|
||||
{
|
||||
logger.error(std::format("Failed to mount archive: {} ({})", path.c_str(), error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
*this = std::string(mount);
|
||||
}
|
||||
|
||||
Archive::~Archive() { PHYSFS_unmount(c_str()); }
|
||||
|
||||
bool Archive::is_valid() { return !empty(); }
|
||||
|
||||
std::vector<uint8_t> Path::read() const
|
||||
{
|
||||
auto pathNormalized = path_normalize(*this);
|
||||
auto file = PHYSFS_openRead(pathNormalized.c_str());
|
||||
|
||||
if (!file)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to read PhysicsFS file (PHYFSFS_openRead): {} ({})", pathNormalized, error_get()));
|
||||
return {};
|
||||
}
|
||||
|
||||
auto size = PHYSFS_fileLength(file);
|
||||
if (size < 0)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to get PhysicsFS file length (PHYSFS_fileLength): {} ({})", pathNormalized, error_get()));
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer((size_t)size);
|
||||
|
||||
auto read = PHYSFS_readBytes(file, buffer.data(), size);
|
||||
|
||||
PHYSFS_close(file);
|
||||
|
||||
if (read != size)
|
||||
{
|
||||
logger.error(std::format("Invalid PhysicsFS file read size: {} ({})", pathNormalized, error_get()));
|
||||
return {};
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
physfs::Path Path::directory_get() const
|
||||
{
|
||||
auto pos = find_last_of('/');
|
||||
if (pos == std::string_view::npos) return physfs::Path("");
|
||||
if (pos == 0) return physfs::Path("/");
|
||||
return physfs::Path(substr(0, pos));
|
||||
}
|
||||
|
||||
bool Path::is_valid() const
|
||||
{
|
||||
auto pathNormalized = path_normalize(*this);
|
||||
return PHYSFS_exists(pathNormalized.c_str());
|
||||
}
|
||||
}
|
||||
29
src/util/physfs.hpp
Normal file
29
src/util/physfs.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace game::util::physfs
|
||||
{
|
||||
class Archive : public std::string
|
||||
{
|
||||
public:
|
||||
using std::string::operator=;
|
||||
|
||||
Archive(const std::filesystem::path&, const std::string& mount);
|
||||
~Archive();
|
||||
|
||||
bool is_valid();
|
||||
};
|
||||
|
||||
class Path : public std::string
|
||||
{
|
||||
public:
|
||||
bool is_valid() const;
|
||||
|
||||
Path directory_get() const;
|
||||
std::vector<uint8_t> read() const;
|
||||
};
|
||||
|
||||
std::string error_get();
|
||||
}
|
||||
22
src/util/preferences.cpp
Normal file
22
src/util/preferences.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "preferences.hpp"
|
||||
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
|
||||
namespace game::util::preferences
|
||||
{
|
||||
std::filesystem::path path()
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static constexpr auto filePath = "/snivy";
|
||||
std::filesystem::create_directories(filePath);
|
||||
return filePath;
|
||||
#else
|
||||
auto sdlPath = SDL_GetPrefPath(nullptr, "snivy");
|
||||
if (!sdlPath) return {};
|
||||
auto filePath = std::filesystem::path(sdlPath);
|
||||
std::filesystem::create_directories(filePath);
|
||||
SDL_free(sdlPath);
|
||||
return filePath;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
8
src/util/preferences.hpp
Normal file
8
src/util/preferences.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace game::util::preferences
|
||||
{
|
||||
std::filesystem::path path();
|
||||
}
|
||||
11
src/util/string.cpp
Normal file
11
src/util/string.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "string.hpp"
|
||||
|
||||
namespace game::util::string
|
||||
{
|
||||
std::string to_lower(const std::string& string)
|
||||
{
|
||||
std::string transformed = string;
|
||||
std::ranges::transform(transformed, transformed.begin(), [](const unsigned char c) { return std::tolower(c); });
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@
|
||||
|
||||
namespace game::util::string
|
||||
{
|
||||
template <typename Number>
|
||||
std::string format_commas(Number value, int decimalDigits = -1)
|
||||
template <typename Number> std::string format_commas(Number value, int decimalDigits = -1)
|
||||
{
|
||||
static_assert(std::is_arithmetic_v<Number>, "format_commas requires numeric types");
|
||||
|
||||
@@ -59,4 +58,6 @@ namespace game::util::string
|
||||
|
||||
return formattedInteger + fraction + exponent;
|
||||
}
|
||||
|
||||
std::string to_lower(const std::string&);
|
||||
}
|
||||
17
src/util/time.cpp
Normal file
17
src/util/time.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "time.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
|
||||
namespace game::util::time
|
||||
{
|
||||
std::string get(const char* format)
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
auto localTime = *std::localtime(&time);
|
||||
std::ostringstream timeString;
|
||||
timeString << std::put_time(&localTime, format);
|
||||
return timeString.str();
|
||||
}
|
||||
}
|
||||
10
src/util/time.hpp
Normal file
10
src/util/time.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace game::util::time
|
||||
{
|
||||
constexpr auto SECOND_M = 60;
|
||||
|
||||
std::string get(const char*);
|
||||
}
|
||||
@@ -10,4 +10,4 @@ namespace game::util::unordered_map
|
||||
if (it != map.end()) return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/util/vector.hpp
Normal file
86
src/util/vector.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "math.hpp"
|
||||
|
||||
namespace game::util::vector
|
||||
{
|
||||
template <typename T> bool in_bounds(std::vector<T>& vector, int index)
|
||||
{
|
||||
return (index >= 0 && index < (int)vector.size());
|
||||
}
|
||||
|
||||
template <typename T> T* find(std::vector<T>& vector, int index)
|
||||
{
|
||||
if (!in_bounds(vector, index)) return nullptr;
|
||||
return &vector[index];
|
||||
}
|
||||
|
||||
template <typename T> T* find(std::vector<T>& vector, T& value)
|
||||
{
|
||||
auto it = find(vector.begin(), vector.end(), value);
|
||||
return (it == vector.end()) ? nullptr : std::addressof(*it);
|
||||
}
|
||||
|
||||
template <typename T> int index_get(std::vector<T>& vector, T& value)
|
||||
{
|
||||
auto it = find(vector.begin(), vector.end(), value);
|
||||
return (it == vector.end()) ? -1 : (int)std::distance(vector.begin(), it);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args> int emplace_index(std::vector<T>& vector, Args&&... args)
|
||||
{
|
||||
auto index = (int)vector.size();
|
||||
vector.emplace_back(std::forward<Args>(args)...);
|
||||
return index;
|
||||
}
|
||||
|
||||
template <typename T> int push_index(std::vector<T>& vector, const T& value)
|
||||
{
|
||||
auto index = (int)vector.size();
|
||||
vector.push_back(value);
|
||||
return index;
|
||||
}
|
||||
|
||||
template <typename T> int push_index(std::vector<T>& vector, T&& value)
|
||||
{
|
||||
auto index = (int)vector.size();
|
||||
vector.push_back(std::move(value));
|
||||
return index;
|
||||
}
|
||||
|
||||
template <typename T, typename WeightFunction>
|
||||
int random_index_weighted(const std::vector<T>& vector, WeightFunction weightFunction)
|
||||
{
|
||||
if (vector.empty()) return -1;
|
||||
if (vector.size() == 1) return 0;
|
||||
|
||||
float total{};
|
||||
float accumulator{};
|
||||
|
||||
for (auto& item : vector)
|
||||
{
|
||||
auto weight = (float)(weightFunction(item));
|
||||
if (weight > 0.0f) total += weight;
|
||||
}
|
||||
|
||||
if (total <= 0.0f) return -1;
|
||||
|
||||
float randomValue = math::random_max(total);
|
||||
int lastIndex = -1;
|
||||
|
||||
for (int i = 0; i < (int)vector.size(); i++)
|
||||
{
|
||||
auto weight = (float)(weightFunction(vector[i]));
|
||||
if (weight <= 0.0f) continue;
|
||||
|
||||
lastIndex = i;
|
||||
accumulator += weight;
|
||||
if (randomValue < accumulator) return i;
|
||||
}
|
||||
|
||||
return lastIndex;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace game::util::vector
|
||||
{
|
||||
template <typename T> bool in_bounds(std::vector<T>& vector, int index)
|
||||
{
|
||||
return (index >= 0 && index < vector.size());
|
||||
}
|
||||
|
||||
template <typename T> T* find(std::vector<T>& vector, int index)
|
||||
{
|
||||
if (!in_bounds(vector, index)) return nullptr;
|
||||
return &vector[index];
|
||||
}
|
||||
}
|
||||
53
src/util/web_filesystem.cpp
Normal file
53
src/util/web_filesystem.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "web_filesystem.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace game::util::web_filesystem
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_JS(void, idbfs_init_async, (), {
|
||||
Module.filesystemReady = 0;
|
||||
try
|
||||
{
|
||||
FS.mkdir('/snivy');
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
}
|
||||
FS.mount(IDBFS, {}, '/snivy');
|
||||
FS.syncfs(
|
||||
true, function(err) {
|
||||
if (err) console.error('IDBFS init sync failed', err);
|
||||
Module.filesystemReady = 1;
|
||||
});
|
||||
});
|
||||
|
||||
EM_JS(int, idbfs_ready, (), { return Module.filesystemReady ? 1 : 0; });
|
||||
|
||||
EM_JS(void, idbfs_flush_async, (), {
|
||||
FS.syncfs(
|
||||
false, function(err) {
|
||||
if (err) console.error('IDBFS flush failed', err);
|
||||
});
|
||||
});
|
||||
#endif
|
||||
|
||||
void init_and_wait()
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
idbfs_init_async();
|
||||
while (!idbfs_ready())
|
||||
emscripten_sleep(16);
|
||||
#endif
|
||||
}
|
||||
|
||||
void flush_async()
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
idbfs_flush_async();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
7
src/util/web_filesystem.hpp
Normal file
7
src/util/web_filesystem.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace game::util::web_filesystem
|
||||
{
|
||||
void init_and_wait();
|
||||
void flush_async();
|
||||
};
|
||||
49
src/util/working_directory.cpp
Normal file
49
src/util/working_directory.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "working_directory.hpp"
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
namespace game::util
|
||||
{
|
||||
WorkingDirectory::WorkingDirectory(const std::filesystem::path& path, Type type)
|
||||
{
|
||||
std::error_code ec{};
|
||||
previous = std::filesystem::current_path(ec);
|
||||
if (ec)
|
||||
{
|
||||
std::cout << std::format("Could not query current directory: {}", ec.message()) << "\n";
|
||||
previous.clear();
|
||||
ec.clear();
|
||||
}
|
||||
|
||||
if (type == FILE && path.has_parent_path())
|
||||
{
|
||||
std::filesystem::path parentPath = path.parent_path();
|
||||
std::filesystem::current_path(parentPath, ec);
|
||||
if (ec)
|
||||
std::cout << std::format("Could not set current directory to {}: {}", parentPath.string(), ec.message())
|
||||
<< "\n";
|
||||
}
|
||||
else if (std::filesystem::is_directory(path))
|
||||
{
|
||||
std::filesystem::current_path(path, ec);
|
||||
if (ec)
|
||||
std::cout << std::format("Could not set current directory to {}: {}", path.string(), ec.message()) << "\n";
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
WorkingDirectory::~WorkingDirectory()
|
||||
{
|
||||
if (previous.empty() || !isValid) return;
|
||||
|
||||
std::error_code ec{};
|
||||
std::filesystem::current_path(previous, ec);
|
||||
if (ec)
|
||||
std::cout << std::format("Could not restore current directory to {}: {}", previous.string(), ec.message())
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
bool WorkingDirectory::is_valid() const { return isValid; }
|
||||
}
|
||||
24
src/util/working_directory.hpp
Normal file
24
src/util/working_directory.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace game::util
|
||||
{
|
||||
class WorkingDirectory
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
DIRECTORY,
|
||||
FILE
|
||||
};
|
||||
|
||||
std::filesystem::path previous{};
|
||||
bool isValid{};
|
||||
|
||||
WorkingDirectory() = default;
|
||||
WorkingDirectory(const std::filesystem::path&, Type type = DIRECTORY);
|
||||
~WorkingDirectory();
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace game::util::xml
|
||||
{
|
||||
XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* value)
|
||||
{
|
||||
const char* temp = nullptr;
|
||||
auto result = element->QueryStringAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && temp && value) *value = temp;
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_path_attribute(XMLElement* element, const char* attribute, std::filesystem::path* value)
|
||||
{
|
||||
std::string temp{};
|
||||
auto result = query_string_attribute(element, attribute, &temp);
|
||||
if (value) *value = std::filesystem::path(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_color_attribute(XMLElement* element, const char* attribute, float* value)
|
||||
{
|
||||
int temp{};
|
||||
auto result = element->QueryIntAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && value) *value = (temp / 255.0f);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace game::util::xml
|
||||
{
|
||||
tinyxml2::XMLError query_string_attribute(tinyxml2::XMLElement*, const char*, std::string*);
|
||||
tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement*, const char*, std::filesystem::path*);
|
||||
tinyxml2::XMLError query_color_attribute(tinyxml2::XMLElement*, const char*, float*);
|
||||
}
|
||||
Reference in New Issue
Block a user