a (not so) mini update

This commit is contained in:
2026-04-01 02:08:50 -04:00
parent fb6f902f28
commit af04a9b313
64 changed files with 1158 additions and 583 deletions

View File

@@ -237,15 +237,23 @@ namespace game::entity
if (!queuedPlay.empty())
{
auto& index = animationMap.at(queuedPlay.animation);
if (queuedPlay.isPlayAfterAnimation)
nextQueuedPlay = queuedPlay;
else if ((state == STOPPED || index != animationIndex) && currentQueuedPlay.isInterruptible)
if (!animationMap.contains(queuedPlay.animation))
{
play(queuedPlay.animation, queuedPlay.mode, queuedPlay.time, queuedPlay.speedMultiplier);
currentQueuedPlay = queuedPlay;
logger.error(std::string("Animation \"" + queuedPlay.animation + "\" does not exist! Unable to play!"));
if (!defaultAnimation.empty()) queue_default_animation();
}
else
{
auto& index = animationMap.at(queuedPlay.animation);
if (queuedPlay.isPlayAfterAnimation)
nextQueuedPlay = queuedPlay;
else if ((state == STOPPED || index != animationIndex) && currentQueuedPlay.isInterruptible)
{
play(queuedPlay.animation, queuedPlay.mode, queuedPlay.time, queuedPlay.speedMultiplier);
currentQueuedPlay = queuedPlay;
}
queuedPlay = QueuedPlay{};
}
queuedPlay = QueuedPlay{};
}
auto animation = animation_get();

View File

@@ -22,10 +22,10 @@ namespace game::entity
auto& save = data.save;
auto saveIsValid = save.is_valid();
capacity = saveIsValid ? save.capacity : data.capacity;
weight = saveIsValid ? save.weight : data.weight;
digestionRate = saveIsValid ? save.digestionRate : data.digestionRate;
eatSpeed = saveIsValid ? save.eatSpeed : data.eatSpeed;
capacity = saveIsValid ? save.capacity : (double)data.capacity;
weight = saveIsValid ? save.weight : (double)data.weight;
digestionRate = saveIsValid ? save.digestionRate : (double)data.digestionRate;
eatSpeed = saveIsValid ? save.eatSpeed : (double)data.eatSpeed;
calories = saveIsValid ? save.calories : 0;
@@ -77,7 +77,7 @@ namespace game::entity
float Character::weight_get(measurement::System system)
{
return system == measurement::IMPERIAL ? weight * (float)measurement::KG_TO_LB : weight;
return system == measurement::IMPERIAL ? weight * (double)measurement::KG_TO_LB : weight;
}
int Character::stage_from_weight_get(float checkWeight) const
@@ -99,7 +99,7 @@ namespace game::entity
{
if (stageIndex == -1) stageIndex = this->stage;
float threshold = data.weight;
double threshold = data.weight;
if (!data.stages.empty())
{
@@ -111,7 +111,7 @@ namespace game::entity
threshold = data.stages[stageIndex - 1].threshold;
}
return system == measurement::IMPERIAL ? threshold * (float)measurement::KG_TO_LB : threshold;
return system == measurement::IMPERIAL ? threshold * (double)measurement::KG_TO_LB : threshold;
}
float Character::stage_threshold_next_get(measurement::System system) const
@@ -122,23 +122,35 @@ namespace game::entity
float Character::stage_progress_get()
{
auto currentStage = stage_get();
if (currentStage >= stage_max_get()) return 1.0f;
if (currentStage >= stage_max_get()) return 1.0;
auto currentThreshold = stage_threshold_get(currentStage);
auto nextThreshold = stage_threshold_get(currentStage + 1);
if (nextThreshold <= currentThreshold) return 1.0f;
if (nextThreshold <= currentThreshold) return 1.0;
return (weight - currentThreshold) / (nextThreshold - currentThreshold);
}
float Character::digestion_rate_get() { return digestionRate * 60; }
float Character::digestion_rate_get() { return digestionRate * 60.0; }
float Character::max_capacity() const { return capacity * data.capacityMaxMultiplier; }
bool Character::is_over_capacity() const { return calories > capacity; }
bool Character::is_max_capacity() const { return calories >= max_capacity(); }
float Character::capacity_percent_get() const { return calories / max_capacity(); }
std::string Character::animation_name_convert(const std::string& name) { return std::format("{}{}", name, stage); }
std::string Character::animation_append_id_get() const
{
if (stage <= 0) return {};
auto stageIndex = stage - 1;
if (stageIndex < 0 || stageIndex >= (int)data.stages.size()) return {};
return data.stages[stageIndex].animationAppendID;
}
std::string Character::animation_name_convert(const std::string& name)
{
auto appendID = animation_append_id_get();
return appendID.empty() ? name : name + appendID;
}
void Character::play_convert(const std::string& animation, Mode playMode, float startAtTime,
float speedMultiplierValue)
{
@@ -148,9 +160,8 @@ namespace game::entity
void Character::expand_areas_apply()
{
auto stageProgress = stage_progress_get();
auto capacityProgress = isDigesting
? (float)calories / max_capacity() * (float)digestionTimer / data.digestionTimerMax
: calories / max_capacity();
auto capacityProgress = isDigesting ? (double)calories / max_capacity() * (double)digestionTimer / data.digestionTimerMax
: calories / max_capacity();
for (int i = 0; i < (int)data.expandAreas.size(); i++)
{
@@ -158,8 +169,8 @@ namespace game::entity
auto& overrideLayer = overrides[expandAreaOverrideLayerIDs[i]];
auto& overrideNull = overrides[expandAreaOverrideNullIDs[i]];
auto stageScaleAdd = ((expandArea.scaleAdd * stageProgress) * 0.5f);
auto capacityScaleAdd = ((expandArea.scaleAdd * capacityProgress) * 0.5f);
auto stageScaleAdd = ((double)expandArea.scaleAdd * stageProgress) * 0.5;
auto capacityScaleAdd = ((double)expandArea.scaleAdd * capacityProgress) * 0.5;
auto scaleAdd =
glm::clamp(glm::vec2(), glm::vec2(stageScaleAdd + capacityScaleAdd), glm::vec2(expandArea.scaleAdd));
@@ -201,20 +212,21 @@ namespace game::entity
if (digestionTimer <= 0)
{
auto increment = calories * data.caloriesToKilogram;
auto nextWeight = glm::clamp(weight + increment, data.weight, data.weightMax);
if (is_over_capacity())
{
auto capacityMaxCalorieDifference = (calories - capacity);
auto overCapacityPercent = capacityMaxCalorieDifference / (max_capacity() - capacity);
auto capacityIncrement =
(overCapacityPercent * data.capacityIfOverStuffedOnDigestBonus) * capacityMaxCalorieDifference;
capacity = glm::clamp(data.capacityMin, capacity + capacityIncrement, data.capacityMax);
(double)((overCapacityPercent * data.capacityIfOverStuffedOnDigestBonus) * capacityMaxCalorieDifference);
capacity = glm::clamp(capacity + capacityIncrement, (double)data.capacityMin, (double)data.capacityMax);
}
totalCaloriesConsumed += calories;
calories = 0;
if (auto nextStage = stage_from_weight_get(weight + increment); nextStage > stage_from_weight_get(weight))
if (auto nextStage = stage_from_weight_get((double)nextWeight); nextStage > stage_from_weight_get(weight))
{
queuedPlay = QueuedPlay{};
nextQueuedPlay = QueuedPlay{};
@@ -226,7 +238,7 @@ namespace game::entity
else
isJustDigested = true;
weight += increment;
weight = (double)nextWeight;
isDigesting = false;
digestionTimer = data.digestionTimerMax;
@@ -258,7 +270,7 @@ namespace game::entity
auto talk_reset = [&]()
{
isTalking = false;
talkTimer = 0.0f;
talkTimer = 0.0;
talkOverride.frame = FrameOptional();
};
@@ -277,9 +289,9 @@ namespace game::entity
talkOverride.frame.size = frame.size;
talkOverride.frame.pivot = frame.pivot;
talkTimer += 1.0f;
talkTimer += 1.0;
if (talkTimer > animationTalkDurations.at(animationIndex)) talkTimer = 0.0f;
if (talkTimer > animationTalkDurations.at(animationIndex)) talkTimer = 0.0;
}
else
talk_reset();
@@ -301,7 +313,7 @@ namespace game::entity
auto blink_reset = [&]()
{
isBlinking = false;
blinkTimer = 0.0f;
blinkTimer = 0.0;
blinkOverride.frame = FrameOptional();
};
@@ -320,7 +332,7 @@ namespace game::entity
blinkOverride.frame.size = frame.size;
blinkOverride.frame.pivot = frame.pivot;
blinkTimer += 1.0f;
blinkTimer += 1.0;
if (blinkTimer >= blinkDuration) blink_reset();
}
@@ -348,14 +360,6 @@ namespace game::entity
{is_over_capacity() && !data.animations.idleFull.empty() ? data.animations.idleFull : data.animations.idle});
}
void Character::queue_interact_area_animation(resource::xml::Character::InteractArea& interactArea)
{
if (isStageUp) return;
if (interactArea.animation.empty()) return;
queue_play({is_over_capacity() && !interactArea.animationFull.empty() ? interactArea.animationFull
: interactArea.animation});
}
void Character::spritesheet_set(SpritesheetType type)
{
switch (type)

View File

@@ -85,9 +85,9 @@ namespace game::entity
void tick();
void play_convert(const std::string&, Mode = PLAY, float time = 0.0f, float speedMultiplier = 1.0f);
void queue_idle_animation();
void queue_interact_area_animation(resource::xml::Character::InteractArea&);
void queue_play(QueuedPlay);
std::string animation_append_id_get() const;
std::string animation_name_convert(const std::string& name);
};
}

View File

