Files
shweets-sim/src/state/play.cpp
T
2026-08-09 00:20:29 -04:00

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));
}
};