#include "state.h" #include #include #include #include "util/math_.h" using namespace glm; using namespace game::resource; using namespace game::util; namespace game { constexpr auto TICK_RATE = 30; constexpr auto TICK_INTERVAL = (1000 / TICK_RATE); constexpr auto UPDATE_RATE = 60; constexpr auto UPDATE_INTERVAL = (1000 / UPDATE_RATE); State::State(SDL_Window* inWindow, SDL_GLContext inContext, vec2 size) : window(inWindow), context(inContext), canvas(size, true) { } void State::tick() { for (auto& item : items) item.tick(); character.tick(); if (character.isJustDigestionStart) resources.sound_play(audio::GURGLES[(int)math::random_roll(std::size(audio::GURGLES))]); if (character.is_event("Burp")) resources.sound_play(audio::BURPS[(int)math::random_roll(std::size(audio::BURPS))]); if (character.isJustDigestionEnd && !character.isJustStageUp) { character.state_set(Character::PAT, true); textWindow.set_random(resources.dialogue.postDigestIDs, resources, character); } cursor.tick(); textWindow.tick(resources, character); } void State::update() { static bool isRubbing{}; auto& inventory = mainMenuWindow.inventory; auto& dialogue = resources.dialogue; int width{}; int height{}; SDL_GetWindowSize(window, &width, &height); SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL3_ProcessEvent(&event); if (event.type == SDL_EVENT_QUIT) isRunning = false; } if (!isRunning) return; ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); auto style = ImGui::GetStyle(); if (textWindow.isFlagActivated) { switch (textWindow.flag) { case Dialogue::Entry::ACTIVATE_WINDOWS: isInfo = true; isMainMenu = true; break; case Dialogue::Entry::DEACTIVATE_WINDOWS: isInfo = false; isMainMenu = false; break; case Dialogue::Entry::ONLY_INFO: isInfo = true; isMainMenu = false; break; case Dialogue::Entry::ACTIVATE_CHEATS: mainMenuWindow.isCheats = true; break; default: break; } } auto infoSize = ImVec2(width * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 3.5); auto infoPos = style.WindowPadding; if (isInfo) infoWindow.update(resources, gameData, character, infoSize, infoPos); auto textSize = ImVec2(width - style.WindowPadding.x * 2, ImGui::GetTextLineHeightWithSpacing() * 8); auto textPos = ImVec2(style.WindowPadding.x, height - textSize.y - style.WindowPadding.y); if (isText) textWindow.update(resources, character, textSize, textPos); auto mainSize = ImVec2((width * 0.5f) - style.WindowPadding.x * 3, height - textSize.y - (style.WindowPadding.y * 3)); auto mainPos = ImVec2(infoPos.x + infoSize.x + style.WindowPadding.x, style.WindowPadding.y); if (isMainMenu) mainMenuWindow.update(resources, character, gameData, textWindow, mainSize, mainPos); /* Inventory */ if (inventory.isQueued) { if (items.size() > Item::DEPLOYED_MAX) { inventory.isQueued = false; inventory.queuedItemType = Item::NONE; resources.sound_play(audio::MISS); } else { auto& type = inventory.queuedItemType; auto position = glm::vec2(((Item::SPAWN_X_MAX - Item::SPAWN_X_MIN) * math::random()) + Item::SPAWN_X_MIN, ((Item::SPAWN_Y_MAX - Item::SPAWN_Y_MIN) * math::random()) + Item::SPAWN_Y_MIN); items.emplace_back(&resources.anm2s[anm2::ITEMS], position, type); inventory.values[type]--; if (inventory.values[type] == 0) inventory.values.erase(type); type = Item::NONE; inventory.isQueued = false; } } /* Item */ Item::hoveredItem = nullptr; Item::heldItemPrevious = Item::heldItem; bool isSpeak{}; std::vector* dialogueIDs{}; for (int i = 0; i < items.size(); i++) { auto& item = items[i]; item.update(resources); if (&item == Item::heldItem && &item != &items.back()) { std::swap(items[i], items.back()); Item::heldItem = &items.back(); continue; } if (item.isToBeDeleted) { items.erase(items.begin() + i--); continue; } if (Item::queuedReturnItem == &item) { if (Item::queuedReturnItem->state == Item::DEFAULT) { inventory.values[item.type]++; resources.sound_play(audio::RETURN); } else resources.sound_play(audio::DISPOSE); items.erase(items.begin() + i--); Item::queuedReturnItem = nullptr; continue; } } auto& item = Item::heldItem; auto isHeldItemChanged = item != Item::heldItemPrevious; if (item && character.state != Character::STAGE_UP) { auto& type = item->type; auto& calories = Item::CALORIES[type]; auto& category = Item::CATEGORIES[type]; auto& position = item->position; auto caloriesChew = Item::CALORIES[type] / (Item::CHEW_COUNT_MAX + 1); auto digestionRateBonusChew = Item::DIGESTION_RATE_BONUSES[type] / (Item::CHEW_COUNT_MAX + 1); auto eatSpeedBonusChew = Item::EAT_SPEED_BONUSES[type] / (Item::CHEW_COUNT_MAX + 1); auto isAbleToEat = character.calories + caloriesChew <= character.max_capacity(); if (category == Item::FOOD) { auto isByMouth = math::is_point_in_rectf(character.mouth_rect_get(), position); if (character.state == Character::EAT && !isHeldItemChanged) { if (!isByMouth) { if (character.is_over_capacity()) { dialogueIDs = &dialogue.foodEasedIDs; character.state_set(Character::IDLE); } else { dialogueIDs = &dialogue.foodStolenIDs; character.state_set(Character::ANGRY); } isSpeak = true; } if (character.is_event(Character::EVENT_EAT)) { item->chewCount++; if (digestionRateBonusChew > 0 || digestionRateBonusChew < 0) { character.digestionRate = glm::clamp(Character::DIGESTION_RATE_MIN, character.digestionRate + digestionRateBonusChew, Character::DIGESTION_RATE_MAX); } if (eatSpeedBonusChew > 0) { character.eatSpeedMultiplier = glm::clamp(Character::EAT_SPEED_MULTIPLIER_MIN, character.eatSpeedMultiplier + eatSpeedBonusChew, Character::EAT_SPEED_MULTIPLIER_MAX); } character.calories += caloriesChew; character.totalCaloriesConsumed += caloriesChew; character.consume_played_event(); if (item->chewCount > Item::CHEW_COUNT_MAX) { character.isFinishedFood = true; character.foodItemsEaten++; item->isToBeDeleted = true; Item::heldItem = nullptr; cursor.play(Cursor::ANIMATION_DEFAULT); } else item->state_set(item->chewCount == 1 ? Item::CHEW_1 : item->chewCount == 2 ? Item::CHEW_2 : Item::DEFAULT); if (character.calories + caloriesChew >= character.max_capacity() && Item::heldItem) { character.state_set(Character::SHOCKED); dialogueIDs = &dialogue.fullIDs; isSpeak = true; } } } else if (character.state == Character::ANGRY) { } else { cursor.play(Cursor::ANIMATION_GRAB); if (!isAbleToEat) { character.state_set(Character::SHOCKED); if (caloriesChew > character.max_capacity()) dialogueIDs = &dialogue.capacityLowIDs; else dialogueIDs = &dialogue.fullIDs; } else if (character.is_over_capacity()) dialogueIDs = &dialogue.feedFullIDs; else { character.state_set(Character::EAGER); dialogueIDs = &dialogue.feedHungryIDs; } if (isHeldItemChanged) isSpeak = true; if (isAbleToEat && isByMouth) if (character.state != Character::EAT) character.state_set(Character::EAT); isRubbing = false; } } } else if (isHeldItemChanged && character.state != Character::ANGRY && character.state != Character::STAGE_UP) character.state_set(Character::IDLE); /* Character */ if (character.isFinishedFood && character.state == Character::IDLE) { if (!character.is_over_capacity()) dialogueIDs = &dialogue.eatHungryIDs; if (math::random_percent_roll(Character::BURP_BIG_CHANCE)) { character.state_set(Character::BURP_BIG); dialogueIDs = &dialogue.burpBigIDs; } else if (math::random_percent_roll(Character::BURP_SMALL_CHANCE)) { character.state_set(Character::BURP_SMALL); dialogueIDs = &dialogue.burpSmallIDs; } else if (!character.is_over_capacity() && math::random_percent_roll(Character::PAT_CHANCE)) character.state_set(Character::PAT); character.isFinishedFood = false; isSpeak = true; } /* Character */ if (character.isJustAppeared) { textWindow.set(dialogue.get("Start"), character); isText = true; character.isJustAppeared = false; } if (character.isJustFinalThreshold && !character.isFinalThresholdReached) { Item::heldItem = nullptr; Item::heldItemPrevious = nullptr; textWindow.set(dialogue.get("End"), character); items.clear(); character.isFinalThresholdReached = true; } /* Dialogue */ if (isSpeak && dialogueIDs) textWindow.set_random(*dialogueIDs, resources, character); /* Rubbing/Grabbing */ bool isHeadRubPossible = math::is_point_in_rectf(character.head_rect_get(), cursor.position) && !Item::heldItem; bool isBellyRubPossible = math::is_point_in_rectf(character.belly_rect_get(), cursor.position) && !Item::heldItem; bool isTailRubPossible = math::is_point_in_rectf(character.tail_rect_get(), cursor.position) && !Item::heldItem; auto isRubPossible = isHeadRubPossible || isBellyRubPossible || isTailRubPossible; if (isRubPossible) { if (!isRubbing) cursor.play(Cursor::ANIMATION_HOVER); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { isRubbing = true; resources.sound_play(audio::RUB); if (isHeadRubPossible) { character.state_set(Character::HEAD_RUB); cursor.play(Cursor::ANIMATION_RUB); } else if (isBellyRubPossible) { character.state_set(Character::BELLY_RUB); cursor.play(Cursor::ANIMATION_GRAB); } else if (isTailRubPossible) { character.state_set(Character::TAIL_RUB); cursor.play(Cursor::ANIMATION_GRAB); } } } else if (Item::hoveredItem) cursor.play(Cursor::ANIMATION_HOVER); else if (!Item::heldItem) cursor.play(Cursor::ANIMATION_DEFAULT); if (isRubbing) { if (isBellyRubPossible && !character.isDigesting) { auto delta = ImGui::GetIO().MouseDelta; auto power = fabs(delta.x) + fabs(delta.y); if (character.calories > 0) character.digestionProgress += power * Character::DIGESTION_RUB_BONUS; } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || !isRubPossible) { isRubbing = false; character.state_set(Character::IDLE); } } SDL_HideCursor(); cursor.update(); } void State::render() { SDL_GL_MakeCurrent(window, context); int width{}; int height{}; SDL_GetWindowSize(window, &width, &height); auto& textureShader = resources.shaders[shader::TEXTURE]; auto& rectShader = resources.shaders[shader::RECT]; canvas.bind(); canvas.clear(glm::vec4(0, 0, 0, 1)); auto bgModel = math::quad_model_get(vec2(width, height)); canvas.texture_render(textureShader, resources.textures[texture::BG].id, bgModel); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); character.render(textureShader, rectShader, canvas); for (auto& item : items) item.render(textureShader, rectShader, canvas); cursor.render(textureShader, rectShader, canvas); canvas.unbind(); SDL_GL_SwapWindow(window); } void State::loop() { auto currentTick = SDL_GetTicks(); auto currentUpdate = SDL_GetTicks(); if (currentUpdate - previousUpdate >= UPDATE_INTERVAL) { update(); render(); previousUpdate = currentUpdate; } if (currentTick - previousTick >= TICK_INTERVAL) { tick(); previousTick = currentTick; } SDL_Delay(1); } }