refactoring, new game(s) in progress
This commit is contained in:
@@ -114,7 +114,9 @@ file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
|
|||||||
src/resource/xml/*.cpp
|
src/resource/xml/*.cpp
|
||||||
src/state/*.cpp
|
src/state/*.cpp
|
||||||
src/state/play/*.cpp
|
src/state/play/*.cpp
|
||||||
src/state/play/arcade/*.cpp
|
src/state/play/menu/*.cpp
|
||||||
|
src/state/play/menu/arcade/*.cpp
|
||||||
|
src/state/play/item/*.cpp
|
||||||
src/state/select/*.cpp
|
src/state/select/*.cpp
|
||||||
src/entity/*.cpp
|
src/entity/*.cpp
|
||||||
src/window/*.cpp
|
src/window/*.cpp
|
||||||
|
|||||||
@@ -143,6 +143,30 @@ namespace game::entity
|
|||||||
|
|
||||||
auto override_handle = [&](Anm2::Frame& overrideFrame)
|
auto override_handle = [&](Anm2::Frame& overrideFrame)
|
||||||
{
|
{
|
||||||
|
auto vec2_set = [](glm::vec2& destination, const Anm2::FrameOptional::Vec2& source)
|
||||||
|
{
|
||||||
|
if (source.x.has_value()) destination.x = *source.x;
|
||||||
|
if (source.y.has_value()) destination.y = *source.y;
|
||||||
|
};
|
||||||
|
auto vec2_add = [](glm::vec2& destination, const Anm2::FrameOptional::Vec2& source)
|
||||||
|
{
|
||||||
|
if (source.x.has_value()) destination.x += *source.x;
|
||||||
|
if (source.y.has_value()) destination.y += *source.y;
|
||||||
|
};
|
||||||
|
auto vec3_set = [](glm::vec3& destination, const Anm2::FrameOptional::Vec3& source)
|
||||||
|
{
|
||||||
|
if (source.x.has_value()) destination.x = *source.x;
|
||||||
|
if (source.y.has_value()) destination.y = *source.y;
|
||||||
|
if (source.z.has_value()) destination.z = *source.z;
|
||||||
|
};
|
||||||
|
auto vec4_set = [](glm::vec4& destination, const Anm2::FrameOptional::Vec4& source)
|
||||||
|
{
|
||||||
|
if (source.x.has_value()) destination.x = *source.x;
|
||||||
|
if (source.y.has_value()) destination.y = *source.y;
|
||||||
|
if (source.z.has_value()) destination.z = *source.z;
|
||||||
|
if (source.w.has_value()) destination.w = *source.w;
|
||||||
|
};
|
||||||
|
|
||||||
for (auto& override : overrides)
|
for (auto& override : overrides)
|
||||||
{
|
{
|
||||||
if (override.type != type) continue;
|
if (override.type != type) continue;
|
||||||
@@ -153,19 +177,19 @@ namespace game::entity
|
|||||||
switch (override.mode)
|
switch (override.mode)
|
||||||
{
|
{
|
||||||
case Override::SET:
|
case Override::SET:
|
||||||
if (source.position.has_value()) overrideFrame.position = *source.position;
|
vec2_set(overrideFrame.position, source.position);
|
||||||
if (source.pivot.has_value()) overrideFrame.pivot = *source.pivot;
|
vec2_set(overrideFrame.pivot, source.pivot);
|
||||||
if (source.size.has_value()) overrideFrame.size = *source.size;
|
vec2_set(overrideFrame.size, source.size);
|
||||||
if (source.scale.has_value()) overrideFrame.scale = *source.scale;
|
vec2_set(overrideFrame.scale, source.scale);
|
||||||
if (source.crop.has_value()) overrideFrame.crop = *source.crop;
|
vec2_set(overrideFrame.crop, source.crop);
|
||||||
if (source.rotation.has_value()) overrideFrame.rotation = *source.rotation;
|
if (source.rotation.has_value()) overrideFrame.rotation = *source.rotation;
|
||||||
if (source.tint.has_value()) overrideFrame.tint = *source.tint;
|
vec4_set(overrideFrame.tint, source.tint);
|
||||||
if (source.colorOffset.has_value()) overrideFrame.colorOffset = *source.colorOffset;
|
vec3_set(overrideFrame.colorOffset, source.colorOffset);
|
||||||
if (source.isInterpolated.has_value()) overrideFrame.isInterpolated = *source.isInterpolated;
|
if (source.isInterpolated.has_value()) overrideFrame.isInterpolated = *source.isInterpolated;
|
||||||
if (source.isVisible.has_value()) overrideFrame.isVisible = *source.isVisible;
|
if (source.isVisible.has_value()) overrideFrame.isVisible = *source.isVisible;
|
||||||
break;
|
break;
|
||||||
case Override::ADD:
|
case Override::ADD:
|
||||||
if (source.scale.has_value()) overrideFrame.scale += *source.scale;
|
vec2_add(overrideFrame.scale, source.scale);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -344,6 +368,8 @@ namespace game::entity
|
|||||||
|
|
||||||
void Actor::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas)
|
void Actor::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas)
|
||||||
{
|
{
|
||||||
|
if (!isVisible) return;
|
||||||
|
|
||||||
auto animation = animation_get();
|
auto animation = animation_get();
|
||||||
if (!animation) return;
|
if (!animation) return;
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ namespace game::entity
|
|||||||
glm::vec2 position{};
|
glm::vec2 position{};
|
||||||
float time{};
|
float time{};
|
||||||
bool isShowNulls{};
|
bool isShowNulls{};
|
||||||
|
bool isVisible{true};
|
||||||
int animationIndex{-1};
|
int animationIndex{-1};
|
||||||
int playedEventID{-1};
|
int playedEventID{-1};
|
||||||
float startTime{};
|
float startTime{};
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ namespace game
|
|||||||
logger.info("Initialized Dear ImGui OpenGL backend");
|
logger.info("Initialized Dear ImGui OpenGL backend");
|
||||||
|
|
||||||
imgui::style::color_set(settings.color);
|
imgui::style::color_set(settings.color);
|
||||||
imgui::style::rounding_set();
|
imgui::style::widget_set();
|
||||||
math::random_seed_set();
|
math::random_seed_set();
|
||||||
resource::Audio::volume_set((float)settings.volume / 100);
|
resource::Audio::volume_set((float)settings.volume / 100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "canvas.hpp"
|
#include "canvas.hpp"
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "../util/imgui.hpp"
|
#include "../util/imgui.hpp"
|
||||||
@@ -95,7 +96,10 @@ namespace game
|
|||||||
Canvas::Canvas(const Canvas& other) : Canvas(other.size, other.flags)
|
Canvas::Canvas(const Canvas& other) : Canvas(other.size, other.flags)
|
||||||
{
|
{
|
||||||
pan = other.pan;
|
pan = other.pan;
|
||||||
|
shakeOffset = other.shakeOffset;
|
||||||
zoom = other.zoom;
|
zoom = other.zoom;
|
||||||
|
shakeTimer = other.shakeTimer;
|
||||||
|
shakeTimerMax = other.shakeTimerMax;
|
||||||
|
|
||||||
if ((flags & DEFAULT) == 0 && (other.flags & DEFAULT) == 0)
|
if ((flags & DEFAULT) == 0 && (other.flags & DEFAULT) == 0)
|
||||||
{
|
{
|
||||||
@@ -110,7 +114,10 @@ namespace game
|
|||||||
{
|
{
|
||||||
size = other.size;
|
size = other.size;
|
||||||
pan = other.pan;
|
pan = other.pan;
|
||||||
|
shakeOffset = other.shakeOffset;
|
||||||
zoom = other.zoom;
|
zoom = other.zoom;
|
||||||
|
shakeTimer = other.shakeTimer;
|
||||||
|
shakeTimerMax = other.shakeTimerMax;
|
||||||
flags = other.flags;
|
flags = other.flags;
|
||||||
fbo = other.fbo;
|
fbo = other.fbo;
|
||||||
rbo = other.rbo;
|
rbo = other.rbo;
|
||||||
@@ -118,7 +125,10 @@ namespace game
|
|||||||
|
|
||||||
other.size = {};
|
other.size = {};
|
||||||
other.pan = {};
|
other.pan = {};
|
||||||
|
other.shakeOffset = {};
|
||||||
other.zoom = 100.0f;
|
other.zoom = 100.0f;
|
||||||
|
other.shakeTimer = 0;
|
||||||
|
other.shakeTimerMax = 0;
|
||||||
other.flags = FLIP;
|
other.flags = FLIP;
|
||||||
other.fbo = 0;
|
other.fbo = 0;
|
||||||
other.rbo = 0;
|
other.rbo = 0;
|
||||||
@@ -156,7 +166,10 @@ namespace game
|
|||||||
|
|
||||||
size = other.size;
|
size = other.size;
|
||||||
pan = other.pan;
|
pan = other.pan;
|
||||||
|
shakeOffset = other.shakeOffset;
|
||||||
zoom = other.zoom;
|
zoom = other.zoom;
|
||||||
|
shakeTimer = other.shakeTimer;
|
||||||
|
shakeTimerMax = other.shakeTimerMax;
|
||||||
flags = other.flags;
|
flags = other.flags;
|
||||||
fbo = other.fbo;
|
fbo = other.fbo;
|
||||||
rbo = other.rbo;
|
rbo = other.rbo;
|
||||||
@@ -164,7 +177,10 @@ namespace game
|
|||||||
|
|
||||||
other.size = {};
|
other.size = {};
|
||||||
other.pan = {};
|
other.pan = {};
|
||||||
|
other.shakeOffset = {};
|
||||||
other.zoom = 100.0f;
|
other.zoom = 100.0f;
|
||||||
|
other.shakeTimer = 0;
|
||||||
|
other.shakeTimerMax = 0;
|
||||||
other.flags = FLIP;
|
other.flags = FLIP;
|
||||||
other.fbo = 0;
|
other.fbo = 0;
|
||||||
other.rbo = 0;
|
other.rbo = 0;
|
||||||
@@ -242,9 +258,51 @@ namespace game
|
|||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Canvas::texture_render(Shader& shader, const Canvas& source, mat4 model, vec4 tint, vec3 colorOffset) const
|
||||||
|
{
|
||||||
|
auto shakenModel = glm::translate(model, glm::vec3(source.shakeOffset, 0.0f));
|
||||||
|
texture_render(shader, source.texture, shakenModel, tint, colorOffset);
|
||||||
|
}
|
||||||
|
|
||||||
void Canvas::render(Shader& shader, mat4& model, vec4 tint, vec3 colorOffset) const
|
void Canvas::render(Shader& shader, mat4& model, vec4 tint, vec3 colorOffset) const
|
||||||
{
|
{
|
||||||
texture_render(shader, texture, model, tint, colorOffset);
|
auto shakenModel = glm::translate(model, glm::vec3(shakeOffset, 0.0f));
|
||||||
|
texture_render(shader, texture, shakenModel, tint, colorOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Canvas::shake(float magnitude, int timeTicks)
|
||||||
|
{
|
||||||
|
shakeTimerMax = std::max(1, timeTicks);
|
||||||
|
shakeTimer = shakeTimerMax;
|
||||||
|
shakeOffset = {math::random_in_range(-magnitude, magnitude), math::random_in_range(-magnitude, magnitude)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Canvas::tick()
|
||||||
|
{
|
||||||
|
static constexpr auto SHAKE_LERP_FACTOR = 0.35f;
|
||||||
|
static constexpr auto SHAKE_DECAY = 0.85f;
|
||||||
|
static constexpr auto SHAKE_EPSILON = 0.1f;
|
||||||
|
|
||||||
|
if (shakeTimer > 0)
|
||||||
|
{
|
||||||
|
shakeTimer--;
|
||||||
|
|
||||||
|
auto magnitude = glm::length(shakeOffset) * SHAKE_DECAY;
|
||||||
|
auto timerFactor = (float)shakeTimer / (float)std::max(1, shakeTimerMax);
|
||||||
|
auto target =
|
||||||
|
glm::vec2(math::random_in_range(-magnitude, magnitude), math::random_in_range(-magnitude, magnitude)) *
|
||||||
|
timerFactor;
|
||||||
|
shakeOffset = glm::mix(shakeOffset, target, SHAKE_LERP_FACTOR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shakeOffset = glm::mix(shakeOffset, glm::vec2{}, SHAKE_LERP_FACTOR);
|
||||||
|
if (glm::length(shakeOffset) < SHAKE_EPSILON)
|
||||||
|
{
|
||||||
|
shakeOffset = {};
|
||||||
|
shakeTimerMax = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Canvas::bind()
|
void Canvas::bind()
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ namespace game
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr glm::vec4 CLEAR_COLOR = {0, 0, 0, 0};
|
static constexpr glm::vec4 CLEAR_COLOR = {0, 0, 0, 0};
|
||||||
|
static constexpr float SHAKE_MAGNITUDE = 0.05f;
|
||||||
|
static constexpr float SHAKE_TIME_TICKS = 60;
|
||||||
|
|
||||||
enum Flag
|
enum Flag
|
||||||
{
|
{
|
||||||
@@ -46,7 +48,10 @@ namespace game
|
|||||||
|
|
||||||
glm::ivec2 size{};
|
glm::ivec2 size{};
|
||||||
glm::vec2 pan{};
|
glm::vec2 pan{};
|
||||||
|
glm::vec2 shakeOffset{};
|
||||||
float zoom{100.0f};
|
float zoom{100.0f};
|
||||||
|
int shakeTimer{};
|
||||||
|
int shakeTimerMax{};
|
||||||
Flags flags{FLIP};
|
Flags flags{FLIP};
|
||||||
|
|
||||||
Canvas() = default;
|
Canvas() = default;
|
||||||
@@ -64,6 +69,8 @@ namespace game
|
|||||||
void texture_render(resource::Shader&, const Canvas&, glm::mat4, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}) const;
|
void texture_render(resource::Shader&, const Canvas&, glm::mat4, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}) const;
|
||||||
void rect_render(resource::Shader&, glm::mat4&, glm::vec4 = glm::vec4(0, 0, 1, 1)) const;
|
void rect_render(resource::Shader&, glm::mat4&, glm::vec4 = glm::vec4(0, 0, 1, 1)) const;
|
||||||
void render(resource::Shader&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}) const;
|
void render(resource::Shader&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}) const;
|
||||||
|
void shake(float magnitude = SHAKE_MAGNITUDE, int timeTicks = SHAKE_TIME_TICKS);
|
||||||
|
void tick();
|
||||||
void bind();
|
void bind();
|
||||||
void size_set(glm::ivec2 size);
|
void size_set(glm::ivec2 size);
|
||||||
void clear(glm::vec4 color = CLEAR_COLOR);
|
void clear(glm::vec4 color = CLEAR_COLOR);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ namespace game::resource
|
|||||||
internal.reset();
|
internal.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::play(bool isLoop)
|
void Audio::play(bool isLoop) const
|
||||||
{
|
{
|
||||||
if (!internal) return;
|
if (!internal) return;
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ namespace game::resource
|
|||||||
if (options) SDL_DestroyProperties(options);
|
if (options) SDL_DestroyProperties(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::stop()
|
void Audio::stop() const
|
||||||
{
|
{
|
||||||
if (track) MIX_StopTrack(track, 0);
|
if (track) MIX_StopTrack(track, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace game::resource
|
|||||||
void unload();
|
void unload();
|
||||||
|
|
||||||
std::shared_ptr<MIX_Audio> internal{};
|
std::shared_ptr<MIX_Audio> internal{};
|
||||||
MIX_Track* track{nullptr};
|
mutable MIX_Track* track{nullptr};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Audio() = default;
|
Audio() = default;
|
||||||
@@ -26,8 +26,8 @@ namespace game::resource
|
|||||||
Audio& operator=(Audio&&) noexcept;
|
Audio& operator=(Audio&&) noexcept;
|
||||||
~Audio();
|
~Audio();
|
||||||
bool is_valid() const;
|
bool is_valid() const;
|
||||||
void play(bool isLoop = false);
|
void play(bool isLoop = false) const;
|
||||||
void stop();
|
void stop() const;
|
||||||
bool is_playing() const;
|
bool is_playing() const;
|
||||||
static void volume_set(float volume);
|
static void volume_set(float volume);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,14 +109,65 @@ namespace game::resource::xml
|
|||||||
|
|
||||||
struct FrameOptional
|
struct FrameOptional
|
||||||
{
|
{
|
||||||
std::optional<glm::vec2> crop{};
|
struct Vec2
|
||||||
std::optional<glm::vec2> position{};
|
{
|
||||||
std::optional<glm::vec2> pivot{};
|
std::optional<float> x{};
|
||||||
std::optional<glm::vec2> size{};
|
std::optional<float> y{};
|
||||||
std::optional<glm::vec2> scale{};
|
|
||||||
|
Vec2() = default;
|
||||||
|
Vec2(const glm::vec2& value) : x(value.x), y(value.y) {}
|
||||||
|
Vec2& operator=(const glm::vec2& value)
|
||||||
|
{
|
||||||
|
x = value.x;
|
||||||
|
y = value.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vec3
|
||||||
|
{
|
||||||
|
std::optional<float> x{};
|
||||||
|
std::optional<float> y{};
|
||||||
|
std::optional<float> z{};
|
||||||
|
|
||||||
|
Vec3() = default;
|
||||||
|
Vec3(const glm::vec3& value) : x(value.x), y(value.y), z(value.z) {}
|
||||||
|
Vec3& operator=(const glm::vec3& value)
|
||||||
|
{
|
||||||
|
x = value.x;
|
||||||
|
y = value.y;
|
||||||
|
z = value.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vec4
|
||||||
|
{
|
||||||
|
std::optional<float> x{};
|
||||||
|
std::optional<float> y{};
|
||||||
|
std::optional<float> z{};
|
||||||
|
std::optional<float> w{};
|
||||||
|
|
||||||
|
Vec4() = default;
|
||||||
|
Vec4(const glm::vec4& value) : x(value.x), y(value.y), z(value.z), w(value.w) {}
|
||||||
|
Vec4& operator=(const glm::vec4& value)
|
||||||
|
{
|
||||||
|
x = value.x;
|
||||||
|
y = value.y;
|
||||||
|
z = value.z;
|
||||||
|
w = value.w;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vec2 crop{};
|
||||||
|
Vec2 position{};
|
||||||
|
Vec2 pivot{};
|
||||||
|
Vec2 size{};
|
||||||
|
Vec2 scale{};
|
||||||
std::optional<float> rotation{};
|
std::optional<float> rotation{};
|
||||||
std::optional<glm::vec4> tint{};
|
Vec4 tint{};
|
||||||
std::optional<glm::vec3> colorOffset{};
|
Vec3 colorOffset{};
|
||||||
std::optional<bool> isInterpolated{};
|
std::optional<bool> isInterpolated{};
|
||||||
std::optional<bool> isVisible{};
|
std::optional<bool> isVisible{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize area schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto archive = path.directory_get();
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
|
|||||||
@@ -229,6 +229,16 @@ namespace game::resource::xml
|
|||||||
else
|
else
|
||||||
logger.warning(std::format("No character skill_check.xml file found: {}", path.string()));
|
logger.warning(std::format("No character skill_check.xml file found: {}", path.string()));
|
||||||
|
|
||||||
|
if (auto dungeonSchemaPath = physfs::Path(archive + "/" + "dungeon.xml"); dungeonSchemaPath.is_valid())
|
||||||
|
dungeonSchema = Dungeon(dungeonSchemaPath);
|
||||||
|
else
|
||||||
|
logger.warning(std::format("No character dungeon.xml file found: {}", path.string()));
|
||||||
|
|
||||||
|
if (auto orbitSchemaPath = physfs::Path(archive + "/" + "orbit.xml"); orbitSchemaPath.is_valid())
|
||||||
|
orbitSchema = Orbit(orbitSchemaPath, dialogue);
|
||||||
|
else
|
||||||
|
logger.warning(std::format("No character orbit.xml file found: {}", path.string()));
|
||||||
|
|
||||||
if (auto stringsPath = physfs::Path(archive + "/" + "strings.xml"); stringsPath.is_valid())
|
if (auto stringsPath = physfs::Path(archive + "/" + "strings.xml"); stringsPath.is_valid())
|
||||||
strings = Strings(stringsPath);
|
strings = Strings(stringsPath);
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
#include "area.hpp"
|
#include "area.hpp"
|
||||||
#include "cursor.hpp"
|
#include "cursor.hpp"
|
||||||
#include "dialogue.hpp"
|
#include "dialogue.hpp"
|
||||||
|
#include "dungeon.hpp"
|
||||||
#include "item.hpp"
|
#include "item.hpp"
|
||||||
#include "menu.hpp"
|
#include "menu.hpp"
|
||||||
|
#include "orbit.hpp"
|
||||||
#include "save.hpp"
|
#include "save.hpp"
|
||||||
#include "skill_check.hpp"
|
#include "skill_check.hpp"
|
||||||
#include "strings.hpp"
|
#include "strings.hpp"
|
||||||
@@ -100,6 +102,8 @@ namespace game::resource::xml
|
|||||||
Menu menuSchema{};
|
Menu menuSchema{};
|
||||||
Cursor cursorSchema{};
|
Cursor cursorSchema{};
|
||||||
SkillCheck skillCheckSchema{};
|
SkillCheck skillCheckSchema{};
|
||||||
|
Dungeon dungeonSchema{};
|
||||||
|
Orbit orbitSchema{};
|
||||||
Strings strings{};
|
Strings strings{};
|
||||||
|
|
||||||
Save save{};
|
Save save{};
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize cursor schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto archive = path.directory_get();
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ namespace game::resource::xml
|
|||||||
|
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize dialogue: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto root = document.RootElement())
|
if (auto root = document.RootElement())
|
||||||
{
|
{
|
||||||
|
|||||||
39
src/resource/xml/dungeon.cpp
Normal file
39
src/resource/xml/dungeon.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "dungeon.hpp"
|
||||||
|
|
||||||
|
#include "../../log.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace tinyxml2;
|
||||||
|
|
||||||
|
namespace game::resource::xml
|
||||||
|
{
|
||||||
|
Dungeon::Dungeon(const util::physfs::Path& path)
|
||||||
|
{
|
||||||
|
XMLDocument document;
|
||||||
|
|
||||||
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize dungeon schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto root = document.RootElement())
|
||||||
|
{
|
||||||
|
if (std::string_view(root->Name()) != "Dungeon")
|
||||||
|
{
|
||||||
|
logger.error(std::format("Dungeon schema root element is not Dungeon: {}", path.c_str()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
query_string_attribute(root, "Title", &title);
|
||||||
|
query_string_attribute(root, "Description", &description);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = true;
|
||||||
|
logger.info(std::format("Initialized dungeon schema: {}", path.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Dungeon::is_valid() const { return isValid; };
|
||||||
|
}
|
||||||
21
src/resource/xml/dungeon.hpp
Normal file
21
src/resource/xml/dungeon.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../util/physfs.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace game::resource::xml
|
||||||
|
{
|
||||||
|
class Dungeon
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string title{"Dungeon"};
|
||||||
|
std::string description{"Template dungeon schema."};
|
||||||
|
bool isValid{};
|
||||||
|
|
||||||
|
Dungeon() = default;
|
||||||
|
Dungeon(const util::physfs::Path&);
|
||||||
|
|
||||||
|
bool is_valid() const;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,7 +21,11 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize item schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto archive = path.directory_get();
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize menu schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto archive = path.directory_get();
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
|
|||||||
134
src/resource/xml/orbit.cpp
Normal file
134
src/resource/xml/orbit.cpp
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include "orbit.hpp"
|
||||||
|
|
||||||
|
#include "../../log.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace tinyxml2;
|
||||||
|
using namespace game::util;
|
||||||
|
|
||||||
|
namespace game::resource::xml
|
||||||
|
{
|
||||||
|
Orbit::Orbit(const util::physfs::Path& path, Dialogue& dialogue)
|
||||||
|
{
|
||||||
|
XMLDocument document;
|
||||||
|
|
||||||
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize orbit schema: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
|
if (auto root = document.RootElement())
|
||||||
|
{
|
||||||
|
std::string textureRootPath{};
|
||||||
|
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||||
|
|
||||||
|
std::string soundRootPath{};
|
||||||
|
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||||
|
|
||||||
|
if (std::string_view(root->Name()) != "Orbit")
|
||||||
|
{
|
||||||
|
logger.error(std::format("Orbit schema root element is not Orbit: {}", path.c_str()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root->QueryIntAttribute("StartTime", &startTime);
|
||||||
|
root->QueryFloatAttribute("RewardChanceBase", &rewardChanceBase);
|
||||||
|
root->QueryFloatAttribute("RewardChanceScoreBonus", &rewardChanceScoreBonus);
|
||||||
|
root->QueryFloatAttribute("RewardRollChanceBase", &rewardRollChanceBase);
|
||||||
|
root->QueryFloatAttribute("RewardRollScoreBonus", &rewardRollScoreBonus);
|
||||||
|
dialogue.query_pool_id(root, "HurtDialoguePoolID", poolHurt.id);
|
||||||
|
dialogue.query_pool_id(root, "DeathDialoguePoolID", poolDeath.id);
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Player"))
|
||||||
|
{
|
||||||
|
query_anm2(element, "Anm2", archive, textureRootPath, player.anm2);
|
||||||
|
query_string_attribute(element, "HitboxNull", &player.hitboxNull);
|
||||||
|
element->QueryFloatAttribute("FollowerRadius", &player.followerRadius);
|
||||||
|
element->QueryFloatAttribute("TargetAcceleration", &player.targetAcceleration);
|
||||||
|
element->QueryFloatAttribute("RotationSpeed", &player.rotationSpeed);
|
||||||
|
element->QueryFloatAttribute("RotationSpeedMax", &player.rotationSpeedMax);
|
||||||
|
element->QueryFloatAttribute("RotationSpeedFriction", &player.rotationSpeedFriction);
|
||||||
|
element->QueryIntAttribute("TimeAfterHurt", &player.timeAfterHurt);
|
||||||
|
|
||||||
|
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||||
|
{
|
||||||
|
query_string_attribute(animationsElement, "Idle", &player.animations.idle);
|
||||||
|
query_string_attribute(animationsElement, "Spawn", &player.animations.spawn);
|
||||||
|
query_string_attribute(animationsElement, "Death", &player.animations.death);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Follower"))
|
||||||
|
{
|
||||||
|
query_anm2(element, "Anm2", archive, textureRootPath, follower.anm2);
|
||||||
|
query_string_attribute(element, "HitboxNull", &follower.hitboxNull);
|
||||||
|
element->QueryFloatAttribute("TargetAcceleration", &follower.targetAcceleration);
|
||||||
|
query_string_attribute(element, "OverrideTintLayer", &follower.overrideTintLayer);
|
||||||
|
|
||||||
|
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||||
|
{
|
||||||
|
query_string_attribute(animationsElement, "Idle", &follower.animations.idle);
|
||||||
|
query_string_attribute(animationsElement, "Spawn", &follower.animations.spawn);
|
||||||
|
query_string_attribute(animationsElement, "Death", &follower.animations.death);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Enemy"))
|
||||||
|
{
|
||||||
|
query_anm2(element, "Anm2", archive, textureRootPath, enemy.anm2);
|
||||||
|
query_string_attribute(element, "HitboxNull", &enemy.hitboxNull);
|
||||||
|
element->QueryFloatAttribute("Speed", &enemy.speed);
|
||||||
|
element->QueryFloatAttribute("SpeedScoreBonus", &enemy.speedScoreBonus);
|
||||||
|
element->QueryFloatAttribute("SpeedGainBase", &enemy.speedGainBase);
|
||||||
|
element->QueryFloatAttribute("SpeedGainScoreBonus", &enemy.speedGainScoreBonus);
|
||||||
|
element->QueryFloatAttribute("SpawnChanceBase", &enemy.spawnChanceBase);
|
||||||
|
element->QueryFloatAttribute("SpawnChanceScoreBonus", &enemy.spawnChanceScoreBonus);
|
||||||
|
element->QueryFloatAttribute("SpawnPadding", &enemy.spawnPadding);
|
||||||
|
query_string_attribute(element, "OverrideTintLayer", &enemy.overrideTintLayer);
|
||||||
|
|
||||||
|
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||||
|
{
|
||||||
|
query_string_attribute(animationsElement, "Idle", &enemy.animations.idle);
|
||||||
|
query_string_attribute(animationsElement, "Spawn", &enemy.animations.spawn);
|
||||||
|
query_string_attribute(animationsElement, "Death", &enemy.animations.death);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Warning"))
|
||||||
|
{
|
||||||
|
query_anm2(element, "Anm2", archive, textureRootPath, warning.anm2);
|
||||||
|
query_string_attribute(element, "OverrideTintLayer", &warning.overrideTintLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Colors"))
|
||||||
|
{
|
||||||
|
for (auto child = element->FirstChildElement("Color"); child; child = child->NextSiblingElement("Color"))
|
||||||
|
{
|
||||||
|
Color color{};
|
||||||
|
query_vec3(child, "ColorR", "ColorG", "ColorB", color.value);
|
||||||
|
child->QueryIntAttribute("ScoreThreshold", &color.scoreThreshold);
|
||||||
|
dialogue.query_pool_id(child, "DialoguePoolID", color.pool.id);
|
||||||
|
colors.emplace_back(std::move(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Sounds"))
|
||||||
|
{
|
||||||
|
query_sound_entry_collection(element, "LevelUp", archive, soundRootPath, sounds.levelUp);
|
||||||
|
query_sound_entry_collection(element, "Hurt", archive, soundRootPath, sounds.hurt);
|
||||||
|
query_sound_entry_collection(element, "HighScore", archive, soundRootPath, sounds.highScore);
|
||||||
|
query_sound_entry_collection(element, "HighScoreLoss", archive, soundRootPath, sounds.highScoreLoss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = true;
|
||||||
|
logger.info(std::format("Initialized orbit schema: {}", path.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Orbit::is_valid() const { return isValid; };
|
||||||
|
}
|
||||||
100
src/resource/xml/orbit.hpp
Normal file
100
src/resource/xml/orbit.hpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "dialogue.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
namespace game::resource::xml
|
||||||
|
{
|
||||||
|
class Orbit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Sounds
|
||||||
|
{
|
||||||
|
SoundEntryCollection levelUp{};
|
||||||
|
SoundEntryCollection hurt{};
|
||||||
|
SoundEntryCollection highScore{};
|
||||||
|
SoundEntryCollection highScoreLoss{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Animations
|
||||||
|
{
|
||||||
|
std::string idle{};
|
||||||
|
std::string spawn{};
|
||||||
|
std::string death{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Player
|
||||||
|
{
|
||||||
|
Anm2 anm2{};
|
||||||
|
Animations animations{};
|
||||||
|
std::string hitboxNull{"Hitbox"};
|
||||||
|
float followerRadius{};
|
||||||
|
float targetAcceleration{6.0f};
|
||||||
|
float rotationSpeed{0.01f};
|
||||||
|
float rotationSpeedMax{0.10f};
|
||||||
|
float rotationSpeedFriction{0.85f};
|
||||||
|
int timeAfterHurt{30};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Follower
|
||||||
|
{
|
||||||
|
Anm2 anm2{};
|
||||||
|
Animations animations{};
|
||||||
|
std::string hitboxNull{"Hitbox"};
|
||||||
|
float targetAcceleration{4.0f};
|
||||||
|
std::string overrideTintLayer{"Default"};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Enemy
|
||||||
|
{
|
||||||
|
Anm2 anm2{};
|
||||||
|
Animations animations{};
|
||||||
|
std::string hitboxNull{"Hitbox"};
|
||||||
|
float speed{4.0f};
|
||||||
|
float speedScoreBonus{0.001f};
|
||||||
|
float speedGainBase{0.01f};
|
||||||
|
float speedGainScoreBonus{0.001f};
|
||||||
|
float spawnChanceBase{0.35f};
|
||||||
|
float spawnChanceScoreBonus{0.05f};
|
||||||
|
float spawnPadding{8.0f};
|
||||||
|
std::string overrideTintLayer{"Default"};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Warning
|
||||||
|
{
|
||||||
|
Anm2 anm2{};
|
||||||
|
std::string overrideTintLayer{"Default"};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Color
|
||||||
|
{
|
||||||
|
glm::vec3 value{};
|
||||||
|
int scoreThreshold{};
|
||||||
|
Dialogue::PoolReference pool{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Player player{};
|
||||||
|
Follower follower{};
|
||||||
|
Enemy enemy{};
|
||||||
|
Warning warning{};
|
||||||
|
Sounds sounds{};
|
||||||
|
|
||||||
|
Dialogue::PoolReference poolHurt{};
|
||||||
|
Dialogue::PoolReference poolDeath{};
|
||||||
|
|
||||||
|
float rewardChanceBase{0.001f};
|
||||||
|
float rewardChanceScoreBonus{0.001f};
|
||||||
|
float rewardRollChanceBase{1.0f};
|
||||||
|
float rewardRollScoreBonus{};
|
||||||
|
|
||||||
|
std::vector<Color> colors{};
|
||||||
|
int startTime{30};
|
||||||
|
|
||||||
|
bool isValid{};
|
||||||
|
|
||||||
|
Orbit() = default;
|
||||||
|
Orbit(const util::physfs::Path&, Dialogue&);
|
||||||
|
|
||||||
|
bool is_valid() const;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -56,9 +56,9 @@ namespace game::resource::xml
|
|||||||
if (!element) element = root->FirstChildElement("Play");
|
if (!element) element = root->FirstChildElement("Play");
|
||||||
if (element)
|
if (element)
|
||||||
{
|
{
|
||||||
element->QueryIntAttribute("TotalPlays", &totalPlays);
|
element->QueryIntAttribute("TotalPlays", &skillCheck.totalPlays);
|
||||||
element->QueryIntAttribute("HighScore", &highScore);
|
element->QueryIntAttribute("HighScore", &skillCheck.highScore);
|
||||||
element->QueryIntAttribute("BestCombo", &bestCombo);
|
element->QueryIntAttribute("BestCombo", &skillCheck.bestCombo);
|
||||||
|
|
||||||
if (auto child = element->FirstChildElement("Grades"))
|
if (auto child = element->FirstChildElement("Grades"))
|
||||||
{
|
{
|
||||||
@@ -67,11 +67,16 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
int id{};
|
int id{};
|
||||||
gradeChild->QueryIntAttribute("ID", &id);
|
gradeChild->QueryIntAttribute("ID", &id);
|
||||||
gradeChild->QueryIntAttribute("Count", &gradeCounts[id]);
|
gradeChild->QueryIntAttribute("Count", &skillCheck.gradeCounts[id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto element = root->FirstChildElement("Orbit"))
|
||||||
|
{
|
||||||
|
element->QueryIntAttribute("HighScore", &orbit.highScore);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto element = root->FirstChildElement("Inventory"))
|
if (auto element = root->FirstChildElement("Inventory"))
|
||||||
{
|
{
|
||||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||||
@@ -132,19 +137,22 @@ namespace game::resource::xml
|
|||||||
|
|
||||||
auto skillCheckElement = element->InsertNewChildElement("SkillCheck");
|
auto skillCheckElement = element->InsertNewChildElement("SkillCheck");
|
||||||
|
|
||||||
skillCheckElement->SetAttribute("TotalPlays", totalPlays);
|
skillCheckElement->SetAttribute("TotalPlays", skillCheck.totalPlays);
|
||||||
skillCheckElement->SetAttribute("HighScore", highScore);
|
skillCheckElement->SetAttribute("HighScore", skillCheck.highScore);
|
||||||
skillCheckElement->SetAttribute("BestCombo", bestCombo);
|
skillCheckElement->SetAttribute("BestCombo", skillCheck.bestCombo);
|
||||||
|
|
||||||
auto gradesElement = skillCheckElement->InsertNewChildElement("Grades");
|
auto gradesElement = skillCheckElement->InsertNewChildElement("Grades");
|
||||||
|
|
||||||
for (auto& [i, count] : gradeCounts)
|
for (auto& [i, count] : skillCheck.gradeCounts)
|
||||||
{
|
{
|
||||||
auto gradeElement = gradesElement->InsertNewChildElement("Grade");
|
auto gradeElement = gradesElement->InsertNewChildElement("Grade");
|
||||||
gradeElement->SetAttribute("ID", i);
|
gradeElement->SetAttribute("ID", i);
|
||||||
gradeElement->SetAttribute("Count", count);
|
gradeElement->SetAttribute("Count", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto orbitElement = element->InsertNewChildElement("Orbit");
|
||||||
|
orbitElement->SetAttribute("HighScore", orbit.highScore);
|
||||||
|
|
||||||
auto inventoryElement = element->InsertNewChildElement("Inventory");
|
auto inventoryElement = element->InsertNewChildElement("Inventory");
|
||||||
|
|
||||||
for (auto& [id, quantity] : inventory)
|
for (auto& [id, quantity] : inventory)
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ namespace game::resource::xml
|
|||||||
class Save
|
class Save
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
struct SkillCheck
|
||||||
|
{
|
||||||
|
int totalPlays{};
|
||||||
|
int highScore{};
|
||||||
|
int bestCombo{};
|
||||||
|
std::map<int, int> gradeCounts{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Orbit
|
||||||
|
{
|
||||||
|
int highScore{};
|
||||||
|
};
|
||||||
|
|
||||||
struct Item
|
struct Item
|
||||||
{
|
{
|
||||||
int id{};
|
int id{};
|
||||||
@@ -34,10 +47,8 @@ namespace game::resource::xml
|
|||||||
|
|
||||||
float totalCaloriesConsumed{};
|
float totalCaloriesConsumed{};
|
||||||
int totalFoodItemsEaten{};
|
int totalFoodItemsEaten{};
|
||||||
int totalPlays{};
|
SkillCheck skillCheck{};
|
||||||
int highScore{};
|
Orbit orbit{};
|
||||||
int bestCombo{};
|
|
||||||
std::map<int, int> gradeCounts{};
|
|
||||||
|
|
||||||
std::map<int, int> inventory;
|
std::map<int, int> inventory;
|
||||||
std::vector<Item> items;
|
std::vector<Item> items;
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ namespace game::resource::xml
|
|||||||
{
|
{
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
|
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto archive = path.directory_get();
|
auto archive = path.directory_get();
|
||||||
|
|
||||||
@@ -24,11 +27,14 @@ namespace game::resource::xml
|
|||||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||||
|
|
||||||
root->QueryIntAttribute("RewardScore", &rewardScore);
|
root->QueryIntAttribute("RewardScore", &rewardScore);
|
||||||
root->QueryFloatAttribute("RewardScoreBonus", &rewardScoreBonus);
|
root->QueryFloatAttribute("RewardChanceBase", &rewardChanceBase);
|
||||||
root->QueryFloatAttribute("RewardGradeBonus", &rewardGradeBonus);
|
root->QueryFloatAttribute("RewardChanceScoreBonus", &rewardChanceScoreBonus);
|
||||||
root->QueryFloatAttribute("RangeBase", &rangeBase);
|
root->QueryFloatAttribute("RewardRollChanceBase", &rewardRollChanceBase);
|
||||||
root->QueryFloatAttribute("RangeMin", &rangeMin);
|
root->QueryFloatAttribute("RewardRollScoreBonus", &rewardRollScoreBonus);
|
||||||
root->QueryFloatAttribute("RangeScoreBonus", &rangeScoreBonus);
|
root->QueryFloatAttribute("RewardRollGradeBonus", &rewardRollGradeBonus);
|
||||||
|
root->QueryFloatAttribute("ZoneBase", &zoneBase);
|
||||||
|
root->QueryFloatAttribute("ZoneMin", &zoneMin);
|
||||||
|
root->QueryFloatAttribute("ZoneScoreBonus", &zoneScoreBonus);
|
||||||
root->QueryFloatAttribute("SpeedMin", &speedMin);
|
root->QueryFloatAttribute("SpeedMin", &speedMin);
|
||||||
root->QueryFloatAttribute("SpeedMax", &speedMax);
|
root->QueryFloatAttribute("SpeedMax", &speedMax);
|
||||||
root->QueryFloatAttribute("SpeedScoreBonus", &speedScoreBonus);
|
root->QueryFloatAttribute("SpeedScoreBonus", &speedScoreBonus);
|
||||||
|
|||||||
@@ -34,14 +34,17 @@ namespace game::resource::xml
|
|||||||
Sounds sounds{};
|
Sounds sounds{};
|
||||||
std::vector<Grade> grades{};
|
std::vector<Grade> grades{};
|
||||||
|
|
||||||
float rewardScoreBonus{0.01f};
|
float rewardChanceBase{0.01f};
|
||||||
float rewardGradeBonus{0.05f};
|
float rewardChanceScoreBonus{0.01f};
|
||||||
|
float rewardRollChanceBase{1.0f};
|
||||||
|
float rewardRollScoreBonus{0.05f};
|
||||||
|
float rewardRollGradeBonus{0.05f};
|
||||||
float speedMin{0.005f};
|
float speedMin{0.005f};
|
||||||
float speedMax{0.075f};
|
float speedMax{0.075f};
|
||||||
float speedScoreBonus{0.000025f};
|
float speedScoreBonus{0.000025f};
|
||||||
float rangeBase{0.75f};
|
float zoneBase{0.75f};
|
||||||
float rangeMin{0.10f};
|
float zoneMin{0.10f};
|
||||||
float rangeScoreBonus{0.0005f};
|
float zoneScoreBonus{0.0005f};
|
||||||
int endTimerMax{20};
|
int endTimerMax{20};
|
||||||
int endTimerFailureMax{60};
|
int endTimerFailureMax{60};
|
||||||
int rewardScore{999};
|
int rewardScore{999};
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ namespace game::resource::xml
|
|||||||
values[i] = definitions[i].fallback;
|
values[i] = definitions[i].fallback;
|
||||||
|
|
||||||
XMLDocument document;
|
XMLDocument document;
|
||||||
if (document_load(path, document) != XML_SUCCESS) return;
|
if (document_load(path, document) != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
logger.error(std::format("Unable to initialize strings: {} ({})", path.c_str(), document.ErrorStr()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto root = document.RootElement();
|
auto root = document.RootElement();
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
|
|||||||
@@ -7,24 +7,23 @@
|
|||||||
|
|
||||||
namespace game::resource::xml
|
namespace game::resource::xml
|
||||||
{
|
{
|
||||||
#define GAME_XML_STRING_LIST(X) \
|
#define GAME_XML_STRING_LIST(X) \
|
||||||
X(MenuTabInteract, "TextMenuTabInteract", "Interact") \
|
X(MenuTabInteract, "TextMenuTabInteract", "Interact") \
|
||||||
X(MenuTabArcade, "TextMenuTabArcade", "Arcade") \
|
X(MenuTabArcade, "TextMenuTabArcade", "Arcade") \
|
||||||
X(MenuTabInventory, "TextMenuTabInventory", "Inventory") \
|
X(MenuTabInventory, "TextMenuTabInventory", "Inventory") \
|
||||||
X(MenuTabSettings, "TextMenuTabSettings", "Settings") \
|
X(MenuTabSettings, "TextMenuTabSettings", "Settings") \
|
||||||
X(MenuTabCheats, "TextMenuTabCheats", "Cheats") \
|
X(MenuTabCheats, "TextMenuTabCheats", "Cheats") \
|
||||||
X(MenuTabDebug, "TextMenuTabDebug", "Debug") \
|
|
||||||
X(MenuOpenTooltip, "TextMenuOpenTooltip", "Open Main Menu") \
|
X(MenuOpenTooltip, "TextMenuOpenTooltip", "Open Main Menu") \
|
||||||
X(MenuCloseTooltip, "TextMenuCloseTooltip", "Close Main Menu") \
|
X(MenuCloseTooltip, "TextMenuCloseTooltip", "Close Main Menu") \
|
||||||
X(InteractChatButton, "TextInteractChatButton", "Let's chat!") \
|
X(InteractChatButton, "TextInteractChatButton", "Let's chat!") \
|
||||||
X(InteractHelpButton, "TextInteractHelpButton", "Help") \
|
X(InteractHelpButton, "TextInteractHelpButton", "Help") \
|
||||||
X(InteractFeelingButton, "TextInteractFeelingButton", "How are you feeling?") \
|
X(InteractFeelingButton, "TextInteractFeelingButton", "How are you feeling?") \
|
||||||
X(InteractWeightFormat, "TextInteractWeightFormat", "Weight: %0.2f %s (Stage: %i)") \
|
X(InteractWeightFormat, "TextInteractWeightFormat", "Weight: %0.2f %s (Stage: %i)") \
|
||||||
X(InteractCapacityFormat, "TextInteractCapacityFormat", "Capacity: %0.0f kcal (Max: %0.0f kcal)") \
|
X(InteractCapacityFormat, "TextInteractCapacityFormat", "Capacity: %0.0f kcal (Max: %0.0f kcal)") \
|
||||||
X(InteractDigestionRateFormat, "TextInteractDigestionRateFormat", "Digestion Rate: %0.2f%%/sec") \
|
X(InteractDigestionRateFormat, "TextInteractDigestionRateFormat", "Digestion Rate: %0.2f%%/sec") \
|
||||||
X(InteractEatingSpeedFormat, "TextInteractEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
X(InteractEatingSpeedFormat, "TextInteractEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
||||||
X(InteractTotalCaloriesFormat, "TextInteractTotalCaloriesFormat", "Total Calories Consumed: %0.0f kcal") \
|
X(InteractTotalCaloriesFormat, "TextInteractTotalCaloriesFormat", "Total Calories Consumed: %0.0f kcal") \
|
||||||
X(InteractTotalFoodItemsFormat, "TextInteractTotalFoodItemsFormat", "Total Food Items Eaten: %i") \
|
X(InteractTotalFoodItemsFormat, "TextInteractTotalFoodItemsFormat", "Total Food Items Eaten: %i") \
|
||||||
X(SettingsMeasurementSystem, "TextSettingsMeasurementSystem", "Measurement System") \
|
X(SettingsMeasurementSystem, "TextSettingsMeasurementSystem", "Measurement System") \
|
||||||
X(SettingsMetric, "TextSettingsMetric", "Metric") \
|
X(SettingsMetric, "TextSettingsMetric", "Metric") \
|
||||||
X(SettingsMetricTooltip, "TextSettingsMetricTooltip", "Use kilograms (kg).") \
|
X(SettingsMetricTooltip, "TextSettingsMetricTooltip", "Use kilograms (kg).") \
|
||||||
@@ -35,90 +34,106 @@ namespace game::resource::xml
|
|||||||
X(SettingsVolumeTooltip, "TextSettingsVolumeTooltip", "Adjust master volume.") \
|
X(SettingsVolumeTooltip, "TextSettingsVolumeTooltip", "Adjust master volume.") \
|
||||||
X(SettingsAppearance, "TextSettingsAppearance", "Appearance") \
|
X(SettingsAppearance, "TextSettingsAppearance", "Appearance") \
|
||||||
X(SettingsUseCharacterColor, "TextSettingsUseCharacterColor", "Use Character Color") \
|
X(SettingsUseCharacterColor, "TextSettingsUseCharacterColor", "Use Character Color") \
|
||||||
X(SettingsUseCharacterColorTooltip, "TextSettingsUseCharacterColorTooltip", \
|
X(SettingsUseCharacterColorTooltip, "TextSettingsUseCharacterColorTooltip", \
|
||||||
"When playing, the UI will use the character's preset UI color.") \
|
"When playing, the UI will use the character's preset UI color.") \
|
||||||
X(SettingsColor, "TextSettingsColor", "Color") \
|
X(SettingsColor, "TextSettingsColor", "Color") \
|
||||||
X(SettingsColorTooltip, "TextSettingsColorTooltip", "Change the UI color.") \
|
X(SettingsColorTooltip, "TextSettingsColorTooltip", "Change the UI color.") \
|
||||||
X(SettingsResetButton, "TextSettingsResetButton", "Reset to Default") \
|
X(SettingsResetButton, "TextSettingsResetButton", "Reset to Default") \
|
||||||
X(SettingsSaveButton, "TextSettingsSaveButton", "Save") \
|
X(SettingsSaveButton, "TextSettingsSaveButton", "Save") \
|
||||||
X(SettingsSaveTooltip, "TextSettingsSaveTooltip", "Save the game.\n(Note: the game autosaves frequently.)") \
|
X(SettingsSaveTooltip, "TextSettingsSaveTooltip", "Save the game.\n(Note: the game autosaves frequently.)") \
|
||||||
X(SettingsReturnToCharactersButton, "TextSettingsReturnToCharactersButton", "Return to Characters") \
|
X(SettingsReturnToCharactersButton, "TextSettingsReturnToCharactersButton", "Return to Characters") \
|
||||||
X(SettingsReturnToCharactersTooltip, "TextSettingsReturnToCharactersTooltip", \
|
X(SettingsReturnToCharactersTooltip, "TextSettingsReturnToCharactersTooltip", \
|
||||||
"Go back to the character selection screen.\nProgress will be saved.") \
|
"Go back to the character selection screen.\nProgress will be saved.") \
|
||||||
X(ToastCheatsUnlocked, "TextToastCheatsUnlocked", "Cheats unlocked!") \
|
X(ToastCheatsUnlocked, "TextToastCheatsUnlocked", "Cheats unlocked!") \
|
||||||
X(ToastSaving, "TextToastSaving", "Saving...") \
|
X(ToastSaving, "TextToastSaving", "Saving...") \
|
||||||
X(ToolsHomeButton, "TextToolsHomeButton", "Home") \
|
X(ToolsHomeButton, "TextToolsHomeButton", "Home") \
|
||||||
X(ToolsHomeTooltip, "TextToolsHomeTooltip", "Reset camera view.\n(Shortcut: Home)") \
|
X(ToolsHomeTooltip, "TextToolsHomeTooltip", "Reset camera view.\n(Shortcut: Home)") \
|
||||||
X(ToolsOpenTooltip, "TextToolsOpenTooltip", "Open Tools") \
|
X(ToolsOpenTooltip, "TextToolsOpenTooltip", "Open Tools") \
|
||||||
X(ToolsCloseTooltip, "TextToolsCloseTooltip", "Close Tools") \
|
X(ToolsCloseTooltip, "TextToolsCloseTooltip", "Close Tools") \
|
||||||
X(DebugCursorScreenFormat, "TextDebugCursorScreenFormat", "Cursor Pos (Screen): %0.0f, %0.0f") \
|
X(InventoryEmptyHint, "TextInventoryEmptyHint", "Check the \"Arcade\" tab to earn rewards!") \
|
||||||
X(DebugCursorWorldFormat, "TextDebugCursorWorldFormat", "Cursor Pos (World): %0.0f, %0.0f") \
|
|
||||||
X(DebugAnimations, "TextDebugAnimations", "Animations") \
|
|
||||||
X(DebugNowPlayingFormat, "TextDebugNowPlayingFormat", "Now Playing: %s") \
|
|
||||||
X(DebugDialogue, "TextDebugDialogue", "Dialogue") \
|
|
||||||
X(DebugShowNulls, "TextDebugShowNulls", "Show Nulls (Hitboxes)") \
|
|
||||||
X(DebugShowWorldBounds, "TextDebugShowWorldBounds", "Show World Bounds") \
|
|
||||||
X(DebugItem, "TextDebugItem", "Item") \
|
|
||||||
X(DebugHeld, "TextDebugHeld", "Held") \
|
|
||||||
X(DebugItemTypeFormat, "TextDebugItemTypeFormat", "Type: %i") \
|
|
||||||
X(DebugItemPositionFormat, "TextDebugItemPositionFormat", "Position: %0.0f, %0.0f") \
|
|
||||||
X(DebugItemVelocityFormat, "TextDebugItemVelocityFormat", "Velocity: %0.0f, %0.0f") \
|
|
||||||
X(DebugItemDurabilityFormat, "TextDebugItemDurabilityFormat", "Durability: %i") \
|
|
||||||
X(InventoryEmptyHint, "TextInventoryEmptyHint", "Check the \"Arcade\" tab to earn rewards!") \
|
|
||||||
X(InventoryFlavorFormat, "TextInventoryFlavorFormat", "Flavor: %s") \
|
X(InventoryFlavorFormat, "TextInventoryFlavorFormat", "Flavor: %s") \
|
||||||
X(InventoryCaloriesFormat, "TextInventoryCaloriesFormat", "%0.0f kcal") \
|
X(InventoryCaloriesFormat, "TextInventoryCaloriesFormat", "%0.0f kcal") \
|
||||||
X(InventoryDurabilityFormat, "TextInventoryDurabilityFormat", "Durability: %i") \
|
X(InventoryDurabilityFormat, "TextInventoryDurabilityFormat", "Durability: %i") \
|
||||||
X(InventoryCapacityBonusFormat, "TextInventoryCapacityBonusFormat", "Capacity Bonus: +%0.0f kcal") \
|
X(InventoryCapacityBonusFormat, "TextInventoryCapacityBonusFormat", "Capacity Bonus: +%0.0f kcal") \
|
||||||
X(InventoryDigestionRateBonusFormat, "TextInventoryDigestionRateBonusFormat", "Digestion Rate Bonus: +%0.2f%% / sec") \
|
X(InventoryDigestionRateBonusFormat, "TextInventoryDigestionRateBonusFormat", \
|
||||||
X(InventoryDigestionRatePenaltyFormat, "TextInventoryDigestionRatePenaltyFormat", "Digestion Rate Penalty: %0.2f%% / sec") \
|
"Digestion Rate Bonus: +%0.2f%% / sec") \
|
||||||
X(InventoryEatSpeedBonusFormat, "TextInventoryEatSpeedBonusFormat", "Eat Speed Bonus: +%0.2f%% / sec") \
|
X(InventoryDigestionRatePenaltyFormat, "TextInventoryDigestionRatePenaltyFormat", \
|
||||||
X(InventoryEatSpeedPenaltyFormat, "TextInventoryEatSpeedPenaltyFormat", "Eat Speed Penalty: %0.2f%% / sec") \
|
"Digestion Rate Penalty: %0.2f%% / sec") \
|
||||||
X(InventoryUpgradePreviewFormat, "TextInventoryUpgradePreviewFormat", "Upgrade: %ix -> %s") \
|
X(InventoryEatSpeedBonusFormat, "TextInventoryEatSpeedBonusFormat", "Eat Speed Bonus: +%0.2f%% / sec") \
|
||||||
|
X(InventoryEatSpeedPenaltyFormat, "TextInventoryEatSpeedPenaltyFormat", "Eat Speed Penalty: %0.2f%% / sec") \
|
||||||
|
X(InventoryUpgradePreviewFormat, "TextInventoryUpgradePreviewFormat", "Upgrade: %ix -> %s") \
|
||||||
X(InventoryUnknown, "TextInventoryUnknown", "???") \
|
X(InventoryUnknown, "TextInventoryUnknown", "???") \
|
||||||
X(InventorySpawnButton, "TextInventorySpawnButton", "Spawn") \
|
X(InventorySpawnButton, "TextInventorySpawnButton", "Spawn") \
|
||||||
X(InventoryUpgradeButton, "TextInventoryUpgradeButton", "Upgrade") \
|
X(InventoryUpgradeButton, "TextInventoryUpgradeButton", "Upgrade") \
|
||||||
X(InventoryUpgradeAllButton, "TextInventoryUpgradeAllButton", "Upgrade All") \
|
X(InventoryUpgradeAllButton, "TextInventoryUpgradeAllButton", "Upgrade All") \
|
||||||
X(InventoryUpgradeNoPath, "TextInventoryUpgradeNoPath", "This item cannot be upgraded.") \
|
X(InventoryUpgradeNoPath, "TextInventoryUpgradeNoPath", "This item cannot be upgraded.") \
|
||||||
X(InventoryUpgradeNeedsTemplate, "TextInventoryUpgradeNeedsTemplate", "Needs {}x to upgrade into {}!") \
|
X(InventoryUpgradeNeedsTemplate, "TextInventoryUpgradeNeedsTemplate", "Needs {}x to upgrade into {}!") \
|
||||||
X(InventoryUpgradeOneTemplate, "TextInventoryUpgradeOneTemplate", "Use {}x to upgrade into 1x {}.") \
|
X(InventoryUpgradeOneTemplate, "TextInventoryUpgradeOneTemplate", "Use {}x to upgrade into 1x {}.") \
|
||||||
X(InventoryUpgradeAllTemplate, "TextInventoryUpgradeAllTemplate", "Use {}x to upgrade into {}x {}.") \
|
X(InventoryUpgradeAllTemplate, "TextInventoryUpgradeAllTemplate", "Use {}x to upgrade into {}x {}.") \
|
||||||
X(ArcadeSkillCheckName, "TextArcadeSkillCheckName", "Skill Check") \
|
X(ArcadeSkillCheckName, "TextArcadeSkillCheckName", "Skill Check") \
|
||||||
X(ArcadeSkillCheckDescription, "TextArcadeSkillCheckDescription", \
|
X(ArcadeSkillCheckDescription, "TextArcadeSkillCheckDescription", \
|
||||||
"Test your timing to build score, chain combos, and earn rewards based on your performance.") \
|
"Test your timing! Aim for specific zones for rewards.") \
|
||||||
|
X(ArcadeDungeonName, "TextArcadeDungeonName", "Dungeon") \
|
||||||
|
X(ArcadeDungeonDescription, "TextArcadeDungeonDescription", \
|
||||||
|
"A placeholder dungeon adventure entry. Use this as a template for a future arcade game.") \
|
||||||
|
X(ArcadeOrbitName, "TextArcadeOrbitName", "Orbit") \
|
||||||
|
X(ArcadeOrbitDescription, "TextArcadeOrbitDescription", \
|
||||||
|
"Move colored objects orbiting around the cursor into similarly colored enemies!") \
|
||||||
|
X(ArcadeHowToPlay, "TextArcadeHowToPlay", "How to Play") \
|
||||||
|
X(ArcadeSkillCheckHowToPlay, "TextArcadeSkillCheckHowToPlay", \
|
||||||
|
"Press Space or click to stop the line inside the colored target zones.\nEach success builds score and combo, " \
|
||||||
|
"and high scores improve your reward chances, while increasing game speed and tightening the zones.\nMissing the " \
|
||||||
|
"colored zones ends the run.") \
|
||||||
|
X(ArcadeDungeonHowToPlay, "TextArcadeDungeonHowToPlay", \
|
||||||
|
"This is currently a template page.\nUse it to prototype dungeon rules, rewards, room flow, and UI layout.") \
|
||||||
|
X(ArcadeOrbitHowToPlay, "TextArcadeOrbitHowToPlay", \
|
||||||
|
"Control an object with your cursor.\nThere will be colored, orbiting objects around it.\nUse the mouse buttons " \
|
||||||
|
"to move the objects around your orbit.\nEnemies will appear with the same color as the orbiting objects.\nMatch " \
|
||||||
|
"the colors of the orbiting objects into the same colored enemies for score.\nOver time, more colors will be " \
|
||||||
|
"added nearby the cursor, and more enemies will appear.\nEnemies will also get faster with time.\nHow long can " \
|
||||||
|
"you survive?!") \
|
||||||
|
X(ArcadeDungeonTemplateTitle, "TextArcadeDungeonTemplateTitle", "Dungeon Template") \
|
||||||
|
X(ArcadeDungeonTemplateBody, "TextArcadeDungeonTemplateBody", \
|
||||||
|
"This screen is a placeholder for the Dungeon arcade game.\nAdd room generation, encounters, rewards, and any " \
|
||||||
|
"character-specific hooks here.\nUse the Back button below to return to the arcade menu.") \
|
||||||
|
X(ArcadeScoreFormat, "TextArcadeScoreFormat", "Score: %i pts") \
|
||||||
|
X(ArcadeScoreComboFormat, "TextArcadeScoreComboFormat", "Score: %i pts (%ix)") \
|
||||||
X(ArcadePlayButton, "TextArcadePlayButton", "Play") \
|
X(ArcadePlayButton, "TextArcadePlayButton", "Play") \
|
||||||
X(ArcadeStatsButton, "TextArcadeStatsButton", "Stats") \
|
X(ArcadeInfoButton, "TextArcadeInfoButton", "Info") \
|
||||||
X(ArcadeBackButton, "TextArcadeBackButton", "Back") \
|
X(ArcadeBackButton, "TextArcadeBackButton", "Back") \
|
||||||
X(ArcadeBestFormat, "TextArcadeBestFormat", "Best: %i pts (%ix)") \
|
X(ArcadeStats, "TextArcadeStats", "Stats") \
|
||||||
X(ArcadeTotalSkillChecksFormat, "TextArcadeTotalSkillChecksFormat", "Total Skill Checks: %i") \
|
X(ArcadeBestScoreFormat, "TextArcadeBestScoreFormat", "Best: %i pts") \
|
||||||
|
X(ArcadeBestScoreComboFormat, "TextArcadeBestScoreComboFormat", "Best: %i pts (%ix)") \
|
||||||
|
X(ArcadeTotalSkillChecksFormat, "TextArcadeTotalSkillChecksFormat", "Rounds Attempted: %i") \
|
||||||
X(ArcadeAccuracyFormat, "TextArcadeAccuracyFormat", "Accuracy: %0.2f%%") \
|
X(ArcadeAccuracyFormat, "TextArcadeAccuracyFormat", "Accuracy: %0.2f%%") \
|
||||||
X(InfoProgressMax, "TextInfoProgressMax", "MAX") \
|
X(InfoProgressMax, "TextInfoProgressMax", "MAX") \
|
||||||
X(InfoProgressToNextStage, "TextInfoProgressToNextStage", "To Next Stage") \
|
X(InfoProgressToNextStage, "TextInfoProgressToNextStage", "To Next Stage") \
|
||||||
X(InfoStageProgressFormat, "TextInfoStageProgressFormat", "Stage: %i/%i (%0.1f%%)") \
|
X(InfoStageProgressFormat, "TextInfoStageProgressFormat", "Stage: %i/%i (%0.1f%%)") \
|
||||||
X(InfoMaxedOut, "TextInfoMaxedOut", "Maxed out!") \
|
X(InfoMaxedOut, "TextInfoMaxedOut", "Maxed out!") \
|
||||||
X(InfoStageStartFormat, "TextInfoStageStartFormat", "Start: %0.2f %s") \
|
X(InfoStageStartFormat, "TextInfoStageStartFormat", "Start: %0.2f %s") \
|
||||||
X(InfoStageCurrentFormat, "TextInfoStageCurrentFormat", "Current: %0.2f %s") \
|
X(InfoStageCurrentFormat, "TextInfoStageCurrentFormat", "Current: %0.2f %s") \
|
||||||
X(InfoStageNextFormat, "TextInfoStageNextFormat", "Next: %0.2f %s") \
|
X(InfoStageNextFormat, "TextInfoStageNextFormat", "Next: %0.2f %s") \
|
||||||
X(InfoDigestion, "TextInfoDigestion", "Digestion") \
|
X(InfoDigestion, "TextInfoDigestion", "Digestion") \
|
||||||
X(InfoDigesting, "TextInfoDigesting", "Digesting...") \
|
X(InfoDigesting, "TextInfoDigesting", "Digesting...") \
|
||||||
X(InfoDigestionInProgress, "TextInfoDigestionInProgress", "Digestion in progress...") \
|
X(InfoDigestionInProgress, "TextInfoDigestionInProgress", "Digestion in progress...") \
|
||||||
X(InfoGiveFoodToStartDigesting, "TextInfoGiveFoodToStartDigesting", "Give food to start digesting!") \
|
X(InfoGiveFoodToStartDigesting, "TextInfoGiveFoodToStartDigesting", "Give food to start digesting!") \
|
||||||
X(InfoDigestionRateFormat, "TextInfoDigestionRateFormat", "Rate: %0.2f%% / sec") \
|
X(InfoDigestionRateFormat, "TextInfoDigestionRateFormat", "Rate: %0.2f%% / sec") \
|
||||||
X(InfoEatingSpeedFormat, "TextInfoEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
X(InfoEatingSpeedFormat, "TextInfoEatingSpeedFormat", "Eating Speed: %0.2fx") \
|
||||||
X(SkillCheckScoreFormat, "TextSkillCheckScoreFormat", "Score: %i pts (%ix)") \
|
X(SkillCheckInstructions, "TextSkillCheckInstructions", \
|
||||||
X(SkillCheckBestFormat, "TextSkillCheckBestFormat", "Best: %i pts (%ix)") \
|
"Match the line to the colored areas with Space/click! Better performance, better rewards!") \
|
||||||
X(SkillCheckInstructions, "TextSkillCheckInstructions", "Match the line to the colored areas with Space/click! Better performance, better rewards!") \
|
X(ArcadeScoreLoss, "TextArcadeScoreLoss", "-1") \
|
||||||
X(SkillCheckScoreLoss, "TextSkillCheckScoreLoss", "-1") \
|
X(ArcadeRewardToast, "TextArcadeRewardToast", "Fantastic score! Congratulations!") \
|
||||||
X(SkillCheckRewardToast, "TextSkillCheckRewardToast", "Fantastic score! Congratulations!") \
|
X(ArcadeHighScoreToast, "TextArcadeHighScoreToast", "High Score!") \
|
||||||
X(SkillCheckHighScoreToast, "TextSkillCheckHighScoreToast", "High Score!") \
|
X(ArcadeMenuBackButtonTooltip, "TextArcadeMenuBackButtonTooltip", "Progress will not be saved!") \
|
||||||
X(SkillCheckGradeSuccessTemplate, "TextSkillCheckGradeSuccessTemplate", "{} (+{})") \
|
X(SkillCheckGradeSuccessTemplate, "TextSkillCheckGradeSuccessTemplate", "{} (+{})") \
|
||||||
X(SkillCheckMenuButton, "TextSkillCheckMenuButton", "Menu") \
|
X(ArcadeMenuBackButton, "TextArcadeMenuBackButton", "Menu") \
|
||||||
X(CheatsCalories, "TextCheatsCalories", "Calories") \
|
X(CheatsCalories, "TextCheatsCalories", "Calories") \
|
||||||
X(CheatsCapacity, "TextCheatsCapacity", "Capacity") \
|
X(CheatsCapacity, "TextCheatsCapacity", "Capacity") \
|
||||||
X(CheatsWeight, "TextCheatsWeight", "Weight") \
|
X(CheatsWeight, "TextCheatsWeight", "Weight") \
|
||||||
X(CheatsWeightFormat, "TextCheatsWeightFormat", "%0.2f kg") \
|
X(CheatsWeightFormat, "TextCheatsWeightFormat", "%0.2f kg") \
|
||||||
X(CheatsStage, "TextCheatsStage", "Stage") \
|
X(CheatsStage, "TextCheatsStage", "Stage") \
|
||||||
X(CheatsDigestionRate, "TextCheatsDigestionRate", "Digestion Rate") \
|
X(CheatsDigestionRate, "TextCheatsDigestionRate", "Digestion Rate") \
|
||||||
X(CheatsDigestionRateFormat, "TextCheatsDigestionRateFormat", "%0.2f% / tick") \
|
X(CheatsDigestionRateFormat, "TextCheatsDigestionRateFormat", "%0.2f% / tick") \
|
||||||
X(CheatsEatSpeed, "TextCheatsEatSpeed", "Eat Speed") \
|
X(CheatsEatSpeed, "TextCheatsEatSpeed", "Eat Speed") \
|
||||||
X(CheatsEatSpeedFormat, "TextCheatsEatSpeedFormat", "%0.2fx") \
|
X(CheatsEatSpeedFormat, "TextCheatsEatSpeedFormat", "%0.2fx") \
|
||||||
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest") \
|
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest") \
|
||||||
@@ -132,7 +147,7 @@ namespace game::resource::xml
|
|||||||
#define X(type, attr, fallback) type,
|
#define X(type, attr, fallback) type,
|
||||||
GAME_XML_STRING_LIST(X)
|
GAME_XML_STRING_LIST(X)
|
||||||
#undef X
|
#undef X
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Definition
|
struct Definition
|
||||||
@@ -143,7 +158,7 @@ namespace game::resource::xml
|
|||||||
|
|
||||||
inline static constexpr std::array<Definition, Count> definitions{{
|
inline static constexpr std::array<Definition, Count> definitions{{
|
||||||
#define X(type, attr, fallback) {attr, fallback},
|
#define X(type, attr, fallback) {attr, fallback},
|
||||||
GAME_XML_STRING_LIST(X)
|
GAME_XML_STRING_LIST(X)
|
||||||
#undef X
|
#undef X
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace game::state
|
|||||||
cursor = entity::Cursor(character.data.cursorSchema.anm2);
|
cursor = entity::Cursor(character.data.cursorSchema.anm2);
|
||||||
cursor.interactTypeID = character.data.interactTypeNames.empty() ? -1 : 0;
|
cursor.interactTypeID = character.data.interactTypeNames.empty() ? -1 : 0;
|
||||||
|
|
||||||
menu.inventory = Inventory{};
|
menu.inventory = play::menu::Inventory{};
|
||||||
for (auto& [id, quantity] : saveData.inventory)
|
for (auto& [id, quantity] : saveData.inventory)
|
||||||
{
|
{
|
||||||
if (quantity == 0) continue;
|
if (quantity == 0) continue;
|
||||||
@@ -94,16 +94,17 @@ namespace game::state
|
|||||||
item.rotation);
|
item.rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
imgui::style::rounding_set(menuSchema.rounding);
|
imgui::style::widget_set(menuSchema.rounding);
|
||||||
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
|
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
|
||||||
play::style::color_set(resources, character);
|
play::style::color_set(resources, character);
|
||||||
|
|
||||||
menu.arcade = Arcade(character);
|
menu.arcade = play::menu::Arcade(character);
|
||||||
menu.arcade.skillCheck.totalPlays = saveData.totalPlays;
|
menu.arcade.skillCheck.totalPlays = saveData.skillCheck.totalPlays;
|
||||||
menu.arcade.skillCheck.highScore = saveData.highScore;
|
menu.arcade.skillCheck.highScore = saveData.skillCheck.highScore;
|
||||||
menu.arcade.skillCheck.bestCombo = saveData.bestCombo;
|
menu.arcade.skillCheck.bestCombo = saveData.skillCheck.bestCombo;
|
||||||
menu.arcade.skillCheck.gradeCounts = saveData.gradeCounts;
|
menu.arcade.skillCheck.gradeCounts = saveData.skillCheck.gradeCounts;
|
||||||
menu.arcade.skillCheck.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
|
menu.arcade.skillCheck.isHighScoreAchieved = saveData.skillCheck.highScore > 0 ? true : false;
|
||||||
|
menu.arcade.orbit.highScore = saveData.orbit.highScore;
|
||||||
|
|
||||||
text.entry = nullptr;
|
text.entry = nullptr;
|
||||||
text.isEnabled = false;
|
text.isEnabled = false;
|
||||||
@@ -153,7 +154,7 @@ namespace game::state
|
|||||||
void Play::exit(Resources& resources)
|
void Play::exit(Resources& resources)
|
||||||
{
|
{
|
||||||
imgui::style::color_set(resources.settings.color);
|
imgui::style::color_set(resources.settings.color);
|
||||||
imgui::style::rounding_set();
|
imgui::style::widget_set();
|
||||||
imgui::widget::sounds_set(nullptr, nullptr);
|
imgui::widget::sounds_set(nullptr, nullptr);
|
||||||
ImGui::GetIO().FontDefault = resources.font.get();
|
ImGui::GetIO().FontDefault = resources.font.get();
|
||||||
save(resources);
|
save(resources);
|
||||||
@@ -182,6 +183,7 @@ namespace game::state
|
|||||||
|
|
||||||
auto focus = focus_get();
|
auto focus = focus_get();
|
||||||
auto& dialogue = character.data.dialogue;
|
auto& dialogue = character.data.dialogue;
|
||||||
|
cursor.isVisible = true;
|
||||||
|
|
||||||
if (!menu.isCheats)
|
if (!menu.isCheats)
|
||||||
{
|
{
|
||||||
@@ -298,6 +300,7 @@ namespace game::state
|
|||||||
character.update();
|
character.update();
|
||||||
cursor.update();
|
cursor.update();
|
||||||
world.update(character, cursor, worldCanvas, focus);
|
world.update(character, cursor, worldCanvas, focus);
|
||||||
|
worldCanvas.tick();
|
||||||
|
|
||||||
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
|
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
|
||||||
{
|
{
|
||||||
@@ -337,7 +340,7 @@ namespace game::state
|
|||||||
worldCanvas.unbind();
|
worldCanvas.unbind();
|
||||||
|
|
||||||
canvas.bind();
|
canvas.bind();
|
||||||
canvas.texture_render(textureShader, worldCanvas.texture, windowModel);
|
canvas.texture_render(textureShader, worldCanvas, windowModel);
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
cursor.render(textureShader, rectShader, canvas);
|
cursor.render(textureShader, rectShader, canvas);
|
||||||
@@ -358,10 +361,11 @@ namespace game::state
|
|||||||
save.digestionTimer = character.digestionTimer;
|
save.digestionTimer = character.digestionTimer;
|
||||||
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
||||||
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
||||||
save.totalPlays = menu.arcade.skillCheck.totalPlays;
|
save.skillCheck.totalPlays = menu.arcade.skillCheck.totalPlays;
|
||||||
save.highScore = menu.arcade.skillCheck.highScore;
|
save.skillCheck.highScore = menu.arcade.skillCheck.highScore;
|
||||||
save.bestCombo = menu.arcade.skillCheck.bestCombo;
|
save.skillCheck.bestCombo = menu.arcade.skillCheck.bestCombo;
|
||||||
save.gradeCounts = menu.arcade.skillCheck.gradeCounts;
|
save.skillCheck.gradeCounts = menu.arcade.skillCheck.gradeCounts;
|
||||||
|
save.orbit.highScore = menu.arcade.orbit.highScore;
|
||||||
save.isPostgame = isPostgame;
|
save.isPostgame = isPostgame;
|
||||||
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
|
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
#include "arcade.hpp"
|
|
||||||
|
|
||||||
#include "../../util/imgui/widget.hpp"
|
|
||||||
|
|
||||||
using namespace game::util::imgui;
|
|
||||||
using namespace game::resource::xml;
|
|
||||||
|
|
||||||
namespace game::state::play
|
|
||||||
{
|
|
||||||
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
|
|
||||||
|
|
||||||
void Arcade::tick() { skillCheck.tick(); }
|
|
||||||
|
|
||||||
void Arcade::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
|
||||||
{
|
|
||||||
auto available = ImGui::GetContentRegionAvail();
|
|
||||||
auto& strings = character.data.strings;
|
|
||||||
|
|
||||||
if (view == SKILL_CHECK)
|
|
||||||
{
|
|
||||||
if (skillCheck.update(resources, character, inventory, text)) view = MENU;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
|
|
||||||
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
|
|
||||||
|
|
||||||
if (ImGui::BeginChild("##Arcade Child", childSize))
|
|
||||||
{
|
|
||||||
if (view == MENU)
|
|
||||||
{
|
|
||||||
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
|
||||||
|
|
||||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
|
||||||
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
|
|
||||||
ImGui::PopFont();
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeSkillCheckDescription).c_str());
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
|
|
||||||
view = SKILL_CHECK;
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeStatsButton).c_str(), ImVec2(buttonWidth, 0))))
|
|
||||||
view = SKILL_CHECK_STATS;
|
|
||||||
}
|
|
||||||
else if (view == SKILL_CHECK_STATS)
|
|
||||||
{
|
|
||||||
auto& schema = character.data.skillCheckSchema;
|
|
||||||
|
|
||||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
|
||||||
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
|
|
||||||
ImGui::PopFont();
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text(strings.get(Strings::ArcadeBestFormat).c_str(), skillCheck.highScore, skillCheck.bestCombo);
|
|
||||||
ImGui::Text(strings.get(Strings::ArcadeTotalSkillChecksFormat).c_str(), skillCheck.totalPlays);
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)schema.grades.size(); i++)
|
|
||||||
{
|
|
||||||
auto& grade = schema.grades[i];
|
|
||||||
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Text(strings.get(Strings::ArcadeAccuracyFormat).c_str(), skillCheck.accuracy_score_get(character));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
if (view == SKILL_CHECK_STATS)
|
|
||||||
{
|
|
||||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) view = MENU;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "arcade/skill_check.hpp"
|
|
||||||
|
|
||||||
namespace game::state::play
|
|
||||||
{
|
|
||||||
class Arcade
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum View
|
|
||||||
{
|
|
||||||
MENU,
|
|
||||||
SKILL_CHECK,
|
|
||||||
SKILL_CHECK_STATS
|
|
||||||
};
|
|
||||||
|
|
||||||
SkillCheck skillCheck{};
|
|
||||||
View view{MENU};
|
|
||||||
|
|
||||||
Arcade() = default;
|
|
||||||
Arcade(entity::Character&);
|
|
||||||
|
|
||||||
void tick();
|
|
||||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,452 +0,0 @@
|
|||||||
#include "skill_check.hpp"
|
|
||||||
|
|
||||||
#include <imgui_internal.h>
|
|
||||||
|
|
||||||
#include "../../../util/imgui.hpp"
|
|
||||||
#include "../../../util/imgui/widget.hpp"
|
|
||||||
#include "../../../util/math.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <format>
|
|
||||||
#include <ranges>
|
|
||||||
|
|
||||||
using namespace game::util;
|
|
||||||
using namespace game::entity;
|
|
||||||
using namespace game::resource;
|
|
||||||
using namespace game::resource::xml;
|
|
||||||
using namespace glm;
|
|
||||||
|
|
||||||
namespace game::state::play
|
|
||||||
{
|
|
||||||
float SkillCheck::accuracy_score_get(entity::Character& character)
|
|
||||||
{
|
|
||||||
if (totalPlays == 0) return 0.0f;
|
|
||||||
|
|
||||||
auto& schema = character.data.skillCheckSchema;
|
|
||||||
|
|
||||||
float combinedWeight{};
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)schema.grades.size(); i++)
|
|
||||||
{
|
|
||||||
auto& grade = schema.grades[i];
|
|
||||||
combinedWeight += gradeCounts[i] * grade.weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return glm::clamp(0.0f, math::to_percent(combinedWeight / totalPlays), 100.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
SkillCheck::Challenge SkillCheck::challenge_generate(entity::Character& character)
|
|
||||||
{
|
|
||||||
auto& schema = character.data.skillCheckSchema;
|
|
||||||
|
|
||||||
Challenge newChallenge;
|
|
||||||
|
|
||||||
Range newRange{};
|
|
||||||
|
|
||||||
auto rangeSize = std::max(schema.rangeMin, schema.rangeBase - (schema.rangeScoreBonus * score));
|
|
||||||
newRange.min = math::random_max(1.0f - rangeSize);
|
|
||||||
newRange.max = newRange.min + rangeSize;
|
|
||||||
|
|
||||||
newChallenge.range = newRange;
|
|
||||||
newChallenge.tryValue = 0.0f;
|
|
||||||
|
|
||||||
newChallenge.speed =
|
|
||||||
glm::clamp(schema.speedMin, schema.speedMin + (schema.speedScoreBonus * score), schema.speedMax);
|
|
||||||
|
|
||||||
if (math::random_bool())
|
|
||||||
{
|
|
||||||
newChallenge.tryValue = 1.0f;
|
|
||||||
newChallenge.speed *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
SkillCheck::SkillCheck(entity::Character& character) { challenge = challenge_generate(character); }
|
|
||||||
|
|
||||||
void SkillCheck::tick()
|
|
||||||
{
|
|
||||||
for (auto& [i, actor] : itemActors)
|
|
||||||
actor.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
|
||||||
{
|
|
||||||
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
|
|
||||||
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
|
|
||||||
static constexpr ImVec4 PERFECT_COLOR = ImVec4(1, 1, 1, 0.50);
|
|
||||||
static constexpr auto LINE_HEIGHT = 5.0f;
|
|
||||||
static constexpr auto LINE_WIDTH_BONUS = 10.0f;
|
|
||||||
static constexpr auto TOAST_MESSAGE_SPEED = 1.0f;
|
|
||||||
static constexpr auto ITEM_FALL_GRAVITY = 2400.0f;
|
|
||||||
|
|
||||||
auto& dialogue = character.data.dialogue;
|
|
||||||
auto& schema = character.data.skillCheckSchema;
|
|
||||||
auto& itemSchema = character.data.itemSchema;
|
|
||||||
auto& strings = character.data.strings;
|
|
||||||
auto& style = ImGui::GetStyle();
|
|
||||||
auto drawList = ImGui::GetWindowDrawList();
|
|
||||||
auto position = ImGui::GetCursorScreenPos();
|
|
||||||
auto size = ImGui::GetContentRegionAvail();
|
|
||||||
auto spacing = ImGui::GetTextLineHeightWithSpacing();
|
|
||||||
auto& io = ImGui::GetIO();
|
|
||||||
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
|
|
||||||
size.y = std::max(0.0f, size.y - menuButtonHeight);
|
|
||||||
|
|
||||||
auto cursorPos = ImGui::GetCursorPos();
|
|
||||||
|
|
||||||
ImGui::Text(strings.get(Strings::SkillCheckScoreFormat).c_str(), score, combo);
|
|
||||||
std::array<char, 128> bestBuffer{};
|
|
||||||
std::snprintf(bestBuffer.data(), bestBuffer.size(), strings.get(Strings::SkillCheckBestFormat).c_str(), highScore,
|
|
||||||
bestCombo);
|
|
||||||
auto bestString = std::string(bestBuffer.data());
|
|
||||||
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
|
|
||||||
|
|
||||||
ImGui::Text(strings.get(Strings::SkillCheckBestFormat).c_str(), highScore, bestCombo);
|
|
||||||
|
|
||||||
if (score == 0 && isActive)
|
|
||||||
{
|
|
||||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
|
|
||||||
ImGui::TextWrapped("%s", strings.get(Strings::SkillCheckInstructions).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto barMin = ImVec2(position.x + (size.x * 0.5f) - (spacing * 0.5f), position.y + (spacing * 2.0f));
|
|
||||||
auto barMax = ImVec2(barMin.x + (spacing * 2.0f), barMin.y + size.y - (spacing * 4.0f));
|
|
||||||
auto endTimerProgress = (float)endTimer / endTimerMax;
|
|
||||||
|
|
||||||
auto bgColor = ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
|
|
||||||
bgColor = imgui::to_imvec4(imgui::to_vec4(bgColor) * BG_COLOR_MULTIPLIER);
|
|
||||||
drawList->AddRectFilled(barMin, barMax, ImGui::GetColorU32(bgColor));
|
|
||||||
|
|
||||||
auto barWidth = barMax.x - barMin.x;
|
|
||||||
auto barHeight = barMax.y - barMin.y;
|
|
||||||
|
|
||||||
auto sub_ranges_get = [&](Range& range)
|
|
||||||
{
|
|
||||||
auto& min = range.min;
|
|
||||||
auto& max = range.max;
|
|
||||||
std::vector<Range> ranges{};
|
|
||||||
|
|
||||||
auto baseHeight = max - min;
|
|
||||||
auto center = (min + max) * 0.5f;
|
|
||||||
|
|
||||||
int rangeCount{};
|
|
||||||
|
|
||||||
for (auto& grade : schema.grades)
|
|
||||||
{
|
|
||||||
if (grade.isFailure) continue;
|
|
||||||
|
|
||||||
auto scale = powf(0.5f, (float)rangeCount);
|
|
||||||
auto halfHeight = baseHeight * scale * 0.5f;
|
|
||||||
|
|
||||||
rangeCount++;
|
|
||||||
|
|
||||||
ranges.push_back({center - halfHeight, center + halfHeight});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto range_draw = [&](Range& range, float alpha = 1.0f)
|
|
||||||
{
|
|
||||||
auto subRanges = sub_ranges_get(range);
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)subRanges.size(); i++)
|
|
||||||
{
|
|
||||||
auto& subRange = subRanges[i];
|
|
||||||
int layer = (int)subRanges.size() - 1 - i;
|
|
||||||
|
|
||||||
ImVec2 rectMin = {barMin.x, barMin.y + subRange.min * barHeight};
|
|
||||||
|
|
||||||
ImVec2 rectMax = {barMax.x, barMin.y + subRange.max * barHeight};
|
|
||||||
|
|
||||||
ImVec4 color =
|
|
||||||
i == (int)subRanges.size() - 1 ? PERFECT_COLOR : ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered);
|
|
||||||
color.w = (color.w - (float)layer / subRanges.size()) * alpha;
|
|
||||||
|
|
||||||
drawList->AddRectFilled(rectMin, rectMax, ImGui::GetColorU32(color));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
range_draw(challenge.range, isActive ? 1.0f : 0.0f);
|
|
||||||
|
|
||||||
auto lineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * tryValue));
|
|
||||||
auto lineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, lineMin.y + LINE_HEIGHT);
|
|
||||||
auto lineColor = LINE_COLOR;
|
|
||||||
lineColor.w = isActive ? 1.0f : endTimerProgress;
|
|
||||||
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(lineColor));
|
|
||||||
|
|
||||||
if (!isActive && !isGameOver)
|
|
||||||
{
|
|
||||||
range_draw(queuedChallenge.range, 1.0f - endTimerProgress);
|
|
||||||
|
|
||||||
auto queuedLineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * queuedChallenge.tryValue));
|
|
||||||
auto queuedLineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, queuedLineMin.y + LINE_HEIGHT);
|
|
||||||
auto queuedLineColor = LINE_COLOR;
|
|
||||||
queuedLineColor.w = 1.0f - endTimerProgress;
|
|
||||||
drawList->AddRectFilled(queuedLineMin, queuedLineMax, ImGui::GetColorU32(queuedLineColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive)
|
|
||||||
{
|
|
||||||
tryValue += challenge.speed;
|
|
||||||
|
|
||||||
if (tryValue > 1.0f || tryValue < 0.0f)
|
|
||||||
{
|
|
||||||
tryValue = tryValue > 1.0f ? 0.0f : tryValue < 0.0f ? 1.0f : tryValue;
|
|
||||||
|
|
||||||
if (score > 0)
|
|
||||||
{
|
|
||||||
score--;
|
|
||||||
schema.sounds.scoreLoss.play();
|
|
||||||
auto toastMessagePosition =
|
|
||||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckScoreLoss).c_str()).x -
|
|
||||||
ImGui::GetTextLineHeightWithSpacing(),
|
|
||||||
lineMin.y);
|
|
||||||
toasts.emplace_back(strings.get(Strings::SkillCheckScoreLoss), toastMessagePosition, schema.endTimerMax,
|
|
||||||
schema.endTimerMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(barMin);
|
|
||||||
auto barButtonSize = ImVec2(barMax.x - barMin.x, barMax.y - barMin.y);
|
|
||||||
|
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Space) ||
|
|
||||||
WIDGET_FX(ImGui::InvisibleButton("##SkillCheckBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
|
|
||||||
{
|
|
||||||
int gradeID{};
|
|
||||||
|
|
||||||
auto subRanges = sub_ranges_get(challenge.range);
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)subRanges.size(); i++)
|
|
||||||
{
|
|
||||||
auto& subRange = subRanges[i];
|
|
||||||
|
|
||||||
if (tryValue >= subRange.min && tryValue <= subRange.max)
|
|
||||||
gradeID = std::min((int)gradeID + 1, (int)schema.grades.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
gradeCounts[gradeID]++;
|
|
||||||
totalPlays++;
|
|
||||||
|
|
||||||
auto& grade = schema.grades.at(gradeID);
|
|
||||||
grade.sound.play();
|
|
||||||
|
|
||||||
if (text.is_interruptible() && grade.pool.is_valid()) text.set(dialogue.get(grade.pool), character);
|
|
||||||
|
|
||||||
if (!grade.isFailure)
|
|
||||||
{
|
|
||||||
combo++;
|
|
||||||
score += grade.value;
|
|
||||||
|
|
||||||
if (score >= schema.rewardScore && !isRewardScoreAchieved)
|
|
||||||
{
|
|
||||||
schema.sounds.rewardScore.play();
|
|
||||||
isRewardScoreAchieved = true;
|
|
||||||
|
|
||||||
for (auto& itemID : itemSchema.skillCheckRewardItemPool)
|
|
||||||
{
|
|
||||||
inventory.values[itemID]++;
|
|
||||||
if (!itemActors.contains(itemID))
|
|
||||||
{
|
|
||||||
itemActors[itemID] = Actor(itemSchema.anm2s[itemID], {}, Actor::SET);
|
|
||||||
itemRects[itemID] = itemActors[itemID].rect();
|
|
||||||
}
|
|
||||||
auto rect = itemRects[itemID];
|
|
||||||
auto rectSize = vec2(rect.z, rect.w);
|
|
||||||
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
|
||||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
|
||||||
? 0.0f
|
|
||||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
|
||||||
previewScale = std::min(1.0f, previewScale);
|
|
||||||
auto previewSize = rectSize * previewScale;
|
|
||||||
auto minX = position.x;
|
|
||||||
auto maxX = position.x + size.x - previewSize.x;
|
|
||||||
auto spawnX = minX >= maxX ? position.x : math::random_in_range(minX, maxX);
|
|
||||||
auto spawnY = position.y - previewSize.y - math::random_in_range(0.0f, size.y);
|
|
||||||
items.push_back({itemID, ImVec2(spawnX, spawnY), 0.0f});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto toastMessagePosition =
|
|
||||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckRewardToast).c_str()).x -
|
|
||||||
ImGui::GetTextLineHeightWithSpacing(),
|
|
||||||
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
|
|
||||||
toasts.emplace_back(strings.get(Strings::SkillCheckRewardToast), toastMessagePosition, schema.endTimerMax,
|
|
||||||
schema.endTimerMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (score > highScore)
|
|
||||||
{
|
|
||||||
highScore = score;
|
|
||||||
|
|
||||||
if (isHighScoreAchieved && !isHighScoreAchievedThisRun)
|
|
||||||
{
|
|
||||||
isHighScoreAchievedThisRun = true;
|
|
||||||
schema.sounds.highScore.play();
|
|
||||||
auto toastMessagePosition =
|
|
||||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckHighScoreToast).c_str()).x -
|
|
||||||
ImGui::GetTextLineHeightWithSpacing(),
|
|
||||||
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
|
|
||||||
toasts.emplace_back(strings.get(Strings::SkillCheckHighScoreToast), toastMessagePosition,
|
|
||||||
schema.endTimerMax, schema.endTimerMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (combo > bestCombo) bestCombo = combo;
|
|
||||||
|
|
||||||
auto rewardBonus = (schema.rewardGradeBonus * score) + (schema.rewardGradeBonus * grade.value);
|
|
||||||
while (rewardBonus > 0.0f)
|
|
||||||
{
|
|
||||||
const resource::xml::Item::Pool* pool{};
|
|
||||||
int rewardID{-1};
|
|
||||||
int rarityID{-1};
|
|
||||||
auto chanceBonus = std::max(1.0f, (float)grade.value);
|
|
||||||
|
|
||||||
for (auto& id : itemSchema.rarityIDsSortedByChance)
|
|
||||||
{
|
|
||||||
auto& rarity = itemSchema.rarities[id];
|
|
||||||
if (rarity.chance <= 0.0f) continue;
|
|
||||||
|
|
||||||
if (math::random_percent_roll(rarity.chance * chanceBonus))
|
|
||||||
{
|
|
||||||
pool = &itemSchema.pools[id];
|
|
||||||
rarityID = id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pool && !pool->empty())
|
|
||||||
{
|
|
||||||
rewardID = (*pool)[(int)math::random_roll((float)pool->size())];
|
|
||||||
auto& rarity = itemSchema.rarities.at(rarityID);
|
|
||||||
|
|
||||||
rarity.sound.play();
|
|
||||||
inventory.values[rewardID]++;
|
|
||||||
if (!itemActors.contains(rewardID))
|
|
||||||
{
|
|
||||||
itemActors[rewardID] = Actor(itemSchema.anm2s[rewardID], {}, Actor::SET);
|
|
||||||
itemRects[rewardID] = itemActors[rewardID].rect();
|
|
||||||
}
|
|
||||||
auto rect = itemRects[rewardID];
|
|
||||||
auto rectSize = vec2(rect.z, rect.w);
|
|
||||||
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
|
||||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
|
||||||
? 0.0f
|
|
||||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
|
||||||
previewScale = std::min(1.0f, previewScale);
|
|
||||||
auto previewSize = rectSize * previewScale;
|
|
||||||
auto minX = position.x;
|
|
||||||
auto maxX = position.x + size.x - previewSize.x;
|
|
||||||
auto spawnX = minX >= maxX ? position.x : math::random_in_range(minX, maxX);
|
|
||||||
auto spawnY = position.y - previewSize.y - math::random_in_range(0.0f, size.y);
|
|
||||||
items.push_back({rewardID, ImVec2(spawnX, spawnY), 0.0f});
|
|
||||||
}
|
|
||||||
|
|
||||||
rewardBonus -= 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
score = 0;
|
|
||||||
combo = 0;
|
|
||||||
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
|
||||||
if (highScore > 0) isHighScoreAchieved = true;
|
|
||||||
isRewardScoreAchieved = false;
|
|
||||||
isHighScoreAchievedThisRun = false;
|
|
||||||
highScoreStart = highScore;
|
|
||||||
isGameOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
endTimerMax = grade.isFailure ? schema.endTimerFailureMax : schema.endTimerMax;
|
|
||||||
isActive = false;
|
|
||||||
endTimer = endTimerMax;
|
|
||||||
|
|
||||||
queuedChallenge = challenge_generate(character);
|
|
||||||
|
|
||||||
auto string = grade.isFailure ? grade.name
|
|
||||||
: std::vformat(strings.get(Strings::SkillCheckGradeSuccessTemplate),
|
|
||||||
std::make_format_args(grade.name, grade.value));
|
|
||||||
auto toastMessagePosition =
|
|
||||||
ImVec2(barMin.x - ImGui::CalcTextSize(string.c_str()).x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
|
||||||
toasts.emplace_back(string, toastMessagePosition, endTimerMax, endTimerMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
endTimer--;
|
|
||||||
if (endTimer <= 0)
|
|
||||||
{
|
|
||||||
challenge = queuedChallenge;
|
|
||||||
tryValue = challenge.tryValue;
|
|
||||||
isActive = true;
|
|
||||||
isGameOver = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)toasts.size(); i++)
|
|
||||||
{
|
|
||||||
auto& toastMessage = toasts[i];
|
|
||||||
|
|
||||||
toastMessage.position.y -= TOAST_MESSAGE_SPEED;
|
|
||||||
|
|
||||||
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
|
||||||
textColor.w = ((float)toastMessage.time / toastMessage.timeMax);
|
|
||||||
|
|
||||||
drawList->AddText(toastMessage.position, ImGui::GetColorU32(textColor), toastMessage.message.c_str());
|
|
||||||
|
|
||||||
toastMessage.time--;
|
|
||||||
|
|
||||||
if (toastMessage.time <= 0) toasts.erase(toasts.begin() + i--);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto gravity = ITEM_FALL_GRAVITY;
|
|
||||||
auto windowMin = position;
|
|
||||||
auto windowMax = ImVec2(position.x + size.x, position.y + size.y);
|
|
||||||
ImGui::PushClipRect(windowMin, windowMax, true);
|
|
||||||
for (int i = 0; i < (int)items.size(); i++)
|
|
||||||
{
|
|
||||||
auto& fallingItem = items[i];
|
|
||||||
if (!itemActors.contains(fallingItem.id))
|
|
||||||
{
|
|
||||||
items.erase(items.begin() + i--);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rect = itemRects[fallingItem.id];
|
|
||||||
auto rectSize = vec2(rect.z, rect.w);
|
|
||||||
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
|
||||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
|
||||||
? 0.0f
|
|
||||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
|
||||||
previewScale = std::min(1.0f, previewScale);
|
|
||||||
auto previewSize = rectSize * previewScale;
|
|
||||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
|
||||||
|
|
||||||
if (!itemCanvases.contains(fallingItem.id))
|
|
||||||
itemCanvases.emplace(fallingItem.id, Canvas(canvasSize, Canvas::FLIP));
|
|
||||||
auto& canvas = itemCanvases[fallingItem.id];
|
|
||||||
canvas.zoom = math::to_percent(previewScale);
|
|
||||||
canvas.pan = vec2(rect.x, rect.y);
|
|
||||||
canvas.bind();
|
|
||||||
canvas.size_set(canvasSize);
|
|
||||||
canvas.clear();
|
|
||||||
|
|
||||||
itemActors[fallingItem.id].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
|
||||||
canvas.unbind();
|
|
||||||
|
|
||||||
auto min = fallingItem.position;
|
|
||||||
auto max = ImVec2(fallingItem.position.x + previewSize.x, fallingItem.position.y + previewSize.y);
|
|
||||||
drawList->AddImage(canvas.texture, min, max);
|
|
||||||
|
|
||||||
fallingItem.velocity += gravity * io.DeltaTime;
|
|
||||||
fallingItem.position.y += fallingItem.velocity * io.DeltaTime;
|
|
||||||
if (fallingItem.position.y > position.y + size.y) items.erase(items.begin() + i--);
|
|
||||||
}
|
|
||||||
ImGui::PopClipRect();
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
|
|
||||||
return WIDGET_FX(ImGui::Button(strings.get(Strings::SkillCheckMenuButton).c_str()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,22 @@ namespace game::state::play
|
|||||||
{
|
{
|
||||||
auto interact_area_override_tick = [](entity::Actor::Override& override_)
|
auto interact_area_override_tick = [](entity::Actor::Override& override_)
|
||||||
{
|
{
|
||||||
if (override_.frame.scale.has_value() && override_.frameBase.scale.has_value() && override_.time.has_value() &&
|
auto& scale = override_.frame.scale;
|
||||||
override_.timeStart.has_value())
|
auto& scaleBase = override_.frameBase.scale;
|
||||||
|
auto isScaleValid = scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();
|
||||||
|
|
||||||
|
if (isScaleValid && override_.time.has_value() && override_.timeStart.has_value())
|
||||||
{
|
{
|
||||||
auto percent = glm::clamp(*override_.time / *override_.timeStart, 0.0f, 1.0f);
|
auto percent = glm::clamp(*override_.time / *override_.timeStart, 0.0f, 1.0f);
|
||||||
auto elapsed = 1.0f - percent;
|
auto elapsed = 1.0f - percent;
|
||||||
|
|
||||||
auto oscillation = cosf(elapsed * glm::tau<float>() * override_.cycles);
|
auto oscillation = cosf(elapsed * glm::tau<float>() * override_.cycles);
|
||||||
auto envelope = percent;
|
auto envelope = percent;
|
||||||
auto amplitude = glm::abs(*override_.frameBase.scale);
|
auto amplitude = glm::abs(glm::vec2(*scaleBase.x, *scaleBase.y));
|
||||||
|
auto value = amplitude * (oscillation * envelope);
|
||||||
|
|
||||||
*override_.frame.scale = amplitude * (oscillation * envelope);
|
scale.x = value.x;
|
||||||
|
scale.y = value.y;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using namespace game::resource::xml;
|
|||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play
|
||||||
{
|
{
|
||||||
void Cheats::update(Resources&, entity::Character& character, Inventory& inventory)
|
void Cheats::update(Resources&, entity::Character& character, menu::Inventory& inventory)
|
||||||
{
|
{
|
||||||
auto& strings = character.data.strings;
|
auto& strings = character.data.strings;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "inventory.hpp"
|
#include "menu/inventory.hpp"
|
||||||
#include "text.hpp"
|
#include "text.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -10,6 +10,6 @@ namespace game::state::play
|
|||||||
class Cheats
|
class Cheats
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void update(Resources&, entity::Character&, Inventory&);
|
void update(Resources&, entity::Character&, menu::Inventory&);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ namespace game::state::play
|
|||||||
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
|
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
|
||||||
Text& text)
|
Text& text)
|
||||||
{
|
{
|
||||||
auto& strings = character.data.strings;
|
|
||||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||||
|
|
||||||
ImGui::Text(strings.get(Strings::DebugCursorScreenFormat).c_str(), cursor.position.x, cursor.position.y);
|
ImGui::Text("Cursor Pos (Screen): %0.0f, %0.0f", cursor.position.x, cursor.position.y);
|
||||||
ImGui::Text(strings.get(Strings::DebugCursorWorldFormat).c_str(), cursorPosition.x, cursorPosition.y);
|
ImGui::Text("Cursor Pos (World): %0.0f, %0.0f", cursorPosition.x, cursorPosition.y);
|
||||||
|
|
||||||
ImGui::SeparatorText(strings.get(Strings::DebugAnimations).c_str());
|
ImGui::SeparatorText("Animations");
|
||||||
ImGui::Text(strings.get(Strings::DebugNowPlayingFormat).c_str(), character.animationMapReverse.at(character.animationIndex).c_str());
|
ImGui::Text("Now Playing: %s", character.animationMapReverse.at(character.animationIndex).c_str());
|
||||||
|
|
||||||
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ namespace game::state::play
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::SeparatorText(strings.get(Strings::DebugDialogue).c_str());
|
ImGui::SeparatorText("Dialogue");
|
||||||
|
|
||||||
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
||||||
{
|
{
|
||||||
@@ -52,21 +51,21 @@ namespace game::state::play
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
WIDGET_FX(ImGui::Checkbox(strings.get(Strings::DebugShowNulls).c_str(), &character.isShowNulls));
|
WIDGET_FX(ImGui::Checkbox("Show Nulls (Hitboxes)", &character.isShowNulls));
|
||||||
WIDGET_FX(ImGui::Checkbox(strings.get(Strings::DebugShowWorldBounds).c_str(), &isBoundsDisplay));
|
WIDGET_FX(ImGui::Checkbox("Show World Bounds", &isBoundsDisplay));
|
||||||
|
|
||||||
if (!itemManager.items.empty())
|
if (!itemManager.items.empty())
|
||||||
{
|
{
|
||||||
ImGui::SeparatorText(strings.get(Strings::DebugItem).c_str());
|
ImGui::SeparatorText("Item");
|
||||||
|
|
||||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||||
{
|
{
|
||||||
auto& item = itemManager.items[i];
|
auto& item = itemManager.items[i];
|
||||||
if (itemManager.heldItemIndex == i) ImGui::TextUnformatted(strings.get(Strings::DebugHeld).c_str());
|
if (itemManager.heldItemIndex == i) ImGui::TextUnformatted("Held");
|
||||||
ImGui::Text(strings.get(Strings::DebugItemTypeFormat).c_str(), item.schemaID);
|
ImGui::Text("Type: %i", item.schemaID);
|
||||||
ImGui::Text(strings.get(Strings::DebugItemPositionFormat).c_str(), item.position.x, item.position.y);
|
ImGui::Text("Position: %0.0f, %0.0f", item.position.x, item.position.y);
|
||||||
ImGui::Text(strings.get(Strings::DebugItemVelocityFormat).c_str(), item.velocity.x, item.velocity.y);
|
ImGui::Text("Velocity: %0.0f, %0.0f", item.velocity.x, item.velocity.y);
|
||||||
ImGui::Text(strings.get(Strings::DebugItemDurabilityFormat).c_str(), item.durability);
|
ImGui::Text("Durability: %i", item.durability);
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/state/play/item/reward.cpp
Normal file
61
src/state/play/item/reward.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "reward.hpp"
|
||||||
|
|
||||||
|
#include "../../../util/math.hpp"
|
||||||
|
|
||||||
|
using namespace game::util;
|
||||||
|
|
||||||
|
namespace game::state::play::item
|
||||||
|
{
|
||||||
|
int Reward::random_item_get(const resource::xml::Item& itemSchema, float chanceBonus)
|
||||||
|
{
|
||||||
|
const resource::xml::Item::Pool* pool{};
|
||||||
|
|
||||||
|
for (auto& id : itemSchema.rarityIDsSortedByChance)
|
||||||
|
{
|
||||||
|
auto& rarity = itemSchema.rarities[id];
|
||||||
|
if (rarity.chance <= 0.0f) continue;
|
||||||
|
|
||||||
|
if (math::random_percent_roll(rarity.chance * chanceBonus))
|
||||||
|
{
|
||||||
|
pool = &itemSchema.pools.at(id);
|
||||||
|
rarity.sound.play();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pool || pool->empty()) return INVALID_ID;
|
||||||
|
return (*pool)[(int)math::random_roll((float)pool->size())];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reward::item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||||
|
const resource::xml::Item& itemSchema, const ImVec4& bounds, menu::ItemEffectManager::Mode mode)
|
||||||
|
{
|
||||||
|
if (itemID < 0) return;
|
||||||
|
|
||||||
|
inventory.values[itemID]++;
|
||||||
|
itemEffectManager.spawn(itemID, itemSchema, bounds, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reward::reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||||
|
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
|
||||||
|
float rewardRollCount, menu::ItemEffectManager::Mode mode)
|
||||||
|
{
|
||||||
|
if (!math::random_percent_roll(rewardChance)) return 0;
|
||||||
|
|
||||||
|
auto rollCountWhole = std::max(0, (int)std::floor(rewardRollCount));
|
||||||
|
auto rollCountFraction = std::max(0.0f, rewardRollCount - (float)rollCountWhole);
|
||||||
|
auto rollCount = rollCountWhole + (math::random_percent_roll(rollCountFraction) ? 1 : 0);
|
||||||
|
auto rewardedItemCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < rollCount; i++)
|
||||||
|
{
|
||||||
|
auto itemID = random_item_get(itemSchema);
|
||||||
|
if (itemID == INVALID_ID) continue;
|
||||||
|
|
||||||
|
item_give(itemID, inventory, itemEffectManager, itemSchema, bounds, mode);
|
||||||
|
rewardedItemCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewardedItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/state/play/item/reward.hpp
Normal file
22
src/state/play/item/reward.hpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../menu/inventory.hpp"
|
||||||
|
#include "../menu/item_effect_manager.hpp"
|
||||||
|
|
||||||
|
namespace game::state::play::item
|
||||||
|
{
|
||||||
|
class Reward
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr auto INVALID_ID = -1;
|
||||||
|
|
||||||
|
int random_item_get(const resource::xml::Item& itemSchema, float chanceBonus = 1.0f);
|
||||||
|
void item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||||
|
const resource::xml::Item& itemSchema, const ImVec4& bounds,
|
||||||
|
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
|
||||||
|
int reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||||
|
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
|
||||||
|
float rewardRollCount,
|
||||||
|
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
|
|
||||||
#include "../../util/imgui.hpp"
|
#include "../../util/imgui.hpp"
|
||||||
#include "../../util/imgui/style.hpp"
|
|
||||||
#include "../../util/imgui/widget.hpp"
|
#include "../../util/imgui/widget.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -68,7 +67,7 @@ namespace game::state::play
|
|||||||
|
|
||||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
|
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
|
||||||
{
|
{
|
||||||
arcade.update(resources, character, inventory, text);
|
arcade.update(resources, character, cursor, inventory, text, toasts);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ namespace game::state::play
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabDebug).c_str())))
|
if (WIDGET_FX(ImGui::BeginTabItem("Debug")))
|
||||||
{
|
{
|
||||||
debug.update(character, cursor, itemManager, canvas, text);
|
debug.update(character, cursor, itemManager, canvas, text);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
@@ -121,9 +120,7 @@ namespace game::state::play
|
|||||||
|
|
||||||
if (t <= 0.0f || t >= 1.0f)
|
if (t <= 0.0f || t >= 1.0f)
|
||||||
{
|
{
|
||||||
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip
|
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip : Strings::MenuOpenTooltip).c_str());
|
||||||
: Strings::MenuOpenTooltip)
|
|
||||||
.c_str());
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
isOpen = !isOpen;
|
isOpen = !isOpen;
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
#include "../settings_menu.hpp"
|
#include "../settings_menu.hpp"
|
||||||
|
|
||||||
#include "arcade.hpp"
|
#include "menu/arcade.hpp"
|
||||||
#include "cheats.hpp"
|
#include "cheats.hpp"
|
||||||
#include "debug.hpp"
|
#include "debug.hpp"
|
||||||
#include "interact.hpp"
|
#include "menu/interact.hpp"
|
||||||
#include "inventory.hpp"
|
#include "menu/inventory.hpp"
|
||||||
#include "text.hpp"
|
#include "text.hpp"
|
||||||
|
#include "menu/toasts.hpp"
|
||||||
|
|
||||||
#include "../../util/imgui/window_slide.hpp"
|
#include "../../util/imgui/window_slide.hpp"
|
||||||
|
|
||||||
@@ -18,11 +19,12 @@ namespace game::state::play
|
|||||||
class Menu
|
class Menu
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Arcade arcade;
|
menu::Arcade arcade;
|
||||||
Interact interact;
|
menu::Interact interact;
|
||||||
Cheats cheats;
|
Cheats cheats;
|
||||||
Debug debug;
|
Debug debug;
|
||||||
Inventory inventory;
|
menu::Inventory inventory;
|
||||||
|
menu::Toasts toasts;
|
||||||
|
|
||||||
state::SettingsMenu settingsMenu;
|
state::SettingsMenu settingsMenu;
|
||||||
|
|
||||||
|
|||||||
244
src/state/play/menu/arcade.cpp
Normal file
244
src/state/play/menu/arcade.cpp
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
#include "arcade.hpp"
|
||||||
|
|
||||||
|
#include "../../../util/imgui/widget.hpp"
|
||||||
|
|
||||||
|
using namespace game::util::imgui;
|
||||||
|
using namespace game::resource::xml;
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct GameInfoStrings
|
||||||
|
{
|
||||||
|
Strings::Type name;
|
||||||
|
Strings::Type description;
|
||||||
|
Strings::Type howToPlay;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
|
||||||
|
|
||||||
|
void Arcade::game_reset(entity::Character& character, Game gameCurrent)
|
||||||
|
{
|
||||||
|
switch (gameCurrent)
|
||||||
|
{
|
||||||
|
case SKILL_CHECK:
|
||||||
|
skillCheck.reset(character);
|
||||||
|
break;
|
||||||
|
case DUNGEON:
|
||||||
|
dungeon.reset(character);
|
||||||
|
break;
|
||||||
|
case ORBIT:
|
||||||
|
orbit.reset(character);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Arcade::tick()
|
||||||
|
{
|
||||||
|
skillCheck.tick();
|
||||||
|
dungeon.tick();
|
||||||
|
orbit.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Arcade::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
|
||||||
|
Text& text, Toasts& toasts)
|
||||||
|
{
|
||||||
|
auto available = ImGui::GetContentRegionAvail();
|
||||||
|
auto& strings = character.data.strings;
|
||||||
|
auto game_info_strings_get = [&](Game gameCurrent) -> GameInfoStrings
|
||||||
|
{
|
||||||
|
switch (gameCurrent)
|
||||||
|
{
|
||||||
|
case SKILL_CHECK:
|
||||||
|
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription,
|
||||||
|
Strings::ArcadeSkillCheckHowToPlay};
|
||||||
|
case DUNGEON:
|
||||||
|
return {Strings::ArcadeDungeonName, Strings::ArcadeDungeonDescription, Strings::ArcadeDungeonHowToPlay};
|
||||||
|
case ORBIT:
|
||||||
|
return {Strings::ArcadeOrbitName, Strings::ArcadeOrbitDescription, Strings::ArcadeOrbitHowToPlay};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription, Strings::ArcadeSkillCheckHowToPlay};
|
||||||
|
};
|
||||||
|
auto game_header_draw = [&](Game gameCurrent)
|
||||||
|
{
|
||||||
|
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||||
|
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||||
|
ImGui::TextUnformatted(strings.get(gameInfoStrings.name).c_str());
|
||||||
|
ImGui::PopFont();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto game_menu_draw = [&](Game gameCurrent)
|
||||||
|
{
|
||||||
|
constexpr auto GAME_CHILD_HEIGHT_MULTIPLIER = 7.0f;
|
||||||
|
constexpr auto GAME_DESCRIPTION_HEIGHT_MULTIPLIER = 4.75f;
|
||||||
|
|
||||||
|
auto lineHeight = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
auto gameChildHeight = lineHeight * GAME_CHILD_HEIGHT_MULTIPLIER;
|
||||||
|
auto gameDescriptionHeight = lineHeight * GAME_DESCRIPTION_HEIGHT_MULTIPLIER;
|
||||||
|
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||||
|
auto detailsChildID = [gameCurrent]()
|
||||||
|
{
|
||||||
|
switch (gameCurrent)
|
||||||
|
{
|
||||||
|
case SKILL_CHECK:
|
||||||
|
return "##ArcadeSkillCheckDescription";
|
||||||
|
case DUNGEON:
|
||||||
|
return "##ArcadeDungeonDescription";
|
||||||
|
case ORBIT:
|
||||||
|
return "##ArcadeOrbitDescription";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "##ArcadeDescription";
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (ImGui::BeginChild(gameInfoStrings.name, {0, gameChildHeight}, ImGuiChildFlags_Borders))
|
||||||
|
{
|
||||||
|
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||||
|
|
||||||
|
ImGui::BeginChild(detailsChildID, {0, gameDescriptionHeight});
|
||||||
|
game_header_draw(gameCurrent);
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.description).c_str());
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||||
|
{
|
||||||
|
game_reset(character, gameCurrent);
|
||||||
|
game = gameCurrent;
|
||||||
|
state = GAMEPLAY;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeInfoButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||||
|
{
|
||||||
|
game = gameCurrent;
|
||||||
|
state = INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto game_info_sections_draw = [&](Game gameCurrent)
|
||||||
|
{
|
||||||
|
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||||
|
|
||||||
|
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||||
|
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeHowToPlay).c_str());
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::PushFont(ImGui::GetFont(), resource::Font::NORMAL);
|
||||||
|
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.howToPlay).c_str());
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||||
|
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeStats).c_str());
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::Separator();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto game_stats_draw = [&](Game gameCurrent)
|
||||||
|
{
|
||||||
|
switch (gameCurrent)
|
||||||
|
{
|
||||||
|
case SKILL_CHECK:
|
||||||
|
{
|
||||||
|
auto& schema = character.data.skillCheckSchema;
|
||||||
|
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), skillCheck.highScore,
|
||||||
|
skillCheck.bestCombo);
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeTotalSkillChecksFormat).c_str(), skillCheck.totalPlays);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)schema.grades.size(); i++)
|
||||||
|
{
|
||||||
|
auto& grade = schema.grades[i];
|
||||||
|
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeAccuracyFormat).c_str(), skillCheck.accuracy_score_get(character));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DUNGEON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ORBIT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto game_info_draw = [&](Game gameCurrent)
|
||||||
|
{
|
||||||
|
game_header_draw(gameCurrent);
|
||||||
|
ImGui::Separator();
|
||||||
|
game_info_sections_draw(gameCurrent);
|
||||||
|
game_stats_draw(gameCurrent);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case GAMEPLAY:
|
||||||
|
switch (game)
|
||||||
|
{
|
||||||
|
case SKILL_CHECK:
|
||||||
|
if (skillCheck.update(resources, character, inventory, text, toasts))
|
||||||
|
{
|
||||||
|
game_reset(character, SKILL_CHECK);
|
||||||
|
state = MENU;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DUNGEON:
|
||||||
|
if (dungeon.update(character))
|
||||||
|
{
|
||||||
|
game_reset(character, DUNGEON);
|
||||||
|
state = MENU;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ORBIT:
|
||||||
|
if (orbit.update(resources, character, cursor, inventory, text, toasts))
|
||||||
|
{
|
||||||
|
game_reset(character, ORBIT);
|
||||||
|
state = MENU;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case MENU:
|
||||||
|
case INFO:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||||
|
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
|
||||||
|
|
||||||
|
if (ImGui::BeginChild("##Arcade Child", childSize))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case MENU:
|
||||||
|
game_menu_draw(ORBIT);
|
||||||
|
//game_menu_draw(DUNGEON);
|
||||||
|
game_menu_draw(SKILL_CHECK);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INFO:
|
||||||
|
game_info_draw(game);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GAMEPLAY:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
if (state == INFO)
|
||||||
|
{
|
||||||
|
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) state = MENU;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/state/play/menu/arcade.hpp
Normal file
40
src/state/play/menu/arcade.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "arcade/dungeon.hpp"
|
||||||
|
#include "arcade/orbit.hpp"
|
||||||
|
#include "arcade/skill_check.hpp"
|
||||||
|
#include "toasts.hpp"
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
class Arcade
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Game
|
||||||
|
{
|
||||||
|
SKILL_CHECK,
|
||||||
|
DUNGEON,
|
||||||
|
ORBIT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
MENU,
|
||||||
|
GAMEPLAY,
|
||||||
|
INFO
|
||||||
|
};
|
||||||
|
|
||||||
|
arcade::SkillCheck skillCheck{};
|
||||||
|
arcade::Dungeon dungeon{};
|
||||||
|
arcade::Orbit orbit{};
|
||||||
|
Game game{SKILL_CHECK};
|
||||||
|
State state{MENU};
|
||||||
|
|
||||||
|
Arcade() = default;
|
||||||
|
Arcade(entity::Character&);
|
||||||
|
|
||||||
|
void game_reset(entity::Character&, Game);
|
||||||
|
void tick();
|
||||||
|
void update(Resources&, entity::Character&, entity::Cursor&, Inventory&, Text&, Toasts&);
|
||||||
|
};
|
||||||
|
}
|
||||||
310
src/state/play/menu/arcade/dungeon.cpp
Normal file
310
src/state/play/menu/arcade/dungeon.cpp
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
#include "dungeon.hpp"
|
||||||
|
|
||||||
|
#include "../../../../resource/font.hpp"
|
||||||
|
#include "../../../../resource/xml/strings.hpp"
|
||||||
|
#include "../../../../util/imgui/widget.hpp"
|
||||||
|
#include "../../../../util/math.hpp"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
using namespace game::util::imgui;
|
||||||
|
using namespace game::resource::xml;
|
||||||
|
|
||||||
|
namespace game::state::play::menu::arcade
|
||||||
|
{
|
||||||
|
int Dungeon::tile_value_get(const Tile& tile) const { return (int)tile.value; }
|
||||||
|
bool Dungeon::tile_value_counts_toward_sum(const Tile& tile) const
|
||||||
|
{
|
||||||
|
return (tile.value >= Tile::VALUE_0 && tile.value <= Tile::VALUE_13) || tile.value == Tile::MINE;
|
||||||
|
}
|
||||||
|
bool Dungeon::tile_is_scroll(const Tile& tile) const { return tile.value == Tile::SCROLL; }
|
||||||
|
const char* Dungeon::tile_flag_text_get(const Tile& tile) const
|
||||||
|
{
|
||||||
|
switch (tile.flagValue)
|
||||||
|
{
|
||||||
|
case Tile::FLAG_NONE:
|
||||||
|
return nullptr;
|
||||||
|
case Tile::FLAG_MINE:
|
||||||
|
return "M";
|
||||||
|
case Tile::FLAG_1:
|
||||||
|
return "1";
|
||||||
|
case Tile::FLAG_2:
|
||||||
|
return "2";
|
||||||
|
case Tile::FLAG_3:
|
||||||
|
return "3";
|
||||||
|
case Tile::FLAG_4:
|
||||||
|
return "4";
|
||||||
|
case Tile::FLAG_5:
|
||||||
|
return "5";
|
||||||
|
case Tile::FLAG_6:
|
||||||
|
return "6";
|
||||||
|
case Tile::FLAG_7:
|
||||||
|
return "7";
|
||||||
|
case Tile::FLAG_8:
|
||||||
|
return "8";
|
||||||
|
case Tile::FLAG_9:
|
||||||
|
return "9";
|
||||||
|
case Tile::FLAG_10:
|
||||||
|
return "10";
|
||||||
|
case Tile::FLAG_11:
|
||||||
|
return "11";
|
||||||
|
case Tile::FLAG_12:
|
||||||
|
return "12";
|
||||||
|
case Tile::FLAG_13:
|
||||||
|
return "13";
|
||||||
|
case Tile::FLAG_QUESTION:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Dungeon::surrounding_value_sum_get(int row, int column) const
|
||||||
|
{
|
||||||
|
auto sum = 0;
|
||||||
|
for (int rowOffset = -1; rowOffset <= 1; rowOffset++)
|
||||||
|
for (int columnOffset = -1; columnOffset <= 1; columnOffset++)
|
||||||
|
{
|
||||||
|
if (rowOffset == 0 && columnOffset == 0) continue;
|
||||||
|
|
||||||
|
auto neighborRow = row + rowOffset;
|
||||||
|
auto neighborColumn = column + columnOffset;
|
||||||
|
if (neighborRow < 0 || neighborRow >= GRID_ROWS || neighborColumn < 0 || neighborColumn >= GRID_COLUMNS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& neighbor = tiles[neighborRow * GRID_COLUMNS + neighborColumn];
|
||||||
|
if (!tile_value_counts_toward_sum(neighbor)) continue;
|
||||||
|
|
||||||
|
sum += tile_value_get(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dungeon::reveal_diamond(int row, int column, int radius)
|
||||||
|
{
|
||||||
|
for (int rowOffset = -radius; rowOffset <= radius; rowOffset++)
|
||||||
|
for (int columnOffset = -radius; columnOffset <= radius; columnOffset++)
|
||||||
|
{
|
||||||
|
if (std::abs(rowOffset) + std::abs(columnOffset) > radius) continue;
|
||||||
|
|
||||||
|
auto targetRow = row + rowOffset;
|
||||||
|
auto targetColumn = column + columnOffset;
|
||||||
|
if (targetRow < 0 || targetRow >= GRID_ROWS || targetColumn < 0 || targetColumn >= GRID_COLUMNS) continue;
|
||||||
|
|
||||||
|
auto& tile = tiles[targetRow * GRID_COLUMNS + targetColumn];
|
||||||
|
if (tile.state == Tile::HIDDEN)
|
||||||
|
{
|
||||||
|
tile.state = Tile::SHOWN;
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dungeon::reset(entity::Character&)
|
||||||
|
{
|
||||||
|
tiles.assign(GRID_ROWS * GRID_COLUMNS, Tile{});
|
||||||
|
score = 0;
|
||||||
|
for (auto& tile : tiles)
|
||||||
|
{
|
||||||
|
tile.value = game::util::math::random_percent_roll(5.0f) ? Tile::MINE
|
||||||
|
: (Tile::Value)(int)game::util::math::random_max(14.0f);
|
||||||
|
tile.state = Tile::HIDDEN;
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tiles.empty()) tiles[(int)game::util::math::random_max((float)tiles.size())].value = Tile::SCROLL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dungeon::tick() {}
|
||||||
|
|
||||||
|
bool Dungeon::update(entity::Character& character)
|
||||||
|
{
|
||||||
|
auto& strings = character.data.strings;
|
||||||
|
constexpr float GRID_SPACING = 1.0f;
|
||||||
|
auto& style = ImGui::GetStyle();
|
||||||
|
|
||||||
|
if (tiles.size() != GRID_ROWS * GRID_COLUMNS) reset(character);
|
||||||
|
|
||||||
|
auto contentRegionAvail = ImGui::GetContentRegionAvail();
|
||||||
|
auto childSize =
|
||||||
|
ImVec2(contentRegionAvail.x,
|
||||||
|
std::max(0.0f, contentRegionAvail.y - ImGui::GetFrameHeightWithSpacing() - style.WindowPadding.y));
|
||||||
|
|
||||||
|
if (ImGui::BeginChild("##DungeonGrid", childSize))
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
auto childAvail = ImGui::GetContentRegionAvail();
|
||||||
|
auto cellWidth = std::max(1.0f, (childAvail.x - GRID_SPACING * (GRID_COLUMNS - 1)) / (float)GRID_COLUMNS);
|
||||||
|
auto cellHeight = std::max(1.0f, (childAvail.y - GRID_SPACING * (GRID_ROWS - 1)) / (float)GRID_ROWS);
|
||||||
|
auto cellSize = std::floor(std::min(cellWidth, cellHeight));
|
||||||
|
auto gridWidth = cellSize * (float)GRID_COLUMNS + GRID_SPACING * (GRID_COLUMNS - 1);
|
||||||
|
auto gridHeight = cellSize * (float)GRID_ROWS + GRID_SPACING * (GRID_ROWS - 1);
|
||||||
|
auto cursor = ImGui::GetCursorPos();
|
||||||
|
auto offsetX = std::max(0.0f, (childAvail.x - gridWidth) * 0.5f);
|
||||||
|
auto offsetY = std::max(0.0f, (childAvail.y - gridHeight) * 0.5f);
|
||||||
|
|
||||||
|
ImGui::SetCursorPos(ImVec2(cursor.x + offsetX, cursor.y + offsetY));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(GRID_SPACING, GRID_SPACING));
|
||||||
|
|
||||||
|
for (int row = 0; row < GRID_ROWS; row++)
|
||||||
|
{
|
||||||
|
for (int column = 0; column < GRID_COLUMNS; column++)
|
||||||
|
{
|
||||||
|
auto tileID = row * GRID_COLUMNS + column;
|
||||||
|
auto& tile = tiles[tileID];
|
||||||
|
auto tileValue = tile_value_get(tile);
|
||||||
|
|
||||||
|
ImGui::PushID(tileID);
|
||||||
|
if (tile.state != Tile::HIDDEN)
|
||||||
|
{
|
||||||
|
auto buttonColor = style.Colors[ImGuiCol_WindowBg];
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, buttonColor);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColor);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColor);
|
||||||
|
}
|
||||||
|
auto isLeftPressed = WIDGET_FX(ImGui::Button("##DungeonCell", ImVec2(cellSize, cellSize)));
|
||||||
|
auto isPopupOpen = ImGui::BeginPopupContextItem("##DungeonFlagMenu");
|
||||||
|
if (isPopupOpen)
|
||||||
|
{
|
||||||
|
if (ImGui::Button("M", ImVec2(36.0f, 0.0f)))
|
||||||
|
{
|
||||||
|
tile.flagValue = Tile::FLAG_MINE;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
for (int flagValue = Tile::FLAG_1; flagValue <= Tile::FLAG_13; flagValue++)
|
||||||
|
{
|
||||||
|
auto flagText = std::format("{}", flagValue);
|
||||||
|
if (ImGui::Button(flagText.c_str(), ImVec2(36.0f, 0.0f)))
|
||||||
|
{
|
||||||
|
tile.flagValue = (Tile::FlagValue)flagValue;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (flagValue % 4 != 0 && flagValue != Tile::FLAG_13) ImGui::SameLine();
|
||||||
|
}
|
||||||
|
if (ImGui::Button("?", ImVec2(36.0f, 0.0f)))
|
||||||
|
{
|
||||||
|
tile.flagValue = Tile::FLAG_QUESTION;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Clear"))
|
||||||
|
{
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
auto rectMin = ImGui::GetItemRectMin();
|
||||||
|
auto rectMax = ImGui::GetItemRectMax();
|
||||||
|
if (tile.state != Tile::HIDDEN) ImGui::PopStyleColor(3);
|
||||||
|
ImGui::PopID();
|
||||||
|
|
||||||
|
if (isLeftPressed)
|
||||||
|
{
|
||||||
|
switch (tile.state)
|
||||||
|
{
|
||||||
|
case Tile::HIDDEN:
|
||||||
|
tile.state = Tile::SHOWN;
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
if (tile_is_scroll(tile))
|
||||||
|
{
|
||||||
|
reveal_diamond(row, column, 2);
|
||||||
|
tile.value = Tile::VALUE_0;
|
||||||
|
tile.state = Tile::SHOWN;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tile::SHOWN:
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
if (tile_is_scroll(tile))
|
||||||
|
{
|
||||||
|
reveal_diamond(row, column, 2);
|
||||||
|
tile.value = Tile::VALUE_0;
|
||||||
|
tile.state = Tile::SHOWN;
|
||||||
|
}
|
||||||
|
else if (tileValue > 0)
|
||||||
|
{
|
||||||
|
tile.state = Tile::CORPSE;
|
||||||
|
score += tileValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tile::CORPSE:
|
||||||
|
tile.value = Tile::VALUE_0;
|
||||||
|
tile.state = Tile::SHOWN;
|
||||||
|
tile.flagValue = Tile::FLAG_NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string tileText{};
|
||||||
|
auto textColor = IM_COL32(255, 255, 255, 255);
|
||||||
|
if (tile_is_scroll(tile))
|
||||||
|
{
|
||||||
|
tileText = "!";
|
||||||
|
if (tile.state == Tile::CORPSE)
|
||||||
|
textColor = IM_COL32(255, 230, 64, 255);
|
||||||
|
else
|
||||||
|
textColor = IM_COL32(255, 255, 255, 255);
|
||||||
|
}
|
||||||
|
else if (tile.state == Tile::HIDDEN)
|
||||||
|
{
|
||||||
|
if (auto flagText = tile_flag_text_get(tile))
|
||||||
|
{
|
||||||
|
tileText = flagText;
|
||||||
|
textColor = IM_COL32(64, 128, 255, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
switch (tile.state)
|
||||||
|
{
|
||||||
|
case Tile::HIDDEN:
|
||||||
|
break;
|
||||||
|
case Tile::SHOWN:
|
||||||
|
if (tileValue == 0)
|
||||||
|
{
|
||||||
|
auto surroundingSum = surrounding_value_sum_get(row, column);
|
||||||
|
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tileText = std::format("{}", tileValue);
|
||||||
|
textColor = IM_COL32(255, 64, 64, 255);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tile::CORPSE:
|
||||||
|
if (tileValue == 0)
|
||||||
|
{
|
||||||
|
auto surroundingSum = surrounding_value_sum_get(row, column);
|
||||||
|
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tileText = std::format("{}", tileValue);
|
||||||
|
textColor = IM_COL32(255, 230, 64, 255);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tileText.empty())
|
||||||
|
{
|
||||||
|
auto textSize = ImGui::CalcTextSize(tileText.c_str());
|
||||||
|
auto textPosition = ImVec2(rectMin.x + (rectMax.x - rectMin.x - textSize.x) * 0.5f,
|
||||||
|
rectMin.y + (rectMax.y - rectMin.y - textSize.y) * 0.5f);
|
||||||
|
drawList->AddText(textPosition, textColor, tileText.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column + 1 < GRID_COLUMNS) ImGui::SameLine(0.0f, GRID_SPACING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
|
||||||
|
|
||||||
|
return WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/state/play/menu/arcade/dungeon.hpp
Normal file
82
src/state/play/menu/arcade/dungeon.hpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../../../entity/character.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace game::state::play::menu::arcade
|
||||||
|
{
|
||||||
|
class Dungeon
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Tile
|
||||||
|
{
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
HIDDEN,
|
||||||
|
SHOWN,
|
||||||
|
CORPSE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FlagValue
|
||||||
|
{
|
||||||
|
FLAG_NONE,
|
||||||
|
FLAG_1,
|
||||||
|
FLAG_2,
|
||||||
|
FLAG_3,
|
||||||
|
FLAG_4,
|
||||||
|
FLAG_5,
|
||||||
|
FLAG_6,
|
||||||
|
FLAG_7,
|
||||||
|
FLAG_8,
|
||||||
|
FLAG_9,
|
||||||
|
FLAG_10,
|
||||||
|
FLAG_11,
|
||||||
|
FLAG_12,
|
||||||
|
FLAG_13,
|
||||||
|
FLAG_MINE,
|
||||||
|
FLAG_QUESTION
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Value
|
||||||
|
{
|
||||||
|
VALUE_0,
|
||||||
|
VALUE_1,
|
||||||
|
VALUE_2,
|
||||||
|
VALUE_3,
|
||||||
|
VALUE_4,
|
||||||
|
VALUE_5,
|
||||||
|
VALUE_6,
|
||||||
|
VALUE_7,
|
||||||
|
VALUE_8,
|
||||||
|
VALUE_9,
|
||||||
|
VALUE_10,
|
||||||
|
VALUE_11,
|
||||||
|
VALUE_12,
|
||||||
|
VALUE_13,
|
||||||
|
MINE = 100,
|
||||||
|
SCROLL = 101
|
||||||
|
};
|
||||||
|
|
||||||
|
Value value{VALUE_0};
|
||||||
|
State state{HIDDEN};
|
||||||
|
FlagValue flagValue{FLAG_NONE};
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int GRID_ROWS = 13;
|
||||||
|
static constexpr int GRID_COLUMNS = 13;
|
||||||
|
|
||||||
|
std::vector<Tile> tiles{};
|
||||||
|
int score{};
|
||||||
|
|
||||||
|
int tile_value_get(const Tile&) const;
|
||||||
|
int surrounding_value_sum_get(int row, int column) const;
|
||||||
|
bool tile_value_counts_toward_sum(const Tile&) const;
|
||||||
|
bool tile_is_scroll(const Tile&) const;
|
||||||
|
const char* tile_flag_text_get(const Tile&) const;
|
||||||
|
void reveal_diamond(int row, int column, int radius);
|
||||||
|
void reset(entity::Character&);
|
||||||
|
void tick();
|
||||||
|
bool update(entity::Character&);
|
||||||
|
};
|
||||||
|
}
|
||||||
609
src/state/play/menu/arcade/orbit.cpp
Normal file
609
src/state/play/menu/arcade/orbit.cpp
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
#include "orbit.hpp"
|
||||||
|
|
||||||
|
#include "../../../../util/imgui.hpp"
|
||||||
|
#include "../../../../util/imgui/widget.hpp"
|
||||||
|
#include "../../../../util/math.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <format>
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
using namespace game::util::imgui;
|
||||||
|
using namespace game::resource::xml;
|
||||||
|
using namespace game::util;
|
||||||
|
using namespace glm;
|
||||||
|
|
||||||
|
namespace game::state::play::menu::arcade
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum SpawnSide
|
||||||
|
{
|
||||||
|
TOP,
|
||||||
|
RIGHT,
|
||||||
|
BOTTOM,
|
||||||
|
LEFT
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_rect_overlapping(const glm::vec4& left, const glm::vec4& right)
|
||||||
|
{
|
||||||
|
return left.x < right.x + right.z && left.x + left.z > right.x && left.y < right.y + right.w &&
|
||||||
|
left.y + left.w > right.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_tick(Orbit::Entity& entity, const glm::vec2& target, float acceleration)
|
||||||
|
{
|
||||||
|
auto delta = target - entity.position;
|
||||||
|
auto distance = glm::length(delta);
|
||||||
|
|
||||||
|
if (distance <= 0.001f)
|
||||||
|
{
|
||||||
|
entity.position = target;
|
||||||
|
entity.velocity *= 0.5f;
|
||||||
|
if (glm::length(entity.velocity) <= 0.001f) entity.velocity = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maxSpeed = std::max(acceleration * 8.0f, 1.0f);
|
||||||
|
auto desiredVelocity = glm::normalize(delta) * std::min(distance * 0.35f, maxSpeed);
|
||||||
|
auto steering = desiredVelocity - entity.velocity;
|
||||||
|
auto steeringLength = glm::length(steering);
|
||||||
|
if (steeringLength > acceleration) steering = (steering / steeringLength) * acceleration;
|
||||||
|
|
||||||
|
entity.velocity += steering;
|
||||||
|
|
||||||
|
auto velocityLength = glm::length(entity.velocity);
|
||||||
|
if (velocityLength > maxSpeed) entity.velocity = (entity.velocity / velocityLength) * maxSpeed;
|
||||||
|
|
||||||
|
entity.position += entity.velocity;
|
||||||
|
|
||||||
|
if (glm::distance(entity.position, target) <= maxSpeed)
|
||||||
|
{
|
||||||
|
entity.position = glm::mix(entity.position, target, 0.15f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void follower_angles_refresh(std::vector<Orbit::Entity>& entities)
|
||||||
|
{
|
||||||
|
std::vector<Orbit::Entity*> followers{};
|
||||||
|
for (auto& entity : entities)
|
||||||
|
if (entity.type == Orbit::Entity::FOLLOWER) followers.emplace_back(&entity);
|
||||||
|
|
||||||
|
if (followers.empty()) return;
|
||||||
|
|
||||||
|
std::sort(followers.begin(), followers.end(),
|
||||||
|
[](const Orbit::Entity* left, const Orbit::Entity* right) { return left->colorID < right->colorID; });
|
||||||
|
|
||||||
|
auto baseAngle = followers.front()->orbitAngle;
|
||||||
|
auto spacing = glm::two_pi<float>() / (float)followers.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)followers.size(); i++)
|
||||||
|
followers[i]->orbitAngle = baseAngle + spacing * (float)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glm::vec3* color_value_get(const resource::xml::Orbit& schema, int colorID)
|
||||||
|
{
|
||||||
|
if (colorID < 0 || colorID >= (int)schema.colors.size()) return nullptr;
|
||||||
|
return &schema.colors[colorID].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int random_available_color_get(const resource::xml::Orbit& schema, int level)
|
||||||
|
{
|
||||||
|
auto availableCount = std::min(level, (int)schema.colors.size());
|
||||||
|
if (availableCount <= 0) return -1;
|
||||||
|
return (int)math::random_max((float)availableCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
int unlocked_level_get(const resource::xml::Orbit& schema, int score)
|
||||||
|
{
|
||||||
|
int unlockedLevel = 0;
|
||||||
|
|
||||||
|
for (auto& color : schema.colors)
|
||||||
|
{
|
||||||
|
if (score >= color.scoreThreshold)
|
||||||
|
unlockedLevel++;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::max(1, unlockedLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void color_override_set(Orbit::Entity& entity, const resource::xml::Orbit& schema, int colorID,
|
||||||
|
const std::string& layerName)
|
||||||
|
{
|
||||||
|
auto color = color_value_get(schema, colorID);
|
||||||
|
if (!color) return;
|
||||||
|
if (!entity.layerMap.contains(layerName)) return;
|
||||||
|
|
||||||
|
entity::Actor::Override override_{entity.layerMap.at(layerName), Anm2::LAYER, entity::Actor::Override::SET};
|
||||||
|
override_.frame.tint.x = color->r;
|
||||||
|
override_.frame.tint.y = color->g;
|
||||||
|
override_.frame.tint.z = color->b;
|
||||||
|
entity.overrides.emplace_back(std::move(override_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void idle_queue(Orbit::Entity& entity)
|
||||||
|
{
|
||||||
|
if (!entity.animationIdle.empty())
|
||||||
|
entity.queue_play({.animation = entity.animationIdle, .isPlayAfterAnimation = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn_animation_play(Orbit::Entity& entity)
|
||||||
|
{
|
||||||
|
if (!entity.animationSpawn.empty())
|
||||||
|
{
|
||||||
|
entity.play(entity.animationSpawn, entity::Actor::PLAY_FORCE);
|
||||||
|
idle_queue(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Orbit::spawn(entity::Character& character, Orbit::Entity::Type type, int colorID)
|
||||||
|
{
|
||||||
|
auto& schema = character.data.orbitSchema;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Entity::PLAYER:
|
||||||
|
if (!schema.player.anm2.is_valid()) return;
|
||||||
|
entities.emplace_back(schema.player.anm2, Entity::PLAYER);
|
||||||
|
entities.back().position = centerPosition;
|
||||||
|
entities.back().animationIdle = schema.player.animations.idle;
|
||||||
|
entities.back().animationSpawn = schema.player.animations.spawn;
|
||||||
|
entities.back().animationDeath = schema.player.animations.death;
|
||||||
|
entities.back().hitboxNull = schema.player.hitboxNull;
|
||||||
|
spawn_animation_play(entities.back());
|
||||||
|
return;
|
||||||
|
case Entity::FOLLOWER:
|
||||||
|
{
|
||||||
|
if (!schema.follower.anm2.is_valid()) return;
|
||||||
|
if (colorID < 0 || colorID >= (int)schema.colors.size()) return;
|
||||||
|
|
||||||
|
Entity follower{schema.follower.anm2, Entity::FOLLOWER};
|
||||||
|
follower.colorID = colorID;
|
||||||
|
follower.orbitAngle = 0.0f;
|
||||||
|
follower.position = centerPosition;
|
||||||
|
follower.animationIdle = schema.follower.animations.idle;
|
||||||
|
follower.animationSpawn = schema.follower.animations.spawn;
|
||||||
|
follower.animationDeath = schema.follower.animations.death;
|
||||||
|
follower.hitboxNull = schema.follower.hitboxNull;
|
||||||
|
color_override_set(follower, schema, colorID, schema.follower.overrideTintLayer);
|
||||||
|
spawn_animation_play(follower);
|
||||||
|
|
||||||
|
entities.emplace_back(std::move(follower));
|
||||||
|
follower_angles_refresh(entities);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case Entity::ENEMY:
|
||||||
|
{
|
||||||
|
if (!schema.enemy.anm2.is_valid()) return;
|
||||||
|
|
||||||
|
Entity enemy{schema.enemy.anm2, Entity::ENEMY};
|
||||||
|
enemy.colorID = colorID;
|
||||||
|
enemy.animationIdle = schema.enemy.animations.idle;
|
||||||
|
enemy.animationSpawn = schema.enemy.animations.spawn;
|
||||||
|
enemy.animationDeath = schema.enemy.animations.death;
|
||||||
|
enemy.hitboxNull = schema.enemy.hitboxNull;
|
||||||
|
color_override_set(enemy, schema, colorID, schema.enemy.overrideTintLayer);
|
||||||
|
spawn_animation_play(enemy);
|
||||||
|
|
||||||
|
auto rect = enemy.rect();
|
||||||
|
auto width = rect.z;
|
||||||
|
auto height = rect.w;
|
||||||
|
auto side = (SpawnSide)math::random_max(4.0f);
|
||||||
|
switch (side)
|
||||||
|
{
|
||||||
|
case TOP:
|
||||||
|
enemy.position = vec2(math::random_max(canvas.size.x), -height - schema.enemy.spawnPadding);
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
enemy.position = vec2(canvas.size.x + width + schema.enemy.spawnPadding, math::random_max(canvas.size.y));
|
||||||
|
break;
|
||||||
|
case BOTTOM:
|
||||||
|
enemy.position = vec2(math::random_max(canvas.size.x), canvas.size.y + height + schema.enemy.spawnPadding);
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
enemy.position = vec2(-width - schema.enemy.spawnPadding, math::random_max(canvas.size.y));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.emplace_back(std::move(enemy));
|
||||||
|
|
||||||
|
if (schema.warning.anm2.is_valid())
|
||||||
|
{
|
||||||
|
Entity warning{schema.warning.anm2, Entity::WARNING};
|
||||||
|
warning.colorID = colorID;
|
||||||
|
color_override_set(warning, schema, colorID, schema.warning.overrideTintLayer);
|
||||||
|
|
||||||
|
auto warningRect = warning.rect();
|
||||||
|
auto warningWidth = warningRect.z;
|
||||||
|
auto warningHeight = warningRect.w;
|
||||||
|
|
||||||
|
switch (side)
|
||||||
|
{
|
||||||
|
case TOP:
|
||||||
|
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
|
||||||
|
(float)canvas.size.x - warningWidth * 0.5f),
|
||||||
|
warningHeight * 0.5f);
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
warning.position = vec2((float)canvas.size.x - warningWidth * 0.5f,
|
||||||
|
glm::clamp(entities.back().position.y, warningHeight * 0.5f,
|
||||||
|
(float)canvas.size.y - warningHeight * 0.5f));
|
||||||
|
break;
|
||||||
|
case BOTTOM:
|
||||||
|
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
|
||||||
|
(float)canvas.size.x - warningWidth * 0.5f),
|
||||||
|
(float)canvas.size.y - warningHeight * 0.5f);
|
||||||
|
break;
|
||||||
|
case LEFT:
|
||||||
|
warning.position = vec2(warningWidth * 0.5f, glm::clamp(entities.back().position.y, warningHeight * 0.5f,
|
||||||
|
(float)canvas.size.y - warningHeight * 0.5f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.emplace_back(std::move(warning));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case Entity::WARNING:
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Orbit::reset(entity::Character& character)
|
||||||
|
{
|
||||||
|
entities.clear();
|
||||||
|
sounds = &character.data.orbitSchema.sounds;
|
||||||
|
cursorPosition = {};
|
||||||
|
centerPosition = {};
|
||||||
|
level = 0;
|
||||||
|
score = 0;
|
||||||
|
isHighScoreAchievedThisRun = false;
|
||||||
|
itemEffectManager = {};
|
||||||
|
followerRadius = {};
|
||||||
|
playerTargetAcceleration = {};
|
||||||
|
followerTargetAcceleration = {};
|
||||||
|
playerTimeAfterHurt = {};
|
||||||
|
enemySpeed = {};
|
||||||
|
enemySpeedScoreBonus = {};
|
||||||
|
enemySpeedGainBase = {};
|
||||||
|
enemySpeedGainScoreBonus = {};
|
||||||
|
rotationSpeed = {};
|
||||||
|
rotationSpeedMax = {};
|
||||||
|
rotationSpeedFriction = {};
|
||||||
|
startTimer = character.data.orbitSchema.startTime;
|
||||||
|
hurtTimer = 0;
|
||||||
|
isPlayerDying = false;
|
||||||
|
isRotateLeft = false;
|
||||||
|
isRotateRight = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Orbit::tick()
|
||||||
|
{
|
||||||
|
for (auto& entity : entities)
|
||||||
|
entity.tick();
|
||||||
|
|
||||||
|
itemEffectManager.tick();
|
||||||
|
canvas.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Orbit::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
|
||||||
|
Text& text, menu::Toasts& toasts)
|
||||||
|
{
|
||||||
|
auto& strings = character.data.strings;
|
||||||
|
auto& schema = character.data.orbitSchema;
|
||||||
|
sounds = &schema.sounds;
|
||||||
|
auto& style = ImGui::GetStyle();
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
auto& textureShader = resources.shaders[resource::shader::TEXTURE];
|
||||||
|
auto& rectShader = resources.shaders[resource::shader::RECT];
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
|
||||||
|
auto bestText = std::vformat(strings.get(Strings::ArcadeBestScoreFormat), std::make_format_args(highScore));
|
||||||
|
auto cursorPos = ImGui::GetCursorPos();
|
||||||
|
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(bestText.c_str()).x,
|
||||||
|
cursorPos.y - ImGui::GetTextLineHeightWithSpacing()));
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeBestScoreFormat).c_str(), highScore);
|
||||||
|
auto padding = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
auto contentRegionAvail = ImGui::GetContentRegionAvail();
|
||||||
|
auto contentRegionPosition = ImGui::GetCursorScreenPos();
|
||||||
|
auto contentBounds = ImVec4(contentRegionPosition.x, contentRegionPosition.y, contentRegionAvail.x, contentRegionAvail.y);
|
||||||
|
auto available =
|
||||||
|
imgui::to_vec2(contentRegionAvail) - vec2(0.0f, ImGui::GetFrameHeightWithSpacing() + style.WindowPadding.y);
|
||||||
|
auto canvasSize = glm::max(vec2(1.0f), available - vec2(padding * 2.0f));
|
||||||
|
auto canvasScreenPosition = imgui::to_vec2(ImGui::GetCursorScreenPos()) + vec2(padding);
|
||||||
|
centerPosition = canvasSize * 0.5f;
|
||||||
|
|
||||||
|
if (isPlayerDying)
|
||||||
|
{
|
||||||
|
Entity* playerEntity = nullptr;
|
||||||
|
for (auto& entity : entities)
|
||||||
|
if (entity.type == Entity::PLAYER)
|
||||||
|
{
|
||||||
|
playerEntity = &entity;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerEntity || playerEntity->state == entity::Actor::STOPPED)
|
||||||
|
{
|
||||||
|
reset(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entities.empty() && startTimer <= 0) startTimer = schema.startTime;
|
||||||
|
|
||||||
|
if (entities.empty()) spawn(character, Entity::PLAYER);
|
||||||
|
|
||||||
|
followerRadius = schema.player.followerRadius;
|
||||||
|
playerTargetAcceleration = schema.player.targetAcceleration;
|
||||||
|
followerTargetAcceleration = schema.follower.targetAcceleration;
|
||||||
|
playerTimeAfterHurt = schema.player.timeAfterHurt;
|
||||||
|
enemySpeed = schema.enemy.speed;
|
||||||
|
enemySpeedScoreBonus = schema.enemy.speedScoreBonus;
|
||||||
|
enemySpeedGainBase = schema.enemy.speedGainBase;
|
||||||
|
enemySpeedGainScoreBonus = schema.enemy.speedGainScoreBonus;
|
||||||
|
rotationSpeed = schema.player.rotationSpeed;
|
||||||
|
rotationSpeedMax = schema.player.rotationSpeedMax;
|
||||||
|
rotationSpeedFriction = schema.player.rotationSpeedFriction;
|
||||||
|
auto nextLevel = std::min(unlocked_level_get(schema, score), (int)schema.colors.size());
|
||||||
|
if (nextLevel > level)
|
||||||
|
{
|
||||||
|
schema.sounds.levelUp.play();
|
||||||
|
|
||||||
|
auto colorIndex = nextLevel - 1;
|
||||||
|
if (colorIndex >= 0 && colorIndex < (int)schema.colors.size())
|
||||||
|
{
|
||||||
|
auto& pool = schema.colors[colorIndex].pool;
|
||||||
|
if (pool.is_valid() && text.is_interruptible()) text.set(character.data.dialogue.get(pool), character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
level = nextLevel;
|
||||||
|
|
||||||
|
auto player_get = [&]() -> Entity*
|
||||||
|
{
|
||||||
|
for (auto& entity : entities)
|
||||||
|
if (entity.type == Entity::PLAYER) return &entity;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto player = player_get();
|
||||||
|
|
||||||
|
if (player)
|
||||||
|
{
|
||||||
|
auto desiredFollowerCount = std::min(level, (int)schema.colors.size());
|
||||||
|
auto currentFollowerCount = (int)std::count_if(entities.begin(), entities.end(), [](const Entity& entity)
|
||||||
|
{ return entity.type == Entity::FOLLOWER; });
|
||||||
|
auto currentEnemyCount = (int)std::count_if(entities.begin(), entities.end(),
|
||||||
|
[](const Entity& entity) { return entity.type == Entity::ENEMY; });
|
||||||
|
|
||||||
|
if (currentFollowerCount != desiredFollowerCount)
|
||||||
|
{
|
||||||
|
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||||
|
[](const Entity& entity) { return entity.type == Entity::FOLLOWER; }),
|
||||||
|
entities.end());
|
||||||
|
|
||||||
|
for (int i = 0; i < desiredFollowerCount; i++)
|
||||||
|
spawn(character, Entity::FOLLOWER, i);
|
||||||
|
|
||||||
|
player = player_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && !schema.colors.empty())
|
||||||
|
{
|
||||||
|
auto colorID = random_available_color_get(schema, level);
|
||||||
|
if (colorID == -1) return false;
|
||||||
|
|
||||||
|
if (currentEnemyCount == 0)
|
||||||
|
{
|
||||||
|
spawn(character, Entity::ENEMY, colorID);
|
||||||
|
player = player_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spawnChance = schema.enemy.spawnChanceBase + schema.enemy.spawnChanceScoreBonus * (float)score;
|
||||||
|
if (math::random_percent_roll(spawnChance))
|
||||||
|
{
|
||||||
|
colorID = random_available_color_get(schema, level);
|
||||||
|
if (colorID == -1) return false;
|
||||||
|
spawn(character, Entity::ENEMY, colorID);
|
||||||
|
player = player_get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mousePosition = imgui::to_vec2(ImGui::GetMousePos());
|
||||||
|
auto canvasBoundsMax = canvasScreenPosition + canvasSize;
|
||||||
|
auto isHoveringCanvas = mousePosition.x >= canvasScreenPosition.x && mousePosition.x <= canvasBoundsMax.x &&
|
||||||
|
mousePosition.y >= canvasScreenPosition.y && mousePosition.y <= canvasBoundsMax.y;
|
||||||
|
|
||||||
|
cursor.isVisible = !isHoveringCanvas;
|
||||||
|
isRotateLeft = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
|
||||||
|
ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||||
|
isRotateRight = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
|
||||||
|
ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||||
|
cursorPosition = glm::clamp(mousePosition - canvasScreenPosition, vec2(0.0f), canvasSize);
|
||||||
|
|
||||||
|
if (player)
|
||||||
|
{
|
||||||
|
if (isPlayerDying || hurtTimer > 0) player->velocity *= 0.85f;
|
||||||
|
|
||||||
|
if (!isPlayerDying && hurtTimer <= 0 && isRotateLeft) player->rotationVelocity -= rotationSpeed;
|
||||||
|
if (!isPlayerDying && hurtTimer <= 0 && isRotateRight) player->rotationVelocity += rotationSpeed;
|
||||||
|
player->rotationVelocity = glm::clamp(player->rotationVelocity, -rotationSpeedMax, rotationSpeedMax);
|
||||||
|
player->rotationVelocity *= rotationSpeedFriction;
|
||||||
|
|
||||||
|
if (!isPlayerDying && hurtTimer <= 0)
|
||||||
|
target_tick(*player, startTimer > 0 ? centerPosition : cursorPosition, playerTargetAcceleration);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entity : entities)
|
||||||
|
{
|
||||||
|
switch (entity.type)
|
||||||
|
{
|
||||||
|
case Entity::FOLLOWER:
|
||||||
|
if (player)
|
||||||
|
{
|
||||||
|
entity.orbitAngle += player->rotationVelocity;
|
||||||
|
|
||||||
|
auto radius = std::max(0.0f, followerRadius);
|
||||||
|
auto target = player->position + vec2(std::cos(entity.orbitAngle), std::sin(entity.orbitAngle)) * radius;
|
||||||
|
target_tick(entity, target, followerTargetAcceleration);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Entity::ENEMY:
|
||||||
|
if (player && !entity.isMarkedForRemoval)
|
||||||
|
{
|
||||||
|
auto delta = player->position - entity.position;
|
||||||
|
auto distance = glm::length(delta);
|
||||||
|
auto speed = (enemySpeed + enemySpeedScoreBonus * (float)score) +
|
||||||
|
(enemySpeedGainBase + enemySpeedGainScoreBonus * (float)score);
|
||||||
|
if (distance > 0.001f) entity.position += glm::normalize(delta) * speed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& follower : entities)
|
||||||
|
{
|
||||||
|
if (isPlayerDying) break;
|
||||||
|
if (follower.type != Entity::FOLLOWER) continue;
|
||||||
|
if (follower.hitboxNull.empty() || !follower.nullMap.contains(follower.hitboxNull)) continue;
|
||||||
|
|
||||||
|
auto followerRect = follower.null_frame_rect(follower.nullMap.at(follower.hitboxNull));
|
||||||
|
if (std::isnan(followerRect.x)) continue;
|
||||||
|
|
||||||
|
for (auto& enemy : entities)
|
||||||
|
{
|
||||||
|
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
|
||||||
|
if (enemy.colorID != follower.colorID) continue;
|
||||||
|
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
|
||||||
|
|
||||||
|
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
|
||||||
|
if (std::isnan(enemyRect.x)) continue;
|
||||||
|
if (!is_rect_overlapping(followerRect, enemyRect)) continue;
|
||||||
|
|
||||||
|
enemy.isMarkedForRemoval = true;
|
||||||
|
if (!enemy.animationDeath.empty())
|
||||||
|
enemy.play(enemy.animationDeath, entity::Actor::PLAY_FORCE);
|
||||||
|
else
|
||||||
|
enemy.state = entity::Actor::STOPPED;
|
||||||
|
|
||||||
|
spawn_animation_play(follower);
|
||||||
|
score++;
|
||||||
|
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
|
||||||
|
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score);
|
||||||
|
itemRewards.reward_random_items_try(inventory, itemEffectManager, character.data.itemSchema, contentBounds,
|
||||||
|
rewardChance, rewardRollCount, menu::ItemEffectManager::SHOOT_UP);
|
||||||
|
if (score > highScore)
|
||||||
|
{
|
||||||
|
auto previousHighScore = highScore;
|
||||||
|
highScore = score;
|
||||||
|
|
||||||
|
if (!isHighScoreAchievedThisRun)
|
||||||
|
{
|
||||||
|
isHighScoreAchievedThisRun = true;
|
||||||
|
schema.sounds.highScore.play();
|
||||||
|
|
||||||
|
if (previousHighScore > 0)
|
||||||
|
{
|
||||||
|
auto toastText = strings.get(Strings::ArcadeHighScoreToast);
|
||||||
|
auto toastPosition = imgui::to_imvec2(
|
||||||
|
canvasScreenPosition + player->position -
|
||||||
|
vec2(ImGui::CalcTextSize(toastText.c_str()).x * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 2.0f));
|
||||||
|
toasts.spawn(toastText, toastPosition, 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player && !isPlayerDying && hurtTimer <= 0 && !player->hitboxNull.empty() &&
|
||||||
|
player->nullMap.contains(player->hitboxNull))
|
||||||
|
{
|
||||||
|
auto playerRect = player->null_frame_rect(player->nullMap.at(player->hitboxNull));
|
||||||
|
|
||||||
|
if (!std::isnan(playerRect.x))
|
||||||
|
{
|
||||||
|
auto isHit = false;
|
||||||
|
|
||||||
|
for (auto& enemy : entities)
|
||||||
|
{
|
||||||
|
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
|
||||||
|
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
|
||||||
|
|
||||||
|
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
|
||||||
|
if (std::isnan(enemyRect.x)) continue;
|
||||||
|
if (!is_rect_overlapping(playerRect, enemyRect)) continue;
|
||||||
|
|
||||||
|
isHit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHit)
|
||||||
|
{
|
||||||
|
if (sounds) sounds->hurt.play();
|
||||||
|
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
||||||
|
if (schema.poolDeath.is_valid() && text.is_interruptible())
|
||||||
|
text.set(character.data.dialogue.get(schema.poolDeath), character);
|
||||||
|
hurtTimer = playerTimeAfterHurt;
|
||||||
|
isPlayerDying = true;
|
||||||
|
player->velocity = {};
|
||||||
|
player->rotationVelocity = 0.0f;
|
||||||
|
if (!player->animationDeath.empty())
|
||||||
|
player->play(player->animationDeath, entity::Actor::PLAY_FORCE);
|
||||||
|
else
|
||||||
|
player->state = entity::Actor::STOPPED;
|
||||||
|
|
||||||
|
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||||
|
[](const Entity& entity)
|
||||||
|
{
|
||||||
|
return entity.type == Entity::ENEMY || entity.type == Entity::WARNING ||
|
||||||
|
entity.type == Entity::FOLLOWER;
|
||||||
|
}),
|
||||||
|
entities.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||||
|
[](const Entity& entity)
|
||||||
|
{
|
||||||
|
return (entity.type == Entity::WARNING && entity.state == entity::Actor::STOPPED) ||
|
||||||
|
(entity.type == Entity::ENEMY && entity.isMarkedForRemoval &&
|
||||||
|
entity.state == entity::Actor::STOPPED);
|
||||||
|
}),
|
||||||
|
entities.end());
|
||||||
|
|
||||||
|
if (startTimer > 0) startTimer--;
|
||||||
|
if (hurtTimer > 0) hurtTimer--;
|
||||||
|
|
||||||
|
canvas.bind();
|
||||||
|
canvas.size_set(ivec2(canvasSize));
|
||||||
|
canvas.clear(color::BLACK);
|
||||||
|
|
||||||
|
for (auto& entity : entities)
|
||||||
|
if (entity.type == Entity::PLAYER || entity.type == Entity::FOLLOWER || entity.type == Entity::ENEMY ||
|
||||||
|
entity.type == Entity::WARNING)
|
||||||
|
entity.render(textureShader, rectShader, canvas);
|
||||||
|
|
||||||
|
canvas.unbind();
|
||||||
|
|
||||||
|
ImGui::Dummy(ImVec2(0, padding));
|
||||||
|
ImGui::SetCursorScreenPos(imgui::to_imvec2(canvasScreenPosition));
|
||||||
|
ImGui::Image(canvas.texture, imgui::to_imvec2(canvasSize));
|
||||||
|
itemEffectManager.render(resources, character.data.itemSchema, contentBounds, ImGui::GetIO().DeltaTime);
|
||||||
|
ImGui::Dummy(ImVec2(0, padding));
|
||||||
|
toasts.update(drawList);
|
||||||
|
|
||||||
|
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
|
||||||
|
return isMenuPressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/state/play/menu/arcade/orbit.hpp
Normal file
80
src/state/play/menu/arcade/orbit.hpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../../../entity/actor.hpp"
|
||||||
|
#include "../../../../entity/cursor.hpp"
|
||||||
|
#include "../../../../resources.hpp"
|
||||||
|
|
||||||
|
#include "../../../../util/color.hpp"
|
||||||
|
#include "../inventory.hpp"
|
||||||
|
#include "../item_effect_manager.hpp"
|
||||||
|
#include "../../text.hpp"
|
||||||
|
#include "../toasts.hpp"
|
||||||
|
#include "../../item/reward.hpp"
|
||||||
|
|
||||||
|
namespace game::state::play::menu::arcade
|
||||||
|
{
|
||||||
|
class Orbit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Entity : public entity::Actor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
PLAYER,
|
||||||
|
FOLLOWER,
|
||||||
|
ENEMY,
|
||||||
|
WARNING
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type{PLAYER};
|
||||||
|
glm::vec2 velocity{};
|
||||||
|
float rotationVelocity{};
|
||||||
|
std::string animationIdle{};
|
||||||
|
std::string animationSpawn{};
|
||||||
|
std::string animationDeath{};
|
||||||
|
std::string hitboxNull{"Hitbox"};
|
||||||
|
bool isMarkedForRemoval{};
|
||||||
|
int health{3};
|
||||||
|
int colorID{};
|
||||||
|
float orbitAngle{};
|
||||||
|
|
||||||
|
Entity() = default;
|
||||||
|
Entity(resource::xml::Anm2 anm2, Type type = PLAYER) : entity::Actor(std::move(anm2)), type(type) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Entity> entities{};
|
||||||
|
resource::xml::Orbit::Sounds* sounds{};
|
||||||
|
Canvas canvas{{1, 1}};
|
||||||
|
glm::vec2 cursorPosition{};
|
||||||
|
glm::vec2 centerPosition{};
|
||||||
|
int level{1};
|
||||||
|
int score{};
|
||||||
|
int highScore{};
|
||||||
|
bool isHighScoreAchievedThisRun{};
|
||||||
|
menu::ItemEffectManager itemEffectManager{};
|
||||||
|
game::state::play::item::Reward itemRewards{};
|
||||||
|
float followerRadius{};
|
||||||
|
float playerTargetAcceleration{};
|
||||||
|
float followerTargetAcceleration{};
|
||||||
|
int playerTimeAfterHurt{};
|
||||||
|
float enemySpeed{};
|
||||||
|
float enemySpeedScoreBonus{};
|
||||||
|
float enemySpeedGainBase{};
|
||||||
|
float enemySpeedGainScoreBonus{};
|
||||||
|
float rotationSpeed{};
|
||||||
|
float rotationSpeedMax{};
|
||||||
|
float rotationSpeedFriction{};
|
||||||
|
int startTimer{};
|
||||||
|
int hurtTimer{};
|
||||||
|
bool isPlayerDying{};
|
||||||
|
bool isRotateLeft{};
|
||||||
|
bool isRotateRight{};
|
||||||
|
|
||||||
|
Orbit() = default;
|
||||||
|
void reset(entity::Character&);
|
||||||
|
void tick();
|
||||||
|
void spawn(entity::Character&, Entity::Type, int colorID = -1);
|
||||||
|
bool update(Resources&, entity::Character&, entity::Cursor&, Inventory& inventory, Text& text, menu::Toasts&);
|
||||||
|
};
|
||||||
|
}
|
||||||
339
src/state/play/menu/arcade/skill_check.cpp
Normal file
339
src/state/play/menu/arcade/skill_check.cpp
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
#include "skill_check.hpp"
|
||||||
|
|
||||||
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
#include "../../../../util/imgui.hpp"
|
||||||
|
#include "../../../../util/imgui/widget.hpp"
|
||||||
|
#include "../../../../util/math.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
using namespace game::util;
|
||||||
|
using namespace game::entity;
|
||||||
|
using namespace game::resource;
|
||||||
|
using namespace game::resource::xml;
|
||||||
|
using namespace glm;
|
||||||
|
|
||||||
|
namespace game::state::play::menu::arcade
|
||||||
|
{
|
||||||
|
float SkillCheck::accuracy_score_get(entity::Character& character)
|
||||||
|
{
|
||||||
|
if (totalPlays == 0) return 0.0f;
|
||||||
|
|
||||||
|
auto& schema = character.data.skillCheckSchema;
|
||||||
|
|
||||||
|
float combinedWeight{};
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)schema.grades.size(); i++)
|
||||||
|
{
|
||||||
|
auto& grade = schema.grades[i];
|
||||||
|
combinedWeight += gradeCounts[i] * grade.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return glm::clamp(0.0f, math::to_percent(combinedWeight / totalPlays), 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkillCheck::Challenge SkillCheck::challenge_generate(entity::Character& character)
|
||||||
|
{
|
||||||
|
auto& schema = character.data.skillCheckSchema;
|
||||||
|
|
||||||
|
Challenge newChallenge;
|
||||||
|
|
||||||
|
Zone newZone{};
|
||||||
|
|
||||||
|
auto zoneSize = std::max(schema.zoneMin, schema.zoneBase - (schema.zoneScoreBonus * score));
|
||||||
|
newZone.min = math::random_max(1.0f - zoneSize);
|
||||||
|
newZone.max = newZone.min + zoneSize;
|
||||||
|
|
||||||
|
newChallenge.zone = newZone;
|
||||||
|
newChallenge.tryValue = 0.0f;
|
||||||
|
|
||||||
|
newChallenge.speed =
|
||||||
|
glm::clamp(schema.speedMin, schema.speedMin + (schema.speedScoreBonus * score), schema.speedMax);
|
||||||
|
|
||||||
|
if (math::random_bool())
|
||||||
|
{
|
||||||
|
newChallenge.tryValue = 1.0f;
|
||||||
|
newChallenge.speed *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkillCheck::SkillCheck(entity::Character& character) { challenge = challenge_generate(character); }
|
||||||
|
|
||||||
|
void SkillCheck::reset(entity::Character& character)
|
||||||
|
{
|
||||||
|
challenge = challenge_generate(character);
|
||||||
|
queuedChallenge = {};
|
||||||
|
tryValue = challenge.tryValue;
|
||||||
|
score = 0;
|
||||||
|
combo = 0;
|
||||||
|
endTimer = 0;
|
||||||
|
endTimerMax = 0;
|
||||||
|
highScoreStart = 0;
|
||||||
|
isActive = true;
|
||||||
|
isRewardScoreAchieved = false;
|
||||||
|
isHighScoreAchieved = highScore > 0;
|
||||||
|
isHighScoreAchievedThisRun = false;
|
||||||
|
isGameOver = false;
|
||||||
|
itemEffectManager = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkillCheck::tick() { itemEffectManager.tick(); }
|
||||||
|
|
||||||
|
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text,
|
||||||
|
Toasts& toasts)
|
||||||
|
{
|
||||||
|
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
|
||||||
|
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
|
||||||
|
static constexpr ImVec4 PERFECT_COLOR = ImVec4(1, 1, 1, 0.50);
|
||||||
|
static constexpr auto BAR_SPACING_MULTIPLIER = 1.5f;
|
||||||
|
static constexpr auto LINE_HEIGHT = 5.0f;
|
||||||
|
static constexpr auto LINE_WIDTH_BONUS = 10.0f;
|
||||||
|
auto& dialogue = character.data.dialogue;
|
||||||
|
auto& schema = character.data.skillCheckSchema;
|
||||||
|
auto& itemSchema = character.data.itemSchema;
|
||||||
|
auto& strings = character.data.strings;
|
||||||
|
auto& style = ImGui::GetStyle();
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
auto position = ImGui::GetCursorScreenPos();
|
||||||
|
auto size = ImGui::GetContentRegionAvail();
|
||||||
|
auto spacing = ImGui::GetTextLineHeightWithSpacing() * BAR_SPACING_MULTIPLIER;
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||||
|
size.y = std::max(0.0f, size.y - menuButtonHeight);
|
||||||
|
auto bounds = ImVec4(position.x, position.y, size.x, size.y);
|
||||||
|
|
||||||
|
auto cursorPos = ImGui::GetCursorPos();
|
||||||
|
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeScoreComboFormat).c_str(), score, combo);
|
||||||
|
auto bestString =
|
||||||
|
std::vformat(strings.get(Strings::ArcadeBestScoreComboFormat), std::make_format_args(highScore, bestCombo));
|
||||||
|
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
|
||||||
|
|
||||||
|
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), highScore, bestCombo);
|
||||||
|
|
||||||
|
if (score == 0 && isActive)
|
||||||
|
{
|
||||||
|
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
|
||||||
|
ImGui::TextWrapped("%s", strings.get(Strings::SkillCheckInstructions).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto barMin = ImVec2(position.x + (size.x * 0.5f) - (spacing * 0.5f), position.y + (spacing * 2.0f));
|
||||||
|
auto barMax = ImVec2(barMin.x + (spacing * 2.0f), barMin.y + size.y - (spacing * 4.0f));
|
||||||
|
auto endTimerProgress = (float)endTimer / endTimerMax;
|
||||||
|
|
||||||
|
auto bgColor = ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
|
||||||
|
bgColor = imgui::to_imvec4(imgui::to_vec4(bgColor) * BG_COLOR_MULTIPLIER);
|
||||||
|
drawList->AddRectFilled(barMin, barMax, ImGui::GetColorU32(bgColor));
|
||||||
|
|
||||||
|
auto barWidth = barMax.x - barMin.x;
|
||||||
|
auto barHeight = barMax.y - barMin.y;
|
||||||
|
|
||||||
|
auto sub_zones_get = [&](Zone& zone)
|
||||||
|
{
|
||||||
|
auto& min = zone.min;
|
||||||
|
auto& max = zone.max;
|
||||||
|
std::vector<Zone> zones{};
|
||||||
|
|
||||||
|
auto baseHeight = max - min;
|
||||||
|
auto center = (min + max) * 0.5f;
|
||||||
|
|
||||||
|
int zoneCount{};
|
||||||
|
|
||||||
|
for (auto& grade : schema.grades)
|
||||||
|
{
|
||||||
|
if (grade.isFailure) continue;
|
||||||
|
|
||||||
|
auto scale = powf(0.5f, (float)zoneCount);
|
||||||
|
auto halfHeight = baseHeight * scale * 0.5f;
|
||||||
|
|
||||||
|
zoneCount++;
|
||||||
|
|
||||||
|
zones.push_back({center - halfHeight, center + halfHeight});
|
||||||
|
}
|
||||||
|
|
||||||
|
return zones;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto zone_draw = [&](Zone& zone, float alpha = 1.0f)
|
||||||
|
{
|
||||||
|
auto subZones = sub_zones_get(zone);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)subZones.size(); i++)
|
||||||
|
{
|
||||||
|
auto& subZone = subZones[i];
|
||||||
|
int layer = (int)subZones.size() - 1 - i;
|
||||||
|
|
||||||
|
ImVec2 rectMin = {barMin.x, barMin.y + subZone.min * barHeight};
|
||||||
|
|
||||||
|
ImVec2 rectMax = {barMax.x, barMin.y + subZone.max * barHeight};
|
||||||
|
|
||||||
|
ImVec4 color =
|
||||||
|
i == (int)subZones.size() - 1 ? PERFECT_COLOR : ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered);
|
||||||
|
color.w = (color.w - (float)layer / subZones.size()) * alpha;
|
||||||
|
|
||||||
|
drawList->AddRectFilled(rectMin, rectMax, ImGui::GetColorU32(color));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
zone_draw(challenge.zone, isActive ? 1.0f : 0.0f);
|
||||||
|
|
||||||
|
auto lineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * tryValue));
|
||||||
|
auto lineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, lineMin.y + LINE_HEIGHT);
|
||||||
|
auto lineColor = LINE_COLOR;
|
||||||
|
lineColor.w = isActive ? 1.0f : endTimerProgress;
|
||||||
|
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(lineColor));
|
||||||
|
|
||||||
|
if (!isActive && !isGameOver)
|
||||||
|
{
|
||||||
|
zone_draw(queuedChallenge.zone, 1.0f - endTimerProgress);
|
||||||
|
|
||||||
|
auto queuedLineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * queuedChallenge.tryValue));
|
||||||
|
auto queuedLineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, queuedLineMin.y + LINE_HEIGHT);
|
||||||
|
auto queuedLineColor = LINE_COLOR;
|
||||||
|
queuedLineColor.w = 1.0f - endTimerProgress;
|
||||||
|
drawList->AddRectFilled(queuedLineMin, queuedLineMax, ImGui::GetColorU32(queuedLineColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActive)
|
||||||
|
{
|
||||||
|
tryValue += challenge.speed;
|
||||||
|
|
||||||
|
if (tryValue > 1.0f || tryValue < 0.0f)
|
||||||
|
{
|
||||||
|
tryValue = tryValue > 1.0f ? 0.0f : tryValue < 0.0f ? 1.0f : tryValue;
|
||||||
|
|
||||||
|
if (score > 0)
|
||||||
|
{
|
||||||
|
score--;
|
||||||
|
schema.sounds.scoreLoss.play();
|
||||||
|
auto toastMessagePosition =
|
||||||
|
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeScoreLoss).c_str()).x -
|
||||||
|
ImGui::GetTextLineHeightWithSpacing(),
|
||||||
|
lineMin.y);
|
||||||
|
toasts.spawn(strings.get(Strings::ArcadeScoreLoss), toastMessagePosition, schema.endTimerMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(barMin);
|
||||||
|
auto barButtonSize = ImVec2(barMax.x - barMin.x, barMax.y - barMin.y);
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Space) ||
|
||||||
|
WIDGET_FX(ImGui::InvisibleButton("##SkillCheckBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
|
||||||
|
{
|
||||||
|
int gradeID{};
|
||||||
|
|
||||||
|
auto subZones = sub_zones_get(challenge.zone);
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)subZones.size(); i++)
|
||||||
|
{
|
||||||
|
auto& subZone = subZones[i];
|
||||||
|
|
||||||
|
if (tryValue >= subZone.min && tryValue <= subZone.max)
|
||||||
|
gradeID = std::min((int)gradeID + 1, (int)schema.grades.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gradeCounts[gradeID]++;
|
||||||
|
totalPlays++;
|
||||||
|
|
||||||
|
auto& grade = schema.grades.at(gradeID);
|
||||||
|
grade.sound.play();
|
||||||
|
|
||||||
|
if (text.is_interruptible() && grade.pool.is_valid()) text.set(dialogue.get(grade.pool), character);
|
||||||
|
|
||||||
|
if (!grade.isFailure)
|
||||||
|
{
|
||||||
|
combo++;
|
||||||
|
score += grade.value;
|
||||||
|
|
||||||
|
if (score >= schema.rewardScore && !isRewardScoreAchieved)
|
||||||
|
{
|
||||||
|
schema.sounds.rewardScore.play();
|
||||||
|
isRewardScoreAchieved = true;
|
||||||
|
|
||||||
|
for (auto& itemID : itemSchema.skillCheckRewardItemPool)
|
||||||
|
itemRewards.item_give(itemID, inventory, itemEffectManager, itemSchema, bounds);
|
||||||
|
|
||||||
|
auto toastMessagePosition =
|
||||||
|
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeRewardToast).c_str()).x -
|
||||||
|
ImGui::GetTextLineHeightWithSpacing(),
|
||||||
|
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
|
||||||
|
toasts.spawn(strings.get(Strings::ArcadeRewardToast), toastMessagePosition, schema.endTimerMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > highScore)
|
||||||
|
{
|
||||||
|
highScore = score;
|
||||||
|
|
||||||
|
if (isHighScoreAchieved && !isHighScoreAchievedThisRun)
|
||||||
|
{
|
||||||
|
isHighScoreAchievedThisRun = true;
|
||||||
|
schema.sounds.highScore.play();
|
||||||
|
auto toastMessagePosition =
|
||||||
|
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeHighScoreToast).c_str()).x -
|
||||||
|
ImGui::GetTextLineHeightWithSpacing(),
|
||||||
|
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
|
||||||
|
toasts.spawn(strings.get(Strings::ArcadeHighScoreToast), toastMessagePosition, schema.endTimerMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combo > bestCombo) bestCombo = combo;
|
||||||
|
|
||||||
|
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
|
||||||
|
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score) +
|
||||||
|
(schema.rewardRollGradeBonus * grade.value);
|
||||||
|
itemRewards.reward_random_items_try(inventory, itemEffectManager, itemSchema, bounds, rewardChance,
|
||||||
|
rewardRollCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
score = 0;
|
||||||
|
combo = 0;
|
||||||
|
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
||||||
|
if (highScore > 0) isHighScoreAchieved = true;
|
||||||
|
isRewardScoreAchieved = false;
|
||||||
|
isHighScoreAchievedThisRun = false;
|
||||||
|
highScoreStart = highScore;
|
||||||
|
isGameOver = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
endTimerMax = grade.isFailure ? schema.endTimerFailureMax : schema.endTimerMax;
|
||||||
|
isActive = false;
|
||||||
|
endTimer = endTimerMax;
|
||||||
|
|
||||||
|
queuedChallenge = challenge_generate(character);
|
||||||
|
|
||||||
|
auto string = grade.isFailure ? grade.name
|
||||||
|
: std::vformat(strings.get(Strings::SkillCheckGradeSuccessTemplate),
|
||||||
|
std::make_format_args(grade.name, grade.value));
|
||||||
|
auto toastMessagePosition =
|
||||||
|
ImVec2(barMin.x - ImGui::CalcTextSize(string.c_str()).x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
||||||
|
toasts.spawn(string, toastMessagePosition, endTimerMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endTimer--;
|
||||||
|
if (endTimer <= 0)
|
||||||
|
{
|
||||||
|
challenge = queuedChallenge;
|
||||||
|
tryValue = challenge.tryValue;
|
||||||
|
isActive = true;
|
||||||
|
isGameOver = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toasts.update(drawList);
|
||||||
|
|
||||||
|
itemEffectManager.render(resources, itemSchema, bounds, io.DeltaTime);
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
|
||||||
|
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
|
||||||
|
return isMenuPressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../../../render/canvas.hpp"
|
#include "../item_effect_manager.hpp"
|
||||||
#include "../../../entity/actor.hpp"
|
#include "../../item/reward.hpp"
|
||||||
#include "../../../entity/character.hpp"
|
#include "../toasts.hpp"
|
||||||
#include "../../../resources.hpp"
|
|
||||||
|
#include "../../../../entity/character.hpp"
|
||||||
|
#include "../../../../resources.hpp"
|
||||||
|
|
||||||
#include "../inventory.hpp"
|
#include "../inventory.hpp"
|
||||||
#include "../text.hpp"
|
#include "../../text.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play::menu::arcade
|
||||||
{
|
{
|
||||||
class SkillCheck
|
class SkillCheck
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Range
|
struct Zone
|
||||||
{
|
{
|
||||||
float min{};
|
float min{};
|
||||||
float max{};
|
float max{};
|
||||||
@@ -27,27 +28,12 @@ namespace game::state::play
|
|||||||
|
|
||||||
struct Challenge
|
struct Challenge
|
||||||
{
|
{
|
||||||
Range range{};
|
Zone zone{};
|
||||||
float speed{};
|
float speed{};
|
||||||
float tryValue{};
|
float tryValue{};
|
||||||
int level{};
|
int level{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Toast
|
|
||||||
{
|
|
||||||
std::string message{};
|
|
||||||
ImVec2 position;
|
|
||||||
int time{};
|
|
||||||
int timeMax{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Item
|
|
||||||
{
|
|
||||||
int id{-1};
|
|
||||||
ImVec2 position{};
|
|
||||||
float velocity{};
|
|
||||||
};
|
|
||||||
|
|
||||||
Challenge challenge{};
|
Challenge challenge{};
|
||||||
Challenge queuedChallenge{};
|
Challenge queuedChallenge{};
|
||||||
float tryValue{};
|
float tryValue{};
|
||||||
@@ -71,17 +57,15 @@ namespace game::state::play
|
|||||||
bool isHighScoreAchievedThisRun{false};
|
bool isHighScoreAchievedThisRun{false};
|
||||||
bool isGameOver{};
|
bool isGameOver{};
|
||||||
|
|
||||||
std::vector<Toast> toasts{};
|
game::state::play::menu::ItemEffectManager itemEffectManager{};
|
||||||
std::vector<Item> items{};
|
game::state::play::item::Reward itemRewards{};
|
||||||
std::unordered_map<int, entity::Actor> itemActors{};
|
|
||||||
std::unordered_map<int, glm::vec4> itemRects{};
|
|
||||||
std::unordered_map<int, Canvas> itemCanvases{};
|
|
||||||
|
|
||||||
SkillCheck() = default;
|
SkillCheck() = default;
|
||||||
SkillCheck(entity::Character&);
|
SkillCheck(entity::Character&);
|
||||||
Challenge challenge_generate(entity::Character&);
|
Challenge challenge_generate(entity::Character&);
|
||||||
|
void reset(entity::Character&);
|
||||||
void tick();
|
void tick();
|
||||||
bool update(Resources&, entity::Character&, Inventory&, Text&);
|
bool update(Resources&, entity::Character&, Inventory&, Text&, Toasts&);
|
||||||
float accuracy_score_get(entity::Character&);
|
float accuracy_score_get(entity::Character&);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "interact.hpp"
|
#include "interact.hpp"
|
||||||
|
|
||||||
#include "../../util/imgui/widget.hpp"
|
#include "../../../util/imgui/widget.hpp"
|
||||||
#include "../../util/measurement.hpp"
|
#include "../../../util/measurement.hpp"
|
||||||
|
|
||||||
using namespace game::resource;
|
using namespace game::resource;
|
||||||
using namespace game::resource::xml;
|
using namespace game::resource::xml;
|
||||||
using namespace game::util;
|
using namespace game::util;
|
||||||
using namespace game::util::imgui;
|
using namespace game::util::imgui;
|
||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play::menu
|
||||||
{
|
{
|
||||||
void Interact::update(Resources& resources, Text& text, entity::Character& character)
|
void Interact::update(Resources& resources, Text& text, entity::Character& character)
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "text.hpp"
|
#include "../text.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play::menu
|
||||||
{
|
{
|
||||||
class Interact
|
class Interact
|
||||||
{
|
{
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
#include "inventory.hpp"
|
#include "inventory.hpp"
|
||||||
#include "style.hpp"
|
#include "../style.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include "../../util/color.hpp"
|
#include "../../../util/color.hpp"
|
||||||
#include "../../util/imgui.hpp"
|
#include "../../../util/imgui.hpp"
|
||||||
#include "../../util/imgui/style.hpp"
|
#include "../../../util/imgui/style.hpp"
|
||||||
#include "../../util/imgui/widget.hpp"
|
#include "../../../util/imgui/widget.hpp"
|
||||||
#include "../../util/math.hpp"
|
#include "../../../util/math.hpp"
|
||||||
|
|
||||||
using namespace game::util;
|
using namespace game::util;
|
||||||
using namespace game::util::imgui;
|
using namespace game::util::imgui;
|
||||||
@@ -18,7 +18,7 @@ using namespace game::entity;
|
|||||||
using namespace game::resource;
|
using namespace game::resource;
|
||||||
using namespace glm;
|
using namespace glm;
|
||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play::menu
|
||||||
{
|
{
|
||||||
using Strings = resource::xml::Strings;
|
using Strings = resource::xml::Strings;
|
||||||
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../../entity/character.hpp"
|
#include "../../../entity/character.hpp"
|
||||||
|
|
||||||
#include "../../resources.hpp"
|
#include "../../../resources.hpp"
|
||||||
|
|
||||||
#include "item_manager.hpp"
|
#include "../item_manager.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
namespace game::state::play
|
namespace game::state::play::menu
|
||||||
{
|
{
|
||||||
class Inventory
|
class Inventory
|
||||||
{
|
{
|
||||||
138
src/state/play/menu/item_effect_manager.cpp
Normal file
138
src/state/play/menu/item_effect_manager.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include "item_effect_manager.hpp"
|
||||||
|
|
||||||
|
#include "../../../util/math.hpp"
|
||||||
|
|
||||||
|
using namespace game::util;
|
||||||
|
using namespace game::resource;
|
||||||
|
using namespace glm;
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
void ItemEffectManager::tick()
|
||||||
|
{
|
||||||
|
for (auto& [i, actor] : actors)
|
||||||
|
actor.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemEffectManager::spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode)
|
||||||
|
{
|
||||||
|
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MIN = -250.0f;
|
||||||
|
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MAX = 250.0f;
|
||||||
|
static constexpr auto ITEM_SHOOT_UP_VERTICAL_SPEED_MIN = 500.0f;
|
||||||
|
static constexpr auto ITEM_SHOOT_UP_VERTICAL_SPEED_MAX = 1000.0f;
|
||||||
|
static constexpr auto ITEM_ROTATION_VELOCITY_MIN = -45.0f;
|
||||||
|
static constexpr auto ITEM_ROTATION_VELOCITY_MAX = 45.0f;
|
||||||
|
|
||||||
|
if (!actors.contains(itemID))
|
||||||
|
{
|
||||||
|
actors[itemID] = entity::Actor(itemSchema.anm2s[itemID], {}, entity::Actor::SET);
|
||||||
|
rects[itemID] = actors[itemID].rect();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = ImVec2(bounds.z, bounds.w);
|
||||||
|
auto rect = rects[itemID];
|
||||||
|
auto rectSize = vec2(rect.z, rect.w);
|
||||||
|
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
||||||
|
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||||
|
? 0.0f
|
||||||
|
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||||
|
previewScale = std::min(1.0f, previewScale);
|
||||||
|
auto previewSize = rectSize * previewScale;
|
||||||
|
auto minX = 0.0f;
|
||||||
|
auto maxX = size.x - previewSize.x;
|
||||||
|
auto spawnX = minX >= maxX ? 0.0f : math::random_in_range(minX, maxX);
|
||||||
|
auto rotationVelocity = math::random_in_range(ITEM_ROTATION_VELOCITY_MIN, ITEM_ROTATION_VELOCITY_MAX);
|
||||||
|
|
||||||
|
Entry entry{};
|
||||||
|
entry.id = itemID;
|
||||||
|
entry.mode = mode;
|
||||||
|
entry.rotationVelocity = rotationVelocity;
|
||||||
|
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case SHOOT_UP:
|
||||||
|
entry.position = ImVec2(spawnX, std::max(0.0f, size.y - previewSize.y));
|
||||||
|
entry.velocity.x =
|
||||||
|
math::random_in_range(ITEM_SHOOT_UP_HORIZONTAL_SPEED_MIN, ITEM_SHOOT_UP_HORIZONTAL_SPEED_MAX);
|
||||||
|
entry.velocity.y = -math::random_in_range(ITEM_SHOOT_UP_VERTICAL_SPEED_MIN, ITEM_SHOOT_UP_VERTICAL_SPEED_MAX);
|
||||||
|
break;
|
||||||
|
case FALL_DOWN:
|
||||||
|
default:
|
||||||
|
entry.position = ImVec2(spawnX, -previewSize.y - math::random_in_range(0.0f, size.y));
|
||||||
|
entry.velocity = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.emplace_back(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemEffectManager::render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds,
|
||||||
|
float deltaTime)
|
||||||
|
{
|
||||||
|
static constexpr auto ITEM_FALL_GRAVITY = 2400.0f;
|
||||||
|
auto position = ImVec2(bounds.x, bounds.y);
|
||||||
|
auto size = ImVec2(bounds.z, bounds.w);
|
||||||
|
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
auto windowMin = position;
|
||||||
|
auto windowMax = ImVec2(position.x + size.x, position.y + size.y);
|
||||||
|
|
||||||
|
ImGui::PushClipRect(windowMin, windowMax, true);
|
||||||
|
for (int i = 0; i < (int)entries.size(); i++)
|
||||||
|
{
|
||||||
|
auto& item = entries[i];
|
||||||
|
if (!actors.contains(item.id))
|
||||||
|
{
|
||||||
|
entries.erase(entries.begin() + i--);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rect = rects[item.id];
|
||||||
|
auto rectSize = vec2(rect.z, rect.w);
|
||||||
|
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
||||||
|
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||||
|
? 0.0f
|
||||||
|
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||||
|
previewScale = std::min(1.0f, previewScale);
|
||||||
|
auto previewSize = rectSize * previewScale;
|
||||||
|
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||||
|
|
||||||
|
if (!canvases.contains(item.id)) canvases.emplace(item.id, Canvas(canvasSize, Canvas::FLIP));
|
||||||
|
auto& canvas = canvases[item.id];
|
||||||
|
canvas.zoom = math::to_percent(previewScale);
|
||||||
|
canvas.pan = vec2(rect.x, rect.y);
|
||||||
|
canvas.bind();
|
||||||
|
canvas.size_set(canvasSize);
|
||||||
|
canvas.clear();
|
||||||
|
|
||||||
|
actors[item.id].overrides.emplace_back(-1, resource::xml::Anm2::ROOT, entity::Actor::Override::SET,
|
||||||
|
resource::xml::Anm2::FrameOptional{.rotation = item.rotation});
|
||||||
|
actors[item.id].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||||
|
actors[item.id].overrides.pop_back();
|
||||||
|
canvas.unbind();
|
||||||
|
|
||||||
|
auto min = ImVec2(position.x + item.position.x, position.y + item.position.y);
|
||||||
|
auto max = ImVec2(item.position.x + previewSize.x, item.position.y + previewSize.y);
|
||||||
|
max.x += position.x;
|
||||||
|
max.y += position.y;
|
||||||
|
drawList->AddImage(canvas.texture, min, max);
|
||||||
|
|
||||||
|
item.rotation += item.rotationVelocity * deltaTime;
|
||||||
|
item.position.x += item.velocity.x * deltaTime;
|
||||||
|
item.position.y += item.velocity.y * deltaTime;
|
||||||
|
|
||||||
|
switch (item.mode)
|
||||||
|
{
|
||||||
|
case SHOOT_UP:
|
||||||
|
case FALL_DOWN:
|
||||||
|
default:
|
||||||
|
item.velocity.y += ITEM_FALL_GRAVITY * deltaTime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.position.y > size.y || item.position.x < -previewSize.x || item.position.x > size.x + previewSize.x)
|
||||||
|
entries.erase(entries.begin() + i--);
|
||||||
|
}
|
||||||
|
ImGui::PopClipRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/state/play/menu/item_effect_manager.hpp
Normal file
41
src/state/play/menu/item_effect_manager.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../../render/canvas.hpp"
|
||||||
|
#include "../../../entity/actor.hpp"
|
||||||
|
#include "../../../resources.hpp"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
class ItemEffectManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Mode
|
||||||
|
{
|
||||||
|
FALL_DOWN,
|
||||||
|
SHOOT_UP
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
int id{-1};
|
||||||
|
Mode mode{FALL_DOWN};
|
||||||
|
ImVec2 position{};
|
||||||
|
ImVec2 velocity{};
|
||||||
|
float rotation{};
|
||||||
|
float rotationVelocity{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Entry> entries{};
|
||||||
|
std::unordered_map<int, entity::Actor> actors{};
|
||||||
|
std::unordered_map<int, glm::vec4> rects{};
|
||||||
|
std::unordered_map<int, Canvas> canvases{};
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
void spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode = FALL_DOWN);
|
||||||
|
void render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds, float deltaTime);
|
||||||
|
};
|
||||||
|
}
|
||||||
31
src/state/play/menu/toasts.cpp
Normal file
31
src/state/play/menu/toasts.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "toasts.hpp"
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static constexpr auto TOAST_MESSAGE_SPEED = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toasts::spawn(const std::string& message, const ImVec2& position, int time)
|
||||||
|
{
|
||||||
|
toasts.emplace_back(message, position, time, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Toasts::update(ImDrawList* drawList)
|
||||||
|
{
|
||||||
|
if (!drawList) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)toasts.size(); i++)
|
||||||
|
{
|
||||||
|
auto& toast = toasts[i];
|
||||||
|
toast.position.y -= TOAST_MESSAGE_SPEED;
|
||||||
|
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||||
|
textColor.w = (float)toast.time / toast.timeMax;
|
||||||
|
drawList->AddText(toast.position, ImGui::GetColorU32(textColor), toast.message.c_str());
|
||||||
|
|
||||||
|
toast.time--;
|
||||||
|
if (toast.time <= 0) toasts.erase(toasts.begin() + i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/state/play/menu/toasts.hpp
Normal file
26
src/state/play/menu/toasts.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace game::state::play::menu
|
||||||
|
{
|
||||||
|
class Toasts
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Toast
|
||||||
|
{
|
||||||
|
std::string message{};
|
||||||
|
ImVec2 position{};
|
||||||
|
int time{};
|
||||||
|
int timeMax{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Toast> toasts{};
|
||||||
|
|
||||||
|
void spawn(const std::string& message, const ImVec2& position, int time);
|
||||||
|
void update(ImDrawList*);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,4 +6,5 @@ namespace game::util::color
|
|||||||
{
|
{
|
||||||
constexpr auto WHITE = glm::vec4(1.0f);
|
constexpr auto WHITE = glm::vec4(1.0f);
|
||||||
constexpr auto GRAY = glm::vec4(0.5f, 0.5f, 0.5f, 1.0f);
|
constexpr auto GRAY = glm::vec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
constexpr auto BLACK = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,15 @@
|
|||||||
|
|
||||||
namespace game::util::imgui::style
|
namespace game::util::imgui::style
|
||||||
{
|
{
|
||||||
void rounding_set(float rounding)
|
void widget_set(float rounding)
|
||||||
{
|
{
|
||||||
|
constexpr auto SCROLLBAR_SIZE = 30.0f;
|
||||||
|
|
||||||
auto& style = ImGui::GetStyle();
|
auto& style = ImGui::GetStyle();
|
||||||
style.WindowRounding = rounding;
|
style.WindowRounding = rounding;
|
||||||
style.FrameRounding = rounding;
|
style.FrameRounding = rounding;
|
||||||
style.GrabRounding = rounding;
|
style.GrabRounding = rounding;
|
||||||
|
style.ScrollbarSize = SCROLLBAR_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void color_set(glm::vec3 color)
|
void color_set(glm::vec3 color)
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
namespace game::util::imgui::style
|
namespace game::util::imgui::style
|
||||||
{
|
{
|
||||||
void rounding_set(float rounding = 10.0f);
|
void widget_set(float rounding = 10.0f);
|
||||||
void color_set(glm::vec3 color);
|
void color_set(glm::vec3 color);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user