lfs, current build

This commit is contained in:
2026-08-08 15:20:37 -04:00
parent 2efed2a443
commit a5904b917b
397 changed files with 215918 additions and 2458 deletions
+100 -142
View File
@@ -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
View File
@@ -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();
};
};
+75 -14
View File
@@ -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);
}
}
+7
View File
@@ -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
View File
@@ -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; }
+2 -1
View File
@@ -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;
};
+6 -8
View File
@@ -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);
}
}
+1 -25
View File
@@ -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 -2
View File
@@ -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&);
};
}
-149
View File
@@ -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);
}
}
-18
View File
@@ -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);
};
}
+62
View File
@@ -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();
}
}
+12
View File
@@ -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
View File
@@ -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]);
+15 -5
View File
@@ -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
View File
@@ -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);
-7
View File
@@ -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&);
};
}
-619
View File
@@ -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);
}
}
-77
View File
@@ -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();
};
}
+202 -8
View File
@@ -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();
}
+3 -1
View File
@@ -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
View File
@@ -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;
}
}
+1 -6
View File
@@ -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
View File
@@ -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; }
}
+5 -1
View File
@@ -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
View File
@@ -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);
}
}
+13 -5
View File
@@ -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);
+2 -2
View File
@@ -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());
+1 -1
View File
@@ -13,4 +13,4 @@ namespace game::state::select
void update(Resources&, int characterIndex);
};
}
}