@@ -10,9 +10,9 @@ using namespace glm;
namespace game::entity
{
Item::Item(Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _chewCount, int _animationIndex, glm::vec2 _velocity,
Item::Item(Anm2 _anm2, glm::ivec2 _position, int _schemaID, int _durability, int _animationIndex, glm::vec2 _velocity,
float _rotation)
: Actor(_anm2, _position, SET, 0.0f, _animationIndex), schemaID(_schemaID), chewCount(_chewCount),
: Actor(_anm2, _position, SET, 0.0f, _animationIndex), schemaID(_schemaID), durability(_durability),
velocity(_velocity)
{

View File

@@ -14,12 +14,12 @@ namespace game::entity
int schemaID{};
int rotationOverrideID{};
int chewCount{};
int durability{};
glm::vec2 velocity{};
float angularVelocity{};
Item(resource::xml::Anm2, glm::ivec2 position, int id, int chewCount = 0, int animationIndex = -1,
Item(resource::xml::Anm2, glm::ivec2 position, int id, int durability = 0, int animationIndex = -1,
glm::vec2 velocity = {}, float rotation = 0.0f);
void update();
};

View File

@@ -54,7 +54,7 @@ namespace game
logger.info("Initializing...");
if (!PHYSFS_init((argc > 0 && argv && argv[0]) ? argv[0] : "snivy"))
if (!PHYSFS_init((argc > 0 && argv && argv[0]) ? argv[0] : "shweets-sim"))
{
logger.fatal(std::format("Failed to initialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())));
isError = true;
@@ -85,12 +85,12 @@ namespace game
#ifdef __EMSCRIPTEN__
static constexpr glm::vec2 SIZE = {1600, 900};
window = SDL_CreateWindow("Feed Snivy", SIZE.x, SIZE.y, SDL_WINDOW_OPENGL);
window = SDL_CreateWindow("Shweet's Sim", SIZE.x, SIZE.y, SDL_WINDOW_OPENGL);
#else
SDL_PropertiesID windowProperties = SDL_CreateProperties();
SDL_SetStringProperty(windowProperties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Feed Snivy");
SDL_SetStringProperty(windowProperties, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "Shweet's Sim");
SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, (long)settings.windowSize.x);
SDL_SetNumberProperty(windowProperties, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, (long)settings.windowSize.y);

View File

@@ -14,11 +14,9 @@ namespace game::resource
public:
static constexpr auto NORMAL = 20;
static constexpr auto ABOVE_AVERAGE = 24;
static constexpr auto BIG = 30;
static constexpr auto HEADER_1 = 24;
static constexpr auto HEADER_2 = 30;
static constexpr auto HEADER_3 = 40;
static constexpr auto HEADER_2 = 50;
static constexpr auto HEADER_1 = 60;
Font() = default;
Font(const std::filesystem::path&, float = NORMAL);

View File

@@ -43,28 +43,31 @@ namespace game::resource::xml
query_vec3(root, "ColorR", "ColorG", "ColorB", color);
root->QueryFloatAttribute("Weight", &weight);
root->QueryDoubleAttribute("Weight", &weight);
root->QueryDoubleAttribute("WeightMax", &weightMax);
root->QueryFloatAttribute("Capacity", &capacity);
root->QueryFloatAttribute("CapacityMin", &capacityMin);
root->QueryFloatAttribute("CapacityMax", &capacityMax);
root->QueryFloatAttribute("CapacityMaxMultiplier", &capacityMaxMultiplier);
root->QueryFloatAttribute("CapacityIfOverStuffedOnDigestBonus", &capacityIfOverStuffedOnDigestBonus);
root->QueryDoubleAttribute("Capacity", &capacity);
root->QueryDoubleAttribute("CapacityMin", &capacityMin);
root->QueryDoubleAttribute("CapacityMax", &capacityMax);
root->QueryDoubleAttribute("CapacityMaxMultiplier", &capacityMaxMultiplier);
root->QueryDoubleAttribute("CapacityIfOverStuffedOnDigestBonus", &capacityIfOverStuffedOnDigestBonus);
root->QueryFloatAttribute("CaloriesToKilogram", &caloriesToKilogram);
root->QueryDoubleAttribute("CaloriesToKilogram", &caloriesToKilogram);
root->QueryFloatAttribute("DigestionRate", &digestionRate);
root->QueryFloatAttribute("DigestionRateMin", &digestionRateMin);
root->QueryFloatAttribute("DigestionRateMax", &digestionRateMax);
root->QueryDoubleAttribute("DigestionRate", &digestionRate);
root->QueryDoubleAttribute("DigestionRateMin", &digestionRateMin);
root->QueryDoubleAttribute("DigestionRateMax", &digestionRateMax);
root->QueryIntAttribute("DigestionTimerMax", &digestionTimerMax);
root->QueryFloatAttribute("EatSpeed", &eatSpeed);
root->QueryFloatAttribute("EatSpeedMin", &eatSpeedMin);
root->QueryFloatAttribute("EatSpeedMax", &eatSpeedMax);
root->QueryDoubleAttribute("EatSpeed", &eatSpeed);
root->QueryDoubleAttribute("EatSpeedMin", &eatSpeedMin);
root->QueryDoubleAttribute("EatSpeedMax", &eatSpeedMax);
root->QueryFloatAttribute("BlinkChance", &blinkChance);
root->QueryFloatAttribute("GurgleChance", &gurgleChance);
root->QueryFloatAttribute("GurgleCapacityMultiplier", &gurgleCapacityMultiplier);
root->QueryDoubleAttribute("BlinkChance", &blinkChance);
root->QueryDoubleAttribute("GurgleChance", &gurgleChance);
root->QueryDoubleAttribute("GurgleCapacityMultiplier", &gurgleCapacityMultiplier);
root->QueryIntAttribute("TextBlipPeriodBase", &textBlipPeriodBase);
auto dialoguePath = physfs::Path(archive + "/" + "dialogue.xml");
@@ -100,6 +103,7 @@ namespace game::resource::xml
if (auto element = root->FirstChildElement("Sounds"))
{
query_sound_entry_collection(element, "Blip", archive, soundRootPath, sounds.blip);
query_sound_entry_collection(element, "Digest", archive, soundRootPath, sounds.digest);
query_sound_entry_collection(element, "Gurgle", archive, soundRootPath, sounds.gurgle);
}
@@ -127,6 +131,7 @@ namespace game::resource::xml
child->QueryFloatAttribute("Threshold", &stage.threshold);
child->QueryIntAttribute("AreaID", &stage.areaID);
dialogue.query_pool_id(child, "DialoguePoolID", stage.pool.id);
query_string_attribute(child, "AnimationAppendID", &stage.animationAppendID);
stages.emplace_back(std::move(stage));
}
}
@@ -178,12 +183,11 @@ namespace game::resource::xml
if (child->FindAttribute("Layer")) query_layer_id(child, "Layer", anm2, interactArea.layerID);
query_null_id(child, "Null", anm2, interactArea.nullID);
query_string_attribute(child, "Animation", &interactArea.animation);
query_string_attribute(child, "AnimationFull", &interactArea.animationFull);
query_string_attribute(child, "AnimationCursorHover", &interactArea.animationCursorHover);
query_string_attribute(child, "AnimationCursorActive", &interactArea.animationCursorActive);
query_sound_entry_collection(child, "Sound", archive, soundRootPath, interactArea.sound, "Path");
dialogue.query_pool_id(child, "DialoguePoolID", interactArea.pool.id);
dialogue.query_pool_id(child, "DialoguePoolIDFull", interactArea.poolFull.id);
query_bool_attribute(child, "IsHold", &interactArea.isHold);
child->QueryFloatAttribute("DigestionBonusRub", &interactArea.digestionBonusRub);
child->QueryFloatAttribute("DigestionBonusClick", &interactArea.digestionBonusClick);
@@ -225,6 +229,9 @@ namespace game::resource::xml
else
logger.warning(std::format("No character skill_check.xml file found: {}", path.string()));
if (auto stringsPath = physfs::Path(archive + "/" + "strings.xml"); stringsPath.is_valid())
strings = Strings(stringsPath);
logger.info(std::format("Initialized character: {}", name));
this->path = path;

View File

@@ -11,8 +11,9 @@
#include "dialogue.hpp"
#include "item.hpp"
#include "menu.hpp"
#include "skill_check.hpp"
#include "save.hpp"
#include "skill_check.hpp"
#include "strings.hpp"
namespace game::resource::xml
{
@@ -24,6 +25,7 @@ namespace game::resource::xml
float threshold{};
int areaID{};
Dialogue::PoolReference pool{-1};
std::string animationAppendID{};
};
struct EatArea
@@ -42,8 +44,6 @@ namespace game::resource::xml
struct InteractArea
{
std::string animation{};
std::string animationFull{};
std::string animationCursorActive{};
std::string animationCursorHover{};
SoundEntryCollection sound{};
@@ -53,6 +53,7 @@ namespace game::resource::xml
int typeID{-1};
bool isHold{};
Dialogue::PoolReference pool{-1};
Dialogue::PoolReference poolFull{-1};
float digestionBonusRub{};
float digestionBonusClick{};
@@ -73,6 +74,7 @@ namespace game::resource::xml
struct Sounds
{
SoundEntryCollection blip{};
SoundEntryCollection gurgle{};
SoundEntryCollection digest{};
};
@@ -98,6 +100,7 @@ namespace game::resource::xml
Menu menuSchema{};
Cursor cursorSchema{};
SkillCheck skillCheckSchema{};
Strings strings{};
Save save{};
@@ -119,23 +122,25 @@ namespace game::resource::xml
std::string name{};
std::filesystem::path path{};
float weight{50};
float capacity{2000.0f};
float capacityMin{2000.0f};
float capacityMax{99999.0f};
float capacityMaxMultiplier{1.5f};
float capacityIfOverStuffedOnDigestBonus{0.25f};
float caloriesToKilogram{1000.0f};
float digestionRate{0.05f};
float digestionRateMin{0.0f};
float digestionRateMax{0.25f};
double weight{50};
double weightMax{1000};
double capacity{2000.0};
double capacityMin{2000.0};
double capacityMax{99999.0};
double capacityMaxMultiplier{1.5};
double capacityIfOverStuffedOnDigestBonus{0.25};
double caloriesToKilogram{1000.0};
double digestionRate{0.05};
double digestionRateMin{0.0};
double digestionRateMax{0.25};
int digestionTimerMax{60};
float eatSpeed{1.0f};
float eatSpeedMin{1.0f};
float eatSpeedMax{3.0f};
float blinkChance{1.0f};
float gurgleChance{1.0f};
float gurgleCapacityMultiplier{1.0f};
int textBlipPeriodBase{3};
double eatSpeed{1.0};
double eatSpeedMin{1.0};
double eatSpeedMax{3.0};
double blinkChance{1.0};
double gurgleChance{1.0};
double gurgleCapacityMultiplier{1.0};
Dialogue::PoolReference pool{-1};
Character() = default;

View File

@@ -41,7 +41,7 @@ namespace game::resource::xml
query_string_attribute(root, "Name", &name);
query_string_attribute(root, "Description", &description);
query_string_attribute(root, "Author", &author);
query_string_attribute(root, "Credits", &credits);
query_vec3(root, "ColorR", "ColorG", "ColorB", color);
root->QueryFloatAttribute("Weight", &weight);

View File

@@ -28,7 +28,7 @@ namespace game::resource::xml
int stages{1};
std::string name{};
std::string author{};
std::string credits{};
std::string description{};
std::filesystem::path path{};
float weight{50};

View File

@@ -4,6 +4,7 @@
#include "../../log.hpp"
#include "../../util/math.hpp"
#include <algorithm>
#include <format>
using namespace tinyxml2;
@@ -98,6 +99,8 @@ namespace game::resource::xml
id++;
}
entrySelectionOrder.assign(entries.size(), -1);
}
if (auto element = root->FirstChildElement("Pools"))
@@ -158,15 +161,32 @@ namespace game::resource::xml
}
}
int Dialogue::Pool::get() const
int Dialogue::entry_pick(Pool& pool)
{
if (this->empty()) return -1;
auto index = rand() % this->size();
return this->at(index);
if (pool.empty()) return -1;
std::vector<int> unselected{};
for (auto id : pool)
if (id >= 0 && id < (int)entrySelectionOrder.size() && entrySelectionOrder[id] < 0) unselected.emplace_back(id);
std::vector<int> candidates = unselected.empty() ? std::vector<int>(pool.begin(), pool.end()) : unselected;
if (candidates.empty()) return -1;
auto oldestOrder = entrySelectionOrder[candidates.front()];
for (auto id : candidates)
oldestOrder = std::min(oldestOrder, entrySelectionOrder[id]);
std::vector<int> oldestCandidates{};
for (auto id : candidates)
if (entrySelectionOrder[id] == oldestOrder) oldestCandidates.emplace_back(id);
auto pickedID = oldestCandidates.at(rand() % oldestCandidates.size());
entrySelectionOrder[pickedID] = selectionCounter++;
return pickedID;
}
Dialogue::Entry* Dialogue::get(int id) { return &entries.at(id); }
Dialogue::Entry* Dialogue::get(Dialogue::EntryReference& entry) { return &entries.at(entry.id); }
Dialogue::Entry* Dialogue::get(const std::string& string) { return &entries.at(entryIDMap.at(string)); }
Dialogue::Entry* Dialogue::get(Dialogue::PoolReference& pool) { return &entries.at(pools.at(pool.id).get()); }
Dialogue::Entry* Dialogue::get(Dialogue::Pool& pool) { return &entries.at(pool.get()); }
Dialogue::Entry* Dialogue::get(Dialogue::PoolReference& pool) { return &entries.at(entry_pick(pools.at(pool.id))); }
Dialogue::Entry* Dialogue::get(Dialogue::Pool& pool) { return &entries.at(entry_pick(pool)); }
}

View File

@@ -5,6 +5,7 @@
#include <string>
#include <map>
#include <vector>
#include "../../util/physfs.hpp"
@@ -45,11 +46,7 @@ namespace game::resource::xml
inline bool is_valid() const { return id != -1; };
};
class Pool : public std::vector<int>
{
public:
int get() const;
};
class Pool : public std::vector<int> {};
std::map<std::string, int> entryIDMap;
std::map<int, std::string> entryIDMapReverse;
@@ -57,6 +54,8 @@ namespace game::resource::xml
std::vector<Pool> pools{};
std::map<std::string, int> poolMap{};
std::vector<long long> entrySelectionOrder{};
long long selectionCounter{};
EntryReference start{-1};
EntryReference end{-1};
@@ -83,8 +82,9 @@ namespace game::resource::xml
Entry* get(Dialogue::EntryReference&);
Entry* get(Dialogue::Pool&);
Entry* get(Dialogue::PoolReference&);
int entry_pick(Pool&);
void query_entry_id(tinyxml2::XMLElement* element, const char* name, int& id);
void query_pool_id(tinyxml2::XMLElement* element, const char* name, int& id);
inline bool is_valid() const { return isValid; };
};
}
}

View File

