Shared character defaults, fixed paths and names, one global font

This commit is contained in:
shweet committed 2026-11-13 00:01:38 -05:00
1 parent 4962eac7a8
commit 7dbb4cc154
22 files changed
+434 -567

No files matched your search

+7 -7
View File
@@ -44,7 +44,7 @@ void Body::set(Character& played, Audio& sound, Game game)
void Body::stage_set(int index)
{
stage = index;
suffix = stage > 0 && stage <= (int)character->stages.size() ? character->stages[stage - 1].animationSuffix : "";
suffix = stage > 0 ? character->stage_name_get(stage) : "";
}
float Body::capacity_max_get()
@@ -65,21 +65,21 @@ bool Body::is_over_capacity()
int Body::stage_get(float weight)
{
std::vector<Stage>& stages = character->stages;
std::vector<float>& stages = character->stages;
if (stages.empty() || weight <= character->traits.weight)
return 0;
for (int i = 0; i < (int)stages.size(); i++)
if (weight < stages[i].weight)
if (weight < stages[i])
return i;
return (int)stages.size();
}
float Body::stage_threshold_get(int index)
{
std::vector<Stage>& stages = character->stages;
std::vector<float>& stages = character->stages;
if (index <= 0 || stages.empty())
return character->traits.weight;
return stages[std::min(index, (int)stages.size()) - 1].weight;
return stages[std::min(index, (int)stages.size()) - 1];
}
float Body::stage_progress_get()
@@ -132,7 +132,7 @@ void Body::values_clamp()
{
Traits& traits = character->traits;
stats.weight = value_clamp(stats.weight, traits.weight, traits.weightMax);
stats.capacity = value_clamp(stats.capacity, traits.capacityMin, traits.capacityMax);
stats.capacity = value_clamp(stats.capacity, traits.capacity, traits.capacityMax);
stats.digestionRate = value_clamp(stats.digestionRate, traits.digestionRateMin, traits.digestionRateMax);
stats.calories = value_clamp(stats.calories, 0, std::max(0.0f, capacity_max_get()));
stats.digestionProgress = value_clamp(stats.digestionProgress, 0, DIGESTION_MAX);
@@ -195,7 +195,7 @@ void Body::update(std::vector<Event>& events)
float over = stats.calories - stats.capacity;
float overPercent = over / (capacity_max_get() - stats.capacity);
stats.capacity = value_clamp(stats.capacity + overPercent * traits.overstuffedDigestBonus * over,
traits.capacityMin, traits.capacityMax);
traits.capacity, traits.capacityMax);
}
stats.calories = 0;
if (int nextStage = stage_get(nextWeight); nextStage > stage_get(stats.weight))
+124 -122
View File
@@ -1,10 +1,16 @@
#include "character.h"
#include "json.h"
#include <imgui.h>
constexpr float FONT_SIZE = 20;
constexpr const char* AREA_PATH = "gfx/areas/area.anm2";
constexpr const char* BOUNDS_NULL = "Bounds";
constexpr const char* COMMON_FOLDER = "common/";
constexpr const char* CURSOR_PATH = "gfx/cursor/cursor.anm2";
constexpr const char* DATA_FILE = "character.json";
constexpr const char* ICONS_PATH = "gfx/ui/icons.anm2";
constexpr const char* PORTRAIT_PATH = "gfx/preview/portrait.png";
constexpr const char* RENDER_PATH = "gfx/preview/render.png";
constexpr const char* SPAWN_NULL = "ItemSpawn";
constexpr const char* STAGE_PREFIX = "Stage";
constexpr const char* TAG_NAMES[] = {"animation", "big", "color", "delay", "screenshake",
"shake", "silent", "small", "sound", "wave"};
constexpr const char* USE_NAMES[] = {"", "edible", "tool"};
@@ -69,7 +75,7 @@ int Catalog::item_get(const std::string& id)
void Character::preview_load(const std::string& path, Renderer& renderer)
{
Json json;
json.load(path + "character.json");
json.load(path + DATA_FILE);
if (!(isValid = json.isValid))
return;
object root = json.root;
@@ -83,15 +89,17 @@ void Character::preview_load(const std::string& path, Renderer& renderer)
credits = json_string(root, "credits");
color = json_color(root, "color", color);
traits.weight = json_float(root, "weight");
anm2Path = json_string(root, "anm2");
portrait = root["portrait"].error() ? nullptr : renderer.texture_get(file_find(roots, json_string(root, "portrait")));
render = root["render"].error() ? nullptr : renderer.texture_get(file_find(roots, json_string(root, "render")));
auto texture_get = [&](const char* relative) {
std::string file = folder + relative;
return SDL_GetPathInfo(file.c_str(), nullptr) ? renderer.texture_get(file) : nullptr;
};
portrait = texture_get(PORTRAIT_PATH);
render = texture_get(RENDER_PATH);
for (element entry : json_array(root, "stages"))
{
object fields;
if (!entry.get(fields))
stages.push_back({json_string(fields, "animationSuffix"), json_string(fields, "dialoguePool"),
json_float(fields, "weight"), json_int(fields, "area", -1)});
double weight;
if (!entry.get(weight))
stages.push_back((float)weight);
}
}
@@ -112,14 +120,13 @@ static void interact_parse(object source, InteractArea& area, Character& charact
{
json_keys_check(source,
{"type", "null", "layer", "icon", "isHold", "animation", "idleOnEnd", "cursorHover", "cursorActive",
"dialoguePool", "dialoguePoolFull", "dialogueCooldown", "digestionClick", "digestionHover",
"dialoguePool", "dialogueCooldown", "digestionClick", "digestionHover",
"scaleAmplitude", "scaleCycles", "scaleTime", "sounds", "particles"},
"an interact area");
area.animation = json_string(source, "animation", area.animation);
area.cursorActive = json_string(source, "cursorActive", area.cursorActive);
area.cursorHover = json_string(source, "cursorHover", area.cursorHover);
area.dialoguePool = json_string(source, "dialoguePool", area.dialoguePool);
area.dialoguePoolFull = json_string(source, "dialoguePoolFull", area.dialoguePoolFull);
area.icon = json_string(source, "icon", area.icon);
area.idleOnEnd = json_string(source, "idleOnEnd", area.idleOnEnd);
area.digestionClick = json_float(source, "digestionClick", area.digestionClick);
@@ -142,30 +149,29 @@ static void interact_parse(object source, InteractArea& area, Character& charact
static void character_parse(object root, Character& character, Renderer& renderer, Audio& audio)
{
json_keys_check(root,
{"id", "name", "description", "credits", "render", "portrait", "anm2", "color", "weight", "weightMax",
"capacity", "capacityMin", "capacityMax", "overstuffedMultiplier", "overstuffedDigestBonus",
"caloriesToKilogram", "digestionRate", "digestionRateMin", "digestionRateMax", "digestionTime",
"blinkChance", "gurgleChance", "gurgleCapacityMultiplier", "dialoguePool", "alternate", "stages",
"animations", "talk", "blink", "eatAreas", "expandAreas", "sounds", "interactTypes",
"interactAreas", "areas", "cursor", "menu", "particles"},
{"id", "name", "description", "credits", "color", "weight", "weightMax", "capacity", "capacityMax",
"overstuffedMultiplier", "overstuffedDigestBonus", "caloriesToKilogram", "digestionRate",
"digestionRateMin", "digestionRateMax", "digestionTime", "blinkChance", "gurgleChance",
"gurgleCapacityMultiplier", "alternate", "stages", "animations", "talk", "blink", "eatAreas",
"expandAreas", "sounds", "interactTypes", "interactAreas", "area", "cursor", "menu", "particles"},
"character.json");
character.anm2 = character.anm2_get(character.anm2Path, renderer, audio);
Traits& traits = character.traits;
traits.weightMax = json_float(root, "weightMax", traits.weight);
traits.capacity = json_float(root, "capacity");
traits.capacityMin = json_float(root, "capacityMin");
traits.capacityMax = json_float(root, "capacityMax", traits.capacity);
traits.overstuffedMultiplier = json_float(root, "overstuffedMultiplier", 1);
traits.overstuffedDigestBonus = json_float(root, "overstuffedDigestBonus");
traits.caloriesToKilogram = json_float(root, "caloriesToKilogram");
traits.digestionRate = json_float(root, "digestionRate") / TICK_RATE;
traits.digestionRateMin = json_float(root, "digestionRateMin") / TICK_RATE;
traits.digestionRateMax = json_float(root, "digestionRateMax", traits.digestionRate * TICK_RATE) / TICK_RATE;
traits.digestionTime = ticks_get(json_float(root, "digestionTime"));
traits.blinkChance = json_float(root, "blinkChance");
traits.gurgleChance = json_float(root, "gurgleChance");
traits.gurgleCapacityMultiplier = json_float(root, "gurgleCapacityMultiplier");
character.dialoguePool = json_string(root, "dialoguePool");
auto float_read = [&](object source, const char* key, float& value, float scale = 1) {
value = json_float(source, key, value * scale) / scale;
};
float_read(root, "weightMax", traits.weightMax);
float_read(root, "capacity", traits.capacity);
float_read(root, "capacityMax", traits.capacityMax);
float_read(root, "overstuffedMultiplier", traits.overstuffedMultiplier);
float_read(root, "overstuffedDigestBonus", traits.overstuffedDigestBonus);
float_read(root, "caloriesToKilogram", traits.caloriesToKilogram);
float_read(root, "digestionRate", traits.digestionRate, TICK_RATE);
float_read(root, "digestionRateMin", traits.digestionRateMin, TICK_RATE);
float_read(root, "digestionRateMax", traits.digestionRateMax, TICK_RATE);
float_read(root, "blinkChance", traits.blinkChance);
float_read(root, "gurgleChance", traits.gurgleChance);
float_read(root, "gurgleCapacityMultiplier", traits.gurgleCapacityMultiplier);
traits.digestionTime = ticks_get(json_float(root, "digestionTime", traits.digestionTime / TICK_RATE));
object alternate = json_object(root, "alternate");
json_keys_check(alternate, {"texture", "sound", "chance", "spritesheet"}, "alternate");
@@ -173,13 +179,14 @@ static void character_parse(object root, Character& character, Renderer& rendere
character.alternate.texture = renderer.texture_get(file_find(character.roots, json_string(alternate, "texture")));
if (!alternate["sound"].error())
character.alternate.sound = audio.sound_get(file_find(character.roots, json_string(alternate, "sound")));
character.alternate.chance = json_float(alternate, "chance");
character.alternate.sheet = json_int(alternate, "spritesheet");
float_read(alternate, "chance", character.alternate.chance);
character.alternate.sheet = json_int(alternate, "spritesheet", character.alternate.sheet);
object animations = json_object(root, "animations");
json_keys_check(animations, {"start", "idle", "idleFull", "stageUp"}, "animations");
character.animations = {json_string(animations, "idle"), json_string(animations, "idleFull"),
json_string(animations, "stageUp"), json_string(animations, "start")};
Animations& names = character.animations;
names = {json_string(animations, "idle", names.idle), json_string(animations, "idleFull", names.idleFull),
json_string(animations, "stageUp", names.stageUp), json_string(animations, "start", names.start)};
Anm2* anm2 = character.anm2;
auto layer_get = [&](object source, const char* key) {
@@ -188,20 +195,17 @@ static void character_parse(object root, Character& character, Renderer& rendere
auto null_get = [&](object source, const char* key) {
return anm2 ? anm2->null_get(json_string(source, key)) : -1;
};
auto swap_get = [&](const char* key) {
object swap = json_object(root, key);
json_keys_check(swap, {"source", "destination"}, key);
return Swap{layer_get(swap, "destination"), layer_get(swap, "source")};
auto swap_read = [&](const char* key, Swap& swap) {
object fields = json_object(root, key);
json_keys_check(fields, {"source", "destination"}, key);
swap.source = fields["source"].error() ? swap.source : layer_get(fields, "source");
swap.destination = fields["destination"].error() ? swap.destination : layer_get(fields, "destination");
};
character.talk = swap_get("talk");
character.blink = swap_get("blink");
for (element entry : json_array(root, "stages"))
{
object fields;
if (!entry.get(fields))
json_keys_check(fields, {"weight", "dialoguePool", "animationSuffix", "area"}, "a stage");
}
swap_read("talk", character.talk);
swap_read("blink", character.blink);
if (!root["eatAreas"].error())
character.eatAreas.clear();
for (element entry : json_array(root, "eatAreas"))
{
object fields;
@@ -211,6 +215,8 @@ static void character_parse(object root, Character& character, Renderer& rendere
character.eatAreas.push_back({json_string(fields, "animation"),
anm2 ? anm2->event_get(json_string(fields, "event")) : -1, null_get(fields, "null")});
}
if (!root["expandAreas"].error())
character.expandAreas.clear();
for (element entry : json_array(root, "expandAreas"))
{
object fields;
@@ -221,81 +227,47 @@ static void character_parse(object root, Character& character, Renderer& rendere
{json_float(fields, "scaleMax"), layer_get(fields, "layer"), null_get(fields, "null")});
}
std::vector<std::string> typeNames;
std::vector<InteractArea>& types = character.interactTypes;
auto type_find = [&](std::string_view name) {
return std::find_if(types.begin(), types.end(), [&](InteractArea& type) { return type.name == name; });
};
for (auto [name, value] : json_object(root, "interactTypes"))
{
object fields;
if (value.get(fields))
continue;
InteractArea& type = character.interactTypes.emplace_back();
auto found = type_find(name);
InteractArea& type = found == types.end() ? types.emplace_back() : *found;
interact_parse(fields, type, character, audio);
type.name = name;
type.type = (int)typeNames.size();
typeNames.emplace_back(name);
type.type = (int)(&type - types.data());
}
if (!root["interactAreas"].error())
character.interactAreas.clear();
for (element entry : json_array(root, "interactAreas"))
{
object fields;
if (entry.get(fields))
continue;
auto type = std::find(typeNames.begin(), typeNames.end(), json_string(fields, "type"));
InteractArea area = type == typeNames.end() ? InteractArea{} : character.interactTypes[type - typeNames.begin()];
auto type = type_find(json_string(fields, "type"));
InteractArea area = type == types.end() ? InteractArea{} : *type;
interact_parse(fields, area, character, audio);
if (area.null >= 0)
character.interactAreas.push_back(area);
}
object areas = json_object(root, "areas");
json_keys_check(areas, {"anm2", "boundsNull", "spawnNull", "list"}, "areas");
Anm2* areaAnm2 = character.anm2_get(json_string(areas, "anm2"), renderer, audio);
std::string boundsNull = json_string(areas, "boundsNull", "Bounds");
std::string spawnNull = json_string(areas, "spawnNull", boundsNull);
for (element entry : json_array(areas, "list"))
{
object fields;
if (entry.get(fields))
continue;
json_keys_check(fields, {"anm2", "gravity", "friction", "airResistance"}, "an area");
Area& area = character.areas.emplace_back();
area.anm2 = fields["anm2"].error() ? areaAnm2 : character.anm2_get(json_string(fields, "anm2"), renderer, audio);
area.gravity = json_float(fields, "gravity", area.gravity);
area.friction = json_float(fields, "friction", area.friction);
area.airResistance = json_float(fields, "airResistance", area.airResistance);
if (!area.anm2)
continue;
Actor actor(area.anm2);
area.bounds = actor.null_rect_get(area.anm2->null_get(boundsNull), -1, 0);
area.spawn = actor.null_rect_get(area.anm2->null_get(spawnNull), -1, 0);
area.spawn = is_rect_valid(area.spawn) ? area.spawn : area.bounds;
}
object area = json_object(root, "area");
json_keys_check(area, {"gravity", "friction", "airResistance"}, "area");
float_read(area, "gravity", character.area.gravity);
float_read(area, "friction", character.area.friction);
float_read(area, "airResistance", character.area.airResistance);
object cursor = json_object(root, "cursor");
object cursorAnimations = json_object(cursor, "animations");
json_keys_check(cursor, {"anm2", "animations", "sounds", "particles"}, "cursor");
json_keys_check(cursorAnimations, {"idle", "click", "hover", "grab", "pan", "zoom", "dispose"}, "cursor animations");
auto cursor_get = [&](const char* key) {
return json_string(cursorAnimations, key);
};
character.cursor = {cursor_get("click"),
cursor_get("dispose"),
cursor_get("grab"),
cursor_get("hover"),
cursor_get("idle"),
cursor_get("pan"),
cursor_get("zoom"),
json_strings(json_object(cursor, "particles"), "grab"),
character.anm2_get(json_string(cursor, "anm2"), renderer, audio)};
object menu = json_object(root, "menu");
json_keys_check(menu, {"font", "rounding", "sounds"}, "menu");
auto font_get = [&](const char* key) {
std::string path = json_string(menu, key);
return path.empty() ? nullptr
: ImGui::GetIO().Fonts->AddFontFromFileTTF(file_find(character.roots, path).c_str(),
FONT_SIZE);
};
character.menu = {font_get("font"), json_float(menu, "rounding")};
character.icons = character.anm2_get(ICONS_PATH, renderer, audio);
object cursorParticles = json_object(cursor, "particles");
json_keys_check(cursor, {"sounds", "particles"}, "cursor");
if (!cursorParticles["grab"].error())
character.cursor.grabParticles = json_strings(cursorParticles, "grab");
json_keys_check(json_object(root, "menu"), {"sounds"}, "menu");
for (auto [name, value] : json_object(root, "particles"))
{
@@ -308,6 +280,27 @@ static void character_parse(object root, Character& character, Renderer& rendere
}
}
// Drops the interaction types no area uses, so a character only gets buttons for what it has.
static void interact_types_trim(Character& character)
{
std::vector<InteractArea> used;
for (InteractArea& type : character.interactTypes)
{
bool isUsed = false;
for (InteractArea& area : character.interactAreas)
if (area.type == type.type)
{
area.type = (int)used.size();
isUsed = true;
}
if (!isUsed)
continue;
used.push_back(type);
used.back().type = (int)used.size() - 1;
}
character.interactTypes = used;
}
static void items_parse(object root, Character& character, Renderer& renderer, Audio& audio)
{
json_keys_check(root,
@@ -503,8 +496,9 @@ static void sounds_parse(object root, DataFile file, Character& character, Audio
{
const SoundSource& source = SOUND_SOURCES[i];
object section = *source.section ? json_object(root, source.section) : root;
if (source.file == file)
character.sounds[i] = audio.sounds_get(character.roots, json_strings(json_object(section, "sounds"), source.key));
object sounds = json_object(section, "sounds");
if (source.file == file && !sounds[source.key].error())
character.sounds[i] = audio.sounds_get(character.roots, json_strings(sounds, source.key));
}
}
@@ -513,13 +507,27 @@ void Character::load(Renderer& renderer, Audio& audio)
if (isLoaded)
return;
isLoaded = true;
Json json;
json.load(folder + "character.json");
if (json.isValid)
anm2 = anm2_get(ANM2_PATH, renderer, audio);
cursor.anm2 = anm2_get(CURSOR_PATH, renderer, audio);
icons = anm2_get(ICONS_PATH, renderer, audio);
area.anm2 = anm2_get(AREA_PATH, renderer, audio);
if (area.anm2)
{
Actor actor(area.anm2);
area.bounds = actor.null_rect_get(area.anm2->null_get(BOUNDS_NULL), -1, 0);
area.spawn = actor.null_rect_get(area.anm2->null_get(SPAWN_NULL), -1, 0);
area.spawn = is_rect_valid(area.spawn) ? area.spawn : area.bounds;
}
Json json;
for (const std::string& root : {roots.back(), folder})
{
json.load(root + DATA_FILE);
if (!json.isValid)
continue;
character_parse(json.root, *this, renderer, audio);
sounds_parse(json.root, FILE_CHARACTER, *this, audio);
}
interact_types_trim(*this);
json.load(folder + "items.json");
if (json.isValid)
{
@@ -543,23 +551,17 @@ Actor Character::item_actor_get(int item)
return actor;
}
// The area a stage plays in: the last one a stage up to it names, or the first.
int Character::area_get(int stage)
{
if (areas.empty())
return -1;
int area = 0;
for (int i = 0; i < stage && i < (int)stages.size(); i++)
if (stages[i].area >= 0 && stages[i].area < (int)areas.size())
area = stages[i].area;
return area;
}
std::string Character::save_path_get()
{
return pref_path_get("saves/" + id + ".txt");
}
// A stage's name, which its animations end with (past the first) and its dialogue pool is called.
std::string Character::stage_name_get(int stage)
{
return STAGE_PREFIX + std::to_string(stage + 1);
}
// Every folder in resources/characters holding a character.json, in name order, with its preview loaded.
std::vector<Character> characters_load(Renderer& renderer)
{
+15 -34
View File
@@ -5,8 +5,6 @@
#include <optional>
#include <unordered_map>
struct ImFont;
enum DataFile
{
FILE_CHARACTER,
@@ -64,23 +62,23 @@ enum Hook
HOOK_COUNT
};
struct Stage
{
std::string animationSuffix;
std::string dialoguePool;
float weight = 0;
int area = -1;
};
constexpr const char* ANM2_PATH = "gfx/character/character.anm2";
constexpr const char* CURSOR_ANIMATION_CLICK = "Click";
constexpr const char* CURSOR_ANIMATION_DISPOSE = "Dispose";
constexpr const char* CURSOR_ANIMATION_GRAB = "Grab";
constexpr const char* CURSOR_ANIMATION_HOVER = "Hover";
constexpr const char* CURSOR_ANIMATION_IDLE = "Default";
constexpr const char* CURSOR_ANIMATION_PAN = "Pan";
constexpr const char* CURSOR_ANIMATION_ZOOM = "Zoom";
// A spot on the character the cursor acts on, starting from its type's settings. Types are kept the same way, for
// their tools and defaults.
// their buttons and defaults. Over capacity, the pool named with "Full" after it plays instead, if there is one.
struct InteractArea
{
std::string animation;
std::string cursorActive;
std::string cursorHover;
std::string dialoguePool;
std::string dialoguePoolFull;
std::string icon;
std::string idleOnEnd;
std::string name;
@@ -112,7 +110,7 @@ struct ExpandArea
int null = -1;
};
// A background, with the world's bounds and where items spawn taken from its nulls.
// The background, with the world's bounds and where items spawn taken from its nulls.
struct Area
{
Anm2* anm2 = nullptr;
@@ -246,7 +244,6 @@ struct Traits
float caloriesToKilogram = 0;
float capacity = 0;
float capacityMax = 0;
float capacityMin = 0;
float digestionRate = 0;
float digestionRateMax = 0;
float digestionRateMin = 0;
@@ -284,23 +281,10 @@ struct Swap
struct Cursor
{
std::string click;
std::string dispose;
std::string grab;
std::string hover;
std::string idle;
std::string pan;
std::string zoom;
std::vector<std::string> grabParticles;
Anm2* anm2 = nullptr;
};
struct Menu
{
ImFont* font = nullptr;
float rounding = 0;
};
// The items a character can be given, their rarities, and how often they spawn.
struct Catalog
{
@@ -316,35 +300,32 @@ struct Catalog
int item_get(const std::string& id);
};
// Everything a character folder holds. The preview (what the select screen shows) loads at startup, the rest when
// played.
// Everything a character folder holds, over the defaults in common's character.json. The preview (what the select
// screen shows) loads at startup, the rest when played. Stages are the weights each one starts at.
class Character
{
public:
std::array<Sounds, SOUND_COUNT> sounds;
std::string anm2Path;
std::string credits;
std::string description;
std::string dialoguePool;
std::string folder;
std::string id;
std::string key;
std::string name;
std::unordered_map<std::string, Anm2> anm2s;
std::unordered_map<std::string, Particle> particles;
std::vector<Area> areas;
std::vector<EatArea> eatAreas;
std::vector<ExpandArea> expandAreas;
std::vector<InteractArea> interactAreas;
std::vector<InteractArea> interactTypes;
std::vector<Stage> stages;
std::vector<float> stages;
Roots roots;
Alternate alternate;
Area area;
Animations animations;
Catalog catalog;
Cursor cursor;
Dialogue dialogue;
Menu menu;
Swap blink;
Swap talk;
Traits traits;
@@ -360,8 +341,8 @@ public:
void load(Renderer& renderer, Audio& audio);
Anm2* anm2_get(const std::string& path, Renderer& renderer, Audio& audio);
Actor item_actor_get(int item);
int area_get(int stage);
std::string save_path_get();
std::string stage_name_get(int stage);
};
std::vector<Character> characters_load(Renderer& renderer);
+6 -3
View File
@@ -1,5 +1,7 @@
#include "play.h"
constexpr const char* FULL_SUFFIX = "Full";
constexpr float AUTO_ACCELERATION = 0.45f;
constexpr float AUTO_ANGLE_STEP = 0.08f;
constexpr float AUTO_BRAKING = 2;
@@ -64,8 +66,9 @@ void Interactions::update(Play& play, Pointer pointer)
bool isReady = cooldowns[i] <= 0;
cooldowns[i] = std::max(0, entry.dialogueCooldown);
Dialogue& dialogue = character.dialogue;
bool isFull = body.is_over_capacity() && dialogue.pools.contains(entry.dialoguePoolFull);
const std::string& pool = isFull ? entry.dialoguePoolFull : entry.dialoguePool;
std::string pool = entry.dialoguePool;
if (body.is_over_capacity() && dialogue.pools.contains(pool + FULL_SUFFIX))
pool += FULL_SUFFIX;
if (dialogue.pools.contains(pool))
{
if (text.is_interruptible() && isReady)
@@ -92,7 +95,7 @@ void Interactions::update(Play& play, Pointer pointer)
}
if ((wasInteracting && !isInteracting) || (wasHovering && !isHovering))
active.play(character.cursor.idle);
active.play(CURSOR_ANIMATION_IDLE);
if (wasHolding && !isHolding && !body.actor.pending)
body.idle_play();
pointerPrevious = pointer.position;
+8 -9
View File
@@ -87,8 +87,7 @@ void Items::update(Play& play, Pointer pointer)
Stats& stats = body.stats;
Actor& active = play.pointer_actor_get();
Audio& audio = *play.audio;
int areaIndex = character.area_get(body.stage);
Area area = areaIndex >= 0 ? character.areas[areaIndex] : Area{};
Area& area = character.area;
Rect bounds = play.bounds;
bool isOver = body.is_over_capacity();
auto text_play = [&](Hook hook) {
@@ -97,7 +96,7 @@ void Items::update(Play& play, Pointer pointer)
};
auto release = [&](bool isThrown) {
drop();
active.play(character.cursor.idle);
active.play(CURSOR_ANIMATION_IDLE);
if (!isThrown && !body.actor.pending)
body.idle_play();
};
@@ -122,7 +121,7 @@ void Items::update(Play& play, Pointer pointer)
spawnDelay--;
for (int id : spawnQueue)
{
Rect spawn = areaIndex >= 0 ? area.spawn : bounds;
Rect spawn = area.anm2 ? area.spawn : bounds;
Vec2 position = {spawn.x + random_get(0, spawn.w), spawn.y + random_get(0, spawn.h)};
if (!discovered[id])
{
@@ -172,7 +171,7 @@ void Items::update(Play& play, Pointer pointer)
stats.calories += item.calories;
stats.totalCalories += item.calories;
stats.capacity =
std::clamp(stats.capacity + item.capacityBonus, traits.capacityMin, traits.capacityMax);
std::clamp(stats.capacity + item.capacityBonus, traits.capacity, traits.capacityMax);
stats.digestionRate = std::clamp(stats.digestionRate + item.digestionBonus,
traits.digestionRateMin, traits.digestionRateMax);
stats.totalConsumed++;
@@ -244,7 +243,7 @@ void Items::update(Play& play, Pointer pointer)
{
isHovered = true;
tooltip = held == i || pointer.isAutomated ? -1 : entity.id;
active.play(character.cursor.hover, TRANSITION_IF_IDLE);
active.play(CURSOR_ANIMATION_HOVER, TRANSITION_IF_IDLE);
play.cursorState = CURSOR_HOVER;
if (pointer.isClicked)
{
@@ -254,14 +253,14 @@ void Items::update(Play& play, Pointer pointer)
}
if (pointer.isDown)
{
active.play(character.cursor.grab);
active.play(CURSOR_ANIMATION_GRAB);
play.cursorState = CURSOR_ACTION;
held = moved = i;
}
if (pointer.isRightClicked)
{
audio.play(character.sounds[SOUND_DISPOSE]);
active.play(character.cursor.dispose, TRANSITION_FORCE, INTERRUPT_NEVER);
active.play(CURSOR_ANIMATION_DISPOSE, TRANSITION_FORCE, INTERRUPT_NEVER);
play.particles_spawn(character.catalog.disposeParticles, position);
held = held == i ? -1 : held > i ? held - 1 : held;
moved = moved == i ? -1 : moved > i ? moved - 1 : moved;
@@ -313,6 +312,6 @@ void Items::update(Play& play, Pointer pointer)
held = (int)list.size() - 1;
}
if (wasHovered && !isHovered)
active.play(character.cursor.idle);
active.play(CURSOR_ANIMATION_IDLE);
pointerPrevious = pointer.position;
}
+2 -6
View File
@@ -40,7 +40,6 @@ struct App
Renderer renderer;
Settings settings;
Ui ui;
ImFont* font = nullptr;
SDL_GLContext context = nullptr;
SDL_Window* window = nullptr;
Uint64 tickPrevious = 0;
@@ -65,7 +64,6 @@ static void play_exit()
return;
app.play.save();
app.settings.lastCharacter.clear();
ImGui::GetIO().FontDefault = app.font;
style_set(app.settings.color);
app.screen = SCREEN_SELECT;
}
@@ -80,8 +78,7 @@ static void play_start(const std::string& key, Game game)
app.ui.characterIndex = index;
app.play.set(character, game, app.renderer, app.audio);
app.settings.lastCharacter = character.key;
ImGui::GetIO().FontDefault = character.menu.font ? character.menu.font : app.font;
style_set(app.settings.isCharacterColor ? character.color : app.settings.color, character.menu.rounding);
style_set(app.settings.isCharacterColor ? character.color : app.settings.color);
app.screen = SCREEN_PLAY;
}
@@ -133,8 +130,7 @@ static void init()
ImGui::StyleColorsDark();
ImGui_ImplSDL3_InitForOpenGL(app.window, app.context);
ImGui_ImplOpenGL3_Init(GLSL_VERSION);
app.font = io.Fonts->AddFontFromFileTTF(path_get("font/font.ttf").c_str(), FONT_NORMAL);
io.FontDefault = app.font;
io.FontDefault = io.Fonts->AddFontFromFileTTF(path_get("font/font.ttf").c_str(), FONT_NORMAL);
style_set(app.settings.color);
app.ui.audio = &app.audio;
+8 -8
View File
@@ -115,10 +115,10 @@ void Play::save()
// Shows the area of the body's stage, with its bounds.
void Play::area_set()
{
int index = character->area_get(body.stage);
if (index >= 0 && area.anm2 != character->areas[index].anm2)
area = Actor(character->areas[index].anm2);
bounds = index >= 0 && is_rect_valid(character->areas[index].bounds) ? character->areas[index].bounds : WORLD_BOUNDS;
Area& world = character->area;
if (world.anm2 && area.anm2 != world.anm2)
area = Actor(world.anm2);
bounds = is_rect_valid(world.bounds) ? world.bounds : WORLD_BOUNDS;
}
void Play::toast_add(const std::string& message)
@@ -233,7 +233,7 @@ void Play::update(Vec2 screenSize)
if (cursor.anm2 && !autofeed.isActive && pointer.isClicked && !items.isHovered && !interactions.isHovering &&
cursorState == CURSOR_DEFAULT)
cursor.play(character->cursor.click, TRANSITION_FORCE, INTERRUPT_ALWAYS);
cursor.play(CURSOR_ANIMATION_CLICK, TRANSITION_FORCE, INTERRUPT_ALWAYS);
body.update(events);
events_handle(screenSize);
@@ -250,7 +250,7 @@ void Play::update(Vec2 screenSize)
{
bool isActive = (each == &autoCursor) == autofeed.isActive;
if ((!isActive || cursorState == CURSOR_DEFAULT) && !each->pending)
each->play(character->cursor.idle, TRANSITION_IF_IDLE);
each->play(CURSOR_ANIMATION_IDLE, TRANSITION_IF_IDLE);
each->update(*audio);
}
cursorState = CURSOR_DEFAULT;
@@ -323,14 +323,14 @@ void Camera::update(Play& play, Vec2 screenSize)
if (ImGui::IsMouseDown(ImGuiMouseButton_Middle) ||
(ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGui::IsKeyDown(ImGuiMod_Ctrl)))
{
play.cursor.play(cursor.pan);
play.cursor.play(CURSOR_ANIMATION_PAN);
pan = pan - Vec2{io.MouseDelta.x, io.MouseDelta.y} / (zoom / PERCENT);
}
if (io.MouseWheel != 0)
{
Vec2 mouse = {io.MousePos.x, io.MousePos.y};
Vec2 world = world_get(mouse);
play.cursor.play(cursor.zoom);
play.cursor.play(CURSOR_ANIMATION_ZOOM);
int index = std::clamp(zoom_index_get(zoom) + (io.MouseWheel > 0 ? 1 : -1), 0,
(int)std::size(ZOOM_LADDER) - 1);
zoom = ZOOM_LADDER[index];
+7 -9
View File
@@ -24,6 +24,7 @@ constexpr float INDICATOR_BOB = 0.12f;
constexpr float INDICATOR_SIZE = 0.35f;
constexpr float INDICATOR_SPEED = 2;
constexpr float ITEM_SIZE = 96;
constexpr float ROUNDING = 10;
constexpr float BAR_WIDTH_MIN = 120;
constexpr float BUTTON_SIZE_MAX = 96;
constexpr float BUTTON_SIZE_MIN = 56;
@@ -108,10 +109,10 @@ static Palette palette_get(Color color)
{ImGuiCol_ChildBg, shade(COLOR_BACKGROUND, 0)}}};
}
void style_set(Color color, float rounding)
void style_set(Color color)
{
ImGuiStyle& style = ImGui::GetStyle();
style.WindowRounding = style.FrameRounding = style.GrabRounding = style.PopupRounding = rounding;
style.WindowRounding = style.FrameRounding = style.GrabRounding = style.PopupRounding = ROUNDING;
style.ScrollbarSize = SCROLLBAR_SIZE;
style.FontSizeBase = FONT_NORMAL;
for (auto [id, value] : palette_get(color))
@@ -461,9 +462,7 @@ void Ui::taskbar_draw(Play& play, ImVec2 screen)
Items& items = play.items;
Dialogue& dialogue = data.dialogue;
ImGuiStyle& style = ImGui::GetStyle();
std::string moodPool = body.stage <= 0
? data.dialoguePool
: data.stages[std::min(body.stage, (int)data.stages.size()) - 1].dialoguePool;
std::string moodPool = data.stage_name_get(body.stage);
const char* labels[] = {"Weight", "Capacity", "Digestion"};
bool isMax = body.stage >= (int)data.stages.size();
@@ -777,8 +776,7 @@ void Ui::settings_draw(Settings& settings)
isColorChanged = true;
}
if (isColorChanged)
style_set(character && settings.isCharacterColor ? character->color : settings.color,
character ? character->menu.rounding : 0);
style_set(character && settings.isCharacterColor ? character->color : settings.color);
if (!character)
return;
ImGui::Separator();
@@ -837,7 +835,7 @@ void Ui::cheats_draw(Play& play)
Stats& stats = body.stats;
Traits& traits = data.traits;
is_fx(ImGui::SliderFloat("Calories", &stats.calories, 0, body.capacity_max_get(), "%0.0f kcal"));
is_fx(ImGui::SliderFloat("Capacity", &stats.capacity, traits.capacityMin, traits.capacityMax, "%0.0f kcal"));
is_fx(ImGui::SliderFloat("Capacity", &stats.capacity, traits.capacity, traits.capacityMax, "%0.0f kcal"));
if (is_fx(ImGui::SliderFloat("Weight", &stats.weight, traits.weight, traits.weightMax, "%0.2f kg")))
{
body.stage_set(body.stage_get(stats.weight));
@@ -1043,7 +1041,7 @@ void Ui::select_update(std::vector<Character>& characters, Settings& settings)
}
if (is_fx(ImGui::BeginTabItem("In Game")))
{
Anm2* anm2 = selected->anm2_get(selected->anm2Path, *renderer, *audio);
Anm2* anm2 = selected->anm2_get(ANM2_PATH, *renderer, *audio);
if (previewActor.anm2 != anm2 || !anm2)
previewActor = Actor(anm2);
previewActor.update(*audio);
+1 -1
View File
@@ -52,7 +52,7 @@ struct Command
using Commands = std::vector<Command>;
void style_set(Color color, float rounding = 0);
void style_set(Color color);
// The taskbar, its windows, the dialogue box, toasts and the select screen.
class Ui