Shared character defaults, fixed paths and names, one global font
This commit is contained in:
1 parent
4962eac7a8
commit
7dbb4cc154
22 files changed
+434
-567
No files matched your search
+7
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user