@@ -110,7 +110,8 @@ namespace game::resource::xml
std::string itemTextureRootPath{};
query_string_attribute(element, "TextureRootPath", &itemTextureRootPath);
element->QueryIntAttribute("ChewCount", &chewCount);
element->QueryIntAttribute("Durability", &durability);
if (element->FindAttribute("ChewCount")) element->QueryIntAttribute("ChewCount", &durability);
element->QueryIntAttribute("QuantityMax", &quantityMax);
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
@@ -121,11 +122,14 @@ namespace game::resource::xml
query_string_attribute(child, "Description", &item.description);
query_float_optional_attribute(child, "Calories", item.calories);
query_float_optional_attribute(child, "CapacityBonus", item.capacityBonus);
query_optional_vec3(child, "ColorR", "ColorG", "ColorB", item.color);
query_float_optional_attribute(child, "DigestionBonus", item.digestionBonus);
query_float_optional_attribute(child, "EatSpeedBonus", item.eatSpeedBonus);
query_float_optional_attribute(child, "Gravity", item.gravity);
query_int_optional_attribute(child, "ChewCount", item.chewCount);
query_int_optional_attribute(child, "Durability", item.durability);
if (!item.durability.has_value()) query_int_optional_attribute(child, "ChewCount", item.durability);
if (child->FindAttribute("UpgradeID"))
{

View File

@@ -6,6 +6,8 @@
#include <unordered_set>
#include <vector>
#include <glm/glm.hpp>
#include "../audio.hpp"
#include "anm2.hpp"
#include "sound_entry.hpp"
@@ -46,10 +48,12 @@ namespace game::resource::xml
std::optional<int> upgradeID{};
std::optional<int> flavorID;
std::optional<float> calories{};
std::optional<float> capacityBonus{};
std::optional<glm::vec3> color{};
std::optional<float> eatSpeedBonus{};
std::optional<float> digestionBonus{};
std::optional<float> gravity{};
std::optional<int> chewCount{};
std::optional<int> durability{};
bool isSkillCheckReward{};
bool isToggleSpritesheet{};
};
@@ -90,7 +94,7 @@ namespace game::resource::xml
Animations animations{};
Sounds sounds{};
Anm2 baseAnm2{};
int chewCount{2};
int durability{2};
int quantityMax{99};
bool isValid{};

View File

@@ -91,7 +91,8 @@ namespace game::resource::xml
{
Item item{};
child->QueryIntAttribute("ID", &item.id);
child->QueryIntAttribute("ChewCount", &item.chewCount);
child->QueryIntAttribute("Durability", &item.durability);
if (child->FindAttribute("ChewCount")) child->QueryIntAttribute("ChewCount", &item.durability);
child->QueryFloatAttribute("PositionX", &item.position.x);
child->QueryFloatAttribute("PositionY", &item.position.y);
child->QueryFloatAttribute("VelocityX", &item.velocity.x);
@@ -162,7 +163,7 @@ namespace game::resource::xml
auto itemElement = itemsElement->InsertNewChildElement("Item");
itemElement->SetAttribute("ID", item.id);
itemElement->SetAttribute("ChewCount", item.chewCount);
itemElement->SetAttribute("Durability", item.durability);
itemElement->SetAttribute("PositionX", item.position.x);
itemElement->SetAttribute("PositionY", item.position.y);
itemElement->SetAttribute("VelocityX", item.velocity.x);

View File

@@ -15,7 +15,7 @@ namespace game::resource::xml
struct Item
{
int id{};
int chewCount{};
int durability{};
glm::vec2 position{};
glm::vec2 velocity{};
float rotation{};

View File

@@ -23,7 +23,7 @@ namespace game::resource::xml
int volume{50};
bool isUseCharacterColor{true};
glm::vec3 color{0.120f, 0.515f, 0.115f};
glm::vec3 color{0.120f, 0.120f, 0.120f};
glm::ivec2 windowSize{1600, 900};
glm::vec2 windowPosition{};

View File

@@ -0,0 +1,48 @@
#include "strings.hpp"
#include "../../log.hpp"
#include "util.hpp"
#include <format>
using namespace tinyxml2;
namespace game::resource::xml
{
namespace
{
std::string definition_element_name_get(const Strings::Definition& definition)
{
std::string name = definition.attribute;
if (name.rfind("Text", 0) == 0) name.replace(0, 4, "String");
return name;
}
}
Strings::Strings()
{
for (int i = 0; i < Count; i++)
values[i] = definitions[i].fallback;
}
Strings::Strings(const util::physfs::Path& path)
{
for (int i = 0; i < Count; i++)
values[i] = definitions[i].fallback;
XMLDocument document;
if (document_load(path, document) != XML_SUCCESS) return;
auto root = document.RootElement();
if (!root) return;
for (int i = 0; i < Count; i++)
if (auto element = root->FirstChildElement(definition_element_name_get(definitions[i]).c_str()))
query_string_attribute(element, "Text", &values[i]);
isValid = true;
logger.info(std::format("Initialized strings: {}", path.c_str()));
}
const std::string& Strings::get(Type type) const { return values[type]; }
}

View File

@@ -0,0 +1,159 @@
#pragma once
#include "../../util/physfs.hpp"
#include <array>
#include <string>
namespace game::resource::xml
{
#define GAME_XML_STRING_LIST(X) \
X(MenuTabInteract, "TextMenuTabInteract", "Interact") \
X(MenuTabArcade, "TextMenuTabArcade", "Arcade") \
X(MenuTabInventory, "TextMenuTabInventory", "Inventory") \
X(MenuTabSettings, "TextMenuTabSettings", "Settings") \
X(MenuTabCheats, "TextMenuTabCheats", "Cheats") \
X(MenuTabDebug, "TextMenuTabDebug", "Debug") \
X(MenuOpenTooltip, "TextMenuOpenTooltip", "Open Main Menu") \
X(MenuCloseTooltip, "TextMenuCloseTooltip", "Close Main Menu") \
X(InteractChatButton, "TextInteractChatButton", "Let's chat!") \
X(InteractHelpButton, "TextInteractHelpButton", "Help") \
X(InteractFeelingButton, "TextInteractFeelingButton", "How are you feeling?") \
X(InteractWeightFormat, "TextInteractWeightFormat", "Weight: %0.2f %s (Stage: %i)") \
X(InteractCapacityFormat, "TextInteractCapacityFormat", "Capacity: %0.0f kcal (Max: %0.0f kcal)") \
X(InteractDigestionRateFormat, "TextInteractDigestionRateFormat", "Digestion Rate: %0.2f%%/sec") \
X(InteractEatingSpeedFormat, "TextInteractEatingSpeedFormat", "Eating Speed: %0.2fx") \
X(InteractTotalCaloriesFormat, "TextInteractTotalCaloriesFormat", "Total Calories Consumed: %0.0f kcal") \
X(InteractTotalFoodItemsFormat, "TextInteractTotalFoodItemsFormat", "Total Food Items Eaten: %i") \
X(SettingsMeasurementSystem, "TextSettingsMeasurementSystem", "Measurement System") \
X(SettingsMetric, "TextSettingsMetric", "Metric") \
X(SettingsMetricTooltip, "TextSettingsMetricTooltip", "Use kilograms (kg).") \
X(SettingsImperial, "TextSettingsImperial", "Imperial") \
X(SettingsImperialTooltip, "TextSettingsImperialTooltip", "Use pounds (lbs).") \
X(SettingsSound, "TextSettingsSound", "Sound") \
X(SettingsVolume, "TextSettingsVolume", "Volume") \
X(SettingsVolumeTooltip, "TextSettingsVolumeTooltip", "Adjust master volume.") \
X(SettingsAppearance, "TextSettingsAppearance", "Appearance") \
X(SettingsUseCharacterColor, "TextSettingsUseCharacterColor", "Use Character Color") \
X(SettingsUseCharacterColorTooltip, "TextSettingsUseCharacterColorTooltip", \
"When playing, the UI will use the character's preset UI color.") \
X(SettingsColor, "TextSettingsColor", "Color") \
X(SettingsColorTooltip, "TextSettingsColorTooltip", "Change the UI color.") \
X(SettingsResetButton, "TextSettingsResetButton", "Reset to Default") \
X(SettingsSaveButton, "TextSettingsSaveButton", "Save") \
X(SettingsSaveTooltip, "TextSettingsSaveTooltip", "Save the game.\n(Note: the game autosaves frequently.)") \
X(SettingsReturnToCharactersButton, "TextSettingsReturnToCharactersButton", "Return to Characters") \
X(SettingsReturnToCharactersTooltip, "TextSettingsReturnToCharactersTooltip", \
"Go back to the character selection screen.\nProgress will be saved.") \
X(ToastCheatsUnlocked, "TextToastCheatsUnlocked", "Cheats unlocked!") \
X(ToastSaving, "TextToastSaving", "Saving...") \
X(ToolsHomeButton, "TextToolsHomeButton", "Home") \
X(ToolsHomeTooltip, "TextToolsHomeTooltip", "Reset camera view.\n(Shortcut: Home)") \
X(ToolsOpenTooltip, "TextToolsOpenTooltip", "Open Tools") \
X(ToolsCloseTooltip, "TextToolsCloseTooltip", "Close Tools") \
X(DebugCursorScreenFormat, "TextDebugCursorScreenFormat", "Cursor Pos (Screen): %0.0f, %0.0f") \
X(DebugCursorWorldFormat, "TextDebugCursorWorldFormat", "Cursor Pos (World): %0.0f, %0.0f") \
X(DebugAnimations, "TextDebugAnimations", "Animations") \
X(DebugNowPlayingFormat, "TextDebugNowPlayingFormat", "Now Playing: %s") \
X(DebugDialogue, "TextDebugDialogue", "Dialogue") \
X(DebugShowNulls, "TextDebugShowNulls", "Show Nulls (Hitboxes)") \
X(DebugShowWorldBounds, "TextDebugShowWorldBounds", "Show World Bounds") \
X(DebugItem, "TextDebugItem", "Item") \
X(DebugHeld, "TextDebugHeld", "Held") \
X(DebugItemTypeFormat, "TextDebugItemTypeFormat", "Type: %i") \
X(DebugItemPositionFormat, "TextDebugItemPositionFormat", "Position: %0.0f, %0.0f") \
X(DebugItemVelocityFormat, "TextDebugItemVelocityFormat", "Velocity: %0.0f, %0.0f") \
X(DebugItemDurabilityFormat, "TextDebugItemDurabilityFormat", "Durability: %i") \
X(InventoryEmptyHint, "TextInventoryEmptyHint", "Check the \"Arcade\" tab to earn rewards!") \
X(InventoryFlavorFormat, "TextInventoryFlavorFormat", "Flavor: %s") \
X(InventoryCaloriesFormat, "TextInventoryCaloriesFormat", "%0.0f kcal") \
X(InventoryDurabilityFormat, "TextInventoryDurabilityFormat", "Durability: %i") \
X(InventoryCapacityBonusFormat, "TextInventoryCapacityBonusFormat", "Capacity Bonus: +%0.0f kcal") \
X(InventoryDigestionRateBonusFormat, "TextInventoryDigestionRateBonusFormat", "Digestion Rate Bonus: +%0.2f%% / sec") \
X(InventoryDigestionRatePenaltyFormat, "TextInventoryDigestionRatePenaltyFormat", "Digestion Rate Penalty: %0.2f%% / sec") \
X(InventoryEatSpeedBonusFormat, "TextInventoryEatSpeedBonusFormat", "Eat Speed Bonus: +%0.2f%% / sec") \
X(InventoryEatSpeedPenaltyFormat, "TextInventoryEatSpeedPenaltyFormat", "Eat Speed Penalty: %0.2f%% / sec") \
X(InventoryUpgradePreviewFormat, "TextInventoryUpgradePreviewFormat", "Upgrade: %ix -> %s") \
X(InventoryUnknown, "TextInventoryUnknown", "???") \
X(InventorySpawnButton, "TextInventorySpawnButton", "Spawn") \
X(InventoryUpgradeButton, "TextInventoryUpgradeButton", "Upgrade") \
X(InventoryUpgradeAllButton, "TextInventoryUpgradeAllButton", "Upgrade All") \
X(InventoryUpgradeNoPath, "TextInventoryUpgradeNoPath", "This item cannot be upgraded.") \
X(InventoryUpgradeNeedsTemplate, "TextInventoryUpgradeNeedsTemplate", "Needs {}x to upgrade into {}!") \
X(InventoryUpgradeOneTemplate, "TextInventoryUpgradeOneTemplate", "Use {}x to upgrade into 1x {}.") \
X(InventoryUpgradeAllTemplate, "TextInventoryUpgradeAllTemplate", "Use {}x to upgrade into {}x {}.") \
X(ArcadeSkillCheckName, "TextArcadeSkillCheckName", "Skill Check") \
X(ArcadeSkillCheckDescription, "TextArcadeSkillCheckDescription", \
"Test your timing to build score, chain combos, and earn rewards based on your performance.") \
X(ArcadePlayButton, "TextArcadePlayButton", "Play") \
X(ArcadeStatsButton, "TextArcadeStatsButton", "Stats") \
X(ArcadeBackButton, "TextArcadeBackButton", "Back") \
X(ArcadeBestFormat, "TextArcadeBestFormat", "Best: %i pts (%ix)") \
X(ArcadeTotalSkillChecksFormat, "TextArcadeTotalSkillChecksFormat", "Total Skill Checks: %i") \
X(ArcadeAccuracyFormat, "TextArcadeAccuracyFormat", "Accuracy: %0.2f%%") \
X(InfoProgressMax, "TextInfoProgressMax", "MAX") \
X(InfoProgressToNextStage, "TextInfoProgressToNextStage", "To Next Stage") \
X(InfoStageProgressFormat, "TextInfoStageProgressFormat", "Stage: %i/%i (%0.1f%%)") \
X(InfoMaxedOut, "TextInfoMaxedOut", "Maxed out!") \
X(InfoStageStartFormat, "TextInfoStageStartFormat", "Start: %0.2f %s") \
X(InfoStageCurrentFormat, "TextInfoStageCurrentFormat", "Current: %0.2f %s") \
X(InfoStageNextFormat, "TextInfoStageNextFormat", "Next: %0.2f %s") \
X(InfoDigestion, "TextInfoDigestion", "Digestion") \
X(InfoDigesting, "TextInfoDigesting", "Digesting...") \
X(InfoDigestionInProgress, "TextInfoDigestionInProgress", "Digestion in progress...") \
X(InfoGiveFoodToStartDigesting, "TextInfoGiveFoodToStartDigesting", "Give food to start digesting!") \
X(InfoDigestionRateFormat, "TextInfoDigestionRateFormat", "Rate: %0.2f%% / sec") \
X(InfoEatingSpeedFormat, "TextInfoEatingSpeedFormat", "Eating Speed: %0.2fx") \
X(SkillCheckScoreFormat, "TextSkillCheckScoreFormat", "Score: %i pts (%ix)") \
X(SkillCheckBestFormat, "TextSkillCheckBestFormat", "Best: %i pts (%ix)") \
X(SkillCheckInstructions, "TextSkillCheckInstructions", "Match the line to the colored areas with Space/click! Better performance, better rewards!") \
X(SkillCheckScoreLoss, "TextSkillCheckScoreLoss", "-1") \
X(SkillCheckRewardToast, "TextSkillCheckRewardToast", "Fantastic score! Congratulations!") \
X(SkillCheckHighScoreToast, "TextSkillCheckHighScoreToast", "High Score!") \
X(SkillCheckGradeSuccessTemplate, "TextSkillCheckGradeSuccessTemplate", "{} (+{})") \
X(SkillCheckMenuButton, "TextSkillCheckMenuButton", "Menu") \
X(CheatsCalories, "TextCheatsCalories", "Calories") \
X(CheatsCapacity, "TextCheatsCapacity", "Capacity") \
X(CheatsWeight, "TextCheatsWeight", "Weight") \
X(CheatsWeightFormat, "TextCheatsWeightFormat", "%0.2f kg") \
X(CheatsStage, "TextCheatsStage", "Stage") \
X(CheatsDigestionRate, "TextCheatsDigestionRate", "Digestion Rate") \
X(CheatsDigestionRateFormat, "TextCheatsDigestionRateFormat", "%0.2f% / tick") \
X(CheatsEatSpeed, "TextCheatsEatSpeed", "Eat Speed") \
X(CheatsEatSpeedFormat, "TextCheatsEatSpeedFormat", "%0.2fx") \
X(CheatsDigestButton, "TextCheatsDigestButton", "Digest") \
X(CheatsInventory, "TextCheatsInventory", "Inventory")
class Strings
{
public:
enum Type
{
#define X(type, attr, fallback) type,
GAME_XML_STRING_LIST(X)
#undef X
Count
};
struct Definition
{
const char* attribute;
const char* fallback;
};
inline static constexpr std::array<Definition, Count> definitions{{
#define X(type, attr, fallback) {attr, fallback},
GAME_XML_STRING_LIST(X)
#undef X
}};
std::array<std::string, Count> values{};
bool isValid{};
Strings();
Strings(const util::physfs::Path&);
const std::string& get(Type) const;
};
}

View File

@@ -117,6 +117,29 @@ namespace game::resource::xml
return result;
}
XMLError query_optional_vec3(XMLElement* element, const char* attributeX, const char* attributeY,
const char* attributeZ, std::optional<glm::vec3>& value)
{
auto hasX = element->FindAttribute(attributeX);
auto hasY = element->FindAttribute(attributeY);
auto hasZ = element->FindAttribute(attributeZ);
if (!hasX && !hasY && !hasZ)
{
value.reset();
return XML_NO_ATTRIBUTE;
}
value = glm::vec3();
auto result = XML_SUCCESS;
if (hasX) result = query_result_merge(result, element->QueryFloatAttribute(attributeX, &value->x));
if (hasY) result = query_result_merge(result, element->QueryFloatAttribute(attributeY, &value->y));
if (hasZ) result = query_result_merge(result, element->QueryFloatAttribute(attributeZ, &value->z));
return result;
}
XMLError document_load(const physfs::Path& path, XMLDocument& document)
{
if (!path.is_valid())

View File

@@ -32,6 +32,8 @@ namespace game::resource::xml
std::optional<float>& value);
tinyxml2::XMLError query_int_optional_attribute(tinyxml2::XMLElement* element, const char* attribute,
std::optional<int>& value);
tinyxml2::XMLError query_optional_vec3(tinyxml2::XMLElement* element, const char* attributeX, const char* attributeY,
const char* attributeZ, std::optional<glm::vec3>& value);
tinyxml2::XMLError query_event_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& eventID);
tinyxml2::XMLError query_layer_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& layerID);

