Whatever The Fuck Man
This commit is contained in:
+175
-126
@@ -21,12 +21,12 @@ namespace game::state
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
|
||||
int durabilityMax)
|
||||
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
|
||||
int durability, int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
|
||||
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
|
||||
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
}
|
||||
}
|
||||
@@ -35,19 +35,29 @@ namespace game::state
|
||||
{
|
||||
if (!isWindows) return World::CENTER;
|
||||
|
||||
return menu.isOpen && tools.isOpen ? World::MENU_TOOLS
|
||||
: menu.isOpen ? World::MENU
|
||||
: tools.isOpen ? World::TOOLS
|
||||
: World::CENTER;
|
||||
auto isToolsOpen = tools.isOpen && !menu.is_fullscreen_visible_get();
|
||||
return menu.isOpen && isToolsOpen ? World::MENU_TOOLS
|
||||
: menu.isOpen ? World::MENU
|
||||
: isToolsOpen ? World::TOOLS
|
||||
: World::CENTER;
|
||||
}
|
||||
|
||||
bool Play::cursor_enabled_get() const
|
||||
{
|
||||
auto* root = character.data.cursorSchema.root();
|
||||
return root && root->type == Schema::Element::CURSOR;
|
||||
}
|
||||
|
||||
void Play::start_sequence_begin()
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
if (!dialogue.start.is_valid()) return;
|
||||
auto* start = dialogue.get(Schema::Element::START);
|
||||
if (!start) return;
|
||||
|
||||
character.queue_play({.animation = dialogue.start.animation, .isInterruptible = false});
|
||||
character.tick();
|
||||
character.play({.animation = start->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.interrupt = Entity::Interrupt::NEVER});
|
||||
character.update();
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
isWindows = false;
|
||||
@@ -59,7 +69,7 @@ namespace game::state
|
||||
void Play::end_sequence_begin()
|
||||
{
|
||||
auto& dialogue = character.data.dialogue;
|
||||
if (!dialogue.end.is_valid()) return;
|
||||
if (!dialogue.get(Schema::Element::END)) return;
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
@@ -71,68 +81,92 @@ namespace game::state
|
||||
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
|
||||
{
|
||||
auto& data = resources.character_get(selectedCharacterIndex);
|
||||
auto& saveData = data.save;
|
||||
auto* saveRoot = data.save.root();
|
||||
auto isSaveValid = saveRoot && saveRoot->type == Schema::Element::SAVE;
|
||||
auto* saveCharacter = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::CHARACTER) : nullptr;
|
||||
auto* saveInventory = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::INVENTORY) : nullptr;
|
||||
auto* saveItems = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ITEMS) : nullptr;
|
||||
auto* savePan = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::PAN) : nullptr;
|
||||
auto* saveZoom = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ZOOM) : nullptr;
|
||||
auto& itemSchema = data.itemSchema;
|
||||
auto& dialogue = data.dialogue;
|
||||
auto& menuSchema = data.menuSchema;
|
||||
auto* characterRoot = data.root();
|
||||
this->characterIndex = selectedCharacterIndex;
|
||||
resources.last_character_save(selectedCharacterIndex);
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
|
||||
character =
|
||||
entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
|
||||
character = Entity(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
|
||||
character.digestionRate =
|
||||
glm::clamp(character.digestionRate, (float)data.digestionRateMin, (float)data.digestionRateMax);
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)data.eatSpeedMin, (float)data.eatSpeedMax);
|
||||
character.capacity = glm::clamp(character.capacity, (float)data.capacityMin, (float)data.capacityMax);
|
||||
glm::clamp(character.digestionRate, characterRoot->digestionRateMin, characterRoot->digestionRateMax);
|
||||
character.eatSpeed =
|
||||
glm::clamp(character.eatSpeed, characterRoot->eatSpeedMinMultiplier, characterRoot->eatSpeedMaxMultiplier);
|
||||
character.capacity =
|
||||
glm::clamp(character.capacity, characterRoot->capacityMinCalories, characterRoot->capacityMaxCalories);
|
||||
|
||||
auto* alternateSpritesheet = data.alternate_spritesheet();
|
||||
auto isAlternateSpritesheet =
|
||||
(game == NEW_GAME && math::random_percent_roll(data.alternateSpritesheet.chanceOnNewGame));
|
||||
alternateSpritesheet && game == NEW_GAME && math::random_percent_roll(alternateSpritesheet->chanceOnNewGame);
|
||||
|
||||
if (isAlternateSpritesheet || saveData.isAlternateSpritesheet)
|
||||
if (isAlternateSpritesheet || (isSaveValid && saveRoot->isAlternateSpritesheet))
|
||||
{
|
||||
character.spritesheet_set(entity::Character::ALTERNATE);
|
||||
if (game == NEW_GAME) character.data.alternateSpritesheet.sound.play();
|
||||
character.spritesheet_set(Entity::ALTERNATE);
|
||||
if (game == NEW_GAME && alternateSpritesheet) alternateSpritesheet->soundEntry.sound.play();
|
||||
}
|
||||
|
||||
character.totalCaloriesConsumed = saveData.totalCaloriesConsumed;
|
||||
character.totalFoodItemsEaten = saveData.totalFoodItemsEaten;
|
||||
character.totalCaloriesConsumed = saveCharacter ? saveCharacter->totalCaloriesConsumed : 0;
|
||||
character.totalFoodItemsEaten = saveCharacter ? saveCharacter->totalFoodItemsEaten : 0;
|
||||
autofeed = play::Autofeed{};
|
||||
characterManager = CharacterManager{};
|
||||
particleManager = ParticleManager{};
|
||||
areaManager = AreaManager{};
|
||||
|
||||
cursor = entity::Cursor(character.data.cursorSchema.anm2);
|
||||
cursor.interactTypeID = character.data.interactTypeNames.empty() ? -1 : 0;
|
||||
cursor = Entity{};
|
||||
cursor.entityType = CURSOR;
|
||||
cursor.interactTypeID = character.data.interact_type_names_get().empty() ? -1 : 0;
|
||||
autofeedCursor = Entity{};
|
||||
autofeedCursor.entityType = AUTOCURSOR;
|
||||
autofeedCursor.interactTypeID = cursor.interactTypeID;
|
||||
autofeedCursor.position = cursor.position;
|
||||
autofeedCursor.isVisible = false;
|
||||
if (cursor_enabled_get())
|
||||
{
|
||||
cursor = Entity(character.data.cursorSchema.baseAnm2);
|
||||
cursor.entityType = CURSOR;
|
||||
cursor.interactTypeID = character.data.interact_type_names_get().empty() ? -1 : 0;
|
||||
autofeedCursor = Entity(character.data.cursorSchema.baseAnm2);
|
||||
autofeedCursor.entityType = AUTOCURSOR;
|
||||
autofeedCursor.interactTypeID = cursor.interactTypeID;
|
||||
autofeedCursor.position = cursor.position;
|
||||
autofeedCursor.isVisible = false;
|
||||
}
|
||||
|
||||
menu.inventory = play::menu::Inventory{};
|
||||
for (auto& [id, quantity] : saveData.inventory)
|
||||
for (auto* item : saveInventory ? data.save.children_get(*saveInventory, Schema::Element::ITEM)
|
||||
: std::vector<Schema::Element*>{})
|
||||
{
|
||||
if (quantity == 0) continue;
|
||||
menu.inventory.values[id] = quantity;
|
||||
if (item->quantity == 0) continue;
|
||||
menu.inventory.values[item->id] = item->quantity;
|
||||
}
|
||||
|
||||
itemManager = ItemManager{};
|
||||
for (auto& item : saveData.items)
|
||||
for (auto* item :
|
||||
saveItems ? data.save.children_get(*saveItems, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
|
||||
{
|
||||
auto& anm2 = itemSchema.anm2s.at(item.id);
|
||||
auto& schemaItem = itemSchema.items.at(item.id);
|
||||
auto durabilityMax = schemaItem.durability.value_or(itemSchema.durability);
|
||||
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item.durability, durabilityMax);
|
||||
auto& saveItem = itemSchema.anm2s.at(item.id);
|
||||
itemManager.items.emplace_back(saveItem, item.position, item.id, item.durability, animationIndex, item.velocity,
|
||||
item.rotation);
|
||||
auto& anm2 = itemSchema.anm2s.at(item->id);
|
||||
auto& schemaItem = itemSchema.items.at(item->id);
|
||||
auto durabilityMax = schemaItem.isDurability ? schemaItem.durability : itemSchema.durability;
|
||||
auto animationIndex = durability_animation_index_get(itemSchema, anm2, item->durability, durabilityMax);
|
||||
auto& saveItem = itemSchema.anm2s.at(item->id);
|
||||
itemManager.items.emplace_back(saveItem, item->position, item->id, item->durability, animationIndex,
|
||||
item->velocity, item->rotation);
|
||||
}
|
||||
|
||||
imgui::style::widget_set(menuSchema.rounding);
|
||||
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
|
||||
imgui::widget::sounds_set(&menuSchema.root()->soundHover, &menuSchema.root()->soundSelect);
|
||||
play::style::color_set(resources, character);
|
||||
|
||||
menu.arcade = play::menu::Arcade(character);
|
||||
menu.arcade.skillCheck.totalPlays = saveData.skillCheck.totalPlays;
|
||||
menu.arcade.skillCheck.highScore = saveData.skillCheck.highScore;
|
||||
menu.arcade.skillCheck.bestCombo = saveData.skillCheck.bestCombo;
|
||||
menu.arcade.skillCheck.gradeCounts = saveData.skillCheck.gradeCounts;
|
||||
menu.arcade.skillCheck.isHighScoreAchieved = saveData.skillCheck.highScore > 0 ? true : false;
|
||||
menu.arcade.orbit.highScore = saveData.orbit.highScore;
|
||||
|
||||
text.entry = nullptr;
|
||||
text.isEnabled = false;
|
||||
|
||||
@@ -142,7 +176,7 @@ namespace game::state
|
||||
menu.isCheats = false;
|
||||
#endif
|
||||
|
||||
isPostgame = saveData.isPostgame;
|
||||
isPostgame = isSaveValid && saveRoot->isPostgame;
|
||||
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
|
||||
if (isPostgame) menu.isCheats = true;
|
||||
|
||||
@@ -150,12 +184,16 @@ namespace game::state
|
||||
|
||||
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
|
||||
|
||||
character.queue_idle_animation();
|
||||
character.tick();
|
||||
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
|
||||
character.update();
|
||||
areaManager.set(character);
|
||||
worldCanvas.size_set(imgui::to_vec2(ImGui::GetMainViewport()->Size));
|
||||
world.set(character, worldCanvas, focus_get());
|
||||
if (savePan) worldCanvas.pan = savePan->position;
|
||||
if (saveZoom && saveZoom->zoom >= World::ZOOM_MIN)
|
||||
worldCanvas.zoom = glm::clamp(World::ZOOM_MIN, saveZoom->zoom, World::ZOOM_MAX);
|
||||
|
||||
if (game == NEW_GAME && dialogue.start.is_valid()) start_sequence_begin();
|
||||
if (game == NEW_GAME && dialogue.get(Schema::Element::START)) start_sequence_begin();
|
||||
|
||||
if (isPostgame)
|
||||
{
|
||||
@@ -173,25 +211,13 @@ namespace game::state
|
||||
|
||||
void Play::exit(Resources& resources)
|
||||
{
|
||||
imgui::style::color_set(resources.settings.color);
|
||||
imgui::style::color_set(resources.settings.root()->color);
|
||||
imgui::style::widget_set();
|
||||
imgui::widget::sounds_set(nullptr, nullptr);
|
||||
ImGui::GetIO().FontDefault = resources.font.get();
|
||||
save(resources);
|
||||
}
|
||||
|
||||
void Play::tick(Resources&)
|
||||
{
|
||||
character.tick();
|
||||
cursor.tick();
|
||||
menu.tick();
|
||||
toasts.tick();
|
||||
text.tick(character);
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
item.tick();
|
||||
}
|
||||
|
||||
void Play::update(Resources& resources)
|
||||
{
|
||||
static constexpr std::array<ImGuiKey, 10> CHEAT_CODE = {
|
||||
@@ -200,10 +226,16 @@ namespace game::state
|
||||
static constexpr std::array<ImGuiKey, 6> CHEAT_INPUT_KEYS = {
|
||||
ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A};
|
||||
static constexpr auto CHEAT_CODE_INPUT_TIME_SECONDS = 5.0;
|
||||
static constexpr auto UI_ALPHA_MAX = 1.0f;
|
||||
|
||||
auto focus = focus_get();
|
||||
auto& dialogue = character.data.dialogue;
|
||||
cursor.isVisible = true;
|
||||
auto isCursorEnabled = cursor_enabled_get();
|
||||
if (isCursorEnabled)
|
||||
{
|
||||
cursor.isVisible = true;
|
||||
autofeedCursor.isVisible = false;
|
||||
}
|
||||
|
||||
if (!menu.isCheats)
|
||||
{
|
||||
@@ -233,7 +265,7 @@ namespace game::state
|
||||
cheatCodeIndex = 0;
|
||||
cheatCodeStartTime = 0.0;
|
||||
toasts.push(character.data.strings.get(Strings::ToastCheatsUnlocked));
|
||||
character.data.menuSchema.sounds.cheatsActivated.play();
|
||||
character.data.menuSchema.root()->soundCheatsActivated.play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,32 +278,19 @@ namespace game::state
|
||||
|
||||
if (isWindows)
|
||||
{
|
||||
menu.update(resources, itemManager, character, cursor, text, worldCanvas);
|
||||
tools.update(character, cursor, world, focus, worldCanvas);
|
||||
info.update(resources, character);
|
||||
auto isToolsDisabled = menu.is_fullscreen_visible_get();
|
||||
auto uiAlpha = UI_ALPHA_MAX - menu.fullscreen_alpha_get();
|
||||
tools.update(character, cursor, world, focus, worldCanvas, isToolsDisabled, uiAlpha);
|
||||
info.update(resources, character, uiAlpha);
|
||||
menu.update(resources, itemManager, character, text, autofeed);
|
||||
toasts.update();
|
||||
|
||||
#if DEBUG
|
||||
if (menu.isDebugOpen && ImGui::IsKeyPressed(ImGuiKey_F8, false)) end_sequence_begin();
|
||||
#endif
|
||||
|
||||
if (menu.debug.isStartSequenceRequested)
|
||||
{
|
||||
menu.debug.isStartSequenceRequested = false;
|
||||
start_sequence_begin();
|
||||
}
|
||||
if (menu.debug.isEndSequenceRequested)
|
||||
{
|
||||
menu.debug.isEndSequenceRequested = false;
|
||||
end_sequence_begin();
|
||||
}
|
||||
}
|
||||
|
||||
auto isEndSequenceActive = isEndBegin && !isEndEnd;
|
||||
itemManager.isDisabled = isEndSequenceActive;
|
||||
characterManager.isDisabled = isEndSequenceActive;
|
||||
|
||||
if (text.isEnabled) text.update(character);
|
||||
if (text.isEnabled) text.update(character, worldCanvas);
|
||||
|
||||
if (isStart)
|
||||
{
|
||||
@@ -279,16 +298,17 @@ namespace game::state
|
||||
{
|
||||
if (auto animation = character.animation_get())
|
||||
{
|
||||
if (animation->isLoop || character.state == entity::Actor::STOPPED)
|
||||
if (animation->isLoop || character.state == Entity::STOPPED)
|
||||
{
|
||||
text.set(dialogue.get(dialogue.start.id), character);
|
||||
auto* start = dialogue.get(Schema::Element::START);
|
||||
text.set(start ? dialogue.dialogue_entry_get(*start) : nullptr, character);
|
||||
isStartBegin = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isStartEnd)
|
||||
{
|
||||
if (text.entry->is_last())
|
||||
if (text.entry && text.is_terminal() && text.is_finished())
|
||||
{
|
||||
isWindows = true;
|
||||
isStartEnd = true;
|
||||
@@ -306,7 +326,8 @@ namespace game::state
|
||||
{
|
||||
if (character.is_animation_finished())
|
||||
{
|
||||
text.set(dialogue.get(dialogue.end.id), character);
|
||||
auto* end = dialogue.get(Schema::Element::END);
|
||||
text.set(end ? dialogue.dialogue_entry_get(*end) : nullptr, character);
|
||||
isEndBegin = true;
|
||||
isWindows = false;
|
||||
tools.isOpen = false;
|
||||
@@ -319,7 +340,7 @@ namespace game::state
|
||||
}
|
||||
else if (!isEndEnd)
|
||||
{
|
||||
if (text.entry->is_last())
|
||||
if (text.entry && text.is_terminal() && text.is_finished())
|
||||
{
|
||||
menu.isOpen = true;
|
||||
isWindows = true;
|
||||
@@ -332,13 +353,29 @@ namespace game::state
|
||||
}
|
||||
}
|
||||
|
||||
itemManager.update(character, cursor, areaManager, text, World::BOUNDS, worldCanvas);
|
||||
characterManager.update(character, cursor, text, worldCanvas);
|
||||
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
|
||||
auto& interactionCursor = autofeed.active_get() ? autofeedCursor : cursor;
|
||||
itemManager.isAutomation = autofeed.active_get();
|
||||
itemManager.update(character, interactionCursor, areaManager, text, World::BOUNDS, worldCanvas);
|
||||
characterManager.update(character, interactionCursor, text, worldCanvas);
|
||||
for (auto& particle : itemManager.particles)
|
||||
particleManager.spawn(character.data, particle.label, particle.position);
|
||||
for (auto& particle : characterManager.particles)
|
||||
particleManager.spawn(character.data, particle.label, particle.position);
|
||||
|
||||
if (isCursorEnabled && !autofeed.active_get() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
!itemManager.isItemHovered && !characterManager.isHovering && cursor.cursorState == Entity::DEFAULT)
|
||||
if (auto* cursorRoot = character.data.cursorSchema.root())
|
||||
if (auto animation = cursorRoot->animationClick.get())
|
||||
cursor.play({.animation = *animation, .mode = Entity::PLAY_FORCE, .interrupt = Entity::Interrupt::ALWAYS});
|
||||
|
||||
character.update();
|
||||
areaManager.update(character);
|
||||
particleManager.update();
|
||||
cursor.update();
|
||||
if (autofeed.active_get()) autofeedCursor.update();
|
||||
world.update(character, cursor, worldCanvas, focus);
|
||||
worldCanvas.tick();
|
||||
worldCanvas.update();
|
||||
|
||||
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
|
||||
{
|
||||
@@ -354,70 +391,82 @@ namespace game::state
|
||||
auto& rectShader = resources.shaders[shader::RECT];
|
||||
auto size = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto& bgTexture = character.data.areaSchema.areas.at(areaManager.get(character)).texture;
|
||||
|
||||
auto windowModel = math::quad_model_get(vec2(size));
|
||||
auto worldModel = math::quad_model_get(bgTexture.size);
|
||||
|
||||
worldCanvas.bind();
|
||||
worldCanvas.size_set(size);
|
||||
worldCanvas.clear();
|
||||
worldCanvas.texture_render(textureShader, bgTexture.id, worldModel);
|
||||
|
||||
areaManager.render(character, textureShader, rectShader, worldCanvas);
|
||||
|
||||
character.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
for (auto& item : itemManager.items)
|
||||
item.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
if (menu.debug.isBoundsDisplay)
|
||||
{
|
||||
auto boundsModel =
|
||||
math::quad_model_get(glm::vec2(World::BOUNDS.z, World::BOUNDS.w), glm::vec2(World::BOUNDS.x, World::BOUNDS.y),
|
||||
glm::vec2(World::BOUNDS.x, World::BOUNDS.y) * 0.5f);
|
||||
worldCanvas.rect_render(rectShader, boundsModel);
|
||||
}
|
||||
auto isCursorEnabled = cursor_enabled_get();
|
||||
if (isCursorEnabled && autofeed.active_get()) autofeedCursor.render(textureShader, rectShader, worldCanvas);
|
||||
particleManager.render(textureShader, rectShader, worldCanvas);
|
||||
|
||||
worldCanvas.unbind();
|
||||
|
||||
auto isCursor = isCursorEnabled && !resources.isDeveloper;
|
||||
|
||||
canvas.bind();
|
||||
canvas.texture_render(textureShader, worldCanvas, windowModel);
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
cursor.render(textureShader, rectShader, canvas);
|
||||
if (isCursor) cursor.render(textureShader, rectShader, canvas);
|
||||
canvas.unbind();
|
||||
}
|
||||
|
||||
void Play::save(Resources& resources)
|
||||
{
|
||||
resource::xml::Save save;
|
||||
resource::xml::Schema save;
|
||||
auto& root = save.element_add(resource::xml::Schema::Element::SAVE);
|
||||
root.isPostgame = isPostgame;
|
||||
root.isAlternateSpritesheet = character.spritesheetType == Entity::ALTERNATE;
|
||||
|
||||
save.weight = character.weight;
|
||||
save.calories = character.calories;
|
||||
save.capacity = character.capacity;
|
||||
save.digestionRate = character.digestionRate;
|
||||
save.eatSpeed = character.eatSpeed;
|
||||
save.digestionProgress = character.digestionProgress;
|
||||
save.isDigesting = character.isDigesting;
|
||||
save.digestionTimer = character.digestionTimer;
|
||||
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
||||
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
||||
save.skillCheck.totalPlays = menu.arcade.skillCheck.totalPlays;
|
||||
save.skillCheck.highScore = menu.arcade.skillCheck.highScore;
|
||||
save.skillCheck.bestCombo = menu.arcade.skillCheck.bestCombo;
|
||||
save.skillCheck.gradeCounts = menu.arcade.skillCheck.gradeCounts;
|
||||
save.orbit.highScore = menu.arcade.orbit.highScore;
|
||||
save.isPostgame = isPostgame;
|
||||
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
|
||||
auto& panElement = save.element_add(resource::xml::Schema::Element::PAN, 0);
|
||||
panElement.position = worldCanvas.pan;
|
||||
|
||||
auto& zoomElement = save.element_add(resource::xml::Schema::Element::ZOOM, 0);
|
||||
zoomElement.zoom = worldCanvas.zoom;
|
||||
|
||||
auto& characterElement = save.element_add(resource::xml::Schema::Element::CHARACTER, 0);
|
||||
characterElement.weightKilograms = character.weight;
|
||||
characterElement.calories = character.calories;
|
||||
characterElement.capacityCalories = character.capacity;
|
||||
characterElement.digestionRate = character.digestionRate;
|
||||
characterElement.eatSpeedMultiplier = character.eatSpeed;
|
||||
characterElement.digestionProgress = character.digestionProgress;
|
||||
characterElement.isDigesting = character.isDigesting;
|
||||
characterElement.digestionTimer = character.digestionTimer;
|
||||
characterElement.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
||||
characterElement.totalFoodItemsEaten = character.totalFoodItemsEaten;
|
||||
|
||||
auto inventoryIndex = (int)save.elements.size();
|
||||
save.element_add(resource::xml::Schema::Element::INVENTORY, 0);
|
||||
|
||||
for (auto& [id, quantity] : menu.inventory.values)
|
||||
{
|
||||
if (quantity == 0) continue;
|
||||
save.inventory[id] = quantity;
|
||||
auto& item = save.element_add(resource::xml::Schema::Element::ITEM, inventoryIndex);
|
||||
item.id = id;
|
||||
item.quantity = quantity;
|
||||
}
|
||||
|
||||
auto itemsIndex = (int)save.elements.size();
|
||||
save.element_add(resource::xml::Schema::Element::ITEMS, 0);
|
||||
for (auto& item : itemManager.items)
|
||||
save.items.emplace_back(item.schemaID, item.durability, item.position, item.velocity,
|
||||
*item.overrides[item.rotationOverrideID].frame.rotation);
|
||||
|
||||
save.isValid = true;
|
||||
{
|
||||
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, itemsIndex);
|
||||
itemElement.id = item.schemaID;
|
||||
itemElement.durability = item.durability;
|
||||
itemElement.position = item.position;
|
||||
itemElement.velocity = item.velocity;
|
||||
itemElement.rotation = *item.overrides[item.rotationOverrideID].frame.rotation;
|
||||
}
|
||||
|
||||
resources.character_save_set(characterIndex, save);
|
||||
save.serialize(character.data.save_path_get());
|
||||
|
||||
+8
-3
@@ -3,10 +3,12 @@
|
||||
#include "../resources.hpp"
|
||||
|
||||
#include "play/area_manager.hpp"
|
||||
#include "play/autofeed.hpp"
|
||||
#include "play/character_manager.hpp"
|
||||
#include "play/info.hpp"
|
||||
#include "play/item_manager.hpp"
|
||||
#include "play/menu.hpp"
|
||||
#include "play/particle_manager.hpp"
|
||||
#include "play/text.hpp"
|
||||
#include "play/toasts.hpp"
|
||||
#include "play/tools.hpp"
|
||||
@@ -25,8 +27,9 @@ namespace game::state
|
||||
CONTINUE
|
||||
};
|
||||
|
||||
entity::Character character;
|
||||
entity::Cursor cursor;
|
||||
Entity character;
|
||||
Entity cursor;
|
||||
Entity autofeedCursor;
|
||||
|
||||
play::Info info;
|
||||
play::Menu menu;
|
||||
@@ -34,7 +37,9 @@ namespace game::state
|
||||
play::Text text;
|
||||
play::World world;
|
||||
play::Toasts toasts;
|
||||
play::Autofeed autofeed{};
|
||||
play::ItemManager itemManager{};
|
||||
play::ParticleManager particleManager{};
|
||||
play::CharacterManager characterManager{};
|
||||
play::AreaManager areaManager{};
|
||||
|
||||
@@ -60,10 +65,10 @@ namespace game::state
|
||||
Canvas worldCanvas{play::World::SIZE};
|
||||
|
||||
Play() = default;
|
||||
bool cursor_enabled_get() const;
|
||||
void set(Resources&, int characterIndex, Game = CONTINUE);
|
||||
void exit(Resources& resources);
|
||||
void update(Resources&);
|
||||
void tick(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
void save(Resources&);
|
||||
play::World::Focus focus_get();
|
||||
|
||||
@@ -1,26 +1,73 @@
|
||||
#include "area_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
int AreaManager::get(entity::Character& character)
|
||||
int AreaManager::index_get(Entity& character)
|
||||
{
|
||||
auto& data = character.data;
|
||||
auto& schema = data.areaSchema;
|
||||
if (schema.areas.empty()) return -1;
|
||||
|
||||
auto size = (int)data.stages.size();
|
||||
auto stages = data.stages_get();
|
||||
auto size = (int)stages.size();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
auto& stage = data.stages[size - i - 1];
|
||||
if (stage.areaID != -1) return stage.areaID;
|
||||
auto* stage = stages[size - i - 1];
|
||||
if (stage->areaID >= 0 && stage->areaID < (int)schema.areas.size()) return stage->areaID;
|
||||
}
|
||||
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
game::resource::xml::Schema::AreaEntry* AreaManager::get(Entity& character)
|
||||
{
|
||||
auto index = index_get(character);
|
||||
if (index == -1) return nullptr;
|
||||
return &character.data.areaSchema.areas[index];
|
||||
}
|
||||
|
||||
void AreaManager::set(Entity& character)
|
||||
{
|
||||
entities.clear();
|
||||
auto& areas = character.data.areaSchema.areas;
|
||||
entities.reserve(areas.size());
|
||||
|
||||
for (auto& area : areas)
|
||||
entities.emplace_back(area.anm2.is_valid() ? Entity(area.anm2) : Entity{});
|
||||
}
|
||||
|
||||
void AreaManager::update(Entity& character)
|
||||
{
|
||||
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
|
||||
|
||||
auto index = index_get(character);
|
||||
if (index == -1 || index >= (int)entities.size()) return;
|
||||
entities[index].update();
|
||||
}
|
||||
|
||||
void AreaManager::render(Entity& character, Shader& textureShader, Shader& rectShader, Canvas& canvas)
|
||||
{
|
||||
if (entities.size() != character.data.areaSchema.areas.size()) set(character);
|
||||
|
||||
auto index = index_get(character);
|
||||
if (index == -1) return;
|
||||
|
||||
auto& area = character.data.areaSchema.areas[index];
|
||||
if (index < (int)entities.size() && area.anm2.is_valid())
|
||||
{
|
||||
entities[index].render(textureShader, rectShader, canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!area.texture.is_valid()) return;
|
||||
|
||||
auto worldModel = math::quad_model_get(area.texture.size);
|
||||
canvas.texture_render(textureShader, area.texture.id, worldModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include <vector>
|
||||
|
||||
#include "../../entity.hpp"
|
||||
#include "../../resource/shader.hpp"
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class AreaManager
|
||||
{
|
||||
public:
|
||||
int get(entity::Character&);
|
||||
std::vector<Entity> entities{};
|
||||
|
||||
int index_get(Entity&);
|
||||
game::resource::xml::Schema::AreaEntry* get(Entity&);
|
||||
void set(Entity&);
|
||||
void update(Entity&);
|
||||
void render(Entity&, game::resource::Shader&, game::resource::Shader&, Canvas&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#include "autofeed.hpp"
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr float CURSOR_SPEED = 9.0f;
|
||||
constexpr float DIGEST_CURSOR_SPEED = 3.0f;
|
||||
constexpr float CURSOR_SPEED_REFERENCE_AREA = 512.0f * 512.0f;
|
||||
constexpr float CURSOR_SPEED_SCALE_MIN = 0.5f;
|
||||
constexpr float CURSOR_SPEED_SCALE_MAX = 2.0f;
|
||||
constexpr float CALORIE_EPSILON = 0.001f;
|
||||
|
||||
glm::vec2 rect_center(glm::vec4 rect) { return {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; }
|
||||
|
||||
bool is_finite(glm::vec2 value) { return std::isfinite(value.x) && std::isfinite(value.y); }
|
||||
|
||||
bool is_finite(glm::vec4 value)
|
||||
{
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
|
||||
value.z > 0.0f && value.w > 0.0f;
|
||||
}
|
||||
|
||||
bool move_toward(Entity& cursor, glm::vec2 target, float speed)
|
||||
{
|
||||
if (!is_finite(cursor.position)) cursor.position = {};
|
||||
if (!is_finite(target)) return false;
|
||||
|
||||
auto delta = target - cursor.position;
|
||||
auto distance = glm::length(delta);
|
||||
if (!std::isfinite(distance)) return false;
|
||||
if (distance <= speed || distance <= 0.001f)
|
||||
{
|
||||
cursor.position = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
cursor.position += delta / distance * speed;
|
||||
return false;
|
||||
}
|
||||
|
||||
float cursor_speed_scale_get(Entity& character)
|
||||
{
|
||||
auto rect = character.rect();
|
||||
if (!is_finite(rect)) return 1.0f;
|
||||
|
||||
auto area = rect.z * rect.w;
|
||||
if (!std::isfinite(area) || area <= 0.0f) return 1.0f;
|
||||
|
||||
return glm::clamp(std::sqrt(area / CURSOR_SPEED_REFERENCE_AREA), CURSOR_SPEED_SCALE_MIN, CURSOR_SPEED_SCALE_MAX);
|
||||
}
|
||||
|
||||
bool is_food(const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
return item.categoryID >= 0 && item.categoryID < (int)schema.categories.size() &&
|
||||
schema.categories[item.categoryID].isEdible;
|
||||
}
|
||||
|
||||
bool is_negative_effect(const Schema::ItemEntry& item)
|
||||
{
|
||||
return (item.isCapacityBonus && item.capacityBonus < 0.0f) ||
|
||||
(item.isEatSpeedBonus && item.eatSpeedBonus < 0.0f) ||
|
||||
(item.isDigestionBonus && item.digestionBonus < 0.0f);
|
||||
}
|
||||
|
||||
bool is_autofeed_food(const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
return is_food(schema, item) && !is_negative_effect(item);
|
||||
}
|
||||
|
||||
int durability_max_get(const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
return item.isDurability ? item.durability : schema.durability;
|
||||
}
|
||||
|
||||
float calories_per_bite_get(const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
auto durabilityMax = durability_max_get(schema, item);
|
||||
return item.isCalories && durabilityMax > 0 ? item.calories / (float)durabilityMax : 0.0f;
|
||||
}
|
||||
|
||||
bool can_eat(const Entity& character, const Schema& schema, const Schema::ItemEntry& item)
|
||||
{
|
||||
auto caloriesPerBite = calories_per_bite_get(schema, item);
|
||||
return caloriesPerBite > 0.0f && character.calories + caloriesPerBite <= character.max_capacity();
|
||||
}
|
||||
|
||||
struct FoodScore
|
||||
{
|
||||
float calories{};
|
||||
int bites{};
|
||||
float caloriesPerBite{};
|
||||
bool isValid{};
|
||||
};
|
||||
|
||||
FoodScore food_score_get(const Entity& character, const Schema& schema, const Schema::ItemEntry& item,
|
||||
int durability)
|
||||
{
|
||||
if (!is_autofeed_food(schema, item)) return {};
|
||||
|
||||
auto caloriesPerBite = calories_per_bite_get(schema, item);
|
||||
auto remainingCapacity = character.max_capacity() - character.calories;
|
||||
auto durabilityMax = durability_max_get(schema, item);
|
||||
auto remainingBites = glm::max(0, durabilityMax - durability);
|
||||
if (caloriesPerBite <= 0.0f || remainingCapacity < caloriesPerBite || remainingBites <= 0) return {};
|
||||
|
||||
auto bites = glm::clamp((int)std::floor(remainingCapacity / caloriesPerBite), 0, remainingBites);
|
||||
auto calories = (float)bites * caloriesPerBite;
|
||||
return {.calories = calories, .bites = bites, .caloriesPerBite = caloriesPerBite, .isValid = bites > 0};
|
||||
}
|
||||
|
||||
bool is_better_food_score(const FoodScore& score, const FoodScore& best)
|
||||
{
|
||||
if (!score.isValid) return false;
|
||||
if (!best.isValid) return true;
|
||||
if (score.calories != best.calories) return score.calories > best.calories;
|
||||
if (score.bites != best.bites) return score.bites < best.bites;
|
||||
return score.caloriesPerBite > best.caloriesPerBite;
|
||||
}
|
||||
|
||||
bool can_eat_anything(Entity& character, ItemManager& itemManager, menu::Inventory& inventory)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
for (auto& entity : itemManager.items)
|
||||
{
|
||||
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
|
||||
auto& item = schema.items[entity.schemaID];
|
||||
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
|
||||
}
|
||||
|
||||
for (auto& [id, quantity] : inventory.values)
|
||||
{
|
||||
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
|
||||
auto& item = schema.items[id];
|
||||
if (is_autofeed_food(schema, item) && can_eat(character, schema, item)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int best_inventory_food_get(Entity& character, menu::Inventory& inventory)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto bestID = -1;
|
||||
auto bestScore = FoodScore{};
|
||||
|
||||
for (auto& [id, quantity] : inventory.values)
|
||||
{
|
||||
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
|
||||
auto& item = schema.items[id];
|
||||
auto score = food_score_get(character, schema, item, 0);
|
||||
|
||||
if (is_better_food_score(score, bestScore))
|
||||
{
|
||||
bestScore = score;
|
||||
bestID = id;
|
||||
}
|
||||
}
|
||||
|
||||
return bestID;
|
||||
}
|
||||
|
||||
bool is_inventory_food(Entity& character, menu::Inventory& inventory)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
for (auto& [id, quantity] : inventory.values)
|
||||
{
|
||||
if (quantity <= 0 || id < 0 || id >= (int)schema.items.size()) continue;
|
||||
if (is_autofeed_food(schema, schema.items[id])) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_world_food(Entity& character, ItemManager& itemManager)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
for (auto& entity : itemManager.items)
|
||||
{
|
||||
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
|
||||
if (is_autofeed_food(schema, schema.items[entity.schemaID])) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_food(Entity& character, menu::Inventory& inventory, ItemManager& itemManager)
|
||||
{
|
||||
return character.calories > CALORIE_EPSILON || is_inventory_food(character, inventory) ||
|
||||
is_world_food(character, itemManager);
|
||||
}
|
||||
|
||||
int best_world_food_get(Entity& character, ItemManager& itemManager, bool requireCanEat)
|
||||
{
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto bestIndex = -1;
|
||||
auto bestScore = FoodScore{};
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
auto& entity = itemManager.items[i];
|
||||
if (entity.schemaID < 0 || entity.schemaID >= (int)schema.items.size()) continue;
|
||||
|
||||
auto& item = schema.items[entity.schemaID];
|
||||
if (!is_autofeed_food(schema, item)) continue;
|
||||
if (requireCanEat && !can_eat(character, schema, item)) continue;
|
||||
|
||||
auto score = food_score_get(character, schema, item, entity.durability);
|
||||
if (is_better_food_score(score, bestScore))
|
||||
{
|
||||
bestScore = score;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
bool eat_rect_get(Entity& character, glm::vec4& rect)
|
||||
{
|
||||
for (auto* eatArea : character.data.eat_areas_get())
|
||||
{
|
||||
if (!eatArea) continue;
|
||||
auto nullID = character.data.null_id_get(eatArea->null);
|
||||
if (nullID == -1) continue;
|
||||
|
||||
rect = character.null_frame_rect(nullID);
|
||||
if (is_finite(rect)) return true;
|
||||
|
||||
auto animationIndex = character.animation_index_get(eatArea->animation + character.animation_append_id_get());
|
||||
rect = character.null_frame_rect(nullID, animationIndex, 0.0f);
|
||||
if (is_finite(rect)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const Schema::Element* digestion_area_get(Entity& character)
|
||||
{
|
||||
for (auto* interactArea : character.data.interact_areas_get())
|
||||
{
|
||||
if (!interactArea) continue;
|
||||
if (interactArea->digestionBonusOnClick <= 0.0f && interactArea->digestionBonusOnHover <= 0.0f) continue;
|
||||
if (character.data.null_id_get(interactArea->null) == -1) continue;
|
||||
return interactArea;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& digestClickCooldown)
|
||||
{
|
||||
isItemMouseHeld = false;
|
||||
isDigestMouseHeld = false;
|
||||
digestClickCooldown = 0;
|
||||
if (!is_finite(cursor.position)) cursor.position = {};
|
||||
}
|
||||
}
|
||||
|
||||
void Autofeed::update(Resources& resources, Entity& character, Entity& cursor, menu::Inventory& inventory,
|
||||
ItemManager& itemManager, CharacterManager& characterManager, Canvas& canvas)
|
||||
{
|
||||
isActive = false;
|
||||
cursor.isVisible = false;
|
||||
|
||||
if (!isEnabled)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_food(character, inventory, itemManager))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (character.isStageUp || character.isJustStageUp || character.isJustStageFinal)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor.position == glm::vec2{} && is_finite(character.position)) cursor.position = character.position;
|
||||
|
||||
auto& schema = character.data.itemSchema;
|
||||
if (inventory.upgrade_all_possible(schema))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
auto itemInput = ItemManager::Input{};
|
||||
itemInput.isAutomation = true;
|
||||
auto characterInput = CharacterManager::Input{};
|
||||
auto speedScale = cursor_speed_scale_get(character);
|
||||
auto cursorSpeed = CURSOR_SPEED * speedScale;
|
||||
auto digestCursorSpeed = DIGEST_CURSOR_SPEED * speedScale;
|
||||
auto eatRect = glm::vec4{};
|
||||
auto isEatTarget = eat_rect_get(character, eatRect);
|
||||
|
||||
if (itemManager.heldItemIndex != -1 && itemManager.heldItemIndex < (int)itemManager.items.size())
|
||||
{
|
||||
auto& heldItem = itemManager.items[itemManager.heldItemIndex];
|
||||
if (heldItem.schemaID < 0 || heldItem.schemaID >= (int)schema.items.size())
|
||||
{
|
||||
isActive = false;
|
||||
cursor.isVisible = false;
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
auto& schemaItem = schema.items[heldItem.schemaID];
|
||||
|
||||
if (isEatTarget && can_eat(character, schema, schemaItem))
|
||||
{
|
||||
isActive = true;
|
||||
cursor.isVisible = true;
|
||||
move_toward(cursor, rect_center(eatRect), cursorSpeed);
|
||||
|
||||
itemInput.isMouseLeftDown = true;
|
||||
itemManager.inputOverride = itemInput;
|
||||
isItemMouseHeld = true;
|
||||
isDigestMouseHeld = false;
|
||||
return;
|
||||
}
|
||||
|
||||
itemInput.isMouseLeftReleased = true;
|
||||
itemManager.inputOverride = itemInput;
|
||||
isActive = true;
|
||||
cursor.isVisible = true;
|
||||
isItemMouseHeld = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bestWorldFood = isEatTarget ? best_world_food_get(character, itemManager, true) : -1;
|
||||
|
||||
if (bestWorldFood != -1)
|
||||
{
|
||||
auto& item = itemManager.items[bestWorldFood];
|
||||
auto rect = item.rect();
|
||||
if (!is_finite(rect))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
isActive = true;
|
||||
cursor.isVisible = true;
|
||||
move_toward(cursor, rect_center(rect), cursorSpeed);
|
||||
|
||||
auto isInItemRect = math::is_point_in_rectf(rect, cursor.position);
|
||||
|
||||
itemInput.isMouseLeftClicked = isInItemRect && !isItemMouseHeld;
|
||||
itemInput.isMouseLeftDown = isInItemRect || isItemMouseHeld;
|
||||
itemManager.inputOverride = itemInput;
|
||||
if (isInItemRect) isItemMouseHeld = true;
|
||||
isDigestMouseHeld = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto isInventoryFood = is_inventory_food(character, inventory);
|
||||
auto isWorldFood = is_world_food(character, itemManager);
|
||||
if (character.calories <= CALORIE_EPSILON && !isInventoryFood && !isWorldFood)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
auto bestInventoryFood = isEatTarget ? best_inventory_food_get(character, inventory) : -1;
|
||||
if (bestInventoryFood != -1 && !isWorldFood && (int)itemManager.items.size() < ItemManager::LIMIT)
|
||||
{
|
||||
itemManager.queuedItemIDs.emplace_back(bestInventoryFood);
|
||||
inventory.values[bestInventoryFood]--;
|
||||
schema.root()->soundSummon.play();
|
||||
cursor.isVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((isWorldFood && bestWorldFood == -1) || !can_eat_anything(character, itemManager, inventory))
|
||||
{
|
||||
auto* interactArea = digestion_area_get(character);
|
||||
if (!interactArea)
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
|
||||
auto rect = character.null_frame_rect(character.data.null_id_get(interactArea->null));
|
||||
if (!is_finite(rect))
|
||||
{
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
return;
|
||||
}
|
||||
auto radius = glm::max(1.0f, glm::min(rect.z, rect.w) * 0.25f);
|
||||
digestAngle += 0.08f;
|
||||
auto targetWorld = rect_center(rect) + glm::vec2(std::cos(digestAngle), std::sin(digestAngle)) * radius;
|
||||
auto isAtTarget = move_toward(cursor, targetWorld, digestCursorSpeed);
|
||||
isActive = true;
|
||||
cursor.isVisible = true;
|
||||
|
||||
cursor.interactTypeID = character.data.interact_type_id_get(interactArea->typeString);
|
||||
if (interactArea->digestionBonusOnHover > 0.0f)
|
||||
{
|
||||
auto isInDigestRect = math::is_point_in_rectf(rect, cursor.position);
|
||||
characterInput.isMouseLeftClick = isInDigestRect && !isDigestMouseHeld;
|
||||
characterInput.isMouseLeftDown = isInDigestRect;
|
||||
characterInput.isMouseLeftReleased = isDigestMouseHeld && !isInDigestRect;
|
||||
isDigestMouseHeld = isInDigestRect;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (digestClickCooldown > 0) digestClickCooldown--;
|
||||
characterInput.isMouseLeftClick = isAtTarget && digestClickCooldown <= 0 && !isDigestMouseHeld;
|
||||
characterInput.isMouseLeftReleased = isDigestMouseHeld;
|
||||
if (characterInput.isMouseLeftClick)
|
||||
{
|
||||
isDigestMouseHeld = true;
|
||||
digestClickCooldown = 45;
|
||||
}
|
||||
else if (characterInput.isMouseLeftReleased)
|
||||
isDigestMouseHeld = false;
|
||||
}
|
||||
characterManager.inputOverride = characterInput;
|
||||
isItemMouseHeld = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, digestClickCooldown);
|
||||
}
|
||||
|
||||
void Autofeed::toggle() { isEnabled = !isEnabled; }
|
||||
void Autofeed::disable() { isEnabled = false; }
|
||||
bool Autofeed::available_get(Entity& character, menu::Inventory& inventory, ItemManager& itemManager) const
|
||||
{
|
||||
return is_food(character, inventory, itemManager);
|
||||
}
|
||||
bool Autofeed::enabled_get() const { return isEnabled; }
|
||||
bool Autofeed::active_get() const { return isActive; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../resources.hpp"
|
||||
#include "character_manager.hpp"
|
||||
#include "item_manager.hpp"
|
||||
#include "menu/inventory.hpp"
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class Autofeed
|
||||
{
|
||||
bool isItemMouseHeld{};
|
||||
bool isDigestMouseHeld{};
|
||||
int digestClickCooldown{};
|
||||
float digestAngle{};
|
||||
bool isEnabled{};
|
||||
bool isActive{};
|
||||
|
||||
public:
|
||||
void update(Resources&, Entity& character, Entity& cursor, menu::Inventory&, ItemManager&,
|
||||
CharacterManager&, Canvas&);
|
||||
void toggle();
|
||||
void disable();
|
||||
bool available_get(Entity& character, menu::Inventory&, ItemManager&) const;
|
||||
bool enabled_get() const;
|
||||
bool active_get() const;
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
using namespace game::resource::xml;
|
||||
@@ -10,13 +11,18 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void CharacterManager::update(entity::Character& character, entity::Cursor& cursor, Text& text, Canvas& canvas)
|
||||
void CharacterManager::update(Entity& character, Entity& cursor, Text& text, Canvas& canvas)
|
||||
{
|
||||
auto interact_area_override_tick = [](entity::Actor::Override& override_)
|
||||
static constexpr auto SCALE_EFFECT_TIME_TICKS_DEFAULT = 20.0f;
|
||||
static constexpr auto SCALE_EFFECT_CYCLES_DEFAULT = 1.0f;
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto interact_area_override_tick = [](Entity::Override& override_)
|
||||
{
|
||||
auto& scale = override_.frame.scale;
|
||||
auto& scaleBase = override_.frameBase.scale;
|
||||
auto isScaleValid = scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();
|
||||
auto isScaleValid =
|
||||
scale.x.has_value() && scale.y.has_value() && scaleBase.x.has_value() && scaleBase.y.has_value();
|
||||
|
||||
if (isScaleValid && override_.time.has_value() && override_.timeStart.has_value())
|
||||
{
|
||||
@@ -34,11 +40,22 @@ namespace game::state::play
|
||||
};
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto cursorWorldPosition = canvas.screen_position_convert(cursor.position);
|
||||
auto cursorWorldPosition =
|
||||
cursor.entityType == AUTOCURSOR ? cursor.position : canvas.screen_position_convert(cursor.position);
|
||||
auto isMouseLeftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
|
||||
|
||||
if (inputOverride)
|
||||
{
|
||||
isImguiCaptureMouse = inputOverride->isImguiCaptureMouse;
|
||||
isMouseLeftClick = inputOverride->isMouseLeftClick;
|
||||
isMouseLeftDown = inputOverride->isMouseLeftDown;
|
||||
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
|
||||
inputOverride.reset();
|
||||
}
|
||||
|
||||
if (isDisabled)
|
||||
{
|
||||
isInteracting = false;
|
||||
@@ -52,76 +69,121 @@ namespace game::state::play
|
||||
isHoveringPrevious = isHovering;
|
||||
isHoldInteractingPrevious = isHoldInteracting;
|
||||
isHovering = false;
|
||||
particles.clear();
|
||||
if (!isInteracting) isHoldInteracting = false;
|
||||
|
||||
if (isJustStoppedHoldInteracting)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
if (character.queuedPlay.empty()) character.queue_idle_animation();
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
if (!character.is_animation_request())
|
||||
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
|
||||
isJustStoppedHoldInteracting = false;
|
||||
}
|
||||
else if (isJustStoppedInteracting)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
isJustStoppedInteracting = false;
|
||||
}
|
||||
|
||||
if (isJustStoppedHovering)
|
||||
{
|
||||
cursor.queue_play({cursor.defaultAnimation});
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
isJustStoppedHovering = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)character.data.interactAreas.size(); i++)
|
||||
{
|
||||
auto& interactArea = character.data.interactAreas.at(i);
|
||||
if (interactArea.nullID == -1) continue;
|
||||
auto rect = character.null_frame_rect(interactArea.nullID);
|
||||
auto interactAreas = character.data.interact_areas_get();
|
||||
if (interactAreaDialogueTimers.size() != interactAreas.size())
|
||||
interactAreaDialogueTimers.assign(interactAreas.size(), 0);
|
||||
if (!text.entry) std::ranges::fill(interactAreaDialogueTimers, 0);
|
||||
for (auto& timer : interactAreaDialogueTimers)
|
||||
if (timer > 0) timer--;
|
||||
|
||||
if (cursor.state == entity::Cursor::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
|
||||
!isImguiCaptureMouse && interactArea.typeID == cursor.interactTypeID)
|
||||
for (int i = 0; i < (int)interactAreas.size(); i++)
|
||||
{
|
||||
auto* interactArea = interactAreas.at(i);
|
||||
auto nullID = character.data.null_id_get(interactArea->null);
|
||||
auto layerID = character.data.layer_id_get(interactArea->layer);
|
||||
if (nullID == -1) continue;
|
||||
auto rect = character.null_frame_rect(nullID);
|
||||
|
||||
if (cursor.cursorState == Entity::DEFAULT && math::is_point_in_rectf(rect, cursorWorldPosition) &&
|
||||
!isImguiCaptureMouse &&
|
||||
character.data.interact_type_id_get(interactArea->typeString) == cursor.interactTypeID)
|
||||
{
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
cursor.queue_play({interactArea.animationCursorHover});
|
||||
cursor.cursorState = Entity::HOVER;
|
||||
cursor.play({.animation = interactArea->animationCursorHover, .transition = Entity::Transition::IF_IDLE});
|
||||
isHovering = true;
|
||||
interactAreaID = i;
|
||||
|
||||
if (isMouseLeftClick)
|
||||
auto interact_area_proc = [&](bool isInteract)
|
||||
{
|
||||
isInteracting = true;
|
||||
isHoldInteracting = interactArea.isHold;
|
||||
interactArea.sound.play();
|
||||
|
||||
if (interactArea.digestionBonusClick > 0 && character.calories > 0 && !character.isDigesting)
|
||||
character.digestionProgress += interactArea.digestionBonusClick;
|
||||
|
||||
if (interactArea.layerID != -1)
|
||||
if (isInteract)
|
||||
{
|
||||
character.overrides.emplace_back(entity::Actor::Override(
|
||||
interactArea.layerID, Anm2::LAYER, entity::Actor::Override::ADD,
|
||||
{.scale = glm::vec2(interactArea.scaleEffectAmplitude)}, std::optional<float>(interactArea.time),
|
||||
interact_area_override_tick, interactArea.scaleEffectCycles));
|
||||
isInteracting = true;
|
||||
isHoldInteracting = interactArea->isHold;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor.cursorState = Entity::ACTION;
|
||||
cursor.play({.animation = interactArea->animationCursorActive,
|
||||
.mode = Entity::PLAY_FORCE,
|
||||
.interrupt = Entity::Interrupt::ALWAYS});
|
||||
}
|
||||
interactArea->soundEntries.play();
|
||||
for (auto* particle : character.data.children_get(*interactArea, Schema::Element::PARTICLE))
|
||||
if (particle && !particle->typeString.empty())
|
||||
particles.push_back({.label = particle->typeString, .position = cursorWorldPosition});
|
||||
|
||||
if (interactArea->digestionBonusOnClick > 0 && character.calories > 0 && !character.isDigesting)
|
||||
character.digestionProgress += interactArea->digestionBonusOnClick;
|
||||
|
||||
if (layerID != -1)
|
||||
{
|
||||
auto scaleEffectTimeTicks = interactArea->timeTicks == ZERO_FLOAT ? SCALE_EFFECT_TIME_TICKS_DEFAULT
|
||||
: interactArea->timeTicks;
|
||||
auto scaleEffectCycles = interactArea->scaleEffectCycles == ZERO_FLOAT ? SCALE_EFFECT_CYCLES_DEFAULT
|
||||
: interactArea->scaleEffectCycles;
|
||||
character.overrides.emplace_back(Entity::Override(
|
||||
layerID, Anm2::LAYER, Entity::Override::ADD, {.scale = glm::vec2(interactArea->scaleEffectAmplitude)},
|
||||
std::optional<float>(scaleEffectTimeTicks), interact_area_override_tick, scaleEffectCycles));
|
||||
}
|
||||
|
||||
if (text.is_interruptible())
|
||||
auto isDialogueReady = interactAreaDialogueTimers[i] <= 0;
|
||||
interactAreaDialogueTimers[i] = std::max(0, interactArea->timeToActivateNewDialogueTicks);
|
||||
|
||||
auto poolFullID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolIDFull);
|
||||
auto poolID = character.data.dialogue_pool_id_get(interactArea->dialoguePoolID);
|
||||
if (character.is_over_capacity() && poolFullID != -1) poolID = poolFullID;
|
||||
|
||||
if (poolID != -1)
|
||||
{
|
||||
auto& pool = character.is_over_capacity() && interactArea.poolFull.is_valid() ? interactArea.poolFull
|
||||
: interactArea.pool;
|
||||
if (pool.is_valid())
|
||||
text.set(dialogue.get(pool), character);
|
||||
if (text.is_interruptible() && isDialogueReady)
|
||||
{
|
||||
auto* entry = dialogue.dialogue_pool_entry_get(poolID);
|
||||
if (entry) text.set(entry, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (interactArea->dialoguePoolID.empty() && !interactArea->animation.empty())
|
||||
character.play({.animation = interactArea->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.mode = Entity::PLAY_FORCE});
|
||||
};
|
||||
|
||||
if (interactArea->isHold && (isMouseLeftClick || (isMouseLeftDown && !isInteracting)))
|
||||
interact_area_proc(true);
|
||||
else if (!interactArea->isHold && isMouseLeftClick)
|
||||
interact_area_proc(false);
|
||||
|
||||
if (isInteracting)
|
||||
{
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
cursor.queue_play({interactArea.animationCursorActive});
|
||||
cursor.cursorState = Entity::ACTION;
|
||||
cursor.play({interactArea->animationCursorActive});
|
||||
|
||||
if (interactArea.digestionBonusRub > 0 && character.calories > 0 && !character.isDigesting)
|
||||
if (interactArea->digestionBonusOnHover > 0 && character.calories > 0 && !character.isDigesting)
|
||||
{
|
||||
auto mouseDelta = cursorWorldPosition - cursorWorldPositionPrevious;
|
||||
auto digestionBonus = (fabs(mouseDelta.x) + fabs(mouseDelta.y)) * interactArea.digestionBonusRub;
|
||||
character.digestionProgress += digestionBonus;
|
||||
if (fabs(mouseDelta.x) > 0.0f || fabs(mouseDelta.y) > 0.0f)
|
||||
character.digestionProgress += interactArea->digestionBonusOnHover;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +203,11 @@ namespace game::state::play
|
||||
|
||||
cursorWorldPositionPrevious = cursorWorldPosition;
|
||||
|
||||
if (character.isJustDigested && text.is_interruptible()) text.set(dialogue.get(dialogue.digest), character);
|
||||
if (character.isJustStageUp) text.set(dialogue.get(dialogue.stageUp), character);
|
||||
if (character.isJustDigested && text.is_interruptible())
|
||||
if (auto* digest = dialogue.get(Schema::Element::DIGEST))
|
||||
text.set(dialogue.dialogue_pool_entry_get(*digest), character);
|
||||
if (character.isJustStageUp)
|
||||
if (auto* stageUp = dialogue.get(Schema::Element::STAGE_UP))
|
||||
text.set(dialogue.dialogue_pool_entry_get(*stageUp), character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
#include "../../entity.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class CharacterManager
|
||||
@@ -20,10 +22,28 @@ namespace game::state::play
|
||||
bool isHoldInteractingPrevious{};
|
||||
bool isJustStoppedHoldInteracting{};
|
||||
int interactAreaID{-1};
|
||||
std::vector<int> interactAreaDialogueTimers{};
|
||||
|
||||
glm::vec2 cursorWorldPositionPrevious{};
|
||||
std::string queuedAnimation{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, Text&, Canvas&);
|
||||
struct Particle
|
||||
{
|
||||
std::string label{};
|
||||
glm::vec2 position{};
|
||||
};
|
||||
|
||||
struct Input
|
||||
{
|
||||
bool isImguiCaptureMouse{};
|
||||
bool isMouseLeftClick{};
|
||||
bool isMouseLeftDown{};
|
||||
bool isMouseLeftReleased{};
|
||||
};
|
||||
|
||||
std::optional<Input> inputOverride{};
|
||||
std::vector<Particle> particles{};
|
||||
|
||||
void update(Entity&, Entity&, Text&, Canvas&);
|
||||
};
|
||||
}
|
||||
|
||||
+23
-17
@@ -12,7 +12,7 @@ using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Cheats::update(Resources&, entity::Character& character, menu::Inventory& inventory)
|
||||
void Cheats::update(Resources&, Entity& character, menu::Inventory& inventory)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
@@ -20,16 +20,20 @@ namespace game::state::play
|
||||
{
|
||||
auto stage = character.stage + 1;
|
||||
auto maxCapacity = (float)character.max_capacity();
|
||||
auto capacityMin = (float)character.data.capacityMin;
|
||||
auto capacityMax = (float)character.data.capacityMax;
|
||||
auto weightMin = (float)character.data.weight;
|
||||
auto weightMax = (float)character.data.weightMax;
|
||||
auto digestionRateMin = (float)character.data.digestionRateMin;
|
||||
auto digestionRateMax = (float)character.data.digestionRateMax;
|
||||
auto eatSpeedMin = (float)character.data.eatSpeedMin;
|
||||
auto eatSpeedMax = (float)character.data.eatSpeedMax;
|
||||
auto capacityMin = (float)character.data.root()->capacityMinCalories;
|
||||
auto capacityMax = (float)character.data.root()->capacityMaxCalories;
|
||||
auto weightMin = (float)character.data.root()->weightKilograms;
|
||||
auto weightMax = (float)character.data.root()->weightMaxKilograms;
|
||||
auto digestionRateMin = (float)character.data.root()->digestionRateMin * Entity::UPDATE_RATE;
|
||||
auto digestionRateMax = (float)character.data.root()->digestionRateMax * Entity::UPDATE_RATE;
|
||||
auto digestionRate = character.digestion_rate_get();
|
||||
auto eatSpeedMin = (float)character.data.root()->eatSpeedMinMultiplier;
|
||||
auto eatSpeedMax = (float)character.data.root()->eatSpeedMaxMultiplier;
|
||||
|
||||
auto weight_update = [&]() { character.queue_idle_animation(); };
|
||||
auto weight_update = [&]()
|
||||
{
|
||||
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
|
||||
};
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCalories).c_str(), &character.calories, 0.0f, maxCapacity,
|
||||
"%0.0f kcal"));
|
||||
@@ -41,24 +45,26 @@ namespace game::state::play
|
||||
strings.get(Strings::CheatsWeightFormat).c_str())))
|
||||
weight_update();
|
||||
|
||||
auto stages = character.data.stages_get();
|
||||
if (WIDGET_FX(ImGui::SliderInt(strings.get(Strings::CheatsStage).c_str(), &stage, 1,
|
||||
(int)character.data.stages.size() + 1)))
|
||||
(int)stages.size() + 1)))
|
||||
{
|
||||
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
|
||||
character.stage = glm::clamp(0, stage - 1, (int)stages.size());
|
||||
character.weight =
|
||||
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
|
||||
character.stage == 0 ? character.data.root()->weightKilograms : stages.at(character.stage - 1)->thresholdKilograms;
|
||||
weight_update();
|
||||
}
|
||||
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &character.digestionRate,
|
||||
digestionRateMin, digestionRateMax,
|
||||
strings.get(Strings::CheatsDigestionRateFormat).c_str()));
|
||||
if (WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &digestionRate,
|
||||
digestionRateMin, digestionRateMax,
|
||||
strings.get(Strings::CheatsDigestionRateFormat).c_str())))
|
||||
character.digestionRate = digestionRate / Entity::UPDATE_RATE;
|
||||
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsEatSpeed).c_str(), &character.eatSpeed, eatSpeedMin,
|
||||
eatSpeedMax,
|
||||
strings.get(Strings::CheatsEatSpeedFormat).c_str()));
|
||||
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::CheatsDigestButton).c_str())))
|
||||
character.digestionProgress = entity::Character::DIGESTION_MAX;
|
||||
character.digestionProgress = Entity::DIGESTION_MAX;
|
||||
|
||||
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@ namespace game::state::play
|
||||
class Cheats
|
||||
{
|
||||
public:
|
||||
void update(Resources&, entity::Character&, menu::Inventory&);
|
||||
void update(Resources&, Entity&, menu::Inventory&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#include "debug.hpp"
|
||||
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
|
||||
Text& text)
|
||||
{
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
|
||||
ImGui::Text("Cursor Pos (Screen): %0.0f, %0.0f", cursor.position.x, cursor.position.y);
|
||||
ImGui::Text("Cursor Pos (World): %0.0f, %0.0f", cursorPosition.x, cursorPosition.y);
|
||||
|
||||
ImGui::SeparatorText("Animations");
|
||||
ImGui::Text("Now Playing: %s", character.animationMapReverse.at(character.animationIndex).c_str());
|
||||
|
||||
auto childSize = ImVec2(0, ImGui::GetContentRegionAvail().y / 3);
|
||||
|
||||
if (ImGui::BeginChild("##Animations", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.animations.size(); i++)
|
||||
{
|
||||
auto& animation = character.animations[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(animation.name.c_str())))
|
||||
character.play(animation.name.c_str(), entity::Actor::PLAY_FORCE);
|
||||
ImGui::SetItemTooltip("%s", animation.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SeparatorText("Dialogue");
|
||||
|
||||
if (character.data.dialogue.start.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Play Start Sequence"))) isStartSequenceRequested = true;
|
||||
if (character.data.dialogue.end.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button("Play End Sequence"))) isEndSequenceRequested = true;
|
||||
|
||||
if (ImGui::BeginChild("##Dialogue", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (int i = 0; i < (int)character.data.dialogue.entries.size(); i++)
|
||||
{
|
||||
auto& entry = character.data.dialogue.entries[i];
|
||||
ImGui::PushID(i);
|
||||
if (WIDGET_FX(ImGui::Selectable(entry.name.c_str()))) text.set(&entry, character);
|
||||
ImGui::SetItemTooltip("%s", entry.name.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
WIDGET_FX(ImGui::Checkbox("Show Nulls (Hitboxes)", &character.isShowNulls));
|
||||
WIDGET_FX(ImGui::Checkbox("Show World Bounds", &isBoundsDisplay));
|
||||
|
||||
if (!itemManager.items.empty())
|
||||
{
|
||||
ImGui::SeparatorText("Item");
|
||||
|
||||
for (int i = 0; i < (int)itemManager.items.size(); i++)
|
||||
{
|
||||
auto& item = itemManager.items[i];
|
||||
if (itemManager.heldItemIndex == i) ImGui::TextUnformatted("Held");
|
||||
ImGui::Text("Type: %i", item.schemaID);
|
||||
ImGui::Text("Position: %0.0f, %0.0f", item.position.x, item.position.y);
|
||||
ImGui::Text("Velocity: %0.0f, %0.0f", item.velocity.x, item.velocity.y);
|
||||
ImGui::Text("Durability: %i", item.durability);
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
|
||||
#include "item_manager.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class Debug
|
||||
{
|
||||
public:
|
||||
bool isBoundsDisplay{};
|
||||
bool isStartSequenceRequested{};
|
||||
bool isEndSequenceRequested{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, ItemManager&, Canvas&, Text&);
|
||||
};
|
||||
}
|
||||
+116
-99
@@ -14,119 +14,136 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Info::update(Resources& resources, entity::Character& character)
|
||||
float info_height_get()
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
|
||||
return ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER;
|
||||
}
|
||||
|
||||
void info_content_draw(Resources& resources, Entity& character, ImVec2 size)
|
||||
{
|
||||
static constexpr auto HALF_MULTIPLIER = 0.5f;
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto& strings = character.data.strings;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto childSize = ImVec2((size.x - style.ItemSpacing.x) * HALF_MULTIPLIER, size.y);
|
||||
|
||||
if (ImGui::BeginChild("##Weight", childSize))
|
||||
{
|
||||
auto* settings = resources.settings.root();
|
||||
auto system = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
|
||||
auto weight = character.weight_get(system);
|
||||
auto stage = character.stage_get();
|
||||
auto stageMax = character.stage_max_get();
|
||||
auto stageCount = character.stage_count_get();
|
||||
auto stageWeight = character.stage_threshold_get(stage, system);
|
||||
auto stageNextWeight = character.stage_threshold_next_get(system);
|
||||
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
|
||||
|
||||
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
|
||||
imgui::text_unformatted_fit_draw(weightString.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2),
|
||||
Font::HEADER_2);
|
||||
ImGui::SetItemTooltip("%s", weightString.c_str());
|
||||
|
||||
auto stageProgress = character.stage_progress_get();
|
||||
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
|
||||
strings.get(stage >= stageMax ? Strings::InfoProgressMax : Strings::InfoProgressToNextStage)
|
||||
.c_str());
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageCount,
|
||||
math::to_percent(stageProgress));
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
|
||||
if (stage >= stageMax)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
|
||||
else
|
||||
{
|
||||
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight, unitString);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Calories and Capacity", childSize))
|
||||
{
|
||||
auto& calories = character.calories;
|
||||
auto& capacity = character.capacity;
|
||||
|
||||
auto overstuffedPercent = std::max(ZERO_FLOAT, (calories - capacity) / (character.max_capacity() - capacity));
|
||||
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
|
||||
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
|
||||
character.is_over_capacity() ? character.max_capacity() : character.capacity);
|
||||
imgui::text_unformatted_fit_draw(caloriesString.c_str(),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2), Font::HEADER_2);
|
||||
ImGui::SetItemTooltip("%s", caloriesString.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
auto digestionProgress = character.isDigesting
|
||||
? (float)character.digestionTimer / character.data.root()->digestionTimerMax
|
||||
: character.digestionProgress / Entity::DIGESTION_MAX;
|
||||
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT),
|
||||
strings.get(character.isDigesting ? Strings::InfoDigesting : Strings::InfoDigestion).c_str());
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (character.isDigesting)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
|
||||
else if (digestionProgress <= ZERO_FLOAT)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
|
||||
else
|
||||
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
|
||||
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void Info::update(Resources& resources, Entity& character, float fadeAlpha)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto ALPHA_MIN = 0.0f;
|
||||
static constexpr auto ALPHA_MAX = 1.0f;
|
||||
static constexpr auto STYLE_VAR_COUNT = 1;
|
||||
if (fadeAlpha <= ALPHA_MIN) return;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER - (style.WindowPadding.x * 2.0f),
|
||||
ImGui::GetTextLineHeightWithSpacing() * HEIGHT_MULTIPLIER);
|
||||
info_height_get());
|
||||
auto pos = ImVec2((windowSize.x * 0.5f) - (size.x * 0.5f), style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Info", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
{
|
||||
auto childSize = ImVec2(ImGui::GetContentRegionAvail().x / 2, ImGui::GetContentRegionAvail().y);
|
||||
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
if (fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
|
||||
|
||||
if (ImGui::BeginChild("##Weight", childSize))
|
||||
{
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto stage = character.stage_get();
|
||||
auto stageMax = character.stage_max_get();
|
||||
auto stageWeight = character.stage_threshold_get(stage, system);
|
||||
auto stageNextWeight = character.stage_threshold_next_get(system);
|
||||
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
|
||||
|
||||
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_1);
|
||||
ImGui::TextUnformatted(weightString.c_str());
|
||||
ImGui::SetItemTooltip("%s", weightString.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto stageProgress = character.stage_progress_get();
|
||||
ImGui::ProgressBar(stageProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
strings.get(stage >= stageMax ? Strings::InfoProgressMax
|
||||
: Strings::InfoProgressToNextStage)
|
||||
.c_str());
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::Text(strings.get(Strings::InfoStageProgressFormat).c_str(), stage + 1, stageMax + 1,
|
||||
math::to_percent(stageProgress));
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, imgui::to_imvec4(color::GRAY));
|
||||
if (stage >= stageMax)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
|
||||
else
|
||||
{
|
||||
ImGui::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight, unitString);
|
||||
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight, unitString);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::BeginChild("##Calories and Capacity", childSize))
|
||||
{
|
||||
auto& calories = character.calories;
|
||||
auto& capacity = character.capacity;
|
||||
|
||||
auto overstuffedPercent = std::max(0.0f, (calories - capacity) / (character.max_capacity() - capacity));
|
||||
auto caloriesColor = ImVec4(1.0f, 1.0f - overstuffedPercent, 1.0f - overstuffedPercent, 1.0f);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_1);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
|
||||
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
|
||||
character.is_over_capacity() ? character.max_capacity() : character.capacity);
|
||||
ImGui::TextUnformatted(caloriesString.c_str());
|
||||
ImGui::SetItemTooltip("%s", caloriesString.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopFont();
|
||||
|
||||
auto digestionProgress = character.isDigesting
|
||||
? (float)character.digestionTimer / character.data.digestionTimerMax
|
||||
: character.digestionProgress / entity::Character::DIGESTION_MAX;
|
||||
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
|
||||
strings.get(character.isDigesting ? Strings::InfoDigesting
|
||||
: Strings::InfoDigestion)
|
||||
.c_str());
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
if (character.isDigesting)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
|
||||
else if (digestionProgress <= 0.0f)
|
||||
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
|
||||
else
|
||||
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
|
||||
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
|
||||
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * fadeAlpha);
|
||||
if (ImGui::Begin("##Info", nullptr, flags))
|
||||
info_content_draw(resources, character, ImGui::GetContentRegionAvail());
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(STYLE_VAR_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity.hpp"
|
||||
#include "../../resources.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
float info_height_get();
|
||||
void info_content_draw(Resources&, Entity&, ImVec2);
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
void update(Resources&, entity::Character&);
|
||||
void update(Resources&, Entity&, float fadeAlpha);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#include "reward.hpp"
|
||||
|
||||
#include "../../../util/math.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::state::play::item
|
||||
{
|
||||
int Reward::random_item_get(const resource::xml::Item& itemSchema, float chanceBonus)
|
||||
{
|
||||
const resource::xml::Item::Pool* pool{};
|
||||
auto totalWeight = 0.0f;
|
||||
|
||||
for (auto& id : itemSchema.rarityIDs)
|
||||
{
|
||||
auto& rarity = itemSchema.rarities[id];
|
||||
if (rarity.weight <= 0.0f) continue;
|
||||
totalWeight += rarity.weight * chanceBonus;
|
||||
}
|
||||
|
||||
if (totalWeight <= 0.0f) return INVALID_ID;
|
||||
|
||||
auto roll = math::random_roll(totalWeight);
|
||||
|
||||
for (auto& id : itemSchema.rarityIDs)
|
||||
{
|
||||
auto& rarity = itemSchema.rarities[id];
|
||||
auto weight = rarity.weight * chanceBonus;
|
||||
if (weight <= 0.0f) continue;
|
||||
|
||||
roll -= weight;
|
||||
if (roll <= 0.0f)
|
||||
{
|
||||
pool = &itemSchema.pools.at(id);
|
||||
rarity.sound.play();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pool || pool->empty()) return INVALID_ID;
|
||||
return (*pool)[(int)math::random_roll((float)pool->size())];
|
||||
}
|
||||
|
||||
void Reward::item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||
const resource::xml::Item& itemSchema, const ImVec4& bounds, menu::ItemEffectManager::Mode mode)
|
||||
{
|
||||
if (itemID < 0) return;
|
||||
|
||||
inventory.values[itemID]++;
|
||||
itemEffectManager.spawn(itemID, itemSchema, bounds, mode);
|
||||
}
|
||||
|
||||
int Reward::reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
|
||||
float rewardRollCount, menu::ItemEffectManager::Mode mode)
|
||||
{
|
||||
auto rollCountWhole = std::max(0, (int)std::floor(rewardRollCount));
|
||||
auto rollCountFraction = std::max(0.0f, rewardRollCount - (float)rollCountWhole);
|
||||
auto rollCount = rollCountWhole + (math::random_percent_roll(rollCountFraction) ? 1 : 0);
|
||||
auto rewardedItemCount = 0;
|
||||
|
||||
for (int i = 0; i < rollCount; i++)
|
||||
{
|
||||
if (!math::random_percent_roll(rewardChance)) continue;
|
||||
|
||||
auto itemID = random_item_get(itemSchema);
|
||||
if (itemID == INVALID_ID) continue;
|
||||
|
||||
item_give(itemID, inventory, itemEffectManager, itemSchema, bounds, mode);
|
||||
rewardedItemCount++;
|
||||
}
|
||||
|
||||
return rewardedItemCount;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../menu/inventory.hpp"
|
||||
#include "../menu/item_effect_manager.hpp"
|
||||
|
||||
namespace game::state::play::item
|
||||
{
|
||||
class Reward
|
||||
{
|
||||
public:
|
||||
static constexpr auto INVALID_ID = -1;
|
||||
|
||||
int random_item_get(const resource::xml::Item& itemSchema, float chanceBonus = 1.0f);
|
||||
void item_give(int itemID, menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||
const resource::xml::Item& itemSchema, const ImVec4& bounds,
|
||||
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
|
||||
int reward_random_items_try(menu::Inventory& inventory, menu::ItemEffectManager& itemEffectManager,
|
||||
const resource::xml::Item& itemSchema, const ImVec4& bounds, float rewardChance,
|
||||
float rewardRollCount,
|
||||
menu::ItemEffectManager::Mode mode = menu::ItemEffectManager::FALL_DOWN);
|
||||
};
|
||||
}
|
||||
+148
-57
@@ -17,17 +17,45 @@ namespace game::state::play
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int durability_animation_index_get(const resource::xml::Item& schema, const resource::xml::Anm2& anm2, int durability,
|
||||
int durability_animation_index_get(const resource::xml::Schema& schema, const resource::xml::Anm2& anm2,
|
||||
int durability, int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
|
||||
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
}
|
||||
|
||||
int durability_animation_index_get(const resource::xml::Schema& schema, const Entity& entity, int durability,
|
||||
int durabilityMax)
|
||||
{
|
||||
if (durability >= durabilityMax) return -1;
|
||||
|
||||
auto animationName = schema.animations.chew + std::to_string(std::max(0, durability));
|
||||
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
|
||||
auto animationName = schema.root()->animationChew + std::to_string(std::max(0, durability));
|
||||
return entity.animationMap.contains(animationName) ? entity.animationMap.at(animationName) : -1;
|
||||
}
|
||||
|
||||
bool is_finite(glm::vec4 value)
|
||||
{
|
||||
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z) && std::isfinite(value.w) &&
|
||||
value.z > 0.0f && value.w > 0.0f;
|
||||
}
|
||||
|
||||
glm::vec2 item_spawn_position_get(Entity& character, const glm::vec4& bounds)
|
||||
{
|
||||
auto rect = character.rect();
|
||||
if (!is_finite(rect)) rect = bounds;
|
||||
auto center = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w);
|
||||
auto radius = rect.w * 0.5f;
|
||||
auto angle = glm::pi<float>() + math::random_max(glm::pi<float>());
|
||||
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
|
||||
|
||||
return {glm::clamp(position.x, bounds.x, bounds.x + bounds.z),
|
||||
glm::clamp(position.y, bounds.y, bounds.y + bounds.w)};
|
||||
}
|
||||
}
|
||||
|
||||
void ItemManager::update(entity::Character& character, entity::Cursor& cursor, AreaManager& areaManager, Text& text,
|
||||
void ItemManager::update(Entity& character, Entity& cursor, AreaManager& areaManager, Text& text,
|
||||
const glm::vec4& bounds, Canvas& canvas)
|
||||
{
|
||||
static constexpr float ROTATION_MAX = 90.0f;
|
||||
@@ -38,13 +66,33 @@ namespace game::state::play
|
||||
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto& cursorSchema = character.data.cursorSchema;
|
||||
auto& area = character.data.areaSchema.areas.at(areaManager.get(character));
|
||||
auto* cursorRoot = cursorSchema.root();
|
||||
auto areaFallback = resource::xml::Schema::AreaEntry{.gravity = 0.95f, .friction = 0.80f, .airResistance = 0.975f};
|
||||
auto* areaSelected = areaManager.get(character);
|
||||
auto& area = areaSelected ? *areaSelected : areaFallback;
|
||||
auto& friction = area.friction;
|
||||
auto& airResistance = area.airResistance;
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto isOverCapacity = character.is_over_capacity();
|
||||
auto dialogue_pool_entry_get = [&](resource::xml::Schema::Element::Type type)
|
||||
{
|
||||
auto* reference = dialogue.get(type);
|
||||
return reference ? dialogue.dialogue_pool_entry_get(*reference) : nullptr;
|
||||
};
|
||||
auto particles_queue =
|
||||
[&](resource::xml::Schema& particleSchema, resource::xml::Schema::Element::Type type, glm::vec2 position)
|
||||
{
|
||||
auto* root = particleSchema.root();
|
||||
auto* actions = root ? particleSchema.child_get(*root, resource::xml::Schema::Element::ACTIONS) : nullptr;
|
||||
auto* action = actions ? particleSchema.child_get(*actions, type) : nullptr;
|
||||
if (!action) return;
|
||||
for (auto* particle : particleSchema.children_get(*action, resource::xml::Schema::Element::PARTICLE))
|
||||
if (particle && !particle->typeString.empty())
|
||||
particles.push_back({.label = particle->typeString, .position = position});
|
||||
};
|
||||
|
||||
auto cursorPosition = canvas.screen_position_convert(cursor.position);
|
||||
auto cursorPosition =
|
||||
cursor.entityType == AUTOCURSOR ? cursor.position : canvas.screen_position_convert(cursor.position);
|
||||
auto cursorDelta = cursorPosition - cursorPositionPrevious;
|
||||
|
||||
auto isImguiCaptureMouse = ImGui::GetIO().WantCaptureMouse;
|
||||
@@ -54,9 +102,22 @@ namespace game::state::play
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto isAutomation = this->isAutomation;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
if (inputOverride)
|
||||
{
|
||||
isImguiCaptureMouse = inputOverride->isImguiCaptureMouse;
|
||||
isMouseLeftClicked = inputOverride->isMouseLeftClicked;
|
||||
isMouseLeftDown = inputOverride->isMouseLeftDown;
|
||||
isMouseLeftReleased = inputOverride->isMouseLeftReleased;
|
||||
isMouseRightClicked = inputOverride->isMouseRightClicked;
|
||||
isMouseRightDown = inputOverride->isMouseRightDown;
|
||||
isAutomation = isAutomation || inputOverride->isAutomation;
|
||||
inputOverride.reset();
|
||||
}
|
||||
|
||||
if (isDisabled)
|
||||
{
|
||||
if (heldItemIndex != -1)
|
||||
@@ -77,41 +138,42 @@ namespace game::state::play
|
||||
|
||||
if (isJustItemHoveredStopped)
|
||||
{
|
||||
cursor.queue_default_animation();
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
isJustItemHoveredStopped = false;
|
||||
}
|
||||
|
||||
if (isJustItemHeldStopped || isJustItemThrown)
|
||||
{
|
||||
cursor.queue_default_animation();
|
||||
if (!isJustItemThrown && character.queuedPlay.empty()) character.queue_idle_animation();
|
||||
cursor.play({cursor.defaultAnimation});
|
||||
if (!isJustItemThrown && !character.is_animation_request())
|
||||
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
|
||||
isJustItemHeldStopped = false;
|
||||
isJustItemThrown = false;
|
||||
}
|
||||
|
||||
isItemHoveredPrevious = isItemHovered;
|
||||
isItemHovered = false;
|
||||
particles.clear();
|
||||
if (isItemHovered != isItemHoveredPrevious && !isItemHovered) isJustItemHoveredStopped = true;
|
||||
|
||||
for (auto& id : queuedItemIDs)
|
||||
{
|
||||
auto spawnBounds = character.rect();
|
||||
auto position = glm::vec2(math::random_in_range(spawnBounds.x, spawnBounds.x + spawnBounds.z),
|
||||
math::random_in_range(spawnBounds.y, spawnBounds.y + spawnBounds.w));
|
||||
auto position = item_spawn_position_get(character, bounds);
|
||||
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto& item = itemSchema.items.at(id);
|
||||
auto& anm2 = itemSchema.anm2s.at(id);
|
||||
auto durabilityMax = item.durability.value_or(itemSchema.durability);
|
||||
auto durabilityMax = item.isDurability ? item.durability : itemSchema.durability;
|
||||
auto animationIndex = durability_animation_index_get(itemSchema, anm2, 0, durabilityMax);
|
||||
items.emplace_back(anm2, position, id, 0, animationIndex);
|
||||
particles_queue(itemSchema, resource::xml::Schema::Element::SUMMON, position);
|
||||
}
|
||||
queuedItemIDs.clear();
|
||||
|
||||
if (isMouseRightDown)
|
||||
{
|
||||
auto animation = cursorSchema.animations.return_.get();
|
||||
if (animation) cursor.queue_play({*animation});
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationReturn.get()) cursor.play({*animation});
|
||||
}
|
||||
|
||||
if (auto heldItem = vector::find(items, heldItemIndex))
|
||||
@@ -131,33 +193,47 @@ namespace game::state::play
|
||||
|
||||
if (schema.categories[item.categoryID].isEdible)
|
||||
{
|
||||
auto& durabilityMax = item.durability.has_value() ? *item.durability : schema.durability;
|
||||
auto caloriesPerBite = item.calories.has_value() && durabilityMax > 0 ? *item.calories / durabilityMax : 0;
|
||||
auto durabilityMax = item.isDurability ? item.durability : schema.durability;
|
||||
auto caloriesPerBite = item.isCalories && durabilityMax > 0 ? item.calories / durabilityMax : 0;
|
||||
auto isCanEat = character.calories + caloriesPerBite <= character.max_capacity();
|
||||
|
||||
if (isJustItemHeld)
|
||||
{
|
||||
if (isCanEat)
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.feedFull : dialogue.feed), character);
|
||||
else if (caloriesPerBite > character.capacity)
|
||||
text.set(dialogue.get(dialogue.lowCapacity), character);
|
||||
else
|
||||
text.set(dialogue.get(dialogue.full), character);
|
||||
if (!isAutomation)
|
||||
{
|
||||
if (isCanEat)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FEED_FULL
|
||||
: resource::xml::Schema::Element::FEED),
|
||||
character);
|
||||
else if (caloriesPerBite > character.capacity)
|
||||
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::LOW_CAPACITY), character);
|
||||
else
|
||||
text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::FULL), character);
|
||||
}
|
||||
isJustItemHeld = false;
|
||||
}
|
||||
|
||||
for (auto& eatArea : character.data.eatAreas)
|
||||
for (auto* eatArea : character.data.eat_areas_get())
|
||||
{
|
||||
heldItem = vector::find(items, heldItemIndex);
|
||||
if (!heldItem) break;
|
||||
|
||||
auto rect = character.null_frame_rect(eatArea.nullID);
|
||||
auto nullID = character.data.null_id_get(eatArea->null);
|
||||
auto rect = character.null_frame_rect(nullID);
|
||||
if (!is_finite(rect))
|
||||
{
|
||||
auto animationIndex =
|
||||
character.animation_index_get(eatArea->animation + character.animation_append_id_get());
|
||||
rect = character.null_frame_rect(nullID, animationIndex, 0.0f);
|
||||
}
|
||||
|
||||
if (isCanEat && math::is_point_in_rectf(rect, heldItem->position))
|
||||
{
|
||||
character.queue_play({.animation = eatArea.animation, .speedMultiplier = character.eatSpeed});
|
||||
character.play({.animation = eatArea->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.speedMultiplier = character.eatSpeed});
|
||||
|
||||
if (character.playedEventID == eatArea.eventID)
|
||||
if (character.playedEventID == character.data.event_id_get(eatArea->event))
|
||||
{
|
||||
heldItem->durability++;
|
||||
character.consume_played_event();
|
||||
@@ -165,23 +241,24 @@ namespace game::state::play
|
||||
character.calories += caloriesPerBite;
|
||||
character.totalCaloriesConsumed += caloriesPerBite;
|
||||
|
||||
if (item.capacityBonus.has_value())
|
||||
if (item.isCapacityBonus)
|
||||
{
|
||||
character.capacity += *item.capacityBonus / durabilityMax;
|
||||
character.capacity =
|
||||
glm::clamp(character.capacity, (float)character.data.capacityMin, (float)character.data.capacityMax);
|
||||
character.capacity += item.capacityBonus / durabilityMax;
|
||||
character.capacity = glm::clamp(character.capacity, (float)character.data.root()->capacityMinCalories,
|
||||
(float)character.data.root()->capacityMaxCalories);
|
||||
}
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
if (item.isEatSpeedBonus)
|
||||
{
|
||||
character.eatSpeed += *item.eatSpeedBonus / durabilityMax;
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.eatSpeedMin,
|
||||
(float)character.data.eatSpeedMax);
|
||||
character.eatSpeed += item.eatSpeedBonus / durabilityMax;
|
||||
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.root()->eatSpeedMinMultiplier,
|
||||
(float)character.data.root()->eatSpeedMaxMultiplier);
|
||||
}
|
||||
if (item.digestionBonus.has_value())
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
character.digestionRate += *item.digestionBonus / durabilityMax;
|
||||
character.digestionRate = glm::clamp(character.digestionRate, (float)character.data.digestionRateMin,
|
||||
(float)character.data.digestionRateMax);
|
||||
character.digestionRate += item.digestionBonus / durabilityMax;
|
||||
character.digestionRate =
|
||||
glm::clamp(character.digestionRate, (float)character.data.root()->digestionRateMin,
|
||||
(float)character.data.root()->digestionRateMax);
|
||||
}
|
||||
|
||||
if (heldItem->durability >= durabilityMax)
|
||||
@@ -195,7 +272,7 @@ namespace game::state::play
|
||||
{
|
||||
auto animationIndex =
|
||||
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
|
||||
heldItem->play(animationIndex, entity::Actor::SET);
|
||||
heldItem->play({.index = animationIndex, .mode = Entity::SET});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,12 +281,12 @@ namespace game::state::play
|
||||
{
|
||||
if (fabs(delta.x) >= THROW_THRESHOLD || fabs(delta.y) >= THROW_THRESHOLD)
|
||||
{
|
||||
cursorSchema.sounds.throw_.play();
|
||||
text.set(dialogue.get(dialogue.throw_), character);
|
||||
if (cursorRoot) cursorRoot->soundThrow.play();
|
||||
if (!isAutomation) text.set(dialogue_pool_entry_get(resource::xml::Schema::Element::THROW), character);
|
||||
isJustItemThrown = true;
|
||||
}
|
||||
else
|
||||
cursorSchema.sounds.release.play();
|
||||
else if (cursorRoot)
|
||||
cursorRoot->soundRelease.play();
|
||||
|
||||
heldItem->velocity -= delta;
|
||||
heldItemIndex = -1;
|
||||
@@ -217,11 +294,14 @@ namespace game::state::play
|
||||
}
|
||||
|
||||
// Food stolen
|
||||
if (auto animation = character.animation_get(character.animation_name_convert(eatArea.animation));
|
||||
if (auto animation = character.animation_get(eatArea->animation + character.animation_append_id_get());
|
||||
animation && character.is_playing(animation->name))
|
||||
{
|
||||
if (!math::is_point_in_rectf(rect, heldItem->position))
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.foodTakenFull : dialogue.foodTaken), character);
|
||||
if (!isAutomation)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::FOOD_TAKEN_FULL
|
||||
: resource::xml::Schema::Element::FOOD_TAKEN),
|
||||
character);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,7 +309,10 @@ namespace game::state::play
|
||||
|
||||
if (auto animation = character.animation_get(); character.time >= animation->frameNum && isQueueFinishFood)
|
||||
{
|
||||
text.set(dialogue.get(isOverCapacity ? dialogue.eatFull : dialogue.eat), character);
|
||||
if (!isAutomation)
|
||||
text.set(dialogue_pool_entry_get(isOverCapacity ? resource::xml::Schema::Element::EAT_FULL
|
||||
: resource::xml::Schema::Element::EAT),
|
||||
character);
|
||||
isQueueFinishFood = false;
|
||||
}
|
||||
|
||||
@@ -246,27 +329,31 @@ namespace game::state::play
|
||||
auto& schemaItem = schema.items[item.schemaID];
|
||||
auto& rotationOverride = item.overrides[item.rotationOverrideID];
|
||||
auto& rotation = *rotationOverride.frame.rotation;
|
||||
auto& gravity = schemaItem.gravity.has_value() ? *schemaItem.gravity : area.gravity;
|
||||
auto& gravity = schemaItem.isGravity ? schemaItem.gravity : area.gravity;
|
||||
|
||||
item.update();
|
||||
|
||||
if (math::is_point_in_rectf(item.rect(), cursorPosition) && !isImguiCaptureMouse)
|
||||
{
|
||||
isItemHovered = true;
|
||||
if (auto animation = cursorSchema.animations.hover.get()) cursor.queue_play({*animation});
|
||||
cursor.state = entity::Cursor::HOVER;
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationHover.get())
|
||||
cursor.play({.animation = *animation, .transition = Entity::Transition::IF_IDLE});
|
||||
cursor.cursorState = Entity::HOVER;
|
||||
|
||||
if (isMouseLeftClicked)
|
||||
{
|
||||
cursorSchema.sounds.grab.play();
|
||||
if (cursorRoot) cursorRoot->soundGrab.play();
|
||||
particles_queue(character.data.cursorSchema, resource::xml::Schema::Element::GRAB, cursorPosition);
|
||||
isJustItemHeld = true;
|
||||
}
|
||||
|
||||
if (isMouseLeftDown)
|
||||
{
|
||||
isItemHeld = true;
|
||||
if (auto animation = cursorSchema.animations.grab.get()) cursor.queue_play({*animation});
|
||||
cursor.state = entity::Cursor::ACTION;
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationGrab.get()) cursor.play({*animation});
|
||||
cursor.cursorState = Entity::ACTION;
|
||||
heldItemIndex = i;
|
||||
heldItemMoveIndex = i;
|
||||
}
|
||||
@@ -274,10 +361,14 @@ namespace game::state::play
|
||||
if (isMouseRightClicked)
|
||||
{
|
||||
if (item.durability > 0)
|
||||
schema.sounds.dispose.play();
|
||||
{
|
||||
schema.root()->soundDispose.play();
|
||||
particles_queue(schema, resource::xml::Schema::Element::DISPOSE, item.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
schema.sounds.return_.play();
|
||||
schema.root()->soundReturn.play();
|
||||
particles_queue(schema, resource::xml::Schema::Element::RETURN_, item.position);
|
||||
returnItemIDs.emplace_back(item.schemaID);
|
||||
}
|
||||
|
||||
@@ -303,7 +394,7 @@ namespace game::state::play
|
||||
item.angularVelocity *= friction;
|
||||
item.angularVelocity = -item.angularVelocity;
|
||||
rotation = -rotation;
|
||||
schema.sounds.bounce.play();
|
||||
schema.root()->soundBounce.play();
|
||||
}
|
||||
|
||||
if (item.position.y <= bounds.y || item.position.y >= bounds.w)
|
||||
@@ -331,7 +422,7 @@ namespace game::state::play
|
||||
{
|
||||
item.velocity.y = -item.velocity.y;
|
||||
item.angularVelocity *= friction;
|
||||
schema.sounds.bounce.play();
|
||||
schema.root()->soundBounce.play();
|
||||
}
|
||||
|
||||
item.velocity.y *= friction;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity/cursor.hpp"
|
||||
#include "../../entity/item.hpp"
|
||||
#include "../../entity.hpp"
|
||||
|
||||
#include "area_manager.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class ItemManager
|
||||
@@ -15,7 +15,8 @@ namespace game::state::play
|
||||
static constexpr auto LIMIT = 100;
|
||||
|
||||
bool isDisabled{};
|
||||
std::vector<entity::Item> items{};
|
||||
bool isAutomation{};
|
||||
std::vector<Entity> items{};
|
||||
int heldItemIndex{-1};
|
||||
int queuedRemoveItemIndex{-1};
|
||||
|
||||
@@ -39,6 +40,26 @@ namespace game::state::play
|
||||
std::vector<int> queuedItemIDs{};
|
||||
std::vector<int> returnItemIDs{};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
|
||||
struct Particle
|
||||
{
|
||||
std::string label{};
|
||||
glm::vec2 position{};
|
||||
};
|
||||
|
||||
struct Input
|
||||
{
|
||||
bool isImguiCaptureMouse{};
|
||||
bool isMouseLeftClicked{};
|
||||
bool isMouseLeftDown{};
|
||||
bool isMouseLeftReleased{};
|
||||
bool isMouseRightClicked{};
|
||||
bool isMouseRightDown{};
|
||||
bool isAutomation{};
|
||||
};
|
||||
|
||||
std::optional<Input> inputOverride{};
|
||||
std::vector<Particle> particles{};
|
||||
|
||||
void update(Entity&, Entity&, AreaManager&, Text&, const glm::vec4& bounds, Canvas&);
|
||||
};
|
||||
}
|
||||
|
||||
+89
-61
@@ -5,48 +5,60 @@
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Menu::tick()
|
||||
{
|
||||
inventory.tick();
|
||||
arcade.tick();
|
||||
}
|
||||
|
||||
void Menu::update(Resources& resources, ItemManager& itemManager, entity::Character& character,
|
||||
entity::Cursor& cursor, Text& text, Canvas& canvas)
|
||||
void Menu::update(Resources& resources, ItemManager& itemManager, Entity& character, Text& text, Autofeed& autofeed)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HALF_MULTIPLIER = 0.5f;
|
||||
static constexpr auto BAR_DIVIDER_THICKNESS = 1.0f;
|
||||
static constexpr auto PADDING_SIZE_MULTIPLIER = 2.0f;
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
static constexpr auto ONE_FLOAT = 1.0f;
|
||||
static constexpr auto MENU_STYLE_VAR_COUNT = 2;
|
||||
static constexpr auto MENU_BAR_STYLE_VAR_COUNT = 4;
|
||||
static constexpr auto MENU_BAR_FLAGS = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
|
||||
|
||||
auto& schema = character.data.menuSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
isDebugOpen = false;
|
||||
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
fullscreenSlide.update(isOpen && isFullscreen, io.DeltaTime);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
auto barSize =
|
||||
ImVec2(imgui::side_bar_width_get(), windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
|
||||
auto fullscreenOffset = barSize.x;
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
|
||||
auto normalSize =
|
||||
ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
|
||||
auto fullscreenSize =
|
||||
ImVec2(windowSize.x - fullscreenOffset, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
|
||||
auto fullscreenT = fullscreenSlide.eased_get();
|
||||
auto size = ImVec2(normalSize.x + (fullscreenSize.x - normalSize.x) * fullscreenT,
|
||||
normalSize.y + (fullscreenSize.y - normalSize.y) * fullscreenT);
|
||||
auto targetX = windowSize.x - size.x;
|
||||
auto t = slide.value_get();
|
||||
auto eased = slide.eased_get();
|
||||
auto isFullscreenVisible = fullscreenSlide.value_get() > ZERO_FLOAT;
|
||||
auto isFullscreenResizeSettled =
|
||||
isFullscreen ? fullscreenSlide.value_get() >= ONE_FLOAT : fullscreenSlide.value_get() <= ZERO_FLOAT;
|
||||
auto posX = windowSize.x + (targetX - windowSize.x) * eased;
|
||||
|
||||
auto pos = ImVec2(posX, style.WindowPadding.y);
|
||||
auto barSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), windowSize.y - style.WindowPadding.y * 2);
|
||||
auto barPos = ImVec2(pos.x - barSize.x - style.WindowPadding.x, style.WindowPadding.y);
|
||||
auto barPos = ImVec2(pos.x - barSize.x, style.WindowPadding.y);
|
||||
|
||||
if (slide.is_visible())
|
||||
{
|
||||
@@ -62,19 +74,19 @@ namespace game::state::play
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
|
||||
{
|
||||
interact.update(resources, text, character);
|
||||
interact.update(resources, text, character, isFullscreenVisible);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabGather).c_str())))
|
||||
{
|
||||
arcade.update(resources, character, cursor, inventory, text, toasts);
|
||||
gather.update(isFullscreen, isFullscreenResizeSettled);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
|
||||
{
|
||||
inventory.update(resources, itemManager, character);
|
||||
inventory.update(resources, itemManager, character, autofeed);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
@@ -90,15 +102,6 @@ namespace game::state::play
|
||||
cheats.update(resources, character, inventory);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Debug")))
|
||||
{
|
||||
isDebugOpen = true;
|
||||
debug.update(character, cursor, itemManager, canvas, text);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
@@ -110,59 +113,84 @@ namespace game::state::play
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::Begin("##Menu Open Bar", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, barSize);
|
||||
if (ImGui::Begin("##Menu Open Bar", nullptr, MENU_BAR_FLAGS))
|
||||
{
|
||||
auto buttonSize = ImGui::GetContentRegionAvail();
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
auto halfButtonSize = ImVec2(buttonSize.x, buttonSize.y * HALF_MULTIPLIER);
|
||||
auto activeButtonSize = isOpen && !isFullscreenVisible ? halfButtonSize : buttonSize;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
auto result = WIDGET_FX(ImGui::Button("##MenuToggle", buttonSize));
|
||||
auto tooltip_set = [&](const std::string& tooltip)
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
ImGui::SetItemTooltip("%s", tooltip.c_str());
|
||||
ImGui::PopStyleVar();
|
||||
};
|
||||
|
||||
auto isMainResult = WIDGET_FX(ImGui::Button("##MenuToggleNormal", activeButtonSize));
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip : Strings::MenuOpenTooltip).c_str());
|
||||
if (result)
|
||||
tooltip_set(strings.get(isFullscreenVisible ? Strings::MenuRestoreTooltip
|
||||
: isOpen ? Strings::MenuCloseTooltip
|
||||
: Strings::MenuOpenTooltip));
|
||||
if (isMainResult)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
|
||||
if (isOpen)
|
||||
schema.sounds.open.play();
|
||||
if (isFullscreenVisible)
|
||||
{
|
||||
if (isFullscreen) schema.root()->soundClose.play();
|
||||
isFullscreen = false;
|
||||
}
|
||||
else
|
||||
schema.sounds.close.play();
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
isFullscreen = false;
|
||||
if (isOpen)
|
||||
schema.root()->soundOpen.play();
|
||||
else
|
||||
schema.root()->soundClose.play();
|
||||
}
|
||||
}
|
||||
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
|
||||
if (!isOpen && !isMainResult && ImGui::IsItemHovered())
|
||||
{
|
||||
isOpen = true;
|
||||
schema.sounds.open.play();
|
||||
isFullscreen = false;
|
||||
schema.root()->soundOpen.play();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
auto center = ImVec2(cursorPos.x + (buttonSize.x * 0.5f), cursorPos.y + (buttonSize.y * 0.5f));
|
||||
auto half = std::min(buttonSize.x, buttonSize.y) * 0.22f;
|
||||
ImVec2 tip;
|
||||
ImVec2 baseA;
|
||||
ImVec2 baseB;
|
||||
if (isOpen)
|
||||
auto fullscreenCursorPos = ImGui::GetCursorScreenPos();
|
||||
auto isFullscreenResult = false;
|
||||
if (isOpen && !isFullscreenVisible)
|
||||
{
|
||||
tip = ImVec2(center.x + half, center.y);
|
||||
baseA = ImVec2(center.x - half, center.y - half);
|
||||
baseB = ImVec2(center.x - half, center.y + half);
|
||||
}
|
||||
else
|
||||
{
|
||||
tip = ImVec2(center.x - half, center.y);
|
||||
baseA = ImVec2(center.x + half, center.y - half);
|
||||
baseB = ImVec2(center.x + half, center.y + half);
|
||||
isFullscreenResult = WIDGET_FX(ImGui::Button("##MenuToggleFullscreen", halfButtonSize));
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
{
|
||||
tooltip_set(strings.get(Strings::MenuOpenFullscreenTooltip));
|
||||
if (isFullscreenResult)
|
||||
{
|
||||
isFullscreen = true;
|
||||
schema.root()->soundOpen.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
auto direction = !isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
|
||||
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, activeButtonSize, direction,
|
||||
ImGui::GetColorU32(ImGuiCol_Text));
|
||||
if (isOpen && !isFullscreenVisible)
|
||||
{
|
||||
auto separatorY = cursorPos.y + halfButtonSize.y;
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(cursorPos.x, separatorY),
|
||||
ImVec2(cursorPos.x + buttonSize.x, separatorY),
|
||||
ImGui::GetColorU32(ImGuiCol_Separator), BAR_DIVIDER_THICKNESS);
|
||||
imgui::triangle_draw(*ImGui::GetWindowDrawList(), fullscreenCursorPos, halfButtonSize,
|
||||
imgui::TriangleDirection::LEFT, ImGui::GetColorU32(ImGuiCol_Text));
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(MENU_BAR_STYLE_VAR_COUNT);
|
||||
ImGui::PopStyleVar(MENU_STYLE_VAR_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -4,13 +4,11 @@
|
||||
|
||||
#include "../settings_menu.hpp"
|
||||
|
||||
#include "menu/arcade.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "debug.hpp"
|
||||
#include "menu/gather.hpp"
|
||||
#include "menu/interact.hpp"
|
||||
#include "menu/inventory.hpp"
|
||||
#include "text.hpp"
|
||||
#include "menu/toasts.hpp"
|
||||
|
||||
#include "../../util/imgui/window_slide.hpp"
|
||||
|
||||
@@ -19,13 +17,10 @@ namespace game::state::play
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
menu::Arcade arcade;
|
||||
menu::Interact interact;
|
||||
menu::Gather gather;
|
||||
Cheats cheats;
|
||||
Debug debug;
|
||||
menu::Inventory inventory;
|
||||
menu::Toasts toasts;
|
||||
|
||||
state::SettingsMenu settingsMenu;
|
||||
|
||||
#if DEBUG
|
||||
@@ -35,10 +30,14 @@ namespace game::state::play
|
||||
#endif
|
||||
|
||||
bool isOpen{true};
|
||||
bool isDebugOpen{};
|
||||
bool isFullscreen{};
|
||||
static constexpr auto SLIDE_DURATION = 0.125f;
|
||||
static constexpr auto SLIDE_CLOSED = 0.0f;
|
||||
util::imgui::WindowSlide slide{};
|
||||
util::imgui::WindowSlide fullscreenSlide{SLIDE_DURATION, SLIDE_CLOSED};
|
||||
|
||||
void tick();
|
||||
void update(Resources&, ItemManager&, entity::Character&, entity::Cursor&, Text&, Canvas&);
|
||||
bool is_fullscreen_visible_get() const { return isOpen && (isFullscreen || fullscreenSlide.is_visible()); }
|
||||
float fullscreen_alpha_get() const { return fullscreenSlide.eased_get(); }
|
||||
void update(Resources&, ItemManager&, Entity&, Text&, Autofeed&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
#include "arcade.hpp"
|
||||
|
||||
#include "../../../util/imgui/widget.hpp"
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct GameInfoStrings
|
||||
{
|
||||
Strings::Type name;
|
||||
Strings::Type description;
|
||||
Strings::Type howToPlay;
|
||||
};
|
||||
}
|
||||
|
||||
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
|
||||
|
||||
void Arcade::game_reset(entity::Character& character, Game gameCurrent)
|
||||
{
|
||||
switch (gameCurrent)
|
||||
{
|
||||
case SKILL_CHECK:
|
||||
skillCheck.reset(character);
|
||||
break;
|
||||
case DUNGEON:
|
||||
dungeon.reset(character);
|
||||
break;
|
||||
case ORBIT:
|
||||
orbit.reset(character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Arcade::tick()
|
||||
{
|
||||
skillCheck.tick();
|
||||
dungeon.tick();
|
||||
orbit.tick();
|
||||
}
|
||||
|
||||
void Arcade::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
|
||||
Text& text, Toasts& toasts)
|
||||
{
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto& strings = character.data.strings;
|
||||
auto game_info_strings_get = [&](Game gameCurrent) -> GameInfoStrings
|
||||
{
|
||||
switch (gameCurrent)
|
||||
{
|
||||
case SKILL_CHECK:
|
||||
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription,
|
||||
Strings::ArcadeSkillCheckHowToPlay};
|
||||
case DUNGEON:
|
||||
return {Strings::ArcadeDungeonName, Strings::ArcadeDungeonDescription, Strings::ArcadeDungeonHowToPlay};
|
||||
case ORBIT:
|
||||
return {Strings::ArcadeOrbitName, Strings::ArcadeOrbitDescription, Strings::ArcadeOrbitHowToPlay};
|
||||
}
|
||||
|
||||
return {Strings::ArcadeSkillCheckName, Strings::ArcadeSkillCheckDescription, Strings::ArcadeSkillCheckHowToPlay};
|
||||
};
|
||||
auto game_header_draw = [&](Game gameCurrent)
|
||||
{
|
||||
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
ImGui::TextUnformatted(strings.get(gameInfoStrings.name).c_str());
|
||||
ImGui::PopFont();
|
||||
};
|
||||
|
||||
auto game_menu_draw = [&](Game gameCurrent)
|
||||
{
|
||||
constexpr auto GAME_CHILD_HEIGHT_MULTIPLIER = 7.0f;
|
||||
constexpr auto GAME_DESCRIPTION_HEIGHT_MULTIPLIER = 4.75f;
|
||||
|
||||
auto lineHeight = ImGui::GetTextLineHeightWithSpacing();
|
||||
auto gameChildHeight = lineHeight * GAME_CHILD_HEIGHT_MULTIPLIER;
|
||||
auto gameDescriptionHeight = lineHeight * GAME_DESCRIPTION_HEIGHT_MULTIPLIER;
|
||||
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||
auto detailsChildID = [gameCurrent]()
|
||||
{
|
||||
switch (gameCurrent)
|
||||
{
|
||||
case SKILL_CHECK:
|
||||
return "##ArcadeSkillCheckDescription";
|
||||
case DUNGEON:
|
||||
return "##ArcadeDungeonDescription";
|
||||
case ORBIT:
|
||||
return "##ArcadeOrbitDescription";
|
||||
}
|
||||
|
||||
return "##ArcadeDescription";
|
||||
}();
|
||||
|
||||
if (ImGui::BeginChild(gameInfoStrings.name, {0, gameChildHeight}, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
|
||||
ImGui::BeginChild(detailsChildID, {0, gameDescriptionHeight});
|
||||
game_header_draw(gameCurrent);
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.description).c_str());
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||
{
|
||||
game_reset(character, gameCurrent);
|
||||
game = gameCurrent;
|
||||
state = GAMEPLAY;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeInfoButton).c_str(), ImVec2(buttonWidth, 0))))
|
||||
{
|
||||
game = gameCurrent;
|
||||
state = INFO;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
};
|
||||
|
||||
auto game_info_sections_draw = [&](Game gameCurrent)
|
||||
{
|
||||
auto gameInfoStrings = game_info_strings_get(gameCurrent);
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeHowToPlay).c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Separator();
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::NORMAL);
|
||||
ImGui::TextWrapped("%s", strings.get(gameInfoStrings.howToPlay).c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeStats).c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Separator();
|
||||
};
|
||||
|
||||
auto game_stats_draw = [&](Game gameCurrent)
|
||||
{
|
||||
switch (gameCurrent)
|
||||
{
|
||||
case SKILL_CHECK:
|
||||
{
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), skillCheck.highScore,
|
||||
skillCheck.bestCombo);
|
||||
ImGui::Text(strings.get(Strings::ArcadeTotalSkillChecksFormat).c_str(), skillCheck.totalPlays);
|
||||
|
||||
for (int i = 0; i < (int)schema.grades.size(); i++)
|
||||
{
|
||||
auto& grade = schema.grades[i];
|
||||
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
|
||||
}
|
||||
|
||||
ImGui::Text(strings.get(Strings::ArcadeAccuracyFormat).c_str(), skillCheck.accuracy_score_get(character));
|
||||
break;
|
||||
}
|
||||
|
||||
case DUNGEON:
|
||||
break;
|
||||
|
||||
case ORBIT:
|
||||
break;
|
||||
}
|
||||
};
|
||||
auto game_info_draw = [&](Game gameCurrent)
|
||||
{
|
||||
game_header_draw(gameCurrent);
|
||||
ImGui::Separator();
|
||||
game_info_sections_draw(gameCurrent);
|
||||
game_stats_draw(gameCurrent);
|
||||
};
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case GAMEPLAY:
|
||||
switch (game)
|
||||
{
|
||||
case SKILL_CHECK:
|
||||
if (skillCheck.update(resources, character, inventory, text, toasts))
|
||||
{
|
||||
game_reset(character, SKILL_CHECK);
|
||||
state = MENU;
|
||||
}
|
||||
break;
|
||||
|
||||
case DUNGEON:
|
||||
if (dungeon.update(character))
|
||||
{
|
||||
game_reset(character, DUNGEON);
|
||||
state = MENU;
|
||||
}
|
||||
break;
|
||||
|
||||
case ORBIT:
|
||||
if (orbit.update(resources, character, cursor, inventory, text, toasts))
|
||||
{
|
||||
game_reset(character, ORBIT);
|
||||
state = MENU;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case MENU:
|
||||
case INFO:
|
||||
break;
|
||||
}
|
||||
|
||||
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
|
||||
|
||||
if (ImGui::BeginChild("##Arcade Child", childSize))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case MENU:
|
||||
game_menu_draw(ORBIT);
|
||||
//game_menu_draw(DUNGEON);
|
||||
game_menu_draw(SKILL_CHECK);
|
||||
break;
|
||||
|
||||
case INFO:
|
||||
game_info_draw(game);
|
||||
break;
|
||||
|
||||
case GAMEPLAY:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (state == INFO)
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) state = MENU;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "arcade/dungeon.hpp"
|
||||
#include "arcade/orbit.hpp"
|
||||
#include "arcade/skill_check.hpp"
|
||||
#include "toasts.hpp"
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
class Arcade
|
||||
{
|
||||
public:
|
||||
enum Game
|
||||
{
|
||||
SKILL_CHECK,
|
||||
DUNGEON,
|
||||
ORBIT
|
||||
};
|
||||
|
||||
enum State
|
||||
{
|
||||
MENU,
|
||||
GAMEPLAY,
|
||||
INFO
|
||||
};
|
||||
|
||||
arcade::SkillCheck skillCheck{};
|
||||
arcade::Dungeon dungeon{};
|
||||
arcade::Orbit orbit{};
|
||||
Game game{SKILL_CHECK};
|
||||
State state{MENU};
|
||||
|
||||
Arcade() = default;
|
||||
Arcade(entity::Character&);
|
||||
|
||||
void game_reset(entity::Character&, Game);
|
||||
void tick();
|
||||
void update(Resources&, entity::Character&, entity::Cursor&, Inventory&, Text&, Toasts&);
|
||||
};
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
#include "dungeon.hpp"
|
||||
|
||||
#include "../../../../resource/font.hpp"
|
||||
#include "../../../../resource/xml/strings.hpp"
|
||||
#include "../../../../util/imgui/widget.hpp"
|
||||
#include "../../../../util/math.hpp"
|
||||
|
||||
#include <format>
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
int Dungeon::tile_value_get(const Tile& tile) const { return (int)tile.value; }
|
||||
bool Dungeon::tile_value_counts_toward_sum(const Tile& tile) const
|
||||
{
|
||||
return (tile.value >= Tile::VALUE_0 && tile.value <= Tile::VALUE_13) || tile.value == Tile::MINE;
|
||||
}
|
||||
bool Dungeon::tile_is_scroll(const Tile& tile) const { return tile.value == Tile::SCROLL; }
|
||||
const char* Dungeon::tile_flag_text_get(const Tile& tile) const
|
||||
{
|
||||
switch (tile.flagValue)
|
||||
{
|
||||
case Tile::FLAG_NONE:
|
||||
return nullptr;
|
||||
case Tile::FLAG_MINE:
|
||||
return "M";
|
||||
case Tile::FLAG_1:
|
||||
return "1";
|
||||
case Tile::FLAG_2:
|
||||
return "2";
|
||||
case Tile::FLAG_3:
|
||||
return "3";
|
||||
case Tile::FLAG_4:
|
||||
return "4";
|
||||
case Tile::FLAG_5:
|
||||
return "5";
|
||||
case Tile::FLAG_6:
|
||||
return "6";
|
||||
case Tile::FLAG_7:
|
||||
return "7";
|
||||
case Tile::FLAG_8:
|
||||
return "8";
|
||||
case Tile::FLAG_9:
|
||||
return "9";
|
||||
case Tile::FLAG_10:
|
||||
return "10";
|
||||
case Tile::FLAG_11:
|
||||
return "11";
|
||||
case Tile::FLAG_12:
|
||||
return "12";
|
||||
case Tile::FLAG_13:
|
||||
return "13";
|
||||
case Tile::FLAG_QUESTION:
|
||||
return "?";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Dungeon::surrounding_value_sum_get(int row, int column) const
|
||||
{
|
||||
auto sum = 0;
|
||||
for (int rowOffset = -1; rowOffset <= 1; rowOffset++)
|
||||
for (int columnOffset = -1; columnOffset <= 1; columnOffset++)
|
||||
{
|
||||
if (rowOffset == 0 && columnOffset == 0) continue;
|
||||
|
||||
auto neighborRow = row + rowOffset;
|
||||
auto neighborColumn = column + columnOffset;
|
||||
if (neighborRow < 0 || neighborRow >= GRID_ROWS || neighborColumn < 0 || neighborColumn >= GRID_COLUMNS)
|
||||
continue;
|
||||
|
||||
auto& neighbor = tiles[neighborRow * GRID_COLUMNS + neighborColumn];
|
||||
if (!tile_value_counts_toward_sum(neighbor)) continue;
|
||||
|
||||
sum += tile_value_get(neighbor);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
void Dungeon::reveal_diamond(int row, int column, int radius)
|
||||
{
|
||||
for (int rowOffset = -radius; rowOffset <= radius; rowOffset++)
|
||||
for (int columnOffset = -radius; columnOffset <= radius; columnOffset++)
|
||||
{
|
||||
if (std::abs(rowOffset) + std::abs(columnOffset) > radius) continue;
|
||||
|
||||
auto targetRow = row + rowOffset;
|
||||
auto targetColumn = column + columnOffset;
|
||||
if (targetRow < 0 || targetRow >= GRID_ROWS || targetColumn < 0 || targetColumn >= GRID_COLUMNS) continue;
|
||||
|
||||
auto& tile = tiles[targetRow * GRID_COLUMNS + targetColumn];
|
||||
if (tile.state == Tile::HIDDEN)
|
||||
{
|
||||
tile.state = Tile::SHOWN;
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Dungeon::reset(entity::Character&)
|
||||
{
|
||||
tiles.assign(GRID_ROWS * GRID_COLUMNS, Tile{});
|
||||
score = 0;
|
||||
for (auto& tile : tiles)
|
||||
{
|
||||
tile.value = game::util::math::random_percent_roll(5.0f) ? Tile::MINE
|
||||
: (Tile::Value)(int)game::util::math::random_max(14.0f);
|
||||
tile.state = Tile::HIDDEN;
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
}
|
||||
|
||||
if (!tiles.empty()) tiles[(int)game::util::math::random_max((float)tiles.size())].value = Tile::SCROLL;
|
||||
}
|
||||
|
||||
void Dungeon::tick() {}
|
||||
|
||||
bool Dungeon::update(entity::Character& character)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
constexpr float GRID_SPACING = 1.0f;
|
||||
auto& style = ImGui::GetStyle();
|
||||
|
||||
if (tiles.size() != GRID_ROWS * GRID_COLUMNS) reset(character);
|
||||
|
||||
auto contentRegionAvail = ImGui::GetContentRegionAvail();
|
||||
auto childSize =
|
||||
ImVec2(contentRegionAvail.x,
|
||||
std::max(0.0f, contentRegionAvail.y - ImGui::GetFrameHeightWithSpacing() - style.WindowPadding.y));
|
||||
|
||||
if (ImGui::BeginChild("##DungeonGrid", childSize))
|
||||
{
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto childAvail = ImGui::GetContentRegionAvail();
|
||||
auto cellWidth = std::max(1.0f, (childAvail.x - GRID_SPACING * (GRID_COLUMNS - 1)) / (float)GRID_COLUMNS);
|
||||
auto cellHeight = std::max(1.0f, (childAvail.y - GRID_SPACING * (GRID_ROWS - 1)) / (float)GRID_ROWS);
|
||||
auto cellSize = std::floor(std::min(cellWidth, cellHeight));
|
||||
auto gridWidth = cellSize * (float)GRID_COLUMNS + GRID_SPACING * (GRID_COLUMNS - 1);
|
||||
auto gridHeight = cellSize * (float)GRID_ROWS + GRID_SPACING * (GRID_ROWS - 1);
|
||||
auto cursor = ImGui::GetCursorPos();
|
||||
auto offsetX = std::max(0.0f, (childAvail.x - gridWidth) * 0.5f);
|
||||
auto offsetY = std::max(0.0f, (childAvail.y - gridHeight) * 0.5f);
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(cursor.x + offsetX, cursor.y + offsetY));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(GRID_SPACING, GRID_SPACING));
|
||||
|
||||
for (int row = 0; row < GRID_ROWS; row++)
|
||||
{
|
||||
for (int column = 0; column < GRID_COLUMNS; column++)
|
||||
{
|
||||
auto tileID = row * GRID_COLUMNS + column;
|
||||
auto& tile = tiles[tileID];
|
||||
auto tileValue = tile_value_get(tile);
|
||||
|
||||
ImGui::PushID(tileID);
|
||||
if (tile.state != Tile::HIDDEN)
|
||||
{
|
||||
auto buttonColor = style.Colors[ImGuiCol_WindowBg];
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, buttonColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColor);
|
||||
}
|
||||
auto isLeftPressed = WIDGET_FX(ImGui::Button("##DungeonCell", ImVec2(cellSize, cellSize)));
|
||||
auto isPopupOpen = ImGui::BeginPopupContextItem("##DungeonFlagMenu");
|
||||
if (isPopupOpen)
|
||||
{
|
||||
if (ImGui::Button("M", ImVec2(36.0f, 0.0f)))
|
||||
{
|
||||
tile.flagValue = Tile::FLAG_MINE;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
for (int flagValue = Tile::FLAG_1; flagValue <= Tile::FLAG_13; flagValue++)
|
||||
{
|
||||
auto flagText = std::format("{}", flagValue);
|
||||
if (ImGui::Button(flagText.c_str(), ImVec2(36.0f, 0.0f)))
|
||||
{
|
||||
tile.flagValue = (Tile::FlagValue)flagValue;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
if (flagValue % 4 != 0 && flagValue != Tile::FLAG_13) ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::Button("?", ImVec2(36.0f, 0.0f)))
|
||||
{
|
||||
tile.flagValue = Tile::FLAG_QUESTION;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear"))
|
||||
{
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
auto rectMin = ImGui::GetItemRectMin();
|
||||
auto rectMax = ImGui::GetItemRectMax();
|
||||
if (tile.state != Tile::HIDDEN) ImGui::PopStyleColor(3);
|
||||
ImGui::PopID();
|
||||
|
||||
if (isLeftPressed)
|
||||
{
|
||||
switch (tile.state)
|
||||
{
|
||||
case Tile::HIDDEN:
|
||||
tile.state = Tile::SHOWN;
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
if (tile_is_scroll(tile))
|
||||
{
|
||||
reveal_diamond(row, column, 2);
|
||||
tile.value = Tile::VALUE_0;
|
||||
tile.state = Tile::SHOWN;
|
||||
}
|
||||
break;
|
||||
case Tile::SHOWN:
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
if (tile_is_scroll(tile))
|
||||
{
|
||||
reveal_diamond(row, column, 2);
|
||||
tile.value = Tile::VALUE_0;
|
||||
tile.state = Tile::SHOWN;
|
||||
}
|
||||
else if (tileValue > 0)
|
||||
{
|
||||
tile.state = Tile::CORPSE;
|
||||
score += tileValue;
|
||||
}
|
||||
break;
|
||||
case Tile::CORPSE:
|
||||
tile.value = Tile::VALUE_0;
|
||||
tile.state = Tile::SHOWN;
|
||||
tile.flagValue = Tile::FLAG_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string tileText{};
|
||||
auto textColor = IM_COL32(255, 255, 255, 255);
|
||||
if (tile_is_scroll(tile))
|
||||
{
|
||||
tileText = "!";
|
||||
if (tile.state == Tile::CORPSE)
|
||||
textColor = IM_COL32(255, 230, 64, 255);
|
||||
else
|
||||
textColor = IM_COL32(255, 255, 255, 255);
|
||||
}
|
||||
else if (tile.state == Tile::HIDDEN)
|
||||
{
|
||||
if (auto flagText = tile_flag_text_get(tile))
|
||||
{
|
||||
tileText = flagText;
|
||||
textColor = IM_COL32(64, 128, 255, 255);
|
||||
}
|
||||
}
|
||||
else
|
||||
switch (tile.state)
|
||||
{
|
||||
case Tile::HIDDEN:
|
||||
break;
|
||||
case Tile::SHOWN:
|
||||
if (tileValue == 0)
|
||||
{
|
||||
auto surroundingSum = surrounding_value_sum_get(row, column);
|
||||
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
|
||||
}
|
||||
else
|
||||
{
|
||||
tileText = std::format("{}", tileValue);
|
||||
textColor = IM_COL32(255, 64, 64, 255);
|
||||
}
|
||||
break;
|
||||
case Tile::CORPSE:
|
||||
if (tileValue == 0)
|
||||
{
|
||||
auto surroundingSum = surrounding_value_sum_get(row, column);
|
||||
if (surroundingSum > 0) tileText = std::format("{}", surroundingSum);
|
||||
}
|
||||
else
|
||||
{
|
||||
tileText = std::format("{}", tileValue);
|
||||
textColor = IM_COL32(255, 230, 64, 255);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tileText.empty())
|
||||
{
|
||||
auto textSize = ImGui::CalcTextSize(tileText.c_str());
|
||||
auto textPosition = ImVec2(rectMin.x + (rectMax.x - rectMin.x - textSize.x) * 0.5f,
|
||||
rectMin.y + (rectMax.y - rectMin.y - textSize.y) * 0.5f);
|
||||
drawList->AddText(textPosition, textColor, tileText.c_str());
|
||||
}
|
||||
|
||||
if (column + 1 < GRID_COLUMNS) ImGui::SameLine(0.0f, GRID_SPACING);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
|
||||
|
||||
return WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()));
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../../entity/character.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
class Dungeon
|
||||
{
|
||||
public:
|
||||
struct Tile
|
||||
{
|
||||
enum State
|
||||
{
|
||||
HIDDEN,
|
||||
SHOWN,
|
||||
CORPSE
|
||||
};
|
||||
|
||||
enum FlagValue
|
||||
{
|
||||
FLAG_NONE,
|
||||
FLAG_1,
|
||||
FLAG_2,
|
||||
FLAG_3,
|
||||
FLAG_4,
|
||||
FLAG_5,
|
||||
FLAG_6,
|
||||
FLAG_7,
|
||||
FLAG_8,
|
||||
FLAG_9,
|
||||
FLAG_10,
|
||||
FLAG_11,
|
||||
FLAG_12,
|
||||
FLAG_13,
|
||||
FLAG_MINE,
|
||||
FLAG_QUESTION
|
||||
};
|
||||
|
||||
enum Value
|
||||
{
|
||||
VALUE_0,
|
||||
VALUE_1,
|
||||
VALUE_2,
|
||||
VALUE_3,
|
||||
VALUE_4,
|
||||
VALUE_5,
|
||||
VALUE_6,
|
||||
VALUE_7,
|
||||
VALUE_8,
|
||||
VALUE_9,
|
||||
VALUE_10,
|
||||
VALUE_11,
|
||||
VALUE_12,
|
||||
VALUE_13,
|
||||
MINE = 100,
|
||||
SCROLL = 101
|
||||
};
|
||||
|
||||
Value value{VALUE_0};
|
||||
State state{HIDDEN};
|
||||
FlagValue flagValue{FLAG_NONE};
|
||||
};
|
||||
|
||||
static constexpr int GRID_ROWS = 13;
|
||||
static constexpr int GRID_COLUMNS = 13;
|
||||
|
||||
std::vector<Tile> tiles{};
|
||||
int score{};
|
||||
|
||||
int tile_value_get(const Tile&) const;
|
||||
int surrounding_value_sum_get(int row, int column) const;
|
||||
bool tile_value_counts_toward_sum(const Tile&) const;
|
||||
bool tile_is_scroll(const Tile&) const;
|
||||
const char* tile_flag_text_get(const Tile&) const;
|
||||
void reveal_diamond(int row, int column, int radius);
|
||||
void reset(entity::Character&);
|
||||
void tick();
|
||||
bool update(entity::Character&);
|
||||
};
|
||||
}
|
||||
@@ -1,605 +0,0 @@
|
||||
#include "orbit.hpp"
|
||||
|
||||
#include "../../../../util/imgui.hpp"
|
||||
#include "../../../../util/imgui/widget.hpp"
|
||||
#include "../../../../util/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::resource::xml;
|
||||
using namespace game::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
namespace
|
||||
{
|
||||
enum SpawnSide
|
||||
{
|
||||
TOP,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
LEFT
|
||||
};
|
||||
|
||||
bool is_rect_overlapping(const glm::vec4& left, const glm::vec4& right)
|
||||
{
|
||||
return left.x < right.x + right.z && left.x + left.z > right.x && left.y < right.y + right.w &&
|
||||
left.y + left.w > right.y;
|
||||
}
|
||||
|
||||
void target_tick(Orbit::Entity& entity, const glm::vec2& target, float acceleration)
|
||||
{
|
||||
auto delta = target - entity.position;
|
||||
auto distance = glm::length(delta);
|
||||
|
||||
if (distance <= 0.001f)
|
||||
{
|
||||
entity.position = target;
|
||||
entity.velocity *= 0.5f;
|
||||
if (glm::length(entity.velocity) <= 0.001f) entity.velocity = {};
|
||||
return;
|
||||
}
|
||||
|
||||
auto maxSpeed = std::max(acceleration * 8.0f, 1.0f);
|
||||
auto desiredVelocity = glm::normalize(delta) * std::min(distance * 0.35f, maxSpeed);
|
||||
auto steering = desiredVelocity - entity.velocity;
|
||||
auto steeringLength = glm::length(steering);
|
||||
if (steeringLength > acceleration) steering = (steering / steeringLength) * acceleration;
|
||||
|
||||
entity.velocity += steering;
|
||||
|
||||
auto velocityLength = glm::length(entity.velocity);
|
||||
if (velocityLength > maxSpeed) entity.velocity = (entity.velocity / velocityLength) * maxSpeed;
|
||||
|
||||
entity.position += entity.velocity;
|
||||
|
||||
if (glm::distance(entity.position, target) <= maxSpeed)
|
||||
{
|
||||
entity.position = glm::mix(entity.position, target, 0.15f);
|
||||
}
|
||||
}
|
||||
|
||||
void follower_angles_refresh(std::vector<Orbit::Entity>& entities)
|
||||
{
|
||||
std::vector<Orbit::Entity*> followers{};
|
||||
for (auto& entity : entities)
|
||||
if (entity.type == Orbit::Entity::FOLLOWER) followers.emplace_back(&entity);
|
||||
|
||||
if (followers.empty()) return;
|
||||
|
||||
std::sort(followers.begin(), followers.end(),
|
||||
[](const Orbit::Entity* left, const Orbit::Entity* right) { return left->colorID < right->colorID; });
|
||||
|
||||
auto baseAngle = followers.front()->orbitAngle;
|
||||
auto spacing = glm::two_pi<float>() / (float)followers.size();
|
||||
|
||||
for (int i = 0; i < (int)followers.size(); i++)
|
||||
followers[i]->orbitAngle = baseAngle + spacing * (float)i;
|
||||
}
|
||||
|
||||
const glm::vec3* color_value_get(const resource::xml::Orbit& schema, int colorID)
|
||||
{
|
||||
if (colorID < 0 || colorID >= (int)schema.colors.size()) return nullptr;
|
||||
return &schema.colors[colorID].value;
|
||||
}
|
||||
|
||||
int random_available_color_get(const resource::xml::Orbit& schema, int level)
|
||||
{
|
||||
auto availableCount = std::min(level, (int)schema.colors.size());
|
||||
if (availableCount <= 0) return -1;
|
||||
return (int)math::random_max((float)availableCount);
|
||||
}
|
||||
|
||||
int unlocked_level_get(const resource::xml::Orbit& schema, int score)
|
||||
{
|
||||
int unlockedLevel = 0;
|
||||
|
||||
for (auto& color : schema.colors)
|
||||
{
|
||||
if (score >= color.scoreThreshold)
|
||||
unlockedLevel++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return std::max(1, unlockedLevel);
|
||||
}
|
||||
|
||||
void color_override_set(Orbit::Entity& entity, const resource::xml::Orbit& schema, int colorID,
|
||||
const std::string& layerName)
|
||||
{
|
||||
auto color = color_value_get(schema, colorID);
|
||||
if (!color) return;
|
||||
if (!entity.layerMap.contains(layerName)) return;
|
||||
|
||||
entity::Actor::Override override_{entity.layerMap.at(layerName), Anm2::LAYER, entity::Actor::Override::SET};
|
||||
override_.frame.tint.x = color->r;
|
||||
override_.frame.tint.y = color->g;
|
||||
override_.frame.tint.z = color->b;
|
||||
entity.overrides.emplace_back(std::move(override_));
|
||||
}
|
||||
|
||||
void idle_queue(Orbit::Entity& entity)
|
||||
{
|
||||
if (!entity.animationIdle.empty())
|
||||
entity.queue_play({.animation = entity.animationIdle, .isPlayAfterAnimation = true});
|
||||
}
|
||||
|
||||
void spawn_animation_play(Orbit::Entity& entity)
|
||||
{
|
||||
if (!entity.animationSpawn.empty())
|
||||
{
|
||||
entity.play(entity.animationSpawn, entity::Actor::PLAY_FORCE);
|
||||
idle_queue(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Orbit::spawn(entity::Character& character, Orbit::Entity::Type type, int colorID)
|
||||
{
|
||||
auto& schema = character.data.orbitSchema;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Entity::PLAYER:
|
||||
if (!schema.player.anm2.is_valid()) return;
|
||||
entities.emplace_back(schema.player.anm2, Entity::PLAYER);
|
||||
entities.back().position = centerPosition;
|
||||
entities.back().animationIdle = schema.player.animations.idle;
|
||||
entities.back().animationSpawn = schema.player.animations.spawn;
|
||||
entities.back().animationDeath = schema.player.animations.death;
|
||||
entities.back().hitboxNull = schema.player.hitboxNull;
|
||||
spawn_animation_play(entities.back());
|
||||
return;
|
||||
case Entity::FOLLOWER:
|
||||
{
|
||||
if (!schema.follower.anm2.is_valid()) return;
|
||||
if (colorID < 0 || colorID >= (int)schema.colors.size()) return;
|
||||
|
||||
Entity follower{schema.follower.anm2, Entity::FOLLOWER};
|
||||
follower.colorID = colorID;
|
||||
follower.orbitAngle = 0.0f;
|
||||
follower.position = centerPosition;
|
||||
follower.animationIdle = schema.follower.animations.idle;
|
||||
follower.animationSpawn = schema.follower.animations.spawn;
|
||||
follower.animationDeath = schema.follower.animations.death;
|
||||
follower.hitboxNull = schema.follower.hitboxNull;
|
||||
color_override_set(follower, schema, colorID, schema.follower.overrideTintLayer);
|
||||
spawn_animation_play(follower);
|
||||
|
||||
entities.emplace_back(std::move(follower));
|
||||
follower_angles_refresh(entities);
|
||||
return;
|
||||
}
|
||||
case Entity::ENEMY:
|
||||
{
|
||||
if (!schema.enemy.anm2.is_valid()) return;
|
||||
|
||||
Entity enemy{schema.enemy.anm2, Entity::ENEMY};
|
||||
enemy.colorID = colorID;
|
||||
enemy.animationIdle = schema.enemy.animations.idle;
|
||||
enemy.animationSpawn = schema.enemy.animations.spawn;
|
||||
enemy.animationDeath = schema.enemy.animations.death;
|
||||
enemy.hitboxNull = schema.enemy.hitboxNull;
|
||||
color_override_set(enemy, schema, colorID, schema.enemy.overrideTintLayer);
|
||||
spawn_animation_play(enemy);
|
||||
|
||||
auto rect = enemy.rect();
|
||||
auto width = rect.z;
|
||||
auto height = rect.w;
|
||||
auto side = (SpawnSide)math::random_max(4.0f);
|
||||
switch (side)
|
||||
{
|
||||
case TOP:
|
||||
enemy.position = vec2(math::random_max(canvas.size.x), -height - schema.enemy.spawnPadding);
|
||||
break;
|
||||
case RIGHT:
|
||||
enemy.position = vec2(canvas.size.x + width + schema.enemy.spawnPadding, math::random_max(canvas.size.y));
|
||||
break;
|
||||
case BOTTOM:
|
||||
enemy.position = vec2(math::random_max(canvas.size.x), canvas.size.y + height + schema.enemy.spawnPadding);
|
||||
break;
|
||||
case LEFT:
|
||||
enemy.position = vec2(-width - schema.enemy.spawnPadding, math::random_max(canvas.size.y));
|
||||
break;
|
||||
}
|
||||
|
||||
entities.emplace_back(std::move(enemy));
|
||||
|
||||
if (schema.warning.anm2.is_valid())
|
||||
{
|
||||
Entity warning{schema.warning.anm2, Entity::WARNING};
|
||||
warning.colorID = colorID;
|
||||
color_override_set(warning, schema, colorID, schema.warning.overrideTintLayer);
|
||||
|
||||
auto warningRect = warning.rect();
|
||||
auto warningWidth = warningRect.z;
|
||||
auto warningHeight = warningRect.w;
|
||||
|
||||
switch (side)
|
||||
{
|
||||
case TOP:
|
||||
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
|
||||
(float)canvas.size.x - warningWidth * 0.5f),
|
||||
warningHeight * 0.5f);
|
||||
break;
|
||||
case RIGHT:
|
||||
warning.position = vec2((float)canvas.size.x - warningWidth * 0.5f,
|
||||
glm::clamp(entities.back().position.y, warningHeight * 0.5f,
|
||||
(float)canvas.size.y - warningHeight * 0.5f));
|
||||
break;
|
||||
case BOTTOM:
|
||||
warning.position = vec2(glm::clamp(entities.back().position.x, warningWidth * 0.5f,
|
||||
(float)canvas.size.x - warningWidth * 0.5f),
|
||||
(float)canvas.size.y - warningHeight * 0.5f);
|
||||
break;
|
||||
case LEFT:
|
||||
warning.position = vec2(warningWidth * 0.5f, glm::clamp(entities.back().position.y, warningHeight * 0.5f,
|
||||
(float)canvas.size.y - warningHeight * 0.5f));
|
||||
break;
|
||||
}
|
||||
|
||||
entities.emplace_back(std::move(warning));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Entity::WARNING:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Orbit::reset(entity::Character& character)
|
||||
{
|
||||
entities.clear();
|
||||
sounds = &character.data.orbitSchema.sounds;
|
||||
cursorPosition = {};
|
||||
centerPosition = {};
|
||||
level = 0;
|
||||
highScoreAtRunStart = highScore;
|
||||
score = 0;
|
||||
isHighScoreAchievedThisRun = false;
|
||||
itemEffectManager = {};
|
||||
followerRadius = {};
|
||||
playerTargetAcceleration = {};
|
||||
followerTargetAcceleration = {};
|
||||
playerTimeAfterHurt = {};
|
||||
enemySpeed = {};
|
||||
enemySpeedScoreBonus = {};
|
||||
enemySpeedGainBase = {};
|
||||
enemySpeedGainScoreBonus = {};
|
||||
rotationSpeed = {};
|
||||
rotationSpeedMax = {};
|
||||
rotationSpeedFriction = {};
|
||||
startTimer = character.data.orbitSchema.startTime;
|
||||
hurtTimer = 0;
|
||||
isPlayerDying = false;
|
||||
isRotateLeft = false;
|
||||
isRotateRight = false;
|
||||
}
|
||||
|
||||
void Orbit::tick()
|
||||
{
|
||||
for (auto& entity : entities)
|
||||
entity.tick();
|
||||
|
||||
itemEffectManager.tick();
|
||||
canvas.tick();
|
||||
}
|
||||
|
||||
bool Orbit::update(Resources& resources, entity::Character& character, entity::Cursor& cursor, Inventory& inventory,
|
||||
Text& text, menu::Toasts& toasts)
|
||||
{
|
||||
auto& strings = character.data.strings;
|
||||
auto& schema = character.data.orbitSchema;
|
||||
sounds = &schema.sounds;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto& textureShader = resources.shaders[resource::shader::TEXTURE];
|
||||
auto& rectShader = resources.shaders[resource::shader::RECT];
|
||||
ImGui::Text(strings.get(Strings::ArcadeScoreFormat).c_str(), score);
|
||||
auto bestText = std::vformat(strings.get(Strings::ArcadeBestScoreFormat), std::make_format_args(highScore));
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(bestText.c_str()).x,
|
||||
cursorPos.y - ImGui::GetTextLineHeightWithSpacing()));
|
||||
ImGui::Text(strings.get(Strings::ArcadeBestScoreFormat).c_str(), highScore);
|
||||
auto padding = ImGui::GetTextLineHeightWithSpacing();
|
||||
auto contentRegionAvail = ImGui::GetContentRegionAvail();
|
||||
auto contentRegionPosition = ImGui::GetCursorScreenPos();
|
||||
auto contentBounds =
|
||||
ImVec4(contentRegionPosition.x, contentRegionPosition.y, contentRegionAvail.x, contentRegionAvail.y);
|
||||
auto available =
|
||||
imgui::to_vec2(contentRegionAvail) - vec2(0.0f, ImGui::GetFrameHeightWithSpacing() + style.WindowPadding.y);
|
||||
auto canvasSize = glm::max(vec2(1.0f), available - vec2(padding * 2.0f));
|
||||
auto canvasScreenPosition = imgui::to_vec2(ImGui::GetCursorScreenPos()) + vec2(padding);
|
||||
centerPosition = canvasSize * 0.5f;
|
||||
|
||||
if (isPlayerDying)
|
||||
{
|
||||
Entity* playerEntity = nullptr;
|
||||
for (auto& entity : entities)
|
||||
if (entity.type == Entity::PLAYER)
|
||||
{
|
||||
playerEntity = &entity;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!playerEntity || playerEntity->state == entity::Actor::STOPPED)
|
||||
{
|
||||
reset(character);
|
||||
}
|
||||
}
|
||||
|
||||
if (entities.empty() && startTimer <= 0) startTimer = schema.startTime;
|
||||
|
||||
if (entities.empty()) spawn(character, Entity::PLAYER);
|
||||
|
||||
followerRadius = schema.player.followerRadius;
|
||||
playerTargetAcceleration = schema.player.targetAcceleration;
|
||||
followerTargetAcceleration = schema.follower.targetAcceleration;
|
||||
playerTimeAfterHurt = schema.player.timeAfterHurt;
|
||||
enemySpeed = schema.enemy.speed;
|
||||
enemySpeedScoreBonus = schema.enemy.speedScoreBonus;
|
||||
enemySpeedGainBase = schema.enemy.speedGainBase;
|
||||
enemySpeedGainScoreBonus = schema.enemy.speedGainScoreBonus;
|
||||
rotationSpeed = schema.player.rotationSpeed;
|
||||
rotationSpeedMax = schema.player.rotationSpeedMax;
|
||||
rotationSpeedFriction = schema.player.rotationSpeedFriction;
|
||||
auto nextLevel = std::min(unlocked_level_get(schema, score), (int)schema.colors.size());
|
||||
if (nextLevel > level)
|
||||
{
|
||||
schema.sounds.levelUp.play();
|
||||
|
||||
auto colorIndex = nextLevel - 1;
|
||||
if (colorIndex >= 0 && colorIndex < (int)schema.colors.size())
|
||||
{
|
||||
auto& pool = schema.colors[colorIndex].pool;
|
||||
if (pool.is_valid() && text.is_interruptible()) text.set(character.data.dialogue.get(pool), character);
|
||||
}
|
||||
}
|
||||
level = nextLevel;
|
||||
|
||||
auto player_get = [&]() -> Entity*
|
||||
{
|
||||
for (auto& entity : entities)
|
||||
if (entity.type == Entity::PLAYER) return &entity;
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto player = player_get();
|
||||
|
||||
if (player)
|
||||
{
|
||||
auto desiredFollowerCount = std::min(level, (int)schema.colors.size());
|
||||
auto currentFollowerCount = (int)std::count_if(entities.begin(), entities.end(), [](const Entity& entity)
|
||||
{ return entity.type == Entity::FOLLOWER; });
|
||||
auto currentEnemyCount = (int)std::count_if(entities.begin(), entities.end(),
|
||||
[](const Entity& entity) { return entity.type == Entity::ENEMY; });
|
||||
|
||||
if (currentFollowerCount != desiredFollowerCount)
|
||||
{
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||
[](const Entity& entity) { return entity.type == Entity::FOLLOWER; }),
|
||||
entities.end());
|
||||
|
||||
for (int i = 0; i < desiredFollowerCount; i++)
|
||||
spawn(character, Entity::FOLLOWER, i);
|
||||
|
||||
player = player_get();
|
||||
}
|
||||
|
||||
if (startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && !schema.colors.empty())
|
||||
{
|
||||
auto colorID = random_available_color_get(schema, level);
|
||||
if (colorID == -1) return false;
|
||||
|
||||
if (currentEnemyCount == 0)
|
||||
{
|
||||
spawn(character, Entity::ENEMY, colorID);
|
||||
player = player_get();
|
||||
}
|
||||
|
||||
auto spawnChance = schema.enemy.spawnChanceBase + schema.enemy.spawnChanceScoreBonus * (float)score;
|
||||
if (math::random_percent_roll(spawnChance))
|
||||
{
|
||||
colorID = random_available_color_get(schema, level);
|
||||
if (colorID == -1) return false;
|
||||
spawn(character, Entity::ENEMY, colorID);
|
||||
player = player_get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto mousePosition = imgui::to_vec2(ImGui::GetMousePos());
|
||||
auto canvasBoundsMax = canvasScreenPosition + canvasSize;
|
||||
auto isHoveringCanvas = mousePosition.x >= canvasScreenPosition.x && mousePosition.x <= canvasBoundsMax.x &&
|
||||
mousePosition.y >= canvasScreenPosition.y && mousePosition.y <= canvasBoundsMax.y;
|
||||
|
||||
cursor.isVisible = !isHoveringCanvas;
|
||||
isRotateLeft = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
|
||||
ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
isRotateRight = startTimer <= 0 && hurtTimer <= 0 && !isPlayerDying && isHoveringCanvas &&
|
||||
ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
cursorPosition = glm::clamp(mousePosition - canvasScreenPosition, vec2(0.0f), canvasSize);
|
||||
|
||||
if (player)
|
||||
{
|
||||
if (isPlayerDying || hurtTimer > 0) player->velocity *= 0.85f;
|
||||
|
||||
if (!isPlayerDying && hurtTimer <= 0 && isRotateLeft) player->rotationVelocity -= rotationSpeed;
|
||||
if (!isPlayerDying && hurtTimer <= 0 && isRotateRight) player->rotationVelocity += rotationSpeed;
|
||||
player->rotationVelocity = glm::clamp(player->rotationVelocity, -rotationSpeedMax, rotationSpeedMax);
|
||||
player->rotationVelocity *= rotationSpeedFriction;
|
||||
|
||||
if (!isPlayerDying && hurtTimer <= 0)
|
||||
target_tick(*player, startTimer > 0 ? centerPosition : cursorPosition, playerTargetAcceleration);
|
||||
}
|
||||
|
||||
for (auto& entity : entities)
|
||||
{
|
||||
switch (entity.type)
|
||||
{
|
||||
case Entity::FOLLOWER:
|
||||
if (player)
|
||||
{
|
||||
entity.orbitAngle += player->rotationVelocity;
|
||||
|
||||
auto radius = std::max(0.0f, followerRadius);
|
||||
auto target = player->position + vec2(std::cos(entity.orbitAngle), std::sin(entity.orbitAngle)) * radius;
|
||||
target_tick(entity, target, followerTargetAcceleration);
|
||||
}
|
||||
break;
|
||||
case Entity::ENEMY:
|
||||
if (player && !entity.isMarkedForRemoval)
|
||||
{
|
||||
auto delta = player->position - entity.position;
|
||||
auto distance = glm::length(delta);
|
||||
auto speed = (enemySpeed + enemySpeedScoreBonus * (float)score) +
|
||||
(enemySpeedGainBase + enemySpeedGainScoreBonus * (float)score);
|
||||
if (distance > 0.001f) entity.position += glm::normalize(delta) * speed;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& follower : entities)
|
||||
{
|
||||
if (isPlayerDying) break;
|
||||
if (follower.type != Entity::FOLLOWER) continue;
|
||||
if (follower.hitboxNull.empty() || !follower.nullMap.contains(follower.hitboxNull)) continue;
|
||||
|
||||
auto followerRect = follower.null_frame_rect(follower.nullMap.at(follower.hitboxNull));
|
||||
if (std::isnan(followerRect.x)) continue;
|
||||
|
||||
for (auto& enemy : entities)
|
||||
{
|
||||
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
|
||||
if (enemy.colorID != follower.colorID) continue;
|
||||
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
|
||||
|
||||
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
|
||||
if (std::isnan(enemyRect.x)) continue;
|
||||
if (!is_rect_overlapping(followerRect, enemyRect)) continue;
|
||||
|
||||
enemy.isMarkedForRemoval = true;
|
||||
if (!enemy.animationDeath.empty())
|
||||
enemy.play(enemy.animationDeath, entity::Actor::PLAY_FORCE);
|
||||
else
|
||||
enemy.state = entity::Actor::STOPPED;
|
||||
|
||||
spawn_animation_play(follower);
|
||||
score++;
|
||||
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
|
||||
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score);
|
||||
itemRewards.reward_random_items_try(inventory, itemEffectManager, character.data.itemSchema, contentBounds,
|
||||
rewardChance, rewardRollCount, menu::ItemEffectManager::SHOOT_UP);
|
||||
if (score > highScore)
|
||||
{
|
||||
highScore = score;
|
||||
|
||||
if (!isHighScoreAchievedThisRun && highScoreAtRunStart > 0)
|
||||
{
|
||||
isHighScoreAchievedThisRun = true;
|
||||
schema.sounds.highScore.play();
|
||||
auto toastText = strings.get(Strings::ArcadeHighScoreToast);
|
||||
auto toastPosition = imgui::to_imvec2(
|
||||
canvasScreenPosition + player->position -
|
||||
vec2(ImGui::CalcTextSize(toastText.c_str()).x * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 2.0f));
|
||||
toasts.spawn(toastText, toastPosition, 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (player && !isPlayerDying && hurtTimer <= 0 && !player->hitboxNull.empty() &&
|
||||
player->nullMap.contains(player->hitboxNull))
|
||||
{
|
||||
auto playerRect = player->null_frame_rect(player->nullMap.at(player->hitboxNull));
|
||||
|
||||
if (!std::isnan(playerRect.x))
|
||||
{
|
||||
auto isHit = false;
|
||||
|
||||
for (auto& enemy : entities)
|
||||
{
|
||||
if (enemy.type != Entity::ENEMY || enemy.isMarkedForRemoval) continue;
|
||||
if (enemy.hitboxNull.empty() || !enemy.nullMap.contains(enemy.hitboxNull)) continue;
|
||||
|
||||
auto enemyRect = enemy.null_frame_rect(enemy.nullMap.at(enemy.hitboxNull));
|
||||
if (std::isnan(enemyRect.x)) continue;
|
||||
if (!is_rect_overlapping(playerRect, enemyRect)) continue;
|
||||
|
||||
isHit = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isHit)
|
||||
{
|
||||
if (sounds) sounds->hurt.play();
|
||||
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
||||
if (schema.poolDeath.is_valid() && text.is_interruptible())
|
||||
text.set(character.data.dialogue.get(schema.poolDeath), character);
|
||||
hurtTimer = playerTimeAfterHurt;
|
||||
isPlayerDying = true;
|
||||
player->velocity = {};
|
||||
player->rotationVelocity = 0.0f;
|
||||
if (!player->animationDeath.empty())
|
||||
player->play(player->animationDeath, entity::Actor::PLAY_FORCE);
|
||||
else
|
||||
player->state = entity::Actor::STOPPED;
|
||||
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||
[](const Entity& entity)
|
||||
{
|
||||
return entity.type == Entity::ENEMY || entity.type == Entity::WARNING ||
|
||||
entity.type == Entity::FOLLOWER;
|
||||
}),
|
||||
entities.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(),
|
||||
[](const Entity& entity)
|
||||
{
|
||||
return (entity.type == Entity::WARNING && entity.state == entity::Actor::STOPPED) ||
|
||||
(entity.type == Entity::ENEMY && entity.isMarkedForRemoval &&
|
||||
entity.state == entity::Actor::STOPPED);
|
||||
}),
|
||||
entities.end());
|
||||
|
||||
if (startTimer > 0) startTimer--;
|
||||
if (hurtTimer > 0) hurtTimer--;
|
||||
|
||||
canvas.bind();
|
||||
canvas.size_set(ivec2(canvasSize));
|
||||
canvas.clear(color::BLACK);
|
||||
|
||||
for (auto& entity : entities)
|
||||
if (entity.type == Entity::PLAYER || entity.type == Entity::FOLLOWER || entity.type == Entity::ENEMY ||
|
||||
entity.type == Entity::WARNING)
|
||||
entity.render(textureShader, rectShader, canvas);
|
||||
|
||||
canvas.unbind();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, padding));
|
||||
ImGui::SetCursorScreenPos(imgui::to_imvec2(canvasScreenPosition));
|
||||
ImGui::Image(canvas.texture, imgui::to_imvec2(canvasSize));
|
||||
itemEffectManager.render(resources, character.data.itemSchema, contentBounds, ImGui::GetIO().DeltaTime);
|
||||
ImGui::Dummy(ImVec2(0, padding));
|
||||
toasts.update(drawList);
|
||||
|
||||
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
|
||||
if (ImGui::IsItemHovered()) ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
|
||||
return isMenuPressed;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../../entity/actor.hpp"
|
||||
#include "../../../../entity/cursor.hpp"
|
||||
#include "../../../../resources.hpp"
|
||||
|
||||
#include "../../../../util/color.hpp"
|
||||
#include "../inventory.hpp"
|
||||
#include "../item_effect_manager.hpp"
|
||||
#include "../../text.hpp"
|
||||
#include "../toasts.hpp"
|
||||
#include "../../item/reward.hpp"
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
class Orbit
|
||||
{
|
||||
public:
|
||||
class Entity : public entity::Actor
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
PLAYER,
|
||||
FOLLOWER,
|
||||
ENEMY,
|
||||
WARNING
|
||||
};
|
||||
|
||||
Type type{PLAYER};
|
||||
glm::vec2 velocity{};
|
||||
float rotationVelocity{};
|
||||
std::string animationIdle{};
|
||||
std::string animationSpawn{};
|
||||
std::string animationDeath{};
|
||||
std::string hitboxNull{"Hitbox"};
|
||||
bool isMarkedForRemoval{};
|
||||
int health{3};
|
||||
int colorID{};
|
||||
float orbitAngle{};
|
||||
|
||||
Entity() = default;
|
||||
Entity(resource::xml::Anm2 anm2, Type type = PLAYER) : entity::Actor(std::move(anm2)), type(type) {}
|
||||
};
|
||||
|
||||
std::vector<Entity> entities{};
|
||||
resource::xml::Orbit::Sounds* sounds{};
|
||||
Canvas canvas{{1, 1}};
|
||||
glm::vec2 cursorPosition{};
|
||||
glm::vec2 centerPosition{};
|
||||
int level{1};
|
||||
int score{};
|
||||
int highScore{};
|
||||
int highScoreAtRunStart{};
|
||||
bool isHighScoreAchievedThisRun{};
|
||||
menu::ItemEffectManager itemEffectManager{};
|
||||
game::state::play::item::Reward itemRewards{};
|
||||
float followerRadius{};
|
||||
float playerTargetAcceleration{};
|
||||
float followerTargetAcceleration{};
|
||||
int playerTimeAfterHurt{};
|
||||
float enemySpeed{};
|
||||
float enemySpeedScoreBonus{};
|
||||
float enemySpeedGainBase{};
|
||||
float enemySpeedGainScoreBonus{};
|
||||
float rotationSpeed{};
|
||||
float rotationSpeedMax{};
|
||||
float rotationSpeedFriction{};
|
||||
int startTimer{};
|
||||
int hurtTimer{};
|
||||
bool isPlayerDying{};
|
||||
bool isRotateLeft{};
|
||||
bool isRotateRight{};
|
||||
|
||||
Orbit() = default;
|
||||
void reset(entity::Character&);
|
||||
void tick();
|
||||
void spawn(entity::Character&, Entity::Type, int colorID = -1);
|
||||
bool update(Resources&, entity::Character&, entity::Cursor&, Inventory& inventory, Text& text, menu::Toasts&);
|
||||
};
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
#include "skill_check.hpp"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "../../../../util/imgui.hpp"
|
||||
#include "../../../../util/imgui/widget.hpp"
|
||||
#include "../../../../util/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
using namespace glm;
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
float SkillCheck::accuracy_score_get(entity::Character& character)
|
||||
{
|
||||
if (totalPlays == 0) return 0.0f;
|
||||
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
float combinedWeight{};
|
||||
|
||||
for (int i = 0; i < (int)schema.grades.size(); i++)
|
||||
{
|
||||
auto& grade = schema.grades[i];
|
||||
combinedWeight += gradeCounts[i] * grade.weight;
|
||||
}
|
||||
|
||||
return glm::clamp(0.0f, math::to_percent(combinedWeight / totalPlays), 100.0f);
|
||||
}
|
||||
|
||||
SkillCheck::Challenge SkillCheck::challenge_generate(entity::Character& character)
|
||||
{
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
|
||||
Challenge newChallenge;
|
||||
|
||||
Zone newZone{};
|
||||
|
||||
auto zoneSize = std::max(schema.zoneMin, schema.zoneBase - (schema.zoneScoreBonus * score));
|
||||
newZone.min = math::random_max(1.0f - zoneSize);
|
||||
newZone.max = newZone.min + zoneSize;
|
||||
|
||||
newChallenge.zone = newZone;
|
||||
newChallenge.tryValue = 0.0f;
|
||||
|
||||
newChallenge.speed =
|
||||
glm::clamp(schema.speedMin, schema.speedMin + (schema.speedScoreBonus * score), schema.speedMax);
|
||||
|
||||
if (math::random_bool())
|
||||
{
|
||||
newChallenge.tryValue = 1.0f;
|
||||
newChallenge.speed *= -1;
|
||||
}
|
||||
|
||||
return newChallenge;
|
||||
}
|
||||
|
||||
SkillCheck::SkillCheck(entity::Character& character) { challenge = challenge_generate(character); }
|
||||
|
||||
void SkillCheck::reset(entity::Character& character)
|
||||
{
|
||||
challenge = challenge_generate(character);
|
||||
queuedChallenge = {};
|
||||
tryValue = challenge.tryValue;
|
||||
score = 0;
|
||||
combo = 0;
|
||||
endTimer = 0;
|
||||
endTimerMax = 0;
|
||||
highScoreStart = 0;
|
||||
isActive = true;
|
||||
isRewardScoreAchieved = false;
|
||||
isHighScoreAchieved = highScore > 0;
|
||||
isHighScoreAchievedThisRun = false;
|
||||
isGameOver = false;
|
||||
itemEffectManager = {};
|
||||
}
|
||||
|
||||
void SkillCheck::tick() { itemEffectManager.tick(); }
|
||||
|
||||
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text,
|
||||
Toasts& toasts)
|
||||
{
|
||||
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
|
||||
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
|
||||
static constexpr ImVec4 PERFECT_COLOR = ImVec4(1, 1, 1, 0.50);
|
||||
static constexpr auto BAR_SPACING_MULTIPLIER = 1.5f;
|
||||
static constexpr auto LINE_HEIGHT = 5.0f;
|
||||
static constexpr auto LINE_WIDTH_BONUS = 10.0f;
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& schema = character.data.skillCheckSchema;
|
||||
auto& itemSchema = character.data.itemSchema;
|
||||
auto& strings = character.data.strings;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto position = ImGui::GetCursorScreenPos();
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
auto spacing = ImGui::GetTextLineHeightWithSpacing() * BAR_SPACING_MULTIPLIER;
|
||||
auto& io = ImGui::GetIO();
|
||||
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
size.y = std::max(0.0f, size.y - menuButtonHeight);
|
||||
auto bounds = ImVec4(position.x, position.y, size.x, size.y);
|
||||
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::Text(strings.get(Strings::ArcadeScoreComboFormat).c_str(), score, combo);
|
||||
auto bestString =
|
||||
std::vformat(strings.get(Strings::ArcadeBestScoreComboFormat), std::make_format_args(highScore, bestCombo));
|
||||
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
|
||||
|
||||
ImGui::Text(strings.get(Strings::ArcadeBestScoreComboFormat).c_str(), highScore, bestCombo);
|
||||
|
||||
if (score == 0 && isActive)
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
|
||||
ImGui::TextWrapped("%s", strings.get(Strings::SkillCheckInstructions).c_str());
|
||||
}
|
||||
|
||||
auto barMin = ImVec2(position.x + (size.x * 0.5f) - (spacing * 0.5f), position.y + (spacing * 2.0f));
|
||||
auto barMax = ImVec2(barMin.x + (spacing * 2.0f), barMin.y + size.y - (spacing * 4.0f));
|
||||
auto endTimerProgress = (float)endTimer / endTimerMax;
|
||||
|
||||
auto bgColor = ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
|
||||
bgColor = imgui::to_imvec4(imgui::to_vec4(bgColor) * BG_COLOR_MULTIPLIER);
|
||||
drawList->AddRectFilled(barMin, barMax, ImGui::GetColorU32(bgColor));
|
||||
|
||||
auto barWidth = barMax.x - barMin.x;
|
||||
auto barHeight = barMax.y - barMin.y;
|
||||
|
||||
auto sub_zones_get = [&](Zone& zone)
|
||||
{
|
||||
auto& min = zone.min;
|
||||
auto& max = zone.max;
|
||||
std::vector<Zone> zones{};
|
||||
|
||||
auto baseHeight = max - min;
|
||||
auto center = (min + max) * 0.5f;
|
||||
|
||||
int zoneCount{};
|
||||
|
||||
for (auto& grade : schema.grades)
|
||||
{
|
||||
if (grade.isFailure) continue;
|
||||
|
||||
auto scale = powf(0.5f, (float)zoneCount);
|
||||
auto halfHeight = baseHeight * scale * 0.5f;
|
||||
|
||||
zoneCount++;
|
||||
|
||||
zones.push_back({center - halfHeight, center + halfHeight});
|
||||
}
|
||||
|
||||
return zones;
|
||||
};
|
||||
|
||||
auto zone_draw = [&](Zone& zone, float alpha = 1.0f)
|
||||
{
|
||||
auto subZones = sub_zones_get(zone);
|
||||
|
||||
for (int i = 0; i < (int)subZones.size(); i++)
|
||||
{
|
||||
auto& subZone = subZones[i];
|
||||
int layer = (int)subZones.size() - 1 - i;
|
||||
|
||||
ImVec2 rectMin = {barMin.x, barMin.y + subZone.min * barHeight};
|
||||
|
||||
ImVec2 rectMax = {barMax.x, barMin.y + subZone.max * barHeight};
|
||||
|
||||
ImVec4 color =
|
||||
i == (int)subZones.size() - 1 ? PERFECT_COLOR : ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered);
|
||||
color.w = (color.w - (float)layer / subZones.size()) * alpha;
|
||||
|
||||
drawList->AddRectFilled(rectMin, rectMax, ImGui::GetColorU32(color));
|
||||
}
|
||||
};
|
||||
|
||||
zone_draw(challenge.zone, isActive ? 1.0f : 0.0f);
|
||||
|
||||
auto lineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * tryValue));
|
||||
auto lineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, lineMin.y + LINE_HEIGHT);
|
||||
auto lineColor = LINE_COLOR;
|
||||
lineColor.w = isActive ? 1.0f : endTimerProgress;
|
||||
drawList->AddRectFilled(lineMin, lineMax, ImGui::GetColorU32(lineColor));
|
||||
|
||||
if (!isActive && !isGameOver)
|
||||
{
|
||||
zone_draw(queuedChallenge.zone, 1.0f - endTimerProgress);
|
||||
|
||||
auto queuedLineMin = ImVec2(barMin.x - LINE_WIDTH_BONUS, barMin.y + (barHeight * queuedChallenge.tryValue));
|
||||
auto queuedLineMax = ImVec2(barMin.x + barWidth + LINE_WIDTH_BONUS, queuedLineMin.y + LINE_HEIGHT);
|
||||
auto queuedLineColor = LINE_COLOR;
|
||||
queuedLineColor.w = 1.0f - endTimerProgress;
|
||||
drawList->AddRectFilled(queuedLineMin, queuedLineMax, ImGui::GetColorU32(queuedLineColor));
|
||||
}
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
tryValue += challenge.speed;
|
||||
|
||||
if (tryValue > 1.0f || tryValue < 0.0f)
|
||||
{
|
||||
tryValue = tryValue > 1.0f ? 0.0f : tryValue < 0.0f ? 1.0f : tryValue;
|
||||
|
||||
if (score > 0)
|
||||
{
|
||||
score--;
|
||||
schema.sounds.scoreLoss.play();
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeScoreLoss).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y);
|
||||
toasts.spawn(strings.get(Strings::ArcadeScoreLoss), toastMessagePosition, schema.endTimerMax);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(barMin);
|
||||
auto barButtonSize = ImVec2(barMax.x - barMin.x, barMax.y - barMin.y);
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Space) ||
|
||||
WIDGET_FX(ImGui::InvisibleButton("##SkillCheckBar", barButtonSize, ImGuiButtonFlags_PressedOnClick)))
|
||||
{
|
||||
int gradeID{};
|
||||
|
||||
auto subZones = sub_zones_get(challenge.zone);
|
||||
|
||||
for (int i = 0; i < (int)subZones.size(); i++)
|
||||
{
|
||||
auto& subZone = subZones[i];
|
||||
|
||||
if (tryValue >= subZone.min && tryValue <= subZone.max)
|
||||
gradeID = std::min((int)gradeID + 1, (int)schema.grades.size() - 1);
|
||||
}
|
||||
|
||||
gradeCounts[gradeID]++;
|
||||
totalPlays++;
|
||||
|
||||
auto& grade = schema.grades.at(gradeID);
|
||||
grade.sound.play();
|
||||
|
||||
if (text.is_interruptible() && grade.pool.is_valid()) text.set(dialogue.get(grade.pool), character);
|
||||
|
||||
if (!grade.isFailure)
|
||||
{
|
||||
combo++;
|
||||
score += grade.value;
|
||||
|
||||
if (score >= schema.rewardScore && !isRewardScoreAchieved)
|
||||
{
|
||||
schema.sounds.rewardScore.play();
|
||||
isRewardScoreAchieved = true;
|
||||
|
||||
for (auto& itemID : itemSchema.skillCheckRewardItemPool)
|
||||
itemRewards.item_give(itemID, inventory, itemEffectManager, itemSchema, bounds);
|
||||
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeRewardToast).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
|
||||
toasts.spawn(strings.get(Strings::ArcadeRewardToast), toastMessagePosition, schema.endTimerMax);
|
||||
}
|
||||
|
||||
if (score > highScore)
|
||||
{
|
||||
highScore = score;
|
||||
|
||||
if (isHighScoreAchieved && !isHighScoreAchievedThisRun)
|
||||
{
|
||||
isHighScoreAchievedThisRun = true;
|
||||
schema.sounds.highScore.play();
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::ArcadeHighScoreToast).c_str()).x -
|
||||
ImGui::GetTextLineHeightWithSpacing(),
|
||||
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
|
||||
toasts.spawn(strings.get(Strings::ArcadeHighScoreToast), toastMessagePosition, schema.endTimerMax);
|
||||
}
|
||||
}
|
||||
|
||||
if (combo > bestCombo) bestCombo = combo;
|
||||
|
||||
auto rewardChance = schema.rewardChanceBase + (schema.rewardChanceScoreBonus * score);
|
||||
auto rewardRollCount = schema.rewardRollChanceBase + (schema.rewardRollScoreBonus * score) +
|
||||
(schema.rewardRollGradeBonus * grade.value);
|
||||
itemRewards.reward_random_items_try(inventory, itemEffectManager, itemSchema, bounds, rewardChance,
|
||||
rewardRollCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
score = 0;
|
||||
combo = 0;
|
||||
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
|
||||
if (highScore > 0) isHighScoreAchieved = true;
|
||||
isRewardScoreAchieved = false;
|
||||
isHighScoreAchievedThisRun = false;
|
||||
highScoreStart = highScore;
|
||||
isGameOver = true;
|
||||
}
|
||||
|
||||
endTimerMax = grade.isFailure ? schema.endTimerFailureMax : schema.endTimerMax;
|
||||
isActive = false;
|
||||
endTimer = endTimerMax;
|
||||
|
||||
queuedChallenge = challenge_generate(character);
|
||||
|
||||
auto string = grade.isFailure ? grade.name
|
||||
: std::vformat(strings.get(Strings::SkillCheckGradeSuccessTemplate),
|
||||
std::make_format_args(grade.name, grade.value));
|
||||
auto toastMessagePosition =
|
||||
ImVec2(barMin.x - ImGui::CalcTextSize(string.c_str()).x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
|
||||
toasts.spawn(string, toastMessagePosition, endTimerMax);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimer--;
|
||||
if (endTimer <= 0)
|
||||
{
|
||||
challenge = queuedChallenge;
|
||||
tryValue = challenge.tryValue;
|
||||
isActive = true;
|
||||
isGameOver = false;
|
||||
}
|
||||
}
|
||||
|
||||
toasts.update(drawList);
|
||||
|
||||
itemEffectManager.render(resources, itemSchema, bounds, io.DeltaTime);
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
|
||||
auto isMenuPressed = WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeMenuBackButton).c_str()));
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetItemTooltip("%s", strings.get(Strings::ArcadeMenuBackButtonTooltip).c_str());
|
||||
return isMenuPressed;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../item_effect_manager.hpp"
|
||||
#include "../../item/reward.hpp"
|
||||
#include "../toasts.hpp"
|
||||
|
||||
#include "../../../../entity/character.hpp"
|
||||
#include "../../../../resources.hpp"
|
||||
|
||||
#include "../inventory.hpp"
|
||||
#include "../../text.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play::menu::arcade
|
||||
{
|
||||
class SkillCheck
|
||||
{
|
||||
|
||||
public:
|
||||
struct Zone
|
||||
{
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct Challenge
|
||||
{
|
||||
Zone zone{};
|
||||
float speed{};
|
||||
float tryValue{};
|
||||
int level{};
|
||||
};
|
||||
|
||||
Challenge challenge{};
|
||||
Challenge queuedChallenge{};
|
||||
float tryValue{};
|
||||
|
||||
int score{};
|
||||
int combo{};
|
||||
|
||||
int endTimer{};
|
||||
int endTimerMax{};
|
||||
|
||||
int highScoreStart{};
|
||||
|
||||
int bestCombo{};
|
||||
int highScore{};
|
||||
int totalPlays{};
|
||||
std::map<int, int> gradeCounts{};
|
||||
|
||||
bool isActive{true};
|
||||
bool isRewardScoreAchieved{false};
|
||||
bool isHighScoreAchieved{false};
|
||||
bool isHighScoreAchievedThisRun{false};
|
||||
bool isGameOver{};
|
||||
|
||||
game::state::play::menu::ItemEffectManager itemEffectManager{};
|
||||
game::state::play::item::Reward itemRewards{};
|
||||
|
||||
SkillCheck() = default;
|
||||
SkillCheck(entity::Character&);
|
||||
Challenge challenge_generate(entity::Character&);
|
||||
void reset(entity::Character&);
|
||||
void tick();
|
||||
bool update(Resources&, entity::Character&, Inventory&, Text&, Toasts&);
|
||||
float accuracy_score_get(entity::Character&);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
#include "gather.hpp"
|
||||
|
||||
#include "../../../resource/font.hpp"
|
||||
#include "../../../util/imgui.hpp"
|
||||
#include "../../../util/math.hpp"
|
||||
#include "../../../util/time.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
constexpr auto CANVAS_MIN_SIZE = 1.0f;
|
||||
constexpr auto CENTER_RATIO = 0.5f;
|
||||
constexpr auto START_TEXT_Y_RATIO = 0.75f;
|
||||
constexpr auto PLAYER_ACCELERATION = 3.0f;
|
||||
constexpr auto PLAYER_FRICTION_PER_SECOND = 0.72f;
|
||||
constexpr auto PLAYER_SIZE = 32.0f;
|
||||
constexpr auto PLAYER_VELOCITY_MAX = 720.0f;
|
||||
constexpr auto PLAYER_VELOCITY_STOP = 0.0f;
|
||||
constexpr auto PLAYER_BOOST_VELOCITY_GAIN = 320.0f;
|
||||
constexpr auto PLAYER_BOOST_VELOCITY_MIN = 540.0f;
|
||||
constexpr auto PLAYER_BOOST_DURATION = 0.35f;
|
||||
constexpr auto BOOST_DIRECTION_EPSILON = 0.001f;
|
||||
constexpr auto BOOST_DIRECTION_UNIT = 1.0f;
|
||||
constexpr auto GAME_TEXT_FONT_SIZE = resource::Font::HEADER_1;
|
||||
constexpr auto STAMINA_MIN = 0.0f;
|
||||
constexpr auto STAMINA_MAX = 1.0f;
|
||||
constexpr auto STAMINA_REGEN_PER_TICK = 0.002f;
|
||||
constexpr auto STAMINA_BOOST_COST = 0.25f;
|
||||
constexpr auto STAMINA_MAGNET_COST = 0.25f;
|
||||
constexpr auto STAMINA_BAR_WIDTH = 120.0f;
|
||||
constexpr auto STAMINA_BAR_HEIGHT = GAME_TEXT_FONT_SIZE;
|
||||
constexpr auto STAMINA_BAR_PADDING = 8.0f;
|
||||
constexpr auto STAMINA_BAR_PADDING_MULTIPLIER = 2.0f;
|
||||
constexpr auto STAMINA_BAR_ROUNDING = 2.0f;
|
||||
constexpr auto COLLECTIBLE_SIZE = 20.0f;
|
||||
constexpr auto COLLECTIBLE_OSCILLATION_SPEED = 6.0f;
|
||||
constexpr auto COLLECTIBLE_OSCILLATION_AMOUNT = 3.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_RADIUS = 72.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTENDED = 240.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS = 1.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER = 0.5f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION = 5200.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_GAIN = 26000.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_ACCELERATION_MAX = 26000.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_VELOCITY_MIN = 900.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_SPEED_MAX = 1800.0f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_FRICTION_PER_SECOND = 0.98f;
|
||||
constexpr auto COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP = 0.35f;
|
||||
constexpr auto COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER = 2.0f;
|
||||
constexpr auto COLLECTIBLE_STRUCTURE_SPACING = 34.0f;
|
||||
constexpr auto COLLECTIBLE_STRUCTURE_COUNT_MIN = 3;
|
||||
constexpr auto COLLECTIBLE_POLYGON_RADIUS_BASE = 28.0f;
|
||||
constexpr auto COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE = 4.5f;
|
||||
constexpr auto STAMINA_PICKUP_COUNT = 1;
|
||||
constexpr auto COLLECTIBLE_TWO_PI = 6.283185f;
|
||||
constexpr auto COLLECTIBLE_POLYGON_CHANCE = 0.20f;
|
||||
constexpr auto COLLECTIBLE_LINE_CHANCE = 0.55f;
|
||||
constexpr auto SCORE_TEXT_PADDING = 8.0f;
|
||||
constexpr auto SCORE_TEXT_LINE_MULTIPLIER = 1.15f;
|
||||
constexpr auto GAME_TIME_START_SECONDS = 30.0f;
|
||||
constexpr auto GAME_TIME_START_TICKS = GAME_TIME_START_SECONDS * Entity::UPDATE_RATE;
|
||||
constexpr auto GAME_TIME_GAIN_SECONDS = 5.0f;
|
||||
constexpr auto GAME_TIME_GAIN_TICKS = GAME_TIME_GAIN_SECONDS * Entity::UPDATE_RATE;
|
||||
constexpr auto WAVE_RESET_FADE_SECONDS = 0.125f;
|
||||
constexpr auto TIME_GAIN_ALPHA_MAX = 1.0f;
|
||||
constexpr auto TIME_GAIN_FADE_SECONDS = 1.25f;
|
||||
constexpr auto TIME_TEXT_PADDING = 8.0f;
|
||||
constexpr auto COLOR_CHANNEL_MAX = 255;
|
||||
constexpr auto COLOR_GREEN_DIM = 64;
|
||||
constexpr auto COLOR_GREEN = 220;
|
||||
constexpr auto COLOR_COLLECTIBLE_BLUE = 255;
|
||||
constexpr auto COLOR_TIME = 255;
|
||||
constexpr auto COLOR_MAGNET_BLUE = 255;
|
||||
constexpr auto COLOR_MAGNET_ALPHA = 70;
|
||||
constexpr auto MAGNET_CIRCLE_SEGMENTS = 96;
|
||||
constexpr auto MAGNET_CIRCLE_THICKNESS = 2.0f;
|
||||
constexpr auto COLOR_COLLECTIBLE_ALPHA = 255;
|
||||
constexpr auto COLOR_COLLECTIBLE_NEXT_ALPHA = 35;
|
||||
constexpr auto STYLE_VAR_COUNT = 1;
|
||||
constexpr auto CANVAS_FLAGS = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
|
||||
|
||||
float Gather::random_range_get(float min, float max)
|
||||
{
|
||||
if (min >= max) return min;
|
||||
return util::math::random_in_range(min, max);
|
||||
}
|
||||
|
||||
glm::vec2 Gather::random_position_get(float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
return {random_range_get(minX, maxX), random_range_get(minY, maxY)};
|
||||
}
|
||||
|
||||
Entity Gather::collectible_make(glm::vec2 position)
|
||||
{
|
||||
Entity collectible{};
|
||||
collectible.position = position;
|
||||
collectible.phase = util::math::random_max(COLLECTIBLE_TWO_PI);
|
||||
return collectible;
|
||||
}
|
||||
|
||||
Gather::Structure Gather::structure_get(int remaining)
|
||||
{
|
||||
auto roll = util::math::random();
|
||||
if (remaining < COLLECTIBLE_STRUCTURE_COUNT_MIN) return Structure::SINGLE;
|
||||
if (roll < COLLECTIBLE_POLYGON_CHANCE) return Structure::POLYGON;
|
||||
if (roll < COLLECTIBLE_LINE_CHANCE)
|
||||
return util::math::random_bool() ? Structure::LINE_HORIZONTAL : Structure::LINE_VERTICAL;
|
||||
return Structure::SINGLE;
|
||||
}
|
||||
|
||||
int Gather::structure_count_get(int remaining)
|
||||
{
|
||||
auto countRange = (remaining - COLLECTIBLE_STRUCTURE_COUNT_MIN) + 1;
|
||||
auto countRoll = std::min(countRange - 1, (int)util::math::random_max((float)countRange));
|
||||
return COLLECTIBLE_STRUCTURE_COUNT_MIN + countRoll;
|
||||
}
|
||||
|
||||
int Gather::wave_collectible_count_get(int wave) { return std::max(1, wave); }
|
||||
|
||||
void Gather::collectible_add(std::vector<Entity>& wave, glm::vec2 position, int targetCount)
|
||||
{
|
||||
if ((int)wave.size() >= targetCount) return;
|
||||
wave.emplace_back(collectible_make(position));
|
||||
}
|
||||
|
||||
void Gather::single_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int targetCount)
|
||||
{
|
||||
auto position = random_position_get(margin, margin, canvasSize.x - margin, canvasSize.y - margin);
|
||||
collectible_add(wave, position, targetCount);
|
||||
}
|
||||
|
||||
void Gather::line_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, bool isHorizontal,
|
||||
int structureCount, int targetCount)
|
||||
{
|
||||
auto structureHalf = COLLECTIBLE_STRUCTURE_SPACING * ((float)structureCount - 1) * CENTER_RATIO;
|
||||
auto clearance = structureHalf + (COLLECTIBLE_SIZE * CENTER_RATIO);
|
||||
auto center =
|
||||
isHorizontal
|
||||
? random_position_get(margin + clearance, margin, canvasSize.x - margin - clearance, canvasSize.y - margin)
|
||||
: random_position_get(margin, margin + clearance, canvasSize.x - margin, canvasSize.y - margin - clearance);
|
||||
for (auto i = 0; i < structureCount; ++i)
|
||||
{
|
||||
auto offset = ((float)i - (((float)structureCount - 1) * CENTER_RATIO)) * COLLECTIBLE_STRUCTURE_SPACING;
|
||||
auto position = isHorizontal ? glm::vec2(center.x + offset, center.y) : glm::vec2(center.x, center.y + offset);
|
||||
collectible_add(wave, position, targetCount);
|
||||
}
|
||||
}
|
||||
|
||||
float Gather::polygon_radius_get(int structureCount)
|
||||
{
|
||||
return COLLECTIBLE_POLYGON_RADIUS_BASE + ((float)structureCount * COLLECTIBLE_POLYGON_RADIUS_PER_COLLECTIBLE);
|
||||
}
|
||||
|
||||
void Gather::polygon_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, float margin, int structureCount,
|
||||
int targetCount)
|
||||
{
|
||||
auto radius = polygon_radius_get(structureCount);
|
||||
auto clearance = radius + (COLLECTIBLE_SIZE * CENTER_RATIO);
|
||||
auto center = random_position_get(margin + clearance, margin + clearance, canvasSize.x - margin - clearance,
|
||||
canvasSize.y - margin - clearance);
|
||||
for (auto i = 0; i < structureCount; ++i)
|
||||
{
|
||||
auto angle = COLLECTIBLE_TWO_PI * ((float)i / (float)structureCount);
|
||||
auto position = center + glm::vec2(std::cos(angle), std::sin(angle)) * radius;
|
||||
collectible_add(wave, position, targetCount);
|
||||
}
|
||||
}
|
||||
|
||||
void Gather::wave_spawn(std::vector<Entity>& wave, ImVec2 canvasSize, int waveNumber)
|
||||
{
|
||||
wave.clear();
|
||||
auto targetCount = wave_collectible_count_get(waveNumber);
|
||||
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
|
||||
while ((int)wave.size() < targetCount)
|
||||
{
|
||||
auto remaining = targetCount - (int)wave.size();
|
||||
switch (structure_get(remaining))
|
||||
{
|
||||
case Structure::LINE_HORIZONTAL:
|
||||
line_spawn(wave, canvasSize, margin, true, structure_count_get(remaining), targetCount);
|
||||
break;
|
||||
case Structure::LINE_VERTICAL:
|
||||
line_spawn(wave, canvasSize, margin, false, structure_count_get(remaining), targetCount);
|
||||
break;
|
||||
case Structure::POLYGON:
|
||||
polygon_spawn(wave, canvasSize, margin, structure_count_get(remaining), targetCount);
|
||||
break;
|
||||
case Structure::SINGLE:
|
||||
default:
|
||||
single_spawn(wave, canvasSize, margin, targetCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Gather::stamina_pickup_count_get(int) { return STAMINA_PICKUP_COUNT; }
|
||||
|
||||
void Gather::stamina_pickups_spawn(std::vector<Entity>& pickups, ImVec2 canvasSize, int waveNumber)
|
||||
{
|
||||
pickups.clear();
|
||||
auto targetCount = stamina_pickup_count_get(waveNumber);
|
||||
auto margin = PLAYER_SIZE * COLLECTIBLE_SPAWN_MARGIN_MULTIPLIER;
|
||||
while ((int)pickups.size() < targetCount)
|
||||
single_spawn(pickups, canvasSize, margin, targetCount);
|
||||
}
|
||||
|
||||
float Gather::wave_time_gain_ticks_get() { return GAME_TIME_GAIN_TICKS; }
|
||||
|
||||
bool Gather::collectible_hit_get(const Entity& collectible, const Entity& player)
|
||||
{
|
||||
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
|
||||
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
|
||||
auto distance = glm::abs(player.position - collectible.position);
|
||||
return distance.x <= playerHalf + collectibleHalf && distance.y <= playerHalf + collectibleHalf;
|
||||
}
|
||||
|
||||
float Gather::magnet_radius_get(float magnetRadiusTime, bool isMenuFullscreen)
|
||||
{
|
||||
auto multiplier = isMenuFullscreen ? STAMINA_MAX : COLLECTIBLE_MAGNET_RADIUS_WINDOWED_MULTIPLIER;
|
||||
auto baseRadius = COLLECTIBLE_MAGNET_RADIUS * multiplier;
|
||||
auto extendedRadius = COLLECTIBLE_MAGNET_RADIUS_EXTENDED * multiplier;
|
||||
auto ratio = std::clamp(magnetRadiusTime / COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS, STAMINA_MIN, STAMINA_MAX);
|
||||
return baseRadius + ((extendedRadius - baseRadius) * ratio);
|
||||
}
|
||||
|
||||
int Gather::collectibles_collect(std::vector<Entity>& wave, const Entity& player)
|
||||
{
|
||||
auto collected = 0;
|
||||
for (auto& collectible : wave)
|
||||
{
|
||||
if (collectible.isCollected || !collectible_hit_get(collectible, player)) continue;
|
||||
collectible.isCollected = true;
|
||||
++collected;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
||||
void Gather::collectibles_magnet_update(std::vector<Entity>& wave, const Entity& player, ImVec2 canvasSize,
|
||||
float magnetRadius, float dt)
|
||||
{
|
||||
auto radiusSquared = magnetRadius * magnetRadius;
|
||||
auto collectibleHalf = COLLECTIBLE_SIZE * CENTER_RATIO;
|
||||
auto minX = collectibleHalf;
|
||||
auto minY = collectibleHalf;
|
||||
auto maxX = std::max(minX, canvasSize.x - collectibleHalf);
|
||||
auto maxY = std::max(minY, canvasSize.y - collectibleHalf);
|
||||
for (auto& collectible : wave)
|
||||
{
|
||||
if (collectible.isCollected) continue;
|
||||
auto direction = player.position - collectible.position;
|
||||
auto distanceSquared = (direction.x * direction.x) + (direction.y * direction.y);
|
||||
if (distanceSquared <= radiusSquared) collectible.isMagnetized = true;
|
||||
if (collectible.isMagnetized && distanceSquared > BOOST_DIRECTION_EPSILON)
|
||||
{
|
||||
collectible.magnetTime += dt;
|
||||
auto magnetAcceleration =
|
||||
std::min(COLLECTIBLE_MAGNET_ACCELERATION_MAX,
|
||||
COLLECTIBLE_MAGNET_ACCELERATION + (COLLECTIBLE_MAGNET_ACCELERATION_GAIN * collectible.magnetTime));
|
||||
auto distance = std::sqrt(distanceSquared);
|
||||
auto directionUnit = direction * (BOOST_DIRECTION_UNIT / distance);
|
||||
auto inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
|
||||
auto inward = directionUnit * inwardVelocity;
|
||||
auto sideways = collectible.velocity - inward;
|
||||
collectible.velocity = inward + (sideways * COLLECTIBLE_MAGNET_SIDE_VELOCITY_KEEP);
|
||||
collectible.velocity += directionUnit * magnetAcceleration * dt;
|
||||
inwardVelocity = (collectible.velocity.x * directionUnit.x) + (collectible.velocity.y * directionUnit.y);
|
||||
if (inwardVelocity < COLLECTIBLE_MAGNET_VELOCITY_MIN)
|
||||
collectible.velocity += directionUnit * (COLLECTIBLE_MAGNET_VELOCITY_MIN - inwardVelocity);
|
||||
}
|
||||
|
||||
auto velocityLength = std::sqrt((collectible.velocity.x * collectible.velocity.x) +
|
||||
(collectible.velocity.y * collectible.velocity.y));
|
||||
if (velocityLength > COLLECTIBLE_MAGNET_SPEED_MAX)
|
||||
{
|
||||
auto velocityScale = COLLECTIBLE_MAGNET_SPEED_MAX / velocityLength;
|
||||
collectible.velocity *= velocityScale;
|
||||
}
|
||||
|
||||
auto velocityRetention = std::pow(COLLECTIBLE_MAGNET_FRICTION_PER_SECOND, dt);
|
||||
collectible.velocity *= velocityRetention;
|
||||
collectible.position += collectible.velocity * dt;
|
||||
if (collectible.position.x < minX || collectible.position.x > maxX) collectible.velocity.x = PLAYER_VELOCITY_STOP;
|
||||
if (collectible.position.y < minY || collectible.position.y > maxY) collectible.velocity.y = PLAYER_VELOCITY_STOP;
|
||||
collectible.position.x = std::clamp(collectible.position.x, minX, maxX);
|
||||
collectible.position.y = std::clamp(collectible.position.y, minY, maxY);
|
||||
}
|
||||
}
|
||||
|
||||
bool Gather::wave_complete_get(const std::vector<Entity>& wave)
|
||||
{
|
||||
if (wave.empty()) return false;
|
||||
for (auto& collectible : wave)
|
||||
if (!collectible.isCollected) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gather::collectibles_draw(const std::vector<Entity>& wave, ImDrawList* drawList, ImVec2 canvasMin, float time,
|
||||
ImU32 color)
|
||||
{
|
||||
for (auto& collectible : wave)
|
||||
{
|
||||
if (collectible.isCollected) continue;
|
||||
auto size = COLLECTIBLE_SIZE + (std::sin((time * COLLECTIBLE_OSCILLATION_SPEED) + collectible.phase) *
|
||||
COLLECTIBLE_OSCILLATION_AMOUNT);
|
||||
auto half = size * CENTER_RATIO;
|
||||
auto min = ImVec2(canvasMin.x + collectible.position.x - half, canvasMin.y + collectible.position.y - half);
|
||||
auto max = ImVec2(min.x + size, min.y + size);
|
||||
drawList->AddRectFilled(min, max, color);
|
||||
}
|
||||
}
|
||||
|
||||
void Gather::magnet_radius_draw(ImDrawList* drawList, ImVec2 canvasMin, const Entity& player, float radius)
|
||||
{
|
||||
auto center = ImVec2(canvasMin.x + player.position.x, canvasMin.y + player.position.y);
|
||||
drawList->AddCircle(center, radius, IM_COL32(0, 0, COLOR_MAGNET_BLUE, COLOR_MAGNET_ALPHA), MAGNET_CIRCLE_SEGMENTS,
|
||||
MAGNET_CIRCLE_THICKNESS);
|
||||
}
|
||||
|
||||
int Gather::alpha_get(int alpha, float multiplier)
|
||||
{
|
||||
return (int)((float)alpha * std::clamp(multiplier, STAMINA_MIN, STAMINA_MAX));
|
||||
}
|
||||
|
||||
void Gather::progress_bar_draw(ImDrawList* drawList, ImVec2 min, ImVec2 max, float progress, ImU32 backgroundColor,
|
||||
ImU32 fillColor, const std::string& text)
|
||||
{
|
||||
auto fillMax = ImVec2(min.x + ((max.x - min.x) * std::clamp(progress, STAMINA_MIN, STAMINA_MAX)), max.y);
|
||||
drawList->AddRectFilled(min, max, backgroundColor, STAMINA_BAR_ROUNDING);
|
||||
drawList->AddRectFilled(min, fillMax, fillColor, STAMINA_BAR_ROUNDING);
|
||||
if (text.empty()) return;
|
||||
|
||||
auto textSize = util::imgui::draw_list_text_size_get(text, GAME_TEXT_FONT_SIZE);
|
||||
auto textPos = ImVec2(min.x + ((max.x - min.x - textSize.x) * CENTER_RATIO),
|
||||
min.y + ((max.y - min.y - textSize.y) * CENTER_RATIO));
|
||||
util::imgui::draw_list_text_draw(
|
||||
*drawList, textPos, text, GAME_TEXT_FONT_SIZE,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
}
|
||||
|
||||
void Gather::reset()
|
||||
{
|
||||
player = {};
|
||||
collectibles.clear();
|
||||
nextCollectibles.clear();
|
||||
staminaPickups.clear();
|
||||
nextStaminaPickups.clear();
|
||||
targetPosition = {};
|
||||
stamina = STAMINA_MAX;
|
||||
boostTime = STAMINA_MIN;
|
||||
magnetRadiusTime = STAMINA_MIN;
|
||||
timeTicks = GAME_TIME_START_TICKS;
|
||||
timeGainTicks = STAMINA_MIN;
|
||||
timeGainAlpha = STAMINA_MIN;
|
||||
score = 0;
|
||||
wave = 1;
|
||||
isStarted = false;
|
||||
isPaused = false;
|
||||
isMenuFullscreenKnown = false;
|
||||
isMenuFullscreenLast = false;
|
||||
isWaveResetPending = false;
|
||||
waveResetAlpha = STAMINA_MAX;
|
||||
}
|
||||
|
||||
void Gather::start(ImVec2 canvasSize)
|
||||
{
|
||||
reset();
|
||||
player.position = glm::vec2(canvasSize.x * CENTER_RATIO, canvasSize.y * CENTER_RATIO);
|
||||
targetPosition = player.position;
|
||||
isStarted = true;
|
||||
wave_reset(canvasSize);
|
||||
}
|
||||
|
||||
void Gather::wave_reset(ImVec2 canvasSize)
|
||||
{
|
||||
wave_spawn(collectibles, canvasSize, wave);
|
||||
wave_spawn(nextCollectibles, canvasSize, wave + 1);
|
||||
stamina_pickups_spawn(staminaPickups, canvasSize, wave);
|
||||
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
|
||||
magnetRadiusTime = STAMINA_MIN;
|
||||
isWaveResetPending = false;
|
||||
waveResetAlpha = STAMINA_MAX;
|
||||
}
|
||||
|
||||
void Gather::wave_reset_request()
|
||||
{
|
||||
isWaveResetPending = true;
|
||||
waveResetAlpha = STAMINA_MAX;
|
||||
magnetRadiusTime = STAMINA_MIN;
|
||||
}
|
||||
|
||||
void Gather::score_draw(ImDrawList* drawList, ImVec2 canvasMin, int score, int wave)
|
||||
{
|
||||
auto scoreText = std::format("{}", score);
|
||||
auto position = ImVec2(canvasMin.x + SCORE_TEXT_PADDING, canvasMin.y + SCORE_TEXT_PADDING);
|
||||
util::imgui::draw_list_text_draw(
|
||||
*drawList, position, scoreText, GAME_TEXT_FONT_SIZE,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
auto waveText = std::format("Wave #{}", wave);
|
||||
auto wavePosition = ImVec2(position.x, position.y + (GAME_TEXT_FONT_SIZE * SCORE_TEXT_LINE_MULTIPLIER));
|
||||
util::imgui::draw_list_text_draw(
|
||||
*drawList, wavePosition, waveText, GAME_TEXT_FONT_SIZE,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
}
|
||||
|
||||
void Gather::time_draw(ImDrawList* drawList, ImVec2 canvasMin, ImVec2 canvasMax, float timeTicks, float timeGainTicks,
|
||||
float timeGainAlpha)
|
||||
{
|
||||
auto timeText = util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeTicks));
|
||||
auto textSize = util::imgui::draw_list_text_size_get(timeText, GAME_TEXT_FONT_SIZE);
|
||||
auto textWidth = util::imgui::draw_list_digit_fixed_width_get(timeText, GAME_TEXT_FONT_SIZE);
|
||||
auto textPos = ImVec2(canvasMax.x - textWidth - TIME_TEXT_PADDING, canvasMin.y + TIME_TEXT_PADDING);
|
||||
util::imgui::draw_list_digit_fixed_width_draw(*drawList, textPos, timeText, GAME_TEXT_FONT_SIZE,
|
||||
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, COLOR_CHANNEL_MAX));
|
||||
|
||||
if (timeGainAlpha <= STAMINA_MIN) return;
|
||||
auto gainText =
|
||||
std::format("+{}", util::time::ticks_to_minutes_seconds_centiseconds((int)std::ceil(timeGainTicks)));
|
||||
auto alpha = (int)(COLOR_CHANNEL_MAX * std::clamp(timeGainAlpha, STAMINA_MIN, STAMINA_MAX));
|
||||
auto gainTextWidth = util::imgui::draw_list_digit_fixed_width_get(gainText, GAME_TEXT_FONT_SIZE);
|
||||
auto gainPos = ImVec2(canvasMax.x - gainTextWidth - TIME_TEXT_PADDING, textPos.y + textSize.y + TIME_TEXT_PADDING);
|
||||
util::imgui::draw_list_digit_fixed_width_draw(*drawList, gainPos, gainText, GAME_TEXT_FONT_SIZE,
|
||||
IM_COL32(COLOR_TIME, COLOR_TIME, COLOR_TIME, alpha));
|
||||
}
|
||||
|
||||
void Gather::update(bool isMenuFullscreen, bool isMenuResizeSettled)
|
||||
{
|
||||
auto canvasSize = ImGui::GetContentRegionAvail();
|
||||
if (canvasSize.x <= CANVAS_MIN_SIZE || canvasSize.y <= CANVAS_MIN_SIZE) return;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::BeginChild("##Gather Canvas Child", canvasSize, ImGuiChildFlags_None, CANVAS_FLAGS))
|
||||
{
|
||||
canvasSize = ImGui::GetContentRegionAvail();
|
||||
auto canvasMin = ImGui::GetCursorScreenPos();
|
||||
auto canvasMax = ImVec2(canvasMin.x + canvasSize.x, canvasMin.y + canvasSize.y);
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
auto dt = ImGui::GetIO().DeltaTime;
|
||||
auto isCanvasFocused = ImGui::IsWindowFocused();
|
||||
|
||||
ImGui::InvisibleButton("##Gather Canvas", canvasSize);
|
||||
auto isCanvasHovered = ImGui::IsItemHovered();
|
||||
|
||||
drawList->AddRectFilled(canvasMin, canvasMax, IM_COL32(0, 0, 0, COLOR_CHANNEL_MAX / 2));
|
||||
|
||||
if (!isMenuFullscreenKnown)
|
||||
{
|
||||
isMenuFullscreenKnown = true;
|
||||
isMenuFullscreenLast = isMenuFullscreen;
|
||||
}
|
||||
auto isMenuFullscreenChanged = isMenuFullscreenLast != isMenuFullscreen;
|
||||
isMenuFullscreenLast = isMenuFullscreen;
|
||||
|
||||
if (isStarted && isMenuFullscreenChanged) wave_reset_request();
|
||||
|
||||
if (isStarted && isWaveResetPending)
|
||||
{
|
||||
waveResetAlpha = std::max(STAMINA_MIN, waveResetAlpha - (dt / WAVE_RESET_FADE_SECONDS));
|
||||
if (isMenuResizeSettled) wave_reset(canvasSize);
|
||||
}
|
||||
|
||||
if (!isStarted && isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) start(canvasSize);
|
||||
|
||||
if (isStarted)
|
||||
{
|
||||
if (isCanvasFocused && ImGui::IsKeyPressed(ImGuiKey_P, false)) isPaused = !isPaused;
|
||||
|
||||
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
|
||||
auto minX = std::min(playerHalf, canvasSize.x * CENTER_RATIO);
|
||||
auto minY = std::min(playerHalf, canvasSize.y * CENTER_RATIO);
|
||||
auto maxX = std::max(minX, canvasSize.x - playerHalf);
|
||||
auto maxY = std::max(minY, canvasSize.y - playerHalf);
|
||||
|
||||
player.position.x = std::clamp(player.position.x, minX, maxX);
|
||||
player.position.y = std::clamp(player.position.y, minY, maxY);
|
||||
targetPosition.x = std::clamp(targetPosition.x, minX, maxX);
|
||||
targetPosition.y = std::clamp(targetPosition.y, minY, maxY);
|
||||
|
||||
if (!isPaused)
|
||||
{
|
||||
if (isCanvasHovered)
|
||||
{
|
||||
auto mouse = ImGui::GetIO().MousePos;
|
||||
targetPosition =
|
||||
glm::vec2(std::clamp(mouse.x - canvasMin.x, minX, maxX), std::clamp(mouse.y - canvasMin.y, minY, maxY));
|
||||
}
|
||||
|
||||
timeTicks = std::max(STAMINA_MIN, timeTicks - (Entity::UPDATE_RATE * dt));
|
||||
if (timeTicks <= STAMINA_MIN) reset();
|
||||
|
||||
if (isStarted)
|
||||
{
|
||||
timeGainAlpha = std::max(STAMINA_MIN, timeGainAlpha - (dt / TIME_GAIN_FADE_SECONDS));
|
||||
stamina =
|
||||
std::clamp(stamina + (STAMINA_REGEN_PER_TICK * Entity::UPDATE_RATE * dt), STAMINA_MIN, STAMINA_MAX);
|
||||
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && stamina >= STAMINA_MAGNET_COST)
|
||||
{
|
||||
magnetRadiusTime = COLLECTIBLE_MAGNET_RADIUS_EXTEND_SECONDS;
|
||||
stamina = std::max(STAMINA_MIN, stamina - STAMINA_MAGNET_COST);
|
||||
}
|
||||
if (isCanvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && stamina >= STAMINA_BOOST_COST)
|
||||
{
|
||||
auto boostDirection = targetPosition - player.position;
|
||||
auto boostDirectionLength =
|
||||
std::sqrt((boostDirection.x * boostDirection.x) + (boostDirection.y * boostDirection.y));
|
||||
if (boostDirectionLength > BOOST_DIRECTION_EPSILON)
|
||||
{
|
||||
auto boostDirectionScale = BOOST_DIRECTION_UNIT / boostDirectionLength;
|
||||
auto boostDirectionUnit = boostDirection * boostDirectionScale;
|
||||
player.velocity.x += boostDirectionUnit.x * PLAYER_BOOST_VELOCITY_GAIN;
|
||||
player.velocity.y += boostDirectionUnit.y * PLAYER_BOOST_VELOCITY_GAIN;
|
||||
auto boostVelocity =
|
||||
(player.velocity.x * boostDirectionUnit.x) + (player.velocity.y * boostDirectionUnit.y);
|
||||
if (boostVelocity < PLAYER_BOOST_VELOCITY_MIN)
|
||||
{
|
||||
auto boostVelocityAdd = PLAYER_BOOST_VELOCITY_MIN - boostVelocity;
|
||||
player.velocity.x += boostDirectionUnit.x * boostVelocityAdd;
|
||||
player.velocity.y += boostDirectionUnit.y * boostVelocityAdd;
|
||||
}
|
||||
boostTime = PLAYER_BOOST_DURATION;
|
||||
stamina = std::max(STAMINA_MIN, stamina - STAMINA_BOOST_COST);
|
||||
}
|
||||
}
|
||||
|
||||
player.velocity.x += (targetPosition.x - player.position.x) * PLAYER_ACCELERATION * dt;
|
||||
player.velocity.y += (targetPosition.y - player.position.y) * PLAYER_ACCELERATION * dt;
|
||||
auto velocityRetention = std::pow(PLAYER_FRICTION_PER_SECOND, dt);
|
||||
player.velocity.x *= velocityRetention;
|
||||
player.velocity.y *= velocityRetention;
|
||||
auto velocityLength =
|
||||
std::sqrt((player.velocity.x * player.velocity.x) + (player.velocity.y * player.velocity.y));
|
||||
auto isBoostActive = boostTime > STAMINA_MIN;
|
||||
if (!isBoostActive && velocityLength > PLAYER_VELOCITY_MAX)
|
||||
{
|
||||
auto velocityScale = PLAYER_VELOCITY_MAX / velocityLength;
|
||||
player.velocity.x *= velocityScale;
|
||||
player.velocity.y *= velocityScale;
|
||||
}
|
||||
boostTime = std::max(STAMINA_MIN, boostTime - dt);
|
||||
player.position.x += player.velocity.x * dt;
|
||||
player.position.y += player.velocity.y * dt;
|
||||
|
||||
if (player.position.x < minX || player.position.x > maxX) player.velocity.x = PLAYER_VELOCITY_STOP;
|
||||
if (player.position.y < minY || player.position.y > maxY) player.velocity.y = PLAYER_VELOCITY_STOP;
|
||||
player.position.x = std::clamp(player.position.x, minX, maxX);
|
||||
player.position.y = std::clamp(player.position.y, minY, maxY);
|
||||
|
||||
if (!isWaveResetPending)
|
||||
{
|
||||
auto magnetRadius = magnet_radius_get(magnetRadiusTime, isMenuFullscreen);
|
||||
collectibles_magnet_update(collectibles, player, canvasSize, magnetRadius, dt);
|
||||
magnetRadiusTime = std::max(STAMINA_MIN, magnetRadiusTime - dt);
|
||||
score += collectibles_collect(collectibles, player);
|
||||
if (collectibles_collect(staminaPickups, player) > 0) stamina = STAMINA_MAX;
|
||||
if (wave_complete_get(collectibles))
|
||||
{
|
||||
timeGainTicks = wave_time_gain_ticks_get();
|
||||
timeTicks += timeGainTicks;
|
||||
timeGainAlpha = TIME_GAIN_ALPHA_MAX;
|
||||
++wave;
|
||||
collectibles = nextCollectibles;
|
||||
staminaPickups = nextStaminaPickups;
|
||||
wave_spawn(nextCollectibles, canvasSize, wave + 1);
|
||||
stamina_pickups_spawn(nextStaminaPickups, canvasSize, wave + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isStarted)
|
||||
{
|
||||
auto playerHalf = PLAYER_SIZE * CENTER_RATIO;
|
||||
auto collectibleAlpha = alpha_get(COLOR_COLLECTIBLE_ALPHA, waveResetAlpha);
|
||||
auto nextCollectibleAlpha = alpha_get(COLOR_COLLECTIBLE_NEXT_ALPHA, waveResetAlpha);
|
||||
collectibles_draw(nextCollectibles, drawList, canvasMin, (float)ImGui::GetTime(),
|
||||
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, nextCollectibleAlpha));
|
||||
collectibles_draw(nextStaminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
|
||||
IM_COL32(0, COLOR_GREEN, 0, nextCollectibleAlpha));
|
||||
collectibles_draw(collectibles, drawList, canvasMin, (float)ImGui::GetTime(),
|
||||
IM_COL32(0, 0, COLOR_COLLECTIBLE_BLUE, collectibleAlpha));
|
||||
collectibles_draw(staminaPickups, drawList, canvasMin, (float)ImGui::GetTime(),
|
||||
IM_COL32(0, COLOR_GREEN, 0, collectibleAlpha));
|
||||
magnet_radius_draw(drawList, canvasMin, player, magnet_radius_get(magnetRadiusTime, isMenuFullscreen));
|
||||
|
||||
auto playerMin =
|
||||
ImVec2(canvasMin.x + player.position.x - playerHalf, canvasMin.y + player.position.y - playerHalf);
|
||||
auto playerMax = ImVec2(playerMin.x + PLAYER_SIZE, playerMin.y + PLAYER_SIZE);
|
||||
drawList->AddRectFilled(playerMin, playerMax,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
|
||||
auto staminaBarWidth =
|
||||
std::min(STAMINA_BAR_WIDTH,
|
||||
std::max(CANVAS_MIN_SIZE, canvasSize.x - (STAMINA_BAR_PADDING * STAMINA_BAR_PADDING_MULTIPLIER)));
|
||||
auto staminaBarMin = ImVec2(canvasMax.x - staminaBarWidth - STAMINA_BAR_PADDING,
|
||||
canvasMax.y - STAMINA_BAR_HEIGHT - STAMINA_BAR_PADDING);
|
||||
auto staminaBarMax = ImVec2(staminaBarMin.x + staminaBarWidth, staminaBarMin.y + STAMINA_BAR_HEIGHT);
|
||||
progress_bar_draw(drawList, staminaBarMin, staminaBarMax, stamina,
|
||||
IM_COL32(0, COLOR_GREEN_DIM, 0, COLOR_CHANNEL_MAX),
|
||||
IM_COL32(0, COLOR_GREEN, 0, COLOR_CHANNEL_MAX));
|
||||
score_draw(drawList, canvasMin, score, wave);
|
||||
time_draw(drawList, canvasMin, canvasMax, timeTicks, timeGainTicks, timeGainAlpha);
|
||||
if (isPaused)
|
||||
util::imgui::draw_list_text_center_draw(
|
||||
*drawList, canvasMin, canvasSize, "PAUSED", GAME_TEXT_FONT_SIZE, CENTER_RATIO, CENTER_RATIO,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
}
|
||||
else
|
||||
util::imgui::draw_list_text_center_draw(
|
||||
*drawList, canvasMin, canvasSize, "Click to start!", GAME_TEXT_FONT_SIZE, CENTER_RATIO, START_TEXT_Y_RATIO,
|
||||
IM_COL32(COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX, COLOR_CHANNEL_MAX));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar(STYLE_VAR_COUNT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../entity.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
class Gather
|
||||
{
|
||||
public:
|
||||
Entity player{};
|
||||
std::vector<Entity> collectibles{};
|
||||
std::vector<Entity> nextCollectibles{};
|
||||
std::vector<Entity> staminaPickups{};
|
||||
std::vector<Entity> nextStaminaPickups{};
|
||||
glm::vec2 targetPosition{};
|
||||
float stamina{};
|
||||
float boostTime{};
|
||||
float magnetRadiusTime{};
|
||||
float timeTicks{};
|
||||
float timeGainTicks{};
|
||||
float timeGainAlpha{};
|
||||
int score{};
|
||||
int wave{};
|
||||
bool isStarted{};
|
||||
bool isPaused{};
|
||||
bool isMenuFullscreenKnown{};
|
||||
bool isMenuFullscreenLast{};
|
||||
bool isWaveResetPending{};
|
||||
float waveResetAlpha{};
|
||||
|
||||
void update(bool isMenuFullscreen, bool isMenuResizeSettled);
|
||||
|
||||
private:
|
||||
enum class Structure
|
||||
{
|
||||
SINGLE,
|
||||
LINE_HORIZONTAL,
|
||||
LINE_VERTICAL,
|
||||
POLYGON
|
||||
};
|
||||
|
||||
static float random_range_get(float, float);
|
||||
static glm::vec2 random_position_get(float, float, float, float);
|
||||
static Entity collectible_make(glm::vec2);
|
||||
static Structure structure_get(int);
|
||||
static int structure_count_get(int);
|
||||
static int wave_collectible_count_get(int);
|
||||
static void collectible_add(std::vector<Entity>&, glm::vec2, int);
|
||||
static void single_spawn(std::vector<Entity>&, ImVec2, float, int);
|
||||
static void line_spawn(std::vector<Entity>&, ImVec2, float, bool, int, int);
|
||||
static float polygon_radius_get(int);
|
||||
static void polygon_spawn(std::vector<Entity>&, ImVec2, float, int, int);
|
||||
static void wave_spawn(std::vector<Entity>&, ImVec2, int);
|
||||
static int stamina_pickup_count_get(int);
|
||||
static void stamina_pickups_spawn(std::vector<Entity>&, ImVec2, int);
|
||||
static float wave_time_gain_ticks_get();
|
||||
static bool collectible_hit_get(const Entity&, const Entity&);
|
||||
static float magnet_radius_get(float, bool isMenuFullscreen);
|
||||
static int collectibles_collect(std::vector<Entity>&, const Entity&);
|
||||
static void collectibles_magnet_update(std::vector<Entity>&, const Entity&, ImVec2, float, float);
|
||||
static bool wave_complete_get(const std::vector<Entity>&);
|
||||
static void collectibles_draw(const std::vector<Entity>&, ImDrawList*, ImVec2, float, ImU32);
|
||||
static void magnet_radius_draw(ImDrawList*, ImVec2, const Entity&, float);
|
||||
static int alpha_get(int, float);
|
||||
static void progress_bar_draw(ImDrawList*, ImVec2, ImVec2, float, ImU32, ImU32, const std::string& = {});
|
||||
static void score_draw(ImDrawList*, ImVec2, int, int);
|
||||
static void time_draw(ImDrawList*, ImVec2, ImVec2, float, float, float);
|
||||
void reset();
|
||||
void start(ImVec2);
|
||||
void wave_reset(ImVec2);
|
||||
void wave_reset_request();
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "interact.hpp"
|
||||
|
||||
#include "../info.hpp"
|
||||
|
||||
#include "../../../util/imgui/widget.hpp"
|
||||
#include "../../../util/measurement.hpp"
|
||||
|
||||
using namespace game::resource;
|
||||
using namespace game::resource::xml;
|
||||
@@ -10,48 +11,73 @@ using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
void Interact::update(Resources& resources, Text& text, entity::Character& character)
|
||||
void Interact::update(Resources& resources, Text& text, Entity& character, bool)
|
||||
{
|
||||
static constexpr auto ZERO_FLOAT = 0.0f;
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& strings = character.data.strings;
|
||||
auto size = ImGui::GetContentRegionAvail();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
|
||||
if (dialogue.random.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.random), character);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
if (dialogue.help.is_valid())
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(dialogue.help), character);
|
||||
|
||||
auto* random = dialogue.get(Schema::Element::RANDOM);
|
||||
auto* help = dialogue.get(Schema::Element::HELP);
|
||||
auto stage = glm::clamp(0, character.stage_get(), character.stage_max_get());
|
||||
auto& pool = stage > 0 ? character.data.stages.at(stage - 1).pool : character.data.pool;
|
||||
|
||||
if (pool.is_valid())
|
||||
if (WIDGET_FX(
|
||||
ImGui::Button(strings.get(Strings::InteractFeelingButton).c_str(), ImVec2(size.x, 0))))
|
||||
text.set(dialogue.get(pool), character);
|
||||
auto poolID = character.data.stage_pool_id_get(stage);
|
||||
auto isFeelingAvailable = poolID != -1;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
ImGui::SeparatorText(character.data.name.c_str());
|
||||
ImGui::SeparatorText(character.data.root()->name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto weight = character.weight_get(system);
|
||||
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
|
||||
info_content_draw(resources, character, ImVec2(ImGui::GetContentRegionAvail().x, info_height_get()));
|
||||
|
||||
ImGui::Text(strings.get(Strings::InteractWeightFormat).c_str(), weight, weightUnit,
|
||||
character.stage_get() + 1);
|
||||
ImGui::Text(strings.get(Strings::InteractCapacityFormat).c_str(), character.capacity,
|
||||
character.max_capacity());
|
||||
ImGui::Text(strings.get(Strings::InteractDigestionRateFormat).c_str(), character.digestion_rate_get());
|
||||
ImGui::Text(strings.get(Strings::InteractEatingSpeedFormat).c_str(), character.eatSpeed);
|
||||
ImGui::Separator();
|
||||
ImGui::Text(strings.get(Strings::InteractTotalCaloriesFormat).c_str(), character.totalCaloriesConsumed);
|
||||
ImGui::Text(strings.get(Strings::InteractTotalFoodItemsFormat).c_str(), character.totalFoodItemsEaten);
|
||||
auto buttonHeight = ZERO_FLOAT;
|
||||
if (random)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
buttonHeight += ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::PopFont();
|
||||
}
|
||||
if (help)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
buttonHeight += ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::PopFont();
|
||||
}
|
||||
if (isFeelingAvailable)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
buttonHeight += ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
auto fillerHeight = ImGui::GetContentRegionAvail().y - buttonHeight;
|
||||
if (fillerHeight > ZERO_FLOAT) ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x, fillerHeight));
|
||||
|
||||
if (random)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractChatButton).c_str(),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
|
||||
text.set(dialogue.dialogue_pool_entry_get(*random), character);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (help)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InteractHelpButton).c_str(),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
|
||||
text.set(dialogue.dialogue_entry_get(*help), character);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (poolID != -1)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
|
||||
if (WIDGET_FX(
|
||||
ImGui::Button(strings.get(Strings::InteractFeelingButton).c_str(),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, ZERO_FLOAT))))
|
||||
text.set(dialogue.dialogue_pool_entry_get(poolID), character);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace game::state::play::menu
|
||||
class Interact
|
||||
{
|
||||
public:
|
||||
void update(Resources&, Text&, entity::Character&);
|
||||
void update(Resources&, Text&, Entity&, bool isMenuFullscreen);
|
||||
};
|
||||
}
|
||||
|
||||
+190
-162
@@ -1,4 +1,5 @@
|
||||
#include "inventory.hpp"
|
||||
#include "../autofeed.hpp"
|
||||
#include "../style.hpp"
|
||||
|
||||
#include <cmath>
|
||||
@@ -15,7 +16,6 @@
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace glm;
|
||||
|
||||
@@ -23,14 +23,85 @@ namespace game::state::play::menu
|
||||
{
|
||||
using Strings = resource::xml::Strings;
|
||||
|
||||
void Inventory::tick()
|
||||
namespace
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.tick();
|
||||
int& quantity_get(std::map<int, int>& values, const resource::xml::Schema& schema, int itemID)
|
||||
{
|
||||
auto& quantity = values[itemID];
|
||||
quantity = glm::clamp(0, quantity, schema.quantityMax);
|
||||
return quantity;
|
||||
}
|
||||
|
||||
bool is_possible_to_upgrade_get(const resource::xml::Schema& schema, const resource::xml::Schema::ItemEntry& item)
|
||||
{
|
||||
return item.isUpgradeID && item.isUpgradeCount && schema.idToStringMap.contains(item.upgradeID);
|
||||
}
|
||||
}
|
||||
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, entity::Character& character)
|
||||
bool Inventory::can_upgrade(const resource::xml::Schema& schema, int itemID)
|
||||
{
|
||||
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
|
||||
|
||||
auto& item = schema.items[itemID];
|
||||
auto& quantity = quantity_get(values, schema, itemID);
|
||||
return is_possible_to_upgrade_get(schema, item) && quantity >= item.upgradeCount;
|
||||
}
|
||||
|
||||
bool Inventory::upgrade(resource::xml::Schema& schema, int itemID, bool isAll)
|
||||
{
|
||||
if (itemID < 0 || itemID >= (int)schema.items.size()) return false;
|
||||
|
||||
auto& item = schema.items[itemID];
|
||||
auto& quantity = quantity_get(values, schema, itemID);
|
||||
|
||||
if (!is_possible_to_upgrade_get(schema, item) || quantity < item.upgradeCount)
|
||||
{
|
||||
schema.root()->soundUpgradeFail.play();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAll)
|
||||
{
|
||||
while (quantity >= item.upgradeCount)
|
||||
{
|
||||
values[item.upgradeID]++;
|
||||
quantity -= item.upgradeCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
values[item.upgradeID]++;
|
||||
quantity -= item.upgradeCount;
|
||||
}
|
||||
|
||||
schema.root()->soundUpgrade.play();
|
||||
if (quantity < item.upgradeCount && selectedItemID == itemID) selectedItemID = item.upgradeID;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Inventory::upgrade_all_possible(resource::xml::Schema& schema)
|
||||
{
|
||||
auto isUpgraded = false;
|
||||
|
||||
for (auto isPassUpgraded = true; isPassUpgraded;)
|
||||
{
|
||||
isPassUpgraded = false;
|
||||
for (int itemID = 0; itemID < (int)schema.items.size(); itemID++)
|
||||
{
|
||||
if (!can_upgrade(schema, itemID)) continue;
|
||||
isPassUpgraded = upgrade(schema, itemID, true) || isPassUpgraded;
|
||||
isUpgraded = isPassUpgraded || isUpgraded;
|
||||
}
|
||||
}
|
||||
|
||||
return isUpgraded;
|
||||
}
|
||||
|
||||
void Inventory::update(Resources& resources, ItemManager& itemManager, Entity& character, Autofeed& autofeed)
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.update();
|
||||
|
||||
static constexpr auto INFO_CHILD_HEIGHT_MAX_MULTIPLIER = 0.5f;
|
||||
bool isSelectedItemPressed{};
|
||||
int pressedItemQuantity{-1};
|
||||
@@ -38,69 +109,59 @@ namespace game::state::play::menu
|
||||
auto& schema = character.data.itemSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
auto quantity_get = [&](int itemID) -> int&
|
||||
{
|
||||
auto& quantity = values[itemID];
|
||||
quantity = glm::clamp(0, quantity, schema.quantityMax);
|
||||
return quantity;
|
||||
};
|
||||
auto inventory_quantity_get = [&](int itemID) -> int& { return quantity_get(values, schema, itemID); };
|
||||
|
||||
auto is_possible_to_upgrade_get = [&](const resource::xml::Item::Entry& item)
|
||||
{
|
||||
return item.upgradeID.has_value() && item.upgradeCount.has_value() &&
|
||||
schema.idToStringMap.contains(*item.upgradeID);
|
||||
};
|
||||
auto is_possible_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item)
|
||||
{ return game::state::play::menu::is_possible_to_upgrade_get(schema, item); };
|
||||
|
||||
auto is_able_to_upgrade_get = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
{ return is_possible_to_upgrade_get(item) && quantity >= *item.upgradeCount; };
|
||||
auto is_able_to_upgrade_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
|
||||
{ return is_possible_to_upgrade_get(item) && quantity >= item.upgradeCount; };
|
||||
|
||||
auto item_header_draw = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
auto item_header_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), quantity);
|
||||
ImGui::PopFont();
|
||||
};
|
||||
|
||||
auto item_summary_draw = [&](const resource::xml::Item::Entry& item)
|
||||
auto item_summary_draw = [&](const resource::xml::Schema::ItemEntry& item)
|
||||
{
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto durability = item.durability.value_or(schema.durability);
|
||||
auto durability = item.isDurability ? item.durability : schema.durability;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::TextWrapped("-- %s (%s) --", category.name.c_str(), rarity.name.c_str());
|
||||
if (item.flavorID.has_value())
|
||||
if (item.isFlavor)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryFlavorFormat).c_str(),
|
||||
schema.flavors[*item.flavorID].name.c_str());
|
||||
if (item.calories.has_value())
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), *item.calories);
|
||||
schema.flavors[item.flavorID].name.c_str());
|
||||
if (item.isCalories) ImGui::TextWrapped(strings.get(Strings::InventoryCaloriesFormat).c_str(), item.calories);
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDurabilityFormat).c_str(), durability);
|
||||
if (item.capacityBonus.has_value())
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), *item.capacityBonus);
|
||||
if (item.digestionBonus.has_value())
|
||||
if (item.isCapacityBonus)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryCapacityBonusFormat).c_str(), item.capacityBonus);
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
if (*item.digestionBonus > 0)
|
||||
if (item.digestionBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
|
||||
*item.digestionBonus * 60.0f);
|
||||
else if (*item.digestionBonus < 0)
|
||||
item.digestionBonus * 60.0f);
|
||||
else if (item.digestionBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
|
||||
*item.digestionBonus * 60.0f);
|
||||
item.digestionBonus * 60.0f);
|
||||
}
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
if (item.isEatSpeedBonus)
|
||||
{
|
||||
if (*item.eatSpeedBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), *item.eatSpeedBonus);
|
||||
else if (*item.eatSpeedBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), *item.eatSpeedBonus);
|
||||
if (item.eatSpeedBonus > 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedBonusFormat).c_str(), item.eatSpeedBonus);
|
||||
else if (item.eatSpeedBonus < 0)
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryEatSpeedPenaltyFormat).c_str(), item.eatSpeedBonus);
|
||||
}
|
||||
if (is_possible_to_upgrade_get(item))
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), *item.upgradeCount,
|
||||
schema.idToStringMap.at(*item.upgradeID).c_str());
|
||||
ImGui::TextWrapped(strings.get(Strings::InventoryUpgradePreviewFormat).c_str(), item.upgradeCount,
|
||||
schema.idToStringMap.at(item.upgradeID).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
};
|
||||
|
||||
auto info_section_separator_height_get = [&]()
|
||||
{ return ImGui::GetStyle().ItemSpacing.y + 1.0f; };
|
||||
auto info_section_separator_height_get = [&]() { return ImGui::GetStyle().ItemSpacing.y + 1.0f; };
|
||||
|
||||
auto info_section_separator_draw = [&]()
|
||||
{
|
||||
@@ -113,7 +174,7 @@ namespace game::state::play::menu
|
||||
ImGui::Dummy(ImVec2(0.0f, separatorHeight));
|
||||
};
|
||||
|
||||
auto item_tooltip_draw = [&](const resource::xml::Item::Entry& item, int quantity)
|
||||
auto item_tooltip_draw = [&](const resource::xml::Schema::ItemEntry& item, int quantity)
|
||||
{
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
|
||||
item_header_draw(item, quantity);
|
||||
@@ -139,7 +200,7 @@ namespace game::state::play::menu
|
||||
return ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, safeWrapWidth).y;
|
||||
};
|
||||
|
||||
auto item_header_height_get = [&](const resource::xml::Item::Entry& item, int quantity, float width)
|
||||
auto item_header_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
|
||||
{
|
||||
float height{};
|
||||
|
||||
@@ -150,11 +211,11 @@ namespace game::state::play::menu
|
||||
return height;
|
||||
};
|
||||
|
||||
auto item_summary_height_get = [&](const resource::xml::Item::Entry& item, float width)
|
||||
auto item_summary_height_get = [&](const resource::xml::Schema::ItemEntry& item, float width)
|
||||
{
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto durability = item.durability.value_or(schema.durability);
|
||||
auto durability = item.isDurability ? item.durability : schema.durability;
|
||||
auto itemSpacing = ImGui::GetStyle().ItemSpacing.y;
|
||||
float height{};
|
||||
int lineCount{};
|
||||
@@ -167,46 +228,48 @@ namespace game::state::play::menu
|
||||
};
|
||||
|
||||
add_line_height(std::format("-- {} ({}) --", category.name, rarity.name));
|
||||
if (item.flavorID.has_value())
|
||||
if (item.isFlavor)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryFlavorFormat),
|
||||
std::make_format_args(schema.flavors[*item.flavorID].name)));
|
||||
if (item.calories.has_value())
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryCaloriesFormat), std::make_format_args(*item.calories)));
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryDurabilityFormat), std::make_format_args(durability)));
|
||||
if (item.capacityBonus.has_value())
|
||||
std::make_format_args(schema.flavors[item.flavorID].name)));
|
||||
if (item.isCalories)
|
||||
add_line_height(
|
||||
std::vformat(strings.get(Strings::InventoryCapacityBonusFormat), std::make_format_args(*item.capacityBonus)));
|
||||
if (item.digestionBonus.has_value())
|
||||
std::vformat(strings.get(Strings::InventoryCaloriesFormat), std::make_format_args(item.calories)));
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryDurabilityFormat), std::make_format_args(durability)));
|
||||
if (item.isCapacityBonus)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryCapacityBonusFormat),
|
||||
std::make_format_args(item.capacityBonus)));
|
||||
if (item.isDigestionBonus)
|
||||
{
|
||||
if (*item.digestionBonus > 0)
|
||||
if (item.digestionBonus > 0)
|
||||
{
|
||||
auto digestionRateBonus = *item.digestionBonus * 60.0f;
|
||||
add_line_height(
|
||||
std::vformat(strings.get(Strings::InventoryDigestionRateBonusFormat), std::make_format_args(digestionRateBonus)));
|
||||
auto digestionRateBonus = item.digestionBonus * 60.0f;
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRateBonusFormat),
|
||||
std::make_format_args(digestionRateBonus)));
|
||||
}
|
||||
else if (*item.digestionBonus < 0)
|
||||
else if (item.digestionBonus < 0)
|
||||
{
|
||||
auto digestionRatePenalty = *item.digestionBonus * 60.0f;
|
||||
auto digestionRatePenalty = item.digestionBonus * 60.0f;
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryDigestionRatePenaltyFormat),
|
||||
std::make_format_args(digestionRatePenalty)));
|
||||
}
|
||||
}
|
||||
if (item.eatSpeedBonus.has_value())
|
||||
if (item.isEatSpeedBonus)
|
||||
{
|
||||
if (*item.eatSpeedBonus > 0)
|
||||
add_line_height(
|
||||
std::vformat(strings.get(Strings::InventoryEatSpeedBonusFormat), std::make_format_args(*item.eatSpeedBonus)));
|
||||
else if (*item.eatSpeedBonus < 0)
|
||||
if (item.eatSpeedBonus > 0)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedBonusFormat),
|
||||
std::make_format_args(item.eatSpeedBonus)));
|
||||
else if (item.eatSpeedBonus < 0)
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryEatSpeedPenaltyFormat),
|
||||
std::make_format_args(*item.eatSpeedBonus)));
|
||||
std::make_format_args(item.eatSpeedBonus)));
|
||||
}
|
||||
if (is_possible_to_upgrade_get(item))
|
||||
add_line_height(std::vformat(strings.get(Strings::InventoryUpgradePreviewFormat),
|
||||
std::make_format_args(*item.upgradeCount, schema.idToStringMap.at(*item.upgradeID))));
|
||||
add_line_height(
|
||||
std::vformat(strings.get(Strings::InventoryUpgradePreviewFormat),
|
||||
std::make_format_args(item.upgradeCount, schema.idToStringMap.at(item.upgradeID))));
|
||||
return height;
|
||||
};
|
||||
|
||||
auto item_details_height_get = [&](const resource::xml::Item::Entry& item, int quantity, float width)
|
||||
auto item_details_height_get = [&](const resource::xml::Schema::ItemEntry& item, int quantity, float width)
|
||||
{
|
||||
auto separatorHeight = info_section_separator_height_get();
|
||||
auto detailBodyHeight =
|
||||
@@ -243,8 +306,8 @@ namespace game::state::play::menu
|
||||
else
|
||||
{
|
||||
auto& item = schema.items[selectedItemID];
|
||||
measuredBodyHeight =
|
||||
isSelectedItemKnown ? item_details_height_get(item, selectedQuantity, infoWidth) : item_unknown_height_get(infoWidth);
|
||||
measuredBodyHeight = isSelectedItemKnown ? item_details_height_get(item, selectedQuantity, infoWidth)
|
||||
: item_unknown_height_get(infoWidth);
|
||||
}
|
||||
|
||||
auto buttonChildHeight = 0.0f;
|
||||
@@ -266,17 +329,17 @@ namespace game::state::play::menu
|
||||
{
|
||||
auto& item = schema.items[itemID];
|
||||
auto& category = schema.categories[item.categoryID];
|
||||
auto& quantity = quantity_get(itemID);
|
||||
auto& quantity = inventory_quantity_get(itemID);
|
||||
|
||||
if (quantity <= 0) return;
|
||||
|
||||
if (category.isEdible)
|
||||
{
|
||||
if (itemManager.items.size() + 1 >= ItemManager::LIMIT)
|
||||
character.data.itemSchema.sounds.dispose.play();
|
||||
character.data.itemSchema.root()->soundDispose.play();
|
||||
else
|
||||
{
|
||||
character.data.itemSchema.sounds.summon.play();
|
||||
character.data.itemSchema.root()->soundSummon.play();
|
||||
itemManager.queuedItemIDs.emplace_back(itemID);
|
||||
quantity--;
|
||||
if (quantity <= 0) selectedItemID = -1;
|
||||
@@ -284,78 +347,17 @@ namespace game::state::play::menu
|
||||
}
|
||||
else if (item.isToggleSpritesheet)
|
||||
{
|
||||
character.spritesheet_set(character.spritesheetType == Character::NORMAL ? Character::ALTERNATE
|
||||
: Character::NORMAL);
|
||||
character.data.alternateSpritesheet.sound.play();
|
||||
character.spritesheet_set(character.spritesheetType == Entity::NORMAL ? Entity::ALTERNATE : Entity::NORMAL);
|
||||
if (auto* alternate = character.data.alternate_spritesheet()) alternate->soundEntry.sound.play();
|
||||
quantity--;
|
||||
}
|
||||
};
|
||||
|
||||
auto item_upgrade = [&](int itemID, bool isAll)
|
||||
auto item_actor_get = [&](int itemID) -> Entity&
|
||||
{
|
||||
auto& item = schema.items[itemID];
|
||||
auto& quantity = quantity_get(itemID);
|
||||
if (!actors.contains(itemID)) actors[itemID] = Entity(schema.anm2s[itemID], {}, Entity::SET);
|
||||
|
||||
if (!is_possible_to_upgrade_get(item))
|
||||
{
|
||||
schema.sounds.upgradeFail.play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_able_to_upgrade_get(item, quantity))
|
||||
{
|
||||
schema.sounds.upgradeFail.play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAll)
|
||||
{
|
||||
while (quantity >= *item.upgradeCount)
|
||||
{
|
||||
values.at(*item.upgradeID)++;
|
||||
quantity -= *item.upgradeCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
values.at(*item.upgradeID)++;
|
||||
quantity -= *item.upgradeCount;
|
||||
}
|
||||
|
||||
schema.sounds.upgrade.play();
|
||||
|
||||
if (quantity < *item.upgradeCount && selectedItemID == itemID) selectedItemID = *item.upgradeID;
|
||||
};
|
||||
|
||||
auto item_canvas_get = [&](int itemID, ImVec2 size)
|
||||
{
|
||||
if (!actors.contains(itemID))
|
||||
{
|
||||
actors[itemID] = Actor(schema.anm2s[itemID], {}, Actor::SET);
|
||||
rects[itemID] = actors[itemID].rect();
|
||||
}
|
||||
|
||||
auto& rect = rects[itemID];
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (size.x <= 0.0f || size.y <= 0.0f || rectSize.x <= 0.0f || rectSize.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
if (!canvases.contains(itemID)) canvases.emplace(itemID, Canvas(canvasSize, Canvas::FLIP));
|
||||
|
||||
auto& canvas = canvases[itemID];
|
||||
canvas.zoom = math::to_percent(previewScale);
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
actors[itemID].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
return std::tuple<Canvas&, glm::vec4&>(canvas, rect);
|
||||
return actors[itemID];
|
||||
};
|
||||
|
||||
if (!itemManager.returnItemIDs.empty())
|
||||
@@ -372,11 +374,18 @@ namespace game::state::play::menu
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
|
||||
auto isInfoVisible = isItemSelected || inventoryCount == 0;
|
||||
auto infoChildHeight = info_child_height_get(available, isItemSelected, isItemSelected ? quantity_get(selectedItemID) : 0);
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
auto toggleButtonHeight = ImGui::GetFrameHeight();
|
||||
ImGui::PopFont();
|
||||
auto toggleChildHeight = toggleButtonHeight + ImGui::GetStyle().WindowPadding.y * 2.0f;
|
||||
auto toggleSpacing = ImGui::GetStyle().ItemSpacing.y;
|
||||
auto infoChildHeight =
|
||||
info_child_height_get(available, isItemSelected, isItemSelected ? inventory_quantity_get(selectedItemID) : 0);
|
||||
|
||||
auto inventoryChildHeight =
|
||||
isInfoVisible ? std::max(0.0f, available.y - infoChildHeight - ImGui::GetStyle().ItemSpacing.y) : available.y;
|
||||
auto inventoryChildHeight = std::max(0.0f, available.y - infoChildHeight - toggleChildHeight -
|
||||
(isInfoVisible ? toggleSpacing * 2.0f : toggleSpacing));
|
||||
auto childSize = ImVec2(available.x, inventoryChildHeight);
|
||||
auto toggleChildSize = ImVec2(available.x, toggleChildHeight);
|
||||
auto infoChildSize = ImVec2(available.x, infoChildHeight);
|
||||
|
||||
if (ImGui::BeginChild("##Inventory List Child", childSize))
|
||||
@@ -390,9 +399,9 @@ namespace game::state::play::menu
|
||||
for (int i = 0; i < (int)schema.items.size(); i++)
|
||||
{
|
||||
auto& item = schema.items[i];
|
||||
auto& quantity = quantity_get(i);
|
||||
auto& quantity = inventory_quantity_get(i);
|
||||
auto& rarity = schema.rarities[item.rarityID];
|
||||
auto hasItemColor = item.color.has_value();
|
||||
auto isItemColor = item.isColor;
|
||||
|
||||
if (rarity.isHidden && quantity <= 0) continue;
|
||||
|
||||
@@ -400,9 +409,9 @@ namespace game::state::play::menu
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
auto [canvas, rect] = item_canvas_get(i, size);
|
||||
auto& actor = item_actor_get(i);
|
||||
auto isSelected = selectedItemID == i;
|
||||
if (hasItemColor) imgui::style::color_set(*item.color);
|
||||
if (isItemColor) imgui::style::color_set(item.color);
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
@@ -412,9 +421,8 @@ namespace game::state::play::menu
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selectedColor);
|
||||
}
|
||||
|
||||
auto isPressed =
|
||||
WIDGET_FX(ImGui::ImageButton("##Image Button", canvas.texture, size, ImVec2(), ImVec2(1, 1), ImVec4(),
|
||||
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1)));
|
||||
auto isPressed = itemButtons[i].button(resources, actor, "##Image Button", size,
|
||||
quantity <= 0 ? ImVec4(0, 0, 0, 0.5f) : ImVec4(1, 1, 1, 1));
|
||||
if (isSelected) ImGui::PopStyleColor(3);
|
||||
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
|
||||
if (isPressed)
|
||||
@@ -441,7 +449,7 @@ namespace game::state::play::menu
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
|
||||
text.c_str());
|
||||
ImGui::PopFont();
|
||||
if (hasItemColor) style::color_set(resources, character);
|
||||
if (isItemColor) style::color_set(resources, character);
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
@@ -460,20 +468,39 @@ namespace game::state::play::menu
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (ImGui::BeginChild("##Autofeed Toggle Child", toggleChildSize, ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
auto isAutofeedAvailable = autofeed.available_get(character, *this, itemManager);
|
||||
auto label = autofeed.enabled_get() ? strings.get(Strings::InventoryDisableAutofeedButton)
|
||||
: strings.get(Strings::InventoryEnableAutofeedButton);
|
||||
ImGui::BeginDisabled(!isAutofeedAvailable);
|
||||
if (WIDGET_FX(ImGui::Button(label.c_str(), {ImGui::GetContentRegionAvail().x, 0}))) autofeed.toggle();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopFont();
|
||||
ImGui::SetItemTooltip("%s", strings
|
||||
.get(autofeed.enabled_get() ? Strings::InventoryDisableAutofeedTooltip
|
||||
: Strings::InventoryEnableAutofeedTooltip)
|
||||
.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
|
||||
isInfoVisible = isItemSelected || inventoryCount == 0;
|
||||
auto selectedQuantity =
|
||||
isItemSelected ? (isSelectedItemPressed && pressedItemQuantity >= 0 ? pressedItemQuantity : quantity_get(selectedItemID))
|
||||
isItemSelected ? (isSelectedItemPressed && pressedItemQuantity >= 0 ? pressedItemQuantity
|
||||
: inventory_quantity_get(selectedItemID))
|
||||
: 0;
|
||||
infoChildHeight = info_child_height_get(available, isItemSelected, selectedQuantity);
|
||||
infoChildSize = ImVec2(available.x, infoChildHeight);
|
||||
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
|
||||
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].color.has_value();
|
||||
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].isColor;
|
||||
|
||||
if (isInfoVisible && ImGui::BeginChild("##Info Child", infoChildSize, ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
if (selectedItemHasColor) imgui::style::color_set(*schema.items[selectedItemID].color);
|
||||
if (selectedItemHasColor) imgui::style::color_set(schema.items[selectedItemID].color);
|
||||
info_section_separator_draw();
|
||||
auto isButtonChildVisible = selectedQuantity > 0;
|
||||
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
|
||||
@@ -497,10 +524,10 @@ namespace game::state::play::menu
|
||||
auto descriptionHeight = wrapped_text_height_get(selectedItem.description, contentWidth);
|
||||
auto sharedDetailHeight = std::max(statsHeight, descriptionHeight);
|
||||
auto headerHeight = item_header_height_get(selectedItem, selectedQuantity, contentWidth);
|
||||
auto desiredInfoContentHeight =
|
||||
headerHeight + separatorHeight + sharedDetailHeight * 2.0f + separatorHeight;
|
||||
auto desiredInfoContentHeight = headerHeight + separatorHeight + sharedDetailHeight * 2.0f + separatorHeight;
|
||||
auto availableInfoContentHeight =
|
||||
std::max(0.0f, ImGui::GetContentRegionAvail().y - (isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
|
||||
std::max(0.0f, ImGui::GetContentRegionAvail().y -
|
||||
(isButtonChildVisible ? separatorHeight + buttonChildHeight : 0.0f));
|
||||
auto infoContentHeight = std::min(desiredInfoContentHeight, availableInfoContentHeight);
|
||||
|
||||
item_header_draw(selectedItem, selectedQuantity);
|
||||
@@ -508,7 +535,8 @@ namespace game::state::play::menu
|
||||
|
||||
auto detailChildHeight = sharedDetailHeight;
|
||||
if (desiredInfoContentHeight > availableInfoContentHeight)
|
||||
detailChildHeight = std::max(0.0f, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) * 0.5f);
|
||||
detailChildHeight =
|
||||
std::max(0.0f, (infoContentHeight - headerHeight - separatorHeight - separatorHeight) * 0.5f);
|
||||
auto detailChildSize = ImVec2(ImGui::GetContentRegionAvail().x, detailChildHeight);
|
||||
|
||||
if (ImGui::BeginChild("##Info Stats Child", detailChildSize))
|
||||
@@ -540,8 +568,8 @@ namespace game::state::play::menu
|
||||
|
||||
auto upgrade_item_name_get = [&]() -> std::string
|
||||
{
|
||||
if (!selectedItem.upgradeID.has_value()) return {};
|
||||
return schema.items.at(*selectedItem.upgradeID).name;
|
||||
if (!selectedItem.isUpgradeID) return {};
|
||||
return schema.items.at(selectedItem.upgradeID).name;
|
||||
};
|
||||
|
||||
auto upgrade_tooltip_get = [&](bool isAll)
|
||||
@@ -549,7 +577,7 @@ namespace game::state::play::menu
|
||||
if (!is_possible_to_upgrade_get(selectedItem)) return strings.get(Strings::InventoryUpgradeNoPath);
|
||||
|
||||
auto upgradeItemName = upgrade_item_name_get();
|
||||
auto upgradeCount = *selectedItem.upgradeCount;
|
||||
auto upgradeCount = selectedItem.upgradeCount;
|
||||
|
||||
if (!canUpgradeSelectedItem)
|
||||
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
|
||||
@@ -574,7 +602,7 @@ namespace game::state::play::menu
|
||||
|
||||
ImGui::BeginDisabled(!canUpgradeSelectedItem);
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
|
||||
item_upgrade(selectedItemID, false);
|
||||
upgrade(schema, selectedItemID, false);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
@@ -583,7 +611,7 @@ namespace game::state::play::menu
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(), rowTwoButtonSize)))
|
||||
item_upgrade(selectedItemID, true);
|
||||
upgrade(schema, selectedItemID, true);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../entity/character.hpp"
|
||||
#include "../../../entity.hpp"
|
||||
|
||||
#include "../../../resource/schema.hpp"
|
||||
#include "../../../resources.hpp"
|
||||
#include "../../../util/imgui/entity_button.hpp"
|
||||
|
||||
#include "../item_manager.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class Autofeed;
|
||||
}
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
class Inventory
|
||||
@@ -16,13 +23,14 @@ namespace game::state::play::menu
|
||||
static constexpr auto SIZE = 96.0f;
|
||||
|
||||
std::map<int, int> values{};
|
||||
std::unordered_map<int, entity::Actor> actors{};
|
||||
std::unordered_map<int, glm::vec4> rects{};
|
||||
std::unordered_map<int, Canvas> canvases{};
|
||||
std::unordered_map<int, Entity> actors{};
|
||||
std::unordered_map<int, util::imgui::widget::EntityButton> itemButtons{};
|
||||
int selectedItemID{-1};
|
||||
|
||||
void tick();
|
||||
void update(Resources&, ItemManager&, entity::Character&);
|
||||
bool can_upgrade(const resource::xml::Schema&, int itemID);
|
||||
bool upgrade(resource::xml::Schema&, int itemID, bool isAll);
|
||||
bool upgrade_all_possible(resource::xml::Schema&);
|
||||
void update(Resources&, ItemManager&, Entity&, Autofeed&);
|
||||
int count();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ using namespace glm;
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
void ItemEffectManager::tick()
|
||||
void ItemEffectManager::update()
|
||||
{
|
||||
for (auto& [i, actor] : actors)
|
||||
actor.tick();
|
||||
actor.update();
|
||||
}
|
||||
|
||||
void ItemEffectManager::spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode)
|
||||
void ItemEffectManager::spawn(int itemID, const resource::xml::Schema& itemSchema, const ImVec4& bounds, Mode mode)
|
||||
{
|
||||
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MIN = -250.0f;
|
||||
static constexpr auto ITEM_SHOOT_UP_HORIZONTAL_SPEED_MAX = 250.0f;
|
||||
@@ -24,13 +24,12 @@ namespace game::state::play::menu
|
||||
static constexpr auto ITEM_ROTATION_VELOCITY_MAX = 45.0f;
|
||||
|
||||
if (!actors.contains(itemID))
|
||||
{
|
||||
actors[itemID] = entity::Actor(itemSchema.anm2s[itemID], {}, entity::Actor::SET);
|
||||
rects[itemID] = actors[itemID].rect();
|
||||
}
|
||||
actors[itemID] = Entity(itemSchema.anm2s[itemID], {}, Entity::SET);
|
||||
|
||||
auto size = ImVec2(bounds.z, bounds.w);
|
||||
auto rect = rects[itemID];
|
||||
auto& canvas = canvases[itemID];
|
||||
if (!std::isfinite(canvas.rect.x)) canvas.rect = actors[itemID].rect();
|
||||
auto rect = canvas.rect;
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
@@ -66,7 +65,7 @@ namespace game::state::play::menu
|
||||
entries.emplace_back(std::move(entry));
|
||||
}
|
||||
|
||||
void ItemEffectManager::render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds,
|
||||
void ItemEffectManager::render(Resources& resources, const resource::xml::Schema& itemSchema, const ImVec4& bounds,
|
||||
float deltaTime)
|
||||
{
|
||||
static constexpr auto ITEM_FALL_GRAVITY = 2400.0f;
|
||||
@@ -87,35 +86,15 @@ namespace game::state::play::menu
|
||||
continue;
|
||||
}
|
||||
|
||||
auto rect = rects[item.id];
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (rectSize.x <= 0.0f || rectSize.y <= 0.0f || size.x <= 0.0f || size.y <= 0.0f ||
|
||||
!std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(size.x / rectSize.x, size.y / rectSize.y);
|
||||
previewScale = std::min(1.0f, previewScale);
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
|
||||
if (!canvases.contains(item.id)) canvases.emplace(item.id, Canvas(canvasSize, Canvas::FLIP));
|
||||
auto& canvas = canvases[item.id];
|
||||
canvas.zoom = math::to_percent(previewScale);
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
|
||||
actors[item.id].overrides.emplace_back(-1, resource::xml::Anm2::ROOT, entity::Actor::Override::SET,
|
||||
resource::xml::Anm2::FrameOptional{.rotation = item.rotation});
|
||||
actors[item.id].render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
actors[item.id].overrides.pop_back();
|
||||
canvas.unbind();
|
||||
auto& textureCanvas = canvas.render_rotated(resources, actors[item.id], size, item.rotation, 1.0f);
|
||||
auto previewSize = canvas.imageSize;
|
||||
|
||||
auto min = ImVec2(position.x + item.position.x, position.y + item.position.y);
|
||||
auto max = ImVec2(item.position.x + previewSize.x, item.position.y + previewSize.y);
|
||||
max.x += position.x;
|
||||
max.y += position.y;
|
||||
drawList->AddImage(canvas.texture, min, max);
|
||||
drawList->AddImage(textureCanvas.texture, min, max);
|
||||
|
||||
item.rotation += item.rotationVelocity * deltaTime;
|
||||
item.position.x += item.velocity.x * deltaTime;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../render/canvas.hpp"
|
||||
#include "../../../entity/actor.hpp"
|
||||
#include "../../../entity.hpp"
|
||||
#include "../../../resources.hpp"
|
||||
#include "../../../util/imgui/entity_canvas.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <unordered_map>
|
||||
@@ -30,12 +31,11 @@ namespace game::state::play::menu
|
||||
};
|
||||
|
||||
std::vector<Entry> entries{};
|
||||
std::unordered_map<int, entity::Actor> actors{};
|
||||
std::unordered_map<int, glm::vec4> rects{};
|
||||
std::unordered_map<int, Canvas> canvases{};
|
||||
std::unordered_map<int, Entity> actors{};
|
||||
std::unordered_map<int, util::imgui::widget::EntityCanvas> canvases{};
|
||||
|
||||
void tick();
|
||||
void spawn(int itemID, const resource::xml::Item& itemSchema, const ImVec4& bounds, Mode mode = FALL_DOWN);
|
||||
void render(Resources& resources, const resource::xml::Item& itemSchema, const ImVec4& bounds, float deltaTime);
|
||||
void update();
|
||||
void spawn(int itemID, const resource::xml::Schema& itemSchema, const ImVec4& bounds, Mode mode = FALL_DOWN);
|
||||
void render(Resources& resources, const resource::xml::Schema& itemSchema, const ImVec4& bounds, float deltaTime);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#include "toasts.hpp"
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr auto TOAST_MESSAGE_SPEED = 1.0f;
|
||||
}
|
||||
|
||||
void Toasts::spawn(const std::string& message, const ImVec2& position, int time)
|
||||
{
|
||||
toasts.emplace_back(message, position, time, time);
|
||||
}
|
||||
|
||||
void Toasts::update(ImDrawList* drawList)
|
||||
{
|
||||
if (!drawList) return;
|
||||
|
||||
for (int i = 0; i < (int)toasts.size(); i++)
|
||||
{
|
||||
auto& toast = toasts[i];
|
||||
toast.position.y -= TOAST_MESSAGE_SPEED;
|
||||
auto textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
textColor.w = (float)toast.time / toast.timeMax;
|
||||
drawList->AddText(toast.position, ImGui::GetColorU32(textColor), toast.message.c_str());
|
||||
|
||||
toast.time--;
|
||||
if (toast.time <= 0) toasts.erase(toasts.begin() + i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play::menu
|
||||
{
|
||||
class Toasts
|
||||
{
|
||||
public:
|
||||
struct Toast
|
||||
{
|
||||
std::string message{};
|
||||
ImVec2 position{};
|
||||
int time{};
|
||||
int timeMax{};
|
||||
};
|
||||
|
||||
std::vector<Toast> toasts{};
|
||||
|
||||
void spawn(const std::string& message, const ImVec2& position, int time);
|
||||
void update(ImDrawList*);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "particle_manager.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void ParticleManager::spawn(Character& character, const std::string& label, glm::vec2 position)
|
||||
{
|
||||
for (auto* particle : character.particleSchema.get_all(Schema::Element::PARTICLE))
|
||||
{
|
||||
if (!particle || particle->typeString != label) continue;
|
||||
if (!particle->anm2Entry.is_valid()) return;
|
||||
|
||||
auto& anm2 = particle->anm2Entry.anm2;
|
||||
auto animationIndex = anm2.animationMap.contains(particle->animation) ? anm2.animationMap.at(particle->animation)
|
||||
: anm2.defaultAnimationID;
|
||||
Entity entity{anm2, position, Entity::PLAY, 0.0f, animationIndex};
|
||||
entity.entityType = PARTICLE;
|
||||
entity.isRemoveOnAnimationEnd = true;
|
||||
|
||||
particles.push_back(std::move(entity));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warning(std::format("Particle not found: {}", label));
|
||||
}
|
||||
|
||||
void ParticleManager::update()
|
||||
{
|
||||
for (int i = 0; i < (int)particles.size();)
|
||||
{
|
||||
particles[i].update();
|
||||
if (particles[i].isToBeDeleted)
|
||||
{
|
||||
particles.erase(particles.begin() + i);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleManager::render(resource::Shader& textureShader, resource::Shader& rectShader, Canvas& canvas)
|
||||
{
|
||||
for (auto& particle : particles)
|
||||
particle.render(textureShader, rectShader, canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
class ParticleManager
|
||||
{
|
||||
public:
|
||||
std::vector<Entity> particles{};
|
||||
|
||||
void spawn(resource::xml::Character&, const std::string&, glm::vec2);
|
||||
void update();
|
||||
void render(resource::Shader&, resource::Shader&, Canvas&);
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity.hpp"
|
||||
#include "../../resources.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
|
||||
namespace game::state::play::style
|
||||
{
|
||||
inline void color_set(Resources& resources, const entity::Character& character)
|
||||
inline void color_set(Resources& resources, const Entity& character)
|
||||
{
|
||||
game::util::imgui::style::color_set(resources.settings.isUseCharacterColor ? character.data.color
|
||||
: resources.settings.color);
|
||||
auto* settings = resources.settings.root();
|
||||
game::util::imgui::style::color_set(settings->isUseCharacterColor ? character.data.root()->color : settings->color);
|
||||
}
|
||||
}
|
||||
|
||||
+279
-58
@@ -4,9 +4,12 @@
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/dialogue_text.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
@@ -14,58 +17,203 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
const char* utf8_advance_chars(const char* text, const char* end, int count)
|
||||
namespace
|
||||
{
|
||||
const char* it = text;
|
||||
while (it < end && count > 0)
|
||||
bool dialogue_effect_is(const resource::xml::Schema::Element& element)
|
||||
{
|
||||
unsigned int codepoint = 0;
|
||||
int step = ImTextCharFromUtf8(&codepoint, it, end);
|
||||
if (step <= 0) break;
|
||||
it += step;
|
||||
--count;
|
||||
return element.type == resource::xml::Schema::Element::EFFECT;
|
||||
}
|
||||
|
||||
constexpr auto OSCILLATION_AMPLITUDE_DEFAULT = 2.0f;
|
||||
constexpr auto OSCILLATION_FREQUENCY_DEFAULT = 1.0f;
|
||||
constexpr auto SCREEN_SHAKE_MAGNITUDE_DEFAULT = 0.03f;
|
||||
constexpr auto SCREEN_SHAKE_TIME_TICKS_DEFAULT = 15;
|
||||
constexpr auto SHAKE_AMPLITUDE_DEFAULT = 1.0f;
|
||||
constexpr auto SHAKE_FREQUENCY_DEFAULT = 30.0f;
|
||||
constexpr auto TEXT_SCALE_BIG = 1.25f;
|
||||
constexpr auto TEXT_SCALE_SMALL = 0.75f;
|
||||
|
||||
std::string_view dialogue_char_get(std::string_view text, int index)
|
||||
{
|
||||
auto* start = text.data();
|
||||
auto* end = start + text.size();
|
||||
auto* charStart = imgui::utf8_advance_chars(start, end, index);
|
||||
auto* charEnd = imgui::utf8_advance_chars(charStart, end, 1);
|
||||
return charEnd > charStart ? std::string_view(charStart, charEnd - charStart) : std::string_view{};
|
||||
}
|
||||
|
||||
struct DialogueDelayCharacter
|
||||
{
|
||||
int delay{};
|
||||
bool isDisableTalk{};
|
||||
};
|
||||
|
||||
DialogueDelayCharacter dialogue_delay_character_get(const resource::xml::Schema& dialogue, std::string_view text,
|
||||
int index, int fallback)
|
||||
{
|
||||
auto value = dialogue_char_get(text, index);
|
||||
if (value.empty()) return {.delay = fallback};
|
||||
|
||||
auto* root = dialogue.root();
|
||||
if (!root) return {.delay = fallback};
|
||||
auto* textPlayback = dialogue.child_get(*root, resource::xml::Schema::Element::TEXT_PLAYBACK);
|
||||
auto* delayCharacters =
|
||||
textPlayback ? dialogue.child_get(*textPlayback, resource::xml::Schema::Element::DELAY_CHARACTERS) : nullptr;
|
||||
if (!delayCharacters) return {.delay = fallback};
|
||||
|
||||
for (auto* delayCharacter : dialogue.children_get(*delayCharacters, resource::xml::Schema::Element::DELAY_CHARACTER))
|
||||
{
|
||||
if (delayCharacter->character == value)
|
||||
return {.delay = fallback + std::max(0, delayCharacter->delayTicks),
|
||||
.isDisableTalk = delayCharacter->isDisableTalk};
|
||||
}
|
||||
return {.delay = fallback};
|
||||
}
|
||||
|
||||
void dialogue_effect_one_shot_apply(const resource::xml::Schema::Element& effect, Canvas& canvas)
|
||||
{
|
||||
if (effect.typeString == "Screenshake")
|
||||
{
|
||||
canvas.shake(SCREEN_SHAKE_MAGNITUDE_DEFAULT, SCREEN_SHAKE_TIME_TICKS_DEFAULT);
|
||||
}
|
||||
else if (effect.typeString == "Sound" && effect.soundEntry.is_valid())
|
||||
{
|
||||
effect.soundEntry.sound.play();
|
||||
}
|
||||
}
|
||||
|
||||
bool dialogue_entry_terminal_get(const resource::xml::Schema& dialogue,
|
||||
const resource::xml::Schema::Element& entry)
|
||||
{
|
||||
if (!entry.next.empty()) return false;
|
||||
for (auto child : entry.children)
|
||||
{
|
||||
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
|
||||
if (dialogue.elements[child].type == resource::xml::Schema::Element::CHOICE) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
void Text::set(resource::xml::Dialogue::Entry* dialogueEntry, entity::Character& character, bool isInterruptible)
|
||||
void Text::set(resource::xml::Schema::Element* dialogueEntry, Entity& character, bool isInterruptible)
|
||||
{
|
||||
if (!dialogueEntry) return;
|
||||
this->entry = dialogueEntry;
|
||||
|
||||
isFinished = false;
|
||||
isTerminal = dialogue_entry_terminal_get(character.data.dialogue, *dialogueEntry);
|
||||
index = 0;
|
||||
updateDelay = 0;
|
||||
baseDelay = 0;
|
||||
currentDelay = 0;
|
||||
isStartEffectsApplied = false;
|
||||
isBlipQueued = false;
|
||||
time = 0.0f;
|
||||
isEnabled = true;
|
||||
if (!dialogueEntry->animation.empty())
|
||||
character.queue_play({.animation = dialogueEntry->animation, .isInterruptible = isInterruptible});
|
||||
character.play({.animation = dialogueEntry->animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.interrupt = isInterruptible ? Entity::Interrupt::IF_ALLOWED
|
||||
: Entity::Interrupt::NEVER});
|
||||
auto* dialogueRoot = character.data.dialogue.root();
|
||||
baseDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
|
||||
currentDelay = baseDelay;
|
||||
for (auto child : dialogueEntry->children)
|
||||
{
|
||||
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
|
||||
auto& effect = character.data.dialogue.elements[child];
|
||||
if (!dialogue_effect_is(effect) || effect.start != 0) continue;
|
||||
if (effect.typeString == "Delay")
|
||||
{
|
||||
baseDelay = std::max(1, effect.delayTicks);
|
||||
currentDelay = baseDelay;
|
||||
}
|
||||
else if (effect.typeString == "Animation" && !effect.animation.empty())
|
||||
{
|
||||
character.play({.animation = effect.animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.mode = Entity::PLAY_FORCE});
|
||||
}
|
||||
}
|
||||
if (dialogueEntry->text.empty())
|
||||
isEnabled = false;
|
||||
else
|
||||
character.isTalking = true;
|
||||
}
|
||||
|
||||
void Text::tick(entity::Character& character)
|
||||
{
|
||||
if (!entry || isFinished) return;
|
||||
|
||||
index++;
|
||||
auto blipPeriod = character.data.textBlipPeriodBase;
|
||||
if (blipPeriod > 0 && index % blipPeriod == 0) character.data.sounds.blip.play();
|
||||
|
||||
if (index >= ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size()))
|
||||
{
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Text::update(entity::Character& character)
|
||||
void Text::update(Entity& character, Canvas& canvas)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr auto HEIGHT_MULTIPLIER = 6.0f;
|
||||
|
||||
if (!entry) return;
|
||||
if (!isFinished)
|
||||
{
|
||||
if (!isStartEffectsApplied)
|
||||
{
|
||||
isStartEffectsApplied = true;
|
||||
for (auto child : entry->children)
|
||||
{
|
||||
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
|
||||
auto& effect = character.data.dialogue.elements[child];
|
||||
if (!dialogue_effect_is(effect) || effect.start != 0) continue;
|
||||
dialogue_effect_one_shot_apply(effect, canvas);
|
||||
}
|
||||
}
|
||||
auto* dialogueRoot = character.data.dialogue.root();
|
||||
auto rootDelay = dialogueRoot && dialogueRoot->delayTicks > 0 ? dialogueRoot->delayTicks : 2;
|
||||
if (baseDelay <= 0) baseDelay = rootDelay;
|
||||
if (currentDelay <= 0) currentDelay = baseDelay;
|
||||
if (++updateDelay >= currentDelay)
|
||||
{
|
||||
updateDelay = 0;
|
||||
index++;
|
||||
currentDelay = baseDelay;
|
||||
for (auto child : entry->children)
|
||||
{
|
||||
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
|
||||
auto& effect = character.data.dialogue.elements[child];
|
||||
if (!dialogue_effect_is(effect) || effect.start != index) continue;
|
||||
if (effect.typeString == "Delay")
|
||||
{
|
||||
baseDelay = std::max(1, effect.delayTicks);
|
||||
currentDelay = baseDelay;
|
||||
}
|
||||
else if (effect.typeString == "Animation" && !effect.animation.empty())
|
||||
{
|
||||
character.play({.animation = effect.animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.mode = Entity::PLAY_FORCE});
|
||||
}
|
||||
else
|
||||
{
|
||||
dialogue_effect_one_shot_apply(effect, canvas);
|
||||
}
|
||||
}
|
||||
auto delayCharacter = dialogue_delay_character_get(character.data.dialogue, entry->text, index - 1, currentDelay);
|
||||
currentDelay = delayCharacter.delay;
|
||||
character.isTalking = !delayCharacter.isDisableTalk;
|
||||
auto blipDelay = dialogueRoot ? dialogueRoot->blipDelayTicks : 0;
|
||||
auto isBlipStep = blipDelay > 0 && index % blipDelay == 0;
|
||||
if (delayCharacter.isDisableTalk)
|
||||
{
|
||||
if (isBlipStep) isBlipQueued = true;
|
||||
}
|
||||
else if (isBlipStep || isBlipQueued)
|
||||
{
|
||||
if (dialogueRoot) dialogueRoot->soundBlip.play();
|
||||
isBlipQueued = false;
|
||||
}
|
||||
|
||||
if (index >= imgui::utf8_char_count(entry->text))
|
||||
{
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
isBlipQueued = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto& dialogue = character.data.dialogue;
|
||||
auto& menuSchema = character.data.menuSchema;
|
||||
|
||||
@@ -88,18 +236,16 @@ namespace game::state::play
|
||||
auto isHovered = ImGui::IsWindowHovered();
|
||||
auto isMouse = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isSpace = ImGui::IsKeyReleased(ImGuiKey_Space);
|
||||
auto isAdvance = (isHovered && (isMouse || isSpace));
|
||||
auto isAdvance = (isHovered && isMouse) || isSpace;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 0));
|
||||
|
||||
if (ImGui::BeginTabBar("##Name"))
|
||||
{
|
||||
if (ImGui::BeginTabItem(character.data.name.c_str())) ImGui::EndTabItem();
|
||||
if (ImGui::BeginTabItem(character.data.root()->name.c_str())) ImGui::EndTabItem();
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
|
||||
auto font = ImGui::GetFont();
|
||||
auto fontSize = resource::Font::NORMAL;
|
||||
|
||||
@@ -108,7 +254,7 @@ namespace game::state::play
|
||||
auto text = [&]()
|
||||
{
|
||||
auto text = entry ? std::string_view(entry->text) : "null";
|
||||
auto length = std::clamp(index, 0, ImTextCountCharsFromUtf8(text.data(), text.data() + text.size()));
|
||||
auto length = std::clamp(index, 0, imgui::utf8_char_count(text));
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
@@ -116,13 +262,42 @@ namespace game::state::play
|
||||
return;
|
||||
}
|
||||
|
||||
const char* textStart = text.data();
|
||||
const char* textEnd = textStart + text.size();
|
||||
const char* textLimit = utf8_advance_chars(textStart, textEnd, length);
|
||||
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + available.x);
|
||||
ImGui::TextUnformatted(textStart, textLimit);
|
||||
ImGui::PopTextWrapPos();
|
||||
std::vector<imgui::DialogueEffectSpan> effects{};
|
||||
effects.reserve(entry->children.size());
|
||||
for (auto child : entry->children)
|
||||
{
|
||||
if (child < 0 || child >= (int)dialogue.elements.size()) continue;
|
||||
auto& effect = dialogue.elements[child];
|
||||
if (!dialogue_effect_is(effect)) continue;
|
||||
if (effect.typeString == "Color")
|
||||
effects.push_back({.type = imgui::DialogueEffectSpan::COLOR,
|
||||
.start = effect.start,
|
||||
.end = effect.end,
|
||||
.color = effect.color});
|
||||
else if (effect.typeString == "Big")
|
||||
effects.push_back({.type = imgui::DialogueEffectSpan::TEXT_SCALE,
|
||||
.start = effect.start,
|
||||
.end = effect.end,
|
||||
.textScale = TEXT_SCALE_BIG});
|
||||
else if (effect.typeString == "Small")
|
||||
effects.push_back({.type = imgui::DialogueEffectSpan::TEXT_SCALE,
|
||||
.start = effect.start,
|
||||
.end = effect.end,
|
||||
.textScale = TEXT_SCALE_SMALL});
|
||||
else if (effect.typeString == "Oscillation")
|
||||
effects.push_back({.type = imgui::DialogueEffectSpan::OSCILLATE,
|
||||
.start = effect.start,
|
||||
.end = effect.end,
|
||||
.frequency = OSCILLATION_FREQUENCY_DEFAULT,
|
||||
.amplitude = OSCILLATION_AMPLITUDE_DEFAULT});
|
||||
else if (effect.typeString == "Shake")
|
||||
effects.push_back({.type = imgui::DialogueEffectSpan::SHAKE,
|
||||
.start = effect.start,
|
||||
.end = effect.end,
|
||||
.frequency = SHAKE_FREQUENCY_DEFAULT,
|
||||
.amplitude = SHAKE_AMPLITUDE_DEFAULT});
|
||||
}
|
||||
imgui::dialogue_text_draw(text, length, effects, glm::vec3{1.0f}, (float)ImGui::GetTime());
|
||||
};
|
||||
|
||||
text();
|
||||
@@ -131,46 +306,65 @@ namespace game::state::play
|
||||
{
|
||||
if (isFinished)
|
||||
{
|
||||
if (!entry->choices.empty())
|
||||
auto choices = dialogue.children_get(*entry, resource::xml::Schema::Element::CHOICE);
|
||||
if (!choices.empty())
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetStyle().WindowPadding.x, available.y));
|
||||
auto buttonSize = imgui::row_widget_size_get((int)entry->choices.size());
|
||||
auto contentMin = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMin().x,
|
||||
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMin().y);
|
||||
auto contentMax = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x,
|
||||
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y);
|
||||
auto buttonY = contentMax.y - ImGui::GetFrameHeight();
|
||||
auto separatorY = buttonY - style.ItemSpacing.y;
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(contentMin.x, separatorY), ImVec2(contentMax.x, separatorY),
|
||||
ImGui::GetColorU32(ImGuiCol_Separator));
|
||||
ImGui::SetCursorScreenPos(ImVec2(contentMin.x, buttonY));
|
||||
auto buttonSize = imgui::row_widget_size_get((int)choices.size());
|
||||
|
||||
for (auto& branch : entry->choices)
|
||||
for (auto index = 0; index < (int)choices.size(); ++index)
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button(branch.text.c_str(), buttonSize)))
|
||||
set(dialogue.get(branch.nextID), character);
|
||||
auto* branch = choices[index];
|
||||
auto label = branch->text.empty() ? "<choice>" : branch->text.c_str();
|
||||
ImGui::PushID(index);
|
||||
if (WIDGET_FX(ImGui::Button(label, buttonSize)))
|
||||
set(dialogue.dialogue_entry_get(*branch), character);
|
||||
|
||||
ImGui::SetItemTooltip("%s", branch.text.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::SetItemTooltip("%s", label);
|
||||
ImGui::PopID();
|
||||
if (index + 1 < (int)choices.size()) ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (isHovered && isSpace)
|
||||
{
|
||||
set(dialogue.get(entry->choices.front().nextID), character);
|
||||
menuSchema.sounds.select.play();
|
||||
auto* next = dialogue.dialogue_entry_get(*choices.front());
|
||||
if (next) menuSchema.root()->soundSelect.play();
|
||||
set(next, character);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry->nextID != -1)
|
||||
if (!entry->next.empty())
|
||||
{
|
||||
ImGui::SetCursorPos(ImVec2(available.x - ImGui::GetTextLineHeightWithSpacing(), available.y));
|
||||
auto indicatorSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
|
||||
auto contentMax = ImVec2(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x,
|
||||
ImGui::GetWindowPos().y + ImGui::GetWindowContentRegionMax().y);
|
||||
ImGui::SetCursorScreenPos(ImVec2(contentMax.x - indicatorSize.x, contentMax.y - indicatorSize.y));
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
auto center = ImVec2(cursorPos.x + (indicatorSize.x * 0.5f), cursorPos.y + (indicatorSize.y * 0.5f));
|
||||
auto offset = std::sin((float)ImGui::GetTime() * 2.0f) * (indicatorSize.x * 0.12f);
|
||||
auto center =
|
||||
ImVec2(cursorPos.x + (indicatorSize.x * 0.5f) + offset, cursorPos.y + (indicatorSize.y * 0.5f));
|
||||
auto half = std::min(indicatorSize.x, indicatorSize.y) * 0.35f;
|
||||
auto tip = ImVec2(center.x + half, center.y);
|
||||
auto baseA = ImVec2(center.x - half, center.y - half);
|
||||
auto baseB = ImVec2(center.x - half, center.y + half);
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
ImGui::Dummy(indicatorSize);
|
||||
ImGui::InvisibleButton("##next-indicator", indicatorSize);
|
||||
|
||||
if (isAdvance)
|
||||
{
|
||||
menuSchema.sounds.select.play();
|
||||
set(dialogue.get(entry->nextID), character);
|
||||
auto* next = dialogue.dialogue_entry_get(*entry);
|
||||
if (next) menuSchema.root()->soundSelect.play();
|
||||
set(next, character);
|
||||
}
|
||||
}
|
||||
else if (isAdvance)
|
||||
@@ -184,7 +378,32 @@ namespace game::state::play
|
||||
{
|
||||
if (isAdvance)
|
||||
{
|
||||
index = ImTextCountCharsFromUtf8(entry->text.c_str(), entry->text.c_str() + entry->text.size());
|
||||
auto target = imgui::utf8_char_count(entry->text);
|
||||
for (auto skippedIndex = index + 1; skippedIndex <= target; ++skippedIndex)
|
||||
{
|
||||
for (auto child : entry->children)
|
||||
{
|
||||
if (child < 0 || child >= (int)character.data.dialogue.elements.size()) continue;
|
||||
auto& effect = character.data.dialogue.elements[child];
|
||||
if (!dialogue_effect_is(effect) || effect.start != skippedIndex) continue;
|
||||
if (effect.typeString == "Delay")
|
||||
{
|
||||
baseDelay = std::max(1, effect.delayTicks);
|
||||
currentDelay = baseDelay;
|
||||
}
|
||||
else if (effect.typeString == "Animation" && !effect.animation.empty())
|
||||
{
|
||||
character.play({.animation = effect.animation,
|
||||
.appendID = character.animation_append_id_get(),
|
||||
.mode = Entity::PLAY_FORCE});
|
||||
}
|
||||
else
|
||||
{
|
||||
dialogue_effect_one_shot_apply(effect, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
index = target;
|
||||
isFinished = true;
|
||||
character.isTalking = false;
|
||||
}
|
||||
@@ -196,7 +415,7 @@ namespace game::state::play
|
||||
};
|
||||
ImGui::End();
|
||||
|
||||
if (isEnabled && isFinished && entry && entry->is_last())
|
||||
if (isEnabled && isFinished && entry && isTerminal)
|
||||
{
|
||||
if (time += ImGui::GetIO().DeltaTime; time > LIFETIME)
|
||||
{
|
||||
@@ -206,5 +425,7 @@ namespace game::state::play
|
||||
}
|
||||
}
|
||||
|
||||
bool Text::is_interruptible() const { return !entry || (entry && entry->is_last()); }
|
||||
bool Text::is_interruptible() const { return !entry || isTerminal; }
|
||||
bool Text::is_finished() const { return isFinished; }
|
||||
bool Text::is_terminal() const { return isTerminal; }
|
||||
}
|
||||
|
||||
+14
-6
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity.hpp"
|
||||
#include "../../render/canvas.hpp"
|
||||
|
||||
#include "../../resources.hpp"
|
||||
|
||||
@@ -11,19 +12,26 @@ namespace game::state::play
|
||||
class Text
|
||||
{
|
||||
int index{};
|
||||
int updateDelay{};
|
||||
int baseDelay{};
|
||||
int currentDelay{};
|
||||
bool isFinished{};
|
||||
bool isTerminal{};
|
||||
bool isStartEffectsApplied{};
|
||||
bool isBlipQueued{};
|
||||
|
||||
public:
|
||||
static constexpr auto LIFETIME = 10.0f;
|
||||
|
||||
resource::xml::Dialogue::Entry* entry{};
|
||||
resource::xml::Schema::Element* entry{};
|
||||
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
|
||||
void set(resource::xml::Dialogue::Entry*, entity::Character&, bool isInterruptible = true);
|
||||
void tick(entity::Character&);
|
||||
void update(entity::Character&);
|
||||
void set(resource::xml::Schema::Element*, Entity&, bool isInterruptible = true);
|
||||
void update(Entity&, Canvas&);
|
||||
bool is_interruptible() const;
|
||||
bool is_finished() const;
|
||||
bool is_terminal() const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+15
-22
@@ -2,10 +2,11 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Toasts::tick()
|
||||
void Toasts::update()
|
||||
{
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
@@ -19,10 +20,7 @@ namespace game::state::play
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Toasts::update()
|
||||
{
|
||||
if (items.empty()) return;
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
@@ -35,13 +33,13 @@ namespace game::state::play
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
auto posY = viewport->Size.y - style.WindowPadding.y -
|
||||
(((ImGui::GetTextLineHeightWithSpacing() + style.WindowPadding.y * 2)) * (items.size() - i));
|
||||
auto height = ImGui::GetTextLineHeightWithSpacing() + style.WindowPadding.y * 2.0f;
|
||||
auto posY = viewport->WorkPos.y + viewport->WorkSize.y - style.WindowPadding.y -
|
||||
(height * (items.size() - i));
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(style.WindowPadding.x, posY));
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(ImGui::CalcTextSize(item.message.c_str()).x + (style.WindowPadding.x * 2),
|
||||
ImGui::GetTextLineHeightWithSpacing()));
|
||||
auto textSize = ImGui::CalcTextSize(item.message.c_str());
|
||||
auto pos = ImVec2(viewport->WorkPos.x + style.WindowPadding.x, posY);
|
||||
auto size = ImVec2(textSize.x + (style.WindowPadding.x * 2), height);
|
||||
|
||||
auto alpha = (float)item.lifetime / Item::LIFETIME;
|
||||
|
||||
@@ -49,18 +47,13 @@ namespace game::state::play
|
||||
borderColor.w = alpha;
|
||||
textColor.w = alpha;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, windowBgColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, textColor);
|
||||
|
||||
auto name = "##Toast " + std::to_string(i);
|
||||
if (ImGui::Begin(name.c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar))
|
||||
ImGui::TextUnformatted(item.message.c_str());
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
auto* drawList = ImGui::GetForegroundDrawList();
|
||||
drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(windowBgColor),
|
||||
style.WindowRounding);
|
||||
drawList->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(borderColor),
|
||||
style.WindowRounding);
|
||||
drawList->AddText(ImVec2(pos.x + style.WindowPadding.x, pos.y + style.WindowPadding.y),
|
||||
ImGui::GetColorU32(textColor), item.message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace game::state::play
|
||||
public:
|
||||
struct Item
|
||||
{
|
||||
static constexpr auto LIFETIME = 30;
|
||||
static constexpr auto LIFETIME = 180;
|
||||
|
||||
std::string message{};
|
||||
int lifetime{};
|
||||
@@ -19,7 +19,6 @@ namespace game::state::play
|
||||
std::vector<Item> items{};
|
||||
|
||||
void update();
|
||||
void tick();
|
||||
void push(const std::string&);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
+85
-49
@@ -3,7 +3,7 @@
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
@@ -11,59 +11,112 @@ using namespace game::resource::xml;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void Tools::update(entity::Character& character, entity::Cursor& cursor, World& world, World::Focus focus,
|
||||
Canvas& canvas)
|
||||
void Tools::update(Entity& character, Entity& cursor, World& world, World::Focus focus, Canvas& canvas,
|
||||
bool isDisabled, float fadeAlpha)
|
||||
{
|
||||
static constexpr auto WIDTH_MULTIPLIER = 0.05f;
|
||||
static constexpr auto ALPHA_MIN = 0.0f;
|
||||
static constexpr auto ALPHA_MAX = 1.0f;
|
||||
static constexpr auto OUTER_STYLE_VAR_COUNT = 3;
|
||||
static constexpr auto BAR_STYLE_VAR_COUNT = 2;
|
||||
static constexpr auto PADDING_SIZE_MULTIPLIER = 2.0f;
|
||||
|
||||
auto style = ImGui::GetStyle();
|
||||
auto& io = ImGui::GetIO();
|
||||
auto& schema = character.data.menuSchema;
|
||||
auto& strings = character.data.strings;
|
||||
|
||||
if (isDisabled) isOpen = false;
|
||||
slide.update(isOpen, io.DeltaTime);
|
||||
if (fadeAlpha <= ALPHA_MIN) return;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * fadeAlpha);
|
||||
|
||||
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
||||
|
||||
auto size = ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * 2);
|
||||
auto size =
|
||||
ImVec2(windowSize.x * WIDTH_MULTIPLIER, windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
|
||||
auto targetX = 0;
|
||||
auto t = slide.value_get();
|
||||
auto eased = slide.eased_get();
|
||||
auto closedX = -size.x;
|
||||
auto posX = closedX + (targetX - closedX) * eased;
|
||||
auto pos = ImVec2(posX, style.WindowPadding.y);
|
||||
auto barSize = ImVec2(ImGui::GetTextLineHeightWithSpacing(), windowSize.y - style.WindowPadding.y * 2);
|
||||
auto barSize =
|
||||
ImVec2(imgui::side_bar_width_get(), windowSize.y - style.WindowPadding.y * PADDING_SIZE_MULTIPLIER);
|
||||
auto barPos = ImVec2(pos.x + size.x, style.WindowPadding.y);
|
||||
auto flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove;
|
||||
if (isDisabled || fadeAlpha < ALPHA_MAX) flags |= ImGuiWindowFlags_NoInputs;
|
||||
|
||||
if (slide.is_visible())
|
||||
{
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
|
||||
if (ImGui::Begin("##Tools", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
if (ImGui::Begin("##Tools", nullptr, flags))
|
||||
{
|
||||
ImGui::BeginDisabled(isDisabled);
|
||||
auto buttonSize = imgui::to_imvec2(vec2(ImGui::GetContentRegionAvail().x));
|
||||
auto* iconsFont = schema.iconsFont.is_valid() ? schema.iconsFont.get() : nullptr;
|
||||
|
||||
auto tool_button = [&](const char* id, const std::string& label, const std::string& iconGlyph,
|
||||
const std::string& tooltip, bool isActive = false)
|
||||
{
|
||||
auto useIcon = iconsFont && !iconGlyph.empty();
|
||||
auto result = false;
|
||||
|
||||
ImGui::PushID(id);
|
||||
if (isActive) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));
|
||||
|
||||
result = WIDGET_FX(ImGui::Button(useIcon ? "##ToolIcon" : label.c_str(), buttonSize));
|
||||
|
||||
if (isActive) ImGui::PopStyleColor();
|
||||
|
||||
if (useIcon)
|
||||
{
|
||||
auto min = ImGui::GetItemRectMin();
|
||||
auto size = ImGui::GetItemRectSize();
|
||||
auto fontSize = size.y;
|
||||
auto textSize = iconsFont->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, iconGlyph.c_str());
|
||||
auto pos = ImVec2(min.x + (size.x - textSize.x) * 0.5f, min.y + (size.y - textSize.y) * 0.5f);
|
||||
ImGui::GetWindowDrawList()->AddText(iconsFont, fontSize, pos, ImGui::GetColorU32(ImGuiCol_Text),
|
||||
iconGlyph.c_str());
|
||||
}
|
||||
|
||||
if (!tooltip.empty()) ImGui::SetItemTooltip("%s", tooltip.c_str());
|
||||
ImGui::PopID();
|
||||
return result;
|
||||
};
|
||||
|
||||
auto cursor_mode_button = [&](const std::string& name, int interactTypeID)
|
||||
{
|
||||
auto isMode = cursor.interactTypeID == interactTypeID;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
ImGui::GetStyleColorVec4(isMode ? ImGuiCol_ButtonHovered : ImGuiCol_Button));
|
||||
if (WIDGET_FX(ImGui::Button(name.c_str(), buttonSize))) cursor.interactTypeID = interactTypeID;
|
||||
ImGui::PopStyleColor();
|
||||
std::string iconGlyph{};
|
||||
for (auto* area : character.data.interact_area_types_get())
|
||||
{
|
||||
if (!area || area->typeString != name || area->iconGlyph.empty()) continue;
|
||||
iconGlyph = area->iconGlyph;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tool_button(name.c_str(), name, iconGlyph, iconsFont && !iconGlyph.empty() ? name : "",
|
||||
cursor.interactTypeID == interactTypeID))
|
||||
cursor.interactTypeID = interactTypeID;
|
||||
};
|
||||
|
||||
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ToolsHomeButton).c_str(), buttonSize)))
|
||||
auto* root = schema.root();
|
||||
auto* home = root ? schema.child_get(*root, Schema::Element::HOME) : nullptr;
|
||||
auto homeIconGlyph = home ? home->iconGlyph : std::string{};
|
||||
if (tool_button("Home", strings.get(Strings::ToolsHomeButton), homeIconGlyph,
|
||||
strings.get(Strings::ToolsHomeTooltip)))
|
||||
world.character_focus(character, canvas, focus);
|
||||
ImGui::SetItemTooltip("%s", strings.get(Strings::ToolsHomeTooltip).c_str());
|
||||
|
||||
for (int i = 0; i < (int)character.data.interactTypeNames.size(); i++)
|
||||
cursor_mode_button(character.data.interactTypeNames[i], i);
|
||||
auto interactTypeNames = character.data.interact_type_names_get();
|
||||
for (int i = 0; i < (int)interactTypeNames.size(); i++)
|
||||
cursor_mode_button(interactTypeNames[i], i);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -73,61 +126,44 @@ namespace game::state::play
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
if (ImGui::Begin("##Tools Open Bar", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove))
|
||||
if (ImGui::Begin("##Tools Open Bar", nullptr, flags))
|
||||
{
|
||||
auto buttonSize = ImGui::GetContentRegionAvail();
|
||||
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
ImGui::BeginDisabled(isDisabled);
|
||||
auto result = WIDGET_FX(ImGui::Button("##ToolsToggle", buttonSize));
|
||||
|
||||
if (t <= 0.0f || t >= 1.0f)
|
||||
if (!isDisabled && (t <= 0.0f || t >= 1.0f))
|
||||
{
|
||||
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::ToolsCloseTooltip
|
||||
: Strings::ToolsOpenTooltip)
|
||||
.c_str());
|
||||
ImGui::SetItemTooltip("%s",
|
||||
strings.get(isOpen ? Strings::ToolsCloseTooltip : Strings::ToolsOpenTooltip).c_str());
|
||||
if (result)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
if (isOpen)
|
||||
schema.sounds.open.play();
|
||||
schema.root()->soundOpen.play();
|
||||
else
|
||||
schema.sounds.close.play();
|
||||
schema.root()->soundClose.play();
|
||||
}
|
||||
if (!isOpen && t <= 0.0f && ImGui::IsItemHovered())
|
||||
{
|
||||
isOpen = true;
|
||||
schema.sounds.open.play();
|
||||
schema.root()->soundOpen.play();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
auto center = ImVec2(cursorPos.x + (buttonSize.x * 0.5f), cursorPos.y + (buttonSize.y * 0.5f));
|
||||
auto half = std::min(buttonSize.x, buttonSize.y) * 0.22f;
|
||||
ImVec2 tip;
|
||||
ImVec2 baseA;
|
||||
ImVec2 baseB;
|
||||
if (isOpen)
|
||||
{
|
||||
tip = ImVec2(center.x - half, center.y);
|
||||
baseA = ImVec2(center.x + half, center.y - half);
|
||||
baseB = ImVec2(center.x + half, center.y + half);
|
||||
}
|
||||
else
|
||||
{
|
||||
tip = ImVec2(center.x + half, center.y);
|
||||
baseA = ImVec2(center.x - half, center.y - half);
|
||||
baseB = ImVec2(center.x - half, center.y + half);
|
||||
}
|
||||
|
||||
auto color = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
ImGui::GetWindowDrawList()->AddTriangleFilled(tip, baseA, baseB, color);
|
||||
auto color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
if (isDisabled) color.w *= style.DisabledAlpha;
|
||||
auto direction = isOpen ? imgui::TriangleDirection::LEFT : imgui::TriangleDirection::RIGHT;
|
||||
imgui::triangle_draw(*ImGui::GetWindowDrawList(), cursorPos, buttonSize, direction, ImGui::GetColorU32(color));
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(BAR_STYLE_VAR_COUNT);
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar(OUTER_STYLE_VAR_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ namespace game::state::play
|
||||
bool isOpen{};
|
||||
util::imgui::WindowSlide slide{0.125f, 0.0f};
|
||||
|
||||
void update(entity::Character&, entity::Cursor&, World&, World::Focus, Canvas&);
|
||||
void update(Entity&, Entity&, World&, World::Focus, Canvas&, bool isDisabled, float fadeAlpha);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,15 +10,49 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::play
|
||||
{
|
||||
void World::set(entity::Character& character, Canvas& canvas, Focus focus)
|
||||
constexpr auto ZOOM_LADDER_EPSILON = 0.01f;
|
||||
constexpr auto ZOOM_WHEEL_ZERO = 0.0f;
|
||||
|
||||
float zoom_ladder_level_get(int index)
|
||||
{
|
||||
auto clampedIndex = std::clamp(index, 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
|
||||
auto exponent = (float)clampedIndex / (float)(World::ZOOM_LADDER_LEVEL_COUNT - 1);
|
||||
return World::ZOOM_MIN * (float)std::pow(World::ZOOM_MAX / World::ZOOM_MIN, exponent);
|
||||
}
|
||||
|
||||
int zoom_ladder_next_index_get(float zoom, int direction)
|
||||
{
|
||||
if (direction > 0)
|
||||
{
|
||||
for (int index = 0; index < World::ZOOM_LADDER_LEVEL_COUNT; ++index)
|
||||
if (zoom < zoom_ladder_level_get(index) - ZOOM_LADDER_EPSILON) return index;
|
||||
return World::ZOOM_LADDER_LEVEL_COUNT - 1;
|
||||
}
|
||||
|
||||
for (int index = World::ZOOM_LADDER_LEVEL_COUNT - 1; index >= 0; --index)
|
||||
if (zoom > zoom_ladder_level_get(index) + ZOOM_LADDER_EPSILON) return index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
float zoom_ladder_zoom_get(float zoom, float wheel)
|
||||
{
|
||||
auto direction = wheel > ZOOM_WHEEL_ZERO ? 1 : -1;
|
||||
auto stepCount = std::max(1, (int)std::ceil(std::abs(wheel)));
|
||||
auto index = zoom_ladder_next_index_get(zoom, direction);
|
||||
index = std::clamp(index + ((stepCount - 1) * direction), 0, World::ZOOM_LADDER_LEVEL_COUNT - 1);
|
||||
return zoom_ladder_level_get(index);
|
||||
}
|
||||
|
||||
void World::set(Entity& character, Canvas& canvas, Focus focus)
|
||||
{
|
||||
character.stage = character.stage_get();
|
||||
character_focus(character, canvas, focus);
|
||||
}
|
||||
|
||||
void World::update(entity::Character& character, entity::Cursor& cursor, Canvas& canvas, Focus focus)
|
||||
void World::update(Entity& character, Entity& cursor, Canvas& canvas, Focus focus)
|
||||
{
|
||||
auto& cursorSchema = character.data.cursorSchema;
|
||||
auto* cursorRoot = cursorSchema.root();
|
||||
auto& pan = canvas.pan;
|
||||
auto& zoom = canvas.zoom;
|
||||
auto& io = ImGui::GetIO();
|
||||
@@ -31,7 +65,8 @@ namespace game::state::play
|
||||
{
|
||||
if ((isMouseMiddleDown) || (isMouseLeftDown && isCtrlDown))
|
||||
{
|
||||
if (auto animation = cursorSchema.animations.pan.get()) cursor.queue_play({*animation});
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationPan.get()) cursor.play({*animation});
|
||||
pan -= imgui::to_vec2(io.MouseDelta) * panMultiplier;
|
||||
}
|
||||
|
||||
@@ -44,9 +79,10 @@ namespace game::state::play
|
||||
auto zoomFactorBefore = math::to_unit(zoomBefore);
|
||||
auto cursorWorld = pan + (cursorPos / zoomFactorBefore);
|
||||
|
||||
if (auto animation = cursorSchema.animations.zoom.get()) cursor.queue_play({*animation});
|
||||
if (cursorRoot)
|
||||
if (auto animation = cursorRoot->animationZoom.get()) cursor.play({*animation});
|
||||
|
||||
zoom = glm::clamp(ZOOM_MIN, zoom + (io.MouseWheel * ZOOM_STEP), ZOOM_MAX);
|
||||
zoom = zoom_ladder_zoom_get(zoom, io.MouseWheel);
|
||||
|
||||
auto zoomFactorAfter = math::to_unit(zoom);
|
||||
pan = cursorWorld - (cursorPos / zoomFactorAfter);
|
||||
@@ -58,7 +94,7 @@ namespace game::state::play
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Home)) character_focus(character, canvas, focus);
|
||||
}
|
||||
|
||||
void World::character_focus(entity::Character& character, Canvas& canvas, Focus focus)
|
||||
void World::character_focus(Entity& character, Canvas& canvas, Focus focus)
|
||||
{
|
||||
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
|
||||
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../render/canvas.hpp"
|
||||
#include "../../entity/character.hpp"
|
||||
#include "../../entity.hpp"
|
||||
|
||||
#include "character_manager.hpp"
|
||||
#include "item_manager.hpp"
|
||||
@@ -13,7 +13,7 @@ namespace game::state::play
|
||||
public:
|
||||
static constexpr auto ZOOM_MIN = 50.0f;
|
||||
static constexpr auto ZOOM_BASE = 100.0f;
|
||||
static constexpr auto ZOOM_STEP = 25.0f;
|
||||
static constexpr auto ZOOM_LADDER_LEVEL_COUNT = 10;
|
||||
static constexpr auto ZOOM_MAX = 400.0f;
|
||||
static constexpr auto SIZE = glm::vec2{1920, 1080};
|
||||
static constexpr auto BOUNDS =
|
||||
@@ -27,9 +27,9 @@ namespace game::state::play
|
||||
TOOLS
|
||||
};
|
||||
|
||||
void update(entity::Character& character, entity::Cursor& cursor, Canvas& canvas, Focus = CENTER);
|
||||
void character_focus(entity::Character& character, Canvas& canvas, Focus = CENTER);
|
||||
void set(entity::Character& character, Canvas& canvas, Focus = CENTER);
|
||||
void update(Entity& character, Entity& cursor, Canvas& canvas, Focus = CENTER);
|
||||
void character_focus(Entity& character, Canvas& canvas, Focus = CENTER);
|
||||
void set(Entity& character, Canvas& canvas, Focus = CENTER);
|
||||
glm::vec2 screen_to_world(glm::vec2 screenPosition, const Canvas& canvas) const;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ using namespace game::util;
|
||||
|
||||
namespace game::state
|
||||
{
|
||||
void Select::tick() { preview.tick(characterIndex); }
|
||||
|
||||
void Select::update(Resources& resources)
|
||||
{
|
||||
preview.update(resources, characterIndex);
|
||||
info.update(resources, characterIndex);
|
||||
characters.update(resources, characterIndex);
|
||||
if (characters.update(resources, characterIndex))
|
||||
{
|
||||
preview.previousCharacterIndex = -1;
|
||||
preview.canvas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Select::render(Resources&, Canvas& canvas)
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace game::state
|
||||
|
||||
int characterIndex{-1};
|
||||
|
||||
void tick();
|
||||
void update(Resources&);
|
||||
void render(Resources&, Canvas&);
|
||||
};
|
||||
|
||||
@@ -7,8 +7,9 @@ using namespace game::util;
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
void Characters::update(Resources& resources, int& characterIndex)
|
||||
bool Characters::update(Resources& resources, int& characterIndex)
|
||||
{
|
||||
auto isRefreshed = false;
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
@@ -27,6 +28,19 @@ namespace game::state::select
|
||||
{
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Characters")))
|
||||
{
|
||||
if (ImGui::BeginPopupContextWindow("characters-context", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
if (ImGui::MenuItem("Refresh"))
|
||||
{
|
||||
resources.characters_refresh();
|
||||
characterIndex = resources.characterPreviews.empty()
|
||||
? -1
|
||||
: std::clamp(characterIndex, 0, (int)resources.characterPreviews.size() - 1);
|
||||
isRefreshed = true;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto cursorStartX = ImGui::GetCursorPosX();
|
||||
|
||||
@@ -34,25 +48,26 @@ namespace game::state::select
|
||||
|
||||
for (int i = 0; i < (int)resources.characterPreviews.size(); i++)
|
||||
{
|
||||
auto& character = resources.characterPreviews[i];
|
||||
auto* character = resources.characterPreviews[i].root();
|
||||
if (!character) continue;
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetCursorPos(cursorPos);
|
||||
imgui::style::color_set(character.color);
|
||||
imgui::style::color_set(character->color);
|
||||
|
||||
auto isSelected = i == characterIndex;
|
||||
|
||||
if (isSelected) ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered));
|
||||
|
||||
if (character.portrait.is_valid())
|
||||
if (character->portrait.is_valid())
|
||||
{
|
||||
if (WIDGET_FX(ImGui::ImageButton(character.name.c_str(), character.portrait.id, buttonSize)))
|
||||
if (WIDGET_FX(ImGui::ImageButton(character->name.c_str(), character->portrait.texture.id, buttonSize)))
|
||||
characterIndex = i;
|
||||
}
|
||||
else if (WIDGET_FX(ImGui::Button(character.name.c_str(), buttonSize)))
|
||||
else if (WIDGET_FX(ImGui::Button(character->name.c_str(), buttonSize)))
|
||||
characterIndex = i;
|
||||
if (isSelected) ImGui::PopStyleColor();
|
||||
ImGui::SetItemTooltip("%s", character.name.c_str());
|
||||
ImGui::SetItemTooltip("%s", character->name.c_str());
|
||||
|
||||
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
|
||||
cursorPos.x += increment;
|
||||
@@ -64,7 +79,7 @@ namespace game::state::select
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
imgui::style::color_set(resources.settings.color);
|
||||
imgui::style::color_set(resources.settings.root()->color);
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
@@ -78,5 +93,6 @@ namespace game::state::select
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
return isRefreshed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ namespace game::state::select
|
||||
public:
|
||||
SettingsMenu settingsMenu;
|
||||
|
||||
void update(Resources&, int& characterIndex);
|
||||
bool update(Resources&, int& characterIndex);
|
||||
};
|
||||
}
|
||||
|
||||
+22
-15
@@ -4,6 +4,7 @@
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/measurement.hpp"
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
@@ -19,7 +20,8 @@ namespace game::state::select
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto& character = resources.characterPreviews[characterIndex];
|
||||
auto* character = resources.characterPreviews[characterIndex].root();
|
||||
if (!character) return;
|
||||
|
||||
auto size = ImVec2(viewport->Size.x / 2.0f - (style.WindowPadding.x * 2.0f),
|
||||
(viewport->Size.y / 2.0f) - (style.WindowPadding.y * 2.0f));
|
||||
@@ -27,15 +29,19 @@ namespace game::state::select
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
imgui::style::color_set(character.color);
|
||||
imgui::style::color_set(character->color);
|
||||
|
||||
if (ImGui::Begin("##Info", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar))
|
||||
{
|
||||
auto& save = character.save;
|
||||
auto& system = resources.settings.measurementSystem;
|
||||
auto& weight = save.is_valid() ? save.weight : character.weight;
|
||||
auto& save = resources.characterSaves.at(characterIndex);
|
||||
auto* settings = resources.settings.root();
|
||||
auto system = settings->measurementSystem == "Imperial" ? IMPERIAL : METRIC;
|
||||
auto* saveRoot = save.root();
|
||||
auto isSaveValid = saveRoot && saveRoot->type == resource::xml::Schema::Element::SAVE;
|
||||
auto* savedCharacter = isSaveValid ? save.child_get(*saveRoot, resource::xml::Schema::Element::CHARACTER) : nullptr;
|
||||
auto weight = savedCharacter ? (double)savedCharacter->weightKilograms : (double)character->weight;
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
|
||||
|
||||
@@ -44,7 +50,7 @@ namespace game::state::select
|
||||
if (ImGui::BeginChild("##Info Child", childSize))
|
||||
{
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
|
||||
ImGui::TextUnformatted(character.name.c_str());
|
||||
ImGui::TextUnformatted(character->name.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
@@ -52,13 +58,13 @@ namespace game::state::select
|
||||
{
|
||||
if (ImGui::BeginTabItem("Overview"))
|
||||
{
|
||||
if (!character.description.empty())
|
||||
if (!character->description.empty())
|
||||
{
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
ImGui::TextWrapped("%s", character.description.c_str());
|
||||
ImGui::TextWrapped("%s", character->description.c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
@@ -70,7 +76,8 @@ namespace game::state::select
|
||||
|
||||
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
|
||||
system == IMPERIAL ? "lbs" : "kg");
|
||||
ImGui::Text("Stages: %i", character.stages);
|
||||
auto stages = resources.characterPreviews[characterIndex].get_all(resource::xml::Schema::Element::STAGE);
|
||||
ImGui::Text("Stages: %i", (int)stages.size() + 1);
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTabItem();
|
||||
@@ -81,8 +88,8 @@ namespace game::state::select
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
|
||||
if (!character.credits.empty())
|
||||
ImGui::TextWrapped("%s", character.credits.c_str());
|
||||
if (!character->credits.empty())
|
||||
ImGui::TextWrapped("%s", character->credits.c_str());
|
||||
else
|
||||
ImGui::TextUnformatted("No credits listed.");
|
||||
ImGui::PopFont();
|
||||
@@ -96,9 +103,9 @@ namespace game::state::select
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
|
||||
auto widgetSize = row_widget_size_get(isSaveValid ? 2 : 1);
|
||||
|
||||
if (save.is_valid())
|
||||
if (isSaveValid)
|
||||
{
|
||||
if (WIDGET_FX(ImGui::Button("Continue", widgetSize))) isContinue = true;
|
||||
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
|
||||
@@ -129,7 +136,7 @@ namespace game::state::select
|
||||
if (ImGui::BeginPopupModal("New Game Warning", &isNewGameWarning,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto popupWidgetSize = row_widget_size_get(save.is_valid() ? 2 : 1);
|
||||
auto popupWidgetSize = row_widget_size_get(isSaveValid ? 2 : 1);
|
||||
ImGui::TextWrapped("This will delete saved progress! Are you sure?");
|
||||
if (WIDGET_FX(ImGui::Button("Yes", popupWidgetSize))) isNewGame = true;
|
||||
ImGui::SameLine();
|
||||
@@ -138,6 +145,6 @@ namespace game::state::select
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
imgui::style::color_set(resources.settings.color);
|
||||
imgui::style::color_set(resources.settings.root()->color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
#include "preview.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../../util/imgui.hpp"
|
||||
#include "../../util/imgui/style.hpp"
|
||||
#include "../../util/imgui/widget.hpp"
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
using namespace game::entity;
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
using namespace game::util::imgui;
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
void Preview::tick(int characterIndex)
|
||||
{
|
||||
if (characterIndex != -1 && isInGame) actor.tick();
|
||||
}
|
||||
|
||||
void Preview::update(Resources& resources, int characterIndex)
|
||||
{
|
||||
if (characterIndex != -1 && isInGame) actor.update();
|
||||
|
||||
if (!vector::in_bounds(resources.characterPreviews, characterIndex)) return;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
auto& character = resources.characterPreviews[characterIndex];
|
||||
auto* character = resources.characterPreviews[characterIndex].root();
|
||||
if (!character) return;
|
||||
auto size = ImVec2(viewport->Size.x / 2.0f - (style.WindowPadding.x * 2.0f),
|
||||
(viewport->Size.y / 2.0f) - (style.WindowPadding.y * 2.0f));
|
||||
auto pos = ImVec2(style.WindowPadding.x, style.WindowPadding.y);
|
||||
|
||||
ImGui::SetNextWindowSize(size);
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
imgui::style::color_set(character.color);
|
||||
imgui::style::color_set(character->color);
|
||||
|
||||
if (ImGui::Begin("##Preview", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
@@ -43,21 +39,29 @@ namespace game::state::select
|
||||
{
|
||||
auto available = ImGui::GetContentRegionAvail();
|
||||
auto availableSize = imgui::to_vec2(available);
|
||||
auto textureSize = vec2(character.render.size);
|
||||
|
||||
if (WIDGET_FX(ImGui::BeginTabItem("Render")))
|
||||
{
|
||||
auto scale =
|
||||
(availableSize.x <= 0.0f || availableSize.y <= 0.0f || textureSize.x <= 0.0f || textureSize.y <= 0.0f)
|
||||
? 0.0f
|
||||
: std::min(availableSize.x / textureSize.x, availableSize.y / textureSize.y);
|
||||
auto textureSize = vec2(character->render.texture.size);
|
||||
|
||||
auto renderSize = ImVec2(textureSize.x * scale, textureSize.y * scale);
|
||||
if (character->render.is_valid())
|
||||
{
|
||||
auto scale =
|
||||
(availableSize.x <= 0.0f || availableSize.y <= 0.0f || textureSize.x <= 0.0f || textureSize.y <= 0.0f)
|
||||
? 0.0f
|
||||
: std::min(availableSize.x / textureSize.x, availableSize.y / textureSize.y);
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (renderSize.x * 0.5f),
|
||||
ImGui::GetCursorPosY() + (availableSize.y * 0.5f) - (renderSize.y * 0.5f)));
|
||||
auto renderSize = ImVec2(textureSize.x * scale, textureSize.y * scale);
|
||||
|
||||
ImGui::Image(character.render.id, renderSize);
|
||||
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (renderSize.x * 0.5f),
|
||||
ImGui::GetCursorPosY() + (availableSize.y * 0.5f) - (renderSize.y * 0.5f)));
|
||||
|
||||
ImGui::Image(character->render.texture.id, renderSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextUnformatted(character->name.c_str());
|
||||
if (!character->description.empty()) ImGui::TextWrapped("%s", character->description.c_str());
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
@@ -68,34 +72,18 @@ namespace game::state::select
|
||||
|
||||
if (previousCharacterIndex != characterIndex)
|
||||
{
|
||||
actor = Actor(resources.characterPreviews[characterIndex].anm2);
|
||||
rect = actor.rect();
|
||||
actor = Entity(resources.character_get(characterIndex).root()->anm2Entry.anm2);
|
||||
canvas.clear();
|
||||
previousCharacterIndex = characterIndex;
|
||||
}
|
||||
|
||||
auto rectSize = vec2(rect.z, rect.w);
|
||||
auto previewScale = (availableSize.x <= 0.0f || availableSize.y <= 0.0f || rectSize.x <= 0.0f ||
|
||||
rectSize.y <= 0.0f || !std::isfinite(rectSize.x) || !std::isfinite(rectSize.y))
|
||||
? 0.0f
|
||||
: std::min(availableSize.x / rectSize.x, availableSize.y / rectSize.y);
|
||||
|
||||
auto previewSize = rectSize * previewScale;
|
||||
auto canvasSize = ivec2(std::max(1.0f, previewSize.x), std::max(1.0f, previewSize.y));
|
||||
|
||||
canvas.zoom = previewScale * 100.0f;
|
||||
canvas.pan = vec2(rect.x, rect.y);
|
||||
auto& rendered = canvas.render(resources, actor, available);
|
||||
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(ImVec2(cursorPos.x + (availableSize.x * 0.5f) - ((float)canvasSize.x * 0.5f),
|
||||
cursorPos.y + (availableSize.y * 0.5f) - ((float)canvasSize.y * 0.5f)));
|
||||
ImGui::SetCursorPos(ImVec2(cursorPos.x + (availableSize.x * 0.5f) - (canvas.imageSize.x * 0.5f),
|
||||
cursorPos.y + (availableSize.y * 0.5f) - (canvas.imageSize.y * 0.5f)));
|
||||
|
||||
canvas.bind();
|
||||
canvas.size_set(canvasSize);
|
||||
canvas.clear();
|
||||
actor.render(resources.shaders[shader::TEXTURE], resources.shaders[shader::RECT], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
ImGui::Image(canvas.texture, imgui::to_imvec2(canvasSize));
|
||||
ImGui::Image(rendered.texture, canvas.imageSize);
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
@@ -106,6 +94,6 @@ namespace game::state::select
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
imgui::style::color_set(resources.settings.color);
|
||||
imgui::style::color_set(resources.settings.root()->color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../entity/actor.hpp"
|
||||
#include "../../entity.hpp"
|
||||
#include "../../resources.hpp"
|
||||
#include "../../util/imgui/entity_canvas.hpp"
|
||||
|
||||
namespace game::state::select
|
||||
{
|
||||
@@ -9,13 +10,11 @@ namespace game::state::select
|
||||
{
|
||||
public:
|
||||
int previousCharacterIndex{-1};
|
||||
entity::Actor actor{};
|
||||
glm::vec4 rect{};
|
||||
Entity actor{};
|
||||
bool isInGame{};
|
||||
|
||||
Canvas canvas{glm::vec2(), Canvas::FLIP};
|
||||
util::imgui::widget::EntityCanvas canvas{};
|
||||
|
||||
void update(Resources& resources, int characterIndex);
|
||||
void tick(int characterIndex);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "../util/imgui/style.hpp"
|
||||
#include "../util/imgui/widget.hpp"
|
||||
#include "../util/display.hpp"
|
||||
#include "../util/math.hpp"
|
||||
#include "../util/measurement.hpp"
|
||||
|
||||
@@ -16,10 +17,16 @@ namespace game::state
|
||||
{
|
||||
void SettingsMenu::update(Resources& resources, Mode mode, const Strings* strings)
|
||||
{
|
||||
auto& settings = resources.settings;
|
||||
auto& measurementSystem = settings.measurementSystem;
|
||||
auto& volume = settings.volume;
|
||||
auto& color = settings.color;
|
||||
auto* settings = resources.settings.root();
|
||||
if (!settings)
|
||||
{
|
||||
resources.settings.settings_default();
|
||||
settings = resources.settings.root();
|
||||
}
|
||||
auto measurementSystem = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
|
||||
auto& volume = settings->volume;
|
||||
auto& isAudioMuted = settings->isAudioMuted;
|
||||
auto& color = settings->color;
|
||||
auto string_get = [&](Strings::Type type, const char* fallback) -> const char*
|
||||
{
|
||||
return strings ? strings->get(type).c_str() : fallback;
|
||||
@@ -35,23 +42,41 @@ namespace game::state
|
||||
WIDGET_FX(ImGui::RadioButton(string_get(Strings::SettingsImperial, "Imperial"),
|
||||
(int*)&measurementSystem, measurement::IMPERIAL));
|
||||
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsImperialTooltip, "Use pounds (lbs)."));
|
||||
settings->measurementSystem = measurementSystem == measurement::IMPERIAL ? "Imperial" : "Metric";
|
||||
|
||||
ImGui::SeparatorText(string_get(Strings::SettingsWindow, "Window"));
|
||||
const char* windowModes[] = {
|
||||
string_get(Strings::SettingsWindowed, "Windowed"),
|
||||
string_get(Strings::SettingsFullscreen, "Fullscreen"),
|
||||
};
|
||||
auto windowMode = display::fullscreen_get() ? 1 : 0;
|
||||
if (WIDGET_FX(ImGui::Combo(string_get(Strings::SettingsWindowMode, "Mode"), &windowMode, windowModes, 2)))
|
||||
display::fullscreen_set(windowMode == 1);
|
||||
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsWindowModeTooltip,
|
||||
"Switch between windowed and fullscreen display.\n(Shortcut: F)"));
|
||||
|
||||
ImGui::SeparatorText(string_get(Strings::SettingsSound, "Sound"));
|
||||
ImGui::BeginDisabled(isAudioMuted);
|
||||
if (WIDGET_FX(
|
||||
ImGui::SliderInt(string_get(Strings::SettingsVolume, "Volume"), &volume, 0, 100, "%d%%")))
|
||||
resources.volume_set(math::to_unit((float)volume));
|
||||
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsVolumeTooltip, "Adjust master volume."));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (WIDGET_FX(ImGui::Checkbox(string_get(Strings::SettingsMute, "Mute"), &isAudioMuted)))
|
||||
resources.audio_mute_set(isAudioMuted);
|
||||
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsMuteTooltip, "Mute all audio.\n(Shortcut: M)"));
|
||||
|
||||
ImGui::SeparatorText(string_get(Strings::SettingsAppearance, "Appearance"));
|
||||
|
||||
if (WIDGET_FX(ImGui::Checkbox(string_get(Strings::SettingsUseCharacterColor,
|
||||
"Use Character Color"),
|
||||
&settings.isUseCharacterColor)))
|
||||
&settings->isUseCharacterColor)))
|
||||
isJustColorSet = true;
|
||||
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsUseCharacterColorTooltip,
|
||||
"When playing, the UI will use the character's preset UI color."));
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(settings.isUseCharacterColor);
|
||||
ImGui::BeginDisabled(settings->isUseCharacterColor);
|
||||
if (WIDGET_FX(
|
||||
ImGui::ColorEdit3(string_get(Strings::SettingsColor, "Color"), value_ptr(color),
|
||||
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
|
||||
@@ -66,8 +91,9 @@ namespace game::state
|
||||
if (WIDGET_FX(ImGui::Button(string_get(Strings::SettingsResetButton, "Reset to Default"),
|
||||
ImVec2(-FLT_MIN, 0))))
|
||||
{
|
||||
settings = resource::xml::Settings();
|
||||
style::color_set(settings.color);
|
||||
resources.settings.settings_default();
|
||||
settings = resources.settings.root();
|
||||
style::color_set(settings->color);
|
||||
}
|
||||
|
||||
if (mode == PLAY)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../resource/xml/strings.hpp"
|
||||
#include "../resource/strings.hpp"
|
||||
#include "../resources.hpp"
|
||||
|
||||
namespace game::state
|
||||
|
||||
Reference in New Issue
Block a user