The Mega Snivy Update
Some checks failed
Build / Build Game (push) Has been cancelled

This commit is contained in:
2026-02-28 21:48:00 -05:00
parent 8b2edd1359
commit 17f3348e94
163 changed files with 8725 additions and 13281 deletions

9
src/util/color.hpp Normal file
View 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
View 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));
}
}

View File

@@ -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); }
}
}

View 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;
}
}

View 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
View 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
View 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
View 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
View 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; \
}())

View 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; }
}

View 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};
};
}

View File

@@ -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); }
}

View 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
}

View File

@@ -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)); }
}
}

View File

@@ -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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,8 @@
#pragma once
#include <filesystem>
namespace game::util::preferences
{
std::filesystem::path path();
}

11
src/util/string.cpp Normal file
View 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;
}
}

View File

@@ -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
View 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
View File

@@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace game::util::time
{
constexpr auto SECOND_M = 60;
std::string get(const char*);
}

View File

@@ -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
View 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;
}
}

View File

@@ -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];
}
}

View 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
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace game::util::web_filesystem
{
void init_and_wait();
void flush_async();
};

View 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; }
}

View 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;
};
}

View File

@@ -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;
}
}

View File

@@ -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*);
}