View File

@@ -1,4 +1,5 @@
#include "play.hpp"
#include "play/style.hpp"
#include <array>
#include <glm/glm.hpp>
@@ -11,12 +12,25 @@
#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
{
namespace
{
int durability_animation_index_get(const resource::xml::Item& 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));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
}
}
World::Focus Play::focus_get()
{
if (!isWindows) return World::CENTER;
@@ -40,9 +54,10 @@ namespace game::state
character =
entity::Character(data, vec2(World::BOUNDS.x + World::BOUNDS.z * 0.5f, World::BOUNDS.w - World::BOUNDS.y));
character.digestionRate = glm::clamp(data.digestionRateMin, character.digestionRate, data.digestionRateMax);
character.eatSpeed = glm::clamp(data.eatSpeedMin, character.eatSpeed, data.eatSpeedMax);
character.capacity = glm::clamp(data.capacityMin, character.capacity, data.capacityMax);
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);
auto isAlternateSpritesheet =
(game == NEW_GAME && math::random_percent_roll(data.alternateSpritesheet.chanceOnNewGame));
@@ -71,29 +86,34 @@ namespace game::state
for (auto& item : saveData.items)
{
auto& anm2 = itemSchema.anm2s.at(item.id);
auto chewAnimation = itemSchema.animations.chew + std::to_string(item.chewCount);
auto animationIndex = item.chewCount > 0 ? anm2.animationMap[chewAnimation] : -1;
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.chewCount, animationIndex, item.velocity,
itemManager.items.emplace_back(saveItem, item.position, item.id, item.durability, animationIndex, item.velocity,
item.rotation);
}
imgui::style::rounding_set(menuSchema.rounding);
imgui::widget::sounds_set(&menuSchema.sounds.hover, &menuSchema.sounds.select);
menu.color_set_check(resources, character);
play::style::color_set(resources, character);
menu.skillCheck = SkillCheck(character);
menu.skillCheck.totalPlays = saveData.totalPlays;
menu.skillCheck.highScore = saveData.highScore;
menu.skillCheck.bestCombo = saveData.bestCombo;
menu.skillCheck.gradeCounts = saveData.gradeCounts;
menu.skillCheck.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
menu.isChat = character.data.dialogue.help.is_valid() || character.data.dialogue.random.is_valid();
menu.arcade = Arcade(character);
menu.arcade.skillCheck.totalPlays = saveData.totalPlays;
menu.arcade.skillCheck.highScore = saveData.highScore;
menu.arcade.skillCheck.bestCombo = saveData.bestCombo;
menu.arcade.skillCheck.gradeCounts = saveData.gradeCounts;
menu.arcade.skillCheck.isHighScoreAchieved = saveData.highScore > 0 ? true : false;
text.entry = nullptr;
text.isEnabled = false;
#if DEBUG
menu.isCheats = true;
#else
menu.isCheats = false;
#endif
isPostgame = saveData.isPostgame;
if (character.stage_get() >= character.stage_max_get()) isPostgame = true;
if (isPostgame) menu.isCheats = true;
@@ -190,7 +210,7 @@ namespace game::state
menu.isCheats = true;
cheatCodeIndex = 0;
cheatCodeStartTime = 0.0;
toasts.push("Cheats unlocked!");
toasts.push(character.data.strings.get(Strings::ToastCheatsUnlocked));
character.data.menuSchema.sounds.cheatsActivated.play();
}
}
@@ -338,10 +358,10 @@ namespace game::state
save.digestionTimer = character.digestionTimer;
save.totalCaloriesConsumed = character.totalCaloriesConsumed;
save.totalFoodItemsEaten = character.totalFoodItemsEaten;
save.totalPlays = menu.skillCheck.totalPlays;
save.highScore = menu.skillCheck.highScore;
save.bestCombo = menu.skillCheck.bestCombo;
save.gradeCounts = menu.skillCheck.gradeCounts;
save.totalPlays = menu.arcade.skillCheck.totalPlays;
save.highScore = menu.arcade.skillCheck.highScore;
save.bestCombo = menu.arcade.skillCheck.bestCombo;
save.gradeCounts = menu.arcade.skillCheck.gradeCounts;
save.isPostgame = isPostgame;
save.isAlternateSpritesheet = character.spritesheetType == entity::Character::ALTERNATE;
@@ -352,7 +372,7 @@ namespace game::state
}
for (auto& item : itemManager.items)
save.items.emplace_back(item.schemaID, item.chewCount, item.position, item.velocity,
save.items.emplace_back(item.schemaID, item.durability, item.position, item.velocity,
*item.overrides[item.rotationOverrideID].frame.rotation);
save.isValid = true;
@@ -360,6 +380,6 @@ namespace game::state
resources.character_save_set(characterIndex, save);
save.serialize(character.data.save_path_get());
toasts.push("Saving...");
toasts.push(character.data.strings.get(Strings::ToastSaving));
}
};

76
src/state/play/arcade.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "arcade.hpp"
#include "../../util/imgui/widget.hpp"
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play
{
Arcade::Arcade(entity::Character& character) : skillCheck(character) {}
void Arcade::tick() { skillCheck.tick(); }
void Arcade::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
{
auto available = ImGui::GetContentRegionAvail();
auto& strings = character.data.strings;
if (view == SKILL_CHECK)
{
if (skillCheck.update(resources, character, inventory, text)) view = MENU;
return;
}
auto buttonHeight = ImGui::GetFrameHeightWithSpacing();
auto childSize = ImVec2(available.x, std::max(0.0f, available.y - buttonHeight));
if (ImGui::BeginChild("##Arcade Child", childSize))
{
if (view == MENU)
{
auto buttonWidth = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
ImGui::PopFont();
ImGui::Separator();
ImGui::TextWrapped("%s", strings.get(Strings::ArcadeSkillCheckDescription).c_str());
ImGui::Separator();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadePlayButton).c_str(), ImVec2(buttonWidth, 0))))
view = SKILL_CHECK;
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeStatsButton).c_str(), ImVec2(buttonWidth, 0))))
view = SKILL_CHECK_STATS;
}
else if (view == SKILL_CHECK_STATS)
{
auto& schema = character.data.skillCheckSchema;
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_2);
ImGui::TextUnformatted(strings.get(Strings::ArcadeSkillCheckName).c_str());
ImGui::PopFont();
ImGui::Separator();
ImGui::Text(strings.get(Strings::ArcadeBestFormat).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));
}
}
ImGui::EndChild();
if (view == SKILL_CHECK_STATS)
{
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ArcadeBackButton).c_str()))) view = MENU;
}
}
}

26
src/state/play/arcade.hpp Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "arcade/skill_check.hpp"
namespace game::state::play
{
class Arcade
{
public:
enum View
{
MENU,
SKILL_CHECK,
SKILL_CHECK_STATS
};
SkillCheck skillCheck{};
View view{MENU};
Arcade() = default;
Arcade(entity::Character&);
void tick();
void update(Resources&, entity::Character&, Inventory&, Text&);
};
}

View File

