This commit is contained in:
@@ -1,421 +0,0 @@
|
||||
#include "actor.h"
|
||||
|
||||
#include "../util/map_.h"
|
||||
#include "../util/math_.h"
|
||||
#include "../util/unordered_map_.h"
|
||||
#include "../util/vector_.h"
|
||||
|
||||
#include "../resource/audio.h"
|
||||
#include "../resource/texture.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::util;
|
||||
using namespace game::anm2;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
Actor::Actor(Anm2* _anm2, vec2 _position, Mode mode, float time) : anm2(_anm2), position(_position)
|
||||
{
|
||||
if (anm2)
|
||||
{
|
||||
this->mode = mode;
|
||||
this->startTime = time;
|
||||
play(anm2->animations.defaultAnimation, mode, time);
|
||||
}
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get(int index)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (index == -1) index = animationIndex;
|
||||
if (anm2->animations.mapReverse.contains(index)) return &anm2->animations.items[index];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (anm2->animations.map.contains(name)) return &anm2->animations.items[anm2->animations.map[name]];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Actor::is_playing(const std::string& name)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (name.empty())
|
||||
return isPlaying;
|
||||
else
|
||||
return isPlaying && anm2->animations.map[name] == animationIndex;
|
||||
}
|
||||
|
||||
int Actor::animation_index_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return -1;
|
||||
if (anm2->animations.map.contains(name)) return anm2->animations.map[name];
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Actor::item_id_get(const std::string& name, anm2::Type type)
|
||||
{
|
||||
if (!anm2 || (type != anm2::LAYER && type != anm2::NULL_)) return -1;
|
||||
|
||||
if (type == anm2::LAYER)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.layers.size(); i++)
|
||||
if (anm2->content.layers.at(i).name == name) return i;
|
||||
}
|
||||
else if (type == anm2::NULL_)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.nulls.size(); i++)
|
||||
if (anm2->content.nulls.at(i).name == name) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id, int animationIndex)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (animationIndex == -1) animationIndex = this->animationIndex;
|
||||
if (auto animation = animation_get(animationIndex))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case anm2::ROOT:
|
||||
return &animation->rootAnimation;
|
||||
break;
|
||||
case anm2::LAYER:
|
||||
return unordered_map::find(animation->layerAnimations, id);
|
||||
case anm2::NULL_:
|
||||
return map::find(animation->nullAnimations, id);
|
||||
break;
|
||||
case anm2::TRIGGER:
|
||||
return &animation->triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Actor::item_length(anm2::Item* item)
|
||||
{
|
||||
if (!item) return -1;
|
||||
|
||||
int duration{};
|
||||
for (auto& frame : item->frames)
|
||||
duration += frame.duration;
|
||||
return duration;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::trigger_get(int atFrame)
|
||||
{
|
||||
if (auto item = item_get(anm2::TRIGGER))
|
||||
for (auto& trigger : item->frames)
|
||||
if (trigger.atFrame == atFrame) return &trigger;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::frame_get(int index, anm2::Type type, int id)
|
||||
{
|
||||
if (auto item = item_get(type, id)) return vector::find(item->frames, index);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Actor::is_event(const std::string& event)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (playedEventID == -1) return false;
|
||||
return event == anm2->content.events.at(playedEventID).name;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time, anm2::Type type, int id)
|
||||
{
|
||||
anm2::Frame frame{};
|
||||
frame.isVisible = false;
|
||||
|
||||
if (item.frames.empty()) return frame;
|
||||
|
||||
time = time < 0.0f ? 0.0f : time;
|
||||
|
||||
anm2::Frame* frameNext = nullptr;
|
||||
anm2::Frame frameNextCopy{};
|
||||
int durationCurrent = 0;
|
||||
int durationNext = 0;
|
||||
|
||||
for (int i = 0; i < item.frames.size(); i++)
|
||||
{
|
||||
anm2::Frame& checkFrame = item.frames[i];
|
||||
|
||||
frame = checkFrame;
|
||||
|
||||
durationNext += frame.duration;
|
||||
|
||||
if (time >= durationCurrent && time < durationNext)
|
||||
{
|
||||
if (i + 1 < (int)item.frames.size())
|
||||
{
|
||||
frameNext = &item.frames[i + 1];
|
||||
frameNextCopy = *frameNext;
|
||||
}
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
durationCurrent += frame.duration;
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled) continue;
|
||||
|
||||
if (id == override->destinationID)
|
||||
{
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale += *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale += *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation += *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation += *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale = *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale = *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation = *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation = *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::ITEM_SET:
|
||||
default:
|
||||
if (override->animationIndex == -1) break;
|
||||
auto& animation = anm2->animations.items[override->animationIndex];
|
||||
auto overrideFrame = frame_generate(animation.layerAnimations[override->sourceID], override->time,
|
||||
anm2::LAYER, override->sourceID);
|
||||
frame.crop = overrideFrame.crop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.isInterpolated && frameNext && frame.duration > 1)
|
||||
{
|
||||
auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent);
|
||||
|
||||
frame.rotation = glm::mix(frame.rotation, frameNextCopy.rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNextCopy.position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNextCopy.scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNextCopy.colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNextCopy.tint, interpolation);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Actor::play(int index, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
this->playedEventID = -1;
|
||||
this->playedTriggers.clear();
|
||||
|
||||
if (!anm2) return;
|
||||
if (mode != FORCE_PLAY && this->animationIndex == index) return;
|
||||
if (!vector::in_bounds(anm2->animations.items, index)) return;
|
||||
this->speedMultiplier = speedMultiplier;
|
||||
this->previousAnimationIndex = animationIndex;
|
||||
this->animationIndex = index;
|
||||
this->time = time;
|
||||
if (mode == PLAY || mode == FORCE_PLAY) isPlaying = true;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (anm2->animations.map.contains(name))
|
||||
play(anm2->animations.map.at(name), mode, time, speedMultiplier);
|
||||
else
|
||||
std::cout << "Animation \"" << name << "\" does not exist! Unable to play!\n";
|
||||
}
|
||||
|
||||
void Actor::tick()
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (!isPlaying) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
playedEventID = -1;
|
||||
|
||||
for (auto& trigger : animation->triggers.frames)
|
||||
{
|
||||
if (!playedTriggers.contains(trigger.atFrame) && time >= trigger.atFrame)
|
||||
{
|
||||
if (auto sound = map::find(anm2->content.sounds, trigger.soundID)) sound->audio.play();
|
||||
playedTriggers.insert((int)trigger.atFrame);
|
||||
playedEventID = trigger.eventID;
|
||||
}
|
||||
}
|
||||
|
||||
auto increment = (anm2->info.fps / 30.0f) * speedMultiplier;
|
||||
time += increment;
|
||||
|
||||
if (time >= animation->frameNum)
|
||||
{
|
||||
if (animation->isLoop)
|
||||
time = 0.0f;
|
||||
else
|
||||
isPlaying = false;
|
||||
|
||||
playedTriggers.clear();
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override->isEnabled || override->length < 0) continue;
|
||||
override->time += increment;
|
||||
if (override->time > override->length) override->isLoop ? override->time = 0.0f : override->isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec4 Actor::null_frame_rect(int nullID)
|
||||
{
|
||||
auto invalidRect = glm::vec4(0.0f / 0.0f);
|
||||
if (!anm2 || nullID == -1) return invalidRect;
|
||||
auto item = item_get(anm2::NULL_, nullID);
|
||||
if (!item) return invalidRect;
|
||||
|
||||
auto animation = animation_get();
|
||||
if (!animation) return invalidRect;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto frame = frame_generate(*item, time, anm2::NULL_, nullID);
|
||||
if (!frame.isVisible) return invalidRect;
|
||||
|
||||
auto rootScale = math::to_unit(root.scale);
|
||||
auto frameScale = math::to_unit(frame.scale);
|
||||
auto combinedScale = rootScale * frameScale;
|
||||
auto scaledSize = NULL_SIZE * glm::abs(combinedScale);
|
||||
|
||||
auto worldPosition = position + root.position + frame.position * rootScale;
|
||||
auto halfSize = scaledSize * 0.5f;
|
||||
|
||||
return glm::vec4(worldPosition - halfSize, scaledSize);
|
||||
}
|
||||
|
||||
void Actor::render(Shader& textureShader, Shader& rectShader, Canvas& canvas)
|
||||
{
|
||||
if (!anm2) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto rootModel =
|
||||
math::quad_model_parent_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation);
|
||||
|
||||
for (auto& i : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations[i];
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto layer = map::find(anm2->content.layers, i);
|
||||
if (!layer) continue;
|
||||
|
||||
auto spritesheet = map::find(anm2->content.spritesheets, layer->spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
auto frame = frame_generate(layerAnimation, time, anm2::LAYER, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model =
|
||||
math::quad_model_get(frame.size, frame.position, frame.pivot, math::to_unit(frame.scale), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) return;
|
||||
|
||||
auto tint = frame.tint * root.tint;
|
||||
auto colorOffset = frame.colorOffset + root.colorOffset;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto uvVertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
|
||||
canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data());
|
||||
}
|
||||
|
||||
if (isShowNulls)
|
||||
{
|
||||
for (int i = 0; i < animation->nullAnimations.size(); i++)
|
||||
{
|
||||
auto& nullAnimation = animation->nullAnimations[i];
|
||||
if (!nullAnimation.isVisible) continue;
|
||||
|
||||
auto frame = frame_generate(nullAnimation, time, anm2::NULL_, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, vec2(1.0f), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
canvas.rect_render(rectShader, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::consume_played_event() { playedEventID = -1; }
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../canvas.h"
|
||||
#include "anm2.h"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Actor
|
||||
{
|
||||
|
||||
public:
|
||||
static constexpr auto NULL_SIZE = glm::vec2(100, 100);
|
||||
|
||||
enum Mode
|
||||
{
|
||||
PLAY,
|
||||
SET,
|
||||
FORCE_PLAY
|
||||
};
|
||||
|
||||
struct Override
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
ITEM_SET,
|
||||
FRAME_ADD,
|
||||
FRAME_SET
|
||||
};
|
||||
|
||||
anm2::FrameOptional frame{};
|
||||
int animationIndex{-1};
|
||||
int sourceID{-1};
|
||||
int destinationID{-1};
|
||||
float length{-1.0f};
|
||||
bool isLoop{false};
|
||||
Mode mode{ITEM_SET};
|
||||
anm2::Type type{anm2::LAYER};
|
||||
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
};
|
||||
|
||||
anm2::Anm2* anm2{};
|
||||
glm::vec2 position{};
|
||||
float time{};
|
||||
bool isPlaying{};
|
||||
bool isShowNulls{};
|
||||
int animationIndex{-1};
|
||||
int previousAnimationIndex{-1};
|
||||
int lastPlayedAnimationIndex{-1};
|
||||
int playedEventID{-1};
|
||||
Mode mode{PLAY};
|
||||
float startTime{};
|
||||
float speedMultiplier{};
|
||||
|
||||
std::unordered_set<int> playedTriggers{};
|
||||
std::vector<Override*> overrides{};
|
||||
|
||||
Actor(anm2::Anm2*, glm::vec2, Mode = PLAY, float = 0.0f);
|
||||
anm2::Animation* animation_get(int = -1);
|
||||
anm2::Animation* animation_get(const std::string&);
|
||||
int animation_index_get(const std::string&);
|
||||
anm2::Item* item_get(anm2::Type, int = -1, int = -1);
|
||||
int item_length(anm2::Item*);
|
||||
anm2::Frame* trigger_get(int);
|
||||
anm2::Frame* frame_get(int, anm2::Type, int = -1);
|
||||
int item_id_get(const std::string&, anm2::Type = anm2::LAYER);
|
||||
anm2::Frame frame_generate(anm2::Item&, float, anm2::Type, int = -1);
|
||||
void play(const std::string&, Mode = PLAY, float = 0.0f, float = 1.0f);
|
||||
void play(int, Mode = PLAY, float = 0.0f, float = 1.0f);
|
||||
bool is_event(const std::string& event);
|
||||
void tick();
|
||||
bool is_playing(const std::string& name = {});
|
||||
void render(Shader& textureShader, Shader& rectShader, Canvas&);
|
||||
glm::vec4 null_frame_rect(int = -1);
|
||||
void consume_played_event();
|
||||
};
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../util/xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::anm2
|
||||
{
|
||||
Info::Info(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Fps", &fps);
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
texture = Texture(path);
|
||||
}
|
||||
|
||||
Layer::Layer(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("SpritesheetId", &spritesheetID);
|
||||
}
|
||||
|
||||
Null::Null(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryBoolAttribute("ShowRect", &isShowRect);
|
||||
}
|
||||
|
||||
Event::Event(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
Sound::Sound(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
audio = Audio(path);
|
||||
}
|
||||
|
||||
Content::Content(XMLElement* element)
|
||||
{
|
||||
if (auto spritesheetsElement = element->FirstChildElement("Spritesheets"))
|
||||
{
|
||||
for (auto child = spritesheetsElement->FirstChildElement("Spritesheet"); child;
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
{
|
||||
int spritesheetId{};
|
||||
Spritesheet spritesheet(child, spritesheetId);
|
||||
spritesheets.emplace(spritesheetId, std::move(spritesheet));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto layersElement = element->FirstChildElement("Layers"))
|
||||
{
|
||||
for (auto child = layersElement->FirstChildElement("Layer"); child; child = child->NextSiblingElement("Layer"))
|
||||
{
|
||||
int layerId{};
|
||||
Layer layer(child, layerId);
|
||||
layers.emplace(layerId, std::move(layer));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullsElement = element->FirstChildElement("Nulls"))
|
||||
{
|
||||
for (auto child = nullsElement->FirstChildElement("Null"); child; child = child->NextSiblingElement("Null"))
|
||||
{
|
||||
int nullId{};
|
||||
Null null(child, nullId);
|
||||
nulls.emplace(nullId, std::move(null));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto eventsElement = element->FirstChildElement("Events"))
|
||||
{
|
||||
for (auto child = eventsElement->FirstChildElement("Event"); child; child = child->NextSiblingElement("Event"))
|
||||
{
|
||||
int eventId{};
|
||||
Event event(child, eventId);
|
||||
events.emplace(eventId, std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto soundsElement = element->FirstChildElement("Sounds"))
|
||||
{
|
||||
for (auto child = soundsElement->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
int soundId{};
|
||||
Sound sound(child, soundId);
|
||||
sounds.emplace(soundId, std::move(sound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Frame::Frame(XMLElement* element, Type type)
|
||||
{
|
||||
if (type != TRIGGER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPivot", &pivot.x);
|
||||
element->QueryFloatAttribute("YPivot", &pivot.y);
|
||||
element->QueryFloatAttribute("XCrop", &crop.x);
|
||||
element->QueryFloatAttribute("YCrop", &crop.y);
|
||||
element->QueryFloatAttribute("Width", &size.x);
|
||||
element->QueryFloatAttribute("Height", &size.y);
|
||||
}
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &duration);
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
xml::query_color_attribute(element, "RedTint", &tint.r);
|
||||
xml::query_color_attribute(element, "GreenTint", &tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", &tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", &tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", &colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", &colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", &colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
else
|
||||
{
|
||||
element->QueryIntAttribute("EventId", &eventID);
|
||||
element->QueryIntAttribute("SoundId", &soundID);
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
}
|
||||
}
|
||||
|
||||
Item::Item(XMLElement* element, Type type, int& id)
|
||||
{
|
||||
if (type == LAYER) element->QueryIntAttribute("LayerId", &id);
|
||||
if (type == NULL_) element->QueryIntAttribute("NullId", &id);
|
||||
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
|
||||
for (auto child = type == TRIGGER ? element->FirstChildElement("Trigger") : element->FirstChildElement("Frame");
|
||||
child; child = type == TRIGGER ? child->NextSiblingElement("Trigger") : child->NextSiblingElement("Frame"))
|
||||
frames.emplace_back(Frame(child, type));
|
||||
}
|
||||
|
||||
Animation::Animation(XMLElement* element)
|
||||
{
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("FrameNum", &frameNum);
|
||||
element->QueryBoolAttribute("Loop", &isLoop);
|
||||
|
||||
int id{-1};
|
||||
|
||||
if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
|
||||
rootAnimation = Item(rootAnimationElement, ROOT, id);
|
||||
|
||||
if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
|
||||
{
|
||||
for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child;
|
||||
child = child->NextSiblingElement("LayerAnimation"))
|
||||
{
|
||||
Item layerAnimation(child, LAYER, id);
|
||||
layerOrder.push_back(id);
|
||||
layerAnimations.emplace(id, std::move(layerAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
|
||||
{
|
||||
for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
|
||||
child = child->NextSiblingElement("NullAnimation"))
|
||||
{
|
||||
Item nullAnimation(child, NULL_, id);
|
||||
nullAnimations.emplace(id, std::move(nullAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto triggersElement = element->FirstChildElement("Triggers")) triggers = Item(triggersElement, TRIGGER, id);
|
||||
}
|
||||
|
||||
Animations::Animations(XMLElement* element)
|
||||
{
|
||||
xml::query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
items.emplace_back(Animation(child));
|
||||
|
||||
for (int i = 0; i < items.size(); i++)
|
||||
{
|
||||
auto& item = items.at(i);
|
||||
map[item.name] = i;
|
||||
mapReverse[i] = item.name;
|
||||
}
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
std::cout << "Failed to initialize anm2: " << document.ErrorStr() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto previousPath = std::filesystem::current_path();
|
||||
std::filesystem::current_path(path.parent_path());
|
||||
|
||||
auto element = document.RootElement();
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) info = Info(infoElement);
|
||||
if (auto contentElement = element->FirstChildElement("Content")) content = Content(contentElement);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations")) animations = Animations(animationsElement);
|
||||
|
||||
std::filesystem::current_path(previousPath);
|
||||
|
||||
std::cout << "Initialzed anm2: " << path.string() << "\n";
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "audio.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace game::anm2
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
NONE,
|
||||
ROOT,
|
||||
LAYER,
|
||||
NULL_,
|
||||
TRIGGER
|
||||
};
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
int fps = 30;
|
||||
|
||||
Info() = default;
|
||||
Info(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture{};
|
||||
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{-1};
|
||||
Layer(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Null
|
||||
{
|
||||
public:
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
Null(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Audio audio{};
|
||||
|
||||
Sound(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Content
|
||||
{
|
||||
public:
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
Content() = default;
|
||||
Content(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
struct Frame
|
||||
{
|
||||
glm::vec2 crop{};
|
||||
glm::vec2 position{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 size{};
|
||||
glm::vec2 scale{100, 100};
|
||||
float rotation{};
|
||||
int duration{};
|
||||
glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glm::vec3 colorOffset{};
|
||||
bool isInterpolated{};
|
||||
int eventID{-1};
|
||||
int soundID{-1};
|
||||
int atFrame{-1};
|
||||
|
||||
bool isVisible{true};
|
||||
|
||||
Frame() = default;
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
};
|
||||
|
||||
struct FrameOptional
|
||||
{
|
||||
std::optional<glm::vec2> crop{};
|
||||
std::optional<glm::vec2> position{};
|
||||
std::optional<glm::vec2> pivot{};
|
||||
std::optional<glm::vec2> size{};
|
||||
std::optional<glm::vec2> scale{};
|
||||
std::optional<float> rotation{};
|
||||
std::optional<glm::vec4> tint{};
|
||||
std::optional<glm::vec3> colorOffset{};
|
||||
std::optional<bool> isInterpolated{};
|
||||
std::optional<bool> isVisible{};
|
||||
};
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{};
|
||||
|
||||
Item() = default;
|
||||
Item(tinyxml2::XMLElement*, Type, int&);
|
||||
};
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{};
|
||||
bool isLoop{};
|
||||
|
||||
Item rootAnimation{};
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers{};
|
||||
|
||||
Animation() = default;
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Animations
|
||||
{
|
||||
public:
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
std::unordered_map<std::string, int> map{};
|
||||
std::unordered_map<int, std::string> mapReverse{};
|
||||
|
||||
Animations() = default;
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
Info info;
|
||||
Content content{};
|
||||
Animations animations{};
|
||||
|
||||
Anm2() = default;
|
||||
Anm2(const std::filesystem::path&);
|
||||
};
|
||||
}
|
||||
@@ -1,73 +1,119 @@
|
||||
#include "audio.h"
|
||||
#include "audio.hpp"
|
||||
|
||||
#include <SDL3/SDL_properties.h>
|
||||
|
||||
#include <iostream>
|
||||
#include "../log.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
static std::shared_ptr<MIX_Audio> audio_make(MIX_Audio* audio)
|
||||
{
|
||||
return std::shared_ptr<MIX_Audio>(audio,
|
||||
[](MIX_Audio* a)
|
||||
{
|
||||
if (a) MIX_DestroyAudio(a);
|
||||
});
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, std::weak_ptr<MIX_Audio>> audioCache{};
|
||||
|
||||
static std::shared_ptr<MIX_Audio> cache_get(const std::string& key)
|
||||
{
|
||||
auto it = audioCache.find(key);
|
||||
if (it == audioCache.end()) return {};
|
||||
|
||||
auto cached = it->second.lock();
|
||||
if (!cached) audioCache.erase(it);
|
||||
return cached;
|
||||
}
|
||||
|
||||
static void cache_set(const std::string& key, const std::shared_ptr<MIX_Audio>& audio)
|
||||
{
|
||||
if (!audio) return;
|
||||
audioCache[key] = audio;
|
||||
}
|
||||
|
||||
MIX_Mixer* Audio::mixer_get()
|
||||
{
|
||||
static auto mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||
return mixer;
|
||||
}
|
||||
|
||||
void Audio::set_gain(float gain)
|
||||
void Audio::volume_set(float volume)
|
||||
{
|
||||
auto mixer = mixer_get();
|
||||
MIX_SetMasterGain(mixer, gain);
|
||||
}
|
||||
|
||||
void Audio::retain()
|
||||
{
|
||||
if (refCount) ++(*refCount);
|
||||
}
|
||||
|
||||
void Audio::release()
|
||||
{
|
||||
if (refCount)
|
||||
{
|
||||
if (--(*refCount) == 0)
|
||||
{
|
||||
if (internal) MIX_DestroyAudio(internal);
|
||||
delete refCount;
|
||||
}
|
||||
refCount = nullptr;
|
||||
}
|
||||
internal = nullptr;
|
||||
MIX_SetMasterGain(mixer, volume);
|
||||
}
|
||||
|
||||
Audio::Audio(const std::filesystem::path& path)
|
||||
{
|
||||
internal = MIX_LoadAudio(mixer_get(), path.c_str(), true);
|
||||
auto key = std::string("fs:") + path.string();
|
||||
internal = cache_get(key);
|
||||
if (internal)
|
||||
{
|
||||
refCount = new int(1);
|
||||
std::cout << "Initialized audio: '" << path.string() << "'\n";
|
||||
logger.info(std::format("Using cached audio: {}", path.string()));
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
internal = audio_make(MIX_LoadAudio(mixer_get(), path.c_str(), true));
|
||||
cache_set(key, internal);
|
||||
if (internal) logger.info(std::format("Initialized audio: {}", path.string()));
|
||||
|
||||
if (!internal) logger.info(std::format("Failed to intialize audio: {} ({})", path.string(), SDL_GetError()));
|
||||
}
|
||||
|
||||
Audio::Audio(const physfs::Path& path)
|
||||
{
|
||||
if (!path.is_valid())
|
||||
{
|
||||
std::cout << "Failed to initialize audio: '" << path.string() << "'\n";
|
||||
logger.error(
|
||||
std::format("Failed to initialize audio from PhysicsFS path: {}", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = std::string("physfs:") + path.c_str();
|
||||
internal = cache_get(key);
|
||||
if (internal)
|
||||
{
|
||||
logger.info(std::format("Using cached audio: {}", path.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = path.read();
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to initialize audio from PhysicsFS path: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto ioStream = SDL_IOFromConstMem(buffer.data(), buffer.size());
|
||||
|
||||
internal = audio_make(MIX_LoadAudio_IO(mixer_get(), ioStream, false, true));
|
||||
cache_set(key, internal);
|
||||
if (internal)
|
||||
logger.info(std::format("Initialized audio: {}", path.c_str()));
|
||||
else
|
||||
logger.info(std::format("Failed to intialize audio: {} ({})", path.c_str(), SDL_GetError()));
|
||||
}
|
||||
|
||||
Audio::Audio(const Audio& other)
|
||||
{
|
||||
internal = other.internal;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
track = nullptr;
|
||||
}
|
||||
|
||||
Audio::Audio(Audio&& other) noexcept
|
||||
{
|
||||
internal = other.internal;
|
||||
internal = std::move(other.internal);
|
||||
track = other.track;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.internal = nullptr;
|
||||
other.track = nullptr;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(const Audio& other)
|
||||
@@ -76,8 +122,7 @@ namespace game::resource
|
||||
{
|
||||
unload();
|
||||
internal = other.internal;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
track = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -87,13 +132,10 @@ namespace game::resource
|
||||
if (this != &other)
|
||||
{
|
||||
unload();
|
||||
internal = other.internal;
|
||||
internal = std::move(other.internal);
|
||||
track = other.track;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.internal = nullptr;
|
||||
other.track = nullptr;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -105,7 +147,7 @@ namespace game::resource
|
||||
MIX_DestroyTrack(track);
|
||||
track = nullptr;
|
||||
}
|
||||
release();
|
||||
internal.reset();
|
||||
}
|
||||
|
||||
void Audio::play(bool isLoop)
|
||||
@@ -126,7 +168,7 @@ namespace game::resource
|
||||
if (!track) return;
|
||||
}
|
||||
|
||||
MIX_SetTrackAudio(track, internal);
|
||||
MIX_SetTrackAudio(track, internal.get());
|
||||
|
||||
SDL_PropertiesID options = 0;
|
||||
|
||||
@@ -149,5 +191,5 @@ namespace game::resource
|
||||
bool Audio::is_playing() const { return track && MIX_TrackPlaying(track); }
|
||||
|
||||
Audio::~Audio() { unload(); }
|
||||
bool Audio::is_valid() const { return internal != nullptr; }
|
||||
bool Audio::is_valid() const { return (bool)internal; }
|
||||
}
|
||||
|
||||
@@ -2,22 +2,24 @@
|
||||
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "../util/physfs.hpp"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Audio
|
||||
{
|
||||
MIX_Audio* internal{nullptr};
|
||||
MIX_Track* track{nullptr};
|
||||
int* refCount{nullptr};
|
||||
static MIX_Mixer* mixer_get();
|
||||
void unload();
|
||||
void retain();
|
||||
void release();
|
||||
|
||||
std::shared_ptr<MIX_Audio> internal{};
|
||||
MIX_Track* track{nullptr};
|
||||
|
||||
public:
|
||||
Audio() = default;
|
||||
Audio(const std::filesystem::path&);
|
||||
Audio(const util::physfs::Path&);
|
||||
Audio(const Audio&);
|
||||
Audio(Audio&&) noexcept;
|
||||
Audio& operator=(const Audio&);
|
||||
@@ -27,6 +29,6 @@ namespace game::resource
|
||||
void play(bool isLoop = false);
|
||||
void stop();
|
||||
bool is_playing() const;
|
||||
static void set_gain(float vol);
|
||||
static void volume_set(float volume);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "dialogue.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../util/map_.h"
|
||||
#include "../util/xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
void label_map_query(XMLElement* element, std::map<std::string, int>& labelMap, const char* attribute, int& id)
|
||||
{
|
||||
std::string label{};
|
||||
xml::query_string_attribute(element, attribute, &label);
|
||||
if (auto foundID = map::find(labelMap, label))
|
||||
id = *foundID;
|
||||
else
|
||||
id = -1;
|
||||
}
|
||||
|
||||
Dialogue::Color::Color(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Start", &start);
|
||||
element->QueryIntAttribute("End", &end);
|
||||
xml::query_color_attribute(element, "R", &value.r);
|
||||
xml::query_color_attribute(element, "G", &value.g);
|
||||
xml::query_color_attribute(element, "B", &value.b);
|
||||
xml::query_color_attribute(element, "A", &value.a);
|
||||
}
|
||||
|
||||
Dialogue::Animation::Animation(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("At", &at);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
Dialogue::Branch::Branch(XMLElement* element, std::map<std::string, int>& labelMap)
|
||||
{
|
||||
if (!element) return;
|
||||
label_map_query(element, labelMap, "Label", nextID);
|
||||
xml::query_string_attribute(element, "Content", &content);
|
||||
}
|
||||
|
||||
Dialogue::Entry::Entry(XMLElement* element, std::map<std::string, int>& labelMap)
|
||||
{
|
||||
if (!element) return;
|
||||
|
||||
xml::query_string_attribute(element, "Content", &content);
|
||||
label_map_query(element, labelMap, "Next", nextID);
|
||||
|
||||
std::string flagString{};
|
||||
xml::query_string_attribute(element, "Flag", &flagString);
|
||||
|
||||
for (int i = 0; i < std::size(FLAG_STRINGS); i++)
|
||||
{
|
||||
if (flagString == FLAG_STRINGS[i])
|
||||
{
|
||||
flag = (Flag)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto child = element->FirstChildElement("Color"); child; child = child->NextSiblingElement("Color"))
|
||||
colors.emplace_back(child);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
animations.emplace_back(child);
|
||||
|
||||
for (auto child = element->FirstChildElement("Branch"); child; child = child->NextSiblingElement("Branch"))
|
||||
branches.emplace_back(child, labelMap);
|
||||
}
|
||||
|
||||
Dialogue::Dialogue(const std::string& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
std::cout << "Failed to initialize dialogue: " << document.ErrorStr() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto element = document.RootElement();
|
||||
int id{};
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
{
|
||||
std::string label{};
|
||||
xml::query_string_attribute(child, "Label", &label);
|
||||
labelMap.emplace(label, id++);
|
||||
}
|
||||
|
||||
id = 0;
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
entryMap.emplace(id++, Entry(child, labelMap));
|
||||
|
||||
for (auto& [label, id] : labelMap)
|
||||
{
|
||||
if (label.starts_with(BURP_SMALL_LABEL)) burpSmallIDs.emplace_back(id);
|
||||
if (label.starts_with(BURP_BIG_LABEL)) burpBigIDs.emplace_back(id);
|
||||
if (label.starts_with(EAT_HUNGRY_LABEL)) eatHungryIDs.emplace_back(id);
|
||||
if (label.starts_with(EAT_FULL_LABEL)) eatFullIDs.emplace_back(id);
|
||||
if (label.starts_with(FULL_LABEL)) fullIDs.emplace_back(id);
|
||||
if (label.starts_with(CAPACITY_LOW_LABEL)) capacityLowIDs.emplace_back(id);
|
||||
if (label.starts_with(FEED_HUNGRY_LABEL)) feedHungryIDs.emplace_back(id);
|
||||
if (label.starts_with(FEED_FULL_LABEL)) feedFullIDs.emplace_back(id);
|
||||
if (label.starts_with(FOOD_STOLEN_LABEL)) foodStolenIDs.emplace_back(id);
|
||||
if (label.starts_with(FOOD_EASED_LABEL)) foodEasedIDs.emplace_back(id);
|
||||
if (label.starts_with(PERFECT_LABEL)) perfectIDs.emplace_back(id);
|
||||
if (label.starts_with(MISS_LABEL)) missIDs.emplace_back(id);
|
||||
if (label.starts_with(POST_DIGEST_LABEL)) postDigestIDs.emplace_back(id);
|
||||
if (label.starts_with(RANDOM_LABEL)) randomIDs.emplace_back(id);
|
||||
}
|
||||
|
||||
std::cout << "Initialzed dialogue: " << path << "\n";
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::get(int id)
|
||||
{
|
||||
if (id == -1) return nullptr;
|
||||
return map::find(entryMap, id);
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::get(const std::string& label)
|
||||
{
|
||||
auto id = map::find(labelMap, label);
|
||||
if (!id) return nullptr;
|
||||
return get(*id);
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::next(Dialogue::Entry* entry) { return get(entry->nextID); }
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/ext/vector_float4.hpp"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Dialogue
|
||||
{
|
||||
public:
|
||||
static constexpr auto FULL_LABEL = "Full";
|
||||
static constexpr auto POST_DIGEST_LABEL = "PostDigest";
|
||||
static constexpr auto BURP_SMALL_LABEL = "BurpSmall";
|
||||
static constexpr auto BURP_BIG_LABEL = "BurpBig";
|
||||
static constexpr auto FEED_HUNGRY_LABEL = "FeedHungry";
|
||||
static constexpr auto FEED_FULL_LABEL = "FeedFull";
|
||||
static constexpr auto EAT_HUNGRY_LABEL = "EatHungry";
|
||||
static constexpr auto EAT_FULL_LABEL = "EatFull";
|
||||
static constexpr auto FOOD_STOLEN_LABEL = "FoodStolen";
|
||||
static constexpr auto FOOD_EASED_LABEL = "FoodEased";
|
||||
static constexpr auto CAPACITY_LOW_LABEL = "CapacityLow";
|
||||
static constexpr auto PERFECT_LABEL = "Perfect";
|
||||
static constexpr auto MISS_LABEL = "Miss";
|
||||
static constexpr auto RANDOM_LABEL = "StartRandom";
|
||||
;
|
||||
|
||||
class Color
|
||||
{
|
||||
public:
|
||||
int start{};
|
||||
int end{};
|
||||
glm::vec4 value{};
|
||||
|
||||
Color(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
int at{-1};
|
||||
std::string name{};
|
||||
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Branch
|
||||
{
|
||||
public:
|
||||
std::string content{};
|
||||
int nextID{-1};
|
||||
|
||||
Branch(tinyxml2::XMLElement*, std::map<std::string, int>&);
|
||||
};
|
||||
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
#define FLAGS \
|
||||
X(NONE, "None") \
|
||||
X(ACTIVATE_WINDOWS, "ActivateWindows") \
|
||||
X(DEACTIVATE_WINDOWS, "DeactivateWindows") \
|
||||
X(ONLY_INFO, "OnlyInfo") \
|
||||
X(ACTIVATE_CHEATS, "ActivateCheats")
|
||||
|
||||
enum Flag
|
||||
{
|
||||
#define X(symbol, name) symbol,
|
||||
FLAGS
|
||||
#undef X
|
||||
};
|
||||
|
||||
static constexpr const char* FLAG_STRINGS[] = {
|
||||
#define X(symbol, name) name,
|
||||
FLAGS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef FLAGS
|
||||
|
||||
std::string content{};
|
||||
std::vector<Color> colors{};
|
||||
std::vector<Animation> animations{};
|
||||
std::vector<Branch> branches{};
|
||||
int nextID{-1};
|
||||
Flag flag{Flag::NONE};
|
||||
|
||||
Entry(tinyxml2::XMLElement*, std::map<std::string, int>&);
|
||||
};
|
||||
|
||||
std::map<std::string, int> labelMap;
|
||||
std::map<int, Entry> entryMap{};
|
||||
|
||||
std::vector<int> eatHungryIDs{};
|
||||
std::vector<int> eatFullIDs{};
|
||||
std::vector<int> feedHungryIDs{};
|
||||
std::vector<int> feedFullIDs{};
|
||||
std::vector<int> burpSmallIDs{};
|
||||
std::vector<int> burpBigIDs{};
|
||||
std::vector<int> fullIDs{};
|
||||
std::vector<int> foodStolenIDs{};
|
||||
std::vector<int> foodEasedIDs{};
|
||||
std::vector<int> perfectIDs{};
|
||||
std::vector<int> postDigestIDs{};
|
||||
std::vector<int> missIDs{};
|
||||
std::vector<int> capacityLowIDs{};
|
||||
std::vector<int> randomIDs{};
|
||||
|
||||
Dialogue(const std::string&);
|
||||
Entry* get(const std::string&);
|
||||
Entry* get(int = -1);
|
||||
Entry* next(Entry*);
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,52 @@
|
||||
#include "font.h"
|
||||
#include "font.hpp"
|
||||
|
||||
#include "../log.hpp"
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
Font::Font(const std::string& path, float size)
|
||||
Font::Font(const std::filesystem::path& path, float size)
|
||||
{
|
||||
internal = ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size);
|
||||
ImFontConfig config;
|
||||
config.FontDataOwnedByAtlas = false;
|
||||
|
||||
internal = ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size, &config);
|
||||
|
||||
if (internal)
|
||||
logger.info(std::format("Initialized font: {}", path.c_str()));
|
||||
else
|
||||
logger.error(std::format("Failed to initialize font: {}", path.c_str()));
|
||||
}
|
||||
|
||||
Font::Font(const physfs::Path& path, float size)
|
||||
{
|
||||
if (!path.is_valid())
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to initialize font from PhysicsFS path: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = path.read();
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to initialize font from PhysicsFS path: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
ImFontConfig config;
|
||||
config.FontDataOwnedByAtlas = false;
|
||||
|
||||
internal = ImGui::GetIO().Fonts->AddFontFromMemoryTTF(buffer.data(), (int)buffer.size(), size, &config);
|
||||
|
||||
if (internal)
|
||||
logger.info(std::format("Initialized font: {}", path.c_str()));
|
||||
else
|
||||
logger.error(std::format("Failed to initialize font: {}", path.c_str()));
|
||||
}
|
||||
|
||||
ImFont* Font::get() { return internal; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../util/physfs.hpp"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Font
|
||||
{
|
||||
ImFont* internal{};
|
||||
|
||||
public:
|
||||
ImFont* internal;
|
||||
static constexpr auto NORMAL = 20;
|
||||
static constexpr auto ABOVE_AVERAGE = 24;
|
||||
static constexpr auto BIG = 30;
|
||||
static constexpr auto HEADER_3 = 40;
|
||||
static constexpr auto HEADER_2 = 50;
|
||||
static constexpr auto HEADER_1 = 60;
|
||||
|
||||
static constexpr auto NORMAL = 12;
|
||||
static constexpr auto BIG = 16;
|
||||
static constexpr auto LARGE = 24;
|
||||
|
||||
Font(const std::string&, float = NORMAL);
|
||||
Font() = default;
|
||||
Font(const std::filesystem::path&, float = NORMAL);
|
||||
Font(const util::physfs::Path&, float = NORMAL);
|
||||
ImFont* get();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "shader.h"
|
||||
#include "shader.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include "../log.hpp"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
@@ -20,7 +20,7 @@ namespace game::resource
|
||||
glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLength);
|
||||
std::string log(logLength, '\0');
|
||||
if (logLength > 0) glGetShaderInfoLog(shaderHandle, logLength, nullptr, log.data());
|
||||
std::cout << "Failed to compile shader: " << log << '\n';
|
||||
logger.error(std::format("Failed to compile shader: {}", log));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -49,11 +49,11 @@ namespace game::resource
|
||||
if (!isLinked)
|
||||
{
|
||||
glDeleteProgram(id);
|
||||
logger.error(std::format("Failed to link shader: {}", id));
|
||||
id = 0;
|
||||
std::cout << "Failed to link shader: " << id << "\n";
|
||||
}
|
||||
else
|
||||
std::cout << "Initialized shader: " << id << "\n";
|
||||
logger.info(std::format("Initialized shader: {}", id));
|
||||
|
||||
glDeleteShader(vertexHandle);
|
||||
glDeleteShader(fragmentHandle);
|
||||
|
||||
@@ -1,60 +1,75 @@
|
||||
#include "texture.h"
|
||||
#include "texture.hpp"
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#endif
|
||||
#include <SDL3/SDL_surface.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#define STBI_ONLY_PNG
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include "../log.hpp"
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
struct CachedTexture
|
||||
{
|
||||
std::weak_ptr<GLuint> idShared{};
|
||||
glm::ivec2 size{};
|
||||
int channels{};
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, CachedTexture> textureCache{};
|
||||
|
||||
static bool cache_get(const std::string& key, std::shared_ptr<GLuint>& idShared, GLuint& id, ivec2& size, int& channels)
|
||||
{
|
||||
auto it = textureCache.find(key);
|
||||
if (it == textureCache.end()) return false;
|
||||
|
||||
auto shared = it->second.idShared.lock();
|
||||
if (!shared)
|
||||
{
|
||||
textureCache.erase(it);
|
||||
return false;
|
||||
}
|
||||
|
||||
idShared = shared;
|
||||
id = *shared;
|
||||
size = it->second.size;
|
||||
channels = it->second.channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cache_set(const std::string& key, const std::shared_ptr<GLuint>& idShared, ivec2 size, int channels)
|
||||
{
|
||||
if (!idShared) return;
|
||||
textureCache[key] = CachedTexture{.idShared = idShared, .size = size, .channels = channels};
|
||||
}
|
||||
|
||||
static std::shared_ptr<GLuint> texture_id_make(GLuint id)
|
||||
{
|
||||
return std::shared_ptr<GLuint>(new GLuint(id),
|
||||
[](GLuint* p)
|
||||
{
|
||||
if (!p) return;
|
||||
if (*p != 0) glDeleteTextures(1, p);
|
||||
delete p;
|
||||
});
|
||||
}
|
||||
|
||||
bool Texture::is_valid() const { return id != 0; }
|
||||
|
||||
void Texture::retain()
|
||||
Texture::~Texture()
|
||||
{
|
||||
if (refCount) ++(*refCount);
|
||||
}
|
||||
|
||||
void Texture::release()
|
||||
{
|
||||
if (refCount)
|
||||
{
|
||||
if (--(*refCount) == 0)
|
||||
{
|
||||
if (is_valid()) glDeleteTextures(1, &id);
|
||||
delete refCount;
|
||||
}
|
||||
refCount = nullptr;
|
||||
}
|
||||
else if (is_valid())
|
||||
{
|
||||
glDeleteTextures(1, &id);
|
||||
}
|
||||
|
||||
idShared.reset();
|
||||
id = 0;
|
||||
}
|
||||
|
||||
Texture::~Texture() { release(); }
|
||||
|
||||
Texture::Texture(const Texture& other)
|
||||
{
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
idShared = other.idShared;
|
||||
}
|
||||
|
||||
Texture::Texture(Texture&& other) noexcept
|
||||
@@ -62,24 +77,21 @@ namespace game::resource
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
idShared = std::move(other.idShared);
|
||||
|
||||
other.id = 0;
|
||||
other.size = {};
|
||||
other.channels = 0;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(const Texture& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
release();
|
||||
idShared = other.idShared;
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -88,45 +100,103 @@ namespace game::resource
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
release();
|
||||
idShared.reset();
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
idShared = std::move(other.idShared);
|
||||
|
||||
other.id = 0;
|
||||
other.size = {};
|
||||
other.channels = 0;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Texture::init(const uint8_t* data)
|
||||
{
|
||||
idShared.reset();
|
||||
id = 0;
|
||||
|
||||
GLuint newId{};
|
||||
glGenTextures(1, &newId);
|
||||
glBindTexture(GL_TEXTURE_2D, newId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
channels = CHANNELS;
|
||||
|
||||
idShared = texture_id_make(newId);
|
||||
id = newId;
|
||||
}
|
||||
|
||||
Texture::Texture(const std::filesystem::path& path)
|
||||
{
|
||||
if (auto data = stbi_load(path.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
|
||||
auto key = std::string("fs:") + path.string();
|
||||
if (cache_get(key, idShared, id, size, channels))
|
||||
{
|
||||
glGenTextures(1, &id);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
stbi_image_free(data);
|
||||
channels = CHANNELS;
|
||||
refCount = new int(1);
|
||||
std::cout << "Initialized texture: '" << path.string() << "\n";
|
||||
logger.info(std::format("Using cached texture: {}", path.string()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto surface = SDL_LoadPNG(path.c_str()))
|
||||
{
|
||||
auto rgbaSurface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
||||
SDL_DestroySurface(surface);
|
||||
surface = rgbaSurface;
|
||||
this->size = ivec2(surface->w, surface->h);
|
||||
init((const uint8_t*)surface->pixels);
|
||||
SDL_DestroySurface(surface);
|
||||
cache_set(key, idShared, this->size, channels);
|
||||
logger.info(std::format("Initialized texture: {}", path.string()));
|
||||
}
|
||||
else
|
||||
logger.error(std::format("Failed to initialize texture: {} ({})", path.string(), SDL_GetError()));
|
||||
}
|
||||
|
||||
Texture::Texture(const physfs::Path& path)
|
||||
{
|
||||
if (!path.is_valid())
|
||||
{
|
||||
id = 0;
|
||||
size = {};
|
||||
channels = 0;
|
||||
refCount = nullptr;
|
||||
std::cout << "Failed to initialize texture: '" << path.string() << "'\n";
|
||||
logger.error(
|
||||
std::format("Failed to initialize texture from PhysicsFS path: {}", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = std::string("physfs:") + path.c_str();
|
||||
if (cache_get(key, idShared, id, size, channels))
|
||||
{
|
||||
logger.info(std::format("Using cached texture: {}", path.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = path.read();
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
logger.error(
|
||||
std::format("Failed to initialize texture from PhysicsFS path: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto ioStream = SDL_IOFromConstMem(buffer.data(), buffer.size());
|
||||
|
||||
if (auto surface = SDL_LoadPNG_IO(ioStream, true))
|
||||
{
|
||||
auto rgbaSurface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
||||
SDL_DestroySurface(surface);
|
||||
surface = rgbaSurface;
|
||||
this->size = ivec2(surface->w, surface->h);
|
||||
init((const uint8_t*)surface->pixels);
|
||||
SDL_DestroySurface(surface);
|
||||
cache_set(key, idShared, this->size, channels);
|
||||
logger.info(std::format("Initialized texture: {}", path.c_str()));
|
||||
}
|
||||
else
|
||||
logger.error(std::format("Failed to initialize texture: {} ({})", path.c_str(), SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <glm/ext/vector_int2.hpp>
|
||||
#include <memory>
|
||||
|
||||
#include "../util/physfs.hpp"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
@@ -29,10 +33,10 @@ namespace game::resource
|
||||
Texture& operator=(const Texture&);
|
||||
Texture& operator=(Texture&&) noexcept;
|
||||
Texture(const std::filesystem::path&);
|
||||
Texture(const util::physfs::Path&);
|
||||
void init(const uint8_t*);
|
||||
|
||||
private:
|
||||
int* refCount{nullptr};
|
||||
void retain();
|
||||
void release();
|
||||
std::shared_ptr<GLuint> idShared{};
|
||||
};
|
||||
}
|
||||
|
||||
11
src/resource/xml/animation_entry.cpp
Normal file
11
src/resource/xml/animation_entry.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "animation_entry.hpp"
|
||||
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
const std::string& AnimationEntryCollection::get()
|
||||
{
|
||||
return at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).animation;
|
||||
}
|
||||
}
|
||||
23
src/resource/xml/animation_entry.hpp
Normal file
23
src/resource/xml/animation_entry.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
// Handles animation entries in .xml files. "Weight" value determines weight of being randomly selected.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class AnimationEntry
|
||||
{
|
||||
public:
|
||||
std::string animation{};
|
||||
float weight{1.0f};
|
||||
};
|
||||
|
||||
class AnimationEntryCollection : public std::vector<AnimationEntry>
|
||||
{
|
||||
public:
|
||||
const std::string& get();
|
||||
};
|
||||
|
||||
}
|
||||
338
src/resource/xml/anm2.cpp
Normal file
338
src/resource/xml/anm2.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "anm2.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "../../util/working_directory.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Anm2::Anm2(const Anm2&) = default;
|
||||
Anm2::Anm2(Anm2&&) = default;
|
||||
Anm2& Anm2::operator=(const Anm2&) = default;
|
||||
Anm2& Anm2::operator=(Anm2&&) = default;
|
||||
|
||||
void Anm2::init(XMLDocument& document, Flags flags, const physfs::Path& archive)
|
||||
{
|
||||
this->flags = flags;
|
||||
|
||||
auto element = document.RootElement();
|
||||
if (!element) return;
|
||||
|
||||
auto parse_frame = [&](XMLElement* element, Type type, int spritesheetID = -1)
|
||||
{
|
||||
Frame frame{};
|
||||
if (!element) return frame;
|
||||
|
||||
if (type != TRIGGER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &frame.position.x);
|
||||
element->QueryFloatAttribute("YPosition", &frame.position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
if (element->FindAttribute("RegionId") && spritesheets.contains(spritesheetID))
|
||||
{
|
||||
auto& spritesheet = spritesheets.at(spritesheetID);
|
||||
element->QueryIntAttribute("RegionId", &frame.regionID);
|
||||
auto& region = spritesheet.regions.at(frame.regionID);
|
||||
frame.crop = region.crop;
|
||||
frame.size = region.size;
|
||||
frame.pivot = region.origin == Spritesheet::Region::Origin::CENTER
|
||||
? glm::vec2(glm::ivec2(frame.size * 0.5f))
|
||||
: region.origin == Spritesheet::Region::Origin::TOP_LEFT ? glm::vec2{}
|
||||
: region.pivot;
|
||||
}
|
||||
else
|
||||
{
|
||||
element->QueryFloatAttribute("XPivot", &frame.pivot.x);
|
||||
element->QueryFloatAttribute("YPivot", &frame.pivot.y);
|
||||
element->QueryFloatAttribute("XCrop", &frame.crop.x);
|
||||
element->QueryFloatAttribute("YCrop", &frame.crop.y);
|
||||
element->QueryFloatAttribute("Width", &frame.size.x);
|
||||
element->QueryFloatAttribute("Height", &frame.size.y);
|
||||
}
|
||||
}
|
||||
element->QueryFloatAttribute("XScale", &frame.scale.x);
|
||||
element->QueryFloatAttribute("YScale", &frame.scale.y);
|
||||
element->QueryIntAttribute("Delay", &frame.duration);
|
||||
element->QueryBoolAttribute("Visible", &frame.isVisible);
|
||||
xml::query_color_attribute(element, "RedTint", &frame.tint.r);
|
||||
xml::query_color_attribute(element, "GreenTint", &frame.tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", &frame.tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", &frame.tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", &frame.colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", &frame.colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", &frame.colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &frame.rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &frame.isInterpolated);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
int soundID{};
|
||||
child->QueryIntAttribute("Id", &soundID);
|
||||
frame.soundIDs.emplace_back(soundID);
|
||||
}
|
||||
|
||||
element->QueryIntAttribute("EventId", &frame.eventID);
|
||||
element->QueryIntAttribute("AtFrame", &frame.atFrame);
|
||||
}
|
||||
|
||||
return frame;
|
||||
};
|
||||
|
||||
auto parse_item = [&](XMLElement* element, Type type, int& id)
|
||||
{
|
||||
Item item{};
|
||||
if (!element) return item;
|
||||
|
||||
if (type == LAYER) element->QueryIntAttribute("LayerId", &id);
|
||||
if (type == NULL_) element->QueryIntAttribute("NullId", &id);
|
||||
|
||||
element->QueryBoolAttribute("Visible", &item.isVisible);
|
||||
|
||||
for (auto child = type == TRIGGER ? element->FirstChildElement("Trigger") : element->FirstChildElement("Frame");
|
||||
child; child = type == TRIGGER ? child->NextSiblingElement("Trigger") : child->NextSiblingElement("Frame"))
|
||||
item.frames.emplace_back(parse_frame(child, type, type == LAYER ? layers.at(id).spritesheetID : -1));
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
auto parse_animation = [&](XMLElement* element)
|
||||
{
|
||||
Animation animation{};
|
||||
if (!element) return animation;
|
||||
|
||||
xml::query_string_attribute(element, "Name", &animation.name);
|
||||
element->QueryIntAttribute("FrameNum", &animation.frameNum);
|
||||
element->QueryBoolAttribute("Loop", &animation.isLoop);
|
||||
|
||||
int id{-1};
|
||||
|
||||
if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
|
||||
animation.rootAnimation = parse_item(rootAnimationElement, ROOT, id);
|
||||
|
||||
if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
|
||||
{
|
||||
for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child;
|
||||
child = child->NextSiblingElement("LayerAnimation"))
|
||||
{
|
||||
auto layerAnimation = parse_item(child, LAYER, id);
|
||||
animation.layerOrder.push_back(id);
|
||||
animation.layerAnimations.emplace(id, std::move(layerAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
|
||||
{
|
||||
for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
|
||||
child = child->NextSiblingElement("NullAnimation"))
|
||||
{
|
||||
auto nullAnimation = parse_item(child, NULL_, id);
|
||||
animation.nullAnimations.emplace(id, std::move(nullAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto triggersElement = element->FirstChildElement("Triggers"))
|
||||
animation.triggers = parse_item(triggersElement, TRIGGER, id);
|
||||
|
||||
return animation;
|
||||
};
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) infoElement->QueryIntAttribute("Fps", &fps);
|
||||
|
||||
if (auto contentElement = element->FirstChildElement("Content"))
|
||||
{
|
||||
if (auto spritesheetsElement = contentElement->FirstChildElement("Spritesheets"))
|
||||
{
|
||||
for (auto child = spritesheetsElement->FirstChildElement("Spritesheet"); child;
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
{
|
||||
int spritesheetId{};
|
||||
Spritesheet spritesheet{};
|
||||
child->QueryIntAttribute("Id", &spritesheetId);
|
||||
xml::query_string_attribute(child, "Path", &spritesheet.path);
|
||||
|
||||
if ((this->flags & NO_SPRITESHEETS) != 0)
|
||||
spritesheet.texture = Texture();
|
||||
else if (!archive.empty())
|
||||
spritesheet.texture = Texture(physfs::Path(archive + "/" + spritesheet.path));
|
||||
else
|
||||
spritesheet.texture = Texture(std::filesystem::path(spritesheet.path));
|
||||
|
||||
for (auto regionChild = child->FirstChildElement("Region"); regionChild;
|
||||
regionChild = regionChild->NextSiblingElement("Region"))
|
||||
{
|
||||
Spritesheet::Region region{};
|
||||
int regionID{};
|
||||
|
||||
regionChild->QueryIntAttribute("Id", ®ionID);
|
||||
xml::query_string_attribute(regionChild, "Name", ®ion.name);
|
||||
regionChild->QueryFloatAttribute("XCrop", ®ion.crop.x);
|
||||
regionChild->QueryFloatAttribute("YCrop", ®ion.crop.y);
|
||||
regionChild->QueryFloatAttribute("Width", ®ion.size.x);
|
||||
regionChild->QueryFloatAttribute("Height", ®ion.size.y);
|
||||
|
||||
if (regionChild->FindAttribute("Origin"))
|
||||
{
|
||||
std::string origin{};
|
||||
xml::query_string_attribute(regionChild, "Origin", &origin);
|
||||
region.origin = origin == "Center" ? Spritesheet::Region::CENTER
|
||||
: origin == "TopLeft" ? Spritesheet::Region::TOP_LEFT
|
||||
: Spritesheet::Region::CUSTOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
regionChild->QueryFloatAttribute("XPivot", ®ion.pivot.x);
|
||||
regionChild->QueryFloatAttribute("YPivot", ®ion.pivot.y);
|
||||
}
|
||||
|
||||
spritesheet.regions.emplace(regionID, std::move(region));
|
||||
spritesheet.regionOrder.emplace_back(regionID);
|
||||
}
|
||||
|
||||
spritesheets.emplace(spritesheetId, std::move(spritesheet));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto layersElement = contentElement->FirstChildElement("Layers"))
|
||||
{
|
||||
for (auto child = layersElement->FirstChildElement("Layer"); child; child = child->NextSiblingElement("Layer"))
|
||||
{
|
||||
int layerId{};
|
||||
Layer layer{};
|
||||
child->QueryIntAttribute("Id", &layerId);
|
||||
xml::query_string_attribute(child, "Name", &layer.name);
|
||||
child->QueryIntAttribute("SpritesheetId", &layer.spritesheetID);
|
||||
layerMap[layer.name] = layerId;
|
||||
layers.emplace(layerId, std::move(layer));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullsElement = contentElement->FirstChildElement("Nulls"))
|
||||
{
|
||||
for (auto child = nullsElement->FirstChildElement("Null"); child; child = child->NextSiblingElement("Null"))
|
||||
{
|
||||
int nullId{};
|
||||
Null null{};
|
||||
child->QueryIntAttribute("Id", &nullId);
|
||||
xml::query_string_attribute(child, "Name", &null.name);
|
||||
child->QueryBoolAttribute("ShowRect", &null.isShowRect);
|
||||
nullMap[null.name] = nullId;
|
||||
nulls.emplace(nullId, std::move(null));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto eventsElement = contentElement->FirstChildElement("Events"))
|
||||
{
|
||||
for (auto child = eventsElement->FirstChildElement("Event"); child; child = child->NextSiblingElement("Event"))
|
||||
{
|
||||
int eventId{};
|
||||
Event event{};
|
||||
child->QueryIntAttribute("Id", &eventId);
|
||||
xml::query_string_attribute(child, "Name", &event.name);
|
||||
eventMap[event.name] = eventId;
|
||||
events.emplace(eventId, std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto soundsElement = contentElement->FirstChildElement("Sounds"))
|
||||
{
|
||||
for (auto child = soundsElement->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
int soundId{};
|
||||
Sound sound{};
|
||||
child->QueryIntAttribute("Id", &soundId);
|
||||
xml::query_string_attribute(child, "Path", &sound.path);
|
||||
if ((this->flags & NO_SOUNDS) != 0)
|
||||
sound.audio = Audio();
|
||||
else if (!archive.empty())
|
||||
sound.audio = Audio(physfs::Path(archive + "/" + sound.path));
|
||||
else
|
||||
sound.audio = Audio(std::filesystem::path(sound.path));
|
||||
sounds.emplace(soundId, std::move(sound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||
{
|
||||
xml::query_string_attribute(animationsElement, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = animationsElement->FirstChildElement("Animation"); child;
|
||||
child = child->NextSiblingElement("Animation"))
|
||||
{
|
||||
if ((this->flags & DEFAULT_ANIMATION_ONLY) != 0)
|
||||
{
|
||||
std::string name{};
|
||||
xml::query_string_attribute(child, "Name", &name);
|
||||
if (name == defaultAnimation)
|
||||
{
|
||||
animations.emplace_back(parse_animation(child));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
animations.emplace_back(parse_animation(child));
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)animations.size(); i++)
|
||||
{
|
||||
auto& animation = animations[i];
|
||||
animationMap[animation.name] = i;
|
||||
animationMapReverse[i] = animation.name;
|
||||
}
|
||||
|
||||
if (animationMap.contains(defaultAnimation))
|
||||
defaultAnimationID = animationMap[defaultAnimation];
|
||||
else
|
||||
defaultAnimationID = -1;
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::filesystem::path& path, Flags flags)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
logger.error(std::format("Failed to initialize anm2: {} ({})", path.string(), document.ErrorStr()));
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
WorkingDirectory workingDirectory(path, WorkingDirectory::FILE);
|
||||
|
||||
this->path = path.string();
|
||||
|
||||
init(document, flags);
|
||||
|
||||
logger.info(std::format("Initialized anm2: {}", path.string()));
|
||||
}
|
||||
|
||||
Anm2::Anm2(const physfs::Path& path, Flags flags)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (xml::document_load(path, document) != XML_SUCCESS)
|
||||
{
|
||||
isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this->path = path;
|
||||
init(document, flags, path.directory_get());
|
||||
|
||||
logger.info(std::format("Initialized anm2: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Anm2::is_valid() const { return isValid; }
|
||||
}
|
||||
177
src/resource/xml/anm2.hpp
Normal file
177
src/resource/xml/anm2.hpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "../audio.hpp"
|
||||
#include "../texture.hpp"
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
NONE,
|
||||
ROOT,
|
||||
LAYER,
|
||||
NULL_,
|
||||
TRIGGER
|
||||
};
|
||||
|
||||
enum Flag
|
||||
{
|
||||
NO_SPRITESHEETS = (1 << 0),
|
||||
NO_SOUNDS = (1 << 1),
|
||||
DEFAULT_ANIMATION_ONLY = (1 << 2)
|
||||
};
|
||||
|
||||
using Flags = int;
|
||||
|
||||
struct Spritesheet
|
||||
{
|
||||
struct Region
|
||||
{
|
||||
enum Origin
|
||||
{
|
||||
TOP_LEFT,
|
||||
CENTER,
|
||||
CUSTOM
|
||||
};
|
||||
|
||||
std::string name{};
|
||||
glm::vec2 crop{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 size{};
|
||||
Origin origin{CUSTOM};
|
||||
};
|
||||
|
||||
std::string path{};
|
||||
resource::Texture texture{};
|
||||
|
||||
std::map<int, Region> regions{};
|
||||
std::vector<int> regionOrder{};
|
||||
};
|
||||
|
||||
struct Sound
|
||||
{
|
||||
std::string path{};
|
||||
resource::Audio audio{};
|
||||
};
|
||||
|
||||
struct Layer
|
||||
{
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{-1};
|
||||
};
|
||||
|
||||
struct Null
|
||||
{
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
std::string name{"New Event"};
|
||||
};
|
||||
|
||||
struct Frame
|
||||
{
|
||||
glm::vec2 crop{};
|
||||
glm::vec2 position{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 size{};
|
||||
glm::vec2 scale{100, 100};
|
||||
float rotation{};
|
||||
int duration{};
|
||||
glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glm::vec3 colorOffset{};
|
||||
bool isInterpolated{};
|
||||
int eventID{-1};
|
||||
int regionID{-1};
|
||||
std::vector<int> soundIDs{};
|
||||
int atFrame{-1};
|
||||
|
||||
bool isVisible{true};
|
||||
};
|
||||
|
||||
struct FrameOptional
|
||||
{
|
||||
std::optional<glm::vec2> crop{};
|
||||
std::optional<glm::vec2> position{};
|
||||
std::optional<glm::vec2> pivot{};
|
||||
std::optional<glm::vec2> size{};
|
||||
std::optional<glm::vec2> scale{};
|
||||
std::optional<float> rotation{};
|
||||
std::optional<glm::vec4> tint{};
|
||||
std::optional<glm::vec3> colorOffset{};
|
||||
std::optional<bool> isInterpolated{};
|
||||
std::optional<bool> isVisible{};
|
||||
};
|
||||
|
||||
struct Item
|
||||
{
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{};
|
||||
};
|
||||
|
||||
struct Animation
|
||||
{
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{};
|
||||
bool isLoop{};
|
||||
|
||||
Item rootAnimation{};
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers{};
|
||||
};
|
||||
|
||||
int fps{30};
|
||||
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
std::unordered_map<std::string, int> layerMap{};
|
||||
std::unordered_map<std::string, int> nullMap{};
|
||||
std::unordered_map<std::string, int> eventMap{};
|
||||
|
||||
std::string defaultAnimation{};
|
||||
int defaultAnimationID{-1};
|
||||
std::vector<Animation> animations{};
|
||||
std::unordered_map<std::string, int> animationMap{};
|
||||
std::unordered_map<int, std::string> animationMapReverse{};
|
||||
std::string path{};
|
||||
bool isValid{};
|
||||
Flags flags{};
|
||||
|
||||
Anm2() = default;
|
||||
Anm2(const Anm2&);
|
||||
Anm2(Anm2&&);
|
||||
Anm2& operator=(const Anm2&);
|
||||
Anm2& operator=(Anm2&&);
|
||||
Anm2(const std::filesystem::path&, Flags = 0);
|
||||
Anm2(const util::physfs::Path&, Flags = 0);
|
||||
|
||||
bool is_valid() const;
|
||||
|
||||
private:
|
||||
void init(tinyxml2::XMLDocument& document, Flags flags, const util::physfs::Path& archive = {});
|
||||
};
|
||||
}
|
||||
45
src/resource/xml/area.cpp
Normal file
45
src/resource/xml/area.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "area.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Area::Area(const physfs::Path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto archive = path.directory_get();
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string textureRootPath{};
|
||||
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||
|
||||
for (auto child = root->FirstChildElement("Area"); child; child = child->NextSiblingElement("Area"))
|
||||
{
|
||||
Entry area{};
|
||||
|
||||
query_texture(child, "Texture", archive, textureRootPath, area.texture);
|
||||
child->QueryFloatAttribute("Gravity", &area.gravity);
|
||||
child->QueryFloatAttribute("Friction", &area.friction);
|
||||
|
||||
areas.emplace_back(std::move(area));
|
||||
}
|
||||
}
|
||||
|
||||
if (areas.empty()) areas.emplace_back(Entry());
|
||||
|
||||
isValid = true;
|
||||
|
||||
logger.info(std::format("Initialized area schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Area::is_valid() const { return isValid; };
|
||||
}
|
||||
29
src/resource/xml/area.hpp
Normal file
29
src/resource/xml/area.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../texture.hpp"
|
||||
#include "../../util/physfs.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Area
|
||||
{
|
||||
public:
|
||||
struct Entry
|
||||
{
|
||||
Texture texture{};
|
||||
float gravity{0.95f};
|
||||
float friction{0.80f};
|
||||
float airResistance{0.975f};
|
||||
};
|
||||
|
||||
std::vector<Entry> areas{};
|
||||
bool isValid{};
|
||||
|
||||
Area() = default;
|
||||
Area(const util::physfs::Path&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
233
src/resource/xml/character.cpp
Normal file
233
src/resource/xml/character.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
#include "character.hpp"
|
||||
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "../../util/preferences.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Character::Character(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
physfs::Archive archive(path, path.filename().string());
|
||||
|
||||
if (!archive.is_valid())
|
||||
{
|
||||
logger.error(std::format("Failed to initialize character from PhysicsFS archive: {} ({})", path.string(),
|
||||
physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
physfs::Path characterPath(archive + "/" + "character.xml");
|
||||
|
||||
if (document_load(characterPath, document) != XML_SUCCESS) return;
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string textureRootPath{};
|
||||
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||
|
||||
std::string soundRootPath{};
|
||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||
|
||||
query_anm2(root, "Anm2", archive, textureRootPath, anm2);
|
||||
query_string_attribute(root, "Name", &name);
|
||||
|
||||
root->QueryFloatAttribute("Weight", &weight);
|
||||
root->QueryFloatAttribute("WeightMin", &weightMin);
|
||||
root->QueryFloatAttribute("WeightMax", &weightMax);
|
||||
|
||||
root->QueryFloatAttribute("Capacity", &capacity);
|
||||
root->QueryFloatAttribute("CapacityMin", &capacityMin);
|
||||
root->QueryFloatAttribute("CapacityMax", &capacityMax);
|
||||
root->QueryFloatAttribute("CapacityMaxMultiplier", &capacityMaxMultiplier);
|
||||
root->QueryFloatAttribute("CapacityIfOverStuffedOnDigestBonus", &capacityIfOverStuffedOnDigestBonus);
|
||||
|
||||
root->QueryFloatAttribute("CaloriesToKilogram", &caloriesToKilogram);
|
||||
|
||||
root->QueryFloatAttribute("DigestionRate", &digestionRate);
|
||||
root->QueryFloatAttribute("DigestionRateMin", &digestionRateMin);
|
||||
root->QueryFloatAttribute("DigestionRateMax", &digestionRateMax);
|
||||
root->QueryIntAttribute("DigestionTimerMax", &digestionTimerMax);
|
||||
|
||||
root->QueryFloatAttribute("EatSpeed", &eatSpeed);
|
||||
root->QueryFloatAttribute("EatSpeedMin", &eatSpeedMin);
|
||||
root->QueryFloatAttribute("EatSpeedMax", &eatSpeedMax);
|
||||
|
||||
root->QueryFloatAttribute("BlinkChance", &blinkChance);
|
||||
root->QueryFloatAttribute("GurgleChance", &gurgleChance);
|
||||
root->QueryFloatAttribute("GurgleCapacityMultiplier", &gurgleCapacityMultiplier);
|
||||
|
||||
auto dialoguePath = physfs::Path(archive + "/" + "dialogue.xml");
|
||||
|
||||
if (!dialoguePath.is_valid())
|
||||
logger.warning(std::format("No character dialogue.xml file found: {}", path.string()));
|
||||
else
|
||||
dialogue = Dialogue(dialoguePath);
|
||||
|
||||
dialogue.query_pool_id(root, "DialoguePoolID", pool.id);
|
||||
|
||||
if (auto element = root->FirstChildElement("AlternateSpritesheet"))
|
||||
{
|
||||
query_texture(element, "Texture", archive, textureRootPath, alternateSpritesheet.texture);
|
||||
query_sound(element, "Sound", archive, soundRootPath, alternateSpritesheet.sound);
|
||||
element->QueryIntAttribute("ID", &alternateSpritesheet.id);
|
||||
element->QueryFloatAttribute("ChanceOnNewGame", &alternateSpritesheet.chanceOnNewGame);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Animations"))
|
||||
{
|
||||
query_animation_entry_collection(element, "FinishFood", animations.finishFood);
|
||||
query_animation_entry_collection(element, "PostDigest", animations.postDigest);
|
||||
|
||||
if (auto child = element->FirstChildElement("Idle"))
|
||||
query_string_attribute(child, "Animation", &animations.idle);
|
||||
|
||||
if (auto child = element->FirstChildElement("IdleFull"))
|
||||
query_string_attribute(child, "Animation", &animations.idleFull);
|
||||
|
||||
if (auto child = element->FirstChildElement("StageUp"))
|
||||
query_string_attribute(child, "Animation", &animations.stageUp);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Digest", archive, soundRootPath, sounds.digest);
|
||||
query_sound_entry_collection(element, "Gurgle", archive, soundRootPath, sounds.gurgle);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Overrides"))
|
||||
{
|
||||
if (auto child = element->FirstChildElement("Talk"))
|
||||
{
|
||||
query_layer_id(child, "LayerSource", anm2, talkOverride.layerSource);
|
||||
query_layer_id(child, "LayerDestination", anm2, talkOverride.layerDestination);
|
||||
}
|
||||
|
||||
if (auto child = element->FirstChildElement("Blink"))
|
||||
{
|
||||
query_layer_id(child, "LayerSource", anm2, blinkOverride.layerSource);
|
||||
query_layer_id(child, "LayerDestination", anm2, blinkOverride.layerDestination);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Stages"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Stage"); child; child = child->NextSiblingElement("Stage"))
|
||||
{
|
||||
Stage stage{};
|
||||
child->QueryFloatAttribute("Threshold", &stage.threshold);
|
||||
child->QueryIntAttribute("AreaID", &stage.areaID);
|
||||
dialogue.query_pool_id(child, "DialoguePoolID", stage.pool.id);
|
||||
stages.emplace_back(std::move(stage));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("EatAreas"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("EatArea"); child; child = child->NextSiblingElement("EatArea"))
|
||||
{
|
||||
EatArea eatArea{};
|
||||
query_null_id(child, "Null", anm2, eatArea.nullID);
|
||||
query_event_id(child, "Event", anm2, eatArea.eventID);
|
||||
query_string_attribute(child, "Animation", &eatArea.animation);
|
||||
|
||||
eatAreas.emplace_back(std::move(eatArea));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("ExpandAreas"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("ExpandArea"); child;
|
||||
child = child->NextSiblingElement("ExpandArea"))
|
||||
{
|
||||
ExpandArea expandArea{};
|
||||
|
||||
query_layer_id(child, "Layer", anm2, expandArea.layerID);
|
||||
query_null_id(child, "Null", anm2, expandArea.nullID);
|
||||
child->QueryFloatAttribute("ScaleAdd", &expandArea.scaleAdd);
|
||||
|
||||
expandAreas.emplace_back(std::move(expandArea));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("InteractAreas"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("InteractArea"); child;
|
||||
child = child->NextSiblingElement("InteractArea"))
|
||||
{
|
||||
InteractArea interactArea{};
|
||||
|
||||
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);
|
||||
child->QueryFloatAttribute("DigestionBonusRub", &interactArea.digestionBonusRub);
|
||||
child->QueryFloatAttribute("DigestionBonusClick", &interactArea.digestionBonusClick);
|
||||
child->QueryFloatAttribute("Time", &interactArea.time);
|
||||
child->QueryFloatAttribute("ScaleEffectAmplitude", &interactArea.scaleEffectAmplitude);
|
||||
child->QueryFloatAttribute("ScaleEffectCycles", &interactArea.scaleEffectCycles);
|
||||
|
||||
std::string typeString{};
|
||||
query_string_attribute(child, "Type", &typeString);
|
||||
|
||||
for (int i = 0; i < (int)std::size(INTERACT_TYPE_STRINGS); i++)
|
||||
if (typeString == INTERACT_TYPE_STRINGS[i]) interactArea.type = (InteractType)i;
|
||||
|
||||
interactAreas.emplace_back(std::move(interactArea));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto itemSchemaPath = physfs::Path(archive + "/" + "items.xml");
|
||||
|
||||
if (auto itemSchemaPath = physfs::Path(archive + "/" + "items.xml"); itemSchemaPath.is_valid())
|
||||
itemSchema = Item(itemSchemaPath);
|
||||
else
|
||||
logger.warning(std::format("No character items.xml file found: {}", path.string()));
|
||||
|
||||
if (auto areaSchemaPath = physfs::Path(archive + "/" + "areas.xml"); areaSchemaPath.is_valid())
|
||||
areaSchema = Area(areaSchemaPath);
|
||||
else
|
||||
logger.warning(std::format("No character areas.xml file found: {}", path.string()));
|
||||
|
||||
if (auto menuSchemaPath = physfs::Path(archive + "/" + "menu.xml"); menuSchemaPath.is_valid())
|
||||
menuSchema = Menu(menuSchemaPath);
|
||||
else
|
||||
logger.warning(std::format("No character menu.xml file found: {}", path.string()));
|
||||
|
||||
if (auto cursorSchemaPath = physfs::Path(archive + "/" + "cursor.xml"); cursorSchemaPath.is_valid())
|
||||
cursorSchema = Cursor(cursorSchemaPath);
|
||||
else
|
||||
logger.warning(std::format("No character cursor.xml file found: {}", path.string()));
|
||||
|
||||
if (auto playSchemaPath = physfs::Path(archive + "/" + "play.xml"); playSchemaPath.is_valid())
|
||||
playSchema = Play(playSchemaPath, dialogue);
|
||||
else
|
||||
logger.warning(std::format("No character play.xml file found: {}", path.string()));
|
||||
|
||||
logger.info(std::format("Initialized character: {}", name));
|
||||
|
||||
this->path = path;
|
||||
save = Save(save_path_get());
|
||||
}
|
||||
|
||||
std::filesystem::path Character::save_path_get()
|
||||
{
|
||||
auto savePath = path.stem();
|
||||
savePath = preferences::path() / "saves" / savePath.replace_extension(".save");
|
||||
std::filesystem::create_directories(savePath.parent_path());
|
||||
return savePath;
|
||||
}
|
||||
}
|
||||
144
src/resource/xml/character.hpp
Normal file
144
src/resource/xml/character.hpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "../../util/interact_type.hpp"
|
||||
#include "../audio.hpp"
|
||||
#include "animation_entry.hpp"
|
||||
#include "anm2.hpp"
|
||||
#include "area.hpp"
|
||||
#include "cursor.hpp"
|
||||
#include "dialogue.hpp"
|
||||
#include "item.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "play.hpp"
|
||||
#include "save.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Character
|
||||
{
|
||||
public:
|
||||
struct Stage
|
||||
{
|
||||
float threshold{};
|
||||
int areaID{};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
};
|
||||
|
||||
struct EatArea
|
||||
{
|
||||
int nullID{-1};
|
||||
int eventID{-1};
|
||||
std::string animation{};
|
||||
};
|
||||
|
||||
struct ExpandArea
|
||||
{
|
||||
int layerID{-1};
|
||||
int nullID{-1};
|
||||
float scaleAdd{};
|
||||
};
|
||||
|
||||
struct InteractArea
|
||||
{
|
||||
std::string animation{};
|
||||
std::string animationFull{};
|
||||
std::string animationCursorActive{};
|
||||
std::string animationCursorHover{};
|
||||
SoundEntryCollection sound{};
|
||||
|
||||
int nullID{-1};
|
||||
int layerID{-1};
|
||||
InteractType type{(InteractType)-1};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
|
||||
float digestionBonusRub{};
|
||||
float digestionBonusClick{};
|
||||
float time{};
|
||||
float scaleEffectAmplitude{};
|
||||
float scaleEffectCycles{};
|
||||
};
|
||||
|
||||
struct Animations
|
||||
{
|
||||
AnimationEntryCollection finishFood{};
|
||||
AnimationEntryCollection postDigest{};
|
||||
|
||||
std::string idle{};
|
||||
std::string idleFull{};
|
||||
std::string stageUp{};
|
||||
};
|
||||
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection gurgle{};
|
||||
SoundEntryCollection digest{};
|
||||
};
|
||||
|
||||
struct Override
|
||||
{
|
||||
int layerSource{};
|
||||
int layerDestination{};
|
||||
};
|
||||
|
||||
struct AlternateSpritesheet
|
||||
{
|
||||
Texture texture{};
|
||||
Audio sound{};
|
||||
int id{-1};
|
||||
float chanceOnNewGame{0.001};
|
||||
};
|
||||
|
||||
Anm2 anm2{};
|
||||
Area areaSchema{};
|
||||
Dialogue dialogue{};
|
||||
Item itemSchema{};
|
||||
Menu menuSchema{};
|
||||
Cursor cursorSchema{};
|
||||
Play playSchema{};
|
||||
|
||||
Save save{};
|
||||
|
||||
Animations animations{};
|
||||
Override talkOverride{};
|
||||
Override blinkOverride{};
|
||||
|
||||
Sounds sounds{};
|
||||
|
||||
std::vector<Stage> stages{};
|
||||
std::vector<ExpandArea> expandAreas{};
|
||||
std::vector<EatArea> eatAreas{};
|
||||
std::vector<InteractArea> interactAreas{};
|
||||
|
||||
AlternateSpritesheet alternateSpritesheet{};
|
||||
|
||||
std::string name{};
|
||||
std::filesystem::path path{};
|
||||
float weight{50};
|
||||
float weightMin{};
|
||||
float weightMax{999};
|
||||
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};
|
||||
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};
|
||||
Dialogue::PoolReference pool{-1};
|
||||
|
||||
Character() = default;
|
||||
Character(const std::filesystem::path&);
|
||||
std::filesystem::path save_path_get();
|
||||
};
|
||||
}
|
||||
66
src/resource/xml/character_preview.cpp
Normal file
66
src/resource/xml/character_preview.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "character_preview.hpp"
|
||||
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "../../util/preferences.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
CharacterPreview::CharacterPreview(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
physfs::Archive archive(path, path.filename().string());
|
||||
|
||||
if (!archive.is_valid())
|
||||
{
|
||||
logger.error(std::format("Failed to initialize character preview from PhysicsFS archive: {} ({})", path.string(),
|
||||
physfs::error_get()));
|
||||
return;
|
||||
}
|
||||
|
||||
physfs::Path characterPath(archive + "/" + "character.xml");
|
||||
|
||||
if (document_load(characterPath, document) != XML_SUCCESS) return;
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string textureRootPath{};
|
||||
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||
|
||||
query_anm2(root, "Anm2", archive, textureRootPath, anm2, Anm2::NO_SOUNDS | Anm2::DEFAULT_ANIMATION_ONLY);
|
||||
query_texture(root, "Render", archive, textureRootPath, render);
|
||||
query_texture(root, "Portrait", archive, textureRootPath, portrait);
|
||||
|
||||
query_string_attribute(root, "Name", &name);
|
||||
query_string_attribute(root, "Description", &description);
|
||||
query_string_attribute(root, "Author", &author);
|
||||
root->QueryFloatAttribute("Weight", &weight);
|
||||
|
||||
if (auto element = root->FirstChildElement("Stages"))
|
||||
for (auto child = element->FirstChildElement("Stage"); child; child = child->NextSiblingElement("Stage"))
|
||||
stages++;
|
||||
}
|
||||
|
||||
this->path = path;
|
||||
save = Save(save_path_get());
|
||||
isValid = true;
|
||||
|
||||
logger.info(std::format("Initialized character preview: {}", name));
|
||||
}
|
||||
|
||||
std::filesystem::path CharacterPreview::save_path_get()
|
||||
{
|
||||
auto savePath = path.stem();
|
||||
savePath = preferences::path() / "saves" / savePath.replace_extension(".save");
|
||||
std::filesystem::create_directories(savePath.parent_path());
|
||||
return savePath;
|
||||
}
|
||||
|
||||
bool CharacterPreview::is_valid() const { return isValid; }
|
||||
}
|
||||
42
src/resource/xml/character_preview.hpp
Normal file
42
src/resource/xml/character_preview.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "anm2.hpp"
|
||||
#include "save.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class CharacterPreview
|
||||
{
|
||||
public:
|
||||
struct Stage
|
||||
{
|
||||
float threshold{};
|
||||
int dialoguePoolID{-1};
|
||||
};
|
||||
|
||||
Anm2 anm2{};
|
||||
Texture portrait{};
|
||||
Texture render{};
|
||||
Save save{};
|
||||
|
||||
int stages{1};
|
||||
|
||||
std::string name{};
|
||||
std::string author{};
|
||||
std::string description{};
|
||||
std::filesystem::path path{};
|
||||
float weight{50};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
CharacterPreview() = default;
|
||||
CharacterPreview(const std::filesystem::path&);
|
||||
|
||||
std::filesystem::path save_path_get();
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
52
src/resource/xml/cursor.cpp
Normal file
52
src/resource/xml/cursor.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "cursor.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Cursor::Cursor(const physfs::Path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto archive = path.directory_get();
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string textureRootPath{};
|
||||
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||
|
||||
std::string soundRootPath{};
|
||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||
|
||||
query_anm2(root, "Anm2", archive, textureRootPath, anm2);
|
||||
|
||||
if (auto element = root->FirstChildElement("Animations"))
|
||||
{
|
||||
query_animation_entry_collection(element, "Idle", animations.idle);
|
||||
query_animation_entry_collection(element, "Hover", animations.hover);
|
||||
query_animation_entry_collection(element, "Grab", animations.grab);
|
||||
query_animation_entry_collection(element, "Pan", animations.pan);
|
||||
query_animation_entry_collection(element, "Zoom", animations.zoom);
|
||||
query_animation_entry_collection(element, "Return", animations.return_);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Grab", archive, soundRootPath, sounds.grab);
|
||||
query_sound_entry_collection(element, "Release", archive, soundRootPath, sounds.release);
|
||||
query_sound_entry_collection(element, "Throw", archive, soundRootPath, sounds.throw_);
|
||||
}
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
|
||||
logger.info(std::format("Initialized area schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Cursor::is_valid() const { return isValid; };
|
||||
}
|
||||
38
src/resource/xml/cursor.hpp
Normal file
38
src/resource/xml/cursor.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
struct Animations
|
||||
{
|
||||
AnimationEntryCollection idle{};
|
||||
AnimationEntryCollection hover{};
|
||||
AnimationEntryCollection grab{};
|
||||
AnimationEntryCollection pan{};
|
||||
AnimationEntryCollection zoom{};
|
||||
AnimationEntryCollection return_{};
|
||||
};
|
||||
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection grab{};
|
||||
SoundEntryCollection release{};
|
||||
SoundEntryCollection throw_{};
|
||||
};
|
||||
|
||||
Animations animations{};
|
||||
Sounds sounds{};
|
||||
Anm2 anm2{};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Cursor() = default;
|
||||
Cursor(const util::physfs::Path&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
165
src/resource/xml/dialogue.cpp
Normal file
165
src/resource/xml/dialogue.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "dialogue.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "../../util/math.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
void Dialogue::query_entry_id(XMLElement* element, const char* name, int& id)
|
||||
{
|
||||
std::string entryID{};
|
||||
query_string_attribute(element, name, &entryID);
|
||||
if (entryIDMap.contains(entryID))
|
||||
id = entryIDMap.at(entryID);
|
||||
else if (entryID.empty())
|
||||
entryID = -1;
|
||||
else
|
||||
{
|
||||
logger.warning("Dialogue entries does not contain: " + entryID);
|
||||
id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Dialogue::query_pool_id(XMLElement* element, const char* name, int& id)
|
||||
{
|
||||
std::string poolID{};
|
||||
query_string_attribute(element, name, &poolID);
|
||||
if (poolMap.contains(poolID))
|
||||
id = poolMap.at(poolID);
|
||||
else if (poolID.empty())
|
||||
poolID = -1;
|
||||
else
|
||||
{
|
||||
logger.warning("Dialogue pools does not contain: " + poolID);
|
||||
id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Dialogue::Dialogue(const physfs::Path& path)
|
||||
{
|
||||
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
if (auto element = root->FirstChildElement("Entries"))
|
||||
{
|
||||
int id{};
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
{
|
||||
std::string stringID{};
|
||||
query_string_attribute(child, "ID", &stringID);
|
||||
entryIDMap.emplace(stringID, id);
|
||||
entryIDMapReverse.emplace(id, stringID);
|
||||
id++;
|
||||
}
|
||||
|
||||
id = 0;
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
{
|
||||
Entry entry{};
|
||||
|
||||
entry.name = entryIDMapReverse.at(id);
|
||||
|
||||
query_string_attribute(child, "Text", &entry.text);
|
||||
|
||||
query_string_attribute(child, "Animation", &entry.animation);
|
||||
|
||||
if (child->FindAttribute("Next"))
|
||||
{
|
||||
std::string nextID{};
|
||||
query_string_attribute(child, "Next", &nextID);
|
||||
if (!entryIDMap.contains(nextID))
|
||||
logger.warning(std::format("Dialogue: next ID does not point to a valid Entry! ({})", nextID));
|
||||
else
|
||||
entry.nextID = entryIDMap.at(nextID);
|
||||
}
|
||||
|
||||
for (auto choiceChild = child->FirstChildElement("Choice"); choiceChild;
|
||||
choiceChild = choiceChild->NextSiblingElement("Choice"))
|
||||
{
|
||||
Choice choice{};
|
||||
query_entry_id(choiceChild, "Next", choice.nextID);
|
||||
query_string_attribute(choiceChild, "Text", &choice.text);
|
||||
entry.choices.emplace_back(std::move(choice));
|
||||
}
|
||||
|
||||
entries.emplace_back(std::move(entry));
|
||||
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Pools"))
|
||||
{
|
||||
int id{};
|
||||
|
||||
for (auto child = element->FirstChildElement("Pool"); child; child = child->NextSiblingElement("Pool"))
|
||||
{
|
||||
Pool pool{};
|
||||
std::string stringID{};
|
||||
query_string_attribute(child, "ID", &stringID);
|
||||
poolMap[stringID] = id;
|
||||
pools.emplace_back(pool);
|
||||
id++;
|
||||
}
|
||||
|
||||
id = 0;
|
||||
|
||||
for (auto child = element->FirstChildElement("Pool"); child; child = child->NextSiblingElement("Pool"))
|
||||
{
|
||||
auto& pool = pools.at(id);
|
||||
|
||||
for (auto entryChild = child->FirstChildElement("PoolEntry"); entryChild;
|
||||
entryChild = entryChild->NextSiblingElement("PoolEntry"))
|
||||
{
|
||||
int entryID{};
|
||||
query_entry_id(entryChild, "ID", entryID);
|
||||
pool.emplace_back(entryID);
|
||||
}
|
||||
|
||||
id++;
|
||||
}
|
||||
|
||||
logger.info(std::format("Initialized dialogue: {}", path.c_str()));
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Start"))
|
||||
{
|
||||
query_entry_id(element, "ID", start.id);
|
||||
query_string_attribute(element, "Animation", &start.animation);
|
||||
}
|
||||
if (auto element = root->FirstChildElement("End")) query_entry_id(element, "ID", end.id);
|
||||
if (auto element = root->FirstChildElement("Help")) query_entry_id(element, "ID", help.id);
|
||||
|
||||
if (auto element = root->FirstChildElement("Digest")) query_pool_id(element, "PoolID", digest.id);
|
||||
if (auto element = root->FirstChildElement("Eat")) query_pool_id(element, "PoolID", eat.id);
|
||||
if (auto element = root->FirstChildElement("EatFull")) query_pool_id(element, "PoolID", eatFull.id);
|
||||
if (auto element = root->FirstChildElement("Feed")) query_pool_id(element, "PoolID", feed.id);
|
||||
if (auto element = root->FirstChildElement("FeedFull")) query_pool_id(element, "PoolID", feedFull.id);
|
||||
if (auto element = root->FirstChildElement("FoodTaken")) query_pool_id(element, "PoolID", foodTaken.id);
|
||||
if (auto element = root->FirstChildElement("FoodTakenFull")) query_pool_id(element, "PoolID", foodTakenFull.id);
|
||||
if (auto element = root->FirstChildElement("Full")) query_pool_id(element, "PoolID", full.id);
|
||||
if (auto element = root->FirstChildElement("LowCapacity")) query_pool_id(element, "PoolID", lowCapacity.id);
|
||||
if (auto element = root->FirstChildElement("Random")) query_pool_id(element, "PoolID", random.id);
|
||||
if (auto element = root->FirstChildElement("Throw")) query_pool_id(element, "PoolID", throw_.id);
|
||||
if (auto element = root->FirstChildElement("StageUp")) query_pool_id(element, "PoolID", stageUp.id);
|
||||
}
|
||||
}
|
||||
|
||||
int Dialogue::Pool::get() const { return this->at(math::random_max(this->size())); }
|
||||
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()); }
|
||||
}
|
||||
90
src/resource/xml/dialogue.hpp
Normal file
90
src/resource/xml/dialogue.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Dialogue
|
||||
{
|
||||
public:
|
||||
struct Choice
|
||||
{
|
||||
std::string text{};
|
||||
int nextID{-1};
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
std::string name{};
|
||||
std::string animation{};
|
||||
std::string text{};
|
||||
std::vector<Choice> choices{};
|
||||
int nextID{-1};
|
||||
|
||||
inline bool is_last() const { return choices.empty() && nextID == -1; };
|
||||
};
|
||||
|
||||
struct EntryReference
|
||||
{
|
||||
int id{-1};
|
||||
std::string animation{};
|
||||
|
||||
inline bool is_valid() const { return id != -1; };
|
||||
};
|
||||
|
||||
class PoolReference
|
||||
{
|
||||
public:
|
||||
int id{-1};
|
||||
inline bool is_valid() const { return id != -1; };
|
||||
};
|
||||
|
||||
class Pool : public std::vector<int>
|
||||
{
|
||||
public:
|
||||
int get() const;
|
||||
};
|
||||
|
||||
std::map<std::string, int> entryIDMap;
|
||||
std::map<int, std::string> entryIDMapReverse;
|
||||
std::vector<Entry> entries{};
|
||||
|
||||
std::vector<Pool> pools{};
|
||||
std::map<std::string, int> poolMap{};
|
||||
|
||||
EntryReference start{-1};
|
||||
EntryReference end{-1};
|
||||
EntryReference help{-1};
|
||||
PoolReference digest{-1};
|
||||
PoolReference eatFull{-1};
|
||||
PoolReference eat{-1};
|
||||
PoolReference feedFull{-1};
|
||||
PoolReference feed{-1};
|
||||
PoolReference foodTakenFull{-1};
|
||||
PoolReference foodTaken{-1};
|
||||
PoolReference full{-1};
|
||||
PoolReference random{-1};
|
||||
PoolReference lowCapacity{-1};
|
||||
PoolReference throw_{-1};
|
||||
PoolReference stageUp{-1};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Dialogue() = default;
|
||||
Dialogue(const util::physfs::Path&);
|
||||
Entry* get(const std::string&);
|
||||
Entry* get(int id);
|
||||
Entry* get(Dialogue::EntryReference&);
|
||||
Entry* get(Dialogue::Pool&);
|
||||
Entry* get(Dialogue::PoolReference&);
|
||||
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; };
|
||||
};
|
||||
}
|
||||
156
src/resource/xml/item.cpp
Normal file
156
src/resource/xml/item.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "item.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Item::Item(const physfs::Path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto archive = path.directory_get();
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string textureRootPath{};
|
||||
query_string_attribute(root, "TextureRootPath", &textureRootPath);
|
||||
|
||||
std::string soundRootPath{};
|
||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||
|
||||
query_anm2(root, "BaseAnm2", archive, textureRootPath, baseAnm2, Anm2::NO_SPRITESHEETS);
|
||||
|
||||
if (auto element = root->FirstChildElement("Categories"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Category"); child; child = child->NextSiblingElement("Category"))
|
||||
{
|
||||
Category category{};
|
||||
query_string_attribute(child, "Name", &category.name);
|
||||
query_bool_attribute(child, "IsEdible", &category.isEdible);
|
||||
categoryMap[category.name] = (int)categories.size();
|
||||
categories.push_back(category);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Rarities"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Rarity"); child; child = child->NextSiblingElement("Rarity"))
|
||||
{
|
||||
Rarity rarity{};
|
||||
|
||||
query_string_attribute(child, "Name", &rarity.name);
|
||||
child->QueryFloatAttribute("Chance", &rarity.chance);
|
||||
query_bool_attribute(child, "IsHidden", &rarity.isHidden);
|
||||
|
||||
query_sound(child, "Sound", archive, soundRootPath, rarity.sound);
|
||||
|
||||
rarityMap[rarity.name] = (int)rarities.size();
|
||||
rarities.emplace_back(std::move(rarity));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Flavors"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Flavor"); child; child = child->NextSiblingElement("Flavor"))
|
||||
{
|
||||
Flavor flavor{};
|
||||
query_string_attribute(child, "Name", &flavor.name);
|
||||
flavorMap[flavor.name] = (int)flavors.size();
|
||||
flavors.push_back(flavor);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Animations"))
|
||||
{
|
||||
if (auto child = element->FirstChildElement("Chew"))
|
||||
query_string_attribute(child, "Animation", &animations.chew);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Bounce", archive, soundRootPath, sounds.bounce);
|
||||
query_sound_entry_collection(element, "Dispose", archive, soundRootPath, sounds.dispose);
|
||||
query_sound_entry_collection(element, "Return", archive, soundRootPath, sounds.return_);
|
||||
query_sound_entry_collection(element, "Summon", archive, soundRootPath, sounds.summon);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Items"))
|
||||
{
|
||||
std::string itemTextureRootPath{};
|
||||
query_string_attribute(element, "TextureRootPath", &itemTextureRootPath);
|
||||
|
||||
element->QueryIntAttribute("ChewCount", &chewCount);
|
||||
element->QueryIntAttribute("QuantityMax", &quantityMax);
|
||||
|
||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||
{
|
||||
Entry item{};
|
||||
|
||||
query_string_attribute(child, "Name", &item.name);
|
||||
query_string_attribute(child, "Description", &item.description);
|
||||
|
||||
query_float_optional_attribute(child, "Calories", item.calories);
|
||||
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_bool_attribute(child, "IsPlayReward", &item.isPlayReward);
|
||||
query_bool_attribute(child, "IsToggleSpritesheet", &item.isToggleSpritesheet);
|
||||
|
||||
std::string categoryString{};
|
||||
query_string_attribute(child, "Category", &categoryString);
|
||||
item.categoryID = categoryMap.contains(categoryString) ? categoryMap[categoryString] : -1;
|
||||
|
||||
std::string rarityString{};
|
||||
query_string_attribute(child, "Rarity", &rarityString);
|
||||
item.rarityID = rarityMap.contains(rarityString) ? rarityMap[rarityString] : -1;
|
||||
|
||||
std::string flavorString{};
|
||||
query_string_attribute(child, "Flavor", &flavorString);
|
||||
if (flavorMap.contains(flavorString)) item.flavorID = flavorMap[flavorString];
|
||||
|
||||
Texture texture{};
|
||||
query_texture(child, "Texture", archive, itemTextureRootPath, texture);
|
||||
|
||||
Anm2 anm2{baseAnm2};
|
||||
if (child->FindAttribute("Anm2")) query_anm2(child, "Anm2", archive, textureRootPath, anm2);
|
||||
anm2.spritesheets.at(0).texture = std::move(texture);
|
||||
anm2s.emplace_back(std::move(anm2));
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)items.size(); i++)
|
||||
{
|
||||
auto& item = items[i];
|
||||
pools[item.rarityID].emplace_back(i);
|
||||
if (item.isPlayReward) rewardItemPool.emplace_back(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)rarities.size(); i++)
|
||||
{
|
||||
rarityIDsSortedByChance.emplace_back(i);
|
||||
}
|
||||
std::stable_sort(rarityIDsSortedByChance.begin(), rarityIDsSortedByChance.end(),
|
||||
[&](int a, int b) { return rarities[a].chance > rarities[b].chance; });
|
||||
|
||||
isValid = true;
|
||||
logger.info(std::format("Initialized item schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Item::is_valid() const { return isValid; };
|
||||
}
|
||||
98
src/resource/xml/item.hpp
Normal file
98
src/resource/xml/item.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "../audio.hpp"
|
||||
#include "anm2.hpp"
|
||||
#include "sound_entry.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
static constexpr auto UNDEFINED = "???";
|
||||
|
||||
struct Category
|
||||
{
|
||||
std::string name{};
|
||||
bool isEdible{};
|
||||
};
|
||||
|
||||
struct Rarity
|
||||
{
|
||||
std::string name{UNDEFINED};
|
||||
float chance{};
|
||||
bool isHidden{};
|
||||
Audio sound{};
|
||||
};
|
||||
|
||||
struct Flavor
|
||||
{
|
||||
std::string name{UNDEFINED};
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
std::string name{UNDEFINED};
|
||||
std::string description{UNDEFINED};
|
||||
int categoryID{};
|
||||
int rarityID{};
|
||||
std::optional<int> flavorID;
|
||||
std::optional<float> calories{};
|
||||
std::optional<float> eatSpeedBonus{};
|
||||
std::optional<float> digestionBonus{};
|
||||
std::optional<float> gravity{};
|
||||
std::optional<int> chewCount{};
|
||||
bool isPlayReward{};
|
||||
bool isToggleSpritesheet{};
|
||||
};
|
||||
|
||||
struct Animations
|
||||
{
|
||||
std::string chew{};
|
||||
};
|
||||
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection bounce{};
|
||||
SoundEntryCollection return_{};
|
||||
SoundEntryCollection dispose{};
|
||||
SoundEntryCollection summon{};
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, int> categoryMap{};
|
||||
std::unordered_map<std::string, int> rarityMap{};
|
||||
std::unordered_map<std::string, int> flavorMap{};
|
||||
|
||||
using Pool = std::vector<int>;
|
||||
|
||||
std::vector<Category> categories{};
|
||||
std::vector<Rarity> rarities{};
|
||||
std::vector<Flavor> flavors{};
|
||||
std::vector<Entry> items{};
|
||||
std::vector<Anm2> anm2s{};
|
||||
|
||||
std::vector<int> rarityIDsSortedByChance{};
|
||||
std::unordered_map<int, Pool> pools{};
|
||||
Pool rewardItemPool{};
|
||||
|
||||
Animations animations{};
|
||||
Sounds sounds{};
|
||||
Anm2 baseAnm2{};
|
||||
int chewCount{2};
|
||||
int quantityMax{99};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Item() = default;
|
||||
Item(const std::filesystem::path&);
|
||||
Item(const util::physfs::Path&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
46
src/resource/xml/menu.cpp
Normal file
46
src/resource/xml/menu.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "menu.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Menu::Menu(const physfs::Path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto archive = path.directory_get();
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string soundRootPath{};
|
||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||
|
||||
std::string fontRootPath{};
|
||||
query_string_attribute(root, "FontRootPath", &fontRootPath);
|
||||
|
||||
query_font(root, "Font", archive, fontRootPath, font);
|
||||
root->QueryFloatAttribute("Rounding", &rounding);
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Open", archive, soundRootPath, sounds.open);
|
||||
query_sound_entry_collection(element, "Close", archive, soundRootPath, sounds.close);
|
||||
query_sound_entry_collection(element, "Hover", archive, soundRootPath, sounds.hover);
|
||||
query_sound_entry_collection(element, "Select", archive, soundRootPath, sounds.select);
|
||||
}
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
|
||||
logger.info(std::format("Initialized menu schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Menu::is_valid() const { return isValid; };
|
||||
}
|
||||
31
src/resource/xml/menu.hpp
Normal file
31
src/resource/xml/menu.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
#include "../font.hpp"
|
||||
#include "sound_entry.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Menu
|
||||
{
|
||||
public:
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection open{};
|
||||
SoundEntryCollection close{};
|
||||
SoundEntryCollection hover{};
|
||||
SoundEntryCollection select{};
|
||||
};
|
||||
|
||||
Sounds sounds{};
|
||||
Font font{};
|
||||
float rounding{};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Menu() = default;
|
||||
Menu(const util::physfs::Path&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
67
src/resource/xml/play.cpp
Normal file
67
src/resource/xml/play.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "play.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Play::Play(const physfs::Path& path, Dialogue& dialogue)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document_load(path, document) != XML_SUCCESS) return;
|
||||
|
||||
auto archive = path.directory_get();
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string soundRootPath{};
|
||||
query_string_attribute(root, "SoundRootPath", &soundRootPath);
|
||||
|
||||
root->QueryIntAttribute("RewardScore", &rewardScore);
|
||||
root->QueryFloatAttribute("RewardScoreBonus", &rewardScoreBonus);
|
||||
root->QueryFloatAttribute("RewardGradeBonus", &rewardGradeBonus);
|
||||
root->QueryFloatAttribute("RangeBase", &rangeBase);
|
||||
root->QueryFloatAttribute("RangeMin", &rangeMin);
|
||||
root->QueryFloatAttribute("RangeScoreBonus", &rangeScoreBonus);
|
||||
root->QueryFloatAttribute("SpeedMin", &speedMin);
|
||||
root->QueryFloatAttribute("SpeedMax", &speedMax);
|
||||
root->QueryFloatAttribute("SpeedScoreBonus", &speedScoreBonus);
|
||||
root->QueryIntAttribute("EndTimerMax", &endTimerMax);
|
||||
root->QueryIntAttribute("EndTimerFailureMax", &endTimerFailureMax);
|
||||
|
||||
if (auto element = root->FirstChildElement("Sounds"))
|
||||
{
|
||||
query_sound_entry_collection(element, "Fall", archive, soundRootPath, sounds.fall);
|
||||
query_sound_entry_collection(element, "ScoreLoss", archive, soundRootPath, sounds.scoreLoss);
|
||||
query_sound_entry_collection(element, "HighScore", archive, soundRootPath, sounds.highScore);
|
||||
query_sound_entry_collection(element, "HighScoreLoss", archive, soundRootPath, sounds.highScoreLoss);
|
||||
query_sound_entry_collection(element, "RewardScore", archive, soundRootPath, sounds.rewardScore);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Grades"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Grade"); child; child = child->NextSiblingElement("Grade"))
|
||||
{
|
||||
Grade grade{};
|
||||
query_string_attribute(child, "Name", &grade.name);
|
||||
query_string_attribute(child, "NamePlural", &grade.namePlural);
|
||||
child->QueryIntAttribute("Value", &grade.value);
|
||||
child->QueryFloatAttribute("Weight", &grade.weight);
|
||||
query_bool_attribute(child, "IsFailure", &grade.isFailure);
|
||||
query_sound(child, "Sound", archive, soundRootPath, grade.sound);
|
||||
dialogue.query_pool_id(child, "DialoguePoolID", grade.pool.id);
|
||||
grades.emplace_back(std::move(grade));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isValid = true;
|
||||
logger.info(std::format("Initialized play schema: {}", path.c_str()));
|
||||
}
|
||||
|
||||
bool Play::is_valid() const { return isValid; };
|
||||
}
|
||||
56
src/resource/xml/play.hpp
Normal file
56
src/resource/xml/play.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
#include "dialogue.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Play
|
||||
{
|
||||
public:
|
||||
struct Grade
|
||||
{
|
||||
std::string name{};
|
||||
std::string namePlural{};
|
||||
int value{};
|
||||
float weight{};
|
||||
bool isFailure{};
|
||||
Audio sound{};
|
||||
Dialogue::PoolReference pool{};
|
||||
};
|
||||
|
||||
struct Sounds
|
||||
{
|
||||
SoundEntryCollection fall{};
|
||||
SoundEntryCollection highScore{};
|
||||
SoundEntryCollection highScoreLoss{};
|
||||
SoundEntryCollection rewardScore{};
|
||||
SoundEntryCollection scoreLoss{};
|
||||
};
|
||||
|
||||
Sounds sounds{};
|
||||
std::vector<Grade> grades{};
|
||||
|
||||
float rewardScoreBonus{0.01};
|
||||
float rewardGradeBonus{0.05};
|
||||
float speedMin{0.005};
|
||||
float speedMax{0.075};
|
||||
float speedScoreBonus{0.000025f};
|
||||
float rangeBase{0.75};
|
||||
float rangeMin{0.10};
|
||||
float rangeScoreBonus{0.0005};
|
||||
int endTimerMax{20};
|
||||
int endTimerFailureMax{60};
|
||||
int rewardScore{999};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Play() = default;
|
||||
Play(const util::physfs::Path&, Dialogue&);
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
183
src/resource/xml/save.cpp
Normal file
183
src/resource/xml/save.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "save.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "../../util/web_filesystem.hpp"
|
||||
#endif
|
||||
|
||||
using namespace game::util;
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Save::Save(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
// Fail silently if there's no save.
|
||||
auto result = document.LoadFile(path.c_str());
|
||||
|
||||
if (result == XML_ERROR_FILE_NOT_FOUND || result == XML_ERROR_FILE_COULD_NOT_BE_OPENED) return;
|
||||
|
||||
if (result != XML_SUCCESS)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Could not initialize character save file: {} ({})", path.string(), document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
query_bool_attribute(root, "IsPostgame", &isPostgame);
|
||||
query_bool_attribute(root, "IsAlternateSpritesheet", &isAlternateSpritesheet);
|
||||
|
||||
if (auto element = root->FirstChildElement("Character"))
|
||||
{
|
||||
|
||||
element->QueryFloatAttribute("Weight", &weight);
|
||||
element->QueryFloatAttribute("Calories", &calories);
|
||||
element->QueryFloatAttribute("Capacity", &capacity);
|
||||
element->QueryFloatAttribute("DigestionRate", &digestionRate);
|
||||
element->QueryFloatAttribute("EatSpeed", &eatSpeed);
|
||||
query_bool_attribute(element, "IsDigesting", &isDigesting);
|
||||
element->QueryFloatAttribute("DigestionProgress", &digestionProgress);
|
||||
element->QueryIntAttribute("DigestionTimer", &digestionTimer);
|
||||
element->QueryFloatAttribute("TotalCaloriesConsumed", &totalCaloriesConsumed);
|
||||
element->QueryIntAttribute("TotalFoodItemsEaten", &totalFoodItemsEaten);
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Play"))
|
||||
{
|
||||
element->QueryIntAttribute("TotalPlays", &totalPlays);
|
||||
element->QueryIntAttribute("HighScore", &highScore);
|
||||
element->QueryIntAttribute("BestCombo", &bestCombo);
|
||||
|
||||
if (auto child = element->FirstChildElement("Grades"))
|
||||
{
|
||||
for (auto gradeChild = child->FirstChildElement("Grade"); gradeChild;
|
||||
gradeChild = gradeChild->NextSiblingElement("Grade"))
|
||||
{
|
||||
int id{};
|
||||
gradeChild->QueryIntAttribute("ID", &id);
|
||||
gradeChild->QueryIntAttribute("Count", &gradeCounts[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Inventory"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||
{
|
||||
int id{};
|
||||
int quantity{};
|
||||
child->QueryIntAttribute("ID", &id);
|
||||
child->QueryIntAttribute("Quantity", &quantity);
|
||||
|
||||
inventory[id] = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto element = root->FirstChildElement("Items"))
|
||||
{
|
||||
for (auto child = element->FirstChildElement("Item"); child; child = child->NextSiblingElement("Item"))
|
||||
{
|
||||
Item item{};
|
||||
child->QueryIntAttribute("ID", &item.id);
|
||||
child->QueryIntAttribute("ChewCount", &item.chewCount);
|
||||
child->QueryFloatAttribute("PositionX", &item.position.x);
|
||||
child->QueryFloatAttribute("PositionY", &item.position.y);
|
||||
child->QueryFloatAttribute("VelocityX", &item.velocity.x);
|
||||
child->QueryFloatAttribute("VelocityY", &item.velocity.y);
|
||||
child->QueryFloatAttribute("Rotation", &item.rotation);
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(std::format("Initialized character save file: {}", path.string()));
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
bool Save::is_valid() const { return isValid; }
|
||||
|
||||
void Save::serialize(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto element = document.NewElement("Save");
|
||||
element->SetAttribute("IsPostgame", isPostgame ? "true" : "false");
|
||||
element->SetAttribute("IsAlternateSpritesheet", isAlternateSpritesheet ? "true" : "false");
|
||||
|
||||
auto characterElement = element->InsertNewChildElement("Character");
|
||||
characterElement->SetAttribute("Weight", weight);
|
||||
characterElement->SetAttribute("Calories", calories);
|
||||
characterElement->SetAttribute("Capacity", capacity);
|
||||
characterElement->SetAttribute("DigestionRate", digestionRate);
|
||||
characterElement->SetAttribute("EatSpeed", eatSpeed);
|
||||
characterElement->SetAttribute("IsDigesting", isDigesting ? "true" : "false");
|
||||
characterElement->SetAttribute("DigestionProgress", digestionProgress);
|
||||
characterElement->SetAttribute("DigestionTimer", digestionTimer);
|
||||
characterElement->SetAttribute("TotalCaloriesConsumed", totalCaloriesConsumed);
|
||||
characterElement->SetAttribute("TotalFoodItemsEaten", totalFoodItemsEaten);
|
||||
|
||||
auto playElement = element->InsertNewChildElement("Play");
|
||||
|
||||
playElement->SetAttribute("TotalPlays", totalPlays);
|
||||
playElement->SetAttribute("HighScore", highScore);
|
||||
playElement->SetAttribute("BestCombo", bestCombo);
|
||||
|
||||
auto gradesElement = playElement->InsertNewChildElement("Grades");
|
||||
|
||||
for (auto& [i, count] : gradeCounts)
|
||||
{
|
||||
auto gradeElement = gradesElement->InsertNewChildElement("Grade");
|
||||
gradeElement->SetAttribute("ID", i);
|
||||
gradeElement->SetAttribute("Count", count);
|
||||
}
|
||||
|
||||
auto inventoryElement = element->InsertNewChildElement("Inventory");
|
||||
|
||||
for (auto& [id, quantity] : inventory)
|
||||
{
|
||||
auto itemElement = inventoryElement->InsertNewChildElement("Item");
|
||||
|
||||
itemElement->SetAttribute("ID", id);
|
||||
itemElement->SetAttribute("Quantity", quantity);
|
||||
}
|
||||
|
||||
auto itemsElement = element->InsertNewChildElement("Items");
|
||||
|
||||
for (auto& item : items)
|
||||
{
|
||||
auto itemElement = itemsElement->InsertNewChildElement("Item");
|
||||
|
||||
itemElement->SetAttribute("ID", item.id);
|
||||
itemElement->SetAttribute("ChewCount", item.chewCount);
|
||||
itemElement->SetAttribute("PositionX", item.position.x);
|
||||
itemElement->SetAttribute("PositionY", item.position.y);
|
||||
itemElement->SetAttribute("VelocityX", item.velocity.x);
|
||||
itemElement->SetAttribute("VelocityY", item.velocity.y);
|
||||
itemElement->SetAttribute("Rotation", item.rotation);
|
||||
}
|
||||
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
if (document.SaveFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
logger.error(std::format("Failed to save character save file: {} ({})", path.string(), document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(std::format("Saved character save file: {}", path.string()));
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
web_filesystem::flush_async();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
54
src/resource/xml/save.hpp
Normal file
54
src/resource/xml/save.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Save
|
||||
{
|
||||
public:
|
||||
struct Item
|
||||
{
|
||||
int id{};
|
||||
int chewCount{};
|
||||
glm::vec2 position{};
|
||||
glm::vec2 velocity{};
|
||||
float rotation{};
|
||||
};
|
||||
|
||||
float weight{};
|
||||
float calories{};
|
||||
float capacity{};
|
||||
float eatSpeed{};
|
||||
float digestionRate{};
|
||||
float digestionProgress{};
|
||||
int digestionTimer{};
|
||||
bool isDigesting{};
|
||||
|
||||
bool isAlternateSpritesheet{};
|
||||
|
||||
float totalCaloriesConsumed{};
|
||||
int totalFoodItemsEaten{};
|
||||
int totalPlays{};
|
||||
int highScore{};
|
||||
int bestCombo{};
|
||||
std::map<int, int> gradeCounts{};
|
||||
|
||||
std::map<int, int> inventory;
|
||||
std::vector<Item> items;
|
||||
|
||||
bool isPostgame{};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Save() = default;
|
||||
Save(const std::filesystem::path&);
|
||||
void serialize(const std::filesystem::path&);
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
80
src/resource/xml/settings.cpp
Normal file
80
src/resource/xml/settings.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "settings.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include "../../util/web_filesystem.hpp"
|
||||
#endif
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Settings::Settings(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
logger.error(
|
||||
std::format("Could not initialize character save file: {} ({})", path.string(), document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto root = document.RootElement())
|
||||
{
|
||||
std::string measurementSystemString{};
|
||||
query_string_attribute(root, "MeasurementSystem", &measurementSystemString);
|
||||
measurementSystem = measurementSystemString == "Imperial" ? measurement::IMPERIAL : measurement::METRIC;
|
||||
root->QueryIntAttribute("Volume", &volume);
|
||||
root->QueryFloatAttribute("ColorR", &color.r);
|
||||
root->QueryFloatAttribute("ColorG", &color.g);
|
||||
root->QueryFloatAttribute("ColorB", &color.b);
|
||||
root->QueryFloatAttribute("WindowX", &windowPosition.x);
|
||||
root->QueryFloatAttribute("WindowY", &windowPosition.y);
|
||||
root->QueryIntAttribute("WindowW", &windowSize.x);
|
||||
root->QueryIntAttribute("WindowH", &windowSize.y);
|
||||
}
|
||||
|
||||
logger.info(std::format("Initialized settings: {}", path.string()));
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
bool Settings::is_valid() const { return isValid; }
|
||||
|
||||
void Settings::serialize(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto element = document.NewElement("Settings");
|
||||
|
||||
element->SetAttribute("MeasurementSystem", measurementSystem == measurement::IMPERIAL ? "Imperial" : "Metric");
|
||||
element->SetAttribute("Volume", volume);
|
||||
element->SetAttribute("ColorR", color.r);
|
||||
element->SetAttribute("ColorG", color.g);
|
||||
element->SetAttribute("ColorB", color.b);
|
||||
element->SetAttribute("WindowX", windowPosition.x);
|
||||
element->SetAttribute("WindowY", windowPosition.y);
|
||||
element->SetAttribute("WindowW", windowSize.x);
|
||||
element->SetAttribute("WindowH", windowSize.y);
|
||||
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
if (document.SaveFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
logger.info(std::format("Failed to initialize settings: {} ({})", path.string(), document.ErrorStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(std::format("Saved settings: {}", path.string()));
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
web_filesystem::flush_async();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
36
src/resource/xml/settings.hpp
Normal file
36
src/resource/xml/settings.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../util/measurement.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class Settings
|
||||
{
|
||||
public:
|
||||
static constexpr auto VOLUME_MIN = 0;
|
||||
static constexpr auto VOLUME_MAX = 100;
|
||||
|
||||
enum Mode
|
||||
{
|
||||
LOADER,
|
||||
IMGUI
|
||||
};
|
||||
|
||||
util::measurement::System measurementSystem{util::measurement::METRIC};
|
||||
int volume{VOLUME_MAX};
|
||||
|
||||
glm::vec3 color{0.09, 0.2196, 0.37};
|
||||
glm::ivec2 windowSize{1280, 720};
|
||||
glm::vec2 windowPosition{};
|
||||
|
||||
bool isValid{};
|
||||
|
||||
Settings() = default;
|
||||
Settings(const std::filesystem::path&);
|
||||
void serialize(const std::filesystem::path&);
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
16
src/resource/xml/sound_entry.cpp
Normal file
16
src/resource/xml/sound_entry.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "sound_entry.hpp"
|
||||
|
||||
#include "../../util/vector.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
Audio& SoundEntryCollection::get()
|
||||
{
|
||||
return at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).sound;
|
||||
}
|
||||
|
||||
void SoundEntryCollection::play()
|
||||
{
|
||||
at(util::vector::random_index_weighted(*this, [](const auto& entry) { return entry.weight; })).play();
|
||||
}
|
||||
}
|
||||
24
src/resource/xml/sound_entry.hpp
Normal file
24
src/resource/xml/sound_entry.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// Handles sound entries in .xml files. "Weight" value determines weight of being randomly selected.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../audio.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
class SoundEntry
|
||||
{
|
||||
public:
|
||||
Audio sound{};
|
||||
float weight{1.0f};
|
||||
|
||||
inline void play() { sound.play(); };
|
||||
};
|
||||
|
||||
class SoundEntryCollection : public std::vector<SoundEntry>
|
||||
{
|
||||
public:
|
||||
Audio& get();
|
||||
void play();
|
||||
};
|
||||
}
|
||||
185
src/resource/xml/util.cpp
Normal file
185
src/resource/xml/util.cpp
Normal file
@@ -0,0 +1,185 @@
|
||||
#include "util.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
#include "../../util/string.hpp"
|
||||
|
||||
#include "../../log.hpp"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* value)
|
||||
{
|
||||
const char* temp = nullptr;
|
||||
auto result = element->QueryStringAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && temp && value) *value = temp;
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_bool_attribute(XMLElement* element, const char* attribute, bool* value)
|
||||
{
|
||||
std::string temp{};
|
||||
auto result = query_string_attribute(element, attribute, &temp);
|
||||
temp = string::to_lower(temp);
|
||||
if (value) *value = temp == "true" || temp == "1" ? true : false;
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_path_attribute(XMLElement* element, const char* attribute, std::filesystem::path* value)
|
||||
{
|
||||
std::string temp{};
|
||||
auto result = query_string_attribute(element, attribute, &temp);
|
||||
if (value) *value = std::filesystem::path(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_color_attribute(XMLElement* element, const char* attribute, float* value)
|
||||
{
|
||||
int temp{};
|
||||
auto result = element->QueryIntAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && value) *value = (temp / 255.0f);
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_float_optional_attribute(XMLElement* element, const char* attribute, std::optional<float>& value)
|
||||
{
|
||||
value.emplace();
|
||||
auto result = element->QueryFloatAttribute(attribute, &*value);
|
||||
if (result == XML_NO_ATTRIBUTE) value.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_int_optional_attribute(XMLElement* element, const char* attribute, std::optional<int>& value)
|
||||
{
|
||||
value.emplace();
|
||||
auto result = element->QueryIntAttribute(attribute, &*value);
|
||||
if (result == XML_NO_ATTRIBUTE) value.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError document_load(const physfs::Path& path, XMLDocument& document)
|
||||
{
|
||||
if (!path.is_valid())
|
||||
{
|
||||
logger.error(std::format("Failed to open XML document: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return XML_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto buffer = path.read();
|
||||
|
||||
if (buffer.empty())
|
||||
{
|
||||
logger.error(std::format("Failed to read XML document: {} ({})", path.c_str(), physfs::error_get()));
|
||||
return XML_ERROR_FILE_COULD_NOT_BE_OPENED;
|
||||
}
|
||||
|
||||
auto result = document.Parse((const char*)buffer.data(), buffer.size());
|
||||
if (result != XML_SUCCESS)
|
||||
logger.error(std::format("Failed to parse XML document: {} ({})", path.c_str(), document.ErrorStr()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void query_event_id(XMLElement* element, const char* name, const Anm2& anm2, int& eventID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
if (anm2.eventMap.contains(string))
|
||||
eventID = anm2.eventMap.at(string);
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 event ID: {} ({})", string, anm2.path));
|
||||
eventID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void query_layer_id(XMLElement* element, const char* name, const Anm2& anm2, int& layerID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
if (anm2.layerMap.contains(string))
|
||||
layerID = anm2.layerMap.at(string);
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 layer ID: {} ({})", string, anm2.path));
|
||||
layerID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void query_null_id(XMLElement* element, const char* name, const Anm2& anm2, int& nullID)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
if (anm2.nullMap.contains(string))
|
||||
nullID = anm2.nullMap.at(string);
|
||||
else
|
||||
{
|
||||
logger.error(std::format("Could not query anm2 null ID: {} ({})", string, anm2.path));
|
||||
nullID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void query_anm2(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Anm2& anm2, Anm2::Flags flags)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
anm2 = Anm2(physfs::Path(archive + "/" + rootPath + "/" + string), flags);
|
||||
}
|
||||
|
||||
void query_texture(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Texture& texture)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
texture = Texture(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
}
|
||||
|
||||
void query_sound(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Audio& sound)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
sound = Audio(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
}
|
||||
|
||||
void query_font(XMLElement* element, const char* name, const std::string& archive, const std::string& rootPath,
|
||||
Font& font)
|
||||
{
|
||||
std::string string{};
|
||||
query_string_attribute(element, name, &string);
|
||||
font = Font(physfs::Path(archive + "/" + rootPath + "/" + string));
|
||||
}
|
||||
|
||||
void query_animation_entry(XMLElement* element, AnimationEntry& animationEntry)
|
||||
{
|
||||
query_string_attribute(element, "Animation", &animationEntry.animation);
|
||||
element->QueryFloatAttribute("Weight", &animationEntry.weight);
|
||||
}
|
||||
|
||||
void query_animation_entry_collection(XMLElement* element, const char* name,
|
||||
AnimationEntryCollection& animationEntryCollection)
|
||||
{
|
||||
for (auto child = element->FirstChildElement(name); child; child = child->NextSiblingElement(name))
|
||||
query_animation_entry(child, animationEntryCollection.emplace_back());
|
||||
}
|
||||
|
||||
void query_sound_entry(XMLElement* element, const std::string& archive, const std::string& rootPath,
|
||||
SoundEntry& soundEntry, const std::string& attributeName)
|
||||
{
|
||||
query_sound(element, attributeName.c_str(), archive, rootPath, soundEntry.sound);
|
||||
element->QueryFloatAttribute("Weight", &soundEntry.weight);
|
||||
}
|
||||
|
||||
void query_sound_entry_collection(XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, SoundEntryCollection& soundEntryCollection,
|
||||
const std::string& attributeName)
|
||||
{
|
||||
for (auto child = element->FirstChildElement(name); child; child = child->NextSiblingElement(name))
|
||||
query_sound_entry(child, archive, rootPath, soundEntryCollection.emplace_back(), attributeName);
|
||||
}
|
||||
}
|
||||
51
src/resource/xml/util.hpp
Normal file
51
src/resource/xml/util.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "animation_entry.hpp"
|
||||
#include "sound_entry.hpp"
|
||||
|
||||
#include "../../util/physfs.hpp"
|
||||
#include "../font.hpp"
|
||||
#include "anm2.hpp"
|
||||
|
||||
namespace game::resource::xml
|
||||
{
|
||||
tinyxml2::XMLError query_string_attribute(tinyxml2::XMLElement*, const char*, std::string*);
|
||||
tinyxml2::XMLError query_bool_attribute(tinyxml2::XMLElement*, const char*, bool*);
|
||||
tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement*, const char*, std::filesystem::path*);
|
||||
tinyxml2::XMLError query_color_attribute(tinyxml2::XMLElement*, const char*, float*);
|
||||
tinyxml2::XMLError query_float_optional_attribute(tinyxml2::XMLElement* element, const char* attribute,
|
||||
std::optional<float>& value);
|
||||
tinyxml2::XMLError query_int_optional_attribute(tinyxml2::XMLElement* element, const char* attribute,
|
||||
std::optional<int>& value);
|
||||
|
||||
void query_event_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& eventID);
|
||||
void query_layer_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& layerID);
|
||||
void query_null_id(tinyxml2::XMLElement* element, const char* name, const Anm2& anm2, int& nullID);
|
||||
|
||||
void query_anm2(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Anm2& anm2, Anm2::Flags flags = {});
|
||||
void query_texture(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Texture& texture);
|
||||
void query_sound(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Audio& sound);
|
||||
void query_font(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, Font& font);
|
||||
|
||||
void query_animation_entry(tinyxml2::XMLElement* element, AnimationEntry& animationEntry);
|
||||
void query_animation_entry_collection(tinyxml2::XMLElement* element, const char* name,
|
||||
AnimationEntryCollection& animationEntryCollection);
|
||||
|
||||
void query_sound_entry(tinyxml2::XMLElement* element, const std::string& archive, const std::string& rootPath,
|
||||
SoundEntry& soundEntry, const std::string& attributeName = "Sound");
|
||||
void query_sound_entry_collection(tinyxml2::XMLElement* element, const char* name, const std::string& archive,
|
||||
const std::string& rootPath, SoundEntryCollection& soundEntryCollection,
|
||||
const std::string& attributeName = "Sound");
|
||||
|
||||
tinyxml2::XMLError document_load(const util::physfs::Path&, tinyxml2::XMLDocument&);
|
||||
}
|
||||
Reference in New Issue
Block a user