This commit is contained in:
2026-08-09 00:20:29 -04:00
parent a5904b917b
commit 019a2f73fb
78 changed files with 197829 additions and 191493 deletions
+6
View File
@@ -311,6 +311,12 @@ namespace game::state
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);
+55 -18
View File
@@ -15,10 +15,14 @@ namespace game::state::play
{
constexpr float CURSOR_SPEED = 9.0f;
constexpr float DIGEST_CURSOR_SPEED = 3.0f;
constexpr float CURSOR_ACCELERATION = 0.45f;
constexpr float DIGEST_CURSOR_ACCELERATION = 0.15f;
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 CURSOR_BRAKING_ACCELERATION_MULTIPLIER = 2.0f;
constexpr float CALORIE_EPSILON = 0.001f;
constexpr float CURSOR_TARGET_EPSILON = 0.001f;
constexpr float FOOD_TIE_PICK_WEIGHT = 1.0f;
glm::vec2 rect_center(glm::vec4 rect) { return {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; }
@@ -31,21 +35,51 @@ namespace game::state::play
value.z > 0.0f && value.w > 0.0f;
}
bool move_toward(Entity& cursor, glm::vec2 target, float speed)
bool move_toward(Entity& cursor, glm::vec2& velocity, glm::vec2 target, float speed, float acceleration)
{
if (!is_finite(cursor.position)) cursor.position = {};
if (!is_finite(cursor.position))
{
cursor.position = {};
velocity = {};
}
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)
if (distance <= CURSOR_TARGET_EPSILON)
{
cursor.position = target;
velocity = {};
return true;
}
cursor.position += delta / distance * speed;
auto targetSpeed = glm::min(speed, std::sqrt(CURSOR_BRAKING_ACCELERATION_MULTIPLIER * acceleration * distance));
auto desiredVelocity = delta / distance * targetSpeed;
auto velocityDelta = desiredVelocity - velocity;
auto velocityDeltaDistance = glm::length(velocityDelta);
if (!std::isfinite(velocityDeltaDistance))
velocity = {};
else if (velocityDeltaDistance > acceleration)
velocity += velocityDelta / velocityDeltaDistance * acceleration;
else
velocity = desiredVelocity;
auto speedCurrent = glm::length(velocity);
if (!std::isfinite(speedCurrent))
{
velocity = {};
return false;
}
if (speedCurrent >= distance)
{
cursor.position = target;
velocity = {};
return true;
}
cursor.position += velocity;
return false;
}
@@ -224,13 +258,14 @@ namespace game::state::play
return nullptr;
}
void state_reset(Entity& cursor, bool& isItemMouseHeld, bool& isDigestMouseHeld, int& targetItemIndex,
int& digestClickCooldown)
void state_reset(Entity& cursor, glm::vec2& cursorVelocity, bool& isItemMouseHeld, bool& isDigestMouseHeld,
int& targetItemIndex, int& digestClickCooldown)
{
isItemMouseHeld = false;
isDigestMouseHeld = false;
targetItemIndex = -1;
digestClickCooldown = 0;
cursorVelocity = {};
if (!is_finite(cursor.position)) cursor.position = {};
}
}
@@ -243,13 +278,13 @@ namespace game::state::play
if (!isEnabled)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
if (character.isStageUp || character.isJustStageUp || character.isJustStageFinal)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
@@ -257,7 +292,7 @@ namespace game::state::play
if (!is_food(character, itemManager))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
@@ -271,6 +306,8 @@ namespace game::state::play
auto speedScale = cursor_speed_scale_get(character);
auto cursorSpeed = CURSOR_SPEED * speedScale;
auto digestCursorSpeed = DIGEST_CURSOR_SPEED * speedScale;
auto cursorAcceleration = CURSOR_ACCELERATION * speedScale;
auto digestCursorAcceleration = DIGEST_CURSOR_ACCELERATION * speedScale;
auto eatRect = glm::vec4{};
auto isEatTarget = eat_rect_get(character, eatRect);
@@ -281,7 +318,7 @@ namespace game::state::play
{
isActive = false;
cursor.isVisible = false;
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto& schemaItem = schema.items[heldItem.schemaID];
@@ -290,7 +327,7 @@ namespace game::state::play
{
isActive = true;
cursor.isVisible = true;
move_toward(cursor, rect_center(eatRect), cursorSpeed);
move_toward(cursor, cursorVelocity, rect_center(eatRect), cursorSpeed, cursorAcceleration);
itemInput.isMouseLeftDown = true;
itemManager.inputOverride = itemInput;
@@ -319,13 +356,13 @@ namespace game::state::play
auto rect = item.rect();
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
isActive = true;
cursor.isVisible = true;
move_toward(cursor, rect_center(rect), cursorSpeed);
move_toward(cursor, cursorVelocity, rect_center(rect), cursorSpeed, cursorAcceleration);
auto isInItemRect = math::is_point_in_rectf(rect, cursor.position);
@@ -341,7 +378,7 @@ namespace game::state::play
auto isWorldFood = is_world_food(character, itemManager);
if (character.calories <= CALORIE_EPSILON && !isWorldFood)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
@@ -350,20 +387,20 @@ namespace game::state::play
auto* interactArea = digestion_area_get(character);
if (!interactArea)
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
return;
}
auto rect = character.null_frame_rect(character.data.null_id_get(interactArea->null));
if (!is_finite(rect))
{
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, 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);
auto isAtTarget = move_toward(cursor, cursorVelocity, targetWorld, digestCursorSpeed, digestCursorAcceleration);
isActive = true;
cursor.isVisible = true;
@@ -394,7 +431,7 @@ namespace game::state::play
return;
}
state_reset(cursor, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
state_reset(cursor, cursorVelocity, isItemMouseHeld, isDigestMouseHeld, targetItemIndex, digestClickCooldown);
}
void Autofeed::toggle() { isEnabled = !isEnabled; }
+1
View File
@@ -14,6 +14,7 @@ namespace game::state::play
int targetItemIndex{-1};
int digestClickCooldown{};
float digestAngle{};
glm::vec2 cursorVelocity{};
bool isEnabled{};
bool isActive{};
+6 -1
View File
@@ -226,6 +226,7 @@ namespace game::state::play
if (isItemHovered != isItemHoveredPrevious && !isItemHovered) isJustItemHoveredStopped = true;
if (spawnDelayTicks > 0) spawnDelayTicks--;
if (discoveredItemIDs.size() != schema.items.size()) discoveredItemIDs.resize(schema.items.size());
newItemFoundCount = 0;
for (auto& id : queuedItemIDs)
{
@@ -237,7 +238,11 @@ namespace game::state::play
auto& anm2 = itemSchema.anm2s.at(id);
items.emplace_back(anm2, position, id);
character.totalItemsSpawned++;
if (id >= 0 && id < (int)discoveredItemIDs.size()) discoveredItemIDs[id] = true;
if (id >= 0 && id < (int)discoveredItemIDs.size())
{
if (!discoveredItemIDs[id]) newItemFoundCount++;
discoveredItemIDs[id] = true;
}
particles_queue(itemSchema, resource::xml::Schema::Element::SUMMON, position);
}
queuedItemIDs.clear();
+1
View File
@@ -44,6 +44,7 @@ namespace game::state::play
std::vector<int> queuedItemIDs{};
std::vector<bool> discoveredItemIDs{};
int newItemFoundCount{};
int spawnDelayTicks{};
struct Particle
{
+24 -12
View File
@@ -54,15 +54,15 @@ namespace game::state::play::menu
{
auto* settings = resources.settings.root();
auto system = settings->measurementSystem == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
auto weight = character.weight_get(system);
auto weight = measurement::weight_display_get(character.weight, 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 stageWeight = measurement::weight_display_get(character.stage_threshold_get(stage), system);
auto stageNextWeight = measurement::weight_display_get(character.stage_threshold_next_get(), system);
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
auto weightString = util::string::format_commas(weight.value, measurement::WEIGHT_DISPLAY_DECIMAL_DIGITS) + " " +
weight.unit;
imgui::text_unformatted_fit_draw(weightString.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, Font::HEADER_2),
Font::HEADER_2);
ImGui::SetItemTooltip("%s", weightString.c_str());
@@ -81,9 +81,9 @@ namespace game::state::play::menu
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::Text(strings.get(Strings::InfoStageStartFormat).c_str(), stageWeight.value, stageWeight.unit);
ImGui::Text(strings.get(Strings::InfoStageCurrentFormat).c_str(), weight.value, weight.unit);
ImGui::Text(strings.get(Strings::InfoStageNextFormat).c_str(), stageNextWeight.value, stageNextWeight.unit);
}
ImGui::PopStyleColor();
ImGui::EndTooltip();
@@ -100,14 +100,26 @@ namespace game::state::play::menu
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);
auto capacityMax = character.max_capacity();
ImGui::PushStyleColor(ImGuiCol_Text, caloriesColor);
auto caloriesString = std::format("{:.0f} kcal / {:.0f} kcal", calories,
character.is_over_capacity() ? character.max_capacity() : character.capacity);
auto caloriesString = std::format("{:.0f} / {:.0f} kcal", calories, capacity);
auto caloriesTooltipStart = std::format("{:.0f} / {:.0f} ", calories, capacity);
auto caloriesTooltipEffective = std::format("({:.0f})", capacityMax);
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();
if (ImGui::BeginItemTooltip())
{
ImGui::TextUnformatted(caloriesTooltipStart.c_str());
ImGui::SameLine(ZERO_FLOAT, ZERO_FLOAT);
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::TextUnformatted(caloriesTooltipEffective.c_str());
ImGui::PopStyleColor();
ImGui::SameLine(ZERO_FLOAT, ZERO_FLOAT);
ImGui::TextUnformatted(" kcal");
ImGui::EndTooltip();
}
auto digestionProgress = character.isDigesting
? (float)character.digestionTimer / character.data.root()->digestionTimerMax
@@ -171,7 +183,7 @@ namespace game::state::play::menu
ImGui::PopFont();
};
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::PushFont(ImGui::GetFont(), resource::Font::HUGE);
ImGui::SeparatorText(character.data.root()->name.c_str());
ImGui::PopFont();
+6 -3
View File
@@ -5,6 +5,7 @@
#include "../../util/imgui/style.hpp"
#include "../../util/imgui/widget.hpp"
#include "../../util/measurement.hpp"
#include "../../util/string.hpp"
#include "../../util/vector.hpp"
using namespace game::util;
@@ -41,7 +42,7 @@ namespace game::state::select
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;
auto weight = savedCharacter ? (double)savedCharacter->weightKilograms : (double)character->weightKilograms;
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_3);
@@ -74,8 +75,10 @@ namespace game::state::select
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
system == IMPERIAL ? "lbs" : "kg");
auto weightDisplay = weight_display_get(weight, system);
auto weightString =
string::format_commas(weightDisplay.value, WEIGHT_DISPLAY_DECIMAL_DIGITS) + " " + weightDisplay.unit;
ImGui::Text("Weight: %s", weightString.c_str());
auto stages = resources.characterPreviews[characterIndex].get_all(resource::xml::Schema::Element::STAGE);
ImGui::Text("Stages: %i", (int)stages.size() + 1);