@@ -6,13 +6,16 @@
#include "../../../util/imgui/widget.hpp"
#include "../../../util/math.hpp"
#include <array>
#include <cmath>
#include <cstdio>
#include <format>
#include <ranges>
using namespace game::util;
using namespace game::entity;
using namespace game::resource;
using namespace game::resource::xml;
using namespace glm;
namespace game::state::play
@@ -69,7 +72,7 @@ namespace game::state::play
actor.tick();
}
void SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
bool SkillCheck::update(Resources& resources, entity::Character& character, Inventory& inventory, Text& text)
{
static constexpr auto BG_COLOR_MULTIPLIER = 0.5f;
static constexpr ImVec4 LINE_COLOR = ImVec4(1, 1, 1, 1);
@@ -82,25 +85,31 @@ namespace game::state::play
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();
auto& io = ImGui::GetIO();
auto menuButtonHeight = ImGui::GetFrameHeightWithSpacing();
size.y = std::max(0.0f, size.y - menuButtonHeight);
auto cursorPos = ImGui::GetCursorPos();
ImGui::Text("Score: %i pts (%ix)", score, combo);
auto bestString = std::format("Best: {} pts({}x)", highScore, bestCombo);
ImGui::Text(strings.get(Strings::SkillCheckScoreFormat).c_str(), score, combo);
std::array<char, 128> bestBuffer{};
std::snprintf(bestBuffer.data(), bestBuffer.size(), strings.get(Strings::SkillCheckBestFormat).c_str(), highScore,
bestCombo);
auto bestString = std::string(bestBuffer.data());
ImGui::SetCursorPos(ImVec2(size.x - ImGui::CalcTextSize(bestString.c_str()).x, cursorPos.y));
ImGui::Text("Best: %i pts (%ix)", highScore, bestCombo);
ImGui::Text(strings.get(Strings::SkillCheckBestFormat).c_str(), highScore, bestCombo);
if (score == 0 && isActive)
{
ImGui::SetCursorPos(ImVec2(style.WindowPadding.x, size.y - style.WindowPadding.y));
ImGui::TextWrapped("Match the line to the colored areas with Space/click! Better performance, better rewards!");
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));
@@ -193,8 +202,11 @@ namespace game::state::play
score--;
schema.sounds.scoreLoss.play();
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize("-1").x - ImGui::GetTextLineHeightWithSpacing(), lineMin.y);
toasts.emplace_back("-1", toastMessagePosition, schema.endTimerMax, schema.endTimerMax);
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckScoreLoss).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y);
toasts.emplace_back(strings.get(Strings::SkillCheckScoreLoss), toastMessagePosition, schema.endTimerMax,
schema.endTimerMax);
}
}
@@ -258,10 +270,10 @@ namespace game::state::play
}
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize("Fantastic score!\nCongratulations!").x -
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckRewardToast).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y + (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y));
toasts.emplace_back("Fantastic score! Congratulations!", toastMessagePosition, schema.endTimerMax,
toasts.emplace_back(strings.get(Strings::SkillCheckRewardToast), toastMessagePosition, schema.endTimerMax,
schema.endTimerMax);
}
@@ -274,9 +286,11 @@ namespace game::state::play
isHighScoreAchievedThisRun = true;
schema.sounds.highScore.play();
auto toastMessagePosition =
ImVec2(barMin.x - ImGui::CalcTextSize("High Score!").x - ImGui::GetTextLineHeightWithSpacing(),
ImVec2(barMin.x - ImGui::CalcTextSize(strings.get(Strings::SkillCheckHighScoreToast).c_str()).x -
ImGui::GetTextLineHeightWithSpacing(),
lineMin.y + ImGui::GetTextLineHeightWithSpacing());
toasts.emplace_back("High Score!", toastMessagePosition, schema.endTimerMax, schema.endTimerMax);
toasts.emplace_back(strings.get(Strings::SkillCheckHighScoreToast), toastMessagePosition,
schema.endTimerMax, schema.endTimerMax);
}
}
@@ -337,10 +351,10 @@ namespace game::state::play
{
score = 0;
combo = 0;
if (isHighScoreAchieved) schema.sounds.highScoreLoss.play();
if (isHighScoreAchievedThisRun) schema.sounds.highScoreLoss.play();
if (highScore > 0) isHighScoreAchieved = true;
isRewardScoreAchieved = false;
isHighScoreAchievedThisRun = true;
isHighScoreAchievedThisRun = false;
highScoreStart = highScore;
isGameOver = true;
}
@@ -351,7 +365,9 @@ namespace game::state::play
queuedChallenge = challenge_generate(character);
auto string = grade.isFailure ? grade.name : std::format("{} (+{})", grade.name, grade.value);
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.emplace_back(string, toastMessagePosition, endTimerMax, endTimerMax);
@@ -429,5 +445,8 @@ namespace game::state::play
if (fallingItem.position.y > position.y + size.y) items.erase(items.begin() + i--);
}
ImGui::PopClipRect();
ImGui::SetCursorScreenPos(ImVec2(position.x, position.y + size.y + ImGui::GetStyle().ItemSpacing.y));
return WIDGET_FX(ImGui::Button(strings.get(Strings::SkillCheckMenuButton).c_str()));
}
}

View File

@@ -81,7 +81,7 @@ namespace game::state::play
SkillCheck(entity::Character&);
Challenge challenge_generate(entity::Character&);
void tick();
void update(Resources&, entity::Character&, Inventory&, Text&);
bool update(Resources&, entity::Character&, Inventory&, Text&);
float accuracy_score_get(entity::Character&);
};
}

View File

@@ -89,14 +89,18 @@ namespace game::state::play
interact_area_override_tick, interactArea.scaleEffectCycles));
}
if (interactArea.pool.is_valid() && text.is_interruptible())
text.set(dialogue.get(interactArea.pool), character);
if (text.is_interruptible())
{
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 (isInteracting)
{
cursor.state = entity::Cursor::ACTION;
character.queue_interact_area_animation(interactArea);
cursor.queue_play({interactArea.animationCursorActive});
if (interactArea.digestionBonusRub > 0 && character.calories > 0 && !character.isDigesting)

View File

@@ -1,32 +0,0 @@
#include "chat.hpp"
#include "../../util/imgui/widget.hpp"
using namespace game::resource;
using namespace game::util::imgui;
namespace game::state::play
{
void Chat::update(Resources&, Text& text, entity::Character& character)
{
auto& dialogue = character.data.dialogue;
auto size = ImGui::GetContentRegionAvail();
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
if (dialogue.random.is_valid())
if (WIDGET_FX(ImGui::Button("Let's chat!", ImVec2(size.x, 0))))
text.set(dialogue.get(dialogue.random), character);
ImGui::PopFont();
if (dialogue.help.is_valid())
if (WIDGET_FX(ImGui::Button("Help", ImVec2(size.x, 0)))) text.set(dialogue.get(dialogue.help), character);
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("How are you feeling?", ImVec2(size.x, 0)))) text.set(dialogue.get(pool), character);
}
}

View File

@@ -8,82 +8,50 @@
using namespace game::util::imgui;
using namespace game::util;
using namespace game::resource::xml;
namespace game::state::play
{
void Cheats::update(Resources&, entity::Character& character, Inventory& inventory, Text& text)
void Cheats::update(Resources&, entity::Character& character, Inventory& inventory)
{
static constexpr auto FEED_INCREMENT = 100.0f;
auto& strings = character.data.strings;
if (ImGui::BeginChild("##Cheats"))
{
if (WIDGET_FX(ImGui::Button("Feed")))
{
character.calories = std::min(character.calories + FEED_INCREMENT, character.max_capacity());
character.queue_idle_animation();
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button("Starve")))
{
character.calories = std::max(0.0f, character.calories - FEED_INCREMENT);
character.queue_idle_animation();
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button("Digest"))) character.digestionProgress = entity::Character::DIGESTION_MAX;
auto stage = character.stage + 1;
if (WIDGET_FX(ImGui::SliderInt("Stage", &stage, 1, (int)character.data.stages.size() + 1)))
auto weight_update = [&]() { character.queue_idle_animation(); };
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCalories).c_str(), &character.calories, 0,
character.max_capacity(), "%0.0f kcal"));
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsCapacity).c_str(), &character.capacity,
character.data.capacityMin, character.data.capacityMax, "%0.0f kcal"));
if (WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsWeight).c_str(), &character.weight,
character.data.weight, character.data.weightMax,
strings.get(Strings::CheatsWeightFormat).c_str())))
weight_update();
if (WIDGET_FX(ImGui::SliderInt(strings.get(Strings::CheatsStage).c_str(), &stage, 1,
(int)character.data.stages.size() + 1)))
{
character.stage = glm::clamp(0, stage - 1, (int)character.data.stages.size());
character.weight =
character.stage == 0 ? character.data.weight : character.data.stages.at(character.stage - 1).threshold;
character.queue_idle_animation();
weight_update();
}
WIDGET_FX(ImGui::SliderFloat("Capacity", &character.capacity, character.data.capacityMin,
character.data.capacityMax, "%0.0f kcal"));
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsDigestionRate).c_str(), &character.digestionRate,
character.data.digestionRateMin, character.data.digestionRateMax,
strings.get(Strings::CheatsDigestionRateFormat).c_str()));
WIDGET_FX(ImGui::SliderFloat(strings.get(Strings::CheatsEatSpeed).c_str(), &character.eatSpeed,
character.data.eatSpeedMin, character.data.eatSpeedMax,
strings.get(Strings::CheatsEatSpeedFormat).c_str()));
WIDGET_FX(ImGui::SliderFloat("Digestion Rate", &character.digestionRate, character.data.digestionRateMin,
character.data.digestionRateMax, "%0.2f% / tick"));
WIDGET_FX(ImGui::SliderFloat("Eat Speed", &character.eatSpeed, character.data.eatSpeedMin,
character.data.eatSpeedMax, "%0.2fx"));
if (WIDGET_FX(ImGui::Button(strings.get(Strings::CheatsDigestButton).c_str())))
character.digestionProgress = entity::Character::DIGESTION_MAX;
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 (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();
ImGui::SeparatorText("Inventory");
ImGui::SeparatorText(strings.get(Strings::CheatsInventory).c_str());
if (ImGui::BeginChild("##Inventory", ImGui::GetContentRegionAvail(), ImGuiChildFlags_Borders))
{

View File

@@ -10,7 +10,6 @@ namespace game::state::play
class Cheats
{
public:
void update(Resources&, entity::Character&, Inventory&, Text&);
void update(Resources&, entity::Character&, Inventory&);
};
}

View File

@@ -5,31 +5,68 @@
#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)
void Debug::update(entity::Character& character, entity::Cursor& cursor, ItemManager& itemManager, Canvas& canvas,
Text& text)
{
auto& strings = character.data.strings;
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::Text(strings.get(Strings::DebugCursorScreenFormat).c_str(), cursor.position.x, cursor.position.y);
ImGui::Text(strings.get(Strings::DebugCursorWorldFormat).c_str(), cursorPosition.x, cursorPosition.y);
WIDGET_FX(ImGui::Checkbox("Show Nulls (Hitboxes)", &character.isShowNulls));
WIDGET_FX(ImGui::Checkbox("Show World Bounds", &isBoundsDisplay));
ImGui::SeparatorText(strings.get(Strings::DebugAnimations).c_str());
ImGui::Text(strings.get(Strings::DebugNowPlayingFormat).c_str(), 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(strings.get(Strings::DebugDialogue).c_str());
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(strings.get(Strings::DebugShowNulls).c_str(), &character.isShowNulls));
WIDGET_FX(ImGui::Checkbox(strings.get(Strings::DebugShowWorldBounds).c_str(), &isBoundsDisplay));
if (!itemManager.items.empty())
{
ImGui::SeparatorText("Item");
ImGui::SeparatorText(strings.get(Strings::DebugItem).c_str());
for (int i = 0; i < (int)itemManager.items.size(); i++)
{
auto& item = itemManager.items[i];
if (itemManager.heldItemIndex == i) ImGui::Text("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("Chew Count: %i", item.chewCount);
if (itemManager.heldItemIndex == i) ImGui::TextUnformatted(strings.get(Strings::DebugHeld).c_str());
ImGui::Text(strings.get(Strings::DebugItemTypeFormat).c_str(), item.schemaID);
ImGui::Text(strings.get(Strings::DebugItemPositionFormat).c_str(), item.position.x, item.position.y);
ImGui::Text(strings.get(Strings::DebugItemVelocityFormat).c_str(), item.velocity.x, item.velocity.y);
ImGui::Text(strings.get(Strings::DebugItemDurabilityFormat).c_str(), item.durability);
ImGui::Separator();
}
}

View File

@@ -4,6 +4,7 @@
#include "../../entity/cursor.hpp"
#include "item_manager.hpp"
#include "text.hpp"
#include <imgui.h>
@@ -14,6 +15,6 @@ namespace game::state::play
public:
bool isBoundsDisplay{};
void update(entity::Character&, entity::Cursor& cursor, ItemManager&, Canvas& canvas);
void update(entity::Character&, entity::Cursor&, ItemManager&, Canvas&, Text&);
};
}

View File

@@ -9,6 +9,7 @@
#include <format>
using namespace game::resource;
using namespace game::resource::xml;
using namespace game::util;
namespace game::state::play
@@ -18,6 +19,7 @@ namespace game::state::play
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
static constexpr auto HEIGHT_MULTIPLIER = 4.0f;
auto& strings = character.data.strings;
auto& style = ImGui::GetStyle();
auto windowSize = imgui::to_ivec2(ImGui::GetMainViewport()->Size);
@@ -45,26 +47,29 @@ namespace game::state::play
auto unitString = (system == measurement::IMPERIAL ? "lbs" : "kg");
auto weightString = util::string::format_commas(weight, 2) + " " + unitString;
ImGui::PushFont(ImGui::GetFont(), Font::ABOVE_AVERAGE);
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),
stage >= stageMax ? "MAX" : "To Next Stage");
strings.get(stage >= stageMax ? Strings::InfoProgressMax
: Strings::InfoProgressToNextStage)
.c_str());
if (ImGui::BeginItemTooltip())
{
ImGui::Text("Stage: %i/%i (%0.1f%%)", stage + 1, stageMax + 1, math::to_percent(stageProgress));
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::Text("Maxed out!");
ImGui::TextUnformatted(strings.get(Strings::InfoMaxedOut).c_str());
else
{
ImGui::Text("Start: %0.2f %s", stageWeight, unitString);
ImGui::Text("Current: %0.2f %s", weight, unitString);
ImGui::Text("Next: %0.2f %s", stageNextWeight, unitString);
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();
@@ -82,7 +87,7 @@ namespace game::state::play
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::ABOVE_AVERAGE);
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);
@@ -95,14 +100,16 @@ namespace game::state::play
? (float)character.digestionTimer / character.data.digestionTimerMax
: character.digestionProgress / entity::Character::DIGESTION_MAX;
ImGui::ProgressBar(digestionProgress, ImVec2(ImGui::GetContentRegionAvail().x, 0),
character.isDigesting ? "Digesting..." : "Digestion");
strings.get(character.isDigesting ? Strings::InfoDigesting
: Strings::InfoDigestion)
.c_str());
if (ImGui::BeginItemTooltip())
{
if (character.isDigesting)
ImGui::TextUnformatted("Digestion in progress...");
ImGui::TextUnformatted(strings.get(Strings::InfoDigestionInProgress).c_str());
else if (digestionProgress <= 0.0f)
ImGui::TextUnformatted("Give food to start digesting!");
ImGui::TextUnformatted(strings.get(Strings::InfoGiveFoodToStartDigesting).c_str());
else
ImGui::Text("%0.2f%%", math::to_percent(digestionProgress));
@@ -110,8 +117,8 @@ namespace game::state::play
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::Text("Rate: %0.2f%% / sec", character.digestion_rate_get());
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
ImGui::Text(strings.get(Strings::InfoDigestionRateFormat).c_str(), character.digestion_rate_get());
ImGui::Text(strings.get(Strings::InfoEatingSpeedFormat).c_str(), character.eatSpeed);
ImGui::PopStyleColor();

View File

@@ -0,0 +1,57 @@
#include "interact.hpp"
#include "../../util/imgui/widget.hpp"
#include "../../util/measurement.hpp"
using namespace game::resource;
using namespace game::resource::xml;
using namespace game::util;
using namespace game::util::imgui;
namespace game::state::play
{
void Interact::update(Resources& resources, Text& text, entity::Character& character)
{
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 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);
ImGui::PushFont(ImGui::GetFont(), resource::Font::HEADER_1);
ImGui::SeparatorText(character.data.name.c_str());
ImGui::PopFont();
auto& system = resources.settings.measurementSystem;
auto weight = character.weight_get(system);
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
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);
}
}

