Whatever The Fuck Man

This commit is contained in:
2026-07-04 00:15:26 -04:00
parent ce77175162
commit 2efed2a443
162 changed files with 13119 additions and 7417 deletions
+175 -126
View File
@@ -21,12 +21,12 @@ namespace game::state
{
namespace
{
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
int durabilityMax)
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
int durability, int durabilityMax)
{
if (durability >= durabilityMax) return -1;
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
}
}
@@ -35,19 +35,29 @@ namespace game::state
{
if (!isWindows) return World::CENTER;
return menu.isOpen && tools.isOpen ? World::MENU_TOOLS
: menu.isOpen ? World::MENU
: tools.isOpen ? World::TOOLS
: World::CENTER;
auto isToolsOpen = tools.isOpen && !menu.is_fullscreen_visible_get();
return menu.isOpen && isToolsOpen ? World::MENU_TOOLS
: menu.isOpen ? World::MENU
: isToolsOpen ? World::TOOLS
: World::CENTER;
}
bool Play::cursor_enabled_get() const
{
auto* root = character.data.cursorSchema.root();
return root && root->type == Schema::Element::CURSOR;
}
void Play::start_sequence_begin()
{
auto& dialogue = character.data.dialogue;
if (!dialogue.start.is_valid()) return;
auto* start = dialogue.get(Schema::Element::START);
if (!start) return;
character.queue_play({.animation = dialogue.start.animation, .isInterruptible = false});
character.tick();
character.play({.animation = start->animation,
.appendID = character.animation_append_id_get(),
.interrupt = Entity::Interrupt::NEVER});
character.update();
text.entry = nullptr;
text.isEnabled = false;
isWindows = false;
@@ -59,7 +69,7 @@ namespace game::state
void Play::end_sequence_begin()
{
auto& dialogue = character.data.dialogue;
if (!dialogue.end.is_valid()) return;
if (!dialogue.get(Schema::Element::END)) return;
text.entry = nullptr;
text.isEnabled = false;
@@ -71,68 +81,92 @@ namespace game::state
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
{
auto& data = resources.character_get(selectedCharacterIndex);
auto& saveData = data.save;
auto* saveRoot = data.save.root();
auto isSaveValid = saveRoot && saveRoot->type == Schema::Element::SAVE;
auto* saveCharacter = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::CHARACTER) : nullptr;
auto* saveInventory = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::INVENTORY) : nullptr;
auto* saveItems = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ITEMS) : nullptr;
auto* savePan = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::PAN) : nullptr;
auto* saveZoom = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ZOOM) : nullptr;
auto& itemSchema = data.itemSchema;
auto& dialogue = data.dialogue;
auto& menuSchema = data.menuSchema;
auto* characterRoot = data.root();
this->characterIndex = selectedCharacterIndex;
resources.last_character_save(selectedCharacterIndex);
cheatCodeIndex = 0;
cheatCodeStartTime = 0.0;
character =
entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
character = Entity(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
character.digestionRate =
glm::clamp(character.digestionRate, (float)data.digestionRateMin, (float)data.digestionRateMax);
character.eatSpeed = glm::clamp(character.eatSpeed, (float)data.eatSpeedMin, (float)data.eatSpeedMax);
character.capacity = glm::clamp(character.capacity, (float)data.capacityMin, (float)data.capacityMax);
glm::clamp(character.digestionRate, characterRoot->digestionRateMin, characterRoot->digestionRateMax);
character.eatSpeed =
glm::clamp(character.eatSpeed, characterRoot->eatSpeedMinMultiplier, characterRoot->eatSpeedMaxMultiplier);
character.capacity =
glm::clamp(character.capacity, characterRoot->capacityMinCalories, characterRoot->capacityMaxCalories);
auto* alternateSpritesheet = data.alternate_spritesheet();
auto isAlternateSpritesheet =
(game == NEW_GAME && math::random_percent_roll(data.alternateSpritesheet.chanceOnNewGame));
alternateSpritesheet && game == NEW_GAME && math::random_percent_roll(alternateSpritesheet->chanceOnNewGame);
if (isAlternateSpritesheet || saveData.isAlternateSpritesheet)
if (isAlternateSpritesheet || (isSaveValid && saveRoot->isAlternateSpritesheet))
{
character.spritesheet_set(entity::Character::ALTERNATE);
if (game == NEW_GAME) character.data.alternateSpritesheet.sound.play();
character.spritesheet_set(Entity::ALTERNATE);
if (game == NEW_GAME && alternateSpritesheet) alternateSpritesheet->soundEntry.sound.play();
}
character.totalCaloriesConsumed = saveData.totalCaloriesConsumed;
character.totalFoodItemsEaten = saveData.totalFoodItemsEaten;
character.totalCaloriesConsumed = saveCharacter ? saveCharacter->totalCaloriesConsumed : 0;
character.totalFoodItemsEaten = saveCharacter ? saveCharacter->totalFoodItemsEaten : 0;
autofeed = play::Autofeed{};
characterManager = CharacterManager{};
particleManager = ParticleManager{};
areaManager = AreaManager{};
cursor = entity::Cursor(character.data.cursorSchema.anm2);
cursor.interactTypeID = character.data.interactTypeNames.empty() ? -1 : 0;
cursor = Entity{};
cursor.entityType = CURSOR;
cursor.interactTypeID = character.data.interact_type_names_get().empty() ? -1 : 0;
autofeedCursor = Entity{};
autofeedCursor.entityType = AUTOCURSOR;
autofeedCursor.interactTypeID = cursor.interactTypeID;
autofeedCursor.position = cursor.position;
autofeedCursor.isVisible = false;
if (cursor_enabled_get())
{
cursor = Entity(character.data.cursorSchema.baseAnm2);
cursor.entityType = CURSOR;
cursor.interactTypeID = character.data.interact_type_names_get().empty() ? -1 : 0;
autofeedCursor = Entity(character.data.cursorSchema.baseAnm2);
autofeedCursor.entityType = AUTOCURSOR;
autofeedCursor.interactTypeID = cursor.interactTypeID;
autofeedCursor.position = cursor.position;
autofeedCursor.isVisible = false;
}
menu.inventory = play::menu::Inventory{};
for (auto& [id, quantity] : saveData.inventory)
for (auto* item : saveInventory ? data.save.children_get(*saveInventory, Schema::Element::ITEM)
: std::vector<Schema::Element*>{})
{
if (quantity == 0) continue;
menu.inventory.values[id] = quantity;
if (item->quantity == 0) continue;
menu.inventory.values[item->id] = item->quantity;
}
itemManager = ItemManager{};
for (auto& item : saveData.items)
for (auto* item :
saveItems ? data.save.children_get(*saveItems, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
{
auto& anm2 = itemSchema.anm2s.at(item.id);
auto& schemaItem = itemSchema.items.at(item.id);
auto durabilityMax = schemaItem.durability.value_or(itemSchema.durability);
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item.durability, durabilityMax);
auto& saveItem = itemSchema.anm2s.at(item.id);
itemManager.items.emplace_back(saveItem, item.position, item.id, item.durability, animationIndex, item.velocity,
item.rotation);
auto& anm2 = itemSchema.anm2s.at(item->id);
auto& schemaItem = itemSchema.items.at(item->id);
auto durabilityMax = schemaItem.isDurability ? schemaItem.durability : itemSchema.durability;
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item->durability, durabilityMax);
auto& saveItem = itemSchema.anm2s.at(item->id);
itemManager.items.emplace_back(saveItem, item->position, item->id, item->durability, animationIndex,
item->velocity, item->rotation);
}
imgui::style::widget_set(menuSchema.rounding);
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
imgui::widget::sounds_set(&menuSchema.root()->soundHover, &menuSchema.root()->soundSelect);
play::style::color_set(resources, character);
menu.arcade = play::menu::Arcade(character);
menu.arcade.skillCheck.totalPlays = saveData.skillCheck.totalPlays;
menu.arcade.skillCheck.highScore = saveData.skillCheck.highScore;
menu.arcade.skillCheck.bestCombo = saveData.skillCheck.bestCombo;
menu.arcade.skillCheck.gradeCounts = saveData.skillCheck.gradeCounts;
menu.arcade.skillCheck.isHighScoreAchieved = saveData.skillCheck.highScore > 0 ? true : false;
menu.arcade.orbit.highScore = saveData.orbit.highScore;
text.entry = nullptr;
text.isEnabled = false;
@@ -142,7 +176,7 @@ namespace game::state
menu.isCheats = false;
#endif
isPostgame = saveData.isPostgame;
isPostgame = isSaveValid && saveRoot->isPostgame;
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
if (isPostgame) menu.isCheats = true;
@@ -150,12 +184,16 @@ namespace game::state
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
character.queue_idle_animation();
character.tick();
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
character.update();
areaManager.set(character);
worldCanvas.size_set(imgui::to_vec2(ImGui::GetMainViewport()->Size));
world.set(character, worldCanvas, focus_get());
if (savePan) worldCanvas.pan = savePan->position;
if (saveZoom && saveZoom->zoom >= World::ZOOM_MIN)
worldCanvas.zoom = glm::clamp(World::ZOOM_MIN, saveZoom->zoom, World::ZOOM_MAX);
if (game == NEW_GAME && dialogue.start.is_valid()) start_sequence_begin();
if (game == NEW_GAME && dialogue.get(Schema::Element::START)) start_sequence_begin();
if (isPostgame)
{
@@ -173,25 +211,13 @@ namespace game::state
void Play::exit(Resources& resources)
{
imgui::style::color_set(resources.settings.color);
imgui::style::color_set(resources.settings.root()->color);
imgui::style::widget_set();
imgui::widget::sounds_set(nullptr, nullptr);
ImGui::GetIO().FontDefault = resources.font.get();
save(resources);
}
void Play::tick(Resources&)
{
character.tick();
cursor.tick();
menu.tick();
toasts.tick();
text.tick(character);
for (auto& item : itemManager.items)
item.tick();
}
void Play::update(Resources& resources)
{
static constexpr std::array<ImGuiKey, 10> CHEAT_CODE = {
@@ -200,10 +226,16 @@ namespace game::state
static constexpr std::array<ImGuiKey, 6> CHEAT_INPUT_KEYS = {
ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A};
static constexpr auto CHEAT_CODE_INPUT_TIME_SECONDS = 5.0;
static constexpr auto UI_ALPHA_MAX = 1.0f;
auto focus = focus_get();
auto& dialogue = character.data.dialogue;
cursor.isVisible = true;
auto isCursorEnabled = cursor_enabled_get();
if (isCursorEnabled)
{
cursor.isVisible = true;
autofeedCursor.isVisible = false;
}
if (!menu.isCheats)
{
@@ -233,7 +265,7 @@ namespace game::state
cheatCodeIndex = 0;
cheatCodeStartTime = 0.0;
toasts.push(character.data.strings.get(Strings::ToastCheatsUnlocked));
character.data.menuSchema.sounds.cheatsActivated.play();
character.data.menuSchema.root()->soundCheatsActivated.play();
}
}
@@ -246,32 +278,19 @@ namespace game::state
if (isWindows)
{
menu.update(resources, itemManager, character, cursor, text, worldCanvas);
tools.update(character, cursor, world, focus, worldCanvas);
info.update(resources, character);
auto isToolsDisabled = menu.is_fullscreen_visible_get();
auto uiAlpha = UI_ALPHA_MAX - menu.fullscreen_alpha_get();
tools.update(character, cursor, world, focus, worldCanvas, isToolsDisabled, uiAlpha);
info.update(resources, character, uiAlpha);
menu.update(resources, itemManager, character, text, autofeed);
toasts.update();
#if DEBUG
if (menu.isDebugOpen && ImGui::IsKeyPressed(ImGuiKey_F8, false)) end_sequence_begin();
#endif
if (menu.debug.isStartSequenceRequested)
{
menu.debug.isStartSequenceRequested = false;
start_sequence_begin();
}
if (menu.debug.isEndSequenceRequested)
{
menu.debug.isEndSequenceRequested = false;
end_sequence_begin();
}
}
auto isEndSequenceActive = isEndBegin && !isEndEnd;
itemManager.isDisabled = isEndSequenceActive;
characterManager.isDisabled = isEndSequenceActive;
if (text.isEnabled) text.update(character);
if (text.isEnabled) text.update(character, worldCanvas);
if (isStart)
{
@@ -279,16 +298,17 @@ namespace game::state
{
if (auto animation = character.animation_get())
{
if (animation->isLoop || character.state == entity::Actor::STOPPED)
if (animation->isLoop || character.state == Entity::STOPPED)
{
text.set(dialogue.get(dialogue.start.id), character);
auto* start = dialogue.get(Schema::Element::START);
text.set(start ? dialogue.dialogue_entry_get(*start) : nullptr, character);
isStartBegin = true;
}
}
}
else if (!isStartEnd)
{
if (text.entry->is_last())
if (text.entry && text.is_terminal() && text.is_finished())
{
isWindows = true;
isStartEnd = true;
@@ -306,7 +326,8 @@ namespace game::state
{
if (character.is_animation_finished())
{
text.set(dialogue.get(dialogue.end.id), character);
auto* end = dialogue.get(Schema::Element::END);
text.set(end ? dialogue.dialogue_entry_get(*end) : nullptr, character);
isEndBegin = true;
isWindows = false;
tools.isOpen = false;
@@ -319,7 +340,7 @@ namespace game::state
}
else if (!isEndEnd)
{
if (text.entry->is_last())
if (text.entry && text.is_terminal() && text.is_finished())
{
menu.isOpen = true;
isWindows = true;
@@ -332,13 +353,29 @@ namespace game::state
}
}
itemManager.update(character, cursor, areaManager, text, World::BOUNDS, worldCanvas);
characterManager.update(character, cursor, text, worldCanvas);
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
auto& interactionCursor = autofeed.active_get() ? autofeedCursor : cursor;
itemManager.isAutomation = autofeed.active_get();
itemManager.update(character, interactionCursor, areaManager, text, World::BOUNDS, worldCanvas);
characterManager.update(character, interactionCursor, text, worldCanvas);
for (auto& particle : itemManager.particles)
particleManager.spawn(character.data, particle.label, particle.position);
for (auto& particle : characterManager.particles)
particleManager.spawn(character.data, particle.label, particle.position);
if (isCursorEnabled && !autofeed.active_get() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
!itemManager.isItemHovered && !characterManager.isHovering && cursor.cursorState == Entity::DEFAULT)
if (auto* cursorRoot = character.data.cursorSchema.root())
if (auto animation = cursorRoot->animationClick.get())
cursor.play({.animation = *animation, .mode = Entity::PLAY_FORCE, .interrupt = Entity::Interrupt::ALWAYS});
character.update();
areaManager.update(character);
particleManager.update();
cursor.update();
if (autofeed.active_get()) autofeedCursor.update();
world.update(character, cursor, worldCanvas, focus);
worldCanvas.tick();
worldCanvas.update();
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
{
@@ -354,70 +391,82 @@ namespace game::state
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);
areaManager.render(character, textureShader, rectShader, worldCanvas);
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);
}
auto isCursorEnabled = cursor_enabled_get();
if (isCursorEnabled && autofeed.active_get()) autofeedCursor.render(textureShader, rectShader, worldCanvas);
particleManager.render(textureShader, rectShader, worldCanvas);
worldCanvas.unbind();
auto isCursor = isCursorEnabled && !resources.isDeveloper;
canvas.bind();
canvas.texture_render(textureShader, worldCanvas, windowModel);
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
cursor.render(textureShader, rectShader, canvas);
if (isCursor) cursor.render(textureShader, rectShader, canvas);
canvas.unbind();
}
void Play::save(Resources& resources)
{
resource::xml::Save save;
resource::xml::Schema save;
auto& root = save.element_add(resource::xml::Schema::Element::SAVE);
root.isPostgame = isPostgame;
root.isAlternateSpritesheet = character.spritesheetType == Entity::ALTERNATE;
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.skillCheck.totalPlays = menu.arcade.skillCheck.totalPlays;
save.skillCheck.highScore = menu.arcade.skillCheck.highScore;
save.skillCheck.bestCombo = menu.arcade.skillCheck.bestCombo;
save.skillCheck.gradeCounts = menu.arcade.skillCheck.gradeCounts;
save.orbit.highScore = menu.arcade.orbit.highScore;
save.isPostgame = isPostgame;
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
auto& panElement = save.element_add(resource::xml::Schema::Element::PAN, 0);
panElement.position = worldCanvas.pan;
auto& zoomElement = save.element_add(resource::xml::Schema::Element::ZOOM, 0);
zoomElement.zoom = worldCanvas.zoom;
auto& characterElement = save.element_add(resource::xml::Schema::Element::CHARACTER, 0);
characterElement.weightKilograms = character.weight;
characterElement.calories = character.calories;
characterElement.capacityCalories = character.capacity;
characterElement.digestionRate = character.digestionRate;
characterElement.eatSpeedMultiplier = character.eatSpeed;
characterElement.digestionProgress = character.digestionProgress;
characterElement.isDigesting = character.isDigesting;
characterElement.digestionTimer = character.digestionTimer;
characterElement.totalCaloriesConsumed = character.totalCaloriesConsumed;
characterElement.totalFoodItemsEaten = character.totalFoodItemsEaten;
auto inventoryIndex = (int)save.elements.size();
save.element_add(resource::xml::Schema::Element::INVENTORY, 0);
for (auto& [id, quantity] : menu.inventory.values)
{
if (quantity == 0) continue;
save.inventory[id] = quantity;
auto& item = save.element_add(resource::xml::Schema::Element::ITEM, inventoryIndex);
item.id = id;
item.quantity = quantity;
}
auto itemsIndex = (int)save.elements.size();
save.element_add(resource::xml::Schema::Element::ITEMS, 0);
for (auto& item : itemManager.items)
save.items.emplace_back(item.schemaID, item.durability, item.position, item.velocity,
*item.overrides[item.rotationOverrideID].frame.rotation);
save.isValid = true;
{
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, itemsIndex);
itemElement.id = item.schemaID;
itemElement.durability = item.durability;
itemElement.position = item.position;
itemElement.velocity = item.velocity;
itemElement.rotation = *item.overrides[item.rotationOverrideID].frame.rotation;
}
resources.character_save_set(characterIndex, save);
save.serialize(character.data.save_path_get());
+8 -3
View File
@@ -3,10 +3,12 @@
#include "../resources.hpp"
#include "play/area_manager.hpp"
#include "play/autofeed.hpp"
#include "play/character_manager.hpp"
#include "play/info.hpp"
#include "play/item_manager.hpp"
#include "play/menu.hpp"
#include "play/particle_manager.hpp"
#include "play/text.hpp"
#include "play/toasts.hpp"
#include "play/tools.hpp"
@@ -25,8 +27,9 @@ namespace game::state
CONTINUE
};
entity::Character character;
entity::Cursor cursor;
Entity character;
Entity cursor;
Entity autofeedCursor;
play::Info info;
play::Menu menu;
@@ -34,7 +37,9 @@ namespace game::state
play::Text text;
play::World world;
play::Toasts toasts;
play::Autofeed autofeed{};
play::ItemManager itemManager{};
play::ParticleManager particleManager{};
play::CharacterManager characterManager{};
play::AreaManager areaManager{};
@@ -60,10 +65,10 @@ namespace game::state
Canvas worldCanvas{play::World::SIZE};
Play() = default;
bool cursor_enabled_get() const;
void set(Resources&, int characterIndex, Game = CONTINUE);
void exit(Resources& resources);
void update(Resources&);
void tick(Resources&);
void render(Resources&, Canvas&);
void save(Resources&);
play::World::Focus focus_get();
+53 -6
View File
@@ -1,26 +1,73 @@
#include "area_manager.hpp"
#include <imgui.h>
#include "../../util/math.hpp"
using namespace game::resource;
using namespace game::util;
namespace game::state::play
{
int AreaManager::get(entity::Character& character)
int AreaManager::index_get(Entity& character)
{
auto& data = character.data;
auto& schema = data.areaSchema;
if (schema.areas.empty()) return -1;
auto size = (int)data.stages.size();
auto stages = data.stages_get();
auto size = (int)stages.size();
for (int i = 0; i < size; i++)
{
auto& stage = data.stages[size - i - 1];
if (stage.areaID != -1) return stage.areaID;
auto* stage = stages[size - i - 1];
if (stage->areaID >= 0 && stage->areaID < (int)schema.areas.size()) return stage->areaID;
}
return -1;
return 0;
}
game::resource::xml::Schema::AreaEntry* AreaManager::get(Entity& character)
{
auto index = index_get(character);
if (index == -1) return nullptr;
return &character.data.areaSchema.areas[index];
}
void AreaManager::set(Entity& character)
{
entities.clear();
auto& areas = character.data.areaSchema.areas;
entities.reserve(areas.size());
for (auto& area : areas)
entities.emplace_back(area.anm2.is_valid() ? Entity(area.anm2) : Entity{});
}
void AreaManager::update(Entity& character)
{
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
auto index = index_get(character);
if (index == -1 || index >= (int)entities.size()) return;
entities[index].update();
}
void AreaManager::render(Entity& character, Shader& textureShader, Shader& rectShader, Canvas& canvas)
{
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
auto index = index_get(character);
if (index == -1) return;
auto& area = character.data.areaSchema.areas[index];
if (index < (int)entities.size() && area.anm2.is_valid())
{
entities[index].render(textureShader, rectShader, canvas);
return;
}
if (!area.texture.is_valid()) return;
auto worldModel = math::quad_model_get(area.texture.size);
canvas.texture_render(textureShader, area.texture.id, worldModel);
}
}
+11 -2
View File
@@ -1,12 +1,21 @@
#pragma once
#include "../../entity/character.hpp"
#include <vector>
#include "../../entity.hpp"
#include "../../resource/shader.hpp"
namespace game::state::play
{
class AreaManager
{
public:
int get(entity::Character&);
std::vector<Entity> entities{};
int index_get(Entity&);
game::resource::xml::Schema::AreaEntry* get(Entity&);
void set(Entity&);
void update(Entity&);
void render(Entity&, game::resource::Shader&, game::resource::Shader&, Canvas&);
};
}
+448
View File
@@ -0,0 +1,448 @@
#include "autofeed.hpp"
#include "../../util/imgui.hpp"
#include "../../util/math.hpp"
#include <cmath>
#include <imgui.h>
using namespace game::resource::xml;
using namespace game::util;
namespace game::state::play
{
namespace
{
constexpr float CURSOR_SPEED = 9.0f;
constexpr float DIGEST_CURSOR_SPEED = 3.0f;
constexpr float CURSOR_SPEED_REFERENCE_AREA = 512.0f * 512.0f;
constexpr float CURSOR_SPEED_SCALE_MIN = 0.5f;
constexpr float CURSOR_SPEED_SCALE_MAX = 2.0f;
constexpr float CALORIE_EPSILON = 0.001f;
glm::vec2 rect_center(glm::vec4 rect) { return {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; }
bool is_finite(glm::vec2 value) { return std::isfinite(value.x) && std::isfinite(value.y); }
bool is_finite(glm::vec4 value)
{
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
value.z > 0.0f && value.w > 0.0f;
}
bool move_toward(Entity& cursor, glm::vec2 target, float speed)
{
if (!is_finite(cursor.position)) cursor.position = {};
if (!is_finite(target)) return false;
auto delta = target - cursor.position;
auto distance = glm::length(delta);
if (!std::isfinite(distance)) return false;
if (distance <= speed || distance <= 0.001f)
{
cursor.position = target;
return true;
}
cursor.position += delta / distance * speed;
return false;
}
float cursor_speed_scale_get(Entity& character)
{
auto rect = character.rect();
if (!is_finite(rect)) return 1.0f;
auto area = rect.z * rect.w;
if (!std::isfinite(area) || area <= 0.0f) return 1.0f;
return glm::clamp(std::sqrt(area / CURSOR_SPEED_REFERENCE_AREA), CURSOR_SPEED_SCALE_MIN, CURSOR_SPEED_SCALE_MAX);
}
bool is_food(const Schema& schema, const Schema::ItemEntry& item)
{
return item.categoryID >= 0 && item.categoryID < (int)schema.categories.size() &&
schema.categories[item.categoryID].isEdible;
}
bool is_negative_effect(const Schema::ItemEntry& item)
{
return (item.isCapacityBonus && item.capacityBonus < 0.0f) ||
(item.isEatSpeedBonus && item.eatSpeedBonus < 0.0f) ||
(item.isDigestionBonus && item.digestionBonus < 0.0f);
}
bool is_autofeed_food(const Schema& schema, const Schema::ItemEntry& item)
{
return is_food(schema, item) && !is_negative_effect(item);
}
int durability_max_get(const Schema& schema, const Schema::ItemEntry& item)
{
return item.isDurability ? item.durability : schema.durability;
}
float calories_per_bite_get(const Schema& schema, const Schema::ItemEntry& item)
{
auto durabilityMax = durability_max_get(schema, item);
return item.isCalories && durabilityMax > 0 ? item.calories / (float)durabilityMax : 0.0f;
}
bool can_eat(const Entity& character, const Schema& schema, const Schema::ItemEntry& item)
{
auto caloriesPerBite = calories_per_bite_get(schema, item);
return caloriesPerBite > 0.0f && character.calories + caloriesPerBite <= character.max_capacity();
}
struct FoodScore
{
float calories{};
int bites{};
float caloriesPerBite{};
bool isValid{};
};
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item,
int durability)
{
if (!is_autofeed_food(schema, item)) return {};
auto caloriesPerBite = calories_per_bite_get(schema, item);
auto remainingCapacity = character.max_capacity() - character.calories;
auto durabilityMax = durability_max_get(schema, item);
auto remainingBites = glm::max(0, durabilityMax - durability);
if (caloriesPerBite <= 0.0f || remainingCapacity < caloriesPerBite || remainingBites <= 0) return {};
auto bites = glm::clamp((int)std::floor(remainingCapacity / caloriesPerBite), 0, remainingBites);
auto calories = (float)bites * caloriesPerBite;
return {.calories = calories, .bites = bites, .caloriesPerBite = caloriesPerBite, .isValid = bites > 0};
}
bool is_better_food_score(const FoodScore& score, const FoodScore& best)
{
if (!score.isValid) return false;
if (!best.isValid) return true;
if (score.calories != best.calories) return score.calories > best.calories;
if (score.bites != best.bites) return score.bites < best.bites;
return score.caloriesPerBite > best.caloriesPerBite;
}
bool can_eat_anything(Entity& character, ItemManager& itemManager, menu::Inventory& inventory)
{
auto& schema = character.data.itemSchema;
for (auto& entity : itemManager.items)
{
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
auto& item = schema.items[entity.schemaID];
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
}
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
auto& item = schema.items[id];
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
}
return false;
}
int best_inventory_food_get(Entity& character, menu::Inventory& inventory)
{
auto& schema = character.data.itemSchema;
auto bestID = -1;
auto bestScore = FoodScore{};
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
auto& item = schema.items[id];
auto score = food_score_get(character, schema, item, 0);
if (is_better_food_score(score, bestScore))
{
bestScore = score;
bestID = id;
}
}
return bestID;
}
bool is_inventory_food(Entity& character, menu::Inventory& inventory)
{
auto& schema = character.data.itemSchema;
for (auto& [id, quantity] : inventory.values)
{
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
if (is_autofeed_food(schema, schema.items[id])) return true;
}
return false;
}
bool is_world_food(Entity& character, ItemManager& itemManager)
{
auto& schema = character.data.itemSchema;
for (auto& entity : itemManager.items)
{
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
if (is_autofeed_food(schema, schema.items[entity.schemaID])) return true;
}
return false;
}
bool is_food(Entity& character, menu::Inventory& inventory, ItemManager& itemManager)
{
return character.calories > CALORIE_EPSILON || is_inventory_food(character, inventory) ||
is_world_food(character, itemManager);
}
int best_world_food_get(Entity& character, ItemManager& itemManager, bool requireCanEat)
{
auto& schema = character.data.itemSchema;
auto bestIndex = -1;
auto bestScore = FoodScore{};
for (int i = 0; i < (int)itemManager.items.size(); i++)
{
auto& entity = itemManager.items[i];
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
auto& item = schema.items[entity.schemaID];
if (!is_autofeed_food(schema, item)) continue;
if (requireCanEat && !can_eat(character, schema, item)) continue;
auto score = food_score_get(character, schema, item, entity.durability);
if (is_better_food_score(score, bestScore))
{
bestScore = score;
bestIndex = i;
}
}
return bestIndex;
}
bool eat_rect_get(Entity& character, glm::vec4& rect)
{
for (auto* eatArea : character.data.eat_areas_get())
{
if (!eatArea) continue;
auto nullID = character.data.null_id_get(eatArea->null);
if (nullID == -1) continue;
rect = character.null_frame_rect(nullID);
if (is_finite(rect)) return true;
auto animationIndex = character.animation_index_get(eatArea->animation + character.animation_append_id_get());
rect = character.null_frame_rect(nullID, animationIndex, 0.0f);
if (is_finite(rect)) return true;
}
return false;
}
const Schema::Element* digestion_area_get(Entity& character)
{
for (auto* interactArea : character.data.interact_areas_get())
{
if (!interactArea) continue;
if (interactArea->digestionBonusOnClick <= 0.0f && interactArea->digestionBonusOnHover <= 0.0f) continue;
if (character.data.null_id_get(interactArea->null) == -1) continue;
return interactArea;
}
return nullptr;
}
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& digestClickCooldown)
{
isItemMouseHeld = false;
isDigestMouseHeld = false;
digestClickCooldown = 0;
if (!is_finite(cursor.position)) cursor.position = {};
}
}
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory& inventory,
ItemManager& itemManager, CharacterManager& characterManager, Canvas& canvas)
{
isActive = false;
cursor.isVisible = false;
if (!isEnabled)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
if (!is_food(character, inventory, itemManager))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
if (character.isStageUp || character.isJustStageUp || character.isJustStageFinal)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
if (cursor.position == glm::vec2{} && is_finite(character.position)) cursor.position = character.position;
auto& schema = character.data.itemSchema;
if (inventory.upgrade_all_possible(schema))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto itemInput = ItemManager::Input{};
itemInput.isAutomation = true;
auto characterInput = CharacterManager::Input{};
auto speedScale = cursor_speed_scale_get(character);
auto cursorSpeed = CURSOR_SPEED * speedScale;
auto digestCursorSpeed = DIGEST_CURSOR_SPEED * speedScale;
auto eatRect = glm::vec4{};
auto isEatTarget = eat_rect_get(character, eatRect);
if (itemManager.heldItemIndex != -1 && itemManager.heldItemIndex < (int)itemManager.items.size())
{
auto& heldItem = itemManager.items[itemManager.heldItemIndex];
if (heldItem.schemaID < 0 || heldItem.schemaID >= (int)schema.items.size())
{
isActive = false;
cursor.isVisible = false;
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto& schemaItem = schema.items[heldItem.schemaID];
if (isEatTarget && can_eat(character, schema, schemaItem))
{
isActive = true;
cursor.isVisible = true;
move_toward(cursor, rect_center(eatRect), cursorSpeed);
itemInput.isMouseLeftDown = true;
itemManager.inputOverride = itemInput;
isItemMouseHeld = true;
isDigestMouseHeld = false;
return;
}
itemInput.isMouseLeftReleased = true;
itemManager.inputOverride = itemInput;
isActive = true;
cursor.isVisible = true;
isItemMouseHeld = false;
return;
}
auto bestWorldFood = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
if (bestWorldFood != -1)
{
auto& item = itemManager.items[bestWorldFood];
auto rect = item.rect();
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
isActive = true;
cursor.isVisible = true;
move_toward(cursor, rect_center(rect), cursorSpeed);
auto isInItemRect = math::is_point_in_rectf(rect, cursor.position);
itemInput.isMouseLeftClicked = isInItemRect && !isItemMouseHeld;
itemInput.isMouseLeftDown = isInItemRect || isItemMouseHeld;
itemManager.inputOverride = itemInput;
if (isInItemRect) isItemMouseHeld = true;
isDigestMouseHeld = false;
return;
}
auto isInventoryFood = is_inventory_food(character, inventory);
auto isWorldFood = is_world_food(character, itemManager);
if (character.calories <= CALORIE_EPSILON && !isInventoryFood && !isWorldFood)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto bestInventoryFood = isEatTarget ? best_inventory_food_get(character, inventory) : -1;
if (bestInventoryFood != -1 && !isWorldFood && (int)itemManager.items.size() < ItemManager::LIMIT)
{
itemManager.queuedItemIDs.emplace_back(bestInventoryFood);
inventory.values[bestInventoryFood]--;
schema.root()->soundSummon.play();
cursor.isVisible = false;
return;
}
if ((isWorldFood && bestWorldFood == -1) || !can_eat_anything(character, itemManager, inventory))
{
auto* interactArea = digestion_area_get(character);
if (!interactArea)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto rect = character.null_frame_rect(character.data.null_id_get(interactArea->null));
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
return;
}
auto radius = glm::max(1.0f, glm::min(rect.z, rect.w) * 0.25f);
digestAngle += 0.08f;
auto targetWorld = rect_center(rect) + glm::vec2(std::cos(digestAngle), std::sin(digestAngle)) * radius;
auto isAtTarget = move_toward(cursor, targetWorld, digestCursorSpeed);
isActive = true;
cursor.isVisible = true;
cursor.interactTypeID = character.data.interact_type_id_get(interactArea->typeString);
if (interactArea->digestionBonusOnHover > 0.0f)
{
auto isInDigestRect = math::is_point_in_rectf(rect, cursor.position);
characterInput.isMouseLeftClick = isInDigestRect && !isDigestMouseHeld;
characterInput.isMouseLeftDown = isInDigestRect;
characterInput.isMouseLeftReleased = isDigestMouseHeld && !isInDigestRect;
isDigestMouseHeld = isInDigestRect;
}
else
{
if (digestClickCooldown > 0) digestClickCooldown--;
characterInput.isMouseLeftClick = isAtTarget && digestClickCooldown <= 0 && !isDigestMouseHeld;
characterInput.isMouseLeftReleased = isDigestMouseHeld;
if (characterInput.isMouseLeftClick)
{
isDigestMouseHeld = true;
digestClickCooldown = 45;
}
else if (characterInput.isMouseLeftReleased)
isDigestMouseHeld = false;
}
characterManager.inputOverride = characterInput;
isItemMouseHeld = false;
return;
}
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
}
void Autofeed::toggle() { isEnabled = !isEnabled; }
void Autofeed::disable() { isEnabled = false; }
bool Autofeed::available_get(Entity& character, menu::Inventory& inventory, ItemManager& itemManager) const
{
return is_food(character, inventory, itemManager);
}
bool Autofeed::enabled_get() const { return isEnabled; }
bool Autofeed::active_get() const { return isActive; }
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "../../resources.hpp"
#include "character_manager.hpp"
#include "item_manager.hpp"
#include "menu/inventory.hpp"
namespace game::state::play
{
class Autofeed
{
bool isItemMouseHeld{};
bool isDigestMouseHeld{};
int digestClickCooldown{};
float digestAngle{};
bool isEnabled{};
bool isActive{};
public:
void update(Resources&, Entity& character, Entity& cursor, menu::Inventory&, ItemManager&,
CharacterManager&, Canvas&);
void toggle();
void disable();
bool available_get(Entity& character, menu::Inventory&, ItemManager&) const;
bool enabled_get() const;
bool active_get() const;
};
}
+109 -43
View File
@@ -3,6 +3,7 @@
#include "../../util/math.hpp"
#include <imgui.h>
#include <algorithm>
#include <optional>
using namespace game::resource::xml;
@@ -10,13 +11,18 @@ using namespace game::util;
namespace game::state::play
{
void CharacterManager::update(entity::Character& character, entity::Cursor& cursor, Text& text, Canvas& canvas)
void CharacterManager::update(Entity& character, Entity& cursor, Text& text, Canvas& canvas)
{
auto interact_area_override_tick = [](entity::Actor::Override& override_)
static constexpr auto SCALE_EFFECT_TIME_TICKS_DEFAULT = 20.0f;
static constexpr auto SCALE_EFFECT_CYCLES_DEFAULT = 1.0f;
static constexpr auto ZERO_FLOAT = 0.0f;
auto interact_area_override_tick = [](Entity::Override& override_)
{
auto& scale = override_.frame.scale;
auto& scaleBase = override_.frameBase.scale;
auto isScaleValid = scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();
auto isScaleValid =
scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();
if (isScaleValid && override_.time.has_value() && override_.timeStart.has_value())
{
@@ -34,11 +40,22 @@ namespace game::state::play
};
auto& dialogue = character.data.dialogue;
auto cursorWorldPosition = canvas.screen_position_convert(cursor.position);
auto cursorWorldPosition =
cursor.entityType == AUTOCURSOR ? cursor.position : canvas.screen_position_convert(cursor.position);
auto isMouseLeftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
if (inputOverride)
{
isImguiCaptureMouse = inputOverride->isImguiCaptureMouse;
isMouseLeftClick = inputOverride->isMouseLeftClick;
isMouseLeftDown = inputOverride->isMouseLeftDown;
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
inputOverride.reset();
}
if (isDisabled)
{
isInteracting = false;
@@ -52,76 +69,121 @@ namespace game::state::play
isHoveringPrevious = isHovering;
isHoldInteractingPrevious = isHoldInteracting;
isHovering = false;
particles.clear();
if (!isInteracting) isHoldInteracting = false;
if (isJustStoppedHoldInteracting)
{
cursor.queue_play({cursor.defaultAnimation});
if (character.queuedPlay.empty()) character.queue_idle_animation();
cursor.play({cursor.defaultAnimation});
if (!character.is_animation_request())
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
isJustStoppedHoldInteracting = false;
}
else if (isJustStoppedInteracting)
{
cursor.queue_play({cursor.defaultAnimation});
cursor.play({cursor.defaultAnimation});
isJustStoppedInteracting = false;
}
if (isJustStoppedHovering)
{
cursor.queue_play({cursor.defaultAnimation});
cursor.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);
auto interactAreas = character.data.interact_areas_get();
if (interactAreaDialogueTimers.size() != interactAreas.size())
interactAreaDialogueTimers.assign(interactAreas.size(), 0);
if (!text.entry) std::ranges::fill(interactAreaDialogueTimers, 0);
for (auto& timer : interactAreaDialogueTimers)
if (timer > 0) timer--;
if (cursor.state == entity::Cursor::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
!isImguiCaptureMouse && interactArea.typeID == cursor.interactTypeID)
for (int i = 0; i < (int)interactAreas.size(); i++)
{
auto* interactArea = interactAreas.at(i);
auto nullID = character.data.null_id_get(interactArea->null);
auto layerID = character.data.layer_id_get(interactArea->layer);
if (nullID == -1) continue;
auto rect = character.null_frame_rect(nullID);
if (cursor.cursorState == Entity::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
!isImguiCaptureMouse &&
character.data.interact_type_id_get(interactArea->typeString) == cursor.interactTypeID)
{
cursor.state = entity::Cursor::HOVER;
cursor.queue_play({interactArea.animationCursorHover});
cursor.cursorState = Entity::HOVER;
cursor.play({.animation = interactArea->animationCursorHover, .transition = Entity::Transition::IF_IDLE});
isHovering = true;
interactAreaID = i;
if (isMouseLeftClick)
auto interact_area_proc = [&](bool isInteract)
{
isInteracting = true;
isHoldInteracting = interactArea.isHold;
interactArea.sound.play();
if (interactArea.digestionBonusClick > 0 && character.calories > 0 && !character.isDigesting)
character.digestionProgress += interactArea.digestionBonusClick;
if (interactArea.layerID != -1)
if (isInteract)
{
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));
isInteracting = true;
isHoldInteracting = interactArea->isHold;
}
else
{
cursor.cursorState = Entity::ACTION;
cursor.play({.animation = interactArea->animationCursorActive,
.mode = Entity::PLAY_FORCE,
.interrupt = Entity::Interrupt::ALWAYS});
}
interactArea->soundEntries.play();
for (auto* particle : character.data.children_get(*interactArea, Schema::Element::PARTICLE))
if (particle && !particle->typeString.empty())
particles.push_back({.label = particle->typeString, .position = cursorWorldPosition});
if (interactArea->digestionBonusOnClick > 0 && character.calories > 0 && !character.isDigesting)
character.digestionProgress += interactArea->digestionBonusOnClick;
if (layerID != -1)
{
auto scaleEffectTimeTicks = interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT
: interactArea->timeTicks;
auto scaleEffectCycles = interactArea->scaleEffectCycles == ZERO_FLOAT ? SCALE_EFFECT_CYCLES_DEFAULT
: interactArea->scaleEffectCycles;
character.overrides.emplace_back(Entity::Override(
layerID, Anm2::LAYER, Entity::Override::ADD, {.scale = glm::vec2(interactArea->scaleEffectAmplitude)},
std::optional<float>(scaleEffectTimeTicks), interact_area_override_tick, scaleEffectCycles));
}
if (text.is_interruptible())
auto isDialogueReady = interactAreaDialogueTimers[i] <= 0;
interactAreaDialogueTimers[i] = std::max(0, interactArea->timeToActivateNewDialogueTicks);
auto poolFullID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolIDFull);
auto poolID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolID);
if (character.is_over_capacity() && poolFullID != -1) poolID = poolFullID;
if (poolID != -1)
{
auto& pool = character.is_over_capacity() && interactArea.poolFull.is_valid() ? interactArea.poolFull
: interactArea.pool;
if (pool.is_valid())
text.set(dialogue.get(pool), character);
if (text.is_interruptible() && isDialogueReady)
{
auto* entry = dialogue.dialogue_pool_entry_get(poolID);
if (entry) text.set(entry, character);
}
}
}
else if (interactArea->dialoguePoolID.empty() && !interactArea->animation.empty())
character.play({.animation = interactArea->animation,
.appendID = character.animation_append_id_get(),
.mode = Entity::PLAY_FORCE});
};
if (interactArea->isHold && (isMouseLeftClick || (isMouseLeftDown && !isInteracting)))
interact_area_proc(true);
else if (!interactArea->isHold && isMouseLeftClick)
interact_area_proc(false);
if (isInteracting)
{
cursor.state = entity::Cursor::ACTION;
cursor.queue_play({interactArea.animationCursorActive});
cursor.cursorState = Entity::ACTION;
cursor.play({interactArea->animationCursorActive});
if (interactArea.digestionBonusRub > 0 && character.calories > 0 && !character.isDigesting)
if (interactArea->digestionBonusOnHover > 0 && character.calories > 0 && !character.isDigesting)
{
auto mouseDelta = cursorWorldPosition - cursorWorldPositionPrevious;
auto digestionBonus = (fabs(mouseDelta.x) + fabs(mouseDelta.y)) * interactArea.digestionBonusRub;
character.digestionProgress += digestionBonus;
if (fabs(mouseDelta.x) > 0.0f || fabs(mouseDelta.y) > 0.0f)
character.digestionProgress += interactArea->digestionBonusOnHover;
}
}
}
@@ -141,7 +203,11 @@ namespace game::state::play
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);
if (character.isJustDigested && text.is_interruptible())
if (auto* digest = dialogue.get(Schema::Element::DIGEST))
text.set(dialogue.dialogue_pool_entry_get(*digest), character);
if (character.isJustStageUp)
if (auto* stageUp = dialogue.get(Schema::Element::STAGE_UP))
text.set(dialogue.dialogue_pool_entry_get(*stageUp), character);
}
}
+23 -3
View File
@@ -1,9 +1,11 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../entity/cursor.hpp"
#include "../../entity.hpp"
#include "text.hpp"
#include <optional>
#include <vector>
namespace game::state::play
{
class CharacterManager
@@ -20,10 +22,28 @@ namespace game::state::play
bool isHoldInteractingPrevious{};
bool isJustStoppedHoldInteracting{};
int interactAreaID{-1};
std::vector<int> interactAreaDialogueTimers{};
glm::vec2 cursorWorldPositionPrevious{};
std::string queuedAnimation{};
void update(entity::Character&, entity::Cursor&, Text&, Canvas&);
struct Particle
{
std::string label{};
glm::vec2 position{};
};
struct Input
{
bool isImguiCaptureMouse{};
bool isMouseLeftClick{};
bool isMouseLeftDown{};
bool isMouseLeftReleased{};
};
std::optional<Input> inputOverride{};
std::vector<Particle> particles{};
void update(Entity&, Entity&, Text&, Canvas&);
};
}
+23 -17
View File
@@ -12,7 +12,7 @@ using namespace game::resource::xml;
namespace game::state::play
{
void Cheats::update(Resources&, entity::Character& character, menu::Inventory& inventory)
void Cheats::update(Resources&, Entity& character, menu::Inventory& inventory)
{
auto& strings = character.data.strings;
@@ -20,16 +20,20 @@ namespace game::state::play
{
auto stage = character.stage + 1;
auto maxCapacity = (float)character.max_capacity();
auto capacityMin = (float)character.data.capacityMin;
auto capacityMax = (float)character.data.capacityMax;
auto weightMin = (float)character.data.weight;
auto weightMax = (float)character.data.weightMax;
auto digestionRateMin = (float)character.data.digestionRateMin;
auto digestionRateMax = (float)character.data.digestionRateMax;
auto eatSpeedMin = (float)character.data.eatSpeedMin;
auto eatSpeedMax = (float)character.data.eatSpeedMax;
auto capacityMin = (float)character.data.root()->capacityMinCalories;
auto capacityMax = (float)character.data.root()->capacityMaxCalories;
auto weightMin = (float)character.data.root()->weightKilograms;
auto weightMax = (float)character.data.root()->weightMaxKilograms;
auto digestionRateMin = (float)character.data.root()->digestionRateMin * Entity::UPDATE_RATE;
auto digestionRateMax = (float)character.data.root()->digestionRateMax * Entity::UPDATE_RATE;
auto digestionRate = character.digestion_rate_get();
auto eatSpeedMin = (float)character.data.root()->eatSpeedMinMultiplier;
auto eatSpeedMax = (float)character.data.root()->eatSpeedMaxMultiplier;
auto weight_update = [&]() { character.queue_idle_animation(); };
auto weight_update = [&]()
{
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
};
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCalories).c_str(), &character.calories, 0.0f, maxCapacity,
"%0.0f kcal"));
@@ -41,24 +45,26 @@ namespace game::state::play
strings.get(Strings::CheatsWeightFormat).c_str())))
weight_update();
auto stages = character.data.stages_get();
if (WIDGET_FX(ImGui::SliderInt(strings.get(Strings::CheatsStage).c_str(), &stage, 1,
(int)character.data.stages.size() + 1)))
(int)stages.size() + 1)))
{
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
character.stage = glm::clamp(0, stage - 1, (int)stages.size());
character.weight =
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
character.stage == 0 ? character.data.root()->weightKilograms : stages.at(character.stage - 1)->thresholdKilograms;
weight_update();
}
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &character.digestionRate,
digestionRateMin, digestionRateMax,
strings.get(Strings::CheatsDigestionRateFormat).c_str()));
if (WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &digestionRate,
digestionRateMin, digestionRateMax,
strings.get(Strings::CheatsDigestionRateFormat).c_str())))
character.digestionRate = digestionRate / Entity::UPDATE_RATE;
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsEatSpeed).c_str(), &character.eatSpeed, eatSpeedMin,
eatSpeedMax,
strings.get(Strings::CheatsEatSpeedFormat).c_str()));
if (WIDGET_FX(ImGui::Button(strings.get(Strings::CheatsDigestButton).c_str())))
character.digestionProgress = entity::Character::DIGESTION_MAX;
character.digestionProgress = Entity::DIGESTION_MAX;
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
+1 -1
View File
@@ -10,6 +10,6 @@ namespace game::state::play
class Cheats
{
public:
void update(Resources&, entity::Character&, menu::Inventory&);
void update(Resources&, Entity&, menu::Inventory&);
};
}
-78
View File
@@ -1,78 +0,0 @@
#include "debug.hpp"
#include "../../util/imgui/widget.hpp"
#include <ranges>
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play
{
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
Text& text)
{
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);
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 (character.data.dialogue.start.is_valid())
if (WIDGET_FX(ImGui::Button("Play Start Sequence"))) isStartSequenceRequested = true;
if (character.data.dialogue.end.is_valid())
if (WIDGET_FX(ImGui::Button("Play End Sequence"))) isEndSequenceRequested = true;
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();
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::TextUnformatted("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("Durability: %i", item.durability);
ImGui::Separator();
}
}
}
}
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../entity/cursor.hpp"
#include "item_manager.hpp"
#include "text.hpp"
#include <imgui.h>
namespace game::state::play
{
class Debug
{
public:
bool isBoundsDisplay{};
bool isStartSequenceRequested{};
bool isEndSequenceRequested{};
void update(entity::Character&, entity::Cursor&, ItemManager&, Canvas&, Text&);
};
}
+116 -99
View File
@@ -14,119 +14,136 @@ using namespace game::util;
namespace game::state::play
{
void Info::update(Resources& resources, entity::Character& character)
float info_height_get()
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
return ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER;
}
void info_content_draw(Resources& resources, Entity& character, ImVec2 size)
{
static constexpr auto HALF_MULTIPLIER = 0.5f;
static constexpr auto ZERO_FLOAT = 0.0f;
auto& strings = character.data.strings;
auto& style = ImGui::GetStyle();
auto childSize = ImVec2((size.x - style.ItemSpacing.x) * HALF_MULTIPLIER, size.y);
if (ImGui::BeginChild("##Weight", childSize))
{
auto* settings = resources.settings.root();
auto system = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
auto weight = character.weight_get(system);
auto stage = character.stage_get();
auto stageMax = character.stage_max_get();
auto stageCount = character.stage_count_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::text_unformatted_fit_draw(weightString.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2),
Font::HEADER_2);
ImGui::SetItemTooltip("%s", weightString.c_str());
auto stageProgress = character.stage_progress_get();
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(stage >= stageMax ? Strings::InfoProgressMax : Strings::InfoProgressToNextStage)
.c_str());
if (ImGui::BeginItemTooltip())
{
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageCount,
math::to_percent(stageProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
if (stage >= stageMax)
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
else
{
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), 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(ZERO_FLOAT, (calories - capacity) / (character.max_capacity() - capacity));
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
character.is_over_capacity() ? character.max_capacity() : character.capacity);
imgui::text_unformatted_fit_draw(caloriesString.c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2), Font::HEADER_2);
ImGui::SetItemTooltip("%s", caloriesString.c_str());
ImGui::PopStyleColor();
auto digestionProgress = character.isDigesting
? (float)character.digestionTimer / character.data.root()->digestionTimerMax
: character.digestionProgress / Entity::DIGESTION_MAX;
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
strings.get(character.isDigesting ? Strings::InfoDigesting : Strings::InfoDigestion).c_str());
if (ImGui::BeginItemTooltip())
{
if (character.isDigesting)
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
else if (digestionProgress <= ZERO_FLOAT)
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
else
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
}
void Info::update(Resources& resources, Entity& character, float fadeAlpha)
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto ALPHA_MIN = 0.0f;
static constexpr auto ALPHA_MAX = 1.0f;
static constexpr auto STYLE_VAR_COUNT = 1;
if (fadeAlpha <= ALPHA_MIN) return;
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);
info_height_get());
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);
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
if (fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
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::HEADER_1);
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),
strings.get(stage >= stageMax ? Strings::InfoProgressMax
: Strings::InfoProgressToNextStage)
.c_str());
if (ImGui::BeginItemTooltip())
{
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageMax + 1,
math::to_percent(stageProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
if (stage >= stageMax)
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
else
{
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), 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::HEADER_1);
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),
strings.get(character.isDigesting ? Strings::InfoDigesting
: Strings::InfoDigestion)
.c_str());
if (ImGui::BeginItemTooltip())
{
if (character.isDigesting)
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
else if (digestionProgress <= 0.0f)
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
else
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
ImGui::PopStyleColor();
ImGui::EndTooltip();
}
}
ImGui::EndChild();
}
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * fadeAlpha);
if (ImGui::Begin("##Info", nullptr, flags))
info_content_draw(resources, character, ImGui::GetContentRegionAvail());
ImGui::End();
ImGui::PopStyleVar(STYLE_VAR_COUNT);
}
}
+5 -2
View File
@@ -1,15 +1,18 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../entity.hpp"
#include "../../resources.hpp"
#include <imgui.h>
namespace game::state::play
{
float info_height_get();
void info_content_draw(Resources&, Entity&, ImVec2);
class Info
{
public:
void update(Resources&, entity::Character&);
void update(Resources&, Entity&, float fadeAlpha);
};
}
-75
View File
@@ -1,75 +0,0 @@
#include "reward.hpp"
#include "../../../util/math.hpp"
using namespace game::util;
namespace game::state::play::item
{
int Reward::random_item_get(const resource::xml::Item& itemSchema, float chanceBonus)
{
const resource::xml::Item::Pool* pool{};
auto totalWeight = 0.0f;
for (auto& id : itemSchema.rarityIDs)
{
auto& rarity = itemSchema.rarities[id];
if (rarity.weight <= 0.0f) continue;
totalWeight += rarity.weight * chanceBonus;
}
if (totalWeight <= 0.0f) return INVALID_ID;
auto roll = math::random_roll(totalWeight);
for (auto& id : itemSchema.rarityIDs)
{
auto& rarity = itemSchema.rarities[id];
auto weight = rarity.weight * chanceBonus;
if (weight <= 0.0f) continue;
roll -= weight;
if (roll <= 0.0f)
{
pool = &itemSchema.pools.at(id);
rarity.sound.play();
break;
}
}
if (!pool || pool->empty()) return INVALID_ID;
return (*pool)[(int)math::random_roll((float)pool->size())];
}
void Reward::item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
const resource::xml::Item& itemSchema, const ImVec4& bounds, menu::ItemEffectManager::Mode mode)
{
if (itemID < 0) return;
inventory.values[itemID]++;
itemEffectManager.spawn(itemID, itemSchema, bounds, mode);
}
int Reward::reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
float rewardRollCount, menu::ItemEffectManager::Mode mode)
{
auto rollCountWhole = std::max(0, (int)std::floor(rewardRollCount));
auto rollCountFraction = std::max(0.0f, rewardRollCount - (float)rollCountWhole);
auto rollCount = rollCountWhole + (math::random_percent_roll(rollCountFraction) ? 1 : 0);
auto rewardedItemCount = 0;
for (int i = 0; i < rollCount; i++)
{
if (!math::random_percent_roll(rewardChance)) continue;
auto itemID = random_item_get(itemSchema);
if (itemID == INVALID_ID) continue;
item_give(itemID, inventory, itemEffectManager, itemSchema, bounds, mode);
rewardedItemCount++;
}
return rewardedItemCount;
}
}
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include "../menu/inventory.hpp"
#include "../menu/item_effect_manager.hpp"
namespace game::state::play::item
{
class Reward
{
public:
static constexpr auto INVALID_ID = -1;
int random_item_get(const resource::xml::Item& itemSchema, float chanceBonus = 1.0f);
void item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
const resource::xml::Item& itemSchema, const ImVec4& bounds,
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
int reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
float rewardRollCount,
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
};
}
+148 -57
View File
@@ -17,17 +17,45 @@ namespace game::state::play
{
namespace
{
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
int durability, int durabilityMax)
{
if (durability >= durabilityMax) return -1;
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
}
int durability_animation_index_get(const resource::xml::Schema& schema, const Entity& entity, int durability,
int durabilityMax)
{
if (durability >= durabilityMax) return -1;
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
return entity.animationMap.contains(animationName) ? entity.animationMap.at(animationName) : -1;
}
bool is_finite(glm::vec4 value)
{
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
value.z > 0.0f && value.w > 0.0f;
}
glm::vec2 item_spawn_position_get(Entity& character, const glm::vec4& bounds)
{
auto rect = character.rect();
if (!is_finite(rect)) rect = bounds;
auto center = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w);
auto radius = rect.w * 0.5f;
auto angle = glm::pi<float>() + math::random_max(glm::pi<float>());
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
return {glm::clamp(position.x, bounds.x, bounds.x + bounds.z),
glm::clamp(position.y, bounds.y, bounds.y + bounds.w)};
}
}
void ItemManager::update(entity::Character& character, entity::Cursor& cursor, AreaManager& areaManager, Text& text,
void ItemManager::update(Entity& character, Entity& cursor, AreaManager& areaManager, Text& text,
const glm::vec4& bounds, Canvas& canvas)
{
static constexpr float ROTATION_MAX = 90.0f;
@@ -38,13 +66,33 @@ namespace game::state::play
auto& schema = character.data.itemSchema;
auto& cursorSchema = character.data.cursorSchema;
auto& area = character.data.areaSchema.areas.at(areaManager.get(character));
auto* cursorRoot = cursorSchema.root();
auto areaFallback = resource::xml::Schema::AreaEntry{.gravity = 0.95f, .friction = 0.80f, .airResistance = 0.975f};
auto* areaSelected = areaManager.get(character);
auto& area = areaSelected ? *areaSelected : areaFallback;
auto& friction = area.friction;
auto& airResistance = area.airResistance;
auto& dialogue = character.data.dialogue;
auto isOverCapacity = character.is_over_capacity();
auto dialogue_pool_entry_get = [&](resource::xml::Schema::Element::Type type)
{
auto* reference = dialogue.get(type);
return reference ? dialogue.dialogue_pool_entry_get(*reference) : nullptr;
};
auto particles_queue =
[&](resource::xml::Schema& particleSchema, resource::xml::Schema::Element::Type type, glm::vec2 position)
{
auto* root = particleSchema.root();
auto* actions = root ? particleSchema.child_get(*root, resource::xml::Schema::Element::ACTIONS) : nullptr;
auto* action = actions ? particleSchema.child_get(*actions, type) : nullptr;
if (!action) return;
for (auto* particle : particleSchema.children_get(*action, resource::xml::Schema::Element::PARTICLE))
if (particle && !particle->typeString.empty())
particles.push_back({.label = particle->typeString, .position = position});
};
auto cursorPosition = canvas.screen_position_convert(cursor.position);
auto cursorPosition =
cursor.entityType == AUTOCURSOR ? cursor.position : canvas.screen_position_convert(cursor.position);
auto cursorDelta = cursorPosition - cursorPositionPrevious;
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
@@ -54,9 +102,22 @@ namespace game::state::play
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto isAutomation = this->isAutomation;
auto& io = ImGui::GetIO();
if (inputOverride)
{
isImguiCaptureMouse = inputOverride->isImguiCaptureMouse;
isMouseLeftClicked = inputOverride->isMouseLeftClicked;
isMouseLeftDown = inputOverride->isMouseLeftDown;
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
isMouseRightClicked = inputOverride->isMouseRightClicked;
isMouseRightDown = inputOverride->isMouseRightDown;
isAutomation = isAutomation || inputOverride->isAutomation;
inputOverride.reset();
}
if (isDisabled)
{
if (heldItemIndex != -1)
@@ -77,41 +138,42 @@ namespace game::state::play
if (isJustItemHoveredStopped)
{
cursor.queue_default_animation();
cursor.play({cursor.defaultAnimation});
isJustItemHoveredStopped = false;
}
if (isJustItemHeldStopped || isJustItemThrown)
{
cursor.queue_default_animation();
if (!isJustItemThrown && character.queuedPlay.empty()) character.queue_idle_animation();
cursor.play({cursor.defaultAnimation});
if (!isJustItemThrown && !character.is_animation_request())
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
isJustItemHeldStopped = false;
isJustItemThrown = false;
}
isItemHoveredPrevious = isItemHovered;
isItemHovered = false;
particles.clear();
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 position = item_spawn_position_get(character, bounds);
auto& itemSchema = character.data.itemSchema;
auto& item = itemSchema.items.at(id);
auto& anm2 = itemSchema.anm2s.at(id);
auto durabilityMax = item.durability.value_or(itemSchema.durability);
auto durabilityMax = item.isDurability ? item.durability : itemSchema.durability;
auto animationIndex = durability_animation_index_get(itemSchema, anm2, 0, durabilityMax);
items.emplace_back(anm2, position, id, 0, animationIndex);
particles_queue(itemSchema, resource::xml::Schema::Element::SUMMON, position);
}
queuedItemIDs.clear();
if (isMouseRightDown)
{
auto animation = cursorSchema.animations.return_.get();
if (animation) cursor.queue_play({*animation});
if (cursorRoot)
if (auto animation = cursorRoot->animationReturn.get()) cursor.play({*animation});
}
if (auto heldItem = vector::find(items, heldItemIndex))
@@ -131,33 +193,47 @@ namespace game::state::play
if (schema.categories[item.categoryID].isEdible)
{
auto& durabilityMax = item.durability.has_value() ? *item.durability : schema.durability;
auto caloriesPerBite = item.calories.has_value() && durabilityMax > 0 ? *item.calories / durabilityMax : 0;
auto durabilityMax = item.isDurability ? item.durability : schema.durability;
auto caloriesPerBite = item.isCalories && durabilityMax > 0 ? item.calories / durabilityMax : 0;
auto isCanEat = character.calories + caloriesPerBite <= character.max_capacity();
if (isJustItemHeld)
{
if (isCanEat)
text.set(dialogue.get(isOverCapacity ? dialogue.feedFull : dialogue.feed), character);
else if (caloriesPerBite > character.capacity)
text.set(dialogue.get(dialogue.lowCapacity), character);
else
text.set(dialogue.get(dialogue.full), character);
if (!isAutomation)
{
if (isCanEat)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FEED_FULL
: resource::xml::Schema::Element::FEED),
character);
else if (caloriesPerBite > character.capacity)
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::LOW_CAPACITY), character);
else
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::FULL), character);
}
isJustItemHeld = false;
}
for (auto& eatArea : character.data.eatAreas)
for (auto* eatArea : character.data.eat_areas_get())
{
heldItem = vector::find(items, heldItemIndex);
if (!heldItem) break;
auto rect = character.null_frame_rect(eatArea.nullID);
auto nullID = character.data.null_id_get(eatArea->null);
auto rect = character.null_frame_rect(nullID);
if (!is_finite(rect))
{
auto animationIndex =
character.animation_index_get(eatArea->animation + character.animation_append_id_get());
rect = character.null_frame_rect(nullID, animationIndex, 0.0f);
}
if (isCanEat && math::is_point_in_rectf(rect, heldItem->position))
{
character.queue_play({.animation = eatArea.animation, .speedMultiplier = character.eatSpeed});
character.play({.animation = eatArea->animation,
.appendID = character.animation_append_id_get(),
.speedMultiplier = character.eatSpeed});
if (character.playedEventID == eatArea.eventID)
if (character.playedEventID == character.data.event_id_get(eatArea->event))
{
heldItem->durability++;
character.consume_played_event();
@@ -165,23 +241,24 @@ namespace game::state::play
character.calories += caloriesPerBite;
character.totalCaloriesConsumed += caloriesPerBite;
if (item.capacityBonus.has_value())
if (item.isCapacityBonus)
{
character.capacity += *item.capacityBonus / durabilityMax;
character.capacity =
glm::clamp(character.capacity, (float)character.data.capacityMin, (float)character.data.capacityMax);
character.capacity += item.capacityBonus / durabilityMax;
character.capacity = glm::clamp(character.capacity, (float)character.data.root()->capacityMinCalories,
(float)character.data.root()->capacityMaxCalories);
}
if (item.eatSpeedBonus.has_value())
if (item.isEatSpeedBonus)
{
character.eatSpeed += *item.eatSpeedBonus / durabilityMax;
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.eatSpeedMin,
(float)character.data.eatSpeedMax);
character.eatSpeed += item.eatSpeedBonus / durabilityMax;
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.root()->eatSpeedMinMultiplier,
(float)character.data.root()->eatSpeedMaxMultiplier);
}
if (item.digestionBonus.has_value())
if (item.isDigestionBonus)
{
character.digestionRate += *item.digestionBonus / durabilityMax;
character.digestionRate = glm::clamp(character.digestionRate, (float)character.data.digestionRateMin,
(float)character.data.digestionRateMax);
character.digestionRate += item.digestionBonus / durabilityMax;
character.digestionRate =
glm::clamp(character.digestionRate, (float)character.data.root()->digestionRateMin,
(float)character.data.root()->digestionRateMax);
}
if (heldItem->durability >= durabilityMax)
@@ -195,7 +272,7 @@ namespace game::state::play
{
auto animationIndex =
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
heldItem->play(animationIndex, entity::Actor::SET);
heldItem->play({.index = animationIndex, .mode = Entity::SET});
}
}
}
@@ -204,12 +281,12 @@ namespace game::state::play
{
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
{
cursorSchema.sounds.throw_.play();
text.set(dialogue.get(dialogue.throw_), character);
if (cursorRoot) cursorRoot->soundThrow.play();
if (!isAutomation) text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::THROW), character);
isJustItemThrown = true;
}
else
cursorSchema.sounds.release.play();
else if (cursorRoot)
cursorRoot->soundRelease.play();
heldItem->velocity -= delta;
heldItemIndex = -1;
@@ -217,11 +294,14 @@ namespace game::state::play
}
// Food stolen
if (auto animation = character.animation_get(character.animation_name_convert(eatArea.animation));
if (auto animation = character.animation_get(eatArea->animation + character.animation_append_id_get());
animation && character.is_playing(animation->name))
{
if (!math::is_point_in_rectf(rect, heldItem->position))
text.set(dialogue.get(isOverCapacity ? dialogue.foodTakenFull : dialogue.foodTaken), character);
if (!isAutomation)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
: resource::xml::Schema::Element::FOOD_TAKEN),
character);
}
}
}
@@ -229,7 +309,10 @@ namespace game::state::play
if (auto animation = character.animation_get(); character.time >= animation->frameNum && isQueueFinishFood)
{
text.set(dialogue.get(isOverCapacity ? dialogue.eatFull : dialogue.eat), character);
if (!isAutomation)
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::EAT_FULL
: resource::xml::Schema::Element::EAT),
character);
isQueueFinishFood = false;
}
@@ -246,27 +329,31 @@ namespace game::state::play
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;
auto& gravity = schemaItem.isGravity ? schemaItem.gravity : area.gravity;
item.update();
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
{
isItemHovered = true;
if (auto animation = cursorSchema.animations.hover.get()) cursor.queue_play({*animation});
cursor.state = entity::Cursor::HOVER;
if (cursorRoot)
if (auto animation = cursorRoot->animationHover.get())
cursor.play({.animation = *animation, .transition = Entity::Transition::IF_IDLE});
cursor.cursorState = Entity::HOVER;
if (isMouseLeftClicked)
{
cursorSchema.sounds.grab.play();
if (cursorRoot) cursorRoot->soundGrab.play();
particles_queue(character.data.cursorSchema, resource::xml::Schema::Element::GRAB, cursorPosition);
isJustItemHeld = true;
}
if (isMouseLeftDown)
{
isItemHeld = true;
if (auto animation = cursorSchema.animations.grab.get()) cursor.queue_play({*animation});
cursor.state = entity::Cursor::ACTION;
if (cursorRoot)
if (auto animation = cursorRoot->animationGrab.get()) cursor.play({*animation});
cursor.cursorState = Entity::ACTION;
heldItemIndex = i;
heldItemMoveIndex = i;
}
@@ -274,10 +361,14 @@ namespace game::state::play
if (isMouseRightClicked)
{
if (item.durability > 0)
schema.sounds.dispose.play();
{
schema.root()->soundDispose.play();
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
}
else
{
schema.sounds.return_.play();
schema.root()->soundReturn.play();
particles_queue(schema, resource::xml::Schema::Element::RETURN_, item.position);
returnItemIDs.emplace_back(item.schemaID);
}
@@ -303,7 +394,7 @@ namespace game::state::play
item.angularVelocity *= friction;
item.angularVelocity = -item.angularVelocity;
rotation = -rotation;
schema.sounds.bounce.play();
schema.root()->soundBounce.play();
}
if (item.position.y <= bounds.y || item.position.y >= bounds.w)
@@ -331,7 +422,7 @@ namespace game::state::play
{
item.velocity.y = -item.velocity.y;
item.angularVelocity *= friction;
schema.sounds.bounce.play();
schema.root()->soundBounce.play();
}
item.velocity.y *= friction;
+26 -5
View File
@@ -1,12 +1,12 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../entity/cursor.hpp"
#include "../../entity/item.hpp"
#include "../../entity.hpp"
#include "area_manager.hpp"
#include "text.hpp"
#include <optional>
namespace game::state::play
{
class ItemManager
@@ -15,7 +15,8 @@ namespace game::state::play
static constexpr auto LIMIT = 100;
bool isDisabled{};
std::vector<entity::Item> items{};
bool isAutomation{};
std::vector<Entity> items{};
int heldItemIndex{-1};
int queuedRemoveItemIndex{-1};
@@ -39,6 +40,26 @@ namespace game::state::play
std::vector<int> queuedItemIDs{};
std::vector<int> returnItemIDs{};
void update(entity::Character&, entity::Cursor&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
struct Particle
{
std::string label{};
glm::vec2 position{};
};
struct Input
{
bool isImguiCaptureMouse{};
bool isMouseLeftClicked{};
bool isMouseLeftDown{};
bool isMouseLeftReleased{};
bool isMouseRightClicked{};
bool isMouseRightDown{};
bool isAutomation{};
};
std::optional<Input> inputOverride{};
std::vector<Particle> particles{};
void update(Entity&, Entity&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
};
}
+89 -61
View File
@@ -5,48 +5,60 @@
#include "../../util/imgui.hpp"
#include "../../util/imgui/widget.hpp"
#include <algorithm>
using namespace game::util;
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play
{
void Menu::tick()
{
inventory.tick();
arcade.tick();
}
void Menu::update(Resources& resources, ItemManager& itemManager, entity::Character& character,
entity::Cursor& cursor, Text& text, Canvas& canvas)
void Menu::update(Resources& resources, ItemManager& itemManager, Entity& character, Text& text, Autofeed& autofeed)
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto HALF_MULTIPLIER = 0.5f;
static constexpr auto BAR_DIVIDER_THICKNESS = 1.0f;
static constexpr auto PADDING_SIZE_MULTIPLIER = 2.0f;
static constexpr auto ZERO_FLOAT = 0.0f;
static constexpr auto ONE_FLOAT = 1.0f;
static constexpr auto MENU_STYLE_VAR_COUNT = 2;
static constexpr auto MENU_BAR_STYLE_VAR_COUNT = 4;
static constexpr auto MENU_BAR_FLAGS = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
auto& schema = character.data.menuSchema;
auto& strings = character.data.strings;
auto style = ImGui::GetStyle();
auto& io = ImGui::GetIO();
isDebugOpen = false;
slide.update(isOpen, io.DeltaTime);
fullscreenSlide.update(isOpen && isFullscreen, io.DeltaTime);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
auto barSize =
ImVec2(imgui::side_bar_width_get(), windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenOffset = barSize.x;
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
auto normalSize =
ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenSize =
ImVec2(windowSize.x - fullscreenOffset, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto fullscreenT = fullscreenSlide.eased_get();
auto size = ImVec2(normalSize.x + (fullscreenSize.x - normalSize.x) * fullscreenT,
normalSize.y + (fullscreenSize.y - normalSize.y) * fullscreenT);
auto targetX = windowSize.x - size.x;
auto t = slide.value_get();
auto eased = slide.eased_get();
auto isFullscreenVisible = fullscreenSlide.value_get() > ZERO_FLOAT;
auto isFullscreenResizeSettled =
isFullscreen ? fullscreenSlide.value_get() >= ONE_FLOAT : fullscreenSlide.value_get() <= ZERO_FLOAT;
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);
auto barPos = ImVec2(pos.x - barSize.x, style.WindowPadding.y);
if (slide.is_visible())
{
@@ -62,19 +74,19 @@ namespace game::state::play
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
{
interact.update(resources, text, character);
interact.update(resources, text, character, isFullscreenVisible);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabGather).c_str())))
{
arcade.update(resources, character, cursor, inventory, text, toasts);
gather.update(isFullscreen, isFullscreenResizeSettled);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
{
inventory.update(resources, itemManager, character);
inventory.update(resources, itemManager, character, autofeed);
ImGui::EndTabItem();
}
@@ -90,15 +102,6 @@ namespace game::state::play
cheats.update(resources, character, inventory);
ImGui::EndTabItem();
}
#if DEBUG
if (WIDGET_FX(ImGui::BeginTabItem("Debug")))
{
isDebugOpen = true;
debug.update(character, cursor, itemManager, canvas, text);
ImGui::EndTabItem();
}
#endif
}
ImGui::EndTabBar();
}
@@ -110,59 +113,84 @@ namespace game::state::play
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))
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, barSize);
if (ImGui::Begin("##Menu Open Bar", nullptr, MENU_BAR_FLAGS))
{
auto buttonSize = ImGui::GetContentRegionAvail();
auto cursorPos = ImGui::GetCursorScreenPos();
auto halfButtonSize = ImVec2(buttonSize.x, buttonSize.y * HALF_MULTIPLIER);
auto activeButtonSize = isOpen && !isFullscreenVisible ? halfButtonSize : buttonSize;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
auto result = WIDGET_FX(ImGui::Button("##MenuToggle", buttonSize));
auto tooltip_set = [&](const std::string& tooltip)
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::SetItemTooltip("%s", tooltip.c_str());
ImGui::PopStyleVar();
};
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", activeButtonSize));
if (t <= 0.0f || t >= 1.0f)
{
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip : Strings::MenuOpenTooltip).c_str());
if (result)
tooltip_set(strings.get(isFullscreenVisible ? Strings::MenuRestoreTooltip
: isOpen ? Strings::MenuCloseTooltip
: Strings::MenuOpenTooltip));
if (isMainResult)
{
isOpen = !isOpen;
if (isOpen)
schema.sounds.open.play();
if (isFullscreenVisible)
{
if (isFullscreen) schema.root()->soundClose.play();
isFullscreen = false;
}
else
schema.sounds.close.play();
{
isOpen = !isOpen;
isFullscreen = false;
if (isOpen)
schema.root()->soundOpen.play();
else
schema.root()->soundClose.play();
}
}
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
if (!isOpen && !isMainResult && ImGui::IsItemHovered())
{
isOpen = true;
schema.sounds.open.play();
isFullscreen = false;
schema.root()->soundOpen.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)
auto fullscreenCursorPos = ImGui::GetCursorScreenPos();
auto isFullscreenResult = false;
if (isOpen && !isFullscreenVisible)
{
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);
isFullscreenResult = WIDGET_FX(ImGui::Button("##MenuToggleFullscreen", halfButtonSize));
if (t <= 0.0f || t >= 1.0f)
{
tooltip_set(strings.get(Strings::MenuOpenFullscreenTooltip));
if (isFullscreenResult)
{
isFullscreen = true;
schema.root()->soundOpen.play();
}
}
}
auto color = ImGui::GetColorU32(ImGuiCol_Text);
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
auto direction = !isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, activeButtonSize, direction,
ImGui::GetColorU32(ImGuiCol_Text));
if (isOpen && !isFullscreenVisible)
{
auto separatorY = cursorPos.y + halfButtonSize.y;
ImGui::GetWindowDrawList()->AddLine(ImVec2(cursorPos.x, separatorY),
ImVec2(cursorPos.x + buttonSize.x, separatorY),
ImGui::GetColorU32(ImGuiCol_Separator), BAR_DIVIDER_THICKNESS);
imgui::triangle_draw(*ImGui::GetWindowDrawList(), fullscreenCursorPos, halfButtonSize,
imgui::TriangleDirection::LEFT, ImGui::GetColorU32(ImGuiCol_Text));
}
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(MENU_BAR_STYLE_VAR_COUNT);
ImGui::PopStyleVar(MENU_STYLE_VAR_COUNT);
}
}
+9 -10
View File
@@ -4,13 +4,11 @@
#include "../settings_menu.hpp"
#include "menu/arcade.hpp"
#include "cheats.hpp"
#include "debug.hpp"
#include "menu/gather.hpp"
#include "menu/interact.hpp"
#include "menu/inventory.hpp"
#include "text.hpp"
#include "menu/toasts.hpp"
#include "../../util/imgui/window_slide.hpp"
@@ -19,13 +17,10 @@ namespace game::state::play
class Menu
{
public:
menu::Arcade arcade;
menu::Interact interact;
menu::Gather gather;
Cheats cheats;
Debug debug;
menu::Inventory inventory;
menu::Toasts toasts;
state::SettingsMenu settingsMenu;
#if DEBUG
@@ -35,10 +30,14 @@ namespace game::state::play
#endif
bool isOpen{true};
bool isDebugOpen{};
bool isFullscreen{};
static constexpr auto SLIDE_DURATION = 0.125f;
static constexpr auto SLIDE_CLOSED = 0.0f;
util::imgui::WindowSlide slide{};
util::imgui::WindowSlide fullscreenSlide{SLIDE_DURATION, SLIDE_CLOSED};
void tick();
void update(Resources&, ItemManager&, entity::Character&, entity::Cursor&, Text&, Canvas&);
bool is_fullscreen_visible_get() const { return isOpen && (isFullscreen || fullscreenSlide.is_visible()); }
float fullscreen_alpha_get() const { return fullscreenSlide.eased_get(); }
void update(Resources&, ItemManager&, Entity&, Text&, Autofeed&);
};
}
-244
View File
@@ -1,244 +0,0 @@
#include "arcade.hpp"
#include "../../../util/imgui/widget.hpp"
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play::menu
{
namespace
{
struct GameInfoStrings
{
Strings::Type name;
Strings::Type description;
Strings::Type howToPlay;
};
}
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
void Arcade::game_reset(entity::Character& character, Game gameCurrent)
{
switch (gameCurrent)
{
case SKILL_CHECK:
skillCheck.reset(character);
break;
case DUNGEON:
dungeon.reset(character);
break;
case ORBIT:
orbit.reset(character);
break;
}
}
void Arcade::tick()
{
skillCheck.tick();
dungeon.tick();
orbit.tick();
}
void Arcade::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
Text& text, Toasts& toasts)
{
auto available = ImGui::GetContentRegionAvail();
auto& strings = character.data.strings;
auto game_info_strings_get = [&](Game gameCurrent) -> GameInfoStrings
{
switch (gameCurrent)
{
case SKILL_CHECK:
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription,
Strings::ArcadeSkillCheckHowToPlay};
case DUNGEON:
return {Strings::ArcadeDungeonName, Strings::ArcadeDungeonDescription, Strings::ArcadeDungeonHowToPlay};
case ORBIT:
return {Strings::ArcadeOrbitName, Strings::ArcadeOrbitDescription, Strings::ArcadeOrbitHowToPlay};
}
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription, Strings::ArcadeSkillCheckHowToPlay};
};
auto game_header_draw = [&](Game gameCurrent)
{
auto gameInfoStrings = game_info_strings_get(gameCurrent);
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
ImGui::TextUnformatted(strings.get(gameInfoStrings.name).c_str());
ImGui::PopFont();
};
auto game_menu_draw = [&](Game gameCurrent)
{
constexpr auto GAME_CHILD_HEIGHT_MULTIPLIER = 7.0f;
constexpr auto GAME_DESCRIPTION_HEIGHT_MULTIPLIER = 4.75f;
auto lineHeight = ImGui::GetTextLineHeightWithSpacing();
auto gameChildHeight = lineHeight * GAME_CHILD_HEIGHT_MULTIPLIER;
auto gameDescriptionHeight = lineHeight * GAME_DESCRIPTION_HEIGHT_MULTIPLIER;
auto gameInfoStrings = game_info_strings_get(gameCurrent);
auto detailsChildID = [gameCurrent]()
{
switch (gameCurrent)
{
case SKILL_CHECK:
return "##ArcadeSkillCheckDescription";
case DUNGEON:
return "##ArcadeDungeonDescription";
case ORBIT:
return "##ArcadeOrbitDescription";
}
return "##ArcadeDescription";
}();
if (ImGui::BeginChild(gameInfoStrings.name, {0, gameChildHeight}, ImGuiChildFlags_Borders))
{
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
ImGui::BeginChild(detailsChildID, {0, gameDescriptionHeight});
game_header_draw(gameCurrent);
ImGui::Separator();
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.description).c_str());
ImGui::EndChild();
ImGui::Separator();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
{
game_reset(character, gameCurrent);
game = gameCurrent;
state = GAMEPLAY;
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeInfoButton).c_str(), ImVec2(buttonWidth, 0))))
{
game = gameCurrent;
state = INFO;
}
}
ImGui::EndChild();
};
auto game_info_sections_draw = [&](Game gameCurrent)
{
auto gameInfoStrings = game_info_strings_get(gameCurrent);
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeHowToPlay).c_str());
ImGui::PopFont();
ImGui::Separator();
ImGui::PushFont(ImGui::GetFont(), resource::Font::NORMAL);
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.howToPlay).c_str());
ImGui::PopFont();
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeStats).c_str());
ImGui::PopFont();
ImGui::Separator();
};
auto game_stats_draw = [&](Game gameCurrent)
{
switch (gameCurrent)
{
case SKILL_CHECK:
{
auto& schema = character.data.skillCheckSchema;
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), skillCheck.highScore,
skillCheck.bestCombo);
ImGui::Text(strings.get(Strings::ArcadeTotalSkillChecksFormat).c_str(), skillCheck.totalPlays);
for (int i = 0; i < (int)schema.grades.size(); i++)
{
auto& grade = schema.grades[i];
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
}
ImGui::Text(strings.get(Strings::ArcadeAccuracyFormat).c_str(), skillCheck.accuracy_score_get(character));
break;
}
case DUNGEON:
break;
case ORBIT:
break;
}
};
auto game_info_draw = [&](Game gameCurrent)
{
game_header_draw(gameCurrent);
ImGui::Separator();
game_info_sections_draw(gameCurrent);
game_stats_draw(gameCurrent);
};
switch (state)
{
case GAMEPLAY:
switch (game)
{
case SKILL_CHECK:
if (skillCheck.update(resources, character, inventory, text, toasts))
{
game_reset(character, SKILL_CHECK);
state = MENU;
}
break;
case DUNGEON:
if (dungeon.update(character))
{
game_reset(character, DUNGEON);
state = MENU;
}
break;
case ORBIT:
if (orbit.update(resources, character, cursor, inventory, text, toasts))
{
game_reset(character, ORBIT);
state = MENU;
}
break;
}
return;
case MENU:
case INFO:
break;
}
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
if (ImGui::BeginChild("##Arcade Child", childSize))
{
switch (state)
{
case MENU:
game_menu_draw(ORBIT);
//game_menu_draw(DUNGEON);
game_menu_draw(SKILL_CHECK);
break;
case INFO:
game_info_draw(game);
break;
case GAMEPLAY:
break;
}
}
ImGui::EndChild();
if (state == INFO)
{
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) state = MENU;
}
}
}
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include "arcade/dungeon.hpp"
#include "arcade/orbit.hpp"
#include "arcade/skill_check.hpp"
#include "toasts.hpp"
namespace game::state::play::menu
{
class Arcade
{
public:
enum Game
{
SKILL_CHECK,
DUNGEON,
ORBIT
};
enum State
{
MENU,
GAMEPLAY,
INFO
};
arcade::SkillCheck skillCheck{};
arcade::Dungeon dungeon{};
arcade::Orbit orbit{};
Game game{SKILL_CHECK};
State state{MENU};
Arcade() = default;
Arcade(entity::Character&);
void game_reset(entity::Character&, Game);
void tick();
void update(Resources&, entity::Character&, entity::Cursor&, Inventory&, Text&, Toasts&);
};
}
-310
View File
@@ -1,310 +0,0 @@
#include "dungeon.hpp"
#include "../../../../resource/font.hpp"
#include "../../../../resource/xml/strings.hpp"
#include "../../../../util/imgui/widget.hpp"
#include "../../../../util/math.hpp"
#include <format>
#include <imgui.h>
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play::menu::arcade
{
int Dungeon::tile_value_get(const Tile& tile) const { return (int)tile.value; }
bool Dungeon::tile_value_counts_toward_sum(const Tile& tile) const
{
return (tile.value >= Tile::VALUE_0 && tile.value <= Tile::VALUE_13) || tile.value == Tile::MINE;
}
bool Dungeon::tile_is_scroll(const Tile& tile) const { return tile.value == Tile::SCROLL; }
const char* Dungeon::tile_flag_text_get(const Tile& tile) const
{
switch (tile.flagValue)
{
case Tile::FLAG_NONE:
return nullptr;
case Tile::FLAG_MINE:
return "M";
case Tile::FLAG_1:
return "1";
case Tile::FLAG_2:
return "2";
case Tile::FLAG_3:
return "3";
case Tile::FLAG_4:
return "4";
case Tile::FLAG_5:
return "5";
case Tile::FLAG_6:
return "6";
case Tile::FLAG_7:
return "7";
case Tile::FLAG_8:
return "8";
case Tile::FLAG_9:
return "9";
case Tile::FLAG_10:
return "10";
case Tile::FLAG_11:
return "11";
case Tile::FLAG_12:
return "12";
case Tile::FLAG_13:
return "13";
case Tile::FLAG_QUESTION:
return "?";
}
return nullptr;
}
int Dungeon::surrounding_value_sum_get(int row, int column) const
{
auto sum = 0;
for (int rowOffset = -1; rowOffset <= 1; rowOffset++)
for (int columnOffset = -1; columnOffset <= 1; columnOffset++)
{
if (rowOffset == 0 && columnOffset == 0) continue;
auto neighborRow = row + rowOffset;
auto neighborColumn = column + columnOffset;
if (neighborRow < 0 || neighborRow >= GRID_ROWS || neighborColumn < 0 || neighborColumn >= GRID_COLUMNS)
continue;
auto& neighbor = tiles[neighborRow * GRID_COLUMNS + neighborColumn];
if (!tile_value_counts_toward_sum(neighbor)) continue;
sum += tile_value_get(neighbor);
}
return sum;
}
void Dungeon::reveal_diamond(int row, int column, int radius)
{
for (int rowOffset = -radius; rowOffset <= radius; rowOffset++)
for (int columnOffset = -radius; columnOffset <= radius; columnOffset++)
{
if (std::abs(rowOffset) + std::abs(columnOffset) > radius) continue;
auto targetRow = row + rowOffset;
auto targetColumn = column + columnOffset;
if (targetRow < 0 || targetRow >= GRID_ROWS || targetColumn < 0 || targetColumn >= GRID_COLUMNS) continue;
auto& tile = tiles[targetRow * GRID_COLUMNS + targetColumn];
if (tile.state == Tile::HIDDEN)
{
tile.state = Tile::SHOWN;
tile.flagValue = Tile::FLAG_NONE;
}
}
}
void Dungeon::reset(entity::Character&)
{
tiles.assign(GRID_ROWS * GRID_COLUMNS, Tile{});
score = 0;
for (auto& tile : tiles)
{
tile.value = game::util::math::random_percent_roll(5.0f) ? Tile::MINE
: (Tile::Value)(int)game::util::math::random_max(14.0f);
tile.state = Tile::HIDDEN;
tile.flagValue = Tile::FLAG_NONE;
}
if (!tiles.empty()) tiles[(int)game::util::math::random_max((float)tiles.size())].value = Tile::SCROLL;
}
void Dungeon::tick() {}
bool Dungeon::update(entity::Character& character)
{
auto& strings = character.data.strings;
constexpr float GRID_SPACING = 1.0f;
auto& style = ImGui::GetStyle();
if (tiles.size() != GRID_ROWS * GRID_COLUMNS) reset(character);
auto contentRegionAvail = ImGui::GetContentRegionAvail();
auto childSize =
ImVec2(contentRegionAvail.x,
std::max(0.0f, contentRegionAvail.y - ImGui::GetFrameHeightWithSpacing() - style.WindowPadding.y));
if (ImGui::BeginChild("##DungeonGrid", childSize))
{
auto drawList = ImGui::GetWindowDrawList();
auto childAvail = ImGui::GetContentRegionAvail();
auto cellWidth = std::max(1.0f, (childAvail.x - GRID_SPACING * (GRID_COLUMNS - 1)) / (float)GRID_COLUMNS);
auto cellHeight = std::max(1.0f, (childAvail.y - GRID_SPACING * (GRID_ROWS - 1)) / (float)GRID_ROWS);
auto cellSize = std::floor(std::min(cellWidth, cellHeight));
auto gridWidth = cellSize * (float)GRID_COLUMNS + GRID_SPACING * (GRID_COLUMNS - 1);
auto gridHeight = cellSize * (float)GRID_ROWS + GRID_SPACING * (GRID_ROWS - 1);
auto cursor = ImGui::GetCursorPos();
auto offsetX = std::max(0.0f, (childAvail.x - gridWidth) * 0.5f);
auto offsetY = std::max(0.0f, (childAvail.y - gridHeight) * 0.5f);
ImGui::SetCursorPos(ImVec2(cursor.x + offsetX, cursor.y + offsetY));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(GRID_SPACING, GRID_SPACING));
for (int row = 0; row < GRID_ROWS; row++)
{
for (int column = 0; column < GRID_COLUMNS; column++)
{
auto tileID = row * GRID_COLUMNS + column;
auto& tile = tiles[tileID];
auto tileValue = tile_value_get(tile);
ImGui::PushID(tileID);
if (tile.state != Tile::HIDDEN)
{
auto buttonColor = style.Colors[ImGuiCol_WindowBg];
ImGui::PushStyleColor(ImGuiCol_Button, buttonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColor);
}
auto isLeftPressed = WIDGET_FX(ImGui::Button("##DungeonCell", ImVec2(cellSize, cellSize)));
auto isPopupOpen = ImGui::BeginPopupContextItem("##DungeonFlagMenu");
if (isPopupOpen)
{
if (ImGui::Button("M", ImVec2(36.0f, 0.0f)))
{
tile.flagValue = Tile::FLAG_MINE;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
for (int flagValue = Tile::FLAG_1; flagValue <= Tile::FLAG_13; flagValue++)
{
auto flagText = std::format("{}", flagValue);
if (ImGui::Button(flagText.c_str(), ImVec2(36.0f, 0.0f)))
{
tile.flagValue = (Tile::FlagValue)flagValue;
ImGui::CloseCurrentPopup();
}
if (flagValue % 4 != 0 && flagValue != Tile::FLAG_13) ImGui::SameLine();
}
if (ImGui::Button("?", ImVec2(36.0f, 0.0f)))
{
tile.flagValue = Tile::FLAG_QUESTION;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Clear"))
{
tile.flagValue = Tile::FLAG_NONE;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
auto rectMin = ImGui::GetItemRectMin();
auto rectMax = ImGui::GetItemRectMax();
if (tile.state != Tile::HIDDEN) ImGui::PopStyleColor(3);
ImGui::PopID();
if (isLeftPressed)
{
switch (tile.state)
{
case Tile::HIDDEN:
tile.state = Tile::SHOWN;
tile.flagValue = Tile::FLAG_NONE;
if (tile_is_scroll(tile))
{
reveal_diamond(row, column, 2);
tile.value = Tile::VALUE_0;
tile.state = Tile::SHOWN;
}
break;
case Tile::SHOWN:
tile.flagValue = Tile::FLAG_NONE;
if (tile_is_scroll(tile))
{
reveal_diamond(row, column, 2);
tile.value = Tile::VALUE_0;
tile.state = Tile::SHOWN;
}
else if (tileValue > 0)
{
tile.state = Tile::CORPSE;
score += tileValue;
}
break;
case Tile::CORPSE:
tile.value = Tile::VALUE_0;
tile.state = Tile::SHOWN;
tile.flagValue = Tile::FLAG_NONE;
break;
}
}
std::string tileText{};
auto textColor = IM_COL32(255, 255, 255, 255);
if (tile_is_scroll(tile))
{
tileText = "!";
if (tile.state == Tile::CORPSE)
textColor = IM_COL32(255, 230, 64, 255);
else
textColor = IM_COL32(255, 255, 255, 255);
}
else if (tile.state == Tile::HIDDEN)
{
if (auto flagText = tile_flag_text_get(tile))
{
tileText = flagText;
textColor = IM_COL32(64, 128, 255, 255);
}
}
else
switch (tile.state)
{
case Tile::HIDDEN:
break;
case Tile::SHOWN:
if (tileValue == 0)
{
auto surroundingSum = surrounding_value_sum_get(row, column);
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
}
else
{
tileText = std::format("{}", tileValue);
textColor = IM_COL32(255, 64, 64, 255);
}
break;
case Tile::CORPSE:
if (tileValue == 0)
{
auto surroundingSum = surrounding_value_sum_get(row, column);
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
}
else
{
tileText = std::format("{}", tileValue);
textColor = IM_COL32(255, 230, 64, 255);
}
break;
}
if (!tileText.empty())
{
auto textSize = ImGui::CalcTextSize(tileText.c_str());
auto textPosition = ImVec2(rectMin.x + (rectMax.x - rectMin.x - textSize.x) * 0.5f,
rectMin.y + (rectMax.y - rectMin.y - textSize.y) * 0.5f);
drawList->AddText(textPosition, textColor, tileText.c_str());
}
if (column + 1 < GRID_COLUMNS) ImGui::SameLine(0.0f, GRID_SPACING);
}
}
ImGui::PopStyleVar(2);
}
ImGui::EndChild();
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
return WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()));
}
}
-82
View File
@@ -1,82 +0,0 @@
#pragma once
#include "../../../../entity/character.hpp"
#include <vector>
namespace game::state::play::menu::arcade
{
class Dungeon
{
public:
struct Tile
{
enum State
{
HIDDEN,
SHOWN,
CORPSE
};
enum FlagValue
{
FLAG_NONE,
FLAG_1,
FLAG_2,
FLAG_3,
FLAG_4,
FLAG_5,
FLAG_6,
FLAG_7,
FLAG_8,
FLAG_9,
FLAG_10,
FLAG_11,
FLAG_12,
FLAG_13,
FLAG_MINE,
FLAG_QUESTION
};
enum Value
{
VALUE_0,
VALUE_1,
VALUE_2,
VALUE_3,
VALUE_4,
VALUE_5,
VALUE_6,
VALUE_7,
VALUE_8,
VALUE_9,
VALUE_10,
VALUE_11,
VALUE_12,
VALUE_13,
MINE = 100,
SCROLL = 101
};
Value value{VALUE_0};
State state{HIDDEN};
FlagValue flagValue{FLAG_NONE};
};
static constexpr int GRID_ROWS = 13;
static constexpr int GRID_COLUMNS = 13;
std::vector<Tile> tiles{};
int score{};
int tile_value_get(const Tile&) const;
int surrounding_value_sum_get(int row, int column) const;
bool tile_value_counts_toward_sum(const Tile&) const;
bool tile_is_scroll(const Tile&) const;
const char* tile_flag_text_get(const Tile&) const;
void reveal_diamond(int row, int column, int radius);
void reset(entity::Character&);
void tick();
bool update(entity::Character&);
};
}
-605
View File
@@ -1,605 +0,0 @@
#include "orbit.hpp"
#include "../../../../util/imgui.hpp"
#include "../../../../util/imgui/widget.hpp"
#include "../../../../util/math.hpp"
#include <algorithm>
#include <cmath>
#include <format>
#include <glm/gtc/constants.hpp>
#include <imgui.h>
using namespace game::util::imgui;
using namespace game::resource::xml;
using namespace game::util;
using namespace glm;
namespace game::state::play::menu::arcade
{
namespace
{
enum SpawnSide
{
TOP,
RIGHT,
BOTTOM,
LEFT
};
bool is_rect_overlapping(const glm::vec4& left, const glm::vec4& right)
{
return left.x < right.x + right.z && left.x + left.z > right.x && left.y < right.y + right.w &&
left.y + left.w > right.y;
}
void target_tick(Orbit::Entity& entity, const glm::vec2& target, float acceleration)
{
auto delta = target - entity.position;
auto distance = glm::length(delta);
if (distance <= 0.001f)
{
entity.position = target;
entity.velocity *= 0.5f;
if (glm::length(entity.velocity) <= 0.001f) entity.velocity = {};
return;
}
auto maxSpeed = std::max(acceleration * 8.0f, 1.0f);
auto desiredVelocity = glm::normalize(delta) * std::min(distance * 0.35f, maxSpeed);
auto steering = desiredVelocity - entity.velocity;
auto steeringLength = glm::length(steering);
if (steeringLength > acceleration) steering = (steering / steeringLength) * acceleration;
entity.velocity += steering;
auto velocityLength = glm::length(entity.velocity);
if (velocityLength > maxSpeed) entity.velocity = (entity.velocity / velocityLength) * maxSpeed;
entity.position += entity.velocity;
if (glm::distance(entity.position, target) <= maxSpeed)
{
entity.position = glm::mix(entity.position, target, 0.15f);
}
}
void follower_angles_refresh(std::vector<Orbit::Entity>& entities)
{
std::vector<Orbit::Entity*> followers{};
for (auto& entity : entities)
if (entity.type == Orbit::Entity::FOLLOWER) followers.emplace_back(&entity);
if (followers.empty()) return;
std::sort(followers.begin(), followers.end(),
[](const Orbit::Entity* left, const Orbit::Entity* right) { return left->colorID < right->colorID; });
auto baseAngle = followers.front()->orbitAngle;
auto spacing = glm::two_pi<float>() / (float)followers.size();
for (int i = 0; i < (int)followers.size(); i++)
followers[i]->orbitAngle = baseAngle + spacing * (float)i;
}
const glm::vec3* color_value_get(const resource::xml::Orbit& schema, int colorID)
{
if (colorID < 0 || colorID >= (int)schema.colors.size()) return nullptr;
return &schema.colors[colorID].value;
}
int random_available_color_get(const resource::xml::Orbit& schema, int level)
{
auto availableCount = std::min(level, (int)schema.colors.size());
if (availableCount <= 0) return -1;
return (int)math::random_max((float)availableCount);
}
int unlocked_level_get(const resource::xml::Orbit& schema, int score)
{
int unlockedLevel = 0;
for (auto& color : schema.colors)
{
if (score >= color.scoreThreshold)
unlockedLevel++;
else
break;
}
return std::max(1, unlockedLevel);
}
void color_override_set(Orbit::Entity& entity, const resource::xml::Orbit& schema, int colorID,
const std::string& layerName)
{
auto color = color_value_get(schema, colorID);
if (!color) return;
if (!entity.layerMap.contains(layerName)) return;
entity::Actor::Override override_{entity.layerMap.at(layerName), Anm2::LAYER, entity::Actor::Override::SET};
override_.frame.tint.x = color->r;
override_.frame.tint.y = color->g;
override_.frame.tint.z = color->b;
entity.overrides.emplace_back(std::move(override_));
}
void idle_queue(Orbit::Entity& entity)
{
if (!entity.animationIdle.empty())
entity.queue_play({.animation = entity.animationIdle, .isPlayAfterAnimation = true});
}
void spawn_animation_play(Orbit::Entity& entity)
{
if (!entity.animationSpawn.empty())
{
entity.play(entity.animationSpawn, entity::Actor::PLAY_FORCE);
idle_queue(entity);
}
}
}
void Orbit::spawn(entity::Character& character, Orbit::Entity::Type type, int colorID)
{
auto& schema = character.data.orbitSchema;
switch (type)
{
case Entity::PLAYER:
if (!schema.player.anm2.is_valid()) return;
entities.emplace_back(schema.player.anm2, Entity::PLAYER);
entities.back().position = centerPosition;
entities.back().animationIdle = schema.player.animations.idle;
entities.back().animationSpawn = schema.player.animations.spawn;
entities.back().animationDeath = schema.player.animations.death;
entities.back().hitboxNull = schema.player.hitboxNull;
spawn_animation_play(entities.back());
return;
case Entity::FOLLOWER:
{
if (!schema.follower.anm2.is_valid()) return;
if (colorID < 0 || colorID >= (int)schema.colors.size()) return;
Entity follower{schema.follower.anm2, Entity::FOLLOWER};
follower.colorID = colorID;
follower.orbitAngle = 0.0f;
follower.position = centerPosition;
follower.animationIdle = schema.follower.animations.idle;
follower.animationSpawn = schema.follower.animations.spawn;
follower.animationDeath = schema.follower.animations.death;
follower.hitboxNull = schema.follower.hitboxNull;
color_override_set(follower, schema, colorID, schema.follower.overrideTintLayer);
spawn_animation_play(follower);
entities.emplace_back(std::move(follower));
follower_angles_refresh(entities);
return;
}
case Entity::ENEMY:
{
if (!schema.enemy.anm2.is_valid()) return;
Entity enemy{schema.enemy.anm2, Entity::ENEMY};
enemy.colorID = colorID;
enemy.animationIdle = schema.enemy.animations.idle;
enemy.animationSpawn = schema.enemy.animations.spawn;
enemy.animationDeath = schema.enemy.animations.death;
enemy.hitboxNull = schema.enemy.hitboxNull;
color_override_set(enemy, schema, colorID, schema.enemy.overrideTintLayer);
spawn_animation_play(enemy);
auto rect = enemy.rect();
auto width = rect.z;
auto height = rect.w;
auto side = (SpawnSide)math::random_max(4.0f);
switch (side)
{
case TOP:
enemy.position = vec2(math::random_max(canvas.size.x), -height - schema.enemy.spawnPadding);
break;
case RIGHT:
enemy.position = vec2(canvas.size.x + width + schema.enemy.spawnPadding, math::random_max(canvas.size.y));
break;
case BOTTOM:
enemy.position = vec2(math::random_max(canvas.size.x), canvas.size.y + height + schema.enemy.spawnPadding);
break;
case LEFT:
enemy.position = vec2(-width - schema.enemy.spawnPadding, math::random_max(canvas.size.y));
break;
}
entities.emplace_back(std::move(enemy));
if (schema.warning.anm2.is_valid())
{
Entity warning{schema.warning.anm2, Entity::WARNING};
warning.colorID = colorID;
color_override_set(warning, schema, colorID, schema.warning.overrideTintLayer);
auto warningRect = warning.rect();
auto warningWidth = warningRect.z;
auto warningHeight = warningRect.w;
switch (side)
{
case TOP:
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
(float)canvas.size.x - warningWidth * 0.5f),
warningHeight * 0.5f);
break;
case RIGHT:
warning.position = vec2((float)canvas.size.x - warningWidth * 0.5f,
glm::clamp(entities.back().position.y, warningHeight * 0.5f,
(float)canvas.size.y - warningHeight * 0.5f));
break;
case BOTTOM:
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
(float)canvas.size.x - warningWidth * 0.5f),
(float)canvas.size.y - warningHeight * 0.5f);
break;
case LEFT:
warning.position = vec2(warningWidth * 0.5f, glm::clamp(entities.back().position.y, warningHeight * 0.5f,
(float)canvas.size.y - warningHeight * 0.5f));
break;
}
entities.emplace_back(std::move(warning));
}
return;
}
case Entity::WARNING:
default:
return;
}
}
void Orbit::reset(entity::Character& character)
{
entities.clear();
sounds = &character.data.orbitSchema.sounds;
cursorPosition = {};
centerPosition = {};
level = 0;
highScoreAtRunStart = highScore;
score = 0;
isHighScoreAchievedThisRun = false;
itemEffectManager = {};
followerRadius = {};
playerTargetAcceleration = {};
followerTargetAcceleration = {};
playerTimeAfterHurt = {};
enemySpeed = {};
enemySpeedScoreBonus = {};
enemySpeedGainBase = {};
enemySpeedGainScoreBonus = {};
rotationSpeed = {};
rotationSpeedMax = {};
rotationSpeedFriction = {};
startTimer = character.data.orbitSchema.startTime;
hurtTimer = 0;
isPlayerDying = false;
isRotateLeft = false;
isRotateRight = false;
}
void Orbit::tick()
{
for (auto& entity : entities)
entity.tick();
itemEffectManager.tick();
canvas.tick();
}
bool Orbit::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
Text& text, menu::Toasts& toasts)
{
auto& strings = character.data.strings;
auto& schema = character.data.orbitSchema;
sounds = &schema.sounds;
auto& style = ImGui::GetStyle();
auto drawList = ImGui::GetWindowDrawList();
auto& textureShader = resources.shaders[resource::shader::TEXTURE];
auto& rectShader = resources.shaders[resource::shader::RECT];
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
auto bestText = std::vformat(strings.get(Strings::ArcadeBestScoreFormat), std::make_format_args(highScore));
auto cursorPos = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(bestText.c_str()).x,
cursorPos.y - ImGui::GetTextLineHeightWithSpacing()));
ImGui::Text(strings.get(Strings::ArcadeBestScoreFormat).c_str(), highScore);
auto padding = ImGui::GetTextLineHeightWithSpacing();
auto contentRegionAvail = ImGui::GetContentRegionAvail();
auto contentRegionPosition = ImGui::GetCursorScreenPos();
auto contentBounds =
ImVec4(contentRegionPosition.x, contentRegionPosition.y, contentRegionAvail.x, contentRegionAvail.y);
auto available =
imgui::to_vec2(contentRegionAvail) - vec2(0.0f, ImGui::GetFrameHeightWithSpacing() + style.WindowPadding.y);
auto canvasSize = glm::max(vec2(1.0f), available - vec2(padding * 2.0f));
auto canvasScreenPosition = imgui::to_vec2(ImGui::GetCursorScreenPos()) + vec2(padding);
centerPosition = canvasSize * 0.5f;
if (isPlayerDying)
{
Entity* playerEntity = nullptr;
for (auto& entity : entities)
if (entity.type == Entity::PLAYER)
{
playerEntity = &entity;
break;
}
if (!playerEntity || playerEntity->state == entity::Actor::STOPPED)
{
reset(character);
}
}
if (entities.empty() && startTimer <= 0) startTimer = schema.startTime;
if (entities.empty()) spawn(character, Entity::PLAYER);
followerRadius = schema.player.followerRadius;
playerTargetAcceleration = schema.player.targetAcceleration;
followerTargetAcceleration = schema.follower.targetAcceleration;
playerTimeAfterHurt = schema.player.timeAfterHurt;
enemySpeed = schema.enemy.speed;
enemySpeedScoreBonus = schema.enemy.speedScoreBonus;
enemySpeedGainBase = schema.enemy.speedGainBase;
enemySpeedGainScoreBonus = schema.enemy.speedGainScoreBonus;
rotationSpeed = schema.player.rotationSpeed;
rotationSpeedMax = schema.player.rotationSpeedMax;
rotationSpeedFriction = schema.player.rotationSpeedFriction;
auto nextLevel = std::min(unlocked_level_get(schema, score), (int)schema.colors.size());
if (nextLevel > level)
{
schema.sounds.levelUp.play();
auto colorIndex = nextLevel - 1;
if (colorIndex >= 0 && colorIndex < (int)schema.colors.size())
{
auto& pool = schema.colors[colorIndex].pool;
if (pool.is_valid() && text.is_interruptible()) text.set(character.data.dialogue.get(pool), character);
}
}
level = nextLevel;
auto player_get = [&]() -> Entity*
{
for (auto& entity : entities)
if (entity.type == Entity::PLAYER) return &entity;
return nullptr;
};
auto player = player_get();
if (player)
{
auto desiredFollowerCount = std::min(level, (int)schema.colors.size());
auto currentFollowerCount = (int)std::count_if(entities.begin(), entities.end(), [](const Entity& entity)
{ return entity.type == Entity::FOLLOWER; });
auto currentEnemyCount = (int)std::count_if(entities.begin(), entities.end(),
[](const Entity& entity) { return entity.type == Entity::ENEMY; });
if (currentFollowerCount != desiredFollowerCount)
{
entities.erase(std::remove_if(entities.begin(), entities.end(),
[](const Entity& entity) { return entity.type == Entity::FOLLOWER; }),
entities.end());
for (int i = 0; i < desiredFollowerCount; i++)
spawn(character, Entity::FOLLOWER, i);
player = player_get();
}
if (startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && !schema.colors.empty())
{
auto colorID = random_available_color_get(schema, level);
if (colorID == -1) return false;
if (currentEnemyCount == 0)
{
spawn(character, Entity::ENEMY, colorID);
player = player_get();
}
auto spawnChance = schema.enemy.spawnChanceBase + schema.enemy.spawnChanceScoreBonus * (float)score;
if (math::random_percent_roll(spawnChance))
{
colorID = random_available_color_get(schema, level);
if (colorID == -1) return false;
spawn(character, Entity::ENEMY, colorID);
player = player_get();
}
}
}
auto mousePosition = imgui::to_vec2(ImGui::GetMousePos());
auto canvasBoundsMax = canvasScreenPosition + canvasSize;
auto isHoveringCanvas = mousePosition.x >= canvasScreenPosition.x && mousePosition.x <= canvasBoundsMax.x &&
mousePosition.y >= canvasScreenPosition.y && mousePosition.y <= canvasBoundsMax.y;
cursor.isVisible = !isHoveringCanvas;
isRotateLeft = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
ImGui::IsMouseDown(ImGuiMouseButton_Left);
isRotateRight = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
ImGui::IsMouseDown(ImGuiMouseButton_Right);
cursorPosition = glm::clamp(mousePosition - canvasScreenPosition, vec2(0.0f), canvasSize);
if (player)
{
if (isPlayerDying || hurtTimer > 0) player->velocity *= 0.85f;
if (!isPlayerDying && hurtTimer <= 0 && isRotateLeft) player->rotationVelocity -= rotationSpeed;
if (!isPlayerDying && hurtTimer <= 0 && isRotateRight) player->rotationVelocity += rotationSpeed;
player->rotationVelocity = glm::clamp(player->rotationVelocity, -rotationSpeedMax, rotationSpeedMax);
player->rotationVelocity *= rotationSpeedFriction;
if (!isPlayerDying && hurtTimer <= 0)
target_tick(*player, startTimer > 0 ? centerPosition : cursorPosition, playerTargetAcceleration);
}
for (auto& entity : entities)
{
switch (entity.type)
{
case Entity::FOLLOWER:
if (player)
{
entity.orbitAngle += player->rotationVelocity;
auto radius = std::max(0.0f, followerRadius);
auto target = player->position + vec2(std::cos(entity.orbitAngle), std::sin(entity.orbitAngle)) * radius;
target_tick(entity, target, followerTargetAcceleration);
}
break;
case Entity::ENEMY:
if (player && !entity.isMarkedForRemoval)
{
auto delta = player->position - entity.position;
auto distance = glm::length(delta);
auto speed = (enemySpeed + enemySpeedScoreBonus * (float)score) +
(enemySpeedGainBase + enemySpeedGainScoreBonus * (float)score);
if (distance > 0.001f) entity.position += glm::normalize(delta) * speed;
}
break;
default:
break;
}
}
for (auto& follower : entities)
{
if (isPlayerDying) break;
if (follower.type != Entity::FOLLOWER) continue;
if (follower.hitboxNull.empty() || !follower.nullMap.contains(follower.hitboxNull)) continue;
auto followerRect = follower.null_frame_rect(follower.nullMap.at(follower.hitboxNull));
if (std::isnan(followerRect.x)) continue;
for (auto& enemy : entities)
{
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
if (enemy.colorID != follower.colorID) continue;
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
if (std::isnan(enemyRect.x)) continue;
if (!is_rect_overlapping(followerRect, enemyRect)) continue;
enemy.isMarkedForRemoval = true;
if (!enemy.animationDeath.empty())
enemy.play(enemy.animationDeath, entity::Actor::PLAY_FORCE);
else
enemy.state = entity::Actor::STOPPED;
spawn_animation_play(follower);
score++;
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score);
itemRewards.reward_random_items_try(inventory, itemEffectManager, character.data.itemSchema, contentBounds,
rewardChance, rewardRollCount, menu::ItemEffectManager::SHOOT_UP);
if (score > highScore)
{
highScore = score;
if (!isHighScoreAchievedThisRun && highScoreAtRunStart > 0)
{
isHighScoreAchievedThisRun = true;
schema.sounds.highScore.play();
auto toastText = strings.get(Strings::ArcadeHighScoreToast);
auto toastPosition = imgui::to_imvec2(
canvasScreenPosition + player->position -
vec2(ImGui::CalcTextSize(toastText.c_str()).x * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 2.0f));
toasts.spawn(toastText, toastPosition, 60);
}
}
}
}
if (player && !isPlayerDying && hurtTimer <= 0 && !player->hitboxNull.empty() &&
player->nullMap.contains(player->hitboxNull))
{
auto playerRect = player->null_frame_rect(player->nullMap.at(player->hitboxNull));
if (!std::isnan(playerRect.x))
{
auto isHit = false;
for (auto& enemy : entities)
{
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
if (std::isnan(enemyRect.x)) continue;
if (!is_rect_overlapping(playerRect, enemyRect)) continue;
isHit = true;
break;
}
if (isHit)
{
if (sounds) sounds->hurt.play();
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
if (schema.poolDeath.is_valid() && text.is_interruptible())
text.set(character.data.dialogue.get(schema.poolDeath), character);
hurtTimer = playerTimeAfterHurt;
isPlayerDying = true;
player->velocity = {};
player->rotationVelocity = 0.0f;
if (!player->animationDeath.empty())
player->play(player->animationDeath, entity::Actor::PLAY_FORCE);
else
player->state = entity::Actor::STOPPED;
entities.erase(std::remove_if(entities.begin(), entities.end(),
[](const Entity& entity)
{
return entity.type == Entity::ENEMY || entity.type == Entity::WARNING ||
entity.type == Entity::FOLLOWER;
}),
entities.end());
}
}
}
entities.erase(std::remove_if(entities.begin(), entities.end(),
[](const Entity& entity)
{
return (entity.type == Entity::WARNING && entity.state == entity::Actor::STOPPED) ||
(entity.type == Entity::ENEMY && entity.isMarkedForRemoval &&
entity.state == entity::Actor::STOPPED);
}),
entities.end());
if (startTimer > 0) startTimer--;
if (hurtTimer > 0) hurtTimer--;
canvas.bind();
canvas.size_set(ivec2(canvasSize));
canvas.clear(color::BLACK);
for (auto& entity : entities)
if (entity.type == Entity::PLAYER || entity.type == Entity::FOLLOWER || entity.type == Entity::ENEMY ||
entity.type == Entity::WARNING)
entity.render(textureShader, rectShader, canvas);
canvas.unbind();
ImGui::Dummy(ImVec2(0, padding));
ImGui::SetCursorScreenPos(imgui::to_imvec2(canvasScreenPosition));
ImGui::Image(canvas.texture, imgui::to_imvec2(canvasSize));
itemEffectManager.render(resources, character.data.itemSchema, contentBounds, ImGui::GetIO().DeltaTime);
ImGui::Dummy(ImVec2(0, padding));
toasts.update(drawList);
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
if (ImGui::IsItemHovered()) ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
return isMenuPressed;
}
}
-81
View File
@@ -1,81 +0,0 @@
#pragma once
#include "../../../../entity/actor.hpp"
#include "../../../../entity/cursor.hpp"
#include "../../../../resources.hpp"
#include "../../../../util/color.hpp"
#include "../inventory.hpp"
#include "../item_effect_manager.hpp"
#include "../../text.hpp"
#include "../toasts.hpp"
#include "../../item/reward.hpp"
namespace game::state::play::menu::arcade
{
class Orbit
{
public:
class Entity : public entity::Actor
{
public:
enum Type
{
PLAYER,
FOLLOWER,
ENEMY,
WARNING
};
Type type{PLAYER};
glm::vec2 velocity{};
float rotationVelocity{};
std::string animationIdle{};
std::string animationSpawn{};
std::string animationDeath{};
std::string hitboxNull{"Hitbox"};
bool isMarkedForRemoval{};
int health{3};
int colorID{};
float orbitAngle{};
Entity() = default;
Entity(resource::xml::Anm2 anm2, Type type = PLAYER) : entity::Actor(std::move(anm2)), type(type) {}
};
std::vector<Entity> entities{};
resource::xml::Orbit::Sounds* sounds{};
Canvas canvas{{1, 1}};
glm::vec2 cursorPosition{};
glm::vec2 centerPosition{};
int level{1};
int score{};
int highScore{};
int highScoreAtRunStart{};
bool isHighScoreAchievedThisRun{};
menu::ItemEffectManager itemEffectManager{};
game::state::play::item::Reward itemRewards{};
float followerRadius{};
float playerTargetAcceleration{};
float followerTargetAcceleration{};
int playerTimeAfterHurt{};
float enemySpeed{};
float enemySpeedScoreBonus{};
float enemySpeedGainBase{};
float enemySpeedGainScoreBonus{};
float rotationSpeed{};
float rotationSpeedMax{};
float rotationSpeedFriction{};
int startTimer{};
int hurtTimer{};
bool isPlayerDying{};
bool isRotateLeft{};
bool isRotateRight{};
Orbit() = default;
void reset(entity::Character&);
void tick();
void spawn(entity::Character&, Entity::Type, int colorID = -1);
bool update(Resources&, entity::Character&, entity::Cursor&, Inventory& inventory, Text& text, menu::Toasts&);
};
}
-339
View File
@@ -1,339 +0,0 @@
#include "skill_check.hpp"
#include <imgui_internal.h>
#include "../../../../util/imgui.hpp"
#include "../../../../util/imgui/widget.hpp"
#include "../../../../util/math.hpp"
#include <cmath>
#include <format>
using namespace game::util;
using namespace game::entity;
using namespace game::resource;
using namespace game::resource::xml;
using namespace glm;
namespace game::state::play::menu::arcade
{
float SkillCheck::accuracy_score_get(entity::Character& character)
{
if (totalPlays == 0) return 0.0f;
auto& schema = character.data.skillCheckSchema;
float combinedWeight{};
for (int i = 0; i < (int)schema.grades.size(); i++)
{
auto& grade = schema.grades[i];
combinedWeight += gradeCounts[i] * grade.weight;
}
return glm::clamp(0.0f, math::to_percent(combinedWeight / totalPlays), 100.0f);
}
SkillCheck::Challenge SkillCheck::challenge_generate(entity::Character& character)
{
auto& schema = character.data.skillCheckSchema;
Challenge newChallenge;
Zone newZone{};
auto zoneSize = std::max(schema.zoneMin, schema.zoneBase - (schema.zoneScoreBonus * score));
newZone.min = math::random_max(1.0f - zoneSize);
newZone.max = newZone.min + zoneSize;
newChallenge.zone = newZone;
newChallenge.tryValue = 0.0f;
newChallenge.speed =
glm::clamp(schema.speedMin, schema.speedMin + (schema.speedScoreBonus * score), schema.speedMax);
if (math::random_bool())
{
newChallenge.tryValue = 1.0f;
newChallenge.speed *= -1;
}
return newChallenge;
}
SkillCheck::SkillCheck(entity::Character& character) { challenge = challenge_generate(character); }
void SkillCheck::reset(entity::Character& character)
{
challenge = challenge_generate(character);
queuedChallenge = {};
tryValue = challenge.tryValue;
score = 0;
combo = 0;
endTimer = 0;
endTimerMax = 0;
highScoreStart = 0;
isActive = true;
isRewardScoreAchieved = false;
isHighScoreAchieved = highScore > 0;
isHighScoreAchievedThisRun = false;
isGameOver = false;
itemEffectManager = {};
}
void SkillCheck::tick() { itemEffectManager.tick(); }
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text,
Toasts& toasts)
{
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
static constexpr ImVec4 PERFECT_COLOR = ImVec4(1, 1, 1, 0.50);
static constexpr auto BAR_SPACING_MULTIPLIER = 1.5f;
static constexpr auto LINE_HEIGHT = 5.0f;
static constexpr auto LINE_WIDTH_BONUS = 10.0f;
auto& dialogue = character.data.dialogue;
auto& schema = character.data.skillCheckSchema;
auto& itemSchema = character.data.itemSchema;
auto& strings = character.data.strings;
auto& style = ImGui::GetStyle();
auto drawList = ImGui::GetWindowDrawList();
auto position = ImGui::GetCursorScreenPos();
auto size = ImGui::GetContentRegionAvail();
auto spacing = ImGui::GetTextLineHeightWithSpacing() * BAR_SPACING_MULTIPLIER;
auto& io = ImGui::GetIO();
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
size.y = std::max(0.0f, size.y - menuButtonHeight);
auto bounds = ImVec4(position.x, position.y, size.x, size.y);
auto cursorPos = ImGui::GetCursorPos();
ImGui::Text(strings.get(Strings::ArcadeScoreComboFormat).c_str(), score, combo);
auto bestString =
std::vformat(strings.get(Strings::ArcadeBestScoreComboFormat), std::make_format_args(highScore, bestCombo));
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), highScore, bestCombo);
if (score == 0 && isActive)
{
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
ImGui::TextWrapped("%s", strings.get(Strings::SkillCheckInstructions).c_str());
}
auto barMin = ImVec2(position.x + (size.x * 0.5f) - (spacing * 0.5f), position.y + (spacing * 2.0f));
auto barMax = ImVec2(barMin.x + (spacing * 2.0f), barMin.y + size.y - (spacing * 4.0f));
auto endTimerProgress = (float)endTimer / endTimerMax;
auto bgColor = ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
bgColor = imgui::to_imvec4(imgui::to_vec4(bgColor) * BG_COLOR_MULTIPLIER);
drawList->AddRectFilled(barMin, barMax, ImGui::GetColorU32(bgColor));
auto barWidth = barMax.x - barMin.x;
auto barHeight = barMax.y - barMin.y;
auto sub_zones_get = [&](Zone& zone)
{
auto& min = zone.min;
auto& max = zone.max;
std::vector<Zone> zones{};
auto baseHeight = max - min;
auto center = (min + max) * 0.5f;
int zoneCount{};
for (auto& grade : schema.grades)
{
if (grade.isFailure) continue;
auto scale = powf(0.5f, (float)zoneCount);
auto halfHeight = baseHeight * scale * 0.5f;
zoneCount++;
zones.push_back({center - halfHeight, center + halfHeight});
}
return zones;
};
auto zone_draw = [&](Zone& zone, float alpha = 1.0f)
{
auto subZones = sub_zones_get(zone);
for (int i = 0; i < (int)subZones.size(); i++)
{
auto& subZone = subZones[i];
int layer = (int)subZones.size() - 1 - i;
ImVec2 rectMin = {barMin.x, barMin.y + subZone.min * barHeight};
ImVec2 rectMax = {barMax.x, barMin.y + subZone.max * barHeight};
ImVec4 color =
i == (int)subZones.size() - 1 ? PERFECT_COLOR : ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered);
color.w = (color.w - (float)layer / subZones.size()) * alpha;
drawList->AddRectFilled(rectMin, rectMax, ImGui::GetColorU32(color));
}
};
zone_draw(challenge.zone, isActive ? 1.0f : 0.0f);
auto lineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * tryValue));
auto lineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, lineMin.y + LINE_HEIGHT);
auto lineColor = LINE_COLOR;
lineColor.w = isActive ? 1.0f : endTimerProgress;
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(lineColor));
if (!isActive && !isGameOver)
{
zone_draw(queuedChallenge.zone, 1.0f - endTimerProgress);
auto queuedLineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * queuedChallenge.tryValue));
auto queuedLineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, queuedLineMin.y + LINE_HEIGHT);
auto queuedLineColor = LINE_COLOR;
queuedLineColor.w = 1.0f - endTimerProgress;
drawList->AddRectFilled(queuedLineMin, queuedLineMax, ImGui::GetColorU32(queuedLineColor));
}
if (isActive)
{
tryValue += challenge.speed;
if (tryValue > 1.0f || tryValue < 0.0f)
{
tryValue = tryValue > 1.0f ? 0.0f : tryValue < 0.0f ? 1.0f : tryValue;
if (score > 0)
{
score--;
schema.sounds.scoreLoss.play();
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeScoreLoss).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y);
toasts.spawn(strings.get(Strings::ArcadeScoreLoss), toastMessagePosition, schema.endTimerMax);
}
}
ImGui::SetCursorScreenPos(barMin);
auto barButtonSize = ImVec2(barMax.x - barMin.x, barMax.y - barMin.y);
if (ImGui::IsKeyPressed(ImGuiKey_Space) ||
WIDGET_FX(ImGui::InvisibleButton("##SkillCheckBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
{
int gradeID{};
auto subZones = sub_zones_get(challenge.zone);
for (int i = 0; i < (int)subZones.size(); i++)
{
auto& subZone = subZones[i];
if (tryValue >= subZone.min && tryValue <= subZone.max)
gradeID = std::min((int)gradeID + 1, (int)schema.grades.size() - 1);
}
gradeCounts[gradeID]++;
totalPlays++;
auto& grade = schema.grades.at(gradeID);
grade.sound.play();
if (text.is_interruptible() && grade.pool.is_valid()) text.set(dialogue.get(grade.pool), character);
if (!grade.isFailure)
{
combo++;
score += grade.value;
if (score >= schema.rewardScore && !isRewardScoreAchieved)
{
schema.sounds.rewardScore.play();
isRewardScoreAchieved = true;
for (auto& itemID : itemSchema.skillCheckRewardItemPool)
itemRewards.item_give(itemID, inventory, itemEffectManager, itemSchema, bounds);
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeRewardToast).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
toasts.spawn(strings.get(Strings::ArcadeRewardToast), toastMessagePosition, schema.endTimerMax);
}
if (score > highScore)
{
highScore = score;
if (isHighScoreAchieved && !isHighScoreAchievedThisRun)
{
isHighScoreAchievedThisRun = true;
schema.sounds.highScore.play();
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeHighScoreToast).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
toasts.spawn(strings.get(Strings::ArcadeHighScoreToast), toastMessagePosition, schema.endTimerMax);
}
}
if (combo > bestCombo) bestCombo = combo;
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score) +
(schema.rewardRollGradeBonus * grade.value);
itemRewards.reward_random_items_try(inventory, itemEffectManager, itemSchema, bounds, rewardChance,
rewardRollCount);
}
else
{
score = 0;
combo = 0;
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
if (highScore > 0) isHighScoreAchieved = true;
isRewardScoreAchieved = false;
isHighScoreAchievedThisRun = false;
highScoreStart = highScore;
isGameOver = true;
}
endTimerMax = grade.isFailure ? schema.endTimerFailureMax : schema.endTimerMax;
isActive = false;
endTimer = endTimerMax;
queuedChallenge = challenge_generate(character);
auto string = grade.isFailure ? grade.name
: std::vformat(strings.get(Strings::SkillCheckGradeSuccessTemplate),
std::make_format_args(grade.name, grade.value));
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize(string.c_str()).x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
toasts.spawn(string, toastMessagePosition, endTimerMax);
}
}
else
{
endTimer--;
if (endTimer <= 0)
{
challenge = queuedChallenge;
tryValue = challenge.tryValue;
isActive = true;
isGameOver = false;
}
}
toasts.update(drawList);
itemEffectManager.render(resources, itemSchema, bounds, io.DeltaTime);
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
if (ImGui::IsItemHovered())
ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
return isMenuPressed;
}
}
@@ -1,71 +0,0 @@
#pragma once
#include "../item_effect_manager.hpp"
#include "../../item/reward.hpp"
#include "../toasts.hpp"
#include "../../../../entity/character.hpp"
#include "../../../../resources.hpp"
#include "../inventory.hpp"
#include "../../text.hpp"
#include <imgui.h>
#include <map>
#include <vector>
namespace game::state::play::menu::arcade
{
class SkillCheck
{
public:
struct Zone
{
float min{};
float max{};
};
struct Challenge
{
Zone zone{};
float speed{};
float tryValue{};
int level{};
};
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{};
game::state::play::menu::ItemEffectManager itemEffectManager{};
game::state::play::item::Reward itemRewards{};
SkillCheck() = default;
SkillCheck(entity::Character&);
Challenge challenge_generate(entity::Character&);
void reset(entity::Character&);
void tick();
bool update(Resources&, entity::Character&, Inventory&, Text&, Toasts&);
float accuracy_score_get(entity::Character&);
};
}
+619
View File
@@ -0,0 +1,619 @@
#include "gather.hpp"
#include "../../../resource/font.hpp"
#include "../../../util/imgui.hpp"
#include "../../../util/math.hpp"
#include "../../../util/time.hpp"
#include <algorithm>
#include <cmath>
#include <format>
#include <imgui.h>
namespace game::state::play::menu
{
constexpr auto CANVAS_MIN_SIZE = 1.0f;
constexpr auto CENTER_RATIO = 0.5f;
constexpr auto START_TEXT_Y_RATIO = 0.75f;
constexpr auto PLAYER_ACCELERATION = 3.0f;
constexpr auto PLAYER_FRICTION_PER_SECOND = 0.72f;
constexpr auto PLAYER_SIZE = 32.0f;
constexpr auto PLAYER_VELOCITY_MAX = 720.0f;
constexpr auto PLAYER_VELOCITY_STOP = 0.0f;
constexpr auto PLAYER_BOOST_VELOCITY_GAIN = 320.0f;
constexpr auto PLAYER_BOOST_VELOCITY_MIN = 540.0f;
constexpr auto PLAYER_BOOST_DURATION = 0.35f;
constexpr auto BOOST_DIRECTION_EPSILON = 0.001f;
constexpr auto BOOST_DIRECTION_UNIT = 1.0f;
constexpr auto GAME_TEXT_FONT_SIZE = resource::Font::HEADER_1;
constexpr auto STAMINA_MIN = 0.0f;
constexpr auto STAMINA_MAX = 1.0f;
constexpr auto STAMINA_REGEN_PER_TICK = 0.002f;
constexpr auto STAMINA_BOOST_COST = 0.25f;
constexpr auto STAMINA_MAGNET_COST = 0.25f;
constexpr auto STAMINA_BAR_WIDTH = 120.0f;
constexpr auto STAMINA_BAR_HEIGHT = GAME_TEXT_FONT_SIZE;
constexpr auto STAMINA_BAR_PADDING = 8.0f;
constexpr auto STAMINA_BAR_PADDING_MULTIPLIER = 2.0f;
constexpr auto STAMINA_BAR_ROUNDING = 2.0f;
constexpr auto COLLECTIBLE_SIZE = 20.0f;
constexpr auto COLLECTIBLE_OSCILLATION_SPEED = 6.0f;
constexpr auto COLLECTIBLE_OSCILLATION_AMOUNT = 3.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS = 72.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTENDED = 240.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS = 1.0f;
constexpr auto COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER = 0.5f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION = 5200.0f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_GAIN = 26000.0f;
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_MAX = 26000.0f;
constexpr auto COLLECTIBLE_MAGNET_VELOCITY_MIN = 900.0f;
constexpr auto COLLECTIBLE_MAGNET_SPEED_MAX = 1800.0f;
constexpr auto COLLECTIBLE_MAGNET_FRICTION_PER_SECOND = 0.98f;
constexpr auto COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP = 0.35f;
constexpr auto COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER = 2.0f;
constexpr auto COLLECTIBLE_STRUCTURE_SPACING = 34.0f;
constexpr auto COLLECTIBLE_STRUCTURE_COUNT_MIN = 3;
constexpr auto COLLECTIBLE_POLYGON_RADIUS_BASE = 28.0f;
constexpr auto COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE = 4.5f;
constexpr auto STAMINA_PICKUP_COUNT = 1;
constexpr auto COLLECTIBLE_TWO_PI = 6.283185f;
constexpr auto COLLECTIBLE_POLYGON_CHANCE = 0.20f;
constexpr auto COLLECTIBLE_LINE_CHANCE = 0.55f;
constexpr auto SCORE_TEXT_PADDING = 8.0f;
constexpr auto SCORE_TEXT_LINE_MULTIPLIER = 1.15f;
constexpr auto GAME_TIME_START_SECONDS = 30.0f;
constexpr auto GAME_TIME_START_TICKS = GAME_TIME_START_SECONDS * Entity::UPDATE_RATE;
constexpr auto GAME_TIME_GAIN_SECONDS = 5.0f;
constexpr auto GAME_TIME_GAIN_TICKS = GAME_TIME_GAIN_SECONDS * Entity::UPDATE_RATE;
constexpr auto WAVE_RESET_FADE_SECONDS = 0.125f;
constexpr auto TIME_GAIN_ALPHA_MAX = 1.0f;
constexpr auto TIME_GAIN_FADE_SECONDS = 1.25f;
constexpr auto TIME_TEXT_PADDING = 8.0f;
constexpr auto COLOR_CHANNEL_MAX = 255;
constexpr auto COLOR_GREEN_DIM = 64;
constexpr auto COLOR_GREEN = 220;
constexpr auto COLOR_COLLECTIBLE_BLUE = 255;
constexpr auto COLOR_TIME = 255;
constexpr auto COLOR_MAGNET_BLUE = 255;
constexpr auto COLOR_MAGNET_ALPHA = 70;
constexpr auto MAGNET_CIRCLE_SEGMENTS = 96;
constexpr auto MAGNET_CIRCLE_THICKNESS = 2.0f;
constexpr auto COLOR_COLLECTIBLE_ALPHA = 255;
constexpr auto COLOR_COLLECTIBLE_NEXT_ALPHA = 35;
constexpr auto STYLE_VAR_COUNT = 1;
constexpr auto CANVAS_FLAGS = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
float Gather::random_range_get(float min, float max)
{
if (min >= max) return min;
return util::math::random_in_range(min, max);
}
glm::vec2 Gather::random_position_get(float minX, float minY, float maxX, float maxY)
{
return {random_range_get(minX, maxX), random_range_get(minY, maxY)};
}
Entity Gather::collectible_make(glm::vec2 position)
{
Entity collectible{};
collectible.position = position;
collectible.phase = util::math::random_max(COLLECTIBLE_TWO_PI);
return collectible;
}
Gather::Structure Gather::structure_get(int remaining)
{
auto roll = util::math::random();
if (remaining < COLLECTIBLE_STRUCTURE_COUNT_MIN) return Structure::SINGLE;
if (roll < COLLECTIBLE_POLYGON_CHANCE) return Structure::POLYGON;
if (roll < COLLECTIBLE_LINE_CHANCE)
return util::math::random_bool() ? Structure::LINE_HORIZONTAL : Structure::LINE_VERTICAL;
return Structure::SINGLE;
}
int Gather::structure_count_get(int remaining)
{
auto countRange = (remaining - COLLECTIBLE_STRUCTURE_COUNT_MIN) + 1;
auto countRoll = std::min(countRange - 1, (int)util::math::random_max((float)countRange));
return COLLECTIBLE_STRUCTURE_COUNT_MIN + countRoll;
}
int Gather::wave_collectible_count_get(int wave) { return std::max(1, wave); }
void Gather::collectible_add(std::vector<Entity>& wave, glm::vec2 position, int targetCount)
{
if ((int)wave.size() >= targetCount) return;
wave.emplace_back(collectible_make(position));
}
void Gather::single_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int targetCount)
{
auto position = random_position_get(margin, margin, canvasSize.x - margin, canvasSize.y - margin);
collectible_add(wave, position, targetCount);
}
void Gather::line_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, bool isHorizontal,
int structureCount, int targetCount)
{
auto structureHalf = COLLECTIBLE_STRUCTURE_SPACING * ((float)structureCount - 1) * CENTER_RATIO;
auto clearance = structureHalf + (COLLECTIBLE_SIZE * CENTER_RATIO);
auto center =
isHorizontal
? random_position_get(margin + clearance, margin, canvasSize.x - margin - clearance, canvasSize.y - margin)
: random_position_get(margin, margin + clearance, canvasSize.x - margin, canvasSize.y - margin - clearance);
for (auto i = 0; i < structureCount; ++i)
{
auto offset = ((float)i - (((float)structureCount - 1) * CENTER_RATIO)) * COLLECTIBLE_STRUCTURE_SPACING;
auto position = isHorizontal ? glm::vec2(center.x + offset, center.y) : glm::vec2(center.x, center.y + offset);
collectible_add(wave, position, targetCount);
}
}
float Gather::polygon_radius_get(int structureCount)
{
return COLLECTIBLE_POLYGON_RADIUS_BASE + ((float)structureCount * COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE);
}
void Gather::polygon_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int structureCount,
int targetCount)
{
auto radius = polygon_radius_get(structureCount);
auto clearance = radius + (COLLECTIBLE_SIZE * CENTER_RATIO);
auto center = random_position_get(margin + clearance, margin + clearance, canvasSize.x - margin - clearance,
canvasSize.y - margin - clearance);
for (auto i = 0; i < structureCount; ++i)
{
auto angle = COLLECTIBLE_TWO_PI * ((float)i / (float)structureCount);
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
collectible_add(wave, position, targetCount);
}
}
void Gather::wave_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, int waveNumber)
{
wave.clear();
auto targetCount = wave_collectible_count_get(waveNumber);
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
while ((int)wave.size() < targetCount)
{
auto remaining = targetCount - (int)wave.size();
switch (structure_get(remaining))
{
case Structure::LINE_HORIZONTAL:
line_spawn(wave, canvasSize, margin, true, structure_count_get(remaining), targetCount);
break;
case Structure::LINE_VERTICAL:
line_spawn(wave, canvasSize, margin, false, structure_count_get(remaining), targetCount);
break;
case Structure::POLYGON:
polygon_spawn(wave, canvasSize, margin, structure_count_get(remaining), targetCount);
break;
case Structure::SINGLE:
default:
single_spawn(wave, canvasSize, margin, targetCount);
break;
}
}
}
int Gather::stamina_pickup_count_get(int) { return STAMINA_PICKUP_COUNT; }
void Gather::stamina_pickups_spawn(std::vector<Entity>& pickups, ImVec2 canvasSize, int waveNumber)
{
pickups.clear();
auto targetCount = stamina_pickup_count_get(waveNumber);
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
while ((int)pickups.size() < targetCount)
single_spawn(pickups, canvasSize, margin, targetCount);
}
float Gather::wave_time_gain_ticks_get() { return GAME_TIME_GAIN_TICKS; }
bool Gather::collectible_hit_get(const Entity& collectible, const Entity& player)
{
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
auto distance = glm::abs(player.position - collectible.position);
return distance.x <= playerHalf + collectibleHalf && distance.y <= playerHalf + collectibleHalf;
}
float Gather::magnet_radius_get(float magnetRadiusTime, bool isMenuFullscreen)
{
auto multiplier = isMenuFullscreen ? STAMINA_MAX : COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER;
auto baseRadius = COLLECTIBLE_MAGNET_RADIUS * multiplier;
auto extendedRadius = COLLECTIBLE_MAGNET_RADIUS_EXTENDED * multiplier;
auto ratio = std::clamp(magnetRadiusTime / COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS, STAMINA_MIN, STAMINA_MAX);
return baseRadius + ((extendedRadius - baseRadius) * ratio);
}
int Gather::collectibles_collect(std::vector<Entity>& wave, const Entity& player)
{
auto collected = 0;
for (auto& collectible : wave)
{
if (collectible.isCollected || !collectible_hit_get(collectible, player)) continue;
collectible.isCollected = true;
++collected;
}
return collected;
}
void Gather::collectibles_magnet_update(std::vector<Entity>& wave, const Entity& player, ImVec2 canvasSize,
float magnetRadius, float dt)
{
auto radiusSquared = magnetRadius * magnetRadius;
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
auto minX = collectibleHalf;
auto minY = collectibleHalf;
auto maxX = std::max(minX, canvasSize.x - collectibleHalf);
auto maxY = std::max(minY, canvasSize.y - collectibleHalf);
for (auto& collectible : wave)
{
if (collectible.isCollected) continue;
auto direction = player.position - collectible.position;
auto distanceSquared = (direction.x * direction.x) + (direction.y * direction.y);
if (distanceSquared <= radiusSquared) collectible.isMagnetized = true;
if (collectible.isMagnetized && distanceSquared > BOOST_DIRECTION_EPSILON)
{
collectible.magnetTime += dt;
auto magnetAcceleration =
std::min(COLLECTIBLE_MAGNET_ACCELERATION_MAX,
COLLECTIBLE_MAGNET_ACCELERATION + (COLLECTIBLE_MAGNET_ACCELERATION_GAIN * collectible.magnetTime));
auto distance = std::sqrt(distanceSquared);
auto directionUnit = direction * (BOOST_DIRECTION_UNIT / distance);
auto inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
auto inward = directionUnit * inwardVelocity;
auto sideways = collectible.velocity - inward;
collectible.velocity = inward + (sideways * COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP);
collectible.velocity += directionUnit * magnetAcceleration * dt;
inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
if (inwardVelocity < COLLECTIBLE_MAGNET_VELOCITY_MIN)
collectible.velocity += directionUnit * (COLLECTIBLE_MAGNET_VELOCITY_MIN - inwardVelocity);
}
auto velocityLength = std::sqrt((collectible.velocity.x * collectible.velocity.x) +
(collectible.velocity.y * collectible.velocity.y));
if (velocityLength > COLLECTIBLE_MAGNET_SPEED_MAX)
{
auto velocityScale = COLLECTIBLE_MAGNET_SPEED_MAX / velocityLength;
collectible.velocity *= velocityScale;
}
auto velocityRetention = std::pow(COLLECTIBLE_MAGNET_FRICTION_PER_SECOND, dt);
collectible.velocity *= velocityRetention;
collectible.position += collectible.velocity * dt;
if (collectible.position.x < minX || collectible.position.x > maxX) collectible.velocity.x = PLAYER_VELOCITY_STOP;
if (collectible.position.y < minY || collectible.position.y > maxY) collectible.velocity.y = PLAYER_VELOCITY_STOP;
collectible.position.x = std::clamp(collectible.position.x, minX, maxX);
collectible.position.y = std::clamp(collectible.position.y, minY, maxY);
}
}
bool Gather::wave_complete_get(const std::vector<Entity>& wave)
{
if (wave.empty()) return false;
for (auto& collectible : wave)
if (!collectible.isCollected) return false;
return true;
}
void Gather::collectibles_draw(const std::vector<Entity>& wave, ImDrawList* drawList, ImVec2 canvasMin, float time,
ImU32 color)
{
for (auto& collectible : wave)
{
if (collectible.isCollected) continue;
auto size = COLLECTIBLE_SIZE + (std::sin((time * COLLECTIBLE_OSCILLATION_SPEED) + collectible.phase) *
COLLECTIBLE_OSCILLATION_AMOUNT);
auto half = size * CENTER_RATIO;
auto min = ImVec2(canvasMin.x + collectible.position.x - half, canvasMin.y + collectible.position.y - half);
auto max = ImVec2(min.x + size, min.y + size);
drawList->AddRectFilled(min, max, color);
}
}
void Gather::magnet_radius_draw(ImDrawList* drawList, ImVec2 canvasMin, const Entity& player, float radius)
{
auto center = ImVec2(canvasMin.x + player.position.x, canvasMin.y + player.position.y);
drawList->AddCircle(center, radius, IM_COL32(0, 0, COLOR_MAGNET_BLUE, COLOR_MAGNET_ALPHA), MAGNET_CIRCLE_SEGMENTS,
MAGNET_CIRCLE_THICKNESS);
}
int Gather::alpha_get(int alpha, float multiplier)
{
return (int)((float)alpha * std::clamp(multiplier, STAMINA_MIN, STAMINA_MAX));
}
void Gather::progress_bar_draw(ImDrawList* drawList, ImVec2 min, ImVec2 max, float progress, ImU32 backgroundColor,
ImU32 fillColor, const std::string& text)
{
auto fillMax = ImVec2(min.x + ((max.x - min.x) * std::clamp(progress, STAMINA_MIN, STAMINA_MAX)), max.y);
drawList->AddRectFilled(min, max, backgroundColor, STAMINA_BAR_ROUNDING);
drawList->AddRectFilled(min, fillMax, fillColor, STAMINA_BAR_ROUNDING);
if (text.empty()) return;
auto textSize = util::imgui::draw_list_text_size_get(text, GAME_TEXT_FONT_SIZE);
auto textPos = ImVec2(min.x + ((max.x - min.x - textSize.x) * CENTER_RATIO),
min.y + ((max.y - min.y - textSize.y) * CENTER_RATIO));
util::imgui::draw_list_text_draw(
*drawList, textPos, text, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
void Gather::reset()
{
player = {};
collectibles.clear();
nextCollectibles.clear();
staminaPickups.clear();
nextStaminaPickups.clear();
targetPosition = {};
stamina = STAMINA_MAX;
boostTime = STAMINA_MIN;
magnetRadiusTime = STAMINA_MIN;
timeTicks = GAME_TIME_START_TICKS;
timeGainTicks = STAMINA_MIN;
timeGainAlpha = STAMINA_MIN;
score = 0;
wave = 1;
isStarted = false;
isPaused = false;
isMenuFullscreenKnown = false;
isMenuFullscreenLast = false;
isWaveResetPending = false;
waveResetAlpha = STAMINA_MAX;
}
void Gather::start(ImVec2 canvasSize)
{
reset();
player.position = glm::vec2(canvasSize.x * CENTER_RATIO, canvasSize.y * CENTER_RATIO);
targetPosition = player.position;
isStarted = true;
wave_reset(canvasSize);
}
void Gather::wave_reset(ImVec2 canvasSize)
{
wave_spawn(collectibles, canvasSize, wave);
wave_spawn(nextCollectibles, canvasSize, wave + 1);
stamina_pickups_spawn(staminaPickups, canvasSize, wave);
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
magnetRadiusTime = STAMINA_MIN;
isWaveResetPending = false;
waveResetAlpha = STAMINA_MAX;
}
void Gather::wave_reset_request()
{
isWaveResetPending = true;
waveResetAlpha = STAMINA_MAX;
magnetRadiusTime = STAMINA_MIN;
}
void Gather::score_draw(ImDrawList* drawList, ImVec2 canvasMin, int score, int wave)
{
auto scoreText = std::format("{}", score);
auto position = ImVec2(canvasMin.x + SCORE_TEXT_PADDING, canvasMin.y + SCORE_TEXT_PADDING);
util::imgui::draw_list_text_draw(
*drawList, position, scoreText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
auto waveText = std::format("Wave #{}", wave);
auto wavePosition = ImVec2(position.x, position.y + (GAME_TEXT_FONT_SIZE * SCORE_TEXT_LINE_MULTIPLIER));
util::imgui::draw_list_text_draw(
*drawList, wavePosition, waveText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
void Gather::time_draw(ImDrawList* drawList, ImVec2 canvasMin, ImVec2 canvasMax, float timeTicks, float timeGainTicks,
float timeGainAlpha)
{
auto timeText = util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeTicks));
auto textSize = util::imgui::draw_list_text_size_get(timeText, GAME_TEXT_FONT_SIZE);
auto textWidth = util::imgui::draw_list_digit_fixed_width_get(timeText, GAME_TEXT_FONT_SIZE);
auto textPos = ImVec2(canvasMax.x - textWidth - TIME_TEXT_PADDING, canvasMin.y + TIME_TEXT_PADDING);
util::imgui::draw_list_digit_fixed_width_draw(*drawList, textPos, timeText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, COLOR_CHANNEL_MAX));
if (timeGainAlpha <= STAMINA_MIN) return;
auto gainText =
std::format("+{}", util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeGainTicks)));
auto alpha = (int)(COLOR_CHANNEL_MAX * std::clamp(timeGainAlpha, STAMINA_MIN, STAMINA_MAX));
auto gainTextWidth = util::imgui::draw_list_digit_fixed_width_get(gainText, GAME_TEXT_FONT_SIZE);
auto gainPos = ImVec2(canvasMax.x - gainTextWidth - TIME_TEXT_PADDING, textPos.y + textSize.y + TIME_TEXT_PADDING);
util::imgui::draw_list_digit_fixed_width_draw(*drawList, gainPos, gainText, GAME_TEXT_FONT_SIZE,
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, alpha));
}
void Gather::update(bool isMenuFullscreen, bool isMenuResizeSettled)
{
auto canvasSize = ImGui::GetContentRegionAvail();
if (canvasSize.x <= CANVAS_MIN_SIZE || canvasSize.y <= CANVAS_MIN_SIZE) return;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Gather Canvas Child", canvasSize, ImGuiChildFlags_None, CANVAS_FLAGS))
{
canvasSize = ImGui::GetContentRegionAvail();
auto canvasMin = ImGui::GetCursorScreenPos();
auto canvasMax = ImVec2(canvasMin.x + canvasSize.x, canvasMin.y + canvasSize.y);
auto drawList = ImGui::GetWindowDrawList();
auto dt = ImGui::GetIO().DeltaTime;
auto isCanvasFocused = ImGui::IsWindowFocused();
ImGui::InvisibleButton("##Gather Canvas", canvasSize);
auto isCanvasHovered = ImGui::IsItemHovered();
drawList->AddRectFilled(canvasMin, canvasMax, IM_COL32(0, 0, 0, COLOR_CHANNEL_MAX / 2));
if (!isMenuFullscreenKnown)
{
isMenuFullscreenKnown = true;
isMenuFullscreenLast = isMenuFullscreen;
}
auto isMenuFullscreenChanged = isMenuFullscreenLast != isMenuFullscreen;
isMenuFullscreenLast = isMenuFullscreen;
if (isStarted && isMenuFullscreenChanged) wave_reset_request();
if (isStarted && isWaveResetPending)
{
waveResetAlpha = std::max(STAMINA_MIN, waveResetAlpha - (dt / WAVE_RESET_FADE_SECONDS));
if (isMenuResizeSettled) wave_reset(canvasSize);
}
if (!isStarted && isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) start(canvasSize);
if (isStarted)
{
if (isCanvasFocused && ImGui::IsKeyPressed(ImGuiKey_P, false)) isPaused = !isPaused;
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto minX = std::min(playerHalf, canvasSize.x * CENTER_RATIO);
auto minY = std::min(playerHalf, canvasSize.y * CENTER_RATIO);
auto maxX = std::max(minX, canvasSize.x - playerHalf);
auto maxY = std::max(minY, canvasSize.y - playerHalf);
player.position.x = std::clamp(player.position.x, minX, maxX);
player.position.y = std::clamp(player.position.y, minY, maxY);
targetPosition.x = std::clamp(targetPosition.x, minX, maxX);
targetPosition.y = std::clamp(targetPosition.y, minY, maxY);
if (!isPaused)
{
if (isCanvasHovered)
{
auto mouse = ImGui::GetIO().MousePos;
targetPosition =
glm::vec2(std::clamp(mouse.x - canvasMin.x, minX, maxX), std::clamp(mouse.y - canvasMin.y, minY, maxY));
}
timeTicks = std::max(STAMINA_MIN, timeTicks - (Entity::UPDATE_RATE * dt));
if (timeTicks <= STAMINA_MIN) reset();
if (isStarted)
{
timeGainAlpha = std::max(STAMINA_MIN, timeGainAlpha - (dt / TIME_GAIN_FADE_SECONDS));
stamina =
std::clamp(stamina + (STAMINA_REGEN_PER_TICK * Entity::UPDATE_RATE * dt), STAMINA_MIN, STAMINA_MAX);
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && stamina >= STAMINA_MAGNET_COST)
{
magnetRadiusTime = COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS;
stamina = std::max(STAMINA_MIN, stamina - STAMINA_MAGNET_COST);
}
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && stamina >= STAMINA_BOOST_COST)
{
auto boostDirection = targetPosition - player.position;
auto boostDirectionLength =
std::sqrt((boostDirection.x * boostDirection.x) + (boostDirection.y * boostDirection.y));
if (boostDirectionLength > BOOST_DIRECTION_EPSILON)
{
auto boostDirectionScale = BOOST_DIRECTION_UNIT / boostDirectionLength;
auto boostDirectionUnit = boostDirection * boostDirectionScale;
player.velocity.x += boostDirectionUnit.x * PLAYER_BOOST_VELOCITY_GAIN;
player.velocity.y += boostDirectionUnit.y * PLAYER_BOOST_VELOCITY_GAIN;
auto boostVelocity =
(player.velocity.x * boostDirectionUnit.x) + (player.velocity.y * boostDirectionUnit.y);
if (boostVelocity < PLAYER_BOOST_VELOCITY_MIN)
{
auto boostVelocityAdd = PLAYER_BOOST_VELOCITY_MIN - boostVelocity;
player.velocity.x += boostDirectionUnit.x * boostVelocityAdd;
player.velocity.y += boostDirectionUnit.y * boostVelocityAdd;
}
boostTime = PLAYER_BOOST_DURATION;
stamina = std::max(STAMINA_MIN, stamina - STAMINA_BOOST_COST);
}
}
player.velocity.x += (targetPosition.x - player.position.x) * PLAYER_ACCELERATION * dt;
player.velocity.y += (targetPosition.y - player.position.y) * PLAYER_ACCELERATION * dt;
auto velocityRetention = std::pow(PLAYER_FRICTION_PER_SECOND, dt);
player.velocity.x *= velocityRetention;
player.velocity.y *= velocityRetention;
auto velocityLength =
std::sqrt((player.velocity.x * player.velocity.x) + (player.velocity.y * player.velocity.y));
auto isBoostActive = boostTime > STAMINA_MIN;
if (!isBoostActive && velocityLength > PLAYER_VELOCITY_MAX)
{
auto velocityScale = PLAYER_VELOCITY_MAX / velocityLength;
player.velocity.x *= velocityScale;
player.velocity.y *= velocityScale;
}
boostTime = std::max(STAMINA_MIN, boostTime - dt);
player.position.x += player.velocity.x * dt;
player.position.y += player.velocity.y * dt;
if (player.position.x < minX || player.position.x > maxX) player.velocity.x = PLAYER_VELOCITY_STOP;
if (player.position.y < minY || player.position.y > maxY) player.velocity.y = PLAYER_VELOCITY_STOP;
player.position.x = std::clamp(player.position.x, minX, maxX);
player.position.y = std::clamp(player.position.y, minY, maxY);
if (!isWaveResetPending)
{
auto magnetRadius = magnet_radius_get(magnetRadiusTime, isMenuFullscreen);
collectibles_magnet_update(collectibles, player, canvasSize, magnetRadius, dt);
magnetRadiusTime = std::max(STAMINA_MIN, magnetRadiusTime - dt);
score += collectibles_collect(collectibles, player);
if (collectibles_collect(staminaPickups, player) > 0) stamina = STAMINA_MAX;
if (wave_complete_get(collectibles))
{
timeGainTicks = wave_time_gain_ticks_get();
timeTicks += timeGainTicks;
timeGainAlpha = TIME_GAIN_ALPHA_MAX;
++wave;
collectibles = nextCollectibles;
staminaPickups = nextStaminaPickups;
wave_spawn(nextCollectibles, canvasSize, wave + 1);
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
}
}
}
}
}
if (isStarted)
{
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
auto collectibleAlpha = alpha_get(COLOR_COLLECTIBLE_ALPHA, waveResetAlpha);
auto nextCollectibleAlpha = alpha_get(COLOR_COLLECTIBLE_NEXT_ALPHA, waveResetAlpha);
collectibles_draw(nextCollectibles, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, nextCollectibleAlpha));
collectibles_draw(nextStaminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, COLOR_GREEN, 0, nextCollectibleAlpha));
collectibles_draw(collectibles, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, collectibleAlpha));
collectibles_draw(staminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
IM_COL32(0, COLOR_GREEN, 0, collectibleAlpha));
magnet_radius_draw(drawList, canvasMin, player, magnet_radius_get(magnetRadiusTime, isMenuFullscreen));
auto playerMin =
ImVec2(canvasMin.x + player.position.x - playerHalf, canvasMin.y + player.position.y - playerHalf);
auto playerMax = ImVec2(playerMin.x + PLAYER_SIZE, playerMin.y + PLAYER_SIZE);
drawList->AddRectFilled(playerMin, playerMax,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
auto staminaBarWidth =
std::min(STAMINA_BAR_WIDTH,
std::max(CANVAS_MIN_SIZE, canvasSize.x - (STAMINA_BAR_PADDING * STAMINA_BAR_PADDING_MULTIPLIER)));
auto staminaBarMin = ImVec2(canvasMax.x - staminaBarWidth - STAMINA_BAR_PADDING,
canvasMax.y - STAMINA_BAR_HEIGHT - STAMINA_BAR_PADDING);
auto staminaBarMax = ImVec2(staminaBarMin.x + staminaBarWidth, staminaBarMin.y + STAMINA_BAR_HEIGHT);
progress_bar_draw(drawList, staminaBarMin, staminaBarMax, stamina,
IM_COL32(0, COLOR_GREEN_DIM, 0, COLOR_CHANNEL_MAX),
IM_COL32(0, COLOR_GREEN, 0, COLOR_CHANNEL_MAX));
score_draw(drawList, canvasMin, score, wave);
time_draw(drawList, canvasMin, canvasMax, timeTicks, timeGainTicks, timeGainAlpha);
if (isPaused)
util::imgui::draw_list_text_center_draw(
*drawList, canvasMin, canvasSize, "PAUSED", GAME_TEXT_FONT_SIZE, CENTER_RATIO, CENTER_RATIO,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
else
util::imgui::draw_list_text_center_draw(
*drawList, canvasMin, canvasSize, "Click to start!", GAME_TEXT_FONT_SIZE, CENTER_RATIO, START_TEXT_Y_RATIO,
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
}
ImGui::EndChild();
ImGui::PopStyleVar(STYLE_VAR_COUNT);
}
}
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include "../../../entity.hpp"
#include <imgui.h>
#include <string>
#include <vector>
namespace game::state::play::menu
{
class Gather
{
public:
Entity player{};
std::vector<Entity> collectibles{};
std::vector<Entity> nextCollectibles{};
std::vector<Entity> staminaPickups{};
std::vector<Entity> nextStaminaPickups{};
glm::vec2 targetPosition{};
float stamina{};
float boostTime{};
float magnetRadiusTime{};
float timeTicks{};
float timeGainTicks{};
float timeGainAlpha{};
int score{};
int wave{};
bool isStarted{};
bool isPaused{};
bool isMenuFullscreenKnown{};
bool isMenuFullscreenLast{};
bool isWaveResetPending{};
float waveResetAlpha{};
void update(bool isMenuFullscreen, bool isMenuResizeSettled);
private:
enum class Structure
{
SINGLE,
LINE_HORIZONTAL,
LINE_VERTICAL,
POLYGON
};
static float random_range_get(float, float);
static glm::vec2 random_position_get(float, float, float, float);
static Entity collectible_make(glm::vec2);
static Structure structure_get(int);
static int structure_count_get(int);
static int wave_collectible_count_get(int);
static void collectible_add(std::vector<Entity>&, glm::vec2, int);
static void single_spawn(std::vector<Entity>&, ImVec2, float, int);
static void line_spawn(std::vector<Entity>&, ImVec2, float, bool, int, int);
static float polygon_radius_get(int);
static void polygon_spawn(std::vector<Entity>&, ImVec2, float, int, int);
static void wave_spawn(std::vector<Entity>&, ImVec2, int);
static int stamina_pickup_count_get(int);
static void stamina_pickups_spawn(std::vector<Entity>&, ImVec2, int);
static float wave_time_gain_ticks_get();
static bool collectible_hit_get(const Entity&, const Entity&);
static float magnet_radius_get(float, bool isMenuFullscreen);
static int collectibles_collect(std::vector<Entity>&, const Entity&);
static void collectibles_magnet_update(std::vector<Entity>&, const Entity&, ImVec2, float, float);
static bool wave_complete_get(const std::vector<Entity>&);
static void collectibles_draw(const std::vector<Entity>&, ImDrawList*, ImVec2, float, ImU32);
static void magnet_radius_draw(ImDrawList*, ImVec2, const Entity&, float);
static int alpha_get(int, float);
static void progress_bar_draw(ImDrawList*, ImVec2, ImVec2, float, ImU32, ImU32, const std::string& = {});
static void score_draw(ImDrawList*, ImVec2, int, int);
static void time_draw(ImDrawList*, ImVec2, ImVec2, float, float, float);
void reset();
void start(ImVec2);
void wave_reset(ImVec2);
void wave_reset_request();
};
}
+61 -35
View File
@@ -1,7 +1,8 @@
#include "interact.hpp"
#include "../info.hpp"
#include "../../../util/imgui/widget.hpp"
#include "../../../util/measurement.hpp"
using namespace game::resource;
using namespace game::resource::xml;
@@ -10,48 +11,73 @@ using namespace game::util::imgui;
namespace game::state::play::menu
{
void Interact::update(Resources& resources, Text& text, entity::Character& character)
void Interact::update(Resources& resources, Text& text, Entity& character, bool)
{
static constexpr auto ZERO_FLOAT = 0.0f;
auto& dialogue = character.data.dialogue;
auto& strings = character.data.strings;
auto size = ImGui::GetContentRegionAvail();
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
if (dialogue.random.is_valid())
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(), ImVec2(size.x, 0))))
text.set(dialogue.get(dialogue.random), character);
ImGui::PopFont();
if (dialogue.help.is_valid())
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(), ImVec2(size.x, 0))))
text.set(dialogue.get(dialogue.help), character);
auto* random = dialogue.get(Schema::Element::RANDOM);
auto* help = dialogue.get(Schema::Element::HELP);
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(strings.get(Strings::InteractFeelingButton).c_str(), ImVec2(size.x, 0))))
text.set(dialogue.get(pool), character);
auto poolID = character.data.stage_pool_id_get(stage);
auto isFeelingAvailable = poolID != -1;
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::SeparatorText(character.data.name.c_str());
ImGui::SeparatorText(character.data.root()->name.c_str());
ImGui::PopFont();
auto& system = resources.settings.measurementSystem;
auto weight = character.weight_get(system);
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, info_height_get()));
ImGui::Text(strings.get(Strings::InteractWeightFormat).c_str(), weight, weightUnit,
character.stage_get() + 1);
ImGui::Text(strings.get(Strings::InteractCapacityFormat).c_str(), character.capacity,
character.max_capacity());
ImGui::Text(strings.get(Strings::InteractDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::Text(strings.get(Strings::InteractEatingSpeedFormat).c_str(), character.eatSpeed);
ImGui::Separator();
ImGui::Text(strings.get(Strings::InteractTotalCaloriesFormat).c_str(), character.totalCaloriesConsumed);
ImGui::Text(strings.get(Strings::InteractTotalFoodItemsFormat).c_str(), character.totalFoodItemsEaten);
auto buttonHeight = ZERO_FLOAT;
if (random)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
buttonHeight += ImGui::GetFrameHeightWithSpacing();
ImGui::PopFont();
}
if (help)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
buttonHeight += ImGui::GetFrameHeightWithSpacing();
ImGui::PopFont();
}
if (isFeelingAvailable)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
buttonHeight += ImGui::GetFrameHeightWithSpacing();
ImGui::PopFont();
}
auto fillerHeight = ImGui::GetContentRegionAvail().y - buttonHeight;
if (fillerHeight > ZERO_FLOAT) ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x, fillerHeight));
if (random)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
text.set(dialogue.dialogue_pool_entry_get(*random), character);
ImGui::PopFont();
}
if (help)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
text.set(dialogue.dialogue_entry_get(*help), character);
ImGui::PopFont();
}
if (poolID != -1)
{
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
if (WIDGET_FX(
ImGui::Button(strings.get(Strings::InteractFeelingButton).c_str(),
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
text.set(dialogue.dialogue_pool_entry_get(poolID), character);
ImGui::PopFont();
}
}
}
+1 -1
View File
@@ -9,6 +9,6 @@ namespace game::state::play::menu
class Interact
{
public:
void update(Resources&, Text&, entity::Character&);
void update(Resources&, Text&, Entity&, bool isMenuFullscreen);
};
}
+190 -162
View File
@@ -1,4 +1,5 @@
#include "inventory.hpp"
#include "../autofeed.hpp"
#include "../style.hpp"
#include <cmath>
@@ -15,7 +16,6 @@
using namespace game::util;
using namespace game::util::imgui;
using namespace game::entity;
using namespace game::resource;
using namespace glm;
@@ -23,14 +23,85 @@ namespace game::state::play::menu
{
using Strings = resource::xml::Strings;
void Inventory::tick()
namespace
{
for (auto& [i, actor] : actors)
actor.tick();
int& quantity_get(std::map<int, int>& values, const resource::xml::Schema& schema, int itemID)
{
auto& quantity = values[itemID];
quantity = glm::clamp(0, quantity, schema.quantityMax);
return quantity;
}
bool is_possible_to_upgrade_get(const resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
{
return item.isUpgradeID && item.isUpgradeCount && schema.idToStringMap.contains(item.upgradeID);
}
}
void Inventory::update(Resources& resources, ItemManager& itemManager, entity::Character& character)
bool Inventory::can_upgrade(const resource::xml::Schema& schema, int itemID)
{
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
auto& item = schema.items[itemID];
auto& quantity = quantity_get(values, schema, itemID);
return is_possible_to_upgrade_get(schema, item) && quantity >= item.upgradeCount;
}
bool Inventory::upgrade(resource::xml::Schema& schema, int itemID, bool isAll)
{
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
auto& item = schema.items[itemID];
auto& quantity = quantity_get(values, schema, itemID);
if (!is_possible_to_upgrade_get(schema, item) || quantity < item.upgradeCount)
{
schema.root()->soundUpgradeFail.play();
return false;
}
if (isAll)
{
while (quantity >= item.upgradeCount)
{
values[item.upgradeID]++;
quantity -= item.upgradeCount;
}
}
else
{
values[item.upgradeID]++;
quantity -= item.upgradeCount;
}
schema.root()->soundUpgrade.play();
if (quantity < item.upgradeCount && selectedItemID == itemID) selectedItemID = item.upgradeID;
return true;
}
bool Inventory::upgrade_all_possible(resource::xml::Schema& schema)
{
auto isUpgraded = false;
for (auto isPassUpgraded = true; isPassUpgraded;)
{
isPassUpgraded = false;
for (int itemID = 0; itemID < (int)schema.items.size(); itemID++)
{
if (!can_upgrade(schema, itemID)) continue;
isPassUpgraded = upgrade(schema, itemID, true) || isPassUpgraded;
isUpgraded = isPassUpgraded || isUpgraded;
}
}
return isUpgraded;
}
void Inventory::update(Resources& resources, ItemManager& itemManager, Entity& character, Autofeed& autofeed)
{
for (auto& [i, actor] : actors)
actor.update();
static constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
bool isSelectedItemPressed{};
int pressedItemQuantity{-1};
@@ -38,69 +109,59 @@ namespace game::state::play::menu
auto& schema = character.data.itemSchema;
auto& strings = character.data.strings;
auto quantity_get = [&](int itemID) -> int&
{
auto& quantity = values[itemID];
quantity = glm::clamp(0, quantity, schema.quantityMax);
return quantity;
};
auto inventory_quantity_get = [&](int itemID) -> int& { return quantity_get(values, schema, itemID); };
auto is_possible_to_upgrade_get = [&](const resource::xml::Item::Entry& item)
{
return item.upgradeID.has_value() && item.upgradeCount.has_value() &&
schema.idToStringMap.contains(*item.upgradeID);
};
auto is_possible_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item)
{ return game::state::play::menu::is_possible_to_upgrade_get(schema, item); };
auto is_able_to_upgrade_get = [&](const resource::xml::Item::Entry& item, int quantity)
{ return is_possible_to_upgrade_get(item) && quantity >= *item.upgradeCount; };
auto is_able_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
{ return is_possible_to_upgrade_get(item) && quantity >= item.upgradeCount; };
auto item_header_draw = [&](const resource::xml::Item::Entry& item, int quantity)
auto item_header_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), quantity);
ImGui::PopFont();
};
auto item_summary_draw = [&](const resource::xml::Item::Entry& item)
auto item_summary_draw = [&](const resource::xml::Schema::ItemEntry& item)
{
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
auto durability = item.durability.value_or(schema.durability);
auto durability = item.isDurability ? item.durability : schema.durability;
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::TextWrapped("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
if (item.flavorID.has_value())
if (item.isFlavor)
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(),
schema.flavors[*item.flavorID].name.c_str());
if (item.calories.has_value())
ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), *item.calories);
schema.flavors[item.flavorID].name.c_str());
if (item.isCalories) ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), item.calories);
ImGui::TextWrapped(strings.get(Strings::InventoryDurabilityFormat).c_str(), durability);
if (item.capacityBonus.has_value())
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), *item.capacityBonus);
if (item.digestionBonus.has_value())
if (item.isCapacityBonus)
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
if (item.isDigestionBonus)
{
if (*item.digestionBonus > 0)
if (item.digestionBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
*item.digestionBonus * 60.0f);
else if (*item.digestionBonus < 0)
item.digestionBonus * 60.0f);
else if (item.digestionBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
*item.digestionBonus * 60.0f);
item.digestionBonus * 60.0f);
}
if (item.eatSpeedBonus.has_value())
if (item.isEatSpeedBonus)
{
if (*item.eatSpeedBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), *item.eatSpeedBonus);
else if (*item.eatSpeedBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), *item.eatSpeedBonus);
if (item.eatSpeedBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), item.eatSpeedBonus);
else if (item.eatSpeedBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), item.eatSpeedBonus);
}
if (is_possible_to_upgrade_get(item))
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), *item.upgradeCount,
schema.idToStringMap.at(*item.upgradeID).c_str());
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), item.upgradeCount,
schema.idToStringMap.at(item.upgradeID).c_str());
ImGui::PopStyleColor();
};
auto info_section_separator_height_get = [&]()
{ return ImGui::GetStyle().ItemSpacing.y + 1.0f; };
auto info_section_separator_height_get = [&]() { return ImGui::GetStyle().ItemSpacing.y + 1.0f; };
auto info_section_separator_draw = [&]()
{
@@ -113,7 +174,7 @@ namespace game::state::play::menu
ImGui::Dummy(ImVec2(0.0f, separatorHeight));
};
auto item_tooltip_draw = [&](const resource::xml::Item::Entry& item, int quantity)
auto item_tooltip_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
{
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
item_header_draw(item, quantity);
@@ -139,7 +200,7 @@ namespace game::state::play::menu
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
};
auto item_header_height_get = [&](const resource::xml::Item::Entry& item, int quantity, float width)
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
{
float height{};
@@ -150,11 +211,11 @@ namespace game::state::play::menu
return height;
};
auto item_summary_height_get = [&](const resource::xml::Item::Entry& item, float width)
auto item_summary_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
{
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
auto durability = item.durability.value_or(schema.durability);
auto durability = item.isDurability ? item.durability : schema.durability;
auto itemSpacing = ImGui::GetStyle().ItemSpacing.y;
float height{};
int lineCount{};
@@ -167,46 +228,48 @@ namespace game::state::play::menu
};
add_line_height(std::format("-- {} ({}) --", category.name, rarity.name));
if (item.flavorID.has_value())
if (item.isFlavor)
add_line_height(std::vformat(strings.get(Strings::InventoryFlavorFormat),
std::make_format_args(schema.flavors[*item.flavorID].name)));
if (item.calories.has_value())
add_line_height(std::vformat(strings.get(Strings::InventoryCaloriesFormat), std::make_format_args(*item.calories)));
add_line_height(std::vformat(strings.get(Strings::InventoryDurabilityFormat), std::make_format_args(durability)));
if (item.capacityBonus.has_value())
std::make_format_args(schema.flavors[item.flavorID].name)));
if (item.isCalories)
add_line_height(
std::vformat(strings.get(Strings::InventoryCapacityBonusFormat), std::make_format_args(*item.capacityBonus)));
if (item.digestionBonus.has_value())
std::vformat(strings.get(Strings::InventoryCaloriesFormat), std::make_format_args(item.calories)));
add_line_height(std::vformat(strings.get(Strings::InventoryDurabilityFormat), std::make_format_args(durability)));
if (item.isCapacityBonus)
add_line_height(std::vformat(strings.get(Strings::InventoryCapacityBonusFormat),
std::make_format_args(item.capacityBonus)));
if (item.isDigestionBonus)
{
if (*item.digestionBonus > 0)
if (item.digestionBonus > 0)
{
auto digestionRateBonus = *item.digestionBonus * 60.0f;
add_line_height(
std::vformat(strings.get(Strings::InventoryDigestionRateBonusFormat), std::make_format_args(digestionRateBonus)));
auto digestionRateBonus = item.digestionBonus * 60.0f;
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRateBonusFormat),
std::make_format_args(digestionRateBonus)));
}
else if (*item.digestionBonus < 0)
else if (item.digestionBonus < 0)
{
auto digestionRatePenalty = *item.digestionBonus * 60.0f;
auto digestionRatePenalty = item.digestionBonus * 60.0f;
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
std::make_format_args(digestionRatePenalty)));
}
}
if (item.eatSpeedBonus.has_value())
if (item.isEatSpeedBonus)
{
if (*item.eatSpeedBonus > 0)
add_line_height(
std::vformat(strings.get(Strings::InventoryEatSpeedBonusFormat), std::make_format_args(*item.eatSpeedBonus)));
else if (*item.eatSpeedBonus < 0)
if (item.eatSpeedBonus > 0)
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedBonusFormat),
std::make_format_args(item.eatSpeedBonus)));
else if (item.eatSpeedBonus < 0)
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedPenaltyFormat),
std::make_format_args(*item.eatSpeedBonus)));
std::make_format_args(item.eatSpeedBonus)));
}
if (is_possible_to_upgrade_get(item))
add_line_height(std::vformat(strings.get(Strings::InventoryUpgradePreviewFormat),
std::make_format_args(*item.upgradeCount, schema.idToStringMap.at(*item.upgradeID))));
add_line_height(
std::vformat(strings.get(Strings::InventoryUpgradePreviewFormat),
std::make_format_args(item.upgradeCount, schema.idToStringMap.at(item.upgradeID))));
return height;
};
auto item_details_height_get = [&](const resource::xml::Item::Entry& item, int quantity, float width)
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
{
auto separatorHeight = info_section_separator_height_get();
auto detailBodyHeight =
@@ -243,8 +306,8 @@ namespace game::state::play::menu
else
{
auto& item = schema.items[selectedItemID];
measuredBodyHeight =
isSelectedItemKnown ? item_details_height_get(item, selectedQuantity, infoWidth) : item_unknown_height_get(infoWidth);
measuredBodyHeight = isSelectedItemKnown ? item_details_height_get(item, selectedQuantity, infoWidth)
: item_unknown_height_get(infoWidth);
}
auto buttonChildHeight = 0.0f;
@@ -266,17 +329,17 @@ namespace game::state::play::menu
{
auto& item = schema.items[itemID];
auto& category = schema.categories[item.categoryID];
auto& quantity = quantity_get(itemID);
auto& quantity = inventory_quantity_get(itemID);
if (quantity <= 0) return;
if (category.isEdible)
{
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
character.data.itemSchema.sounds.dispose.play();
character.data.itemSchema.root()->soundDispose.play();
else
{
character.data.itemSchema.sounds.summon.play();
character.data.itemSchema.root()->soundSummon.play();
itemManager.queuedItemIDs.emplace_back(itemID);
quantity--;
if (quantity <= 0) selectedItemID = -1;
@@ -284,78 +347,17 @@ namespace game::state::play::menu
}
else if (item.isToggleSpritesheet)
{
character.spritesheet_set(character.spritesheetType == Character::NORMAL ? Character::ALTERNATE
: Character::NORMAL);
character.data.alternateSpritesheet.sound.play();
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
quantity--;
}
};
auto item_upgrade = [&](int itemID, bool isAll)
auto item_actor_get = [&](int itemID) -> Entity&
{
auto& item = schema.items[itemID];
auto& quantity = quantity_get(itemID);
if (!actors.contains(itemID)) actors[itemID] = Entity(schema.anm2s[itemID], {}, Entity::SET);
if (!is_possible_to_upgrade_get(item))
{
schema.sounds.upgradeFail.play();
return;
}
if (!is_able_to_upgrade_get(item, quantity))
{
schema.sounds.upgradeFail.play();
return;
}
if (isAll)
{
while (quantity >= *item.upgradeCount)
{
values.at(*item.upgradeID)++;
quantity -= *item.upgradeCount;
}
}
else
{
values.at(*item.upgradeID)++;
quantity -= *item.upgradeCount;
}
schema.sounds.upgrade.play();
if (quantity < *item.upgradeCount && selectedItemID == itemID) selectedItemID = *item.upgradeID;
};
auto item_canvas_get = [&](int itemID, ImVec2 size)
{
if (!actors.contains(itemID))
{
actors[itemID] = Actor(schema.anm2s[itemID], {}, Actor::SET);
rects[itemID] = actors[itemID].rect();
}
auto& rect = rects[itemID];
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(itemID)) canvases.emplace(itemID, Canvas(canvasSize, Canvas::FLIP));
auto& canvas = canvases[itemID];
canvas.zoom = math::to_percent(previewScale);
canvas.pan = vec2(rect.x, rect.y);
canvas.bind();
canvas.size_set(canvasSize);
canvas.clear();
actors[itemID].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
canvas.unbind();
return std::tuple<Canvas&, glm::vec4&>(canvas, rect);
return actors[itemID];
};
if (!itemManager.returnItemIDs.empty())
@@ -372,11 +374,18 @@ namespace game::state::play::menu
auto available = ImGui::GetContentRegionAvail();
auto isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
auto isInfoVisible = isItemSelected || inventoryCount == 0;
auto infoChildHeight = info_child_height_get(available, isItemSelected, isItemSelected ? quantity_get(selectedItemID) : 0);
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto toggleButtonHeight = ImGui::GetFrameHeight();
ImGui::PopFont();
auto toggleChildHeight = toggleButtonHeight + ImGui::GetStyle().WindowPadding.y * 2.0f;
auto toggleSpacing = ImGui::GetStyle().ItemSpacing.y;
auto infoChildHeight =
info_child_height_get(available, isItemSelected, isItemSelected ? inventory_quantity_get(selectedItemID) : 0);
auto inventoryChildHeight =
isInfoVisible ? std::max(0.0f, available.y - infoChildHeight - ImGui::GetStyle().ItemSpacing.y) : available.y;
auto inventoryChildHeight = std::max(0.0f, available.y - infoChildHeight - toggleChildHeight -
(isInfoVisible ? toggleSpacing * 2.0f : toggleSpacing));
auto childSize = ImVec2(available.x, inventoryChildHeight);
auto toggleChildSize = ImVec2(available.x, toggleChildHeight);
auto infoChildSize = ImVec2(available.x, infoChildHeight);
if (ImGui::BeginChild("##Inventory List Child", childSize))
@@ -390,9 +399,9 @@ namespace game::state::play::menu
for (int i = 0; i < (int)schema.items.size(); i++)
{
auto& item = schema.items[i];
auto& quantity = quantity_get(i);
auto& quantity = inventory_quantity_get(i);
auto& rarity = schema.rarities[item.rarityID];
auto hasItemColor = item.color.has_value();
auto isItemColor = item.isColor;
if (rarity.isHidden && quantity <= 0) continue;
@@ -400,9 +409,9 @@ namespace game::state::play::menu
ImGui::SetCursorPos(cursorPos);
auto cursorScreenPos = ImGui::GetCursorScreenPos();
auto [canvas, rect] = item_canvas_get(i, size);
auto& actor = item_actor_get(i);
auto isSelected = selectedItemID == i;
if (hasItemColor) imgui::style::color_set(*item.color);
if (isItemColor) imgui::style::color_set(item.color);
if (isSelected)
{
@@ -412,9 +421,8 @@ namespace game::state::play::menu
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selectedColor);
}
auto isPressed =
WIDGET_FX(ImGui::ImageButton("##Image Button", canvas.texture, size, ImVec2(), ImVec2(1, 1), ImVec4(),
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1)));
auto isPressed = itemButtons[i].button(resources, actor, "##Image Button", size,
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1));
if (isSelected) ImGui::PopStyleColor(3);
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
if (isPressed)
@@ -441,7 +449,7 @@ namespace game::state::play::menu
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
text.c_str());
ImGui::PopFont();
if (hasItemColor) style::color_set(resources, character);
if (isItemColor) style::color_set(resources, character);
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
cursorPos.x += increment;
@@ -460,20 +468,39 @@ namespace game::state::play::menu
}
ImGui::EndChild();
if (ImGui::BeginChild("##Autofeed Toggle Child", toggleChildSize, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto isAutofeedAvailable = autofeed.available_get(character, *this, itemManager);
auto label = autofeed.enabled_get() ? strings.get(Strings::InventoryDisableAutofeedButton)
: strings.get(Strings::InventoryEnableAutofeedButton);
ImGui::BeginDisabled(!isAutofeedAvailable);
if (WIDGET_FX(ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0}))) autofeed.toggle();
ImGui::EndDisabled();
ImGui::PopFont();
ImGui::SetItemTooltip("%s", strings
.get(autofeed.enabled_get() ? Strings::InventoryDisableAutofeedTooltip
: Strings::InventoryEnableAutofeedTooltip)
.c_str());
}
ImGui::EndChild();
isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
isInfoVisible = isItemSelected || inventoryCount == 0;
auto selectedQuantity =
isItemSelected ? (isSelectedItemPressed && pressedItemQuantity >= 0 ? pressedItemQuantity : quantity_get(selectedItemID))
isItemSelected ? (isSelectedItemPressed && pressedItemQuantity >= 0 ? pressedItemQuantity
: inventory_quantity_get(selectedItemID))
: 0;
infoChildHeight = info_child_height_get(available, isItemSelected, selectedQuantity);
infoChildSize = ImVec2(available.x, infoChildHeight);
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].color.has_value();
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].isColor;
if (isInfoVisible && ImGui::BeginChild("##Info Child", infoChildSize, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
{
if (selectedItemHasColor) imgui::style::color_set(*schema.items[selectedItemID].color);
if (selectedItemHasColor) imgui::style::color_set(schema.items[selectedItemID].color);
info_section_separator_draw();
auto isButtonChildVisible = selectedQuantity > 0;
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
@@ -497,10 +524,10 @@ namespace game::state::play::menu
auto descriptionHeight = wrapped_text_height_get(selectedItem.description, contentWidth);
auto sharedDetailHeight = std::max(statsHeight, descriptionHeight);
auto headerHeight = item_header_height_get(selectedItem, selectedQuantity, contentWidth);
auto desiredInfoContentHeight =
headerHeight + separatorHeight + sharedDetailHeight * 2.0f + separatorHeight;
auto desiredInfoContentHeight = headerHeight + separatorHeight + sharedDetailHeight * 2.0f + separatorHeight;
auto availableInfoContentHeight =
std::max(0.0f, ImGui::GetContentRegionAvail().y - (isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
std::max(0.0f, ImGui::GetContentRegionAvail().y -
(isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
item_header_draw(selectedItem, selectedQuantity);
@@ -508,7 +535,8 @@ namespace game::state::play::menu
auto detailChildHeight = sharedDetailHeight;
if (desiredInfoContentHeight > availableInfoContentHeight)
detailChildHeight = std::max(0.0f, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) * 0.5f);
detailChildHeight =
std::max(0.0f, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) * 0.5f);
auto detailChildSize = ImVec2(ImGui::GetContentRegionAvail().x, detailChildHeight);
if (ImGui::BeginChild("##Info Stats Child", detailChildSize))
@@ -540,8 +568,8 @@ namespace game::state::play::menu
auto upgrade_item_name_get = [&]() -> std::string
{
if (!selectedItem.upgradeID.has_value()) return {};
return schema.items.at(*selectedItem.upgradeID).name;
if (!selectedItem.isUpgradeID) return {};
return schema.items.at(selectedItem.upgradeID).name;
};
auto upgrade_tooltip_get = [&](bool isAll)
@@ -549,7 +577,7 @@ namespace game::state::play::menu
if (!is_possible_to_upgrade_get(selectedItem)) return strings.get(Strings::InventoryUpgradeNoPath);
auto upgradeItemName = upgrade_item_name_get();
auto upgradeCount = *selectedItem.upgradeCount;
auto upgradeCount = selectedItem.upgradeCount;
if (!canUpgradeSelectedItem)
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
@@ -574,7 +602,7 @@ namespace game::state::play::menu
ImGui::BeginDisabled(!canUpgradeSelectedItem);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
item_upgrade(selectedItemID, false);
upgrade(schema, selectedItemID, false);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
@@ -583,7 +611,7 @@ namespace game::state::play::menu
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(), rowTwoButtonSize)))
item_upgrade(selectedItemID, true);
upgrade(schema, selectedItemID, true);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
+14 -6
View File
@@ -1,13 +1,20 @@
#pragma once
#include "../../../entity/character.hpp"
#include "../../../entity.hpp"
#include "../../../resource/schema.hpp"
#include "../../../resources.hpp"
#include "../../../util/imgui/entity_button.hpp"
#include "../item_manager.hpp"
#include <imgui.h>
namespace game::state::play
{
class Autofeed;
}
namespace game::state::play::menu
{
class Inventory
@@ -16,13 +23,14 @@ namespace game::state::play::menu
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{};
std::unordered_map<int, Entity> actors{};
std::unordered_map<int, util::imgui::widget::EntityButton> itemButtons{};
int selectedItemID{-1};
void tick();
void update(Resources&, ItemManager&, entity::Character&);
bool can_upgrade(const resource::xml::Schema&, int itemID);
bool upgrade(resource::xml::Schema&, int itemID, bool isAll);
bool upgrade_all_possible(resource::xml::Schema&);
void update(Resources&, ItemManager&, Entity&, Autofeed&);
int count();
};
}
+11 -32
View File
@@ -8,13 +8,13 @@ using namespace glm;
namespace game::state::play::menu
{
void ItemEffectManager::tick()
void ItemEffectManager::update()
{
for (auto& [i, actor] : actors)
actor.tick();
actor.update();
}
void ItemEffectManager::spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode)
void ItemEffectManager::spawn(int itemID, const resource::xml::Schema& itemSchema, const ImVec4& bounds, Mode mode)
{
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MIN = -250.0f;
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MAX = 250.0f;
@@ -24,13 +24,12 @@ namespace game::state::play::menu
static constexpr auto ITEM_ROTATION_VELOCITY_MAX = 45.0f;
if (!actors.contains(itemID))
{
actors[itemID] = entity::Actor(itemSchema.anm2s[itemID], {}, entity::Actor::SET);
rects[itemID] = actors[itemID].rect();
}
actors[itemID] = Entity(itemSchema.anm2s[itemID], {}, Entity::SET);
auto size = ImVec2(bounds.z, bounds.w);
auto rect = rects[itemID];
auto& canvas = canvases[itemID];
if (!std::isfinite(canvas.rect.x)) canvas.rect = actors[itemID].rect();
auto rect = canvas.rect;
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))
@@ -66,7 +65,7 @@ namespace game::state::play::menu
entries.emplace_back(std::move(entry));
}
void ItemEffectManager::render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds,
void ItemEffectManager::render(Resources& resources, const resource::xml::Schema& itemSchema, const ImVec4& bounds,
float deltaTime)
{
static constexpr auto ITEM_FALL_GRAVITY = 2400.0f;
@@ -87,35 +86,15 @@ namespace game::state::play::menu
continue;
}
auto rect = rects[item.id];
auto rectSize = vec2(rect.z, rect.w);
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
? 0.0f
: std::min(size.x / rectSize.x, size.y / rectSize.y);
previewScale = std::min(1.0f, previewScale);
auto previewSize = rectSize * previewScale;
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
if (!canvases.contains(item.id)) canvases.emplace(item.id, Canvas(canvasSize, Canvas::FLIP));
auto& canvas = canvases[item.id];
canvas.zoom = math::to_percent(previewScale);
canvas.pan = vec2(rect.x, rect.y);
canvas.bind();
canvas.size_set(canvasSize);
canvas.clear();
actors[item.id].overrides.emplace_back(-1, resource::xml::Anm2::ROOT, entity::Actor::Override::SET,
resource::xml::Anm2::FrameOptional{.rotation = item.rotation});
actors[item.id].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
actors[item.id].overrides.pop_back();
canvas.unbind();
auto& textureCanvas = canvas.render_rotated(resources, actors[item.id], size, item.rotation, 1.0f);
auto previewSize = canvas.imageSize;
auto min = ImVec2(position.x + item.position.x, position.y + item.position.y);
auto max = ImVec2(item.position.x + previewSize.x, item.position.y + previewSize.y);
max.x += position.x;
max.y += position.y;
drawList->AddImage(canvas.texture, min, max);
drawList->AddImage(textureCanvas.texture, min, max);
item.rotation += item.rotationVelocity * deltaTime;
item.position.x += item.velocity.x * deltaTime;
+7 -7
View File
@@ -1,8 +1,9 @@
#pragma once
#include "../../../render/canvas.hpp"
#include "../../../entity/actor.hpp"
#include "../../../entity.hpp"
#include "../../../resources.hpp"
#include "../../../util/imgui/entity_canvas.hpp"
#include <imgui.h>
#include <unordered_map>
@@ -30,12 +31,11 @@ namespace game::state::play::menu
};
std::vector<Entry> entries{};
std::unordered_map<int, entity::Actor> actors{};
std::unordered_map<int, glm::vec4> rects{};
std::unordered_map<int, Canvas> canvases{};
std::unordered_map<int, Entity> actors{};
std::unordered_map<int, util::imgui::widget::EntityCanvas> canvases{};
void tick();
void spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode = FALL_DOWN);
void render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds, float deltaTime);
void update();
void spawn(int itemID, const resource::xml::Schema& itemSchema, const ImVec4& bounds, Mode mode = FALL_DOWN);
void render(Resources& resources, const resource::xml::Schema& itemSchema, const ImVec4& bounds, float deltaTime);
};
}
-31
View File
@@ -1,31 +0,0 @@
#include "toasts.hpp"
namespace game::state::play::menu
{
namespace
{
static constexpr auto TOAST_MESSAGE_SPEED = 1.0f;
}
void Toasts::spawn(const std::string& message, const ImVec2& position, int time)
{
toasts.emplace_back(message, position, time, time);
}
void Toasts::update(ImDrawList* drawList)
{
if (!drawList) return;
for (int i = 0; i < (int)toasts.size(); i++)
{
auto& toast = toasts[i];
toast.position.y -= TOAST_MESSAGE_SPEED;
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
textColor.w = (float)toast.time / toast.timeMax;
drawList->AddText(toast.position, ImGui::GetColorU32(textColor), toast.message.c_str());
toast.time--;
if (toast.time <= 0) toasts.erase(toasts.begin() + i--);
}
}
}
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include <imgui.h>
#include <string>
#include <vector>
namespace game::state::play::menu
{
class Toasts
{
public:
struct Toast
{
std::string message{};
ImVec2 position{};
int time{};
int timeMax{};
};
std::vector<Toast> toasts{};
void spawn(const std::string& message, const ImVec2& position, int time);
void update(ImDrawList*);
};
}
+51
View File
@@ -0,0 +1,51 @@
#include "particle_manager.hpp"
#include "../../log.hpp"
#include <format>
using namespace game::resource::xml;
namespace game::state::play
{
void ParticleManager::spawn(Character& character, const std::string& label, glm::vec2 position)
{
for (auto* particle : character.particleSchema.get_all(Schema::Element::PARTICLE))
{
if (!particle || particle->typeString != label) continue;
if (!particle->anm2Entry.is_valid()) return;
auto& anm2 = particle->anm2Entry.anm2;
auto animationIndex = anm2.animationMap.contains(particle->animation) ? anm2.animationMap.at(particle->animation)
: anm2.defaultAnimationID;
Entity entity{anm2, position, Entity::PLAY, 0.0f, animationIndex};
entity.entityType = PARTICLE;
entity.isRemoveOnAnimationEnd = true;
particles.push_back(std::move(entity));
return;
}
logger.warning(std::format("Particle not found: {}", label));
}
void ParticleManager::update()
{
for (int i = 0; i < (int)particles.size();)
{
particles[i].update();
if (particles[i].isToBeDeleted)
{
particles.erase(particles.begin() + i);
continue;
}
i++;
}
}
void ParticleManager::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas)
{
for (auto& particle : particles)
particle.render(textureShader, rectShader, canvas);
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "../../entity.hpp"
#include <vector>
namespace game::state::play
{
class ParticleManager
{
public:
std::vector<Entity> particles{};
void spawn(resource::xml::Character&, const std::string&, glm::vec2);
void update();
void render(resource::Shader&, resource::Shader&, Canvas&);
};
}
+4 -4
View File
@@ -1,14 +1,14 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../entity.hpp"
#include "../../resources.hpp"
#include "../../util/imgui/style.hpp"
namespace game::state::play::style
{
inline void color_set(Resources& resources, const entity::Character& character)
inline void color_set(Resources& resources, const Entity& character)
{
game::util::imgui::style::color_set(resources.settings.isUseCharacterColor ? character.data.color
: resources.settings.color);
auto* settings = resources.settings.root();
game::util::imgui::style::color_set(settings->isUseCharacterColor ? character.data.root()->color : settings->color);
}
}
+279 -58
View File
@@ -4,9 +4,12 @@
#include <imgui_internal.h>
#include <algorithm>
#include <cmath>
#include <string_view>
#include <vector>
#include "../../util/imgui.hpp"
#include "../../util/imgui/dialogue_text.hpp"
#include "../../util/imgui/widget.hpp"
#include "../../util/math.hpp"
@@ -14,58 +17,203 @@ using namespace game::util;
namespace game::state::play
{
const char* utf8_advance_chars(const char* text, const char* end, int count)
namespace
{
const char* it = text;
while (it < end && count > 0)
bool dialogue_effect_is(const resource::xml::Schema::Element& element)
{
unsigned int codepoint = 0;
int step = ImTextCharFromUtf8(&codepoint, it, end);
if (step <= 0) break;
it += step;
--count;
return element.type == resource::xml::Schema::Element::EFFECT;
}
constexpr auto OSCILLATION_AMPLITUDE_DEFAULT = 2.0f;
constexpr auto OSCILLATION_FREQUENCY_DEFAULT = 1.0f;
constexpr auto SCREEN_SHAKE_MAGNITUDE_DEFAULT = 0.03f;
constexpr auto SCREEN_SHAKE_TIME_TICKS_DEFAULT = 15;
constexpr auto SHAKE_AMPLITUDE_DEFAULT = 1.0f;
constexpr auto SHAKE_FREQUENCY_DEFAULT = 30.0f;
constexpr auto TEXT_SCALE_BIG = 1.25f;
constexpr auto TEXT_SCALE_SMALL = 0.75f;
std::string_view dialogue_char_get(std::string_view text, int index)
{
auto* start = text.data();
auto* end = start + text.size();
auto* charStart = imgui::utf8_advance_chars(start, end, index);
auto* charEnd = imgui::utf8_advance_chars(charStart, end, 1);
return charEnd > charStart ? std::string_view(charStart, charEnd - charStart) : std::string_view{};
}
struct DialogueDelayCharacter
{
int delay{};
bool isDisableTalk{};
};
DialogueDelayCharacter dialogue_delay_character_get(const resource::xml::Schema& dialogue, std::string_view text,
int index, int fallback)
{
auto value = dialogue_char_get(text, index);
if (value.empty()) return {.delay = fallback};
auto* root = dialogue.root();
if (!root) return {.delay = fallback};
auto* textPlayback = dialogue.child_get(*root, resource::xml::Schema::Element::TEXT_PLAYBACK);
auto* delayCharacters =
textPlayback ? dialogue.child_get(*textPlayback, resource::xml::Schema::Element::DELAY_CHARACTERS) : nullptr;
if (!delayCharacters) return {.delay = fallback};
for (auto* delayCharacter : dialogue.children_get(*delayCharacters, resource::xml::Schema::Element::DELAY_CHARACTER))
{
if (delayCharacter->character == value)
return {.delay = fallback + std::max(0, delayCharacter->delayTicks),
.isDisableTalk = delayCharacter->isDisableTalk};
}
return {.delay = fallback};
}
void dialogue_effect_one_shot_apply(const resource::xml::Schema::Element& effect, Canvas& canvas)
{
if (effect.typeString == "Screenshake")
{
canvas.shake(SCREEN_SHAKE_MAGNITUDE_DEFAULT, SCREEN_SHAKE_TIME_TICKS_DEFAULT);
}
else if (effect.typeString == "Sound" && effect.soundEntry.is_valid())
{
effect.soundEntry.sound.play();
}
}
bool dialogue_entry_terminal_get(const resource::xml::Schema& dialogue,
const resource::xml::Schema::Element& entry)
{
if (!entry.next.empty()) return false;
for (auto child : entry.children)
{
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
if (dialogue.elements[child].type == resource::xml::Schema::Element::CHOICE) return false;
}
return true;
}
return it;
}
void Text::set(resource::xml::Dialogue::Entry* dialogueEntry, entity::Character& character, bool isInterruptible)
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible)
{
if (!dialogueEntry) return;
this->entry = dialogueEntry;
isFinished = false;
isTerminal = dialogue_entry_terminal_get(character.data.dialogue, *dialogueEntry);
index = 0;
updateDelay = 0;
baseDelay = 0;
currentDelay = 0;
isStartEffectsApplied = false;
isBlipQueued = false;
time = 0.0f;
isEnabled = true;
if (!dialogueEntry->animation.empty())
character.queue_play({.animation = dialogueEntry->animation, .isInterruptible = isInterruptible});
character.play({.animation = dialogueEntry->animation,
.appendID = character.animation_append_id_get(),
.interrupt = isInterruptible ? Entity::Interrupt::IF_ALLOWED
: Entity::Interrupt::NEVER});
auto* dialogueRoot = character.data.dialogue.root();
baseDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
currentDelay = baseDelay;
for (auto child : dialogueEntry->children)
{
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
auto& effect = character.data.dialogue.elements[child];
if (!dialogue_effect_is(effect) || effect.start != 0) continue;
if (effect.typeString == "Delay")
{
baseDelay = std::max(1, effect.delayTicks);
currentDelay = baseDelay;
}
else if (effect.typeString == "Animation" && !effect.animation.empty())
{
character.play({.animation = effect.animation,
.appendID = character.animation_append_id_get(),
.mode = Entity::PLAY_FORCE});
}
}
if (dialogueEntry->text.empty())
isEnabled = false;
else
character.isTalking = true;
}
void Text::tick(entity::Character& character)
{
if (!entry || isFinished) return;
index++;
auto blipPeriod = character.data.textBlipPeriodBase;
if (blipPeriod > 0 && index % blipPeriod == 0) character.data.sounds.blip.play();
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)
void Text::update(Entity& character, Canvas& canvas)
{
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto HEIGHT_MULTIPLIER = 6.0f;
if (!entry) return;
if (!isFinished)
{
if (!isStartEffectsApplied)
{
isStartEffectsApplied = true;
for (auto child : entry->children)
{
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
auto& effect = character.data.dialogue.elements[child];
if (!dialogue_effect_is(effect) || effect.start != 0) continue;
dialogue_effect_one_shot_apply(effect, canvas);
}
}
auto* dialogueRoot = character.data.dialogue.root();
auto rootDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
if (baseDelay <= 0) baseDelay = rootDelay;
if (currentDelay <= 0) currentDelay = baseDelay;
if (++updateDelay >= currentDelay)
{
updateDelay = 0;
index++;
currentDelay = baseDelay;
for (auto child : entry->children)
{
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
auto& effect = character.data.dialogue.elements[child];
if (!dialogue_effect_is(effect) || effect.start != index) continue;
if (effect.typeString == "Delay")
{
baseDelay = std::max(1, effect.delayTicks);
currentDelay = baseDelay;
}
else if (effect.typeString == "Animation" && !effect.animation.empty())
{
character.play({.animation = effect.animation,
.appendID = character.animation_append_id_get(),
.mode = Entity::PLAY_FORCE});
}
else
{
dialogue_effect_one_shot_apply(effect, canvas);
}
}
auto delayCharacter = dialogue_delay_character_get(character.data.dialogue, entry->text, index - 1, currentDelay);
currentDelay = delayCharacter.delay;
character.isTalking = !delayCharacter.isDisableTalk;
auto blipDelay = dialogueRoot ? dialogueRoot->blipDelayTicks : 0;
auto isBlipStep = blipDelay > 0 && index % blipDelay == 0;
if (delayCharacter.isDisableTalk)
{
if (isBlipStep) isBlipQueued = true;
}
else if (isBlipStep || isBlipQueued)
{
if (dialogueRoot) dialogueRoot->soundBlip.play();
isBlipQueued = false;
}
if (index >= imgui::utf8_char_count(entry->text))
{
isFinished = true;
character.isTalking = false;
isBlipQueued = false;
}
}
}
auto& dialogue = character.data.dialogue;
auto& menuSchema = character.data.menuSchema;
@@ -88,18 +236,16 @@ namespace game::state::play
auto isHovered = ImGui::IsWindowHovered();
auto isMouse = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isSpace = ImGui::IsKeyReleased(ImGuiKey_Space);
auto isAdvance = (isHovered && (isMouse || isSpace));
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();
if (ImGui::BeginTabItem(character.data.root()->name.c_str())) ImGui::EndTabItem();
ImGui::EndTabBar();
}
auto available = ImGui::GetContentRegionAvail();
auto font = ImGui::GetFont();
auto fontSize = resource::Font::NORMAL;
@@ -108,7 +254,7 @@ namespace game::state::play
auto text = [&]()
{
auto text = entry ? std::string_view(entry->text) : "null";
auto length = std::clamp(index, 0, ImTextCountCharsFromUtf8(text.data(), text.data() + text.size()));
auto length = std::clamp(index, 0, imgui::utf8_char_count(text));
if (length <= 0)
{
@@ -116,13 +262,42 @@ namespace game::state::play
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();
std::vector<imgui::DialogueEffectSpan> effects{};
effects.reserve(entry->children.size());
for (auto child : entry->children)
{
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
auto& effect = dialogue.elements[child];
if (!dialogue_effect_is(effect)) continue;
if (effect.typeString == "Color")
effects.push_back({.type = imgui::DialogueEffectSpan::COLOR,
.start = effect.start,
.end = effect.end,
.color = effect.color});
else if (effect.typeString == "Big")
effects.push_back({.type = imgui::DialogueEffectSpan::TEXT_SCALE,
.start = effect.start,
.end = effect.end,
.textScale = TEXT_SCALE_BIG});
else if (effect.typeString == "Small")
effects.push_back({.type = imgui::DialogueEffectSpan::TEXT_SCALE,
.start = effect.start,
.end = effect.end,
.textScale = TEXT_SCALE_SMALL});
else if (effect.typeString == "Oscillation")
effects.push_back({.type = imgui::DialogueEffectSpan::OSCILLATE,
.start = effect.start,
.end = effect.end,
.frequency = OSCILLATION_FREQUENCY_DEFAULT,
.amplitude = OSCILLATION_AMPLITUDE_DEFAULT});
else if (effect.typeString == "Shake")
effects.push_back({.type = imgui::DialogueEffectSpan::SHAKE,
.start = effect.start,
.end = effect.end,
.frequency = SHAKE_FREQUENCY_DEFAULT,
.amplitude = SHAKE_AMPLITUDE_DEFAULT});
}
imgui::dialogue_text_draw(text, length, effects, glm::vec3{1.0f}, (float)ImGui::GetTime());
};
text();
@@ -131,46 +306,65 @@ namespace game::state::play
{
if (isFinished)
{
if (!entry->choices.empty())
auto choices = dialogue.children_get(*entry, resource::xml::Schema::Element::CHOICE);
if (!choices.empty())
{
ImGui::SetCursorPos(ImVec2(ImGui::GetStyle().WindowPadding.x, available.y));
auto buttonSize = imgui::row_widget_size_get((int)entry->choices.size());
auto contentMin = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMin().x,
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMin().y);
auto contentMax = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x,
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y);
auto buttonY = contentMax.y - ImGui::GetFrameHeight();
auto separatorY = buttonY - style.ItemSpacing.y;
ImGui::GetWindowDrawList()->AddLine(ImVec2(contentMin.x, separatorY), ImVec2(contentMax.x, separatorY),
ImGui::GetColorU32(ImGuiCol_Separator));
ImGui::SetCursorScreenPos(ImVec2(contentMin.x, buttonY));
auto buttonSize = imgui::row_widget_size_get((int)choices.size());
for (auto& branch : entry->choices)
for (auto index = 0; index < (int)choices.size(); ++index)
{
if (WIDGET_FX(ImGui::Button(branch.text.c_str(), buttonSize)))
set(dialogue.get(branch.nextID), character);
auto* branch = choices[index];
auto label = branch->text.empty() ? "<choice>" : branch->text.c_str();
ImGui::PushID(index);
if (WIDGET_FX(ImGui::Button(label, buttonSize)))
set(dialogue.dialogue_entry_get(*branch), character);
ImGui::SetItemTooltip("%s", branch.text.c_str());
ImGui::SameLine();
ImGui::SetItemTooltip("%s", label);
ImGui::PopID();
if (index + 1 < (int)choices.size()) ImGui::SameLine();
}
if (isHovered && isSpace)
{
set(dialogue.get(entry->choices.front().nextID), character);
menuSchema.sounds.select.play();
auto* next = dialogue.dialogue_entry_get(*choices.front());
if (next) menuSchema.root()->soundSelect.play();
set(next, character);
}
}
else
{
if (entry->nextID != -1)
if (!entry->next.empty())
{
ImGui::SetCursorPos(ImVec2(available.x - ImGui::GetTextLineHeightWithSpacing(), available.y));
auto indicatorSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
auto contentMax = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x,
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y);
ImGui::SetCursorScreenPos(ImVec2(contentMax.x - indicatorSize.x, contentMax.y - indicatorSize.y));
auto cursorPos = ImGui::GetCursorScreenPos();
auto center = ImVec2(cursorPos.x + (indicatorSize.x * 0.5f), cursorPos.y + (indicatorSize.y * 0.5f));
auto offset = std::sin((float)ImGui::GetTime() * 2.0f) * (indicatorSize.x * 0.12f);
auto center =
ImVec2(cursorPos.x + (indicatorSize.x * 0.5f) + offset, 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);
ImGui::InvisibleButton("##next-indicator", indicatorSize);
if (isAdvance)
{
menuSchema.sounds.select.play();
set(dialogue.get(entry->nextID), character);
auto* next = dialogue.dialogue_entry_get(*entry);
if (next) menuSchema.root()->soundSelect.play();
set(next, character);
}
}
else if (isAdvance)
@@ -184,7 +378,32 @@ namespace game::state::play
{
if (isAdvance)
{
index = ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size());
auto target = imgui::utf8_char_count(entry->text);
for (auto skippedIndex = index + 1; skippedIndex <= target; ++skippedIndex)
{
for (auto child : entry->children)
{
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
auto& effect = character.data.dialogue.elements[child];
if (!dialogue_effect_is(effect) || effect.start != skippedIndex) continue;
if (effect.typeString == "Delay")
{
baseDelay = std::max(1, effect.delayTicks);
currentDelay = baseDelay;
}
else if (effect.typeString == "Animation" && !effect.animation.empty())
{
character.play({.animation = effect.animation,
.appendID = character.animation_append_id_get(),
.mode = Entity::PLAY_FORCE});
}
else
{
dialogue_effect_one_shot_apply(effect, canvas);
}
}
}
index = target;
isFinished = true;
character.isTalking = false;
}
@@ -196,7 +415,7 @@ namespace game::state::play
};
ImGui::End();
if (isEnabled && isFinished && entry && entry->is_last())
if (isEnabled && isFinished && entry && isTerminal)
{
if (time += ImGui::GetIO().DeltaTime; time > LIFETIME)
{
@@ -206,5 +425,7 @@ namespace game::state::play
}
}
bool Text::is_interruptible() const { return !entry || (entry && entry->is_last()); }
bool Text::is_interruptible() const { return !entry || isTerminal; }
bool Text::is_finished() const { return isFinished; }
bool Text::is_terminal() const { return isTerminal; }
}
+14 -6
View File
@@ -2,7 +2,8 @@
#include <imgui.h>
#include "../../entity/character.hpp"
#include "../../entity.hpp"
#include "../../render/canvas.hpp"
#include "../../resources.hpp"
@@ -11,19 +12,26 @@ namespace game::state::play
class Text
{
int index{};
int updateDelay{};
int baseDelay{};
int currentDelay{};
bool isFinished{};
bool isTerminal{};
bool isStartEffectsApplied{};
bool isBlipQueued{};
public:
static constexpr auto LIFETIME = 10.0f;
resource::xml::Dialogue::Entry* entry{};
resource::xml::Schema::Element* entry{};
bool isEnabled{true};
float time{};
void set(resource::xml::Dialogue::Entry*, entity::Character&, bool isInterruptible = true);
void tick(entity::Character&);
void update(entity::Character&);
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true);
void update(Entity&, Canvas&);
bool is_interruptible() const;
bool is_finished() const;
bool is_terminal() const;
};
}
}
+15 -22
View File
@@ -2,10 +2,11 @@
#include <imgui.h>
#include <ranges>
#include <string>
namespace game::state::play
{
void Toasts::tick()
void Toasts::update()
{
for (int i = 0; i < (int)items.size(); i++)
{
@@ -19,10 +20,7 @@ namespace game::state::play
continue;
}
}
}
void Toasts::update()
{
if (items.empty()) return;
auto viewport = ImGui::GetMainViewport();
@@ -35,13 +33,13 @@ namespace game::state::play
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));
auto height = ImGui::GetTextLineHeightWithSpacing() + style.WindowPadding.y * 2.0f;
auto posY = viewport->WorkPos.y + viewport->WorkSize.y - style.WindowPadding.y -
(height * (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 textSize = ImGui::CalcTextSize(item.message.c_str());
auto pos = ImVec2(viewport->WorkPos.x + style.WindowPadding.x, posY);
auto size = ImVec2(textSize.x + (style.WindowPadding.x * 2), height);
auto alpha = (float)item.lifetime / Item::LIFETIME;
@@ -49,18 +47,13 @@ namespace game::state::play
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);
auto* drawList = ImGui::GetForegroundDrawList();
drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(windowBgColor),
style.WindowRounding);
drawList->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(borderColor),
style.WindowRounding);
drawList->AddText(ImVec2(pos.x + style.WindowPadding.x, pos.y + style.WindowPadding.y),
ImGui::GetColorU32(textColor), item.message.c_str());
}
}
+2 -3
View File
@@ -10,7 +10,7 @@ namespace game::state::play
public:
struct Item
{
static constexpr auto LIFETIME = 30;
static constexpr auto LIFETIME = 180;
std::string message{};
int lifetime{};
@@ -19,7 +19,6 @@ namespace game::state::play
std::vector<Item> items{};
void update();
void tick();
void push(const std::string&);
};
};
};
+85 -49
View File
@@ -3,7 +3,7 @@
#include "../../util/imgui.hpp"
#include "../../util/imgui/widget.hpp"
#include <algorithm>
#include <cfloat>
using namespace game::util;
using namespace game::util::imgui;
@@ -11,59 +11,112 @@ using namespace game::resource::xml;
namespace game::state::play
{
void Tools::update(entity::Character& character, entity::Cursor& cursor, World& world, World::Focus focus,
Canvas& canvas)
void Tools::update(Entity& character, Entity& cursor, World& world, World::Focus focus, Canvas& canvas,
bool isDisabled, float fadeAlpha)
{
static constexpr auto WIDTH_MULTIPLIER = 0.05f;
static constexpr auto ALPHA_MIN = 0.0f;
static constexpr auto ALPHA_MAX = 1.0f;
static constexpr auto OUTER_STYLE_VAR_COUNT = 3;
static constexpr auto BAR_STYLE_VAR_COUNT = 2;
static constexpr auto PADDING_SIZE_MULTIPLIER = 2.0f;
auto style = ImGui::GetStyle();
auto& io = ImGui::GetIO();
auto& schema = character.data.menuSchema;
auto& strings = character.data.strings;
if (isDisabled) isOpen = false;
slide.update(isOpen, io.DeltaTime);
if (fadeAlpha <= ALPHA_MIN) return;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * fadeAlpha);
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
auto size =
ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
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 barSize =
ImVec2(imgui::side_bar_width_get(), windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
auto barPos = ImVec2(pos.x + size.x, style.WindowPadding.y);
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove;
if (isDisabled || fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
if (slide.is_visible())
{
ImGui::SetNextWindowSize(size);
ImGui::SetNextWindowPos(pos);
if (ImGui::Begin("##Tools", nullptr,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove))
if (ImGui::Begin("##Tools", nullptr, flags))
{
ImGui::BeginDisabled(isDisabled);
auto buttonSize = imgui::to_imvec2(vec2(ImGui::GetContentRegionAvail().x));
auto* iconsFont = schema.iconsFont.is_valid() ? schema.iconsFont.get() : nullptr;
auto tool_button = [&](const char* id, const std::string& label, const std::string& iconGlyph,
const std::string& tooltip, bool isActive = false)
{
auto useIcon = iconsFont && !iconGlyph.empty();
auto result = false;
ImGui::PushID(id);
if (isActive) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));
result = WIDGET_FX(ImGui::Button(useIcon ? "##ToolIcon" : label.c_str(), buttonSize));
if (isActive) ImGui::PopStyleColor();
if (useIcon)
{
auto min = ImGui::GetItemRectMin();
auto size = ImGui::GetItemRectSize();
auto fontSize = size.y;
auto textSize = iconsFont->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, iconGlyph.c_str());
auto pos = ImVec2(min.x + (size.x - textSize.x) * 0.5f, min.y + (size.y - textSize.y) * 0.5f);
ImGui::GetWindowDrawList()->AddText(iconsFont, fontSize, pos, ImGui::GetColorU32(ImGuiCol_Text),
iconGlyph.c_str());
}
if (!tooltip.empty()) ImGui::SetItemTooltip("%s", tooltip.c_str());
ImGui::PopID();
return result;
};
auto cursor_mode_button = [&](const std::string& name, int interactTypeID)
{
auto isMode = cursor.interactTypeID == interactTypeID;
ImGui::PushStyleColor(ImGuiCol_Button,
ImGui::GetStyleColorVec4(isMode ? ImGuiCol_ButtonHovered : ImGuiCol_Button));
if (WIDGET_FX(ImGui::Button(name.c_str(), buttonSize))) cursor.interactTypeID = interactTypeID;
ImGui::PopStyleColor();
std::string iconGlyph{};
for (auto* area : character.data.interact_area_types_get())
{
if (!area || area->typeString != name || area->iconGlyph.empty()) continue;
iconGlyph = area->iconGlyph;
break;
}
if (tool_button(name.c_str(), name, iconGlyph, iconsFont && !iconGlyph.empty() ? name : "",
cursor.interactTypeID == interactTypeID))
cursor.interactTypeID = interactTypeID;
};
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ToolsHomeButton).c_str(), buttonSize)))
auto* root = schema.root();
auto* home = root ? schema.child_get(*root, Schema::Element::HOME) : nullptr;
auto homeIconGlyph = home ? home->iconGlyph : std::string{};
if (tool_button("Home", strings.get(Strings::ToolsHomeButton), homeIconGlyph,
strings.get(Strings::ToolsHomeTooltip)))
world.character_focus(character, canvas, focus);
ImGui::SetItemTooltip("%s", strings.get(Strings::ToolsHomeTooltip).c_str());
for (int i = 0; i < (int)character.data.interactTypeNames.size(); i++)
cursor_mode_button(character.data.interactTypeNames[i], i);
auto interactTypeNames = character.data.interact_type_names_get();
for (int i = 0; i < (int)interactTypeNames.size(); i++)
cursor_mode_button(interactTypeNames[i], i);
ImGui::EndDisabled();
}
ImGui::End();
}
@@ -73,61 +126,44 @@ namespace game::state::play
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))
if (ImGui::Begin("##Tools Open Bar", nullptr, flags))
{
auto buttonSize = ImGui::GetContentRegionAvail();
auto cursorPos = ImGui::GetCursorScreenPos();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::BeginDisabled(isDisabled);
auto result = WIDGET_FX(ImGui::Button("##ToolsToggle", buttonSize));
if (t <= 0.0f || t >= 1.0f)
if (!isDisabled && (t <= 0.0f || t >= 1.0f))
{
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::ToolsCloseTooltip
: Strings::ToolsOpenTooltip)
.c_str());
ImGui::SetItemTooltip("%s",
strings.get(isOpen ? Strings::ToolsCloseTooltip : Strings::ToolsOpenTooltip).c_str());
if (result)
{
isOpen = !isOpen;
if (isOpen)
schema.sounds.open.play();
schema.root()->soundOpen.play();
else
schema.sounds.close.play();
schema.root()->soundClose.play();
}
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
{
isOpen = true;
schema.sounds.open.play();
schema.root()->soundOpen.play();
}
}
ImGui::EndDisabled();
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);
auto color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
if (isDisabled) color.w *= style.DisabledAlpha;
auto direction = isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, buttonSize, direction, ImGui::GetColorU32(color));
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(BAR_STYLE_VAR_COUNT);
ImGui::PopStyleVar(2);
ImGui::PopStyleVar(OUTER_STYLE_VAR_COUNT);
}
}
+1 -1
View File
@@ -13,6 +13,6 @@ namespace game::state::play
bool isOpen{};
util::imgui::WindowSlide slide{0.125f, 0.0f};
void update(entity::Character&, entity::Cursor&, World&, World::Focus, Canvas&);
void update(Entity&, Entity&, World&, World::Focus, Canvas&, bool isDisabled, float fadeAlpha);
};
}
+42 -6
View File
@@ -10,15 +10,49 @@ using namespace game::util;
namespace game::state::play
{
void World::set(entity::Character& character, Canvas& canvas, Focus focus)
constexpr auto ZOOM_LADDER_EPSILON = 0.01f;
constexpr auto ZOOM_WHEEL_ZERO = 0.0f;
float zoom_ladder_level_get(int index)
{
auto clampedIndex = std::clamp(index, 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
auto exponent = (float)clampedIndex / (float)(World::ZOOM_LADDER_LEVEL_COUNT - 1);
return World::ZOOM_MIN * (float)std::pow(World::ZOOM_MAX / World::ZOOM_MIN, exponent);
}
int zoom_ladder_next_index_get(float zoom, int direction)
{
if (direction > 0)
{
for (int index = 0; index < World::ZOOM_LADDER_LEVEL_COUNT; ++index)
if (zoom < zoom_ladder_level_get(index) - ZOOM_LADDER_EPSILON) return index;
return World::ZOOM_LADDER_LEVEL_COUNT - 1;
}
for (int index = World::ZOOM_LADDER_LEVEL_COUNT - 1; index >= 0; --index)
if (zoom > zoom_ladder_level_get(index) + ZOOM_LADDER_EPSILON) return index;
return 0;
}
float zoom_ladder_zoom_get(float zoom, float wheel)
{
auto direction = wheel > ZOOM_WHEEL_ZERO ? 1 : -1;
auto stepCount = std::max(1, (int)std::ceil(std::abs(wheel)));
auto index = zoom_ladder_next_index_get(zoom, direction);
index = std::clamp(index + ((stepCount - 1) * direction), 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
return zoom_ladder_level_get(index);
}
void World::set(Entity& character, Canvas& canvas, Focus focus)
{
character.stage = character.stage_get();
character_focus(character, canvas, focus);
}
void World::update(entity::Character& character, entity::Cursor& cursor, Canvas& canvas, Focus focus)
void World::update(Entity& character, Entity& cursor, Canvas& canvas, Focus focus)
{
auto& cursorSchema = character.data.cursorSchema;
auto* cursorRoot = cursorSchema.root();
auto& pan = canvas.pan;
auto& zoom = canvas.zoom;
auto& io = ImGui::GetIO();
@@ -31,7 +65,8 @@ namespace game::state::play
{
if ((isMouseMiddleDown) || (isMouseLeftDown && isCtrlDown))
{
if (auto animation = cursorSchema.animations.pan.get()) cursor.queue_play({*animation});
if (cursorRoot)
if (auto animation = cursorRoot->animationPan.get()) cursor.play({*animation});
pan -= imgui::to_vec2(io.MouseDelta) * panMultiplier;
}
@@ -44,9 +79,10 @@ namespace game::state::play
auto zoomFactorBefore = math::to_unit(zoomBefore);
auto cursorWorld = pan + (cursorPos / zoomFactorBefore);
if (auto animation = cursorSchema.animations.zoom.get()) cursor.queue_play({*animation});
if (cursorRoot)
if (auto animation = cursorRoot->animationZoom.get()) cursor.play({*animation});
zoom = glm::clamp(ZOOM_MIN, zoom + (io.MouseWheel * ZOOM_STEP), ZOOM_MAX);
zoom = zoom_ladder_zoom_get(zoom, io.MouseWheel);
auto zoomFactorAfter = math::to_unit(zoom);
pan = cursorWorld - (cursorPos / zoomFactorAfter);
@@ -58,7 +94,7 @@ namespace game::state::play
if (ImGui::IsKeyPressed(ImGuiKey_Home)) character_focus(character, canvas, focus);
}
void World::character_focus(entity::Character& character, Canvas& canvas, Focus focus)
void World::character_focus(Entity& character, Canvas& canvas, Focus focus)
{
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
+5 -5
View File
@@ -1,7 +1,7 @@
#pragma once
#include "../../render/canvas.hpp"
#include "../../entity/character.hpp"
#include "../../entity.hpp"
#include "character_manager.hpp"
#include "item_manager.hpp"
@@ -13,7 +13,7 @@ namespace game::state::play
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_LADDER_LEVEL_COUNT = 10;
static constexpr auto ZOOM_MAX = 400.0f;
static constexpr auto SIZE = glm::vec2{1920, 1080};
static constexpr auto BOUNDS =
@@ -27,9 +27,9 @@ namespace game::state::play
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);
void update(Entity& character, Entity& cursor, Canvas& canvas, Focus = CENTER);
void character_focus(Entity& character, Canvas& canvas, Focus = CENTER);
void set(Entity& character, Canvas& canvas, Focus = CENTER);
glm::vec2 screen_to_world(glm::vec2 screenPosition, const Canvas& canvas) const;
};
}
+5 -3
View File
@@ -6,13 +6,15 @@ 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);
if (characters.update(resources, characterIndex))
{
preview.previousCharacterIndex = -1;
preview.canvas.clear();
}
}
void Select::render(Resources&, Canvas& canvas)
-1
View File
@@ -17,7 +17,6 @@ namespace game::state
int characterIndex{-1};
void tick();
void update(Resources&);
void render(Resources&, Canvas&);
};
+24 -8
View File
@@ -7,8 +7,9 @@ using namespace game::util;
namespace game::state::select
{
void Characters::update(Resources& resources, int& characterIndex)
bool Characters::update(Resources& resources, int& characterIndex)
{
auto isRefreshed = false;
auto& style = ImGui::GetStyle();
auto viewport = ImGui::GetMainViewport();
@@ -27,6 +28,19 @@ namespace game::state::select
{
if (WIDGET_FX(ImGui::BeginTabItem("Characters")))
{
if (ImGui::BeginPopupContextWindow("characters-context", ImGuiPopupFlags_MouseButtonRight))
{
if (ImGui::MenuItem("Refresh"))
{
resources.characters_refresh();
characterIndex = resources.characterPreviews.empty()
? -1
: std::clamp(characterIndex, 0, (int)resources.characterPreviews.size() - 1);
isRefreshed = true;
}
ImGui::EndPopup();
}
auto cursorPos = ImGui::GetCursorPos();
auto cursorStartX = ImGui::GetCursorPosX();
@@ -34,25 +48,26 @@ namespace game::state::select
for (int i = 0; i < (int)resources.characterPreviews.size(); i++)
{
auto& character = resources.characterPreviews[i];
auto* character = resources.characterPreviews[i].root();
if (!character) continue;
ImGui::PushID(i);
ImGui::SetCursorPos(cursorPos);
imgui::style::color_set(character.color);
imgui::style::color_set(character->color);
auto isSelected = i == characterIndex;
if (isSelected) ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered));
if (character.portrait.is_valid())
if (character->portrait.is_valid())
{
if (WIDGET_FX(ImGui::ImageButton(character.name.c_str(), character.portrait.id, buttonSize)))
if (WIDGET_FX(ImGui::ImageButton(character->name.c_str(), character->portrait.texture.id, buttonSize)))
characterIndex = i;
}
else if (WIDGET_FX(ImGui::Button(character.name.c_str(), buttonSize)))
else if (WIDGET_FX(ImGui::Button(character->name.c_str(), buttonSize)))
characterIndex = i;
if (isSelected) ImGui::PopStyleColor();
ImGui::SetItemTooltip("%s", character.name.c_str());
ImGui::SetItemTooltip("%s", character->name.c_str());
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
cursorPos.x += increment;
@@ -64,7 +79,7 @@ namespace game::state::select
}
ImGui::PopID();
imgui::style::color_set(resources.settings.color);
imgui::style::color_set(resources.settings.root()->color);
}
ImGui::EndTabItem();
}
@@ -78,5 +93,6 @@ namespace game::state::select
}
ImGui::End();
}
return isRefreshed;
}
}
+1 -1
View File
@@ -10,6 +10,6 @@ namespace game::state::select
public:
SettingsMenu settingsMenu;
void update(Resources&, int& characterIndex);
bool update(Resources&, int& characterIndex);
};
}
+22 -15
View File
@@ -4,6 +4,7 @@
#include "../../util/imgui.hpp"
#include "../../util/imgui/style.hpp"
#include "../../util/imgui/widget.hpp"
#include "../../util/measurement.hpp"
#include "../../util/vector.hpp"
using namespace game::util;
@@ -19,7 +20,8 @@ namespace game::state::select
auto& style = ImGui::GetStyle();
auto viewport = ImGui::GetMainViewport();
auto& character = resources.characterPreviews[characterIndex];
auto* character = resources.characterPreviews[characterIndex].root();
if (!character) return;
auto size = ImVec2(viewport->Size.x / 2.0f - (style.WindowPadding.x * 2.0f),
(viewport->Size.y / 2.0f) - (style.WindowPadding.y * 2.0f));
@@ -27,15 +29,19 @@ namespace game::state::select
ImGui::SetNextWindowSize(size);
ImGui::SetNextWindowPos(pos);
imgui::style::color_set(character.color);
imgui::style::color_set(character->color);
if (ImGui::Begin("##Info", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoTitleBar))
{
auto& save = character.save;
auto& system = resources.settings.measurementSystem;
auto& weight = save.is_valid() ? save.weight : character.weight;
auto& save = resources.characterSaves.at(characterIndex);
auto* settings = resources.settings.root();
auto system = settings->measurementSystem == "Imperial" ? IMPERIAL : METRIC;
auto* saveRoot = save.root();
auto isSaveValid = saveRoot && saveRoot->type == resource::xml::Schema::Element::SAVE;
auto* savedCharacter = isSaveValid ? save.child_get(*saveRoot, resource::xml::Schema::Element::CHARACTER) : nullptr;
auto weight = savedCharacter ? (double)savedCharacter->weightKilograms : (double)character->weight;
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
@@ -44,7 +50,7 @@ namespace game::state::select
if (ImGui::BeginChild("##Info Child", childSize))
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
ImGui::TextUnformatted(character.name.c_str());
ImGui::TextUnformatted(character->name.c_str());
ImGui::PopFont();
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
@@ -52,13 +58,13 @@ namespace game::state::select
{
if (ImGui::BeginTabItem("Overview"))
{
if (!character.description.empty())
if (!character->description.empty())
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s", character.description.c_str());
ImGui::TextWrapped("%s", character->description.c_str());
ImGui::PopFont();
ImGui::PopStyleColor();
@@ -70,7 +76,8 @@ namespace game::state::select
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
system == IMPERIAL ? "lbs" : "kg");
ImGui::Text("Stages: %i", character.stages);
auto stages = resources.characterPreviews[characterIndex].get_all(resource::xml::Schema::Element::STAGE);
ImGui::Text("Stages: %i", (int)stages.size() + 1);
ImGui::PopFont();
ImGui::EndTabItem();
@@ -81,8 +88,8 @@ namespace game::state::select
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
if (!character.credits.empty())
ImGui::TextWrapped("%s", character.credits.c_str());
if (!character->credits.empty())
ImGui::TextWrapped("%s", character->credits.c_str());
else
ImGui::TextUnformatted("No credits listed.");
ImGui::PopFont();
@@ -96,9 +103,9 @@ namespace game::state::select
}
ImGui::EndChild();
auto widgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
auto widgetSize = row_widget_size_get(isSaveValid ? 2 : 1);
if (save.is_valid())
if (isSaveValid)
{
if (WIDGET_FX(ImGui::Button("Continue", widgetSize))) isContinue = true;
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
@@ -129,7 +136,7 @@ namespace game::state::select
if (ImGui::BeginPopupModal("New Game Warning", &isNewGameWarning,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
{
auto popupWidgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
auto popupWidgetSize = row_widget_size_get(isSaveValid ? 2 : 1);
ImGui::TextWrapped("This will delete saved progress! Are you sure?");
if (WIDGET_FX(ImGui::Button("Yes", popupWidgetSize))) isNewGame = true;
ImGui::SameLine();
@@ -138,6 +145,6 @@ namespace game::state::select
}
}
ImGui::End();
imgui::style::color_set(resources.settings.color);
imgui::style::color_set(resources.settings.root()->color);
}
}
+30 -42
View File
@@ -1,39 +1,35 @@
#include "preview.hpp"
#include <algorithm>
#include <cmath>
#include "../../util/imgui.hpp"
#include "../../util/imgui/style.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 (characterIndex != -1 && isInGame) actor.update();
if (!vector::in_bounds(resources.characterPreviews, characterIndex)) return;
auto& style = ImGui::GetStyle();
auto viewport = ImGui::GetMainViewport();
auto& character = resources.characterPreviews[characterIndex];
auto* character = resources.characterPreviews[characterIndex].root();
if (!character) return;
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);
imgui::style::color_set(character.color);
imgui::style::color_set(character->color);
if (ImGui::Begin("##Preview", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
@@ -43,21 +39,29 @@ namespace game::state::select
{
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 textureSize = vec2(character->render.texture.size);
auto renderSize = ImVec2(textureSize.x * scale, textureSize.y * scale);
if (character->render.is_valid())
{
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);
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (renderSize.x * 0.5f),
ImGui::GetCursorPosY() + (availableSize.y * 0.5f) - (renderSize.y * 0.5f)));
auto renderSize = ImVec2(textureSize.x * scale, textureSize.y * scale);
ImGui::Image(character.render.id, renderSize);
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (renderSize.x * 0.5f),
ImGui::GetCursorPosY() + (availableSize.y * 0.5f) - (renderSize.y * 0.5f)));
ImGui::Image(character->render.texture.id, renderSize);
}
else
{
ImGui::TextUnformatted(character->name.c_str());
if (!character->description.empty()) ImGui::TextWrapped("%s", character->description.c_str());
}
ImGui::EndTabItem();
}
@@ -68,34 +72,18 @@ namespace game::state::select
if (previousCharacterIndex != characterIndex)
{
actor = Actor(resources.characterPreviews[characterIndex].anm2);
rect = actor.rect();
actor = Entity(resources.character_get(characterIndex).root()->anm2Entry.anm2);
canvas.clear();
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& rendered = canvas.render(resources, actor, available);
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)));
ImGui::SetCursorPos(ImVec2(cursorPos.x + (availableSize.x * 0.5f) - (canvas.imageSize.x * 0.5f),
cursorPos.y + (availableSize.y * 0.5f) - (canvas.imageSize.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::Image(rendered.texture, canvas.imageSize);
ImGui::EndTabItem();
}
@@ -106,6 +94,6 @@ namespace game::state::select
}
}
ImGui::End();
imgui::style::color_set(resources.settings.color);
imgui::style::color_set(resources.settings.root()->color);
}
}
+4 -5
View File
@@ -1,7 +1,8 @@
#pragma once
#include "../../entity/actor.hpp"
#include "../../entity.hpp"
#include "../../resources.hpp"
#include "../../util/imgui/entity_canvas.hpp"
namespace game::state::select
{
@@ -9,13 +10,11 @@ namespace game::state::select
{
public:
int previousCharacterIndex{-1};
entity::Actor actor{};
glm::vec4 rect{};
Entity actor{};
bool isInGame{};
Canvas canvas{glm::vec2(), Canvas::FLIP};
util::imgui::widget::EntityCanvas canvas{};
void update(Resources& resources, int characterIndex);
void tick(int characterIndex);
};
}
+34 -8
View File
@@ -5,6 +5,7 @@
#include "../util/imgui/style.hpp"
#include "../util/imgui/widget.hpp"
#include "../util/display.hpp"
#include "../util/math.hpp"
#include "../util/measurement.hpp"
@@ -16,10 +17,16 @@ namespace game::state
{
void SettingsMenu::update(Resources& resources, Mode mode, const Strings* strings)
{
auto& settings = resources.settings;
auto& measurementSystem = settings.measurementSystem;
auto& volume = settings.volume;
auto& color = settings.color;
auto* settings = resources.settings.root();
if (!settings)
{
resources.settings.settings_default();
settings = resources.settings.root();
}
auto measurementSystem = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
auto& volume = settings->volume;
auto& isAudioMuted = settings->isAudioMuted;
auto& color = settings->color;
auto string_get = [&](Strings::Type type, const char* fallback) -> const char*
{
return strings ? strings->get(type).c_str() : fallback;
@@ -35,23 +42,41 @@ namespace game::state
WIDGET_FX(ImGui::RadioButton(string_get(Strings::SettingsImperial, "Imperial"),
(int*)&measurementSystem, measurement::IMPERIAL));
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsImperialTooltip, "Use pounds (lbs)."));
settings->measurementSystem = measurementSystem == measurement::IMPERIAL ? "Imperial" : "Metric";
ImGui::SeparatorText(string_get(Strings::SettingsWindow, "Window"));
const char* windowModes[] = {
string_get(Strings::SettingsWindowed, "Windowed"),
string_get(Strings::SettingsFullscreen, "Fullscreen"),
};
auto windowMode = display::fullscreen_get() ? 1 : 0;
if (WIDGET_FX(ImGui::Combo(string_get(Strings::SettingsWindowMode, "Mode"), &windowMode, windowModes, 2)))
display::fullscreen_set(windowMode == 1);
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsWindowModeTooltip,
"Switch between windowed and fullscreen display.\n(Shortcut: F)"));
ImGui::SeparatorText(string_get(Strings::SettingsSound, "Sound"));
ImGui::BeginDisabled(isAudioMuted);
if (WIDGET_FX(
ImGui::SliderInt(string_get(Strings::SettingsVolume, "Volume"), &volume, 0, 100, "%d%%")))
resources.volume_set(math::to_unit((float)volume));
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsVolumeTooltip, "Adjust master volume."));
ImGui::EndDisabled();
ImGui::SameLine();
if (WIDGET_FX(ImGui::Checkbox(string_get(Strings::SettingsMute, "Mute"), &isAudioMuted)))
resources.audio_mute_set(isAudioMuted);
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsMuteTooltip, "Mute all audio.\n(Shortcut: M)"));
ImGui::SeparatorText(string_get(Strings::SettingsAppearance, "Appearance"));
if (WIDGET_FX(ImGui::Checkbox(string_get(Strings::SettingsUseCharacterColor,
"Use Character Color"),
&settings.isUseCharacterColor)))
&settings->isUseCharacterColor)))
isJustColorSet = true;
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsUseCharacterColorTooltip,
"When playing, the UI will use the character's preset UI color."));
ImGui::SameLine();
ImGui::BeginDisabled(settings.isUseCharacterColor);
ImGui::BeginDisabled(settings->isUseCharacterColor);
if (WIDGET_FX(
ImGui::ColorEdit3(string_get(Strings::SettingsColor, "Color"), value_ptr(color),
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
@@ -66,8 +91,9 @@ namespace game::state
if (WIDGET_FX(ImGui::Button(string_get(Strings::SettingsResetButton, "Reset to Default"),
ImVec2(-FLT_MIN, 0))))
{
settings = resource::xml::Settings();
style::color_set(settings.color);
resources.settings.settings_default();
settings = resources.settings.root();
style::color_set(settings->color);
}
if (mode == PLAY)
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../resource/xml/strings.hpp"
#include "../resource/strings.hpp"
#include "../resources.hpp"
namespace game::state