This commit is contained in:
56
src/state/configuration.cpp
Normal file
56
src/state/configuration.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "configuration.hpp"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../util/math.hpp"
|
||||
#include "../util/imgui/style.hpp"
|
||||
#include "../util/imgui/widget.hpp"
|
||||
#include "../util/measurement.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
void Configuration::update(Resources& resources, Mode mode)
|
||||
{
|
||||
auto& settings = resources.settings;
|
||||
auto& measurementSystem = settings.measurementSystem;
|
||||
auto& volume = settings.volume;
|
||||
auto& color = settings.color;
|
||||
|
||||
ImGui::SeparatorText("Measurement System");
|
||||
WIDGET_FX(ImGui::RadioButton("Metric", (int*)&measurementSystem, measurement::METRIC));
|
||||
ImGui::SetItemTooltip("%s", "Use kilograms (kg).");
|
||||
ImGui::SameLine();
|
||||
WIDGET_FX(ImGui::RadioButton("Imperial", (int*)&measurementSystem, measurement::IMPERIAL));
|
||||
ImGui::SetItemTooltip("%s", "Use pounds (lbs).");
|
||||
|
||||
ImGui::SeparatorText("Sound");
|
||||
if (WIDGET_FX(ImGui::SliderInt("Volume", &volume, 0, 100, "%d%%")))
|
||||
resources.volume_set(math::to_unit((float)volume));
|
||||
ImGui::SetItemTooltip("%s", "Adjust master volume.");
|
||||
|
||||
ImGui::SeparatorText("Appearance");
|
||||
|
||||
if (WIDGET_FX(
|
||||
ImGui::ColorEdit3("Color", value_ptr(color), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
|
||||
style::color_set(color);
|
||||
ImGui::SetItemTooltip("%s", "Change the UI color.");
|
||||
|
||||
ImGui::Separator();
|
||||
if (WIDGET_FX(ImGui::Button("Reset to Default", ImVec2(-FLT_MIN, 0)))) settings = resource::xml::Settings();
|
||||
|
||||
if (mode == MAIN)
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Save", ImVec2(-FLT_MIN, 0)))) isSave = true;
|
||||
ImGui::SetItemTooltip("%s", "Save the game.\n(Note: the game autosaves frequently.)");
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Return to Characters", ImVec2(-FLT_MIN, 0)))) isGoToSelect = true;
|
||||
ImGui::SetItemTooltip("%s", "Go back to the character selection screen.\nProgress will be saved.");
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/state/configuration.hpp
Normal file
21
src/state/configuration.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "../resources.hpp"
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
class Configuration
|
||||
{
|
||||
public:
|
||||
enum Mode
|
||||
{
|
||||
SELECT,
|
||||
MAIN
|
||||
};
|
||||
|
||||
bool isGoToSelect{};
|
||||
bool isSave{};
|
||||
|
||||
void update(Resources&, Mode = SELECT);
|
||||
};
|
||||
}
|
||||
294
src/state/main.cpp
Normal file
294
src/state/main.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
#include "main.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
|
||||
#include "../util/imgui.hpp"
|
||||
#include "../util/imgui/style.hpp"
|
||||
#include "../util/imgui/widget.hpp"
|
||||
#include "../util/math.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
using namespace game::state::main;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
World::Focus Main::focus_get()
|
||||
{
|
||||
return menu.isOpen && tools.isOpen ? World::MENU_TOOLS
|
||||
: menu.isOpen ? World::MENU
|
||||
: tools.isOpen ? World::TOOLS
|
||||
: World::CENTER;
|
||||
}
|
||||
|
||||
void Main::set(Resources& resources, int characterIndex, enum Game game)
|
||||
{
|
||||
auto& data = resources.character_get(characterIndex);
|
||||
auto& saveData = data.save;
|
||||
auto& itemSchema = data.itemSchema;
|
||||
auto& dialogue = data.dialogue;
|
||||
auto& menuSchema = data.menuSchema;
|
||||
this->characterIndex = characterIndex;
|
||||
|
||||
character =
|
||||
entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
|
||||
|
||||
auto isAlternateSpritesheet =
|
||||
(game == NEW_GAME && math::random_percent_roll(data.alternateSpritesheet.chanceOnNewGame));
|
||||
|
||||
if (isAlternateSpritesheet || saveData.isAlternateSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(entity::Character::ALTERNATE);
|
||||
if (game == NEW_GAME) character.data.alternateSpritesheet.sound.play();
|
||||
}
|
||||
|
||||
character.totalCaloriesConsumed = saveData.totalCaloriesConsumed;
|
||||
character.totalFoodItemsEaten = saveData.totalFoodItemsEaten;
|
||||
characterManager = CharacterManager{};
|
||||
|
||||
cursor = entity::Cursor(character.data.cursorSchema.anm2);
|
||||
|
||||
menu.inventory = Inventory{};
|
||||
for (auto& [id, quantity] : saveData.inventory)
|
||||
{
|
||||
if (quantity == 0) continue;
|
||||
menu.inventory.values[id] = quantity;
|
||||
}
|
||||
|
||||
itemManager = ItemManager{};
|
||||
for (auto& item : saveData.items)
|
||||
{
|
||||
auto& anm2 = itemSchema.anm2s.at(item.id);
|
||||
auto chewAnimation = itemSchema.animations.chew + std::to_string(item.chewCount);
|
||||
auto animationIndex = item.chewCount > 0 ? anm2.animationMap[chewAnimation] : -1;
|
||||
auto& saveItem = itemSchema.anm2s.at(item.id);
|
||||
itemManager.items.emplace_back(saveItem, item.position, item.id, item.chewCount, animationIndex, item.velocity,
|
||||
item.rotation);
|
||||
}
|
||||
|
||||
imgui::style::rounding_set(menuSchema.rounding);
|
||||
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
|
||||
|
||||
menu.play = Play(character);
|
||||
menu.play.totalPlays = saveData.totalPlays;
|
||||
menu.play.highScore = saveData.highScore;
|
||||
menu.play.bestCombo = saveData.bestCombo;
|
||||
menu.play.gradeCounts = saveData.gradeCounts;
|
||||
menu.play.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
|
||||
|
||||
menu.isChat = character.data.dialogue.help.is_valid() || character.data.dialogue.random.is_valid();
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
|
||||
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
|
||||
|
||||
if (game == NEW_GAME && dialogue.start.is_valid())
|
||||
{
|
||||
character.queue_play({.animation = dialogue.start.animation, .isInterruptible = false});
|
||||
character.tick();
|
||||
isWindows = false;
|
||||
isStart = true;
|
||||
}
|
||||
|
||||
isPostgame = saveData.isPostgame;
|
||||
|
||||
if (isPostgame)
|
||||
menu.isCheats = true;
|
||||
else
|
||||
menu.isCheats = true; //false;
|
||||
|
||||
worldCanvas.size_set(imgui::to_vec2(ImGui::GetMainViewport()->Size));
|
||||
|
||||
world.set(character, worldCanvas, focus_get());
|
||||
}
|
||||
|
||||
void Main::exit(Resources& resources)
|
||||
{
|
||||
imgui::style::rounding_set();
|
||||
imgui::widget::sounds_set(nullptr, nullptr);
|
||||
ImGui::GetIO().FontDefault = resources.font.get();
|
||||
save(resources);
|
||||
}
|
||||
|
||||
void Main::tick(Resources& resources)
|
||||
{
|
||||
character.tick();
|
||||
cursor.tick();
|
||||
menu.tick();
|
||||
toasts.tick();
|
||||
text.tick(character);
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
item.tick();
|
||||
}
|
||||
|
||||
void Main::update(Resources& resources)
|
||||
{
|
||||
auto focus = focus_get();
|
||||
auto& dialogue = character.data.dialogue;
|
||||
|
||||
if (isWindows)
|
||||
{
|
||||
menu.update(resources, itemManager, character, cursor, text, worldCanvas);
|
||||
tools.update(character, cursor, world, focus, worldCanvas);
|
||||
info.update(resources, character);
|
||||
toasts.update();
|
||||
}
|
||||
|
||||
if (text.isEnabled) text.update(character);
|
||||
|
||||
if (isStart)
|
||||
{
|
||||
if (!isStartBegin)
|
||||
{
|
||||
if (auto animation = character.animation_get())
|
||||
{
|
||||
if (animation->isLoop || character.state == entity::Actor::STOPPED)
|
||||
{
|
||||
text.set(dialogue.get(dialogue.start.id), character);
|
||||
isStartBegin = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isStartEnd)
|
||||
{
|
||||
if (text.entry->is_last())
|
||||
{
|
||||
isWindows = true;
|
||||
isStartEnd = true;
|
||||
isStart = false;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.isJustStageFinal && !isEnd && !isPostgame) isEnd = true;
|
||||
|
||||
if (isEnd)
|
||||
{
|
||||
if (!isEndBegin)
|
||||
{
|
||||
if (character.is_animation_finished())
|
||||
{
|
||||
text.set(dialogue.get(dialogue.end.id), character);
|
||||
isEndBegin = true;
|
||||
isWindows = false;
|
||||
tools.isOpen = false;
|
||||
menu.isOpen = false;
|
||||
character.calories = 0;
|
||||
character.digestionProgress = 0;
|
||||
itemManager.items.clear();
|
||||
itemManager.heldItemIndex = -1;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
}
|
||||
else if (!isEndEnd)
|
||||
{
|
||||
if (text.entry->is_last())
|
||||
{
|
||||
menu.isOpen = true;
|
||||
isWindows = true;
|
||||
isEndEnd = true;
|
||||
isEnd = false;
|
||||
isPostgame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemManager.update(character, cursor, areaManager, text, World::BOUNDS, worldCanvas);
|
||||
characterManager.update(character, cursor, text, worldCanvas);
|
||||
|
||||
character.update();
|
||||
cursor.update();
|
||||
world.update(character, cursor, worldCanvas, focus);
|
||||
|
||||
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.configuration.isSave)
|
||||
{
|
||||
save(resources);
|
||||
autosaveTime = 0;
|
||||
menu.configuration.isSave = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Main::render(Resources& resources, Canvas& canvas)
|
||||
{
|
||||
auto& textureShader = resources.shaders[shader::TEXTURE];
|
||||
auto& rectShader = resources.shaders[shader::RECT];
|
||||
auto size = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto& bgTexture = character.data.areaSchema.areas.at(areaManager.get(character)).texture;
|
||||
|
||||
auto windowModel = math::quad_model_get(vec2(size));
|
||||
auto worldModel = math::quad_model_get(bgTexture.size);
|
||||
worldCanvas.bind();
|
||||
worldCanvas.size_set(size);
|
||||
worldCanvas.clear();
|
||||
worldCanvas.texture_render(textureShader, bgTexture.id, worldModel);
|
||||
|
||||
character.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
item.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
if (menu.debug.isBoundsDisplay)
|
||||
{
|
||||
auto boundsModel =
|
||||
math::quad_model_get(glm::vec2(World::BOUNDS.z, World::BOUNDS.w), glm::vec2(World::BOUNDS.x, World::BOUNDS.y),
|
||||
glm::vec2(World::BOUNDS.x, World::BOUNDS.y) * 0.5f);
|
||||
worldCanvas.rect_render(rectShader, boundsModel);
|
||||
}
|
||||
worldCanvas.unbind();
|
||||
|
||||
canvas.bind();
|
||||
canvas.texture_render(textureShader, worldCanvas.texture, windowModel);
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
cursor.render(textureShader, rectShader, canvas);
|
||||
canvas.unbind();
|
||||
|
||||
SDL_HideCursor();
|
||||
}
|
||||
|
||||
void Main::save(Resources& resources)
|
||||
{
|
||||
resource::xml::Save save;
|
||||
|
||||
save.weight = character.weight;
|
||||
save.calories = character.calories;
|
||||
save.capacity = character.capacity;
|
||||
save.digestionRate = character.digestionRate;
|
||||
save.eatSpeed = character.eatSpeed;
|
||||
save.digestionProgress = character.digestionProgress;
|
||||
save.isDigesting = character.isDigesting;
|
||||
save.digestionTimer = character.digestionTimer;
|
||||
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
||||
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
||||
save.totalPlays = menu.play.totalPlays;
|
||||
save.highScore = menu.play.highScore;
|
||||
save.bestCombo = menu.play.bestCombo;
|
||||
save.gradeCounts = menu.play.gradeCounts;
|
||||
save.isPostgame = isPostgame;
|
||||
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
|
||||
|
||||
for (auto& [id, quantity] : menu.inventory.values)
|
||||
{
|
||||
if (quantity == 0) continue;
|
||||
save.inventory[id] = quantity;
|
||||
}
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
save.items.emplace_back(item.schemaID, item.chewCount, item.position, item.velocity,
|
||||
*item.overrides[item.rotationOverrideID].frame.rotation);
|
||||
|
||||
save.isValid = true;
|
||||
|
||||
resources.character_save_set(characterIndex, save);
|
||||
save.serialize(character.data.save_path_get());
|
||||
|
||||
toasts.push("Saving...");
|
||||
}
|
||||
};
|
||||
69
src/state/main.hpp
Normal file
69
src/state/main.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "../resources.hpp"
|
||||
|
||||
#include "main/area_manager.hpp"
|
||||
#include "main/character_manager.hpp"
|
||||
#include "main/info.hpp"
|
||||
#include "main/item_manager.hpp"
|
||||
#include "main/menu.hpp"
|
||||
#include "main/text.hpp"
|
||||
#include "main/toasts.hpp"
|
||||
#include "main/tools.hpp"
|
||||
#include "main/world.hpp"
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
class Main
|
||||
{
|
||||
public:
|
||||
static constexpr auto AUTOSAVE_TIME = 30.0f;
|
||||
|
||||
enum Game
|
||||
{
|
||||
NEW_GAME,
|
||||
CONTINUE
|
||||
};
|
||||
|
||||
entity::Character character;
|
||||
entity::Cursor cursor;
|
||||
|
||||
main::Info info;
|
||||
main::Menu menu;
|
||||
main::Tools tools;
|
||||
main::Text text;
|
||||
main::World world;
|
||||
main::Toasts toasts;
|
||||
main::ItemManager itemManager{};
|
||||
main::CharacterManager characterManager{};
|
||||
main::AreaManager areaManager{};
|
||||
|
||||
int characterIndex{};
|
||||
int areaIndex{};
|
||||
|
||||
float autosaveTime{};
|
||||
|
||||
bool isWindows{true};
|
||||
|
||||
bool isStartBegin{};
|
||||
bool isStart{};
|
||||
bool isStartEnd{};
|
||||
|
||||
bool isEndBegin{};
|
||||
bool isEnd{};
|
||||
bool isEndEnd{};
|
||||
|
||||
bool isPostgame{};
|
||||
|
||||
Canvas worldCanvas{main::World::SIZE};
|
||||
|
||||
Main() = default;
|
||||
void set(Resources&, int characterIndex, Game = CONTINUE);
|
||||
void exit(Resources& resources);
|
||||
void update(Resources&);
|
||||
void tick(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
void save(Resources&);
|
||||
main::World::Focus focus_get();
|
||||
};
|
||||
};
|
||||
26
src/state/main/area_manager.cpp
Normal file
26
src/state/main/area_manager.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "area_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
int AreaManager::get(entity::Character& character)
|
||||
{
|
||||
auto& data = character.data;
|
||||
auto& schema = data.areaSchema;
|
||||
if (schema.areas.empty()) return -1;
|
||||
|
||||
auto size = (int)data.stages.size();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
auto& stage = data.stages[size - i - 1];
|
||||
if (stage.areaID != -1) return stage.areaID;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
12
src/state/main/area_manager.hpp
Normal file
12
src/state/main/area_manager.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class AreaManager
|
||||
{
|
||||
public:
|
||||
int get(entity::Character&);
|
||||
};
|
||||
}
|
||||
120
src/state/main/character_manager.cpp
Normal file
120
src/state/main/character_manager.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "character_manager.hpp"
|
||||
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <optional>
|
||||
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void CharacterManager::update(entity::Character& character, entity::Cursor& cursor, Text& text, Canvas& canvas)
|
||||
{
|
||||
auto interact_area_override_tick = [](entity::Actor::Override& override_)
|
||||
{
|
||||
if (override_.frame.scale.has_value() && override_.frameBase.scale.has_value() && override_.time.has_value() &&
|
||||
override_.timeStart.has_value())
|
||||
{
|
||||
auto percent = glm::clamp(*override_.time / *override_.timeStart, 0.0f, 1.0f);
|
||||
auto elapsed = 1.0f - percent;
|
||||
|
||||
auto oscillation = cosf(elapsed * glm::tau<float>() * override_.cycles);
|
||||
auto envelope = percent;
|
||||
auto amplitude = glm::abs(*override_.frameBase.scale);
|
||||
|
||||
*override_.frame.scale = amplitude * (oscillation * envelope);
|
||||
}
|
||||
};
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto cursorWorldPosition = canvas.screen_position_convert(cursor.position);
|
||||
auto isMouseLeftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
|
||||
|
||||
isInteractingPrevious = isInteracting;
|
||||
isHoveringPrevious = isHovering;
|
||||
isHovering = false;
|
||||
|
||||
if (isJustStoppedInteracting)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
if (cursor.mode == RUB) character.queue_idle_animation();
|
||||
isJustStoppedInteracting = false;
|
||||
}
|
||||
|
||||
if (isJustStoppedHovering)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
isJustStoppedHovering = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)character.data.interactAreas.size(); i++)
|
||||
{
|
||||
auto& interactArea = character.data.interactAreas.at(i);
|
||||
if (interactArea.nullID == -1) continue;
|
||||
auto rect = character.null_frame_rect(interactArea.nullID);
|
||||
|
||||
if (cursor.state == entity::Cursor::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
|
||||
!isImguiCaptureMouse && interactArea.type == cursor.mode)
|
||||
{
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
cursor.queue_play({interactArea.animationCursorHover});
|
||||
isHovering = true;
|
||||
interactAreaID = i;
|
||||
|
||||
if (isMouseLeftClick)
|
||||
{
|
||||
isInteracting = true;
|
||||
interactArea.sound.play();
|
||||
lastInteractType = cursor.mode;
|
||||
|
||||
if (interactArea.digestionBonusClick > 0 && character.calories > 0 && !character.isDigesting)
|
||||
character.digestionProgress += interactArea.digestionBonusClick;
|
||||
|
||||
if (interactArea.layerID != -1)
|
||||
{
|
||||
character.overrides.emplace_back(entity::Actor::Override(
|
||||
interactArea.layerID, Anm2::LAYER, entity::Actor::Override::ADD,
|
||||
{.scale = glm::vec2(interactArea.scaleEffectAmplitude)}, std::optional<float>(interactArea.time),
|
||||
interact_area_override_tick, interactArea.scaleEffectCycles));
|
||||
}
|
||||
|
||||
if (interactArea.pool.is_valid() && text.is_interruptible())
|
||||
text.set(dialogue.get(interactArea.pool), character);
|
||||
}
|
||||
|
||||
if (isInteracting)
|
||||
{
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
character.queue_interact_area_animation(interactArea);
|
||||
cursor.queue_play({interactArea.animationCursorActive});
|
||||
|
||||
if (interactArea.digestionBonusRub > 0 && character.calories > 0 && !character.isDigesting)
|
||||
{
|
||||
auto mouseDelta = cursorWorldPosition - cursorWorldPositionPrevious;
|
||||
auto digestionBonus = (fabs(mouseDelta.x) + fabs(mouseDelta.y)) * interactArea.digestionBonusRub;
|
||||
character.digestionProgress += digestionBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((i == interactAreaID && !math::is_point_in_rectf(rect, cursorWorldPosition)) || isMouseLeftReleased ||
|
||||
isImguiCaptureMouse)
|
||||
{
|
||||
isInteracting = false;
|
||||
interactAreaID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInteracting != isInteractingPrevious && !isInteracting) isJustStoppedInteracting = true;
|
||||
if (isHovering != isHoveringPrevious && !isHovering) isJustStoppedHovering = true;
|
||||
|
||||
cursorWorldPositionPrevious = cursorWorldPosition;
|
||||
|
||||
if (character.isJustDigested && text.is_interruptible()) text.set(dialogue.get(dialogue.digest), character);
|
||||
if (character.isJustStageUp) text.set(dialogue.get(dialogue.stageUp), character);
|
||||
}
|
||||
}
|
||||
26
src/state/main/character_manager.hpp
Normal file
26
src/state/main/character_manager.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class CharacterManager
|
||||
{
|
||||
public:
|
||||
bool isInteracting{};
|
||||
bool isHovering{};
|
||||
bool isInteractingPrevious{};
|
||||
bool isHoveringPrevious{};
|
||||
bool isJustStoppedInteracting{};
|
||||
bool isJustStoppedHovering{};
|
||||
int interactAreaID{-1};
|
||||
InteractType lastInteractType{(InteractType)-1};
|
||||
|
||||
glm::vec2 cursorWorldPositionPrevious{};
|
||||
std::string queuedAnimation{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, Text&, Canvas&);
|
||||
};
|
||||
}
|
||||
32
src/state/main/chat.cpp
Normal file
32
src/state/main/chat.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "chat.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Chat::update(Resources& resources, Text& text, entity::Character& character)
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
if (dialogue.random.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Let's chat!", ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.random), character);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
if (dialogue.help.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Help", ImVec2(size.x, 0)))) text.set(dialogue.get(dialogue.help), character);
|
||||
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto& pool = stage > 0 ? character.data.stages.at(stage - 1).pool : character.data.pool;
|
||||
|
||||
if (pool.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("How are you feeling?", ImVec2(size.x, 0)))) text.set(dialogue.get(pool), character);
|
||||
}
|
||||
}
|
||||
14
src/state/main/chat.hpp
Normal file
14
src/state/main/chat.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Chat
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Text&, entity::Character&);
|
||||
};
|
||||
}
|
||||
114
src/state/main/cheats.cpp
Normal file
114
src/state/main/cheats.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "cheats.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/imgui/input_int_ex.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Cheats::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
|
||||
{
|
||||
static constexpr auto FEED_INCREMENT = 100.0f;
|
||||
|
||||
if (ImGui::BeginChild("##Cheats"))
|
||||
{
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Feed")))
|
||||
{
|
||||
character.calories = std::min(character.calories + FEED_INCREMENT, character.max_capacity());
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button("Starve")))
|
||||
{
|
||||
character.calories = std::max(0.0f, character.calories - FEED_INCREMENT);
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button("Digest"))) character.digestionProgress = entity::Character::DIGESTION_MAX;
|
||||
|
||||
if (WIDGET_FX(ImGui::SliderFloat("Weight", &character.weight, character.data.weightMin, character.data.weightMax,
|
||||
"%0.2f kg")))
|
||||
{
|
||||
character.stage = character.stage_get();
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
|
||||
auto stage = character.stage + 1;
|
||||
if (WIDGET_FX(ImGui::SliderInt("Stage", &stage, 1, character.data.stages.size() + 1)))
|
||||
{
|
||||
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
|
||||
character.weight =
|
||||
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
|
||||
character.queue_idle_animation();
|
||||
}
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat("Capacity", &character.capacity, character.data.capacityMin,
|
||||
character.data.capacityMax, "%0.0f kcal"));
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat("Digestion Rate", &character.digestionRate, character.data.digestionRateMin,
|
||||
character.data.digestionRateMax, "%0.2f% / tick"));
|
||||
WIDGET_FX(ImGui::SliderFloat("Eat Speed", &character.eatSpeed, character.data.eatSpeedMin,
|
||||
character.data.eatSpeedMax, "%0.2fx"));
|
||||
|
||||
ImGui::SeparatorText("Animations");
|
||||
ImGui::Text("Now Playing: %s", character.animationMapReverse.at(character.animationIndex).c_str());
|
||||
|
||||
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
||||
|
||||
if (ImGui::BeginChild("##Animations", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.animations.size(); i++)
|
||||
{
|
||||
auto& animation = character.animations[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(animation.name.c_str())))
|
||||
character.play(animation.name.c_str(), entity::Actor::PLAY_FORCE);
|
||||
ImGui::SetItemTooltip("%s", animation.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText("Dialogue");
|
||||
|
||||
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.data.dialogue.entries.size(); i++)
|
||||
{
|
||||
auto& entry = character.data.dialogue.entries[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(entry.name.c_str()))) text.set(&entry, character);
|
||||
ImGui::SetItemTooltip("%s", entry.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText("Inventory");
|
||||
|
||||
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
ImGui::PushID(i);
|
||||
WIDGET_FX(input_int_range(item.name.c_str(), &inventory.values[i], 0, schema.quantityMax, 1, 5));
|
||||
ImGui::SetItemTooltip("%s", item.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
15
src/state/main/cheats.hpp
Normal file
15
src/state/main/cheats.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "inventory.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Cheats
|
||||
{
|
||||
public:
|
||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
};
|
||||
}
|
||||
37
src/state/main/debug.cpp
Normal file
37
src/state/main/debug.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas)
|
||||
{
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
|
||||
ImGui::Text("Cursor Pos (Screen): %0.0f, %0.0f", cursor.position.x, cursor.position.y);
|
||||
ImGui::Text("Cursor Pos (World): %0.0f, %0.0f", cursorPosition.x, cursorPosition.y);
|
||||
|
||||
WIDGET_FX(ImGui::Checkbox("Show Nulls (Hitboxes)", &character.isShowNulls));
|
||||
WIDGET_FX(ImGui::Checkbox("Show World Bounds", &isBoundsDisplay));
|
||||
|
||||
if (!itemManager.items.empty())
|
||||
{
|
||||
ImGui::SeparatorText("Item");
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
auto& item = itemManager.items[i];
|
||||
if (itemManager.heldItemIndex == i) ImGui::Text("Held");
|
||||
ImGui::Text("Type: %i", item.schemaID);
|
||||
ImGui::Text("Position: %0.0f, %0.0f", item.position.x, item.position.y);
|
||||
ImGui::Text("Velocity: %0.0f, %0.0f", item.velocity.x, item.velocity.y);
|
||||
ImGui::Text("Chew Count: %i", item.chewCount);
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/state/main/debug.hpp
Normal file
19
src/state/main/debug.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
|
||||
#include "item_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Debug
|
||||
{
|
||||
public:
|
||||
bool isBoundsDisplay{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor& cursor, ItemManager&, Canvas& canvas);
|
||||
};
|
||||
}
|
||||
125
src/state/main/info.cpp
Normal file
125
src/state/main/info.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "info.hpp"
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
#include "../../util/string.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Info::update(Resources& resources, entity::Character& character)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER - (style.WindowPadding.x * 2.0f),
|
||||
ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER);
|
||||
auto pos = ImVec2((windowSize.x * 0.5f) - (size.x * 0.5f), style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Info", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto childSize = ImVec2(ImGui::GetContentRegionAvail().x / 2, ImGui::GetContentRegionAvail().y);
|
||||
|
||||
if (ImGui::BeginChild("##Weight", childSize))
|
||||
{
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto stage = character.stage_get();
|
||||
auto stageMax = character.stage_max_get();
|
||||
auto stageWeight = character.stage_threshold_get(stage, system);
|
||||
auto stageNextWeight = character.stage_threshold_next_get(system);
|
||||
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
|
||||
|
||||
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::ABOVE_AVERAGE);
|
||||
ImGui::TextUnformatted(weightString.c_str());
|
||||
ImGui::SetItemTooltip("%s", weightString.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto stageProgress = character.stage_progress_get();
|
||||
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
stage >= stageMax ? "MAX" : "To Next Stage");
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::Text("Stage: %i/%i (%0.1f%%)", stage + 1, stageMax + 1, math::to_percent(stageProgress));
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
|
||||
if (stage >= stageMax)
|
||||
ImGui::Text("Maxed out!");
|
||||
else
|
||||
{
|
||||
ImGui::Text("Start: %0.2f %s", stageWeight, unitString);
|
||||
ImGui::Text("Current: %0.2f %s", weight, unitString);
|
||||
ImGui::Text("Next: %0.2f %s", stageNextWeight, unitString);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Calories and Capacity", childSize))
|
||||
{
|
||||
auto& calories = character.calories;
|
||||
auto& capacity = character.capacity;
|
||||
|
||||
auto overstuffedPercent = std::max(0.0f, (calories - capacity) / (character.max_capacity() - capacity));
|
||||
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::ABOVE_AVERAGE);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
|
||||
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
|
||||
character.is_over_capacity() ? character.max_capacity() : character.capacity);
|
||||
ImGui::TextUnformatted(caloriesString.c_str());
|
||||
ImGui::SetItemTooltip("%s", caloriesString.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
|
||||
auto digestionProgress = character.isDigesting
|
||||
? (float)character.digestionTimer / character.data.digestionTimerMax
|
||||
: character.digestionProgress / entity::Character::DIGESTION_MAX;
|
||||
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
character.isDigesting ? "Digesting..." : "Digestion");
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (character.isDigesting)
|
||||
ImGui::TextUnformatted("Digestion in progress...");
|
||||
else if (digestionProgress <= 0.0f)
|
||||
ImGui::TextUnformatted("Give food to start digesting!");
|
||||
else
|
||||
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text("Rate: %0.2f%% / sec", character.digestion_rate_get());
|
||||
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
15
src/state/main/info.hpp
Normal file
15
src/state/main/info.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
void update(Resources&, entity::Character&);
|
||||
};
|
||||
}
|
||||
194
src/state/main/inventory.cpp
Normal file
194
src/state/main/inventory.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "inventory.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Inventory::tick()
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.tick();
|
||||
}
|
||||
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, entity::Character& character)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
if (!itemManager.returnItemIDs.empty())
|
||||
{
|
||||
for (auto& id : itemManager.returnItemIDs)
|
||||
values[id]++;
|
||||
itemManager.returnItemIDs.clear();
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##Inventory Child"))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
|
||||
auto size = ImVec2(SIZE, SIZE);
|
||||
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
auto& quantity = values[i];
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& calories = item.calories;
|
||||
auto& digestionBonus = item.digestionBonus;
|
||||
auto& eatSpeedBonus = item.eatSpeedBonus;
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
|
||||
quantity = glm::clamp(0, quantity, schema.quantityMax);
|
||||
if (rarity.isHidden && quantity <= 0) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (!actors.contains(i))
|
||||
{
|
||||
actors[i] = Actor(schema.anm2s[i], {}, Actor::SET);
|
||||
rects[i] = actors[i].rect();
|
||||
}
|
||||
auto& rect = rects[i];
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
|
||||
auto previewScale = (size.x <= 0.0f || size.y <= 0.0f || rectSize.x <= 0.0f || rectSize.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
if (!canvases.contains(i)) canvases.emplace((int)i, Canvas(canvasSize, Canvas::FLIP));
|
||||
auto& canvas = canvases[i];
|
||||
canvas.zoom = math::to_percent(previewScale);
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
|
||||
actors[i].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
ImGui::BeginDisabled(quantity < 1);
|
||||
if (WIDGET_FX(ImGui::ImageButton("##Image Button", canvas.texture, size, ImVec2(), ImVec2(1, 1), ImVec4(),
|
||||
quantity <= 0 ? ImVec4(0, 0, 0, 1) : ImVec4(1, 1, 1, 1))) &&
|
||||
quantity > 0)
|
||||
{
|
||||
if (category.isEdible)
|
||||
{
|
||||
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
|
||||
character.data.itemSchema.sounds.dispose.play();
|
||||
else
|
||||
{
|
||||
character.data.itemSchema.sounds.summon.play();
|
||||
itemManager.queuedItemIDs.emplace_back(i);
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
else if (item.isToggleSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Character::NORMAL ? Character::ALTERNATE
|
||||
: Character::NORMAL);
|
||||
character.data.alternateSpritesheet.sound.play();
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (quantity > 0)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
|
||||
ImGui::Text("%s (x%i)", item.name.c_str(), quantity);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
|
||||
if (item.flavorID.has_value()) ImGui::Text("Flavor: %s", schema.flavors[*item.flavorID].name.c_str());
|
||||
if (calories.has_value()) ImGui::Text("%0.0f kcal", *calories);
|
||||
if (digestionBonus.has_value())
|
||||
{
|
||||
if (*digestionBonus > 0)
|
||||
ImGui::Text("Digestion Rate Bonus: +%0.2f%% / sec", *digestionBonus * 60.0f);
|
||||
else if (digestionBonus < 0)
|
||||
ImGui::Text("Digestion Rate Penalty: %0.2f%% / sec", *digestionBonus * 60.0f);
|
||||
}
|
||||
if (eatSpeedBonus.has_value())
|
||||
{
|
||||
if (*eatSpeedBonus > 0)
|
||||
ImGui::Text("Eat Speed Bonus: +%0.2f%% / sec", *eatSpeedBonus);
|
||||
else if (eatSpeedBonus < 0)
|
||||
ImGui::Text("Eat Speed Penalty: %0.2f%% / sec", *eatSpeedBonus);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(item.description.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
ImGui::TextUnformatted("???");
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
|
||||
auto text = std::format("x{}", quantity);
|
||||
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
|
||||
cursorScreenPos.y + size.y - ImGui::GetTextLineHeightWithSpacing());
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
|
||||
text.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
|
||||
if (cursorPos.x + increment > ImGui::GetContentRegionAvail().x)
|
||||
{
|
||||
cursorPos.x = cursorStartX;
|
||||
cursorPos.y += increment;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (count() == 0) ImGui::Text("Check the \"Play\" tab to earn rewards!");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
int Inventory::count()
|
||||
{
|
||||
int count{};
|
||||
for (auto& [type, quantity] : values)
|
||||
count += quantity;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
27
src/state/main/inventory.hpp
Normal file
27
src/state/main/inventory.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include "item_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Inventory
|
||||
{
|
||||
public:
|
||||
static constexpr auto SIZE = 96.0f;
|
||||
|
||||
std::map<int, int> values{};
|
||||
std::unordered_map<int, entity::Actor> actors{};
|
||||
std::unordered_map<int, glm::vec4> rects{};
|
||||
std::unordered_map<int, Canvas> canvases{};
|
||||
|
||||
void tick();
|
||||
void update(Resources&, ItemManager&, entity::Character&);
|
||||
int count();
|
||||
};
|
||||
}
|
||||
315
src/state/main/item_manager.cpp
Normal file
315
src/state/main/item_manager.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "item_manager.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../../util/math.hpp"
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void ItemManager::update(entity::Character& character, entity::Cursor& cursor, AreaManager& areaManager, Text& text,
|
||||
const glm::vec4& bounds, Canvas& canvas)
|
||||
{
|
||||
static constexpr float ROTATION_MAX = 90.0f;
|
||||
static constexpr float ROTATION_RETURN_SPEED = 120.0f;
|
||||
static constexpr float ROTATION_GROUND_DAMPING = 0.85f;
|
||||
static constexpr float ROTATION_HOLD_DELTA_ANGULAR_VELOCITY_MULTIPLIER = 0.1f;
|
||||
static constexpr float THROW_THRESHOLD = 10.0f;
|
||||
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto& cursorSchema = character.data.cursorSchema;
|
||||
auto& area = character.data.areaSchema.areas.at(areaManager.get(character));
|
||||
auto& friction = area.friction;
|
||||
auto& airResistance = area.airResistance;
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto isOverCapacity = character.is_over_capacity();
|
||||
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
auto cursorDelta = cursorPosition - cursorPositionPrevious;
|
||||
|
||||
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
|
||||
|
||||
auto isMouseLeftClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
if (isJustItemHoveredStopped)
|
||||
{
|
||||
cursor.queue_default_animation();
|
||||
isJustItemHoveredStopped = false;
|
||||
}
|
||||
|
||||
if (isJustItemHeldStopped || isJustItemThrown)
|
||||
{
|
||||
cursor.queue_default_animation();
|
||||
if (!isJustItemThrown) character.queue_idle_animation();
|
||||
isJustItemHeldStopped = false;
|
||||
isJustItemThrown = false;
|
||||
}
|
||||
|
||||
isItemHoveredPrevious = isItemHovered;
|
||||
isItemHovered = false;
|
||||
if (isItemHovered != isItemHoveredPrevious && !isItemHovered) isJustItemHoveredStopped = true;
|
||||
|
||||
for (auto& id : queuedItemIDs)
|
||||
{
|
||||
auto spawnBounds = character.rect();
|
||||
auto position = glm::vec2(math::random_in_range(spawnBounds.x, spawnBounds.x + spawnBounds.z),
|
||||
math::random_in_range(spawnBounds.y, spawnBounds.y + spawnBounds.w));
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
items.emplace_back(itemSchema.anm2s.at(id), position, id);
|
||||
}
|
||||
queuedItemIDs.clear();
|
||||
|
||||
if (isMouseRightDown) cursor.queue_play({cursorSchema.animations.return_.get()});
|
||||
|
||||
if (auto heldItem = vector::find(items, heldItemIndex))
|
||||
{
|
||||
auto& item = schema.items[heldItem->schemaID];
|
||||
auto& rotationOverride = heldItem->overrides[heldItem->rotationOverrideID];
|
||||
auto& rotation = *rotationOverride.frame.rotation;
|
||||
|
||||
auto delta = cursorPositionPrevious - cursorPosition;
|
||||
heldItem->position = cursorPosition;
|
||||
heldItem->velocity = vec2();
|
||||
|
||||
heldItem->angularVelocity -= delta.x * ROTATION_HOLD_DELTA_ANGULAR_VELOCITY_MULTIPLIER;
|
||||
heldItem->angularVelocity *= friction;
|
||||
rotation *= friction;
|
||||
rotation = glm::clamp(-ROTATION_MAX, rotation, ROTATION_MAX);
|
||||
|
||||
if (schema.categories[item.categoryID].isEdible)
|
||||
{
|
||||
auto& chewCountMax = item.chewCount.has_value() ? *item.chewCount : schema.chewCount;
|
||||
auto caloriesChew = item.calories.has_value() ? *item.calories / (chewCountMax + 1) : 0;
|
||||
auto isCanEat = character.calories + caloriesChew <= character.max_capacity();
|
||||
|
||||
if (isJustItemHeld)
|
||||
{
|
||||
if (isCanEat)
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.feedFull : dialogue.feed), character);
|
||||
else if (caloriesChew > character.capacity)
|
||||
text.set(dialogue.get(dialogue.lowCapacity), character);
|
||||
else
|
||||
text.set(dialogue.get(dialogue.full), character);
|
||||
isJustItemHeld = false;
|
||||
}
|
||||
|
||||
for (auto& eatArea : character.data.eatAreas)
|
||||
{
|
||||
heldItem = vector::find(items, heldItemIndex);
|
||||
if (!heldItem) break;
|
||||
|
||||
auto rect = character.null_frame_rect(eatArea.nullID);
|
||||
|
||||
if (isCanEat && math::is_point_in_rectf(rect, heldItem->position))
|
||||
{
|
||||
character.queue_play(
|
||||
{.animation = eatArea.animation, .speedMultiplier = character.eatSpeed, .isInterruptible = false});
|
||||
|
||||
if (character.playedEventID == eatArea.eventID)
|
||||
{
|
||||
heldItem->chewCount++;
|
||||
character.consume_played_event();
|
||||
|
||||
auto chewAnimation = schema.animations.chew + std::to_string(heldItem->chewCount);
|
||||
auto animationIndex = heldItem->chewCount > 0 ? heldItem->animationMap[chewAnimation] : -1;
|
||||
|
||||
heldItem->play(animationIndex, entity::Actor::SET);
|
||||
|
||||
character.calories += caloriesChew;
|
||||
character.totalCaloriesConsumed += caloriesChew;
|
||||
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
{
|
||||
character.eatSpeed += *item.eatSpeedBonus / (chewCountMax + 1);
|
||||
character.eatSpeed =
|
||||
glm::clamp(character.data.eatSpeedMin, character.eatSpeed, character.data.eatSpeedMax);
|
||||
}
|
||||
if (item.digestionBonus.has_value())
|
||||
{
|
||||
character.digestionRate += *item.digestionBonus / (chewCountMax + 1);
|
||||
character.digestionRate = glm::clamp(character.data.digestionRateMin, character.digestionRate,
|
||||
character.data.digestionRateMax);
|
||||
}
|
||||
|
||||
if (heldItem->chewCount > chewCountMax)
|
||||
{
|
||||
isQueueFinishFood = true;
|
||||
character.totalFoodItemsEaten++;
|
||||
queuedRemoveItemIndex = heldItemIndex;
|
||||
heldItemIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMouseLeftReleased)
|
||||
{
|
||||
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
|
||||
{
|
||||
cursorSchema.sounds.throw_.play();
|
||||
text.set(dialogue.get(dialogue.throw_), character);
|
||||
isJustItemThrown = true;
|
||||
}
|
||||
else
|
||||
cursorSchema.sounds.release.play();
|
||||
|
||||
heldItem->velocity -= delta;
|
||||
heldItemIndex = -1;
|
||||
isJustItemHeldStopped = true;
|
||||
}
|
||||
|
||||
// Food stolen
|
||||
if (auto animation = character.animation_get(character.animation_name_convert(eatArea.animation));
|
||||
character.is_playing(animation->name) && !isOverCapacity)
|
||||
{
|
||||
if (!math::is_point_in_rectf(rect, heldItem->position))
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.foodTakenFull : dialogue.foodTaken), character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto animation = character.animation_get(); character.time >= animation->frameNum && isQueueFinishFood)
|
||||
{
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.eatFull : dialogue.eat), character);
|
||||
isQueueFinishFood = false;
|
||||
}
|
||||
|
||||
if (queuedRemoveItemIndex > -1)
|
||||
{
|
||||
items.erase(items.begin() + queuedRemoveItemIndex);
|
||||
queuedRemoveItemIndex = -1;
|
||||
}
|
||||
|
||||
int heldItemMoveIndex = -1;
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
auto& schemaItem = schema.items[item.schemaID];
|
||||
auto& rotationOverride = item.overrides[item.rotationOverrideID];
|
||||
auto& rotation = *rotationOverride.frame.rotation;
|
||||
auto& gravity = schemaItem.gravity.has_value() ? *schemaItem.gravity : area.gravity;
|
||||
|
||||
item.update();
|
||||
|
||||
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
|
||||
{
|
||||
isItemHovered = true;
|
||||
cursor.queue_play({cursorSchema.animations.hover.get()});
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
|
||||
if (isMouseLeftClicked)
|
||||
{
|
||||
cursorSchema.sounds.grab.play();
|
||||
isJustItemHeld = true;
|
||||
}
|
||||
|
||||
if (isMouseLeftDown)
|
||||
{
|
||||
isItemHeld = true;
|
||||
cursor.queue_play({cursorSchema.animations.grab.get()});
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
heldItemIndex = i;
|
||||
heldItemMoveIndex = i;
|
||||
}
|
||||
|
||||
if (isMouseRightClicked)
|
||||
{
|
||||
if (item.chewCount > 0)
|
||||
schema.sounds.dispose.play();
|
||||
else
|
||||
{
|
||||
schema.sounds.return_.play();
|
||||
returnItemIDs.emplace_back(item.schemaID);
|
||||
}
|
||||
|
||||
if (heldItemIndex == i) heldItemIndex = -1;
|
||||
if (heldItemMoveIndex == i) heldItemMoveIndex = -1;
|
||||
if (heldItemIndex > i) heldItemIndex--;
|
||||
if (heldItemMoveIndex > i) heldItemMoveIndex--;
|
||||
items.erase(items.begin() + i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != heldItemIndex)
|
||||
{
|
||||
|
||||
if (item.position.x <= bounds.x || item.position.x >= bounds.z)
|
||||
{
|
||||
if (item.position.x <= bounds.x) item.position.x = bounds.x + 1.0f;
|
||||
if (item.position.x >= bounds.z) item.position.x = bounds.z - 1.0f;
|
||||
|
||||
item.velocity.x *= friction;
|
||||
item.velocity.x = -item.velocity.x;
|
||||
item.angularVelocity *= friction;
|
||||
item.angularVelocity = -item.angularVelocity;
|
||||
rotation = -rotation;
|
||||
schema.sounds.bounce.play();
|
||||
}
|
||||
|
||||
if (item.position.y <= bounds.y || item.position.y >= bounds.w)
|
||||
{
|
||||
|
||||
if (item.position.y >= bounds.w && item.velocity.y <= gravity)
|
||||
{
|
||||
item.position.y = bounds.w;
|
||||
item.velocity.y = 0;
|
||||
item.velocity.x *= friction;
|
||||
|
||||
rotation = std::fmod(rotation, 360.0f);
|
||||
if (rotation < 0.0f) rotation += 360.0f;
|
||||
if (rotation > 180.0f) rotation -= 360.0f;
|
||||
|
||||
auto returnStep = ROTATION_RETURN_SPEED * io.DeltaTime;
|
||||
if (std::abs(rotation) <= returnStep)
|
||||
rotation = 0.0f;
|
||||
else
|
||||
rotation += (rotation > 0.0f) ? -returnStep : returnStep;
|
||||
|
||||
item.angularVelocity *= ROTATION_GROUND_DAMPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.velocity.y = -item.velocity.y;
|
||||
item.angularVelocity *= friction;
|
||||
schema.sounds.bounce.play();
|
||||
}
|
||||
|
||||
item.velocity.y *= friction;
|
||||
}
|
||||
|
||||
item.velocity.x *= airResistance;
|
||||
item.velocity.y += gravity;
|
||||
}
|
||||
|
||||
item.position.x = glm::clamp(bounds.x, item.position.x, bounds.z);
|
||||
item.position.y = glm::clamp(bounds.y, item.position.y, bounds.w);
|
||||
}
|
||||
|
||||
if (heldItemMoveIndex != -1 && heldItemMoveIndex < (int)items.size() - 1)
|
||||
{
|
||||
auto heldItem = std::move(items[heldItemMoveIndex]);
|
||||
items.erase(items.begin() + heldItemMoveIndex);
|
||||
items.push_back(std::move(heldItem));
|
||||
heldItemIndex = (int)items.size() - 1;
|
||||
}
|
||||
|
||||
cursorPositionPrevious = cursorPosition;
|
||||
cursorDeltaPrevious = cursorDelta;
|
||||
}
|
||||
}
|
||||
43
src/state/main/item_manager.hpp
Normal file
43
src/state/main/item_manager.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
#include "../../entity/item.hpp"
|
||||
|
||||
#include "area_manager.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class ItemManager
|
||||
{
|
||||
public:
|
||||
static constexpr auto LIMIT = 100;
|
||||
|
||||
std::vector<entity::Item> items{};
|
||||
int heldItemIndex{-1};
|
||||
int queuedRemoveItemIndex{-1};
|
||||
|
||||
bool isItemHovered{};
|
||||
bool isItemHoveredPrevious{};
|
||||
bool isJustItemHoveredStopped{};
|
||||
|
||||
bool isItemHeld{};
|
||||
bool isItemHeldPrevious{};
|
||||
bool isJustItemHeldStopped{};
|
||||
bool isJustItemHeld{};
|
||||
bool isJustItemThrown{};
|
||||
|
||||
bool isQueueFinishFood{};
|
||||
|
||||
bool isItemFinished{};
|
||||
|
||||
glm::vec2 cursorPositionPrevious{};
|
||||
glm::vec2 cursorDeltaPrevious{};
|
||||
|
||||
std::vector<int> queuedItemIDs{};
|
||||
std::vector<int> returnItemIDs{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
|
||||
};
|
||||
}
|
||||
165
src/state/main/menu.cpp
Normal file
165
src/state/main/menu.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "menu.hpp"
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Menu::tick()
|
||||
{
|
||||
inventory.tick();
|
||||
play.tick();
|
||||
}
|
||||
|
||||
void Menu::update(Resources& resources, ItemManager& itemManager, entity::Character& character,
|
||||
entity::Cursor& cursor, Text& text, Canvas& canvas)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
|
||||
auto& schema = character.data.menuSchema;
|
||||
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
|
||||
auto targetX = windowSize.x - size.x;
|
||||
auto t = slide.value_get();
|
||||
auto eased = slide.eased_get();
|
||||
auto posX = windowSize.x + (targetX - windowSize.x) * eased;
|
||||
|
||||
auto pos = ImVec2(posX, style.WindowPadding.y);
|
||||
auto barSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), windowSize.y - style.WindowPadding.y * 2);
|
||||
auto barPos = ImVec2(pos.x - barSize.x - style.WindowPadding.x, style.WindowPadding.y);
|
||||
|
||||
if (slide.is_visible())
|
||||
{
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Menu", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
if (ImGui::BeginTabBar("##Options"))
|
||||
{
|
||||
|
||||
if (isChat && WIDGET_FX(ImGui::BeginTabItem("Chat")))
|
||||
{
|
||||
chat.update(resources, text, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Play")))
|
||||
{
|
||||
play.update(resources, character, inventory, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Items")))
|
||||
{
|
||||
inventory.update(resources, itemManager, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Stats")))
|
||||
{
|
||||
stats.update(resources, play, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Settings")))
|
||||
{
|
||||
configuration.update(resources, Configuration::MAIN);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (isCheats && WIDGET_FX(ImGui::BeginTabItem("Cheats")))
|
||||
{
|
||||
cheats.update(resources, character, inventory, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (isDebug && WIDGET_FX(ImGui::BeginTabItem("Debug")))
|
||||
{
|
||||
debug.update(character, cursor, itemManager, canvas);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSize(barSize);
|
||||
ImGui::SetNextWindowPos(barPos);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::Begin("##Menu Open Bar", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto buttonSize = ImGui::GetContentRegionAvail();
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
auto result = WIDGET_FX(ImGui::Button("##MenuToggle", buttonSize));
|
||||
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
ImGui::SetItemTooltip(isOpen ? "Close Main Menu" : "Open Main Menu");
|
||||
if (result)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
|
||||
if (isOpen)
|
||||
schema.sounds.open.play();
|
||||
else
|
||||
schema.sounds.close.play();
|
||||
}
|
||||
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
|
||||
{
|
||||
isOpen = true;
|
||||
schema.sounds.open.play();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
auto center = ImVec2(cursorPos.x + (buttonSize.x * 0.5f), cursorPos.y + (buttonSize.y * 0.5f));
|
||||
auto half = std::min(buttonSize.x, buttonSize.y) * 0.22f;
|
||||
ImVec2 tip;
|
||||
ImVec2 baseA;
|
||||
ImVec2 baseB;
|
||||
if (isOpen)
|
||||
{
|
||||
tip = ImVec2(center.x + half, center.y);
|
||||
baseA = ImVec2(center.x - half, center.y - half);
|
||||
baseB = ImVec2(center.x - half, center.y + half);
|
||||
}
|
||||
else
|
||||
{
|
||||
tip = ImVec2(center.x - half, center.y);
|
||||
baseA = ImVec2(center.x + half, center.y - half);
|
||||
baseB = ImVec2(center.x + half, center.y + half);
|
||||
}
|
||||
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
}
|
||||
39
src/state/main/menu.hpp
Normal file
39
src/state/main/menu.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../configuration.hpp"
|
||||
|
||||
#include "chat.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "debug.hpp"
|
||||
#include "play.hpp"
|
||||
#include "stats.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include "../../util/imgui/window_slide.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
Play play;
|
||||
Chat chat;
|
||||
Cheats cheats;
|
||||
Debug debug;
|
||||
Stats stats;
|
||||
Inventory inventory;
|
||||
|
||||
state::Configuration configuration;
|
||||
|
||||
bool isCheats{true};
|
||||
bool isDebug{true};
|
||||
bool isOpen{true};
|
||||
bool isChat{true};
|
||||
util::imgui::WindowSlide slide{};
|
||||
|
||||
void tick();
|
||||
void update(Resources&, ItemManager&, entity::Character&, entity::Cursor&, Text&, Canvas&);
|
||||
};
|
||||
}
|
||||
433
src/state/main/play.cpp
Normal file
433
src/state/main/play.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#include "play.hpp"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
float Play::accuracy_score_get(entity::Character& character)
|
||||
{
|
||||
if (totalPlays == 0) return 0.0f;
|
||||
|
||||
auto& schema = character.data.playSchema;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Play::Challenge Play::challenge_generate(entity::Character& character)
|
||||
{
|
||||
auto& schema = character.data.playSchema;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Play::Play(entity::Character& character) { challenge = challenge_generate(character); }
|
||||
|
||||
void Play::tick()
|
||||
{
|
||||
for (auto& [i, actor] : itemActors)
|
||||
actor.tick();
|
||||
}
|
||||
|
||||
void Play::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 = 2.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.playSchema;
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
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 cursorPos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::Text("Score: %i pts (%ix)", score, combo);
|
||||
auto bestString = std::format("Best: {} pts({}x)", highScore, bestCombo);
|
||||
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
|
||||
|
||||
ImGui::Text("Best: %i pts (%ix)", highScore, bestCombo);
|
||||
|
||||
if (score == 0 && isActive)
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
|
||||
ImGui::TextWrapped("Match the line to the colored areas with Space/click! Better performance, better rewards!");
|
||||
}
|
||||
|
||||
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, 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 color = LINE_COLOR;
|
||||
color.w = isActive ? 1.0f : endTimerProgress;
|
||||
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(color));
|
||||
|
||||
if (!isActive && !isGameOver)
|
||||
{
|
||||
range_draw(queuedChallenge.range, 1.0f - endTimerProgress);
|
||||
|
||||
auto lineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * queuedChallenge.tryValue));
|
||||
auto lineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, lineMin.y + LINE_HEIGHT);
|
||||
auto color = LINE_COLOR;
|
||||
color.w = 1.0f - endTimerProgress;
|
||||
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(color));
|
||||
}
|
||||
|
||||
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("-1").x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
||||
toasts.emplace_back("-1", 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("##PlayBar", 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.rewardItemPool)
|
||||
{
|
||||
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("Fantastic score!\nCongratulations!").x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
|
||||
toasts.emplace_back("Fantastic score! Congratulations!", 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("High Score!").x - ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
|
||||
toasts.emplace_back("High Score!", 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;
|
||||
if (isHighScoreAchieved) schema.sounds.highScoreLoss.play();
|
||||
if (highScore > 0) isHighScoreAchieved = true;
|
||||
isRewardScoreAchieved = false;
|
||||
isHighScoreAchievedThisRun = true;
|
||||
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::format("{} (+{})", 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 color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
color.w = ((float)toastMessage.time / toastMessage.timeMax);
|
||||
|
||||
drawList->AddText(toastMessage.position, ImGui::GetColorU32(color), 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();
|
||||
}
|
||||
}
|
||||
87
src/state/main/play.hpp
Normal file
87
src/state/main/play.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../canvas.hpp"
|
||||
#include "../../entity/actor.hpp"
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include "inventory.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Play
|
||||
{
|
||||
|
||||
public:
|
||||
struct Range
|
||||
{
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct Challenge
|
||||
{
|
||||
Range range{};
|
||||
float speed{};
|
||||
float tryValue{};
|
||||
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 queuedChallenge{};
|
||||
float tryValue{};
|
||||
|
||||
int score{};
|
||||
int combo{};
|
||||
|
||||
int endTimer{};
|
||||
int endTimerMax{};
|
||||
|
||||
int highScoreStart{};
|
||||
|
||||
int bestCombo{};
|
||||
int highScore{};
|
||||
int totalPlays{};
|
||||
std::map<int, int> gradeCounts{};
|
||||
|
||||
bool isActive{true};
|
||||
bool isRewardScoreAchieved{false};
|
||||
bool isHighScoreAchieved{false};
|
||||
bool isHighScoreAchievedThisRun{false};
|
||||
bool isGameOver{};
|
||||
|
||||
std::vector<Toast> toasts{};
|
||||
std::vector<Item> items{};
|
||||
std::unordered_map<int, entity::Actor> itemActors{};
|
||||
std::unordered_map<int, glm::vec4> itemRects{};
|
||||
std::unordered_map<int, Canvas> itemCanvases{};
|
||||
|
||||
Play() = default;
|
||||
Play(entity::Character&);
|
||||
Challenge challenge_generate(entity::Character&);
|
||||
void tick();
|
||||
void update(Resources&, entity::Character&, Inventory&, Text&);
|
||||
float accuracy_score_get(entity::Character&);
|
||||
};
|
||||
}
|
||||
48
src/state/main/stats.cpp
Normal file
48
src/state/main/stats.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "stats.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/measurement.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Stats::update(Resources& resources, Play& play, entity::Character& character)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
ImGui::TextUnformatted(character.data.name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
auto& playSchema = character.data.playSchema;
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
|
||||
|
||||
ImGui::Text("Weight: %0.2f %s (Stage: %i)", weight, weightUnit, character.stage_get() + 1);
|
||||
ImGui::Text("Capacity: %0.0f kcal (Max: %0.0f kcal)", character.capacity, character.max_capacity());
|
||||
ImGui::Text("Digestion Rate: %0.2f%%/sec", character.digestion_rate_get());
|
||||
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
|
||||
|
||||
ImGui::SeparatorText("Totals");
|
||||
|
||||
ImGui::Text("Total Calories Consumed: %0.0f kcal", character.totalCaloriesConsumed);
|
||||
ImGui::Text("Total Food Items Eaten: %i", character.totalFoodItemsEaten);
|
||||
|
||||
ImGui::SeparatorText("Play");
|
||||
|
||||
ImGui::Text("Best: %i pts (%ix)", play.highScore, play.bestCombo);
|
||||
ImGui::Text("Total Plays: %i", play.totalPlays);
|
||||
|
||||
for (int i = 0; i < (int)playSchema.grades.size(); i++)
|
||||
{
|
||||
auto& grade = playSchema.grades[i];
|
||||
ImGui::Text("%s: %i", grade.namePlural.c_str(), play.gradeCounts[i]);
|
||||
}
|
||||
|
||||
ImGui::Text("Accuracy: %0.2f%%", play.accuracy_score_get(character));
|
||||
}
|
||||
}
|
||||
17
src/state/main/stats.hpp
Normal file
17
src/state/main/stats.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include "play.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Stats
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Play&, entity::Character&);
|
||||
};
|
||||
}
|
||||
205
src/state/main/text.cpp
Normal file
205
src/state/main/text.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
const char* utf8_advance_chars(const char* text, const char* end, int count)
|
||||
{
|
||||
const char* it = text;
|
||||
while (it < end && count > 0)
|
||||
{
|
||||
unsigned int codepoint = 0;
|
||||
int step = ImTextCharFromUtf8(&codepoint, it, end);
|
||||
if (step <= 0) break;
|
||||
it += step;
|
||||
--count;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
void Text::set(resource::xml::Dialogue::Entry* entry, entity::Character& character)
|
||||
{
|
||||
if (!entry) return;
|
||||
this->entry = entry;
|
||||
|
||||
isFinished = false;
|
||||
index = 0;
|
||||
time = 0.0f;
|
||||
isEnabled = true;
|
||||
character.isTalking = true;
|
||||
if (!entry->animation.empty()) character.play_convert(entry->animation);
|
||||
if (entry->text.empty()) isEnabled = false;
|
||||
}
|
||||
|
||||
void Text::tick(entity::Character& character)
|
||||
{
|
||||
if (!entry || isFinished) return;
|
||||
|
||||
index++;
|
||||
|
||||
if (index >= ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size()))
|
||||
{
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Text::update(entity::Character& character)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HEIGHT_MULTIPLIER = 6.0f;
|
||||
|
||||
if (!entry) return;
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& menuSchema = character.data.menuSchema;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER - (style.WindowPadding.x * 2.0f),
|
||||
ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER);
|
||||
auto pos = ImVec2((windowSize.x * 0.5f) - (size.x * 0.5f), windowSize.y - size.y - style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (!entry) return;
|
||||
|
||||
if (ImGui::Begin("##Text", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto isHovered = ImGui::IsWindowHovered();
|
||||
auto isMouse = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isSpace = ImGui::IsKeyReleased(ImGuiKey_Space);
|
||||
auto isAdvance = (isHovered && (isMouse || isSpace));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 0));
|
||||
|
||||
if (ImGui::BeginTabBar("##Name"))
|
||||
{
|
||||
if (ImGui::BeginTabItem(character.data.name.c_str())) ImGui::EndTabItem();
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
|
||||
auto font = ImGui::GetFont();
|
||||
auto fontSize = resource::Font::NORMAL;
|
||||
|
||||
ImGui::PushFont(font, fontSize);
|
||||
|
||||
auto text = [&]()
|
||||
{
|
||||
auto text = entry ? std::string_view(entry->text) : "null";
|
||||
auto length = std::clamp(index, 0, ImTextCountCharsFromUtf8(text.data(), text.data() + text.size()));
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
ImGui::Dummy(ImVec2(1.0f, ImGui::GetTextLineHeight()));
|
||||
return;
|
||||
}
|
||||
|
||||
const char* textStart = text.data();
|
||||
const char* textEnd = textStart + text.size();
|
||||
const char* textLimit = utf8_advance_chars(textStart, textEnd, length);
|
||||
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + available.x);
|
||||
ImGui::TextUnformatted(textStart, textLimit);
|
||||
ImGui::PopTextWrapPos();
|
||||
};
|
||||
|
||||
text();
|
||||
|
||||
if (entry)
|
||||
{
|
||||
if (isFinished)
|
||||
{
|
||||
if (!entry->choices.empty())
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetStyle().WindowPadding.x, available.y));
|
||||
auto buttonSize = imgui::row_widget_size_get(entry->choices.size());
|
||||
|
||||
for (auto& branch : entry->choices)
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button(branch.text.c_str(), buttonSize)))
|
||||
set(dialogue.get(branch.nextID), character);
|
||||
|
||||
ImGui::SetItemTooltip("%s", branch.text.c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (isHovered && isSpace)
|
||||
{
|
||||
set(dialogue.get(entry->choices.front().nextID), character);
|
||||
menuSchema.sounds.select.play();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry->nextID != -1)
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(available.x - ImGui::GetTextLineHeightWithSpacing(), available.y));
|
||||
auto indicatorSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
auto center = ImVec2(cursorPos.x + (indicatorSize.x * 0.5f), cursorPos.y + (indicatorSize.y * 0.5f));
|
||||
auto half = std::min(indicatorSize.x, indicatorSize.y) * 0.35f;
|
||||
auto tip = ImVec2(center.x + half, center.y);
|
||||
auto baseA = ImVec2(center.x - half, center.y - half);
|
||||
auto baseB = ImVec2(center.x - half, center.y + half);
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
ImGui::Dummy(indicatorSize);
|
||||
|
||||
if (isAdvance)
|
||||
{
|
||||
menuSchema.sounds.select.play();
|
||||
set(dialogue.get(entry->nextID), character);
|
||||
}
|
||||
}
|
||||
else if (isAdvance)
|
||||
{
|
||||
isEnabled = false;
|
||||
entry = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAdvance)
|
||||
{
|
||||
index = ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size());
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleVar();
|
||||
};
|
||||
ImGui::End();
|
||||
|
||||
if (isEnabled && isFinished && entry && entry->is_last())
|
||||
{
|
||||
if (time += ImGui::GetIO().DeltaTime; time > LIFETIME)
|
||||
{
|
||||
isEnabled = false;
|
||||
entry = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Text::is_interruptible() const { return !entry || (entry && entry->is_last()); }
|
||||
}
|
||||
29
src/state/main/text.hpp
Normal file
29
src/state/main/text.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
#include "../../resources.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Text
|
||||
{
|
||||
int index{};
|
||||
bool isFinished{};
|
||||
|
||||
public:
|
||||
static constexpr auto LIFETIME = 10.0f;
|
||||
|
||||
resource::xml::Dialogue::Entry* entry{};
|
||||
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
|
||||
void set(resource::xml::Dialogue::Entry*, entity::Character&);
|
||||
void tick(entity::Character&);
|
||||
void update(entity::Character&);
|
||||
bool is_interruptible() const;
|
||||
};
|
||||
}
|
||||
68
src/state/main/toasts.cpp
Normal file
68
src/state/main/toasts.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "toasts.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <ranges>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Toasts::tick()
|
||||
{
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
|
||||
item.lifetime--;
|
||||
|
||||
if (item.lifetime <= 0)
|
||||
{
|
||||
items.erase(items.begin() + i--);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Toasts::update()
|
||||
{
|
||||
if (items.empty()) return;
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto& style = ImGui::GetStyle();
|
||||
|
||||
auto windowBgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||
auto borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
||||
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
auto posY = viewport->Size.y - style.WindowPadding.y -
|
||||
(((ImGui::GetTextLineHeightWithSpacing() + style.WindowPadding.y * 2)) * (items.size() - i));
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(style.WindowPadding.x, posY));
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(ImGui::CalcTextSize(item.message.c_str()).x + (style.WindowPadding.x * 2),
|
||||
ImGui::GetTextLineHeightWithSpacing()));
|
||||
|
||||
auto alpha = (float)item.lifetime / Item::LIFETIME;
|
||||
|
||||
windowBgColor.w = alpha;
|
||||
borderColor.w = alpha;
|
||||
textColor.w = alpha;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, windowBgColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
||||
|
||||
auto name = "##Toast " + std::to_string(i);
|
||||
if (ImGui::Begin(name.c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
ImGui::TextUnformatted(item.message.c_str());
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
||||
|
||||
void Toasts::push(const std::string& message) { items.push_back({message, Item::LIFETIME}); }
|
||||
}
|
||||
25
src/state/main/toasts.hpp
Normal file
25
src/state/main/toasts.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Toasts
|
||||
{
|
||||
public:
|
||||
struct Item
|
||||
{
|
||||
static constexpr auto LIFETIME = 30;
|
||||
|
||||
std::string message{};
|
||||
int lifetime{};
|
||||
};
|
||||
|
||||
std::vector<Item> items{};
|
||||
|
||||
void update();
|
||||
void tick();
|
||||
void push(const std::string&);
|
||||
};
|
||||
};
|
||||
129
src/state/main/tools.cpp
Normal file
129
src/state/main/tools.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "tools.hpp"
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void Tools::update(entity::Character& character, entity::Cursor& cursor, World& world, World::Focus focus,
|
||||
Canvas& canvas)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.05f;
|
||||
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
auto& schema = character.data.menuSchema;
|
||||
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
|
||||
auto targetX = 0;
|
||||
auto t = slide.value_get();
|
||||
auto eased = slide.eased_get();
|
||||
auto closedX = -size.x;
|
||||
auto posX = closedX + (targetX - closedX) * eased;
|
||||
auto pos = ImVec2(posX, style.WindowPadding.y);
|
||||
auto barSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), windowSize.y - style.WindowPadding.y * 2);
|
||||
auto barPos = ImVec2(pos.x + size.x, style.WindowPadding.y);
|
||||
|
||||
if (slide.is_visible())
|
||||
{
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Tools", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto buttonSize = imgui::to_imvec2(vec2(ImGui::GetContentRegionAvail().x));
|
||||
|
||||
auto cursor_mode_button = [&](const char* name, InteractType mode)
|
||||
{
|
||||
auto isMode = cursor.mode == mode;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
ImGui::GetStyleColorVec4(isMode ? ImGuiCol_ButtonHovered : ImGuiCol_Button));
|
||||
if (WIDGET_FX(ImGui::Button(name, buttonSize))) cursor.mode = mode;
|
||||
ImGui::PopStyleColor();
|
||||
};
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("Home", buttonSize))) world.character_focus(character, canvas, focus);
|
||||
ImGui::SetItemTooltip("%s", "Reset camera view.\n(Shortcut: Home)");
|
||||
|
||||
cursor_mode_button("Rub", InteractType::RUB);
|
||||
cursor_mode_button("Kiss", InteractType::KISS);
|
||||
cursor_mode_button("Smack", InteractType::SMACK);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSize(barSize);
|
||||
ImGui::SetNextWindowPos(barPos);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::Begin("##Tools Open Bar", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto buttonSize = ImGui::GetContentRegionAvail();
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
auto result = WIDGET_FX(ImGui::Button("##ToolsToggle", buttonSize));
|
||||
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
ImGui::SetItemTooltip(isOpen ? "Close Tools" : "Open Tools");
|
||||
if (result)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
if (isOpen)
|
||||
schema.sounds.open.play();
|
||||
else
|
||||
schema.sounds.close.play();
|
||||
}
|
||||
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
|
||||
{
|
||||
isOpen = true;
|
||||
schema.sounds.open.play();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
auto center = ImVec2(cursorPos.x + (buttonSize.x * 0.5f), cursorPos.y + (buttonSize.y * 0.5f));
|
||||
auto half = std::min(buttonSize.x, buttonSize.y) * 0.22f;
|
||||
ImVec2 tip;
|
||||
ImVec2 baseA;
|
||||
ImVec2 baseB;
|
||||
if (isOpen)
|
||||
{
|
||||
tip = ImVec2(center.x - half, center.y);
|
||||
baseA = ImVec2(center.x + half, center.y - half);
|
||||
baseB = ImVec2(center.x + half, center.y + half);
|
||||
}
|
||||
else
|
||||
{
|
||||
tip = ImVec2(center.x + half, center.y);
|
||||
baseA = ImVec2(center.x - half, center.y - half);
|
||||
baseB = ImVec2(center.x - half, center.y + half);
|
||||
}
|
||||
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
}
|
||||
18
src/state/main/tools.hpp
Normal file
18
src/state/main/tools.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../../util/imgui/window_slide.hpp"
|
||||
#include "world.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class Tools
|
||||
{
|
||||
public:
|
||||
bool isOpen{};
|
||||
util::imgui::WindowSlide slide{0.125f, 0.0f};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, World&, World::Focus, Canvas&);
|
||||
};
|
||||
}
|
||||
92
src/state/main/world.cpp
Normal file
92
src/state/main/world.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "world.hpp"
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
void World::set(entity::Character& character, Canvas& canvas, Focus focus)
|
||||
{
|
||||
character.stage = character.stage_get();
|
||||
character.queue_idle_animation();
|
||||
character_focus(character, canvas, focus);
|
||||
}
|
||||
|
||||
void World::update(entity::Character& character, entity::Cursor& cursor, Canvas& canvas, Focus focus)
|
||||
{
|
||||
auto& cursorSchema = character.data.cursorSchema;
|
||||
auto& pan = canvas.pan;
|
||||
auto& zoom = canvas.zoom;
|
||||
auto& io = ImGui::GetIO();
|
||||
bool isPan{true};
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto panMultiplier = ZOOM_BASE / zoom;
|
||||
|
||||
if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !ImGui::IsAnyItemActive())
|
||||
{
|
||||
if ((isMouseMiddleDown) && isPan)
|
||||
{
|
||||
cursor.queue_play({cursorSchema.animations.pan.get()});
|
||||
pan -= imgui::to_vec2(io.MouseDelta) * panMultiplier;
|
||||
}
|
||||
|
||||
if (io.MouseWheel != 0)
|
||||
{
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto mousePos = io.MousePos;
|
||||
auto cursorPos = imgui::to_vec2(ImVec2(mousePos.x - viewport->Pos.x, mousePos.y - viewport->Pos.y));
|
||||
auto zoomBefore = zoom;
|
||||
auto zoomFactorBefore = math::to_unit(zoomBefore);
|
||||
auto cursorWorld = pan + (cursorPos / zoomFactorBefore);
|
||||
|
||||
cursor.queue_play({cursorSchema.animations.zoom.get()});
|
||||
|
||||
zoom = glm::clamp(ZOOM_MIN, zoom + (io.MouseWheel * ZOOM_STEP), ZOOM_MAX);
|
||||
|
||||
auto zoomFactorAfter = math::to_unit(zoom);
|
||||
pan = cursorWorld - (cursorPos / zoomFactorAfter);
|
||||
}
|
||||
}
|
||||
|
||||
zoom = glm::clamp(ZOOM_MIN, zoom, ZOOM_MAX);
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Home)) character_focus(character, canvas, focus);
|
||||
}
|
||||
|
||||
void World::character_focus(entity::Character& character, Canvas& canvas, Focus focus)
|
||||
{
|
||||
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
|
||||
static constexpr float PADDING = 100.0f;
|
||||
|
||||
auto rect = character.rect();
|
||||
|
||||
if (!std::isfinite(rect.x) || !std::isfinite(rect.y) || !std::isfinite(rect.z) || !std::isfinite(rect.w) ||
|
||||
rect.z <= 0.0f || rect.w <= 0.0f)
|
||||
return;
|
||||
|
||||
rect = {rect.x - PADDING * 0.5f, rect.y - PADDING * 0.5f, rect.z + PADDING, rect.w + PADDING};
|
||||
|
||||
auto zoomFactor = std::min((float)canvas.size.x / rect.z, (float)canvas.size.y / rect.w);
|
||||
canvas.zoom = glm::clamp(ZOOM_MIN, math::to_percent(zoomFactor), ZOOM_MAX);
|
||||
zoomFactor = math::to_unit(canvas.zoom);
|
||||
|
||||
auto rectCenter = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f);
|
||||
auto viewSizeWorld = glm::vec2(canvas.size) / zoomFactor;
|
||||
canvas.pan = rectCenter - (vec2(viewSizeWorld.x, viewSizeWorld.y) * 0.5f);
|
||||
auto menuWidthWorld = (canvas.size.x * MENU_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
auto toolsWidthWorld = (canvas.size.x * TOOLS_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
|
||||
if (focus == Focus::MENU || focus == Focus::MENU_TOOLS) canvas.pan.x += menuWidthWorld * 0.5f;
|
||||
if (focus == Focus::TOOLS || focus == Focus::MENU_TOOLS) canvas.pan.x -= toolsWidthWorld * 0.5f;
|
||||
|
||||
auto panMin = glm::vec2(0.0f, 0.0f);
|
||||
auto panMax = glm::max(glm::vec2(0.0f), SIZE - viewSizeWorld);
|
||||
canvas.pan = glm::clamp(panMin, canvas.pan, panMax);
|
||||
}
|
||||
}
|
||||
35
src/state/main/world.hpp
Normal file
35
src/state/main/world.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../canvas.hpp"
|
||||
#include "../../entity/character.hpp"
|
||||
|
||||
#include "character_manager.hpp"
|
||||
#include "item_manager.hpp"
|
||||
|
||||
namespace game::state::main
|
||||
{
|
||||
class World
|
||||
{
|
||||
public:
|
||||
static constexpr auto ZOOM_MIN = 50.0f;
|
||||
static constexpr auto ZOOM_BASE = 100.0f;
|
||||
static constexpr auto ZOOM_STEP = 25.0f;
|
||||
static constexpr auto ZOOM_MAX = 400.0f;
|
||||
static constexpr auto SIZE = glm::vec2{1920, 1080};
|
||||
static constexpr auto BOUNDS =
|
||||
glm::vec4(SIZE.x * 0.05, SIZE.y * 0.05, SIZE.x - (SIZE.x * 0.05f), SIZE.y - (SIZE.y * 0.05f));
|
||||
|
||||
enum Focus
|
||||
{
|
||||
CENTER,
|
||||
MENU,
|
||||
MENU_TOOLS,
|
||||
TOOLS
|
||||
};
|
||||
|
||||
void update(entity::Character& character, entity::Cursor& cursor, Canvas& canvas, Focus = CENTER);
|
||||
void character_focus(entity::Character& character, Canvas& canvas, Focus = CENTER);
|
||||
void set(entity::Character& character, Canvas& canvas, Focus = CENTER);
|
||||
glm::vec2 screen_to_world(glm::vec2 screenPosition, const Canvas& canvas) const;
|
||||
};
|
||||
}
|
||||
25
src/state/select.cpp
Normal file
25
src/state/select.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "select.hpp"
|
||||
|
||||
#include <imgui_impl_opengl3.h>
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
void Select::tick() { preview.tick(characterIndex); }
|
||||
|
||||
void Select::update(Resources& resources)
|
||||
{
|
||||
preview.update(resources, characterIndex);
|
||||
info.update(resources, characterIndex);
|
||||
characters.update(resources, characterIndex);
|
||||
}
|
||||
|
||||
void Select::render(Resources& resources, Canvas& canvas)
|
||||
{
|
||||
canvas.bind();
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
canvas.unbind();
|
||||
}
|
||||
};
|
||||
24
src/state/select.hpp
Normal file
24
src/state/select.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../canvas.hpp"
|
||||
|
||||
#include "select/characters.hpp"
|
||||
#include "select/info.hpp"
|
||||
#include "select/preview.hpp"
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
class Select
|
||||
{
|
||||
public:
|
||||
select::Characters characters{};
|
||||
select::Info info{};
|
||||
select::Preview preview{};
|
||||
|
||||
int characterIndex{-1};
|
||||
|
||||
void tick();
|
||||
void update(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
};
|
||||
};
|
||||
81
src/state/select/characters.cpp
Normal file
81
src/state/select/characters.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "characters.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
void Characters::update(Resources& resources, int& characterIndex)
|
||||
{
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
auto size =
|
||||
ImVec2(viewport->Size.x / 2.0f - style.WindowPadding.x, viewport->Size.y - (style.WindowPadding.y * 2.0f));
|
||||
auto pos = ImVec2(viewport->Size.x / 2.0f, style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Main Menu", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar))
|
||||
{
|
||||
if (ImGui::BeginTabBar("##Main Menu Bar"))
|
||||
{
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Characters")))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
|
||||
auto buttonSize = ImVec2(ImGui::GetContentRegionAvail().x / 4, ImGui::GetContentRegionAvail().x / 4);
|
||||
|
||||
for (int i = 0; i < (int)resources.characterPreviews.size(); i++)
|
||||
{
|
||||
auto& character = resources.characterPreviews[i];
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
|
||||
auto isSelected = i == characterIndex;
|
||||
|
||||
if (isSelected) ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered));
|
||||
|
||||
if (character.portrait.is_valid())
|
||||
{
|
||||
if (WIDGET_FX(ImGui::ImageButton(character.name.c_str(), character.portrait.id, buttonSize)))
|
||||
characterIndex = i;
|
||||
}
|
||||
else if (WIDGET_FX(ImGui::Button(character.name.c_str(), buttonSize)))
|
||||
characterIndex = i;
|
||||
if (isSelected) ImGui::PopStyleColor();
|
||||
ImGui::SetItemTooltip("%s", character.name.c_str());
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
|
||||
if (cursorPos.x + increment > ImGui::GetContentRegionAvail().x)
|
||||
{
|
||||
cursorPos.x = cursorStartX;
|
||||
cursorPos.y += increment;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Configuration")))
|
||||
{
|
||||
configuration.update(resources);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/state/select/characters.hpp
Normal file
15
src/state/select/characters.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../resources.hpp"
|
||||
#include "../configuration.hpp"
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
class Characters
|
||||
{
|
||||
public:
|
||||
Configuration configuration;
|
||||
|
||||
void update(Resources&, int& characterIndex);
|
||||
};
|
||||
}
|
||||
127
src/state/select/info.cpp
Normal file
127
src/state/select/info.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include "info.hpp"
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::util::measurement;
|
||||
using namespace game::resource;
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
void Info::update(Resources& resources, int characterIndex)
|
||||
{
|
||||
if (!vector::in_bounds(resources.characterPreviews, characterIndex)) return;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
auto size = ImVec2(viewport->Size.x / 2.0f - (style.WindowPadding.x * 2.0f),
|
||||
(viewport->Size.y / 2.0f) - (style.WindowPadding.y * 2.0f));
|
||||
auto pos = ImVec2(style.WindowPadding.x, (viewport->Size.y / 2.0f) + style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Info", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar))
|
||||
{
|
||||
auto& character = resources.characterPreviews[characterIndex];
|
||||
auto& save = character.save;
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto& weight = save.is_valid() ? save.weight : character.weight;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
|
||||
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Info Child", childSize))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
|
||||
ImGui::TextUnformatted(character.name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
if (!character.description.empty())
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
ImGui::TextWrapped("%s", character.description.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
|
||||
|
||||
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
|
||||
system == IMPERIAL ? "lbs" : "kg");
|
||||
ImGui::Text("Stages: %i", character.stages);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
if (!character.author.empty()) ImGui::TextWrapped("Author: %s", character.author.c_str());
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
|
||||
|
||||
if (save.is_valid())
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button("Continue", widgetSize))) isContinue = true;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", "Continue from a saved game.");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::Button("New Game", widgetSize)))
|
||||
{
|
||||
if (save.is_valid())
|
||||
{
|
||||
ImGui::OpenPopup("New Game Warning");
|
||||
isNewGameWarning = true;
|
||||
}
|
||||
else
|
||||
isNewGame = true;
|
||||
}
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", "Start a new game.\nThis will delete progress!");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * 0.5f, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->GetCenter()), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (ImGui::BeginPopupModal("New Game Warning", &isNewGameWarning,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto widgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
|
||||
ImGui::TextWrapped("This will delete saved progress! Are you sure?");
|
||||
if (WIDGET_FX(ImGui::Button("Yes", widgetSize))) isNewGame = true;
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button("No", widgetSize))) isNewGameWarning = false;
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
16
src/state/select/info.hpp
Normal file
16
src/state/select/info.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../resources.hpp"
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
bool isContinue{};
|
||||
bool isNewGame{};
|
||||
bool isNewGameWarning{};
|
||||
|
||||
void update(Resources&, int characterIndex);
|
||||
};
|
||||
}
|
||||
110
src/state/select/preview.cpp
Normal file
110
src/state/select/preview.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "preview.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
void Preview::tick(int characterIndex)
|
||||
{
|
||||
if (characterIndex != -1 && isInGame) actor.tick();
|
||||
}
|
||||
|
||||
void Preview::update(Resources& resources, int characterIndex)
|
||||
{
|
||||
if (!vector::in_bounds(resources.characterPreviews, characterIndex)) return;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
auto size = ImVec2(viewport->Size.x / 2.0f - (style.WindowPadding.x * 2.0f),
|
||||
(viewport->Size.y / 2.0f) - (style.WindowPadding.y * 2.0f));
|
||||
auto pos = ImVec2(style.WindowPadding.x, style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Preview", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar))
|
||||
{
|
||||
if (ImGui::BeginTabBar("##Preview Tab Bar"))
|
||||
{
|
||||
auto& character = resources.characterPreviews[characterIndex];
|
||||
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto availableSize = imgui::to_vec2(available);
|
||||
auto textureSize = vec2(character.render.size);
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Render")))
|
||||
{
|
||||
auto scale =
|
||||
(availableSize.x <= 0.0f || availableSize.y <= 0.0f || textureSize.x <= 0.0f || textureSize.y <= 0.0f)
|
||||
? 0.0f
|
||||
: std::min(availableSize.x / textureSize.x, availableSize.y / textureSize.y);
|
||||
|
||||
auto size = ImVec2(textureSize.x * scale, textureSize.y * scale);
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (size.y * 0.5f),
|
||||
ImGui::GetCursorPosY() + (availableSize.y * 0.5f) - (size.y * 0.5f)));
|
||||
|
||||
ImGui::Image(character.render.id, size);
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("In Game")))
|
||||
{
|
||||
isInGame = true;
|
||||
|
||||
if (previousCharacterIndex != characterIndex)
|
||||
{
|
||||
actor = Actor(resources.characterPreviews[characterIndex].anm2);
|
||||
rect = actor.rect();
|
||||
previousCharacterIndex = characterIndex;
|
||||
}
|
||||
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (availableSize.x <= 0.0f || availableSize.y <= 0.0f || rectSize.x <= 0.0f ||
|
||||
rectSize.y <= 0.0f || !std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(availableSize.x / rectSize.x, availableSize.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
|
||||
canvas.zoom = previewScale * 100.0f;
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(ImVec2(cursorPos.x + (availableSize.x * 0.5f) - ((float)canvasSize.x * 0.5f),
|
||||
cursorPos.y + (availableSize.y * 0.5f) - ((float)canvasSize.y * 0.5f)));
|
||||
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
actor.render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
ImGui::Image(canvas.texture, imgui::to_imvec2(canvasSize));
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
else
|
||||
isInGame = false;
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
21
src/state/select/preview.hpp
Normal file
21
src/state/select/preview.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/actor.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
class Preview
|
||||
{
|
||||
public:
|
||||
int previousCharacterIndex{-1};
|
||||
entity::Actor actor{};
|
||||
glm::vec4 rect{};
|
||||
bool isInGame{};
|
||||
|
||||
Canvas canvas{glm::vec2(), Canvas::FLIP};
|
||||
|
||||
void update(Resources& resources, int characterIndex);
|
||||
void tick(int characterIndex);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user