View File

@@ -6,7 +6,7 @@
namespace game::state::play
{
class Chat
class Interact
{
public:
void update(Resources&, Text&, entity::Character&);

View File

@@ -1,4 +1,5 @@
#include "inventory.hpp"
#include "style.hpp"
#include <cmath>
#include <format>
@@ -7,6 +8,7 @@
#include "../../util/color.hpp"
#include "../../util/imgui.hpp"
#include "../../util/imgui/style.hpp"
#include "../../util/imgui/widget.hpp"
#include "../../util/math.hpp"
@@ -18,6 +20,8 @@ using namespace glm;
namespace game::state::play
{
using Strings = resource::xml::Strings;
void Inventory::tick()
{
for (auto& [i, actor] : actors)
@@ -29,6 +33,7 @@ namespace game::state::play
static constexpr auto INFO_CHILD_HEIGHT_MULTIPLIER = 1.0f / 3.0f;
auto& schema = character.data.itemSchema;
auto& strings = character.data.strings;
auto quantity_get = [&](int itemID) -> int&
{
@@ -46,6 +51,77 @@ namespace game::state::play
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 item_summary_draw = [&](const resource::xml::Item::Entry& item, int quantity)
{
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
auto durability = item.durability.value_or(schema.durability);
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), quantity);
ImGui::PopFont();
ImGui::Separator();
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())
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);
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.digestionBonus > 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRateBonusFormat).c_str(),
*item.digestionBonus * 60.0f);
else if (*item.digestionBonus < 0)
ImGui::TextWrapped(strings.get(Strings::InventoryDigestionRatePenaltyFormat).c_str(),
*item.digestionBonus * 60.0f);
}
if (item.eatSpeedBonus.has_value())
{
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::PopStyleColor();
ImGui::Separator();
};
auto item_details_draw = [&](const resource::xml::Item::Entry& item, int quantity)
{
item_summary_draw(item, quantity);
if (ImGui::BeginChild("##Info Description Child", ImGui::GetContentRegionAvail()))
ImGui::TextWrapped("%s", item.description.c_str());
ImGui::EndChild();
};
auto item_tooltip_draw = [&](const resource::xml::Item::Entry& item, int quantity)
{
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
item_summary_draw(item, quantity);
ImGui::TextWrapped("%s", item.description.c_str());
ImGui::PopTextWrapPos();
};
auto item_unknown_draw = [&]()
{
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f);
ImGui::TextWrapped("%s", strings.get(Strings::InventoryUnknown).c_str());
ImGui::PopTextWrapPos();
ImGui::PopFont();
};
auto item_use = [&](int itemID)
{
auto& item = schema.items[itemID];
@@ -176,6 +252,7 @@ namespace game::state::play
auto& item = schema.items[i];
auto& quantity = quantity_get(i);
auto& rarity = schema.rarities[item.rarityID];
auto hasItemColor = item.color.has_value();
if (rarity.isHidden && quantity <= 0) continue;
@@ -185,6 +262,7 @@ namespace game::state::play
auto cursorScreenPos = ImGui::GetCursorScreenPos();
auto [canvas, rect] = item_canvas_get(i, size);
auto isSelected = selectedItemID == i;
if (hasItemColor) imgui::style::color_set(*item.color);
if (isSelected)
{
@@ -201,8 +279,16 @@ namespace game::state::play
isAnyInventoryItemHovered = isAnyInventoryItemHovered || ImGui::IsItemHovered();
if (isPressed) selectedItemID = i;
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && quantity > 0) item_use(i);
if (ImGui::BeginItemTooltip())
{
if (quantity > 0)
item_tooltip_draw(item, quantity);
else
item_unknown_draw();
ImGui::EndTooltip();
}
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
auto text = std::format("x{}", quantity);
auto textPos = ImVec2(cursorScreenPos.x + size.x - ImGui::CalcTextSize(text.c_str()).x,
@@ -210,6 +296,7 @@ namespace game::state::play
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)),
text.c_str());
ImGui::PopFont();
if (hasItemColor) style::color_set(resources, character);
auto increment = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
cursorPos.x += increment;
@@ -231,13 +318,15 @@ namespace game::state::play
isItemSelected = selectedItemID >= 0 && selectedItemID < (int)schema.items.size();
auto selectedQuantity = isItemSelected ? quantity_get(selectedItemID) : 0;
auto isSelectedItemKnown = isItemSelected && selectedQuantity > 0;
auto selectedItemHasColor = isItemSelected && schema.items[selectedItemID].color.has_value();
if (isInfoVisible &&
ImGui::BeginChild("##Info Child", infoChildSize, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar))
{
if (selectedItemHasColor) imgui::style::color_set(*schema.items[selectedItemID].color);
ImGui::Separator();
auto isButtonChildVisible = selectedQuantity > 0;
ImGui::PushFont(resources.font.get(), Font::BIG);
ImGui::PushFont(resources.font.get(), Font::HEADER_2);
auto buttonRowHeight = ImGui::GetFrameHeight();
auto buttonChildHeight =
isButtonChildVisible ? buttonRowHeight * 2.0f + ImGui::GetStyle().ItemSpacing.y * 5.0f : 0.0f;
@@ -252,56 +341,18 @@ namespace game::state::play
{
if (!isItemSelected)
{
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::TextWrapped("%s", "Check the \"Arcade\" tab to earn rewards!");
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::TextWrapped("%s", strings.get(Strings::InventoryEmptyHint).c_str());
ImGui::PopFont();
}
else
{
auto& item = schema.items[selectedItemID];
auto& category = schema.categories[item.categoryID];
auto& rarity = schema.rarities[item.rarityID];
if (isSelectedItemKnown)
{
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::TextWrapped("%s (x%i)", item.name.c_str(), selectedQuantity);
ImGui::PopFont();
ImGui::Separator();
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())
ImGui::TextWrapped("Flavor: %s", schema.flavors[*item.flavorID].name.c_str());
if (item.calories.has_value()) ImGui::TextWrapped("%0.0f kcal", *item.calories);
if (item.digestionBonus.has_value())
{
if (*item.digestionBonus > 0)
ImGui::TextWrapped("Digestion Rate Bonus: +%0.2f%% / sec", *item.digestionBonus * 60.0f);
else if (*item.digestionBonus < 0)
ImGui::TextWrapped("Digestion Rate Penalty: %0.2f%% / sec", *item.digestionBonus * 60.0f);
}
if (item.eatSpeedBonus.has_value())
{
if (*item.eatSpeedBonus > 0)
ImGui::TextWrapped("Eat Speed Bonus: +%0.2f%% / sec", *item.eatSpeedBonus);
else if (*item.eatSpeedBonus < 0)
ImGui::TextWrapped("Eat Speed Penalty: %0.2f%% / sec", *item.eatSpeedBonus);
}
if (is_possible_to_upgrade_get(item))
ImGui::TextWrapped("Upgrade: %ix -> %s", *item.upgradeCount,
schema.idToStringMap.at(*item.upgradeID).c_str());
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::TextWrapped("%s", item.description.c_str());
}
item_details_draw(item, selectedQuantity);
else
{
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::TextWrapped("%s", "???");
ImGui::PopFont();
}
item_unknown_draw();
}
}
ImGui::EndChild();
@@ -310,28 +361,75 @@ namespace game::state::play
ImGui::BeginChild("##Info Actions Child", buttonChildSize, ImGuiChildFlags_None,
ImGuiWindowFlags_NoScrollbar))
{
auto& selectedItem = schema.items[selectedItemID];
auto canUseSelectedItem = true;
auto canUpgradeSelectedItem = is_able_to_upgrade_get(schema.items[selectedItemID], selectedQuantity);
auto canUpgradeSelectedItem = is_able_to_upgrade_get(selectedItem, selectedQuantity);
auto rowTwoButtonSize = row_widget_size_get(2);
auto upgrade_item_name_get = [&]() -> std::string
{
if (!selectedItem.upgradeID.has_value()) return {};
return schema.items.at(*selectedItem.upgradeID).name;
};
auto upgrade_tooltip_get = [&](bool isAll)
{
if (!is_possible_to_upgrade_get(selectedItem))
return strings.get(Strings::InventoryUpgradeNoPath);
auto upgradeItemName = upgrade_item_name_get();
auto upgradeCount = *selectedItem.upgradeCount;
if (!canUpgradeSelectedItem)
return std::vformat(strings.get(Strings::InventoryUpgradeNeedsTemplate),
std::make_format_args(upgradeCount, upgradeItemName));
if (!isAll)
return std::vformat(strings.get(Strings::InventoryUpgradeOneTemplate),
std::make_format_args(upgradeCount, upgradeItemName));
auto upgradedCount = selectedQuantity / upgradeCount;
return std::vformat(strings.get(Strings::InventoryUpgradeAllTemplate),
std::make_format_args(upgradeCount, upgradedCount, upgradeItemName));
};
ImGui::Separator();
ImGui::Dummy(ImVec2(0, ImGui::GetStyle().ItemSpacing.y));
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::BeginDisabled(!canUseSelectedItem);
if (WIDGET_FX(ImGui::Button("Spawn", {ImGui::GetContentRegionAvail().x, 0}))) item_use(selectedItemID);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventorySpawnButton).c_str(),
{ImGui::GetContentRegionAvail().x, 0})))
item_use(selectedItemID);
ImGui::EndDisabled();
ImGui::BeginDisabled(!canUpgradeSelectedItem);
if (WIDGET_FX(ImGui::Button("Upgrade", rowTwoButtonSize))) item_upgrade(selectedItemID, false);
if (WIDGET_FX(
ImGui::Button(strings.get(Strings::InventoryUpgradeButton).c_str(), rowTwoButtonSize)))
item_upgrade(selectedItemID, false);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(false).c_str());
ImGui::PopFont();
}
ImGui::SameLine();
if (WIDGET_FX(ImGui::Button("Upgrade All", rowTwoButtonSize))) item_upgrade(selectedItemID, true);
if (WIDGET_FX(ImGui::Button(strings.get(Strings::InventoryUpgradeAllButton).c_str(),
rowTwoButtonSize)))
item_upgrade(selectedItemID, true);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
ImGui::SetItemTooltip("%s", upgrade_tooltip_get(true).c_str());
ImGui::PopFont();
}
ImGui::EndDisabled();
ImGui::PopFont();
}
if (isButtonChildVisible) ImGui::EndChild();
if (selectedItemHasColor) style::color_set(resources, character);
}
if (isInfoVisible) ImGui::EndChild();
}

