441 lines
17 KiB
C++
441 lines
17 KiB
C++
#include "play.hpp"
|
|
#include "play/style.hpp"
|
|
|
|
#include <array>
|
|
#include <glm/glm.hpp>
|
|
#include <imgui.h>
|
|
#include <imgui_impl_opengl3.h>
|
|
|
|
#include "../util/imgui.hpp"
|
|
#include "../util/imgui/style.hpp"
|
|
#include "../util/imgui/widget.hpp"
|
|
#include "../util/math.hpp"
|
|
|
|
using namespace game::resource;
|
|
using namespace game::resource::xml;
|
|
using namespace game::util;
|
|
using namespace game::state::play;
|
|
using namespace glm;
|
|
|
|
namespace game::state
|
|
{
|
|
void entity_debug_flags_apply(Entity& entity, bool isShowLayers, bool isShowNulls)
|
|
{
|
|
entity.isShowLayers = isShowLayers;
|
|
entity.isShowNulls = isShowNulls;
|
|
}
|
|
|
|
int saved_item_id_get(const Schema& itemSchema, const Schema::Element& item)
|
|
{
|
|
if (item.id >= 0 && item.id < (int)itemSchema.items.size() && item.id < (int)itemSchema.anm2s.size())
|
|
return item.id;
|
|
return -1;
|
|
}
|
|
|
|
World::Focus Play::focus_get()
|
|
{
|
|
if (text.is_scene_active()) return World::CENTER;
|
|
|
|
auto isToolsOpen = tools.isOpen;
|
|
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::debug_flags_apply()
|
|
{
|
|
entity_debug_flags_apply(character, isShowLayers, isShowNulls);
|
|
entity_debug_flags_apply(cursor, isShowLayers, isShowNulls);
|
|
entity_debug_flags_apply(autofeedCursor, isShowLayers, isShowNulls);
|
|
areaManager.debug_flags_set(isShowLayers, isShowNulls);
|
|
for (auto& item : itemManager.items)
|
|
entity_debug_flags_apply(item, isShowLayers, isShowNulls);
|
|
for (auto& particle : particleManager.particles)
|
|
entity_debug_flags_apply(particle, isShowLayers, isShowNulls);
|
|
}
|
|
|
|
void Play::set(Resources& resources, int selectedCharacterIndex, enum Game game)
|
|
{
|
|
auto& data = resources.character_get(selectedCharacterIndex);
|
|
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* saveItems = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ITEMS) : nullptr;
|
|
auto* saveDiscovered = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::DISCOVERED) : nullptr;
|
|
auto* savePan = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::PAN) : nullptr;
|
|
auto* saveZoom = isSaveValid ? data.save.child_get(*saveRoot, Schema::Element::ZOOM) : nullptr;
|
|
auto& itemSchema = data.itemSchema;
|
|
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;
|
|
world = play::World{};
|
|
|
|
character = Entity(data, vec2{});
|
|
character.digestionRate =
|
|
glm::clamp(character.digestionRate, characterRoot->digestionRateMin, characterRoot->digestionRateMax);
|
|
character.capacity =
|
|
glm::clamp(character.capacity, characterRoot->capacityMinCalories, characterRoot->capacityMaxCalories);
|
|
|
|
auto* alternateSpritesheet = data.alternate_spritesheet();
|
|
auto isAlternateSpritesheet =
|
|
alternateSpritesheet && game == NEW_GAME && math::random_percent_roll(alternateSpritesheet->chanceOnNewGame);
|
|
|
|
if (isAlternateSpritesheet || (isSaveValid && saveRoot->isAlternateSpritesheet))
|
|
{
|
|
character.spritesheet_set(Entity::ALTERNATE);
|
|
if (game == NEW_GAME && alternateSpritesheet) alternateSpritesheet->soundEntry.sound.play();
|
|
}
|
|
|
|
character.totalCaloriesConsumed = saveCharacter ? saveCharacter->totalCaloriesConsumed : 0;
|
|
character.totalItemsSpawned = saveCharacter ? saveCharacter->totalItemsSpawned : 0;
|
|
character.totalItemsConsumed = saveCharacter ? saveCharacter->totalItemsConsumed : 0;
|
|
character.totalPlaytimeSeconds = saveCharacter ? saveCharacter->totalPlaytimeSeconds : 0;
|
|
autofeed = play::Autofeed{};
|
|
characterManager = CharacterManager{};
|
|
particleManager = ParticleManager{};
|
|
areaManager = AreaManager{};
|
|
areaManager.set(character);
|
|
world.bounds_set(areaManager.bounds_get(character, World::BOUNDS));
|
|
character.position = {};
|
|
|
|
cursor = Entity{};
|
|
cursor.entityType = CURSOR;
|
|
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{};
|
|
|
|
itemManager = ItemManager{};
|
|
itemManager.spawnDelayTicks = saveRoot ? saveRoot->spawnDelayTicks : 0;
|
|
itemManager.discoveredItemIDs.resize(itemSchema.items.size());
|
|
for (auto* item :
|
|
saveDiscovered ? data.save.children_get(*saveDiscovered, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
|
|
{
|
|
auto itemID = saved_item_id_get(itemSchema, *item);
|
|
if (itemID != -1) itemManager.discoveredItemIDs[itemID] = true;
|
|
}
|
|
for (auto* item :
|
|
saveItems ? data.save.children_get(*saveItems, Schema::Element::ITEM) : std::vector<Schema::Element*>{})
|
|
{
|
|
auto itemID = saved_item_id_get(itemSchema, *item);
|
|
if (itemID == -1) continue;
|
|
auto& saveItem = itemSchema.anm2s[itemID];
|
|
itemManager.items.emplace_back(saveItem, item->position, itemID, item->velocity, item->rotation);
|
|
itemManager.discoveredItemIDs[itemID] = true;
|
|
}
|
|
|
|
imgui::style::widget_set(menuSchema.rounding);
|
|
imgui::widget::sounds_set(&menuSchema.root()->soundHover, &menuSchema.root()->soundSelect);
|
|
play::style::color_set(resources, character);
|
|
|
|
text.entry = nullptr;
|
|
text.isEnabled = false;
|
|
isPostgamePending = false;
|
|
|
|
#if DEBUG
|
|
menu.isCheats = true;
|
|
#else
|
|
menu.isCheats = false;
|
|
#endif
|
|
|
|
isPostgame = isSaveValid && saveRoot->isPostgame;
|
|
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
|
|
if (isPostgame) menu.isCheats = true;
|
|
|
|
if (auto font = character.data.menuSchema.font.get()) ImGui::GetIO().FontDefault = font;
|
|
|
|
character.play({.animation = character.idle_animation_get(), .appendID = character.animation_append_id_get()});
|
|
character.update();
|
|
worldCanvas.size_set(imgui::to_vec2(ImGui::GetMainViewport()->Size));
|
|
world.set(character, worldCanvas, focus_get());
|
|
if (savePan) worldCanvas.pan = savePan->position;
|
|
if (saveZoom && saveZoom->zoom >= World::ZOOM_MIN)
|
|
{
|
|
worldCanvas.zoom = glm::clamp(World::ZOOM_MIN, saveZoom->zoom, World::ZOOM_MAX);
|
|
world.zoom_index_set(worldCanvas);
|
|
}
|
|
|
|
if (game == NEW_GAME)
|
|
text.set(dialogue.dialogue_hook_entry_get(Schema::Element::START), character, false);
|
|
|
|
if (isPostgame)
|
|
{
|
|
isPostgamePending = false;
|
|
}
|
|
else
|
|
{
|
|
isPostgamePending = false;
|
|
}
|
|
}
|
|
|
|
void Play::exit(Resources& resources)
|
|
{
|
|
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::update(Resources& resources)
|
|
{
|
|
static constexpr std::array<ImGuiKey, 10> CHEAT_CODE = {
|
|
ImGuiKey_UpArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow, ImGuiKey_DownArrow, ImGuiKey_LeftArrow,
|
|
ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_B, ImGuiKey_A};
|
|
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;
|
|
auto isCursorEnabled = cursor_enabled_get();
|
|
character.totalPlaytimeSeconds += ImGui::GetIO().DeltaTime;
|
|
if (isCursorEnabled)
|
|
{
|
|
cursor.isVisible = true;
|
|
autofeedCursor.isVisible = false;
|
|
}
|
|
|
|
if (!menu.isCheats)
|
|
{
|
|
for (auto key : CHEAT_INPUT_KEYS)
|
|
{
|
|
if (!ImGui::IsKeyPressed(key, false)) continue;
|
|
|
|
if (key == CHEAT_CODE[cheatCodeIndex])
|
|
{
|
|
cheatCodeIndex++;
|
|
cheatCodeStartTime = ImGui::GetTime();
|
|
}
|
|
else if (key == CHEAT_CODE[0])
|
|
{
|
|
cheatCodeIndex = 1;
|
|
cheatCodeStartTime = ImGui::GetTime();
|
|
}
|
|
else
|
|
{
|
|
cheatCodeIndex = 0;
|
|
cheatCodeStartTime = 0.0;
|
|
}
|
|
|
|
if (cheatCodeIndex >= (int)CHEAT_CODE.size())
|
|
{
|
|
menu.isCheats = true;
|
|
cheatCodeIndex = 0;
|
|
cheatCodeStartTime = 0.0;
|
|
toasts.push(character.data.strings.get(Strings::ToastCheatsUnlocked));
|
|
character.data.menuSchema.root()->soundCheatsActivated.play();
|
|
}
|
|
}
|
|
|
|
if (cheatCodeIndex > 0 && (ImGui::GetTime() - cheatCodeStartTime > CHEAT_CODE_INPUT_TIME_SECONDS))
|
|
{
|
|
cheatCodeIndex = 0;
|
|
cheatCodeStartTime = 0.0;
|
|
}
|
|
}
|
|
|
|
auto isSceneActive = text.is_scene_active();
|
|
|
|
if (!isSceneActive)
|
|
{
|
|
tools.update(character, cursor, world, focus, worldCanvas, false, UI_ALPHA_MAX);
|
|
menu.update(resources, itemManager, character, text, autofeed);
|
|
toasts.update();
|
|
}
|
|
|
|
itemManager.isDisabled = isSceneActive;
|
|
characterManager.isDisabled = isSceneActive;
|
|
|
|
if (text.isEnabled) text.update(character, worldCanvas);
|
|
|
|
if (character.isJustStageFinal && !isPostgame && !isPostgamePending)
|
|
{
|
|
auto* end = dialogue.dialogue_hook_entry_get(Schema::Element::END);
|
|
if (end)
|
|
{
|
|
text.set(end, character, false);
|
|
tools.isOpen = false;
|
|
menu.isOpen = false;
|
|
character.calories = 0;
|
|
character.digestionProgress = 0;
|
|
itemManager.heldItemIndex = -1;
|
|
isPostgamePending = true;
|
|
world.character_focus(character, worldCanvas, focus_get());
|
|
}
|
|
else
|
|
{
|
|
isPostgame = true;
|
|
menu.isCheats = true;
|
|
}
|
|
}
|
|
|
|
if (isPostgamePending && text.is_terminal() && text.is_finished() && !text.is_scene_active())
|
|
{
|
|
menu.isOpen = true;
|
|
isPostgamePending = false;
|
|
isPostgame = true;
|
|
menu.isCheats = true;
|
|
world.character_focus(character, worldCanvas, focus_get());
|
|
}
|
|
|
|
if (!text.is_scene_active())
|
|
autofeed.update(resources, character, autofeedCursor, menu.inventory, itemManager, characterManager, worldCanvas);
|
|
auto& interactionCursor = autofeed.active_get() ? autofeedCursor : cursor;
|
|
itemManager.isAutomation = autofeed.active_get();
|
|
itemManager.update(character, interactionCursor, areaManager, text, world.bounds, worldCanvas);
|
|
auto* itemRoot = character.data.itemSchema.root();
|
|
for (int i = 0; i < itemManager.newItemFoundCount; i++)
|
|
{
|
|
toasts.push(character.data.strings.get(Strings::ToastNewItemFound));
|
|
if (itemRoot) itemRoot->soundNewItem.play();
|
|
}
|
|
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);
|
|
world.bounds_set(areaManager.bounds_get(character, world.bounds));
|
|
particleManager.update();
|
|
cursor.update();
|
|
if (autofeed.active_get()) autofeedCursor.update();
|
|
world.update(character, cursor, worldCanvas, focus);
|
|
worldCanvas.update();
|
|
|
|
if (autosaveTime += ImGui::GetIO().DeltaTime; autosaveTime > AUTOSAVE_TIME || menu.settingsMenu.isSave)
|
|
{
|
|
save(resources);
|
|
autosaveTime = 0;
|
|
menu.settingsMenu.isSave = false;
|
|
}
|
|
}
|
|
|
|
void Play::render(Resources& resources, Canvas& canvas)
|
|
{
|
|
auto& textureShader = resources.shaders[shader::TEXTURE];
|
|
auto& rectShader = resources.shaders[shader::RECT];
|
|
auto size = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
|
|
|
|
auto windowModel = math::quad_model_get(vec2(size));
|
|
|
|
worldCanvas.bind();
|
|
worldCanvas.size_set(size);
|
|
worldCanvas.clear();
|
|
debug_flags_apply();
|
|
|
|
areaManager.render(character, textureShader, rectShader, worldCanvas);
|
|
|
|
character.render(textureShader, rectShader, worldCanvas);
|
|
|
|
if (!text.is_scene_active())
|
|
for (auto& item : itemManager.items)
|
|
item.render(textureShader, rectShader, worldCanvas);
|
|
|
|
auto isCursorEnabled = cursor_enabled_get();
|
|
if (isCursorEnabled && autofeed.active_get()) autofeedCursor.render(textureShader, rectShader, worldCanvas);
|
|
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());
|
|
if (isCursor) cursor.render(textureShader, rectShader, canvas);
|
|
canvas.unbind();
|
|
}
|
|
|
|
void Play::save(Resources& resources)
|
|
{
|
|
resource::xml::Schema save;
|
|
auto& itemSchema = character.data.itemSchema;
|
|
auto& root = save.element_add(resource::xml::Schema::Element::SAVE);
|
|
root.isPostgame = isPostgame;
|
|
root.isAlternateSpritesheet = character.spritesheetType == Entity::ALTERNATE;
|
|
root.spawnDelayTicks = itemManager.spawnDelayTicks;
|
|
|
|
auto& panElement = save.element_add(resource::xml::Schema::Element::PAN, 0);
|
|
panElement.position = worldCanvas.pan;
|
|
|
|
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.digestionProgress = character.digestionProgress;
|
|
characterElement.isDigesting = character.isDigesting;
|
|
characterElement.digestionTimer = character.digestionTimer;
|
|
characterElement.totalCaloriesConsumed = character.totalCaloriesConsumed;
|
|
characterElement.totalItemsSpawned = character.totalItemsSpawned;
|
|
characterElement.totalItemsConsumed = character.totalItemsConsumed;
|
|
characterElement.totalPlaytimeSeconds = character.totalPlaytimeSeconds;
|
|
|
|
auto itemsIndex = (int)save.elements.size();
|
|
save.element_add(resource::xml::Schema::Element::ITEMS, 0);
|
|
for (auto& item : itemManager.items)
|
|
{
|
|
if (item.schemaID < 0 || item.schemaID >= (int)itemSchema.items.size()) continue;
|
|
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, itemsIndex);
|
|
itemElement.id = item.schemaID;
|
|
itemElement.position = item.position;
|
|
itemElement.velocity = item.velocity;
|
|
itemElement.rotation = *item.overrides[item.rotationOverrideID].frame.rotation;
|
|
}
|
|
|
|
auto discoveredIndex = (int)save.elements.size();
|
|
save.element_add(resource::xml::Schema::Element::DISCOVERED, 0);
|
|
for (int i = 0; i < (int)itemManager.discoveredItemIDs.size(); i++)
|
|
{
|
|
if (!itemManager.discoveredItemIDs[i]) continue;
|
|
auto& itemElement = save.element_add(resource::xml::Schema::Element::ITEM, discoveredIndex);
|
|
itemElement.id = i;
|
|
}
|
|
|
|
resources.character_save_set(characterIndex, save);
|
|
save.serialize(character.data.save_path_get());
|
|
|
|
toasts.push(character.data.strings.get(Strings::ToastSaving));
|
|
}
|
|
};
|