lfs, current build
This commit is contained in:
+100
-142
@@ -19,23 +19,24 @@ using namespace glm;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
namespace
|
||||
void entity_debug_flags_apply(Entity& entity, bool isShowLayers, bool isShowNulls)
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
|
||||
int durability, int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
entity.isShowLayers = isShowLayers;
|
||||
entity.isShowNulls = isShowNulls;
|
||||
}
|
||||
|
||||
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
}
|
||||
int saved_item_id_get(const Schema& itemSchema, const Schema::Element& item)
|
||||
{
|
||||
if (item.id >= 0 && item.id < (int)itemSchema.items.size() && item.id < (int)itemSchema.anm2s.size())
|
||||
return item.id;
|
||||
return -1;
|
||||
}
|
||||
|
||||
World::Focus Play::focus_get()
|
||||
{
|
||||
if (!isWindows) return World::CENTER;
|
||||
if (text.is_scene_active()) return World::CENTER;
|
||||
|
||||
auto isToolsOpen = tools.isOpen && !menu.is_fullscreen_visible_get();
|
||||
auto isToolsOpen = tools.isOpen;
|
||||
return menu.isOpen && isToolsOpen ? World::MENU_TOOLS
|
||||
: menu.isOpen ? World::MENU
|
||||
: isToolsOpen ? World::TOOLS
|
||||
@@ -48,34 +49,16 @@ namespace game::state
|
||||
return root && root->type == Schema::Element::CURSOR;
|
||||
}
|
||||
|
||||
void Play::start_sequence_begin()
|
||||
void Play::debug_flags_apply()
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto* start = dialogue.get(Schema::Element::START);
|
||||
if (!start) return;
|
||||
|
||||
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;
|
||||
isStart = true;
|
||||
isStartBegin = false;
|
||||
isStartEnd = false;
|
||||
}
|
||||
|
||||
void Play::end_sequence_begin()
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
if (!dialogue.get(Schema::Element::END)) return;
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
isEnd = true;
|
||||
isEndBegin = false;
|
||||
isEndEnd = false;
|
||||
entity_debug_flags_apply(character, isShowLayers, isShowNulls);
|
||||
entity_debug_flags_apply(cursor, isShowLayers, isShowNulls);
|
||||
entity_debug_flags_apply(autofeedCursor, isShowLayers, isShowNulls);
|
||||
areaManager.debug_flags_set(isShowLayers, isShowNulls);
|
||||
for (auto& item : itemManager.items)
|
||||
entity_debug_flags_apply(item, isShowLayers, isShowNulls);
|
||||
for (auto& particle : particleManager.particles)
|
||||
entity_debug_flags_apply(particle, isShowLayers, isShowNulls);
|
||||
}
|
||||
|
||||
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
|
||||
@@ -84,8 +67,8 @@ namespace game::state
|
||||
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* saveDiscovered = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::DISCOVERED) : 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;
|
||||
@@ -96,12 +79,11 @@ namespace game::state
|
||||
resources.last_character_save(selectedCharacterIndex);
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
world = play::World{};
|
||||
|
||||
character = Entity(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
|
||||
character = Entity(data, vec2{});
|
||||
character.digestionRate =
|
||||
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);
|
||||
|
||||
@@ -116,11 +98,16 @@ namespace game::state
|
||||
}
|
||||
|
||||
character.totalCaloriesConsumed = saveCharacter ? saveCharacter->totalCaloriesConsumed : 0;
|
||||
character.totalFoodItemsEaten = saveCharacter ? saveCharacter->totalFoodItemsEaten : 0;
|
||||
character.totalItemsSpawned = saveCharacter ? saveCharacter->totalItemsSpawned : 0;
|
||||
character.totalItemsConsumed = saveCharacter ? saveCharacter->totalItemsConsumed : 0;
|
||||
character.totalPlaytimeSeconds = saveCharacter ? saveCharacter->totalPlaytimeSeconds : 0;
|
||||
autofeed = play::Autofeed{};
|
||||
characterManager = CharacterManager{};
|
||||
particleManager = ParticleManager{};
|
||||
areaManager = AreaManager{};
|
||||
areaManager.set(character);
|
||||
world.bounds_set(areaManager.bounds_get(character, World::BOUNDS));
|
||||
character.position = {};
|
||||
|
||||
cursor = Entity{};
|
||||
cursor.entityType = CURSOR;
|
||||
@@ -143,24 +130,24 @@ namespace game::state
|
||||
}
|
||||
|
||||
menu.inventory = play::menu::Inventory{};
|
||||
for (auto* item : saveInventory ? data.save.children_get(*saveInventory, Schema::Element::ITEM)
|
||||
: std::vector<Schema::Element*>{})
|
||||
{
|
||||
if (item->quantity == 0) continue;
|
||||
menu.inventory.values[item->id] = item->quantity;
|
||||
}
|
||||
|
||||
itemManager = ItemManager{};
|
||||
itemManager.spawnDelayTicks = saveRoot ? saveRoot->spawnDelayTicks : 0;
|
||||
itemManager.discoveredItemIDs.resize(itemSchema.items.size());
|
||||
for (auto* item :
|
||||
saveDiscovered ? data.save.children_get(*saveDiscovered, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
|
||||
{
|
||||
auto itemID = saved_item_id_get(itemSchema, *item);
|
||||
if (itemID != -1) itemManager.discoveredItemIDs[itemID] = true;
|
||||
}
|
||||
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.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);
|
||||
auto itemID = saved_item_id_get(itemSchema, *item);
|
||||
if (itemID == -1) continue;
|
||||
auto& saveItem = itemSchema.anm2s[itemID];
|
||||
itemManager.items.emplace_back(saveItem, item->position, itemID, item->velocity, item->rotation);
|
||||
itemManager.discoveredItemIDs[itemID] = true;
|
||||
}
|
||||
|
||||
imgui::style::widget_set(menuSchema.rounding);
|
||||
@@ -169,6 +156,7 @@ namespace game::state
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
isPostgamePending = false;
|
||||
|
||||
#if DEBUG
|
||||
menu.isCheats = true;
|
||||
@@ -180,32 +168,29 @@ namespace game::state
|
||||
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
|
||||
if (isPostgame) menu.isCheats = true;
|
||||
|
||||
if (game == NEW_GAME) isWindows = false;
|
||||
|
||||
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
|
||||
|
||||
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);
|
||||
world.zoom_index_set(worldCanvas);
|
||||
}
|
||||
|
||||
if (game == NEW_GAME && dialogue.get(Schema::Element::START)) start_sequence_begin();
|
||||
if (game == NEW_GAME)
|
||||
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::START), character, false);
|
||||
|
||||
if (isPostgame)
|
||||
{
|
||||
isEnd = true;
|
||||
isEndBegin = true;
|
||||
isEndEnd = true;
|
||||
isPostgamePending = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
isEnd = false;
|
||||
isEndBegin = false;
|
||||
isEndEnd = false;
|
||||
isPostgamePending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +216,7 @@ namespace game::state
|
||||
auto focus = focus_get();
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto isCursorEnabled = cursor_enabled_get();
|
||||
character.totalPlaytimeSeconds += ImGui::GetIO().DeltaTime;
|
||||
if (isCursorEnabled)
|
||||
{
|
||||
cursor.isVisible = true;
|
||||
@@ -276,87 +262,55 @@ namespace game::state
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindows)
|
||||
auto isSceneActive = text.is_scene_active();
|
||||
|
||||
if (!isSceneActive)
|
||||
{
|
||||
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);
|
||||
tools.update(character, cursor, world, focus, worldCanvas, false, UI_ALPHA_MAX);
|
||||
menu.update(resources, itemManager, character, text, autofeed);
|
||||
toasts.update();
|
||||
}
|
||||
|
||||
auto isEndSequenceActive = isEndBegin && !isEndEnd;
|
||||
itemManager.isDisabled = isEndSequenceActive;
|
||||
characterManager.isDisabled = isEndSequenceActive;
|
||||
itemManager.isDisabled = isSceneActive;
|
||||
characterManager.isDisabled = isSceneActive;
|
||||
|
||||
if (text.isEnabled) text.update(character, worldCanvas);
|
||||
|
||||
if (isStart)
|
||||
if (character.isJustStageFinal && !isPostgame && !isPostgamePending)
|
||||
{
|
||||
if (!isStartBegin)
|
||||
auto* end = dialogue.dialogue_hook_entry_get(Schema::Element::END);
|
||||
if (end)
|
||||
{
|
||||
if (auto animation = character.animation_get())
|
||||
{
|
||||
if (animation->isLoop || character.state == Entity::STOPPED)
|
||||
{
|
||||
auto* start = dialogue.get(Schema::Element::START);
|
||||
text.set(start ? dialogue.dialogue_entry_get(*start) : nullptr, character);
|
||||
isStartBegin = true;
|
||||
}
|
||||
}
|
||||
text.set(end, character, false);
|
||||
tools.isOpen = false;
|
||||
menu.isOpen = false;
|
||||
character.calories = 0;
|
||||
character.digestionProgress = 0;
|
||||
itemManager.heldItemIndex = -1;
|
||||
isPostgamePending = true;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
else if (!isStartEnd)
|
||||
else
|
||||
{
|
||||
if (text.entry && text.is_terminal() && text.is_finished())
|
||||
{
|
||||
isWindows = true;
|
||||
isStartEnd = true;
|
||||
isStart = false;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
isPostgame = true;
|
||||
menu.isCheats = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.isJustStageFinal && !isEnd && !isPostgame) isEnd = true;
|
||||
|
||||
if (isEnd)
|
||||
if (isPostgamePending && text.is_terminal() && text.is_finished() && !text.is_scene_active())
|
||||
{
|
||||
if (!isEndBegin)
|
||||
{
|
||||
if (character.is_animation_finished())
|
||||
{
|
||||
auto* end = dialogue.get(Schema::Element::END);
|
||||
text.set(end ? dialogue.dialogue_entry_get(*end) : nullptr, character);
|
||||
isEndBegin = true;
|
||||
isWindows = false;
|
||||
tools.isOpen = false;
|
||||
menu.isOpen = false;
|
||||
character.calories = 0;
|
||||
character.digestionProgress = 0;
|
||||
itemManager.heldItemIndex = -1;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
}
|
||||
else if (!isEndEnd)
|
||||
{
|
||||
if (text.entry && text.is_terminal() && text.is_finished())
|
||||
{
|
||||
menu.isOpen = true;
|
||||
isWindows = true;
|
||||
isEndEnd = true;
|
||||
isEnd = false;
|
||||
isPostgame = true;
|
||||
menu.isCheats = true;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
}
|
||||
menu.isOpen = true;
|
||||
isPostgamePending = false;
|
||||
isPostgame = true;
|
||||
menu.isCheats = true;
|
||||
world.character_focus(character, worldCanvas, focus_get());
|
||||
}
|
||||
|
||||
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
|
||||
if (!text.is_scene_active())
|
||||
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);
|
||||
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);
|
||||
@@ -371,6 +325,7 @@ namespace game::state
|
||||
|
||||
character.update();
|
||||
areaManager.update(character);
|
||||
world.bounds_set(areaManager.bounds_get(character, world.bounds));
|
||||
particleManager.update();
|
||||
cursor.update();
|
||||
if (autofeed.active_get()) autofeedCursor.update();
|
||||
@@ -396,13 +351,15 @@ namespace game::state
|
||||
worldCanvas.bind();
|
||||
worldCanvas.size_set(size);
|
||||
worldCanvas.clear();
|
||||
debug_flags_apply();
|
||||
|
||||
areaManager.render(character, textureShader, rectShader, worldCanvas);
|
||||
|
||||
character.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
item.render(textureShader, rectShader, worldCanvas);
|
||||
if (!text.is_scene_active())
|
||||
for (auto& item : itemManager.items)
|
||||
item.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
auto isCursorEnabled = cursor_enabled_get();
|
||||
if (isCursorEnabled && autofeed.active_get()) autofeedCursor.render(textureShader, rectShader, worldCanvas);
|
||||
@@ -423,9 +380,11 @@ namespace game::state
|
||||
void Play::save(Resources& resources)
|
||||
{
|
||||
resource::xml::Schema save;
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto& root = save.element_add(resource::xml::Schema::Element::SAVE);
|
||||
root.isPostgame = isPostgame;
|
||||
root.isAlternateSpritesheet = character.spritesheetType == Entity::ALTERNATE;
|
||||
root.spawnDelayTicks = itemManager.spawnDelayTicks;
|
||||
|
||||
auto& panElement = save.element_add(resource::xml::Schema::Element::PAN, 0);
|
||||
panElement.position = worldCanvas.pan;
|
||||
@@ -438,36 +397,35 @@ namespace game::state
|
||||
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;
|
||||
auto& item = save.element_add(resource::xml::Schema::Element::ITEM, inventoryIndex);
|
||||
item.id = id;
|
||||
item.quantity = quantity;
|
||||
}
|
||||
characterElement.totalItemsSpawned = character.totalItemsSpawned;
|
||||
characterElement.totalItemsConsumed = character.totalItemsConsumed;
|
||||
characterElement.totalPlaytimeSeconds = character.totalPlaytimeSeconds;
|
||||
|
||||
auto itemsIndex = (int)save.elements.size();
|
||||
save.element_add(resource::xml::Schema::Element::ITEMS, 0);
|
||||
for (auto& item : itemManager.items)
|
||||
{
|
||||
if (item.schemaID < 0 || item.schemaID >= (int)itemSchema.items.size()) continue;
|
||||
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;
|
||||
}
|
||||
|
||||
auto discoveredIndex = (int)save.elements.size();
|
||||
save.element_add(resource::xml::Schema::Element::DISCOVERED, 0);
|
||||
for (int i = 0; i < (int)itemManager.discoveredItemIDs.size(); i++)
|
||||
{
|
||||
if (!itemManager.discoveredItemIDs[i]) continue;
|
||||
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, discoveredIndex);
|
||||
itemElement.id = i;
|
||||
}
|
||||
|
||||
resources.character_save_set(characterIndex, save);
|
||||
save.serialize(character.data.save_path_get());
|
||||
|
||||
|
||||
+4
-14
@@ -5,7 +5,6 @@
|
||||
#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"
|
||||
@@ -31,7 +30,6 @@ namespace game::state
|
||||
Entity cursor;
|
||||
Entity autofeedCursor;
|
||||
|
||||
play::Info info;
|
||||
play::Menu menu;
|
||||
play::Tools tools;
|
||||
play::Text text;
|
||||
@@ -50,17 +48,10 @@ namespace game::state
|
||||
int cheatCodeIndex{};
|
||||
double cheatCodeStartTime{};
|
||||
|
||||
bool isWindows{true};
|
||||
|
||||
bool isStartBegin{};
|
||||
bool isStart{};
|
||||
bool isStartEnd{};
|
||||
|
||||
bool isEndBegin{};
|
||||
bool isEnd{};
|
||||
bool isEndEnd{};
|
||||
|
||||
bool isPostgame{};
|
||||
bool isPostgamePending{};
|
||||
bool isShowLayers{};
|
||||
bool isShowNulls{};
|
||||
|
||||
Canvas worldCanvas{play::World::SIZE};
|
||||
|
||||
@@ -71,8 +62,7 @@ namespace game::state
|
||||
void update(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
void save(Resources&);
|
||||
void debug_flags_apply();
|
||||
play::World::Focus focus_get();
|
||||
void start_sequence_begin();
|
||||
void end_sequence_begin();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
#include "area_manager.hpp"
|
||||
|
||||
#include "../../util/math.hpp"
|
||||
#include <cmath>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr auto DEFAULT_WORLD_BOUNDS_NULL = "Bounds";
|
||||
constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
bool rect_is_valid(glm::vec4 rect)
|
||||
{
|
||||
return std::isfinite(rect.x) && std::isfinite(rect.y) && std::isfinite(rect.z) && std::isfinite(rect.w) &&
|
||||
rect.z > ZERO_FLOAT && rect.w > ZERO_FLOAT;
|
||||
}
|
||||
|
||||
glm::vec4 bounds_from_rect_get(glm::vec4 rect)
|
||||
{
|
||||
return {rect.x, rect.y, rect.x + rect.z, rect.y + rect.w};
|
||||
}
|
||||
|
||||
glm::vec4 rect_from_bounds_get(glm::vec4 bounds)
|
||||
{
|
||||
return {bounds.x, bounds.y, bounds.z - bounds.x, bounds.w - bounds.y};
|
||||
}
|
||||
}
|
||||
|
||||
int AreaManager::index_get(Entity& character)
|
||||
{
|
||||
auto& data = character.data;
|
||||
@@ -32,14 +54,61 @@ namespace game::state::play
|
||||
return &character.data.areaSchema.areas[index];
|
||||
}
|
||||
|
||||
glm::vec4 AreaManager::bounds_get(Entity& character, const glm::vec4& fallback)
|
||||
{
|
||||
auto index = index_get(character);
|
||||
if (index == -1 || index >= (int)bounds.size()) return fallback;
|
||||
auto result = bounds[index];
|
||||
return rect_is_valid(rect_from_bounds_get(result)) ? result : fallback;
|
||||
}
|
||||
|
||||
glm::vec4 AreaManager::item_spawn_rect_get(Entity& character, const glm::vec4& fallbackBounds)
|
||||
{
|
||||
auto index = index_get(character);
|
||||
if (index == -1 || index >= (int)itemSpawnRects.size()) return rect_from_bounds_get(fallbackBounds);
|
||||
auto result = itemSpawnRects[index];
|
||||
return rect_is_valid(result) ? result : rect_from_bounds_get(bounds_get(character, fallbackBounds));
|
||||
}
|
||||
|
||||
void AreaManager::debug_flags_set(bool isShowLayers, bool isShowNulls)
|
||||
{
|
||||
this->isShowLayers = isShowLayers;
|
||||
this->isShowNulls = isShowNulls;
|
||||
for (auto& entity : entities)
|
||||
{
|
||||
entity.isShowLayers = isShowLayers;
|
||||
entity.isShowNulls = isShowNulls;
|
||||
}
|
||||
}
|
||||
|
||||
void AreaManager::set(Entity& character)
|
||||
{
|
||||
entities.clear();
|
||||
bounds.clear();
|
||||
itemSpawnRects.clear();
|
||||
auto& areas = character.data.areaSchema.areas;
|
||||
entities.reserve(areas.size());
|
||||
bounds.reserve(areas.size());
|
||||
itemSpawnRects.reserve(areas.size());
|
||||
auto* root = character.data.areaSchema.root();
|
||||
auto worldBoundsNull = root && !root->worldBoundsNull.empty() ? root->worldBoundsNull : DEFAULT_WORLD_BOUNDS_NULL;
|
||||
auto itemSpawnNull = root && !root->itemSpawnNull.empty() ? root->itemSpawnNull : worldBoundsNull;
|
||||
|
||||
for (auto& area : areas)
|
||||
entities.emplace_back(area.anm2.is_valid() ? Entity(area.anm2) : Entity{});
|
||||
{
|
||||
auto entity = area.anm2.is_valid() ? Entity(area.anm2) : Entity{};
|
||||
entity.position = {};
|
||||
entity.isShowLayers = isShowLayers;
|
||||
entity.isShowNulls = isShowNulls;
|
||||
auto worldBoundsNullID = entity.nullMap.contains(worldBoundsNull) ? entity.nullMap.at(worldBoundsNull) : -1;
|
||||
auto itemSpawnNullID = entity.nullMap.contains(itemSpawnNull) ? entity.nullMap.at(itemSpawnNull) : worldBoundsNullID;
|
||||
auto rect = entity.null_frame_rect(worldBoundsNullID, entity.defaultAnimationID, ZERO_FLOAT);
|
||||
auto itemSpawnRect = entity.null_frame_rect(itemSpawnNullID, entity.defaultAnimationID, ZERO_FLOAT);
|
||||
if (!rect_is_valid(itemSpawnRect)) itemSpawnRect = rect;
|
||||
entities.emplace_back(std::move(entity));
|
||||
bounds.emplace_back(rect_is_valid(rect) ? bounds_from_rect_get(rect) : glm::vec4(NAN));
|
||||
itemSpawnRects.emplace_back(rect_is_valid(itemSpawnRect) ? itemSpawnRect : glm::vec4(NAN));
|
||||
}
|
||||
}
|
||||
|
||||
void AreaManager::update(Entity& character)
|
||||
@@ -48,6 +117,7 @@ namespace game::state::play
|
||||
|
||||
auto index = index_get(character);
|
||||
if (index == -1 || index >= (int)entities.size()) return;
|
||||
entities[index].position = {};
|
||||
entities[index].update();
|
||||
}
|
||||
|
||||
@@ -56,18 +126,9 @@ namespace game::state::play
|
||||
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
|
||||
|
||||
auto index = index_get(character);
|
||||
if (index == -1) return;
|
||||
if (index == -1 || index >= (int)entities.size()) 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);
|
||||
entities[index].position = {};
|
||||
entities[index].render(textureShader, rectShader, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,16 @@ namespace game::state::play
|
||||
{
|
||||
public:
|
||||
std::vector<Entity> entities{};
|
||||
std::vector<glm::vec4> bounds{};
|
||||
std::vector<glm::vec4> itemSpawnRects{};
|
||||
bool isShowLayers{};
|
||||
bool isShowNulls{};
|
||||
|
||||
int index_get(Entity&);
|
||||
game::resource::xml::Schema::AreaEntry* get(Entity&);
|
||||
glm::vec4 bounds_get(Entity&, const glm::vec4& fallback);
|
||||
glm::vec4 item_spawn_rect_get(Entity&, const glm::vec4& fallbackBounds);
|
||||
void debug_flags_set(bool isShowLayers, bool isShowNulls);
|
||||
void set(Entity&);
|
||||
void update(Entity&);
|
||||
void render(Entity&, game::resource::Shader&, game::resource::Shader&, Canvas&);
|
||||
|
||||
+78
-118
@@ -19,6 +19,7 @@ namespace game::state::play
|
||||
constexpr float CURSOR_SPEED_SCALE_MIN = 0.5f;
|
||||
constexpr float CURSOR_SPEED_SCALE_MAX = 2.0f;
|
||||
constexpr float CALORIE_EPSILON = 0.001f;
|
||||
constexpr float FOOD_TIE_PICK_WEIGHT = 1.0f;
|
||||
|
||||
glm::vec2 rect_center(glm::vec4 rect) { return {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; }
|
||||
|
||||
@@ -62,13 +63,12 @@ namespace game::state::play
|
||||
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;
|
||||
schema.categories[item.categoryID].useMode == "Edible";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -77,45 +77,24 @@ namespace game::state::play
|
||||
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();
|
||||
return is_autofeed_food(schema, item) && item.isCalories && item.calories > 0.0f &&
|
||||
character.calories + item.calories <= character.max_capacity();
|
||||
}
|
||||
|
||||
struct FoodScore
|
||||
{
|
||||
float calories{};
|
||||
int bites{};
|
||||
float caloriesPerBite{};
|
||||
float value{};
|
||||
bool isValid{};
|
||||
};
|
||||
|
||||
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item,
|
||||
int durability)
|
||||
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
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};
|
||||
if (!can_eat(character, schema, item)) return {};
|
||||
return {.calories = item.calories, .bites = 1, .value = item.calories, .isValid = true};
|
||||
}
|
||||
|
||||
bool is_better_food_score(const FoodScore& score, const FoodScore& best)
|
||||
@@ -124,10 +103,31 @@ namespace game::state::play
|
||||
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;
|
||||
return score.value > best.value;
|
||||
}
|
||||
|
||||
bool can_eat_anything(Entity& character, ItemManager& itemManager, menu::Inventory& inventory)
|
||||
bool is_same_food_score(const FoodScore& score, const FoodScore& best)
|
||||
{
|
||||
return score.isValid && best.isValid && score.calories == best.calories && score.bites == best.bites &&
|
||||
score.value == best.value;
|
||||
}
|
||||
|
||||
bool food_score_select(FoodScore& best, int& count, const FoodScore& score)
|
||||
{
|
||||
if (is_better_food_score(score, best))
|
||||
{
|
||||
best = score;
|
||||
count = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_same_food_score(score, best)) return false;
|
||||
|
||||
count++;
|
||||
return math::random_max((float)count) < FOOD_TIE_PICK_WEIGHT;
|
||||
}
|
||||
|
||||
bool can_eat_anything(Entity& character, ItemManager& itemManager)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
for (auto& entity : itemManager.items)
|
||||
@@ -137,47 +137,6 @@ namespace game::state::play
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -193,10 +152,9 @@ namespace game::state::play
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_food(Entity& character, menu::Inventory& inventory, ItemManager& itemManager)
|
||||
bool is_food(Entity& character, ItemManager& itemManager)
|
||||
{
|
||||
return character.calories > CALORIE_EPSILON || is_inventory_food(character, inventory) ||
|
||||
is_world_food(character, itemManager);
|
||||
return character.calories > CALORIE_EPSILON || is_world_food(character, itemManager);
|
||||
}
|
||||
|
||||
int best_world_food_get(Entity& character, ItemManager& itemManager, bool requireCanEat)
|
||||
@@ -204,6 +162,7 @@ namespace game::state::play
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto bestIndex = -1;
|
||||
auto bestScore = FoodScore{};
|
||||
auto bestCount = 0;
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
@@ -214,17 +173,25 @@ namespace game::state::play
|
||||
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;
|
||||
}
|
||||
auto score = food_score_get(character, schema, item);
|
||||
if (food_score_select(bestScore, bestCount, score)) bestIndex = i;
|
||||
}
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
bool world_food_target_is_valid(Entity& character, ItemManager& itemManager, int index)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
if (index < 0 || index >= (int)itemManager.items.size()) return false;
|
||||
|
||||
auto& entity = itemManager.items[index];
|
||||
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) return false;
|
||||
|
||||
auto& item = schema.items[entity.schemaID];
|
||||
return can_eat(character, schema, item) && is_finite(entity.rect());
|
||||
}
|
||||
|
||||
bool eat_rect_get(Entity& character, glm::vec4& rect)
|
||||
{
|
||||
for (auto* eatArea : character.data.eat_areas_get())
|
||||
@@ -257,16 +224,18 @@ namespace game::state::play
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& digestClickCooldown)
|
||||
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& targetItemIndex,
|
||||
int& digestClickCooldown)
|
||||
{
|
||||
isItemMouseHeld = false;
|
||||
isDigestMouseHeld = false;
|
||||
targetItemIndex = -1;
|
||||
digestClickCooldown = 0;
|
||||
if (!is_finite(cursor.position)) cursor.position = {};
|
||||
}
|
||||
}
|
||||
|
||||
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory& inventory,
|
||||
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory&,
|
||||
ItemManager& itemManager, CharacterManager& characterManager, Canvas& canvas)
|
||||
{
|
||||
isActive = false;
|
||||
@@ -274,30 +243,27 @@ namespace game::state::play
|
||||
|
||||
if (!isEnabled)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_food(character, inventory, itemManager))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (character.isStageUp || character.isJustStageUp || character.isJustStageFinal)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
itemManager.spawn_queue(character);
|
||||
|
||||
if (!is_food(character, itemManager))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, 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;
|
||||
@@ -315,7 +281,7 @@ namespace game::state::play
|
||||
{
|
||||
isActive = false;
|
||||
cursor.isVisible = false;
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
auto& schemaItem = schema.items[heldItem.schemaID];
|
||||
@@ -338,10 +304,14 @@ namespace game::state::play
|
||||
isActive = true;
|
||||
cursor.isVisible = true;
|
||||
isItemMouseHeld = false;
|
||||
targetItemIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bestWorldFood = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
|
||||
if (!isEatTarget || !world_food_target_is_valid(character, itemManager, targetItemIndex))
|
||||
targetItemIndex = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
|
||||
|
||||
auto bestWorldFood = targetItemIndex;
|
||||
|
||||
if (bestWorldFood != -1)
|
||||
{
|
||||
@@ -349,7 +319,7 @@ namespace game::state::play
|
||||
auto rect = item.rect();
|
||||
if (!is_finite(rect))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -359,6 +329,7 @@ namespace game::state::play
|
||||
|
||||
auto isInItemRect = math::is_point_in_rectf(rect, cursor.position);
|
||||
|
||||
itemInput.targetItemIndex = bestWorldFood;
|
||||
itemInput.isMouseLeftClicked = isInItemRect && !isItemMouseHeld;
|
||||
itemInput.isMouseLeftDown = isInItemRect || isItemMouseHeld;
|
||||
itemManager.inputOverride = itemInput;
|
||||
@@ -367,37 +338,26 @@ namespace game::state::play
|
||||
return;
|
||||
}
|
||||
|
||||
auto isInventoryFood = is_inventory_food(character, inventory);
|
||||
auto isWorldFood = is_world_food(character, itemManager);
|
||||
if (character.calories <= CALORIE_EPSILON && !isInventoryFood && !isWorldFood)
|
||||
if (character.calories <= CALORIE_EPSILON && !isWorldFood)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, 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))
|
||||
if ((isWorldFood && bestWorldFood == -1) || !can_eat_anything(character, itemManager))
|
||||
{
|
||||
auto* interactArea = digestion_area_get(character);
|
||||
if (!interactArea)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, 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);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
auto radius = glm::max(1.0f, glm::min(rect.z, rect.w) * 0.25f);
|
||||
@@ -434,14 +394,14 @@ namespace game::state::play
|
||||
return;
|
||||
}
|
||||
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
|
||||
}
|
||||
|
||||
void Autofeed::toggle() { isEnabled = !isEnabled; }
|
||||
void Autofeed::disable() { isEnabled = false; }
|
||||
bool Autofeed::available_get(Entity& character, menu::Inventory& inventory, ItemManager& itemManager) const
|
||||
bool Autofeed::available_get(Entity& character, ItemManager& itemManager) const
|
||||
{
|
||||
return is_food(character, inventory, itemManager);
|
||||
return is_food(character, itemManager) || itemManager.spawn_possible_get(character);
|
||||
}
|
||||
bool Autofeed::enabled_get() const { return isEnabled; }
|
||||
bool Autofeed::active_get() const { return isActive; }
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace game::state::play
|
||||
{
|
||||
bool isItemMouseHeld{};
|
||||
bool isDigestMouseHeld{};
|
||||
int targetItemIndex{-1};
|
||||
int digestClickCooldown{};
|
||||
float digestAngle{};
|
||||
bool isEnabled{};
|
||||
@@ -21,7 +22,7 @@ namespace game::state::play
|
||||
CharacterManager&, Canvas&);
|
||||
void toggle();
|
||||
void disable();
|
||||
bool available_get(Entity& character, menu::Inventory&, ItemManager&) const;
|
||||
bool available_get(Entity& character, ItemManager&) const;
|
||||
bool enabled_get() const;
|
||||
bool active_get() const;
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <imgui.h>
|
||||
#include <optional>
|
||||
|
||||
using namespace game::resource::xml;
|
||||
@@ -139,10 +139,10 @@ namespace game::state::play
|
||||
|
||||
if (layerID != -1)
|
||||
{
|
||||
auto scaleEffectTimeTicks = interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT
|
||||
: interactArea->timeTicks;
|
||||
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;
|
||||
: 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));
|
||||
@@ -204,10 +204,8 @@ namespace game::state::play
|
||||
cursorWorldPositionPrevious = cursorWorldPosition;
|
||||
|
||||
if (character.isJustDigested && text.is_interruptible())
|
||||
if (auto* digest = dialogue.get(Schema::Element::DIGEST))
|
||||
text.set(dialogue.dialogue_pool_entry_get(*digest), character);
|
||||
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::DIGEST), character);
|
||||
if (character.isJustStageUp)
|
||||
if (auto* stageUp = dialogue.get(Schema::Element::STAGE_UP))
|
||||
text.set(dialogue.dialogue_pool_entry_get(*stageUp), character);
|
||||
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::STAGE_UP, character.stage_get() + 1), character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "../../util/imgui/input_int_ex.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
@@ -12,7 +11,7 @@ using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Cheats::update(Resources&, Entity& character, menu::Inventory& inventory)
|
||||
void Cheats::update(Resources&, Entity& character)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
@@ -27,8 +26,6 @@ namespace game::state::play
|
||||
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 = [&]()
|
||||
{
|
||||
@@ -59,31 +56,10 @@ namespace game::state::play
|
||||
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::DIGESTION_MAX;
|
||||
|
||||
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
|
||||
|
||||
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
ImGui::PushID(i);
|
||||
WIDGET_FX(input_int_range(item.name.c_str(), &inventory.values[i], 0, schema.quantityMax, 1, 5));
|
||||
ImGui::SetItemTooltip("%s", item.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "menu/inventory.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -10,6 +9,6 @@ namespace game::state::play
|
||||
class Cheats
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Entity&, menu::Inventory&);
|
||||
void update(Resources&, Entity&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#include "info.hpp"
|
||||
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
#include "../../util/string.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
float info_height_get()
|
||||
{
|
||||
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),
|
||||
info_height_get());
|
||||
auto pos = ImVec2((windowSize.x * 0.5f) - (size.x * 0.5f), style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
if (fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#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&, float fadeAlpha);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "item_display.hpp"
|
||||
|
||||
#include "../../resource/font.hpp"
|
||||
#include "../../util/color.hpp"
|
||||
#include "../../util/imgui.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
void item_header_draw(const resource::xml::Schema::ItemEntry& item)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", item.name.c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
void item_summary_draw(Entity& character, resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
|
||||
{
|
||||
using Strings = resource::xml::Strings;
|
||||
|
||||
auto& strings = character.data.strings;
|
||||
auto isCategory = item.categoryID >= 0 && item.categoryID < (int)schema.categories.size();
|
||||
auto isRarity = item.rarityID >= 0 && item.rarityID < (int)schema.rarities.size();
|
||||
auto isFlavor = item.flavorID >= 0 && item.flavorID < (int)schema.flavors.size();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(util::imgui::to_imvec4(util::color::GRAY)));
|
||||
if (isCategory && isRarity)
|
||||
ImGui::TextWrapped("-- %s (%s) --", schema.categories[item.categoryID].name.c_str(),
|
||||
schema.rarities[item.rarityID].name.c_str());
|
||||
else if (isCategory)
|
||||
ImGui::TextWrapped("-- %s --", schema.categories[item.categoryID].name.c_str());
|
||||
if (item.isFlavor && isFlavor)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(), schema.flavors[item.flavorID].name.c_str());
|
||||
if (item.isCalories) ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), item.calories);
|
||||
if (item.isCapacityBonus)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
if (item.digestionBonus > ZERO_FLOAT)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
|
||||
item.digestionBonus * Entity::UPDATE_RATE);
|
||||
else if (item.digestionBonus < ZERO_FLOAT)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
|
||||
item.digestionBonus * Entity::UPDATE_RATE);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void item_tooltip_draw(Entity& character, resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
|
||||
{
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER);
|
||||
item_header_draw(item);
|
||||
ImGui::Separator();
|
||||
item_summary_draw(character, schema, item);
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", item.description.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity.hpp"
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
inline constexpr auto ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER = 24.0f;
|
||||
|
||||
void item_header_draw(const resource::xml::Schema::ItemEntry&);
|
||||
void item_summary_draw(Entity&, resource::xml::Schema&, const resource::xml::Schema::ItemEntry&);
|
||||
void item_tooltip_draw(Entity&, resource::xml::Schema&, const resource::xml::Schema::ItemEntry&);
|
||||
}
|
||||
+230
-123
@@ -1,4 +1,5 @@
|
||||
#include "item_manager.hpp"
|
||||
#include "item_display.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
@@ -15,44 +16,100 @@ using namespace glm;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
namespace
|
||||
static inline float item_spawn_weight_get(resource::xml::Schema& itemSchema, Entity& character, int id)
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
|
||||
int durability, int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
if (id < 0 || id >= (int)itemSchema.items.size()) return ZERO_FLOAT;
|
||||
|
||||
auto& item = itemSchema.items[id];
|
||||
if (item.isCalories && item.calories > character.max_capacity()) return ZERO_FLOAT;
|
||||
if (item.rarityID < 0 || item.rarityID >= (int)itemSchema.rarities.size()) return ZERO_FLOAT;
|
||||
return glm::max(ZERO_FLOAT, itemSchema.rarities[item.rarityID].weight);
|
||||
}
|
||||
|
||||
int ItemManager::spawn_delay_get(Entity& character)
|
||||
{
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
static constexpr auto ONE_INT = 1;
|
||||
static constexpr auto SPAWN_DELAY_TICKS_DEFAULT = 60;
|
||||
static constexpr auto SPAWN_DELAY_STAGE_MULTIPLIER_DEFAULT = 1.0f;
|
||||
|
||||
auto* itemRoot = character.data.itemSchema.root();
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto baseSpawnDelay =
|
||||
itemRoot && itemRoot->baseSpawnDelayTicks > 0 ? itemRoot->baseSpawnDelayTicks : SPAWN_DELAY_TICKS_DEFAULT;
|
||||
auto spawnDelayMultiplier = itemRoot && itemRoot->spawnDelayStageMultiplier > ZERO_FLOAT
|
||||
? itemRoot->spawnDelayStageMultiplier
|
||||
: SPAWN_DELAY_STAGE_MULTIPLIER_DEFAULT;
|
||||
return glm::max(ONE_INT, (int)std::round((float)baseSpawnDelay * std::pow(spawnDelayMultiplier, stage)));
|
||||
}
|
||||
|
||||
bool ItemManager::spawn_possible_get(Entity& character)
|
||||
{
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto* itemRoot = itemSchema.root();
|
||||
auto spawnLimit = itemRoot && itemRoot->itemSpawnLimit > 0 ? itemRoot->itemSpawnLimit : LIMIT;
|
||||
if (!itemRoot || (int)items.size() >= spawnLimit || itemSchema.items.empty()) return false;
|
||||
|
||||
for (int i = 0; i < (int)itemSchema.items.size(); i++)
|
||||
if (item_spawn_weight_get(itemSchema, character, i) > ZERO_FLOAT) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ItemManager::spawn_available_get(Entity& character)
|
||||
{
|
||||
return spawnDelayTicks <= 0 && spawn_possible_get(character);
|
||||
}
|
||||
|
||||
bool ItemManager::spawn_queue(Entity& character, int itemID, bool isLimitIgnored)
|
||||
{
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto* itemRoot = itemSchema.root();
|
||||
auto spawnLimit = itemRoot && itemRoot->itemSpawnLimit > 0 ? itemRoot->itemSpawnLimit : LIMIT;
|
||||
if (!itemRoot || itemID < 0 || itemID >= (int)itemSchema.items.size() ||
|
||||
(!isLimitIgnored && (int)items.size() >= spawnLimit))
|
||||
return false;
|
||||
|
||||
auto& item = itemSchema.items[itemID];
|
||||
itemRoot->soundSummon.play();
|
||||
if (item.rarityID >= 0 && item.rarityID < (int)itemSchema.rarities.size())
|
||||
itemSchema.rarities[item.rarityID].sound.play();
|
||||
queuedItemIDs.emplace_back(itemID);
|
||||
spawnDelayTicks = spawn_delay_get(character);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ItemManager::spawn_queue(Entity& character)
|
||||
{
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
if (!spawn_available_get(character)) return false;
|
||||
|
||||
auto total = ZERO_FLOAT;
|
||||
for (int i = 0; i < (int)itemSchema.items.size(); i++)
|
||||
total += item_spawn_weight_get(itemSchema, character, i);
|
||||
|
||||
if (total <= ZERO_FLOAT) return false;
|
||||
|
||||
auto roll = math::random_max(total);
|
||||
auto accumulator = ZERO_FLOAT;
|
||||
auto itemID = -1;
|
||||
for (int i = 0; i < (int)itemSchema.items.size(); i++)
|
||||
{
|
||||
auto weight = item_spawn_weight_get(itemSchema, character, i);
|
||||
if (weight <= ZERO_FLOAT) continue;
|
||||
itemID = i;
|
||||
accumulator += weight;
|
||||
if (roll < accumulator) break;
|
||||
}
|
||||
|
||||
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.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)};
|
||||
}
|
||||
if (itemID == -1) return false;
|
||||
return spawn_queue(character, itemID);
|
||||
}
|
||||
|
||||
void ItemManager::update(Entity& character, Entity& cursor, AreaManager& areaManager, Text& text,
|
||||
@@ -74,11 +131,8 @@ namespace game::state::play
|
||||
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 dialogue_hook_entry_get = [&](resource::xml::Schema::Element::Type type)
|
||||
{ return dialogue.dialogue_hook_entry_get(type); };
|
||||
auto particles_queue =
|
||||
[&](resource::xml::Schema& particleSchema, resource::xml::Schema::Element::Type type, glm::vec2 position)
|
||||
{
|
||||
@@ -101,8 +155,8 @@ namespace game::state::play
|
||||
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto isAutomation = this->isAutomation;
|
||||
auto targetItemIndex = -1;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
@@ -113,7 +167,7 @@ namespace game::state::play
|
||||
isMouseLeftDown = inputOverride->isMouseLeftDown;
|
||||
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
|
||||
isMouseRightClicked = inputOverride->isMouseRightClicked;
|
||||
isMouseRightDown = inputOverride->isMouseRightDown;
|
||||
targetItemIndex = inputOverride->targetItemIndex;
|
||||
isAutomation = isAutomation || inputOverride->isAutomation;
|
||||
inputOverride.reset();
|
||||
}
|
||||
@@ -123,6 +177,11 @@ namespace game::state::play
|
||||
if (heldItemIndex != -1)
|
||||
{
|
||||
heldItemIndex = -1;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
pendingEatAnimationIndex = -1;
|
||||
isPendingEatRect = false;
|
||||
isEatTextPending = false;
|
||||
isJustItemHeldStopped = true;
|
||||
}
|
||||
isItemHovered = false;
|
||||
@@ -132,10 +191,20 @@ namespace game::state::play
|
||||
isMouseLeftDown = false;
|
||||
isMouseLeftReleased = false;
|
||||
isMouseRightClicked = false;
|
||||
isMouseRightDown = false;
|
||||
isImguiCaptureMouse = true;
|
||||
}
|
||||
|
||||
auto isEatAnimationFinished = pendingEatAnimationIndex == character.animationIndex &&
|
||||
(character.state == Entity::STOPPED || character.is_animation_finished());
|
||||
if (isEatTextPending && isEatAnimationFinished)
|
||||
{
|
||||
text.set(dialogue_hook_entry_get(isEatTextOverCapacity ? resource::xml::Schema::Element::EAT_FULL
|
||||
: resource::xml::Schema::Element::EAT),
|
||||
character);
|
||||
isEatTextPending = false;
|
||||
pendingEatAnimationIndex = -1;
|
||||
}
|
||||
|
||||
if (isJustItemHoveredStopped)
|
||||
{
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
@@ -155,27 +224,24 @@ namespace game::state::play
|
||||
isItemHovered = false;
|
||||
particles.clear();
|
||||
if (isItemHovered != isItemHoveredPrevious && !isItemHovered) isJustItemHoveredStopped = true;
|
||||
if (spawnDelayTicks > 0) spawnDelayTicks--;
|
||||
if (discoveredItemIDs.size() != schema.items.size()) discoveredItemIDs.resize(schema.items.size());
|
||||
|
||||
for (auto& id : queuedItemIDs)
|
||||
{
|
||||
auto position = item_spawn_position_get(character, bounds);
|
||||
auto spawnRect = areaManager.item_spawn_rect_get(character, bounds);
|
||||
auto position = glm::vec2(spawnRect.x + math::random_max(spawnRect.z),
|
||||
spawnRect.y + math::random_max(spawnRect.w));
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto& item = itemSchema.items.at(id);
|
||||
auto& anm2 = itemSchema.anm2s.at(id);
|
||||
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);
|
||||
items.emplace_back(anm2, position, id);
|
||||
character.totalItemsSpawned++;
|
||||
if (id >= 0 && id < (int)discoveredItemIDs.size()) discoveredItemIDs[id] = true;
|
||||
particles_queue(itemSchema, resource::xml::Schema::Element::SUMMON, position);
|
||||
}
|
||||
queuedItemIDs.clear();
|
||||
|
||||
if (isMouseRightDown)
|
||||
{
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationReturn.get()) cursor.play({*animation});
|
||||
}
|
||||
|
||||
if (auto heldItem = vector::find(items, heldItemIndex))
|
||||
{
|
||||
auto& item = schema.items[heldItem->schemaID];
|
||||
@@ -191,24 +257,24 @@ namespace game::state::play
|
||||
rotation *= friction;
|
||||
rotation = glm::clamp(-ROTATION_MAX, rotation, ROTATION_MAX);
|
||||
|
||||
if (schema.categories[item.categoryID].isEdible)
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
if (category.useMode == "Edible")
|
||||
{
|
||||
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();
|
||||
auto calories = item.isCalories ? item.calories : 0.0f;
|
||||
auto isCanEat = calories > 0.0f && character.calories + calories <= character.max_capacity();
|
||||
|
||||
if (isJustItemHeld)
|
||||
{
|
||||
if (!isAutomation)
|
||||
{
|
||||
if (isCanEat)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FEED_FULL
|
||||
text.set(dialogue_hook_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 if (calories > character.capacity)
|
||||
text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::LOW_CAPACITY), character);
|
||||
else
|
||||
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::FULL), character);
|
||||
text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::FULL), character);
|
||||
}
|
||||
isJustItemHeld = false;
|
||||
}
|
||||
@@ -220,77 +286,78 @@ namespace game::state::play
|
||||
|
||||
auto nullID = character.data.null_id_get(eatArea->null);
|
||||
auto rect = character.null_frame_rect(nullID);
|
||||
if (!is_finite(rect))
|
||||
if (!resource::xml::Anm2::is_rect_valid(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.play({.animation = eatArea->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.speedMultiplier = character.eatSpeed});
|
||||
auto eventID = eatArea->event.empty() ? eatArea->eventID : character.data.event_id_get(eatArea->event);
|
||||
auto isEatAreaActive = isCanEat && math::is_point_in_rectf(rect, heldItem->position);
|
||||
auto isPendingEatItem = pendingEatItemIndex == heldItemIndex && pendingEatEventID == eventID;
|
||||
auto isPendingEatAreaActive =
|
||||
isPendingEatItem && isPendingEatRect && math::is_point_in_rectf(pendingEatRect, heldItem->position);
|
||||
auto isEatEventActivated = isPendingEatItem && eventID != -1 && character.playedEventID == eventID;
|
||||
|
||||
if (character.playedEventID == character.data.event_id_get(eatArea->event))
|
||||
if (isEatAreaActive || isPendingEatAreaActive)
|
||||
{
|
||||
if (isEatEventActivated)
|
||||
{
|
||||
heldItem->durability++;
|
||||
character.consume_played_event();
|
||||
|
||||
character.calories += caloriesPerBite;
|
||||
character.totalCaloriesConsumed += caloriesPerBite;
|
||||
character.calories += calories;
|
||||
character.totalCaloriesConsumed += calories;
|
||||
|
||||
if (item.isCapacityBonus)
|
||||
{
|
||||
character.capacity += item.capacityBonus / durabilityMax;
|
||||
character.capacity += item.capacityBonus;
|
||||
character.capacity = glm::clamp(character.capacity, (float)character.data.root()->capacityMinCalories,
|
||||
(float)character.data.root()->capacityMaxCalories);
|
||||
}
|
||||
if (item.isEatSpeedBonus)
|
||||
{
|
||||
character.eatSpeed += item.eatSpeedBonus / durabilityMax;
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.root()->eatSpeedMinMultiplier,
|
||||
(float)character.data.root()->eatSpeedMaxMultiplier);
|
||||
}
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
character.digestionRate += item.digestionBonus / durabilityMax;
|
||||
character.digestionRate += item.digestionBonus;
|
||||
character.digestionRate =
|
||||
glm::clamp(character.digestionRate, (float)character.data.root()->digestionRateMin,
|
||||
(float)character.data.root()->digestionRateMax);
|
||||
}
|
||||
|
||||
if (heldItem->durability >= durabilityMax)
|
||||
character.totalItemsConsumed++;
|
||||
queuedRemoveItemIndex = heldItemIndex;
|
||||
heldItemIndex = -1;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
pendingEatAnimationIndex = character.animationIndex;
|
||||
isPendingEatRect = false;
|
||||
if (!isAutomation)
|
||||
{
|
||||
isQueueFinishFood = true;
|
||||
character.totalFoodItemsEaten++;
|
||||
queuedRemoveItemIndex = heldItemIndex;
|
||||
heldItemIndex = -1;
|
||||
isEatTextPending = true;
|
||||
isEatTextOverCapacity = isOverCapacity;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingEatItemIndex = heldItemIndex;
|
||||
pendingEatEventID = eventID;
|
||||
if (isEatAreaActive)
|
||||
{
|
||||
auto animationIndex =
|
||||
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
|
||||
heldItem->play({.index = animationIndex, .mode = Entity::SET});
|
||||
pendingEatRect = rect;
|
||||
isPendingEatRect = true;
|
||||
}
|
||||
if (auto animation = character.animation_get(eatArea->animation + character.animation_append_id_get());
|
||||
!animation || !character.is_playing(animation->name))
|
||||
character.play({.animation = eatArea->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.interrupt = Entity::Interrupt::ALWAYS});
|
||||
}
|
||||
}
|
||||
|
||||
if (isMouseLeftReleased)
|
||||
else if (pendingEatItemIndex == heldItemIndex)
|
||||
{
|
||||
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
|
||||
{
|
||||
if (cursorRoot) cursorRoot->soundThrow.play();
|
||||
if (!isAutomation) text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::THROW), character);
|
||||
isJustItemThrown = true;
|
||||
}
|
||||
else if (cursorRoot)
|
||||
cursorRoot->soundRelease.play();
|
||||
|
||||
heldItem->velocity -= delta;
|
||||
heldItemIndex = -1;
|
||||
isJustItemHeldStopped = true;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
isPendingEatRect = false;
|
||||
}
|
||||
|
||||
// Food stolen
|
||||
@@ -299,30 +366,65 @@ namespace game::state::play
|
||||
{
|
||||
if (!math::is_point_in_rectf(rect, heldItem->position))
|
||||
if (!isAutomation)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
|
||||
text.set(dialogue_hook_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
|
||||
: resource::xml::Schema::Element::FOOD_TAKEN),
|
||||
character);
|
||||
}
|
||||
|
||||
if (isEatAreaActive) break;
|
||||
}
|
||||
}
|
||||
else if (category.useMode == "Tool")
|
||||
{
|
||||
auto rect = character.rect();
|
||||
if (item.isToggleSpritesheet && resource::xml::Anm2::is_rect_valid(rect) &&
|
||||
math::is_point_in_rectf(rect, heldItem->position))
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
|
||||
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
|
||||
queuedRemoveItemIndex = heldItemIndex;
|
||||
heldItemIndex = -1;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
pendingEatAnimationIndex = -1;
|
||||
isPendingEatRect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto animation = character.animation_get(); character.time >= animation->frameNum && isQueueFinishFood)
|
||||
{
|
||||
if (!isAutomation)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::EAT_FULL
|
||||
: resource::xml::Schema::Element::EAT),
|
||||
character);
|
||||
isQueueFinishFood = false;
|
||||
heldItem = vector::find(items, heldItemIndex);
|
||||
if (heldItem && isMouseLeftReleased)
|
||||
{
|
||||
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
|
||||
{
|
||||
if (cursorRoot) cursorRoot->soundThrow.play();
|
||||
if (!isAutomation) text.set(dialogue_hook_entry_get(resource::xml::Schema::Element::THROW), character);
|
||||
isJustItemThrown = true;
|
||||
}
|
||||
else if (cursorRoot)
|
||||
cursorRoot->soundRelease.play();
|
||||
|
||||
heldItem->velocity -= delta;
|
||||
heldItemIndex = -1;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
pendingEatAnimationIndex = -1;
|
||||
isPendingEatRect = false;
|
||||
isJustItemHeldStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (queuedRemoveItemIndex > -1)
|
||||
{
|
||||
items.erase(items.begin() + queuedRemoveItemIndex);
|
||||
queuedRemoveItemIndex = -1;
|
||||
pendingEatItemIndex = -1;
|
||||
pendingEatEventID = -1;
|
||||
isPendingEatRect = false;
|
||||
}
|
||||
|
||||
int heldItemMoveIndex = -1;
|
||||
int hoveredTooltipItemID = -1;
|
||||
bool isHoveredTooltipItemHeld = false;
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
@@ -330,12 +432,16 @@ namespace game::state::play
|
||||
auto& rotationOverride = item.overrides[item.rotationOverrideID];
|
||||
auto& rotation = *rotationOverride.frame.rotation;
|
||||
auto& gravity = schemaItem.isGravity ? schemaItem.gravity : area.gravity;
|
||||
auto isTargetItemLocked = targetItemIndex >= 0 && targetItemIndex < (int)items.size();
|
||||
|
||||
item.update();
|
||||
|
||||
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
|
||||
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse &&
|
||||
(!isTargetItemLocked || i == targetItemIndex))
|
||||
{
|
||||
isItemHovered = true;
|
||||
hoveredTooltipItemID = item.schemaID;
|
||||
isHoveredTooltipItemHeld = heldItemIndex == i;
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationHover.get())
|
||||
cursor.play({.animation = *animation, .transition = Entity::Transition::IF_IDLE});
|
||||
@@ -360,17 +466,11 @@ namespace game::state::play
|
||||
|
||||
if (isMouseRightClicked)
|
||||
{
|
||||
if (item.durability > 0)
|
||||
{
|
||||
schema.root()->soundDispose.play();
|
||||
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
schema.root()->soundReturn.play();
|
||||
particles_queue(schema, resource::xml::Schema::Element::RETURN_, item.position);
|
||||
returnItemIDs.emplace_back(item.schemaID);
|
||||
}
|
||||
schema.root()->soundDispose.play();
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationDispose.get())
|
||||
cursor.play({.animation = *animation, .mode = Entity::PLAY_FORCE, .interrupt = Entity::Interrupt::NEVER});
|
||||
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
|
||||
|
||||
if (heldItemIndex == i) heldItemIndex = -1;
|
||||
if (heldItemMoveIndex == i) heldItemMoveIndex = -1;
|
||||
@@ -436,6 +536,13 @@ namespace game::state::play
|
||||
item.position.y = glm::clamp(bounds.y, item.position.y, bounds.w);
|
||||
}
|
||||
|
||||
if (cursor.entityType == CURSOR && hoveredTooltipItemID != -1 && !isHoveredTooltipItemHeld &&
|
||||
ImGui::BeginTooltip())
|
||||
{
|
||||
item_tooltip_draw(character, schema, schema.items[hoveredTooltipItemID]);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
if (heldItemMoveIndex != -1 && heldItemMoveIndex < (int)items.size() - 1)
|
||||
{
|
||||
auto heldItem = std::move(items[heldItemMoveIndex]);
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace game::state::play
|
||||
std::vector<Entity> items{};
|
||||
int heldItemIndex{-1};
|
||||
int queuedRemoveItemIndex{-1};
|
||||
int pendingEatItemIndex{-1};
|
||||
int pendingEatEventID{-1};
|
||||
int pendingEatAnimationIndex{-1};
|
||||
glm::vec4 pendingEatRect{};
|
||||
bool isPendingEatRect{};
|
||||
bool isEatTextPending{};
|
||||
bool isEatTextOverCapacity{};
|
||||
|
||||
bool isItemHovered{};
|
||||
bool isItemHoveredPrevious{};
|
||||
@@ -30,16 +37,14 @@ namespace game::state::play
|
||||
bool isJustItemHeld{};
|
||||
bool isJustItemThrown{};
|
||||
|
||||
bool isQueueFinishFood{};
|
||||
|
||||
bool isItemFinished{};
|
||||
|
||||
glm::vec2 cursorPositionPrevious{};
|
||||
glm::vec2 cursorDeltaPrevious{};
|
||||
|
||||
std::vector<int> queuedItemIDs{};
|
||||
std::vector<int> returnItemIDs{};
|
||||
|
||||
std::vector<bool> discoveredItemIDs{};
|
||||
int spawnDelayTicks{};
|
||||
struct Particle
|
||||
{
|
||||
std::string label{};
|
||||
@@ -53,13 +58,18 @@ namespace game::state::play
|
||||
bool isMouseLeftDown{};
|
||||
bool isMouseLeftReleased{};
|
||||
bool isMouseRightClicked{};
|
||||
bool isMouseRightDown{};
|
||||
bool isAutomation{};
|
||||
int targetItemIndex{-1};
|
||||
};
|
||||
|
||||
std::optional<Input> inputOverride{};
|
||||
std::vector<Particle> particles{};
|
||||
|
||||
int spawn_delay_get(Entity&);
|
||||
bool spawn_possible_get(Entity&);
|
||||
bool spawn_available_get(Entity&);
|
||||
bool spawn_queue(Entity&, int itemID, bool isLimitIgnored = false);
|
||||
bool spawn_queue(Entity&);
|
||||
void update(Entity&, Entity&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
|
||||
};
|
||||
}
|
||||
|
||||
+12
-72
@@ -14,25 +14,19 @@ namespace game::state::play
|
||||
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();
|
||||
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
fullscreenSlide.update(isOpen && isFullscreen, io.DeltaTime);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
@@ -40,21 +34,12 @@ namespace game::state::play
|
||||
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 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 size = normalSize;
|
||||
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);
|
||||
@@ -74,19 +59,13 @@ namespace game::state::play
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
|
||||
{
|
||||
interact.update(resources, text, character, isFullscreenVisible);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabGather).c_str())))
|
||||
{
|
||||
gather.update(isFullscreen, isFullscreenResizeSettled);
|
||||
interact.update(resources, itemManager, text, character, autofeed, false);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
|
||||
{
|
||||
inventory.update(resources, itemManager, character, autofeed);
|
||||
inventory.update(resources, itemManager, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
@@ -99,7 +78,7 @@ namespace game::state::play
|
||||
|
||||
if (isCheats && WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabCheats).c_str())))
|
||||
{
|
||||
cheats.update(resources, character, inventory);
|
||||
cheats.update(resources, character);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
@@ -119,8 +98,6 @@ namespace game::state::play
|
||||
{
|
||||
auto buttonSize = ImGui::GetContentRegionAvail();
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
auto halfButtonSize = ImVec2(buttonSize.x, buttonSize.y * HALF_MULTIPLIER);
|
||||
auto activeButtonSize = isOpen && !isFullscreenVisible ? halfButtonSize : buttonSize;
|
||||
|
||||
auto tooltip_set = [&](const std::string& tooltip)
|
||||
{
|
||||
@@ -129,65 +106,28 @@ namespace game::state::play
|
||||
ImGui::PopStyleVar();
|
||||
};
|
||||
|
||||
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", activeButtonSize));
|
||||
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", buttonSize));
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
tooltip_set(strings.get(isFullscreenVisible ? Strings::MenuRestoreTooltip
|
||||
: isOpen ? Strings::MenuCloseTooltip
|
||||
: Strings::MenuOpenTooltip));
|
||||
tooltip_set(strings.get(isOpen ? Strings::MenuCloseTooltip : Strings::MenuOpenTooltip));
|
||||
if (isMainResult)
|
||||
{
|
||||
if (isFullscreenVisible)
|
||||
{
|
||||
if (isFullscreen) schema.root()->soundClose.play();
|
||||
isFullscreen = false;
|
||||
}
|
||||
isOpen = !isOpen;
|
||||
if (isOpen)
|
||||
character.data.menuSchema.root()->soundOpen.play();
|
||||
else
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
isFullscreen = false;
|
||||
if (isOpen)
|
||||
schema.root()->soundOpen.play();
|
||||
else
|
||||
schema.root()->soundClose.play();
|
||||
}
|
||||
character.data.menuSchema.root()->soundClose.play();
|
||||
}
|
||||
if (!isOpen && !isMainResult && ImGui::IsItemHovered())
|
||||
{
|
||||
isOpen = true;
|
||||
isFullscreen = false;
|
||||
schema.root()->soundOpen.play();
|
||||
}
|
||||
}
|
||||
|
||||
auto fullscreenCursorPos = ImGui::GetCursorScreenPos();
|
||||
auto isFullscreenResult = false;
|
||||
if (isOpen && !isFullscreenVisible)
|
||||
{
|
||||
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();
|
||||
}
|
||||
character.data.menuSchema.root()->soundOpen.play();
|
||||
}
|
||||
}
|
||||
|
||||
auto direction = !isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
|
||||
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, activeButtonSize, direction,
|
||||
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, buttonSize, 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(MENU_BAR_STYLE_VAR_COUNT);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "../settings_menu.hpp"
|
||||
|
||||
#include "cheats.hpp"
|
||||
#include "menu/gather.hpp"
|
||||
#include "menu/interact.hpp"
|
||||
#include "menu/inventory.hpp"
|
||||
#include "text.hpp"
|
||||
@@ -18,7 +17,6 @@ namespace game::state::play
|
||||
{
|
||||
public:
|
||||
menu::Interact interact;
|
||||
menu::Gather gather;
|
||||
Cheats cheats;
|
||||
menu::Inventory inventory;
|
||||
state::SettingsMenu settingsMenu;
|
||||
@@ -30,14 +28,9 @@ namespace game::state::play
|
||||
#endif
|
||||
|
||||
bool isOpen{true};
|
||||
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};
|
||||
|
||||
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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,619 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
#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();
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
#include "interact.hpp"
|
||||
|
||||
#include "../info.hpp"
|
||||
|
||||
#include "../../../util/color.hpp"
|
||||
#include "../../../util/imgui.hpp"
|
||||
#include "../../../util/imgui/widget.hpp"
|
||||
#include "../../../util/math.hpp"
|
||||
#include "../../../util/string.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <format>
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
@@ -11,25 +19,172 @@ using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
void Interact::update(Resources& resources, Text& text, Entity& character, bool)
|
||||
std::string playtime_format(float totalPlaytimeSeconds)
|
||||
{
|
||||
static constexpr auto PLAYTIME_SECONDS_PER_MINUTE = 60;
|
||||
static constexpr auto PLAYTIME_MINUTES_PER_HOUR = 60;
|
||||
static constexpr auto PLAYTIME_SECONDS_PER_HOUR = PLAYTIME_SECONDS_PER_MINUTE * PLAYTIME_MINUTES_PER_HOUR;
|
||||
|
||||
auto totalSeconds = std::max(0, (int)std::floor(totalPlaytimeSeconds));
|
||||
auto seconds = totalSeconds % PLAYTIME_SECONDS_PER_MINUTE;
|
||||
if (totalSeconds < PLAYTIME_SECONDS_PER_MINUTE) return std::format("{:02}", seconds);
|
||||
|
||||
auto minutes = totalSeconds / PLAYTIME_SECONDS_PER_MINUTE % PLAYTIME_MINUTES_PER_HOUR;
|
||||
if (totalSeconds < PLAYTIME_SECONDS_PER_HOUR) return std::format("{:02}:{:02}", minutes, seconds);
|
||||
|
||||
auto hours = totalSeconds / PLAYTIME_SECONDS_PER_HOUR;
|
||||
return std::format("{:02}:{:02}:{:02}", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
float interact_info_height_get()
|
||||
{
|
||||
return Font::HEADER_2 + ImGui::GetFrameHeightWithSpacing();
|
||||
}
|
||||
|
||||
void interact_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::PopStyleColor();
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::SeparatorText(strings.get(Strings::InteractStats).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Text(strings.get(Strings::InteractTotalCaloriesFormat).c_str(), character.totalCaloriesConsumed);
|
||||
ImGui::Text(strings.get(Strings::InteractTotalItemsSpawnedFormat).c_str(), character.totalItemsSpawned);
|
||||
ImGui::Text(strings.get(Strings::InteractTotalItemsConsumedFormat).c_str(), character.totalItemsConsumed);
|
||||
auto totalPlaytime = playtime_format(character.totalPlaytimeSeconds);
|
||||
ImGui::Text(strings.get(Strings::InteractTotalPlaytimeFormat).c_str(), totalPlaytime.c_str());
|
||||
}
|
||||
|
||||
void Interact::update(Resources& resources, ItemManager& itemManager, Text& text, Entity& character, Autofeed& autofeed,
|
||||
bool)
|
||||
{
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
static constexpr auto ONE_FLOAT = 1.0f;
|
||||
static constexpr auto TOOLTIP_BUFFER_SIZE = 128;
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& strings = character.data.strings;
|
||||
auto* random = dialogue.get(Schema::Element::RANDOM);
|
||||
auto* help = dialogue.get(Schema::Element::HELP);
|
||||
auto* random = dialogue.dialogue_hook_get(Schema::Element::RANDOM);
|
||||
auto* help = dialogue.dialogue_hook_get(Schema::Element::HELP);
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto poolID = character.data.stage_pool_id_get(stage);
|
||||
auto isFeelingAvailable = poolID != -1;
|
||||
auto isDialogueAvailable = random || help || isFeelingAvailable;
|
||||
auto spawnDelay = itemManager.spawn_delay_get(character);
|
||||
auto isSpawnReady = itemManager.spawnDelayTicks <= 0;
|
||||
auto isSpawnAvailable = itemManager.spawn_available_get(character);
|
||||
auto normal_tooltip_set = [&](const char* tooltip)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", tooltip);
|
||||
ImGui::PopFont();
|
||||
};
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
ImGui::SeparatorText(character.data.root()->name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, info_height_get()));
|
||||
interact_info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, interact_info_height_get()));
|
||||
|
||||
auto buttonHeight = ZERO_FLOAT;
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HUGE);
|
||||
buttonHeight += ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::PopFont();
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
buttonHeight += ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::PopFont();
|
||||
if (isDialogueAvailable) buttonHeight += ImGui::GetStyle().ItemSpacing.y + ONE_FLOAT;
|
||||
if (random)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
@@ -52,12 +207,51 @@ namespace game::state::play::menu
|
||||
auto fillerHeight = ImGui::GetContentRegionAvail().y - buttonHeight;
|
||||
if (fillerHeight > ZERO_FLOAT) ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x, fillerHeight));
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HUGE);
|
||||
if (isSpawnReady)
|
||||
{
|
||||
ImGui::BeginDisabled(!isSpawnAvailable);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractSpawnItemButton).c_str(),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
|
||||
itemManager.spawn_queue(character);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto progress = ONE_FLOAT - glm::clamp((float)itemManager.spawnDelayTicks / (float)spawnDelay, ZERO_FLOAT, ONE_FLOAT);
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::ProgressBar(progress, ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight()),
|
||||
strings.get(Strings::InteractSpawnItemButton).c_str());
|
||||
ImGui::EndDisabled();
|
||||
std::array<char, TOOLTIP_BUFFER_SIZE> tooltip{};
|
||||
std::snprintf(tooltip.data(), tooltip.size(), strings.get(Strings::InteractSpawnItemCooldownFormat).c_str(),
|
||||
(float)itemManager.spawnDelayTicks / Entity::UPDATE_RATE);
|
||||
normal_tooltip_set(tooltip.data());
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
auto autofeedLabel = autofeed.enabled_get() ? strings.get(Strings::InventoryDisableAutofeedButton)
|
||||
: strings.get(Strings::InventoryEnableAutofeedButton);
|
||||
auto isAutofeedAvailable = autofeed.available_get(character, itemManager);
|
||||
ImGui::BeginDisabled(!isAutofeedAvailable);
|
||||
if (WIDGET_FX(ImGui::Button(autofeedLabel.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
|
||||
autofeed.toggle();
|
||||
ImGui::EndDisabled();
|
||||
normal_tooltip_set(strings
|
||||
.get(autofeed.enabled_get() ? Strings::InventoryDisableAutofeedTooltip
|
||||
: Strings::InventoryEnableAutofeedTooltip)
|
||||
.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
if (isDialogueAvailable) ImGui::Separator();
|
||||
|
||||
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);
|
||||
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::RANDOM), character);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -66,7 +260,7 @@ namespace game::state::play::menu
|
||||
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);
|
||||
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::HELP), character);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../text.hpp"
|
||||
#include "../autofeed.hpp"
|
||||
#include "../item_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -9,6 +11,6 @@ namespace game::state::play::menu
|
||||
class Interact
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Text&, Entity&, bool isMenuFullscreen);
|
||||
void update(Resources&, ItemManager&, Text&, Entity&, Autofeed&, bool isMenuFullscreen);
|
||||
};
|
||||
}
|
||||
|
||||
+150
-434
@@ -1,213 +1,107 @@
|
||||
#include "inventory.hpp"
|
||||
#include "../autofeed.hpp"
|
||||
#include "../item_display.hpp"
|
||||
#include "../style.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "../../../resource/font.hpp"
|
||||
#include "../../../util/color.hpp"
|
||||
#include "../../../util/imgui.hpp"
|
||||
#include "../../../util/imgui/style.hpp"
|
||||
#include "../../../util/imgui/widget.hpp"
|
||||
#include "../../../util/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
using Strings = resource::xml::Strings;
|
||||
|
||||
namespace
|
||||
{
|
||||
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;
|
||||
}
|
||||
constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
|
||||
constexpr auto DETAIL_CHILD_HEIGHT_MULTIPLIER = 0.5f;
|
||||
constexpr auto WINDOW_PADDING_MULTIPLIER = 2.0f;
|
||||
constexpr auto ZERO_FLOAT = 0.0f;
|
||||
constexpr auto WIDGET_MIN_SIZE = 1.0f;
|
||||
constexpr auto SELECTED_ITEM_STYLE_COLOR_COUNT = 3;
|
||||
constexpr auto TEXT_FORMAT_BUFFER_SIZE = 512;
|
||||
constexpr auto UNDISCOVERED_ITEM_TINT = 0.0f;
|
||||
constexpr auto UNDISCOVERED_ITEM_ALPHA = 0.5f;
|
||||
constexpr auto UNKNOWN_TEXT = "???";
|
||||
|
||||
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);
|
||||
}
|
||||
template <typename... Args> std::string formatted_text_get(const std::string& format, Args... args)
|
||||
{
|
||||
std::array<char, TEXT_FORMAT_BUFFER_SIZE> buffer{};
|
||||
auto size = std::snprintf(buffer.data(), buffer.size(), format.c_str(), args...);
|
||||
if (size < 0) return {};
|
||||
auto length = std::min(static_cast<std::size_t>(size), buffer.size() - 1);
|
||||
return std::string(buffer.data(), length);
|
||||
}
|
||||
|
||||
bool Inventory::can_upgrade(const resource::xml::Schema& schema, int itemID)
|
||||
float wrapped_text_height_get(std::string_view text, float wrapWidth)
|
||||
{
|
||||
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;
|
||||
auto safeWrapWidth = std::max(WIDGET_MIN_SIZE, wrapWidth);
|
||||
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
|
||||
}
|
||||
|
||||
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)
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, Entity& character)
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.update();
|
||||
|
||||
static constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
|
||||
bool isSelectedItemPressed{};
|
||||
int pressedItemQuantity{-1};
|
||||
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto& strings = character.data.strings;
|
||||
auto isSelectedItemPressed = false;
|
||||
|
||||
auto inventory_quantity_get = [&](int itemID) -> int& { return quantity_get(values, schema, itemID); };
|
||||
|
||||
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::Schema::ItemEntry& item, int quantity)
|
||||
{ return is_possible_to_upgrade_get(item) && quantity >= item.upgradeCount; };
|
||||
|
||||
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::Schema::ItemEntry& item)
|
||||
{
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
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.isFlavor)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(),
|
||||
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.isCapacityBonus)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
if (item.digestionBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
|
||||
item.digestionBonus * 60.0f);
|
||||
else if (item.digestionBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
|
||||
item.digestionBonus * 60.0f);
|
||||
}
|
||||
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 (is_possible_to_upgrade_get(item))
|
||||
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 + WIDGET_MIN_SIZE; };
|
||||
|
||||
auto info_section_separator_draw = [&]()
|
||||
{
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
auto width = ImGui::GetContentRegionAvail().x;
|
||||
auto y = cursorScreenPos.y + separatorHeight * 0.5f;
|
||||
auto y = cursorScreenPos.y + separatorHeight * DETAIL_CHILD_HEIGHT_MULTIPLIER;
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(cursorScreenPos.x, y), ImVec2(cursorScreenPos.x + width, y),
|
||||
ImGui::GetColorU32(ImGuiCol_Separator));
|
||||
ImGui::Dummy(ImVec2(0.0f, separatorHeight));
|
||||
ImGui::Dummy(ImVec2(ZERO_FLOAT, separatorHeight));
|
||||
};
|
||||
|
||||
auto item_tooltip_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
|
||||
auto item_unknown_tooltip_draw = [&]()
|
||||
{
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
|
||||
item_header_draw(item, quantity);
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * ITEM_TOOLTIP_WRAP_FONT_MULTIPLIER);
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
|
||||
ImGui::PopFont();
|
||||
ImGui::Separator();
|
||||
item_summary_draw(item);
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", item.description.c_str());
|
||||
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
|
||||
ImGui::PopTextWrapPos();
|
||||
};
|
||||
|
||||
auto item_unknown_draw = [&]()
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::InventoryUnknown).c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
|
||||
ImGui::PopFont();
|
||||
};
|
||||
|
||||
auto wrapped_text_height_get = [](std::string_view text, float wrapWidth)
|
||||
auto item_unknown_detail_draw = [&]()
|
||||
{
|
||||
auto safeWrapWidth = std::max(1.0f, wrapWidth);
|
||||
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
|
||||
};
|
||||
|
||||
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
|
||||
{
|
||||
float height{};
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
height += wrapped_text_height_get(std::format("{} (x{})", item.name, quantity), width);
|
||||
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
|
||||
ImGui::PopFont();
|
||||
info_section_separator_draw();
|
||||
ImGui::TextWrapped("%s", UNKNOWN_TEXT);
|
||||
};
|
||||
|
||||
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
auto height = wrapped_text_height_get(item.name, width);
|
||||
ImGui::PopFont();
|
||||
return height;
|
||||
};
|
||||
|
||||
@@ -215,10 +109,9 @@ namespace game::state::play::menu
|
||||
{
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto durability = item.isDurability ? item.durability : schema.durability;
|
||||
auto itemSpacing = ImGui::GetStyle().ItemSpacing.y;
|
||||
float height{};
|
||||
int lineCount{};
|
||||
auto height = ZERO_FLOAT;
|
||||
auto lineCount = 0;
|
||||
|
||||
auto add_line_height = [&](std::string_view text)
|
||||
{
|
||||
@@ -227,75 +120,63 @@ namespace game::state::play::menu
|
||||
lineCount++;
|
||||
};
|
||||
|
||||
add_line_height(std::format("-- {} ({}) --", category.name, rarity.name));
|
||||
add_line_height(formatted_text_get("-- %s (%s) --", category.name.c_str(), rarity.name.c_str()));
|
||||
if (item.isFlavor)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryFlavorFormat),
|
||||
std::make_format_args(schema.flavors[item.flavorID].name)));
|
||||
add_line_height(formatted_text_get(strings.get(Strings::InventoryFlavorFormat),
|
||||
schema.flavors[item.flavorID].name.c_str()));
|
||||
if (item.isCalories)
|
||||
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)));
|
||||
add_line_height(formatted_text_get(strings.get(Strings::InventoryCaloriesFormat), item.calories));
|
||||
if (item.isCapacityBonus)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryCapacityBonusFormat),
|
||||
std::make_format_args(item.capacityBonus)));
|
||||
add_line_height(formatted_text_get(strings.get(Strings::InventoryCapacityBonusFormat), item.capacityBonus));
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
if (item.digestionBonus > 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
auto digestionRatePenalty = item.digestionBonus * 60.0f;
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
|
||||
std::make_format_args(digestionRatePenalty)));
|
||||
}
|
||||
if (item.digestionBonus > ZERO_FLOAT)
|
||||
add_line_height(formatted_text_get(strings.get(Strings::InventoryDigestionRateBonusFormat),
|
||||
item.digestionBonus * Entity::UPDATE_RATE));
|
||||
else if (item.digestionBonus < ZERO_FLOAT)
|
||||
add_line_height(formatted_text_get(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
|
||||
item.digestionBonus * Entity::UPDATE_RATE));
|
||||
}
|
||||
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)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedPenaltyFormat),
|
||||
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))));
|
||||
return height;
|
||||
};
|
||||
|
||||
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
|
||||
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
|
||||
{
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
auto detailBodyHeight =
|
||||
std::max(item_summary_height_get(item, width), wrapped_text_height_get(item.description, width));
|
||||
return item_header_height_get(item, quantity, width) + separatorHeight + detailBodyHeight * 2.0f +
|
||||
return item_header_height_get(item, width) + separatorHeight + detailBodyHeight * WINDOW_PADDING_MULTIPLIER +
|
||||
separatorHeight;
|
||||
};
|
||||
|
||||
auto item_unknown_height_get = [&](float width)
|
||||
auto item_unknown_details_height_get = [&](float width)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
auto height = wrapped_text_height_get(strings.get(Strings::InventoryUnknown), width);
|
||||
auto headerHeight = wrapped_text_height_get(UNKNOWN_TEXT, width);
|
||||
ImGui::PopFont();
|
||||
return height;
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
return headerHeight + separatorHeight + wrapped_text_height_get(UNKNOWN_TEXT, width);
|
||||
};
|
||||
|
||||
auto info_child_height_get = [&](ImVec2 available, bool isItemSelected, int selectedQuantity)
|
||||
auto item_discovered_get = [&](int itemID)
|
||||
{
|
||||
auto isInfoVisible = isItemSelected || count() == 0;
|
||||
if (!isInfoVisible) return 0.0f;
|
||||
return itemID >= 0 && itemID < (int)itemManager.discoveredItemIDs.size() && itemManager.discoveredItemIDs[itemID];
|
||||
};
|
||||
|
||||
auto infoWidth = std::max(1.0f, available.x - ImGui::GetStyle().WindowPadding.x * 2.0f);
|
||||
auto infoPadding = ImGui::GetStyle().WindowPadding.y * 2.0f;
|
||||
auto item_catalog_visible_get = [&](int itemID)
|
||||
{
|
||||
return itemID >= 0 && itemID < (int)schema.items.size();
|
||||
};
|
||||
|
||||
auto info_child_height_get = [&](ImVec2 available, bool isItemSelected)
|
||||
{
|
||||
if (!isItemSelected && !schema.items.empty()) return ZERO_FLOAT;
|
||||
|
||||
auto infoWidth =
|
||||
std::max(WIDGET_MIN_SIZE, available.x - ImGui::GetStyle().WindowPadding.x * WINDOW_PADDING_MULTIPLIER);
|
||||
auto infoPadding = ImGui::GetStyle().WindowPadding.y * WINDOW_PADDING_MULTIPLIER;
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
auto measuredBodyHeight = 0.0f;
|
||||
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
|
||||
auto measuredBodyHeight = ZERO_FLOAT;
|
||||
|
||||
if (!isItemSelected)
|
||||
{
|
||||
@@ -306,112 +187,57 @@ 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 = item_discovered_get(selectedItemID) ? item_details_height_get(item, infoWidth)
|
||||
: item_unknown_details_height_get(infoWidth);
|
||||
}
|
||||
|
||||
auto buttonChildHeight = 0.0f;
|
||||
if (selectedQuantity > 0)
|
||||
{
|
||||
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
|
||||
auto buttonRowHeight = ImGui::GetFrameHeight();
|
||||
buttonChildHeight = buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f;
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
auto desiredInfoChildHeight = infoPadding + separatorHeight + measuredBodyHeight + buttonChildHeight +
|
||||
(buttonChildHeight > 0.0f ? separatorHeight : 0.0f);
|
||||
auto desiredInfoChildHeight = infoPadding + separatorHeight + measuredBodyHeight;
|
||||
auto maxInfoChildHeight = available.y * INFO_CHILD_HEIGHT_MAX_MULTIPLIER;
|
||||
return glm::clamp(desiredInfoChildHeight, 0.0f, maxInfoChildHeight);
|
||||
};
|
||||
|
||||
auto item_use = [&](int itemID)
|
||||
{
|
||||
auto& item = schema.items[itemID];
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& quantity = inventory_quantity_get(itemID);
|
||||
|
||||
if (quantity <= 0) return;
|
||||
|
||||
if (category.isEdible)
|
||||
{
|
||||
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
|
||||
character.data.itemSchema.root()->soundDispose.play();
|
||||
else
|
||||
{
|
||||
character.data.itemSchema.root()->soundSummon.play();
|
||||
itemManager.queuedItemIDs.emplace_back(itemID);
|
||||
quantity--;
|
||||
if (quantity <= 0) selectedItemID = -1;
|
||||
}
|
||||
}
|
||||
else if (item.isToggleSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
|
||||
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
|
||||
quantity--;
|
||||
}
|
||||
return std::clamp(desiredInfoChildHeight, ZERO_FLOAT, maxInfoChildHeight);
|
||||
};
|
||||
|
||||
auto item_actor_get = [&](int itemID) -> Entity&
|
||||
{
|
||||
if (!actors.contains(itemID)) actors[itemID] = Entity(schema.anm2s[itemID], {}, Entity::SET);
|
||||
|
||||
return actors[itemID];
|
||||
};
|
||||
|
||||
if (!itemManager.returnItemIDs.empty())
|
||||
{
|
||||
for (auto& id : itemManager.returnItemIDs)
|
||||
values[id]++;
|
||||
itemManager.returnItemIDs.clear();
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##Inventory Child", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
{
|
||||
auto inventoryCount = count();
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
|
||||
auto isInfoVisible = isItemSelected || inventoryCount == 0;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
auto toggleButtonHeight = ImGui::GetFrameHeight();
|
||||
ImGui::PopFont();
|
||||
auto toggleChildHeight = toggleButtonHeight + ImGui::GetStyle().WindowPadding.y * 2.0f;
|
||||
auto isItemSelected =
|
||||
selectedItemID >= 0 && selectedItemID < (int)schema.items.size() && item_catalog_visible_get(selectedItemID);
|
||||
auto isInfoVisible = isItemSelected || schema.items.empty();
|
||||
auto toggleSpacing = ImGui::GetStyle().ItemSpacing.y;
|
||||
auto infoChildHeight =
|
||||
info_child_height_get(available, isItemSelected, isItemSelected ? inventory_quantity_get(selectedItemID) : 0);
|
||||
auto infoChildHeight = info_child_height_get(available, isItemSelected);
|
||||
|
||||
auto inventoryChildHeight = std::max(0.0f, available.y - infoChildHeight - toggleChildHeight -
|
||||
(isInfoVisible ? toggleSpacing * 2.0f : toggleSpacing));
|
||||
auto inventoryChildHeight =
|
||||
std::max(ZERO_FLOAT, available.y - infoChildHeight -
|
||||
(isInfoVisible ? toggleSpacing * WINDOW_PADDING_MULTIPLIER : 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))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
bool isAnyInventoryItemHovered{};
|
||||
|
||||
auto isAnyInventoryItemHovered = false;
|
||||
auto size = ImVec2(SIZE, SIZE);
|
||||
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
auto& quantity = inventory_quantity_get(i);
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
if (!item_catalog_visible_get(i)) continue;
|
||||
|
||||
auto isItemColor = item.isColor;
|
||||
|
||||
if (rarity.isHidden && quantity <= 0) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
auto& actor = item_actor_get(i);
|
||||
auto isSelected = selectedItemID == i;
|
||||
if (isItemColor) imgui::style::color_set(item.color);
|
||||
auto isDiscovered = item_discovered_get(i);
|
||||
if (isItemColor && isDiscovered) imgui::style::color_set(item.color);
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
@@ -421,35 +247,26 @@ namespace game::state::play::menu
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selectedColor);
|
||||
}
|
||||
|
||||
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);
|
||||
auto tint = isDiscovered ? ImVec4(1, 1, 1, 1)
|
||||
: ImVec4(UNDISCOVERED_ITEM_TINT, UNDISCOVERED_ITEM_TINT, UNDISCOVERED_ITEM_TINT,
|
||||
UNDISCOVERED_ITEM_ALPHA);
|
||||
auto isPressed = itemButtons[i].button(resources, actor, "##Image Button", size, tint);
|
||||
if (isSelected) ImGui::PopStyleColor(SELECTED_ITEM_STYLE_COLOR_COUNT);
|
||||
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
|
||||
if (isPressed)
|
||||
{
|
||||
isSelectedItemPressed = selectedItemID != i;
|
||||
selectedItemID = i;
|
||||
pressedItemQuantity = quantity;
|
||||
}
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && quantity > 0) item_use(i);
|
||||
if (!isSelected && ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (quantity > 0)
|
||||
item_tooltip_draw(item, quantity);
|
||||
if (isDiscovered)
|
||||
item_tooltip_draw(character, schema, item);
|
||||
else
|
||||
item_unknown_draw();
|
||||
item_unknown_tooltip_draw();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
auto text = std::format("x{}", quantity);
|
||||
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
|
||||
cursorScreenPos.y + size.y - ImGui::GetTextLineHeightWithSpacing());
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
|
||||
text.c_str());
|
||||
ImGui::PopFont();
|
||||
if (isItemColor) style::color_set(resources, character);
|
||||
if (isItemColor && isDiscovered) style::color_set(resources, character);
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
@@ -468,177 +285,76 @@ 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
|
||||
: inventory_quantity_get(selectedItemID))
|
||||
: 0;
|
||||
infoChildHeight = info_child_height_get(available, isItemSelected, selectedQuantity);
|
||||
isItemSelected =
|
||||
selectedItemID >= 0 && selectedItemID < (int)schema.items.size() && item_catalog_visible_get(selectedItemID);
|
||||
isInfoVisible = isItemSelected || schema.items.empty();
|
||||
infoChildHeight = info_child_height_get(available, isItemSelected);
|
||||
infoChildSize = ImVec2(available.x, infoChildHeight);
|
||||
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
|
||||
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].isColor;
|
||||
auto isSelectedItemDiscovered = isItemSelected && item_discovered_get(selectedItemID);
|
||||
auto selectedItemHasColor = isSelectedItemDiscovered && 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);
|
||||
info_section_separator_draw();
|
||||
auto isButtonChildVisible = selectedQuantity > 0;
|
||||
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
|
||||
auto buttonRowHeight = ImGui::GetFrameHeight();
|
||||
auto buttonChildHeight =
|
||||
isButtonChildVisible ? buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f : 0.0f;
|
||||
ImGui::PopFont();
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
|
||||
if (!isItemSelected)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
|
||||
ImGui::PopFont();
|
||||
item_unknown_draw();
|
||||
}
|
||||
else if (isSelectedItemKnown)
|
||||
else
|
||||
{
|
||||
auto& selectedItem = schema.items[selectedItemID];
|
||||
auto contentWidth = std::max(1.0f, ImGui::GetContentRegionAvail().x);
|
||||
auto statsHeight = item_summary_height_get(selectedItem, contentWidth);
|
||||
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 availableInfoContentHeight =
|
||||
std::max(0.0f, ImGui::GetContentRegionAvail().y -
|
||||
(isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
|
||||
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
|
||||
|
||||
item_header_draw(selectedItem, selectedQuantity);
|
||||
info_section_separator_draw();
|
||||
|
||||
auto detailChildHeight = sharedDetailHeight;
|
||||
if (desiredInfoContentHeight > availableInfoContentHeight)
|
||||
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))
|
||||
if (!isSelectedItemDiscovered)
|
||||
{
|
||||
if (isSelectedItemPressed) ImGui::SetScrollY(0.0f);
|
||||
item_summary_draw(selectedItem);
|
||||
item_unknown_detail_draw();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
info_section_separator_draw();
|
||||
|
||||
if (ImGui::BeginChild("##Info Description Child", detailChildSize))
|
||||
else
|
||||
{
|
||||
if (isSelectedItemPressed) ImGui::SetScrollY(0.0f);
|
||||
ImGui::TextWrapped("%s", selectedItem.description.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
auto contentWidth = std::max(WIDGET_MIN_SIZE, ImGui::GetContentRegionAvail().x);
|
||||
auto statsHeight = item_summary_height_get(selectedItem, contentWidth);
|
||||
auto descriptionHeight = wrapped_text_height_get(selectedItem.description, contentWidth);
|
||||
auto sharedDetailHeight = std::max(statsHeight, descriptionHeight);
|
||||
auto headerHeight = item_header_height_get(selectedItem, contentWidth);
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
auto desiredInfoContentHeight =
|
||||
headerHeight + separatorHeight + sharedDetailHeight * WINDOW_PADDING_MULTIPLIER + separatorHeight;
|
||||
auto availableInfoContentHeight = std::max(ZERO_FLOAT, ImGui::GetContentRegionAvail().y);
|
||||
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
|
||||
|
||||
if (isButtonChildVisible)
|
||||
{
|
||||
item_header_draw(selectedItem);
|
||||
info_section_separator_draw();
|
||||
|
||||
if (ImGui::BeginChild("##Info Actions Child", ImVec2(ImGui::GetContentRegionAvail().x, buttonChildHeight),
|
||||
ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar))
|
||||
auto detailChildHeight = sharedDetailHeight;
|
||||
if (desiredInfoContentHeight > availableInfoContentHeight)
|
||||
detailChildHeight =
|
||||
std::max(ZERO_FLOAT, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) *
|
||||
DETAIL_CHILD_HEIGHT_MULTIPLIER);
|
||||
auto detailChildSize = ImVec2(ImGui::GetContentRegionAvail().x, detailChildHeight);
|
||||
|
||||
if (ImGui::BeginChild("##Info Stats Child", detailChildSize))
|
||||
{
|
||||
auto canUseSelectedItem = true;
|
||||
auto canUpgradeSelectedItem = is_able_to_upgrade_get(selectedItem, selectedQuantity);
|
||||
auto rowTwoButtonSize = row_widget_size_get(2);
|
||||
if (isSelectedItemPressed) ImGui::SetScrollY(ZERO_FLOAT);
|
||||
item_summary_draw(character, schema, selectedItem);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto upgrade_item_name_get = [&]() -> std::string
|
||||
{
|
||||
if (!selectedItem.isUpgradeID) return {};
|
||||
return schema.items.at(selectedItem.upgradeID).name;
|
||||
};
|
||||
info_section_separator_draw();
|
||||
|
||||
auto upgrade_tooltip_get = [&](bool isAll)
|
||||
{
|
||||
if (!is_possible_to_upgrade_get(selectedItem)) return strings.get(Strings::InventoryUpgradeNoPath);
|
||||
|
||||
auto upgradeItemName = upgrade_item_name_get();
|
||||
auto upgradeCount = selectedItem.upgradeCount;
|
||||
|
||||
if (!canUpgradeSelectedItem)
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
|
||||
std::make_format_args(upgradeCount, upgradeItemName));
|
||||
|
||||
if (!isAll)
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeOneTemplate),
|
||||
std::make_format_args(upgradeCount, upgradeItemName));
|
||||
|
||||
auto upgradedCount = selectedQuantity / upgradeCount;
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeAllTemplate),
|
||||
std::make_format_args(upgradeCount, upgradedCount, upgradeItemName));
|
||||
};
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
|
||||
ImGui::BeginDisabled(!canUseSelectedItem);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventorySpawnButton).c_str(),
|
||||
{ImGui::GetContentRegionAvail().x, 0})))
|
||||
item_use(selectedItemID);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!canUpgradeSelectedItem);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
|
||||
upgrade(schema, selectedItemID, false);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(false).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(), rowTwoButtonSize)))
|
||||
upgrade(schema, selectedItemID, true);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(true).c_str());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopFont();
|
||||
if (ImGui::BeginChild("##Info Description Child", detailChildSize))
|
||||
{
|
||||
if (isSelectedItemPressed) ImGui::SetScrollY(ZERO_FLOAT);
|
||||
ImGui::TextWrapped("%s", selectedItem.description.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
else
|
||||
item_unknown_draw();
|
||||
|
||||
if (selectedItemHasColor) style::color_set(resources, character);
|
||||
}
|
||||
if (isInfoVisible) ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
int Inventory::count()
|
||||
{
|
||||
int count{};
|
||||
for (auto& [type, quantity] : values)
|
||||
count += quantity;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,10 @@ namespace game::state::play::menu
|
||||
public:
|
||||
static constexpr auto SIZE = 96.0f;
|
||||
|
||||
std::map<int, int> values{};
|
||||
std::unordered_map<int, Entity> actors{};
|
||||
std::unordered_map<int, util::imgui::widget::EntityButton> itemButtons{};
|
||||
int selectedItemID{-1};
|
||||
|
||||
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();
|
||||
void update(Resources&, ItemManager&, Entity&);
|
||||
};
|
||||
}
|
||||
|
||||
+74
-12
@@ -24,6 +24,30 @@ namespace game::state::play
|
||||
return element.type == resource::xml::Schema::Element::EFFECT;
|
||||
}
|
||||
|
||||
bool dialogue_scene_effect_get(const resource::xml::Schema& dialogue, const resource::xml::Schema::Element& entry)
|
||||
{
|
||||
for (auto child : entry.children)
|
||||
{
|
||||
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
|
||||
auto& effect = dialogue.elements[child];
|
||||
if (dialogue_effect_is(effect) && effect.typeString == "Scene") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dialogue_silent_effect_get(const resource::xml::Schema& dialogue, const resource::xml::Schema::Element& entry,
|
||||
int index)
|
||||
{
|
||||
for (auto child : entry.children)
|
||||
{
|
||||
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
|
||||
auto& effect = dialogue.elements[child];
|
||||
if (dialogue_effect_is(effect) && effect.typeString == "Silent" && effect.start <= index && index < effect.end)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr auto OSCILLATION_AMPLITUDE_DEFAULT = 2.0f;
|
||||
constexpr auto OSCILLATION_FREQUENCY_DEFAULT = 1.0f;
|
||||
constexpr auto SCREEN_SHAKE_MAGNITUDE_DEFAULT = 0.03f;
|
||||
@@ -95,11 +119,15 @@ namespace game::state::play
|
||||
}
|
||||
}
|
||||
|
||||
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible)
|
||||
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible,
|
||||
bool isSequenceAdvance)
|
||||
{
|
||||
if (!dialogueEntry) return;
|
||||
this->entry = dialogueEntry;
|
||||
|
||||
if (!isSequenceAdvance) isSceneActive = false;
|
||||
if (!isSceneActive && !isSequenceAdvance)
|
||||
isSceneActive = dialogue_scene_effect_get(character.data.dialogue, *dialogueEntry);
|
||||
isFinished = false;
|
||||
isTerminal = dialogue_entry_terminal_get(character.data.dialogue, *dialogueEntry);
|
||||
index = 0;
|
||||
@@ -108,13 +136,19 @@ namespace game::state::play
|
||||
currentDelay = 0;
|
||||
isStartEffectsApplied = false;
|
||||
isBlipQueued = false;
|
||||
isAnimationEntryStarted = false;
|
||||
animationEntryTimePrevious = 0.0f;
|
||||
time = 0.0f;
|
||||
isEnabled = true;
|
||||
if (!dialogueEntry->animation.empty())
|
||||
{
|
||||
auto interrupt = isSequenceAdvance ? Entity::Interrupt::ALWAYS
|
||||
: isInterruptible ? Entity::Interrupt::IF_ALLOWED
|
||||
: Entity::Interrupt::NEVER;
|
||||
character.play({.animation = dialogueEntry->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.interrupt = isInterruptible ? Entity::Interrupt::IF_ALLOWED
|
||||
: Entity::Interrupt::NEVER});
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.interrupt = interrupt});
|
||||
}
|
||||
auto* dialogueRoot = character.data.dialogue.root();
|
||||
baseDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
|
||||
currentDelay = baseDelay;
|
||||
@@ -135,10 +169,10 @@ namespace game::state::play
|
||||
.mode = Entity::PLAY_FORCE});
|
||||
}
|
||||
}
|
||||
if (dialogueEntry->text.empty())
|
||||
if (dialogueEntry->text.empty() && dialogueEntry->next.empty())
|
||||
isEnabled = false;
|
||||
else
|
||||
character.isTalking = true;
|
||||
else if (!dialogueEntry->text.empty())
|
||||
character.isTalking = !dialogue_silent_effect_get(character.data.dialogue, *dialogueEntry, 0);
|
||||
}
|
||||
|
||||
void Text::update(Entity& character, Canvas& canvas)
|
||||
@@ -191,11 +225,14 @@ namespace game::state::play
|
||||
}
|
||||
}
|
||||
auto delayCharacter = dialogue_delay_character_get(character.data.dialogue, entry->text, index - 1, currentDelay);
|
||||
auto isSilent = dialogue_silent_effect_get(character.data.dialogue, *entry, index - 1);
|
||||
currentDelay = delayCharacter.delay;
|
||||
character.isTalking = !delayCharacter.isDisableTalk;
|
||||
character.isTalking = !delayCharacter.isDisableTalk && !isSilent;
|
||||
auto blipDelay = dialogueRoot ? dialogueRoot->blipDelayTicks : 0;
|
||||
auto isBlipStep = blipDelay > 0 && index % blipDelay == 0;
|
||||
if (delayCharacter.isDisableTalk)
|
||||
if (isSilent)
|
||||
isBlipQueued = false;
|
||||
else if (delayCharacter.isDisableTalk)
|
||||
{
|
||||
if (isBlipStep) isBlipQueued = true;
|
||||
}
|
||||
@@ -210,10 +247,31 @@ namespace game::state::play
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
isBlipQueued = false;
|
||||
if (isTerminal) isSceneActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entry && entry->text.empty())
|
||||
{
|
||||
auto* animation = character.animation_get();
|
||||
auto isAnimationEntryReady = entry->animation.empty() || !animation || character.state == Entity::STOPPED ||
|
||||
(!isAnimationEntryStarted && animation->frameNum <= 1) ||
|
||||
(animation->isLoop && isAnimationEntryStarted &&
|
||||
character.time < animationEntryTimePrevious);
|
||||
isAnimationEntryStarted = true;
|
||||
animationEntryTimePrevious = character.time;
|
||||
if (!entry->next.empty() && isAnimationEntryReady)
|
||||
set(character.data.dialogue.dialogue_entry_get(*entry), character, true, true);
|
||||
else if (entry->next.empty())
|
||||
{
|
||||
isFinished = true;
|
||||
isEnabled = false;
|
||||
if (isTerminal) isSceneActive = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& menuSchema = character.data.menuSchema;
|
||||
|
||||
@@ -326,7 +384,7 @@ namespace game::state::play
|
||||
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);
|
||||
set(dialogue.dialogue_entry_get(*branch), character, true, true);
|
||||
|
||||
ImGui::SetItemTooltip("%s", label);
|
||||
ImGui::PopID();
|
||||
@@ -337,7 +395,7 @@ namespace game::state::play
|
||||
{
|
||||
auto* next = dialogue.dialogue_entry_get(*choices.front());
|
||||
if (next) menuSchema.root()->soundSelect.play();
|
||||
set(next, character);
|
||||
set(next, character, true, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -364,13 +422,14 @@ namespace game::state::play
|
||||
{
|
||||
auto* next = dialogue.dialogue_entry_get(*entry);
|
||||
if (next) menuSchema.root()->soundSelect.play();
|
||||
set(next, character);
|
||||
set(next, character, true, true);
|
||||
}
|
||||
}
|
||||
else if (isAdvance)
|
||||
{
|
||||
isEnabled = false;
|
||||
entry = nullptr;
|
||||
isSceneActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,6 +465,7 @@ namespace game::state::play
|
||||
index = target;
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
if (isTerminal) isSceneActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,6 +481,7 @@ namespace game::state::play
|
||||
{
|
||||
isEnabled = false;
|
||||
entry = nullptr;
|
||||
isSceneActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,4 +489,5 @@ namespace game::state::play
|
||||
bool Text::is_interruptible() const { return !entry || isTerminal; }
|
||||
bool Text::is_finished() const { return isFinished; }
|
||||
bool Text::is_terminal() const { return isTerminal; }
|
||||
bool Text::is_scene_active() const { return isSceneActive; }
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace game::state::play
|
||||
bool isTerminal{};
|
||||
bool isStartEffectsApplied{};
|
||||
bool isBlipQueued{};
|
||||
bool isSceneActive{};
|
||||
bool isAnimationEntryStarted{};
|
||||
float animationEntryTimePrevious{};
|
||||
|
||||
public:
|
||||
static constexpr auto LIFETIME = 10.0f;
|
||||
@@ -28,10 +31,11 @@ namespace game::state::play
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
|
||||
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true);
|
||||
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true, bool isSequenceAdvance = false);
|
||||
void update(Entity&, Canvas&);
|
||||
bool is_interruptible() const;
|
||||
bool is_finished() const;
|
||||
bool is_terminal() const;
|
||||
bool is_scene_active() const;
|
||||
};
|
||||
}
|
||||
|
||||
+35
-38
@@ -10,37 +10,40 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
constexpr auto ZOOM_LADDER_EPSILON = 0.01f;
|
||||
constexpr auto ZOOM_WHEEL_ZERO = 0.0f;
|
||||
constexpr auto ZERO_FLOAT = 0.0f;
|
||||
constexpr auto ONE_INT = 1;
|
||||
|
||||
float zoom_ladder_level_get(int index)
|
||||
int zoom_ladder_index_get(float zoom)
|
||||
{
|
||||
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)
|
||||
auto result = 0;
|
||||
auto distance = std::abs(zoom - World::ZOOM_LADDER.front());
|
||||
for (int index = 1; index < (int)World::ZOOM_LADDER.size(); index++)
|
||||
{
|
||||
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;
|
||||
auto nextDistance = std::abs(zoom - World::ZOOM_LADDER[index]);
|
||||
if (nextDistance >= distance) continue;
|
||||
distance = nextDistance;
|
||||
result = index;
|
||||
}
|
||||
|
||||
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;
|
||||
return result;
|
||||
}
|
||||
|
||||
float zoom_ladder_zoom_get(float zoom, float wheel)
|
||||
bool bounds_is_valid(glm::vec4 value)
|
||||
{
|
||||
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);
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
|
||||
value.z > value.x && value.w > value.y;
|
||||
}
|
||||
|
||||
void World::bounds_set(const glm::vec4& newBounds)
|
||||
{
|
||||
bounds = bounds_is_valid(newBounds) ? newBounds : BOUNDS;
|
||||
size = {bounds.z - bounds.x, bounds.w - bounds.y};
|
||||
}
|
||||
|
||||
void World::zoom_index_set(Canvas& canvas)
|
||||
{
|
||||
zoomIndex = zoom_ladder_index_get(canvas.zoom);
|
||||
canvas.zoom = ZOOM_LADDER[zoomIndex];
|
||||
}
|
||||
|
||||
void World::set(Entity& character, Canvas& canvas, Focus focus)
|
||||
@@ -82,14 +85,17 @@ namespace game::state::play
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationZoom.get()) cursor.play({*animation});
|
||||
|
||||
zoom = zoom_ladder_zoom_get(zoom, io.MouseWheel);
|
||||
auto direction = io.MouseWheel > ZOOM_WHEEL_ZERO ? ONE_INT : -ONE_INT;
|
||||
zoomIndex = std::clamp(zoomIndex + direction, 0, (int)ZOOM_LADDER.size() - ONE_INT);
|
||||
zoom = ZOOM_LADDER[zoomIndex];
|
||||
|
||||
auto zoomFactorAfter = math::to_unit(zoom);
|
||||
pan = cursorWorld - (cursorPos / zoomFactorAfter);
|
||||
}
|
||||
}
|
||||
|
||||
zoom = glm::clamp(ZOOM_MIN, zoom, ZOOM_MAX);
|
||||
zoomIndex = zoom_ladder_index_get(zoom);
|
||||
zoom = ZOOM_LADDER[zoomIndex];
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Home)) character_focus(character, canvas, focus);
|
||||
}
|
||||
@@ -98,37 +104,28 @@ namespace game::state::play
|
||||
{
|
||||
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
|
||||
static constexpr float INFO_HEIGHT_MULTIPLIER = 4.0f;
|
||||
static constexpr float PADDING = 100.0f;
|
||||
|
||||
auto rect = character.rect();
|
||||
|
||||
if (!std::isfinite(rect.x) || !std::isfinite(rect.y) || !std::isfinite(rect.z) || !std::isfinite(rect.w) ||
|
||||
rect.z <= 0.0f || rect.w <= 0.0f)
|
||||
rect.z <= ZERO_FLOAT || rect.w <= ZERO_FLOAT)
|
||||
return;
|
||||
|
||||
rect = {rect.x - PADDING * 0.5f, rect.y - PADDING * 0.5f, rect.z + PADDING, rect.w + PADDING};
|
||||
|
||||
auto infoHeightPixels =
|
||||
ImGui::GetTextLineHeightWithSpacing() * INFO_HEIGHT_MULTIPLIER + ImGui::GetStyle().WindowPadding.y * 2.0f;
|
||||
auto usableHeightPixels = std::max(1.0f, (float)canvas.size.y - infoHeightPixels);
|
||||
|
||||
auto zoomFactor = std::min((float)canvas.size.x / rect.z, usableHeightPixels / rect.w);
|
||||
auto zoomFactor = std::min((float)canvas.size.x / rect.z, (float)canvas.size.y / rect.w);
|
||||
canvas.zoom = glm::clamp(ZOOM_MIN, math::to_percent(zoomFactor), ZOOM_MAX);
|
||||
zoom_index_set(canvas);
|
||||
zoomFactor = math::to_unit(canvas.zoom);
|
||||
|
||||
auto rectCenter = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f);
|
||||
auto viewSizeWorld = glm::vec2(canvas.size) / zoomFactor;
|
||||
auto infoHeightWorld = infoHeightPixels / zoomFactor;
|
||||
canvas.pan = rectCenter - vec2(viewSizeWorld.x * 0.5f, (viewSizeWorld.y + infoHeightWorld) * 0.5f);
|
||||
canvas.pan = rectCenter - viewSizeWorld * 0.5f;
|
||||
auto menuWidthWorld = (canvas.size.x * MENU_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
auto toolsWidthWorld = (canvas.size.x * TOOLS_WIDTH_MULTIPLIER) / zoomFactor;
|
||||
|
||||
if (focus == Focus::MENU || focus == Focus::MENU_TOOLS) canvas.pan.x += menuWidthWorld * 0.5f;
|
||||
if (focus == Focus::TOOLS || focus == Focus::MENU_TOOLS) canvas.pan.x -= toolsWidthWorld * 0.5f;
|
||||
|
||||
auto panMin = glm::vec2(0.0f, 0.0f);
|
||||
auto panMax = glm::max(glm::vec2(0.0f), SIZE - viewSizeWorld);
|
||||
canvas.pan = glm::clamp(panMin, canvas.pan, panMax);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../render/canvas.hpp"
|
||||
#include <array>
|
||||
|
||||
#include "../../entity.hpp"
|
||||
#include "../../render/canvas.hpp"
|
||||
|
||||
#include "character_manager.hpp"
|
||||
#include "item_manager.hpp"
|
||||
@@ -11,13 +13,13 @@ namespace game::state::play
|
||||
class World
|
||||
{
|
||||
public:
|
||||
static constexpr auto ZOOM_MIN = 50.0f;
|
||||
static constexpr auto ZOOM_MIN = 100.0f;
|
||||
static constexpr auto ZOOM_BASE = 100.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 =
|
||||
glm::vec4(SIZE.x * 0.05, SIZE.y * 0.05, SIZE.x - (SIZE.x * 0.05f), SIZE.y - (SIZE.y * 0.05f));
|
||||
static constexpr auto BOUNDS = glm::vec4{96, 54, 1824, 1026};
|
||||
static constexpr std::array<float, 10> ZOOM_LADDER{100.0f, 115.0f, 130.0f, 150.0f, 175.0f,
|
||||
200.0f, 235.0f, 275.0f, 325.0f, 400.0f};
|
||||
|
||||
enum Focus
|
||||
{
|
||||
@@ -27,6 +29,12 @@ namespace game::state::play
|
||||
TOOLS
|
||||
};
|
||||
|
||||
glm::vec2 size{SIZE};
|
||||
glm::vec4 bounds{BOUNDS};
|
||||
int zoomIndex{};
|
||||
|
||||
void bounds_set(const glm::vec4& newBounds);
|
||||
void zoom_index_set(Canvas& canvas);
|
||||
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);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace game::state::select
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(game::util::color::GRAY)));
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", character->description.c_str());
|
||||
ImGui::PopFont();
|
||||
@@ -86,7 +86,7 @@ namespace game::state::select
|
||||
if (ImGui::BeginTabItem("Credits"))
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(game::util::color::GRAY)));
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
if (!character->credits.empty())
|
||||
ImGui::TextWrapped("%s", character->credits.c_str());
|
||||
|
||||
@@ -13,4 +13,4 @@ namespace game::state::select
|
||||
|
||||
void update(Resources&, int characterIndex);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user