View File

@@ -15,6 +15,18 @@ using namespace glm;
namespace game::state::play
{
namespace
{
int durability_animation_index_get(const resource::xml::Item& 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));
return anm2.animationMap.contains(animationName) ? anm2.animationMap.at(animationName) : -1;
}
}
void ItemManager::update(entity::Character& character, entity::Cursor& cursor, AreaManager& areaManager, Text& text,
const glm::vec4& bounds, Canvas& canvas)
{
@@ -70,7 +82,11 @@ namespace game::state::play
math::random_in_range(spawnBounds.y, spawnBounds.y + spawnBounds.w));
auto& itemSchema = character.data.itemSchema;
items.emplace_back(itemSchema.anm2s.at(id), position, id);
auto& item = itemSchema.items.at(id);
auto& anm2 = itemSchema.anm2s.at(id);
auto durabilityMax = item.durability.value_or(itemSchema.durability);
auto animationIndex = durability_animation_index_get(itemSchema, anm2, 0, durabilityMax);
items.emplace_back(anm2, position, id, 0, animationIndex);
}
queuedItemIDs.clear();
@@ -97,15 +113,15 @@ namespace game::state::play
if (schema.categories[item.categoryID].isEdible)
{
auto& chewCountMax = item.chewCount.has_value() ? *item.chewCount : schema.chewCount;
auto caloriesChew = item.calories.has_value() ? *item.calories / (chewCountMax + 1) : 0;
auto isCanEat = character.calories + caloriesChew <= character.max_capacity();
auto& durabilityMax = item.durability.has_value() ? *item.durability : schema.durability;
auto caloriesPerBite = item.calories.has_value() && 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 (caloriesChew > character.capacity)
else if (caloriesPerBite > character.capacity)
text.set(dialogue.get(dialogue.lowCapacity), character);
else
text.set(dialogue.get(dialogue.full), character);
@@ -125,37 +141,44 @@ namespace game::state::play
if (character.playedEventID == eatArea.eventID)
{
heldItem->chewCount++;
heldItem->durability++;
character.consume_played_event();
auto chewAnimation = schema.animations.chew + std::to_string(heldItem->chewCount);
auto animationIndex = heldItem->chewCount > 0 ? heldItem->animationMap[chewAnimation] : -1;
heldItem->play(animationIndex, entity::Actor::SET);
character.calories += caloriesChew;
character.totalCaloriesConsumed += caloriesChew;
character.calories += caloriesPerBite;
character.totalCaloriesConsumed += caloriesPerBite;
if (item.capacityBonus.has_value())
{
character.capacity += *item.capacityBonus / durabilityMax;
character.capacity =
glm::clamp(character.capacity, (float)character.data.capacityMin, (float)character.data.capacityMax);
}
if (item.eatSpeedBonus.has_value())
{
character.eatSpeed += *item.eatSpeedBonus / (chewCountMax + 1);
character.eatSpeed =
glm::clamp(character.data.eatSpeedMin, character.eatSpeed, character.data.eatSpeedMax);
character.eatSpeed += *item.eatSpeedBonus / durabilityMax;
character.eatSpeed = glm::clamp(character.eatSpeed, (float)character.data.eatSpeedMin,
(float)character.data.eatSpeedMax);
}
if (item.digestionBonus.has_value())
{
character.digestionRate += *item.digestionBonus / (chewCountMax + 1);
character.digestionRate = glm::clamp(character.data.digestionRateMin, character.digestionRate,
character.data.digestionRateMax);
character.digestionRate += *item.digestionBonus / durabilityMax;
character.digestionRate = glm::clamp(character.digestionRate, (float)character.data.digestionRateMin,
(float)character.data.digestionRateMax);
}
if (heldItem->chewCount > chewCountMax)
if (heldItem->durability >= durabilityMax)
{
isQueueFinishFood = true;
character.totalFoodItemsEaten++;
queuedRemoveItemIndex = heldItemIndex;
heldItemIndex = -1;
}
else
{
auto animationIndex =
durability_animation_index_get(schema, *heldItem, heldItem->durability, durabilityMax);
heldItem->play(animationIndex, entity::Actor::SET);
}
}
}
@@ -177,7 +200,7 @@ namespace game::state::play
// Food stolen
if (auto animation = character.animation_get(character.animation_name_convert(eatArea.animation));
character.is_playing(animation->name))
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);
@@ -232,7 +255,7 @@ namespace game::state::play
if (isMouseRightClicked)
{
if (item.chewCount > 0)
if (item.durability > 0)
schema.sounds.dispose.play();
else
{

View File

@@ -1,5 +1,7 @@
#include "menu.hpp"
#include "style.hpp"
#include "../../util/imgui.hpp"
#include "../../util/imgui/style.hpp"
#include "../../util/imgui/widget.hpp"
@@ -8,18 +10,14 @@
using namespace game::util;
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play
{
void Menu::tick()
{
inventory.tick();
skillCheck.tick();
}
void Menu::color_set_check(Resources& resources, entity::Character& character)
{
imgui::style::color_set(resources.settings.isUseCharacterColor ? character.data.color : resources.settings.color);
arcade.tick();
}
void Menu::update(Resources& resources, ItemManager& itemManager, entity::Character& character,
@@ -28,6 +26,7 @@ namespace game::state::play
static constexpr auto WIDTH_MULTIPLIER = 0.30f;
auto& schema = character.data.menuSchema;
auto& strings = character.data.strings;
auto style = ImGui::GetStyle();
auto& io = ImGui::GetIO();
@@ -61,47 +60,41 @@ namespace game::state::play
if (ImGui::BeginTabBar("##Options"))
{
if (isChat && WIDGET_FX(ImGui::BeginTabItem("Chat")))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInteract).c_str())))
{
chat.update(resources, text, character);
interact.update(resources, text, character);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem("Arcade")))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabArcade).c_str())))
{
skillCheck.update(resources, character, inventory, text);
arcade.update(resources, character, inventory, text);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem("Inventory")))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabInventory).c_str())))
{
inventory.update(resources, itemManager, character);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem("Stats")))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabSettings).c_str())))
{
stats.update(resources, skillCheck, character);
settingsMenu.update(resources, SettingsMenu::PLAY, &strings);
if (settingsMenu.isJustColorSet) style::color_set(resources, character);
ImGui::EndTabItem();
}
if (WIDGET_FX(ImGui::BeginTabItem("Settings")))
if (isCheats && WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabCheats).c_str())))
{
settingsMenu.update(resources, SettingsMenu::PLAY);
if (settingsMenu.isJustColorSet) color_set_check(resources, character);
ImGui::EndTabItem();
}
if (isCheats && WIDGET_FX(ImGui::BeginTabItem("Cheats")))
{
cheats.update(resources, character, inventory, text);
cheats.update(resources, character, inventory);
ImGui::EndTabItem();
}
#if DEBUG
if (WIDGET_FX(ImGui::BeginTabItem("Debug")))
if (WIDGET_FX(ImGui::BeginTabItem(strings.get(Strings::MenuTabDebug).c_str())))
{
debug.update(character, cursor, itemManager, canvas);
debug.update(character, cursor, itemManager, canvas, text);
ImGui::EndTabItem();
}
#endif
@@ -128,7 +121,9 @@ namespace game::state::play
if (t <= 0.0f || t >= 1.0f)
{
ImGui::SetItemTooltip(isOpen ? "Close Main Menu" : "Open Main Menu");
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::MenuCloseTooltip
: Strings::MenuOpenTooltip)
.c_str());
if (result)
{
isOpen = !isOpen;

View File

@@ -4,11 +4,11 @@
#include "../settings_menu.hpp"
#include "arcade/skill_check.hpp"
#include "chat.hpp"
#include "arcade.hpp"
#include "cheats.hpp"
#include "debug.hpp"
#include "stats.hpp"
#include "interact.hpp"
#include "inventory.hpp"
#include "text.hpp"
#include "../../util/imgui/window_slide.hpp"
@@ -18,27 +18,24 @@ namespace game::state::play
class Menu
{
public:
SkillCheck skillCheck;
Chat chat;
Arcade arcade;
Interact interact;
Cheats cheats;
Debug debug;
Stats stats;
Inventory inventory;
state::SettingsMenu settingsMenu;
#if DEBUG
bool isCheats{true};
#elif
#else
bool isCheats{};
#endif
bool isOpen{true};
bool isChat{true};
util::imgui::WindowSlide slide{};
void tick();
void update(Resources&, ItemManager&, entity::Character&, entity::Cursor&, Text&, Canvas&);
void color_set_check(Resources&, entity::Character&);
};
}

View File

@@ -1,48 +0,0 @@
#include "stats.hpp"
#include <ranges>
#include "../../util/measurement.hpp"
using namespace game::resource;
using namespace game::util;
namespace game::state::play
{
void Stats::update(Resources& resources, SkillCheck& skillCheck, entity::Character& character)
{
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::TextUnformatted(character.data.name.c_str());
ImGui::PopFont();
ImGui::Separator();
auto& skillCheckSchema = character.data.skillCheckSchema;
auto& system = resources.settings.measurementSystem;
auto weight = character.weight_get(system);
auto weightUnit = system == measurement::IMPERIAL ? "lbs" : "kg";
ImGui::Text("Weight: %0.2f %s (Stage: %i)", weight, weightUnit, character.stage_get() + 1);
ImGui::Text("Capacity: %0.0f kcal (Max: %0.0f kcal)", character.capacity, character.max_capacity());
ImGui::Text("Digestion Rate: %0.2f%%/sec", character.digestion_rate_get());
ImGui::Text("Eating Speed: %0.2fx", character.eatSpeed);
ImGui::SeparatorText("Totals");
ImGui::Text("Total Calories Consumed: %0.0f kcal", character.totalCaloriesConsumed);
ImGui::Text("Total Food Items Eaten: %i", character.totalFoodItemsEaten);
ImGui::SeparatorText("Skill Check");
ImGui::Text("Best: %i pts (%ix)", skillCheck.highScore, skillCheck.bestCombo);
ImGui::Text("Total Skill Checks: %i", skillCheck.totalPlays);
for (int i = 0; i < (int)skillCheckSchema.grades.size(); i++)
{
auto& grade = skillCheckSchema.grades[i];
ImGui::Text("%s: %i", grade.namePlural.c_str(), skillCheck.gradeCounts[i]);
}
ImGui::Text("Accuracy: %0.2f%%", skillCheck.accuracy_score_get(character));
}
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../resources.hpp"
#include "arcade/skill_check.hpp"
#include <imgui.h>
namespace game::state::play
{
class Stats
{
public:
void update(Resources&, SkillCheck&, entity::Character&);
};
}

14
src/state/play/style.hpp Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include "../../entity/character.hpp"
#include "../../resources.hpp"
#include "../../util/imgui/style.hpp"
namespace game::state::play::style
{
inline void color_set(Resources& resources, const entity::Character& character)
{
game::util::imgui::style::color_set(resources.settings.isUseCharacterColor ? character.data.color
: resources.settings.color);
}
}

View File

@@ -37,10 +37,12 @@ namespace game::state::play
index = 0;
time = 0.0f;
isEnabled = true;
character.isTalking = true;
if (!dialogueEntry->animation.empty())
character.queue_play({.animation = dialogueEntry->animation, .isInterruptible = isInterruptible});
if (dialogueEntry->text.empty()) isEnabled = false;
if (dialogueEntry->text.empty())
isEnabled = false;
else
character.isTalking = true;
}
void Text::tick(entity::Character& character)
@@ -48,6 +50,8 @@ namespace game::state::play
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()))
{

View File

@@ -7,6 +7,7 @@
using namespace game::util;
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state::play
{
@@ -18,6 +19,7 @@ namespace game::state::play
auto style = ImGui::GetStyle();
auto& io = ImGui::GetIO();
auto& schema = character.data.menuSchema;
auto& strings = character.data.strings;
slide.update(isOpen, io.DeltaTime);
@@ -56,8 +58,9 @@ namespace game::state::play
ImGui::PopStyleColor();
};
if (WIDGET_FX(ImGui::Button("Home", buttonSize))) world.character_focus(character, canvas, focus);
ImGui::SetItemTooltip("%s", "Reset camera view.\n(Shortcut: Home)");
if (WIDGET_FX(ImGui::Button(strings.get(Strings::ToolsHomeButton).c_str(), buttonSize)))
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);
@@ -82,7 +85,9 @@ namespace game::state::play
if (t <= 0.0f || t >= 1.0f)
{
ImGui::SetItemTooltip(isOpen ? "Close Tools" : "Open Tools");
ImGui::SetItemTooltip("%s", strings.get(isOpen ? Strings::ToolsCloseTooltip
: Strings::ToolsOpenTooltip)
.c_str());
if (result)
{
isOpen = !isOpen;

View File

@@ -62,6 +62,7 @@ namespace game::state::play
{
static constexpr float MENU_WIDTH_MULTIPLIER = 0.30f;
static constexpr float TOOLS_WIDTH_MULTIPLIER = 0.10f;
static constexpr float INFO_HEIGHT_MULTIPLIER = 4.0f;
static constexpr float PADDING = 100.0f;
auto rect = character.rect();
@@ -72,13 +73,18 @@ namespace game::state::play
rect = {rect.x - PADDING * 0.5f, rect.y - PADDING * 0.5f, rect.z + PADDING, rect.w + PADDING};
auto zoomFactor = std::min((float)canvas.size.x / rect.z, (float)canvas.size.y / rect.w);
auto infoHeightPixels =
ImGui::GetTextLineHeightWithSpacing() * INFO_HEIGHT_MULTIPLIER + ImGui::GetStyle().WindowPadding.y * 2.0f;
auto usableHeightPixels = std::max(1.0f, (float)canvas.size.y - infoHeightPixels);
auto zoomFactor = std::min((float)canvas.size.x / rect.z, usableHeightPixels / rect.w);
canvas.zoom = glm::clamp(ZOOM_MIN, math::to_percent(zoomFactor), ZOOM_MAX);
zoomFactor = math::to_unit(canvas.zoom);
auto rectCenter = glm::vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f);
auto viewSizeWorld = glm::vec2(canvas.size) / zoomFactor;
canvas.pan = rectCenter - (vec2(viewSizeWorld.x, viewSizeWorld.y) * 0.5f);
auto infoHeightWorld = infoHeightPixels / zoomFactor;
canvas.pan = rectCenter - vec2(viewSizeWorld.x * 0.5f, (viewSizeWorld.y + infoHeightWorld) * 0.5f);
auto menuWidthWorld = (canvas.size.x * MENU_WIDTH_MULTIPLIER) / zoomFactor;
auto toolsWidthWorld = (canvas.size.x * TOOLS_WIDTH_MULTIPLIER) / zoomFactor;

View File

@@ -47,38 +47,51 @@ namespace game::state::select
ImGui::TextUnformatted(character.name.c_str());
ImGui::PopFont();
if (!character.description.empty())
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::TextWrapped("%s", character.description.c_str());
ImGui::PopFont();
ImGui::PopStyleColor();
}
ImGui::Separator();
ImGui::PushFont(ImGui::GetFont(), Font::BIG);
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
system == IMPERIAL ? "lbs" : "kg");
ImGui::Text("Stages: %i", character.stages);
ImGui::Separator();
ImGui::PopFont();
ImGui::PushFont(ImGui::GetFont(), Font::NORMAL);
if (ImGui::BeginTabBar("##Preview Tabs"))
{
if (ImGui::BeginTabItem("Overview"))
{
if (!character.description.empty())
{
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(imgui::to_imvec4(color::GRAY)));
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::PopFont();
if (!character.author.empty()) ImGui::TextWrapped("Author: %s", character.author.c_str());
ImGui::PopStyleColor();
}
ImGui::PopStyleColor();
ImGui::Separator();
ImGui::PushFont(ImGui::GetFont(), Font::HEADER_2);
ImGui::Text("Weight: %0.2f %s", system == IMPERIAL ? weight * KG_TO_LB : weight,
system == IMPERIAL ? "lbs" : "kg");
ImGui::Text("Stages: %i", character.stages);
ImGui::PopFont();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Credits"))
{
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());
else
ImGui::TextUnformatted("No credits listed.");
ImGui::PopFont();
ImGui::PopStyleColor();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::PopFont();
}
ImGui::EndChild();

View File

@@ -54,7 +54,7 @@ namespace game::state::select
auto renderSize = ImVec2(textureSize.x * scale, textureSize.y * scale);
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + (availableSize.x * 0.5f) - (renderSize.y * 0.5f),
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.id, renderSize);

View File

@@ -10,47 +10,61 @@
using namespace game::util;
using namespace game::util::imgui;
using namespace game::resource::xml;
namespace game::state
{
void SettingsMenu::update(Resources& resources, Mode mode)
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 string_get = [&](Strings::Type type, const char* fallback) -> const char*
{
return strings ? strings->get(type).c_str() : fallback;
};
isJustColorSet = false;
ImGui::SeparatorText("Measurement System");
WIDGET_FX(ImGui::RadioButton("Metric", (int*)&measurementSystem, measurement::METRIC));
ImGui::SetItemTooltip("%s", "Use kilograms (kg).");
ImGui::SeparatorText(string_get(Strings::SettingsMeasurementSystem, "Measurement System"));
WIDGET_FX(ImGui::RadioButton(string_get(Strings::SettingsMetric, "Metric"),
(int*)&measurementSystem, measurement::METRIC));
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsMetricTooltip, "Use kilograms (kg)."));
ImGui::SameLine();
WIDGET_FX(ImGui::RadioButton("Imperial", (int*)&measurementSystem, measurement::IMPERIAL));
ImGui::SetItemTooltip("%s", "Use pounds (lbs).");
WIDGET_FX(ImGui::RadioButton(string_get(Strings::SettingsImperial, "Imperial"),
(int*)&measurementSystem, measurement::IMPERIAL));
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsImperialTooltip, "Use pounds (lbs)."));
ImGui::SeparatorText("Sound");
if (WIDGET_FX(ImGui::SliderInt("Volume", &volume, 0, 100, "%d%%")))
ImGui::SeparatorText(string_get(Strings::SettingsSound, "Sound"));
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", "Adjust master volume.");
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsVolumeTooltip, "Adjust master volume."));
ImGui::SeparatorText("Appearance");
ImGui::SeparatorText(string_get(Strings::SettingsAppearance, "Appearance"));
if (WIDGET_FX(ImGui::Checkbox("Use Character Color", &settings.isUseCharacterColor))) isJustColorSet = true;
ImGui::SetItemTooltip("When playing, the UI will use the character's preset UI color.");
if (WIDGET_FX(ImGui::Checkbox(string_get(Strings::SettingsUseCharacterColor,
"Use Character Color"),
&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);
if (WIDGET_FX(
ImGui::ColorEdit3("Color", value_ptr(color), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
ImGui::ColorEdit3(string_get(Strings::SettingsColor, "Color"), value_ptr(color),
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip)))
{
style::color_set(color);
isJustColorSet = true;
}
ImGui::SetItemTooltip("%s", "Change the UI color.");
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsColorTooltip, "Change the UI color."));
ImGui::EndDisabled();
ImGui::Separator();
if (WIDGET_FX(ImGui::Button("Reset to Default", ImVec2(-FLT_MIN, 0))))
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);
@@ -60,11 +74,19 @@ namespace game::state
{
ImGui::Separator();
if (WIDGET_FX(ImGui::Button("Save", ImVec2(-FLT_MIN, 0)))) isSave = true;
ImGui::SetItemTooltip("%s", "Save the game.\n(Note: the game autosaves frequently.)");
if (WIDGET_FX(
ImGui::Button(string_get(Strings::SettingsSaveButton, "Save"), ImVec2(-FLT_MIN, 0))))
isSave = true;
ImGui::SetItemTooltip(
"%s", string_get(Strings::SettingsSaveTooltip,
"Save the game.\n(Note: the game autosaves frequently.)"));
if (WIDGET_FX(ImGui::Button("Return to Characters", ImVec2(-FLT_MIN, 0)))) isGoToSelect = true;
ImGui::SetItemTooltip("%s", "Go back to the character selection screen.\nProgress will be saved.");
if (WIDGET_FX(ImGui::Button(
string_get(Strings::SettingsReturnToCharactersButton, "Return to Characters"),
ImVec2(-FLT_MIN, 0))))
isGoToSelect = true;
ImGui::SetItemTooltip("%s", string_get(Strings::SettingsReturnToCharactersTooltip,
"Go back to the character selection screen.\nProgress will be saved."));
}
}
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "../resource/xml/strings.hpp"
#include "../resources.hpp"
namespace game::state
@@ -17,6 +18,6 @@ namespace game::state
bool isSave{};
bool isJustColorSet{};
void update(Resources&, Mode = SELECT);
void update(Resources&, Mode = SELECT, const resource::xml::Strings* = nullptr);
};
}

View File

@@ -18,16 +18,20 @@ namespace game::util::imgui::style
static constexpr auto COLOR_BG_ALPHA = 0.90f;
static constexpr auto COLOR_ACTIVE_MULTIPLIER = 1.30f;
static constexpr auto COLOR_HOVERED_MULTIPLIER = 1.60f;
static constexpr auto COLOR_ACTIVE_HOVERED_MIN = 0.1f;
static constexpr auto COLOR_ACTIVE_HOVERED_MAX = 0.9f;
static constexpr auto COLOR_ACCENT_MULTIPLIER = 2.0f;
static constexpr auto COLOR_ACCENT_ACTIVE_MULTIPLIER = 2.25f;
auto& colors = ImGui::GetStyle().Colors;
auto active_hovered_clamp = [](glm::vec3 color)
{ return glm::clamp(color, glm::vec3(COLOR_ACTIVE_HOVERED_MIN), glm::vec3(COLOR_ACTIVE_HOVERED_MAX)); };
auto colorNew = to_imvec4(glm::vec4(color, 1.0f));
auto colorBg = to_imvec4(glm::vec4(color * COLOR_BG_MULTIPLIER, COLOR_BG_ALPHA));
auto colorChildBg = to_imvec4(glm::vec4(color * COLOR_BG_MULTIPLIER, 0.0f));
auto colorActive = to_imvec4(glm::vec4(color * COLOR_ACTIVE_MULTIPLIER, 1.0f));
auto colorHovered = to_imvec4(glm::vec4(color * COLOR_HOVERED_MULTIPLIER, 1.0f));
auto colorActive = to_imvec4(glm::vec4(active_hovered_clamp(color * COLOR_ACTIVE_MULTIPLIER), 1.0f));
auto colorHovered = to_imvec4(glm::vec4(active_hovered_clamp(color * COLOR_HOVERED_MULTIPLIER), 1.0f));
auto colorAccent = to_imvec4(glm::vec4(color * COLOR_ACCENT_MULTIPLIER, 1.0f));
auto colorAccentActive = to_imvec4(glm::vec4(color * COLOR_ACCENT_ACTIVE_MULTIPLIER, 1.0f));

View File

@@ -7,11 +7,11 @@ namespace game::util::preferences
std::filesystem::path path()
{
#ifdef __EMSCRIPTEN__
static constexpr auto filePath = "/snivy";
static constexpr auto filePath = "/shweets-sim";
std::filesystem::create_directories(filePath);
return filePath;
#else
auto sdlPath = SDL_GetPrefPath(nullptr, "snivy");
auto sdlPath = SDL_GetPrefPath(nullptr, "shweets-sim");
if (!sdlPath) return {};
auto filePath = std::filesystem::path(sdlPath);
std::filesystem::create_directories(filePath);

View File

@@ -12,12 +12,12 @@ namespace game::util::web_filesystem
Module.filesystemReady = 0;
try
{
FS.mkdir('/snivy');
FS.mkdir('/shweets-sim');
}
catch (e)
{
}
FS.mount(IDBFS, {}, '/snivy');
FS.mount(IDBFS, {}, '/shweets-sim');
FS.syncfs(
true, function(err) {
if (err) console.error('IDBFS init sync failed', err);
@@ -50,4 +50,4 @@ namespace game::util::web_filesystem
idbfs_flush_async();
#endif
}
}
}