Preload scripts on boot and grab by ID for less file io operations + general cleanup

This commit is contained in:
ethan 2025-04-22 13:37:50 -04:00
parent 3241eafea0
commit 43bbc9e21d
12 changed files with 1195 additions and 1002 deletions

View file

@ -0,0 +1,3 @@
<monster id="monster/shooty/bighead" anim="character/tmp" weapon="gun/pistol" aggressive="true" behaviour="script/behaviour/grunt" hp="10.0"/>
<monster id="monster/shooty/clone" anim="character/player" weapon="gun/shotgun" aggressive="true" behaviour="script/behaviour/grunt" hp="15.0"/>

View file

@ -3,20 +3,17 @@
<scene type="shooter" id="000" bg="backgrounds/blue_sky.png"> <scene type="shooter" id="000" bg="backgrounds/blue_sky.png">
<map name="newmap"/> <map name="newmap"/>
<entities> <entities>
<player x="7" y="5" weapon="gun/machine"> <player x="7" y="5" weapon="gun/shotgun">
<animation id="character/player"/> <animation id="character/player"/>
</player> </player>
<entity x="10" y="3" weapon="gun/pistol"> <entity x="10" y="3" weapon="gun/pistol">
<animation id="character/player"/> <animation id="character/player"/>
<script file="scripts/ai/grunt_behaviour.lua"/> <script id="script/behaviour/grunt"/>
</entity> </entity>
<entity x="6" y="3" weapon="gun/pistol"> <entity x="6" y="3" weapon="gun/pistol">
<animation id="character/tmp"/> <animation id="character/tmp"/>
<script file="scripts/ai/grunt_behaviour.lua"/> <script id="script/behaviour/grunt"/>
</entity>
<entity x="5" y="3" weapon="gun/pistol">
<animation id="character/tmp"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
</entity> </entity>
<entity x="5" y="3" weapon="gun/pistol" monster_id="monster/shooty/bighead"/>
</entities> </entities>
</scene> </scene>

View file

@ -0,0 +1,4 @@
<!-- Shooter Scripts -->
<script id="script/behaviour/grunt" file="scripts/ai/grunt_behaviour.lua"/>
<script id="script/behaviour/scared" file="scripts/ai/scared_behaviour.lua"/>

View file

@ -0,0 +1,3 @@
<!-- Ranged weapon scripts -->
<script id="script/weapon/shotgun" file="scripts/weapons/shotgun_script.lua"/>

View file

@ -12,7 +12,7 @@
</weapon> </weapon>
<weapon id="gun/shotgun" fireSpeed="1750.0" maxAmmo="64" clipSize="4"> <weapon id="gun/shotgun" fireSpeed="1750.0" maxAmmo="64" clipSize="4">
<script file="scripts/weapons/shotgun_script.lua"/> <script id="script/weapon/shotgun"/>
<animation> <animation>
<size x="55.0" y="55.0"/> <size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/> <offset x="-30.0" y="0.0"/>

View file

@ -1,52 +1,55 @@
#ifndef _H_RESOURCEMANAGER_H #ifndef _H_RESOURCEMANAGER_H
#define _H_RESOURCEMANAGER_H #define _H_RESOURCEMANAGER_H
#include <unordered_map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map>
#include "sound/soundeffect.h" #include "graphics/background.h"
#include "utility/xmlloader.h"
#include "graphics/shader.h" #include "graphics/shader.h"
#include "graphics/sprite.h" #include "graphics/sprite.h"
#include "graphics/background.h" #include "sound/soundeffect.h"
#include "utility/script.h"
#include "utility/xmlloader.h"
#include <cassert> #include <cassert>
class Weapon; class Weapon;
class Script;
class AnimationSet; class AnimationSet;
class AIScript;
class WeaponScript;
class SpriteComponent; class SpriteComponent;
class ResourceManager class ResourceManager {
{
public: public:
ResourceManager() : ResourceManager() : xmlLoader(std::make_shared<XMLLoader>()) {
xmlLoader(std::make_shared<XMLLoader>())
{
xmlLoader->loadWeapons("weapons"); xmlLoader->loadWeapons("weapons");
xmlLoader->loadAnimations("animations"); xmlLoader->loadAnimations("animations");
xmlLoader->loadMonsters("monsters");
xmlLoader->loadTileSets("maps/tilesets"); xmlLoader->loadTileSets("maps/tilesets");
xmlLoader->loadMaps("maps"); xmlLoader->loadMaps("maps");
xmlLoader->loadScripts("scripts", loadLuaString);
xmlLoader->loadScenes("scenes"); xmlLoader->loadScenes("scenes");
xmlLoader->loadSoundEffects("sounds"); xmlLoader->loadSoundEffects("sounds");
shaders["__fallback__"] = std::make_unique<GenericShader>(); shaders["__fallback__"] = std::make_unique<GenericShader>();
}; };
// Returns a NON-OWNING pointer to a sprite atlas // Returns a NON-OWNING pointer to a sprite atlas
SpriteAtlas* loadSpriteAtlas (const std::string& path, float frameSize, bool isDirectional = false); SpriteAtlas *loadSpriteAtlas(const std::string &path, float frameSize,
bool isDirectional = false);
// Returns a NON-OWNING pointer to a static sprite // Returns a NON-OWNING pointer to a static sprite
Sprite *loadSpriteStatic(const std::string &path); Sprite *loadSpriteStatic(const std::string &path);
// Returns a NON-OWNING pointer to a background // Returns a NON-OWNING pointer to a background
Background *loadBackground(const std::string &path); Background *loadBackground(const std::string &path);
const unsigned loadSoundEffect(const std::string &id); const unsigned loadSoundEffect(const std::string &id);
std::unique_ptr<AIScript> loadAIScript (const std::string& path); template <typename T = Script>
std::unique_ptr<WeaponScript> loadWeaponScript (const std::string& path); std::unique_ptr<T> loadScript(const std::string &id);
std::unique_ptr<Weapon> loadWeapon (const std::string& name, const unsigned weaponShaderID, const unsigned bulletShaderID); std::unique_ptr<Weapon> loadWeapon(const std::string &name,
std::shared_ptr<AnimationSet> loadAnimationSet (const std::string& name, int entityid = 0); const unsigned weaponShaderID,
const unsigned bulletShaderID);
std::shared_ptr<AnimationSet> loadAnimationSet(const std::string &name,
int entityid = 0);
const unsigned loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath); const unsigned loadShader(const std::string &name,
const std::string &vertexPath,
const std::string &fragPath);
const SceneData *loadScene(const std::string &id); const SceneData *loadScene(const std::string &id);
const TileSetData *loadTileSet(const std::string &name); const TileSetData *loadTileSet(const std::string &name);
@ -62,16 +65,26 @@ private:
std::unordered_map<unsigned, Shader *> shaderIDs; std::unordered_map<unsigned, Shader *> shaderIDs;
std::unordered_map<std::string, std::unique_ptr<SoundEffect>> sounds; std::unordered_map<std::string, std::unique_ptr<SoundEffect>> sounds;
std::unordered_map<std::string, std::unique_ptr<Sprite>> sprites; std::unordered_map<std::string, std::unique_ptr<Sprite>> sprites;
//std::unordered_map<std::string, std::unique_ptr<Weapon>> weapons;
//std::unordered_map<std::string, std::string> scripts;
std::unordered_map<std::string, std::unique_ptr<Background>> backgrounds; std::unordered_map<std::string, std::unique_ptr<Background>> backgrounds;
std::unordered_map<std::string, std::shared_ptr<TileSetData>> tileSets; std::unordered_map<std::string, std::shared_ptr<TileSetData>> tileSets;
//std::unordered_map<std::string, std::shared_ptr<EntityData>> entityData;
//std::unordered_map<std::string, std::shared_ptr<SceneData>> scenes;
//std::unordered_map<std::string, std::shared_ptr<MapData>> maps;
//std::unordered_map<std::string, std::shared_ptr<TileType>> tiles;
std::shared_ptr<XMLLoader> xmlLoader; std::shared_ptr<XMLLoader> xmlLoader;
static bool loadLuaString(ScriptData *script);
}; };
template <typename T>
std::unique_ptr<T> ResourceManager::loadScript(const std::string &id) {
const ScriptData *data = xmlLoader->getScriptData(id);
if (data == nullptr)
return nullptr;
try {
std::unique_ptr<T> script = std::make_unique<T>(data->script);
return std::move(script);
} catch (std::exception &e) {
LOG(ERROR, "Failed to load script '{}'", data->fileName);
return nullptr;
}
}
#endif // _H_RESOURCEMANAGER_H #endif // _H_RESOURCEMANAGER_H

View file

@ -3,22 +3,22 @@
#define SOL_ALL_SAFETIES_ON 1 #define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <sol/sol.hpp>
class Script { class Script {
public: public:
sol::state lua; sol::state lua;
Script(const std::string& path); Script(const std::string &str);
~Script(); ~Script();
private: private:
bool loadScript(const std::string& path) { bool loadScript(const std::string &str) {
auto result = lua.script_file(path); auto result = lua.script(str);
if (!result.valid()) if (!result.valid()) {
{
sol::error err = result; sol::error err = result;
std::cerr << "Failed to load script ( " << path << " ) Error: " << err.what() << std::endl; std::cerr << "Failed to load script. Error: " << err.what() << std::endl;
} }
return result.valid(); return result.valid();
} }
@ -27,19 +27,19 @@ private:
class AIScript : public Script { class AIScript : public Script {
public: public:
AIScript(const std::string& path) : Script(path) { registerUserTypes(); } AIScript(const std::string &str) : Script(str) { registerUserTypes(); }
~AIScript(); ~AIScript();
private: private:
void registerUserTypes(); void registerUserTypes();
}; };
class WeaponScript : public Script { class WeaponScript : public Script {
public: public:
WeaponScript(const std::string& path) : Script(path) { registerUserTypes(); } WeaponScript(const std::string &str) : Script(str) { registerUserTypes(); }
~WeaponScript(); ~WeaponScript();
private: private:
void registerUserTypes(); void registerUserTypes();
}; };

View file

@ -1,25 +1,30 @@
#ifndef _H_XMLLOADER_H #ifndef _H_XMLLOADER_H
#define _H_XMLLOADER_H #define _H_XMLLOADER_H
#include <vector> #include <functional>
#include <string>
#include <memory>
#include <unordered_map>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <fstream>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <sstream>
#include <tinyxml2.h> #include <tinyxml2.h>
struct Tile; struct Tile;
// Except for the player, these definitions are mostly overrides of the monster
// data
struct EntityData { struct EntityData {
bool isPlayer; bool isPlayer;
bool animated; bool animated;
int x = 0, y = 0; int x = 0, y = 0;
float hp;
std::string monsterDef;
std::string graphic; std::string graphic;
std::string weapon = "pistol"; std::string weapon = "gun/pistol";
std::string script; std::string script;
}; };
@ -71,6 +76,13 @@ struct SceneData {
std::vector<EntityData> entities; std::vector<EntityData> entities;
}; };
// Store our script as a string so we only need to read the file at boot
struct ScriptData {
std::string id;
std::string fileName;
std::string script;
};
struct WeaponData { struct WeaponData {
std::string id; std::string id;
float fireSpeed = 250.0f; float fireSpeed = 250.0f;
@ -102,14 +114,24 @@ struct AnimationData {
float frameSize; float frameSize;
}; };
// This holds a lot of defaults, but the scene can
// override a large portion of the definitions here
struct MonsterData {
std::string id;
std::string anim;
std::string weapon;
bool aggressive = false;
std::string behaviour;
float hp;
};
struct SoundData { struct SoundData {
std::string id; // <type>/<object>/<state> std::string id; // <type>/<object>/<state>
std::string path; std::string path;
bool spatial; bool spatial;
}; };
class XMLLoader class XMLLoader {
{
public: public:
XMLLoader() {} XMLLoader() {}
bool loadScenes(const char *sceneFolder); bool loadScenes(const char *sceneFolder);
@ -117,13 +139,25 @@ public:
bool loadAnimations(const char *animationFolder); bool loadAnimations(const char *animationFolder);
bool loadTileSets(const char *tileSetFolder); bool loadTileSets(const char *tileSetFolder);
bool loadMaps(const char *mapFolder); bool loadMaps(const char *mapFolder);
bool loadMonsters(const char *monsterFolder);
// luaLoader is the function we use to load the lua file.
// This will be defined in our resource manager
bool loadScripts(const char *scriptFolder,
std::function<bool(ScriptData *)> luaLoader);
bool loadSoundEffects(const char *soundFolder); bool loadSoundEffects(const char *soundFolder);
const SceneData *getSceneData(const std::string &id) const { const SceneData *getSceneData(const std::string &id) const {
try { try {
return scenes.at(id).get(); return scenes.at(id).get();
} catch (std::exception &) {
return nullptr;
} }
catch (std::exception&) { }
const ScriptData *getScriptData(const std::string &id) const {
try {
return scripts.at(id).get();
} catch (std::exception &) {
return nullptr; return nullptr;
} }
} }
@ -131,8 +165,7 @@ public:
const MapData *getMapData(const std::string &name) const { const MapData *getMapData(const std::string &name) const {
try { try {
return maps.at(name).get(); return maps.at(name).get();
} } catch (std::exception &) {
catch (std::exception&) {
return nullptr; return nullptr;
} }
} }
@ -140,8 +173,7 @@ public:
const WeaponData *getWeaponData(const std::string &id) const { const WeaponData *getWeaponData(const std::string &id) const {
try { try {
return weapons.at(id).get(); return weapons.at(id).get();
} } catch (std::exception &) {
catch (std::exception&) {
return nullptr; return nullptr;
} }
} }
@ -149,8 +181,7 @@ public:
const AnimationData *getAnimationData(const std::string &id) const { const AnimationData *getAnimationData(const std::string &id) const {
try { try {
return animations.at(id).get(); return animations.at(id).get();
} } catch (std::exception &) {
catch (std::exception&) {
return nullptr; return nullptr;
} }
} }
@ -158,8 +189,7 @@ public:
const TileSetData *getTileSetData(const std::string &name) const { const TileSetData *getTileSetData(const std::string &name) const {
try { try {
return tileSets.at(name).get(); return tileSets.at(name).get();
} } catch (std::exception &) {
catch (std::exception&) {
return nullptr; return nullptr;
} }
} }
@ -167,35 +197,47 @@ public:
const SoundData *getSoundData(const std::string &id) const { const SoundData *getSoundData(const std::string &id) const {
try { try {
return sounds.at(id).get(); return sounds.at(id).get();
} } catch (std::exception &) {
catch (std::exception&) {
return nullptr; return nullptr;
} }
} }
// return a full set of animations, may need further optimization. // return a full set of animations, may need further optimization.
// one idea is when loading animations we create a seperate map that holds each set by their reference, so we can just do a simple, // one idea is when loading animations we create a seperate map that holds
// hash table lookup. // each set by their reference, so we can just do a simple, hash table lookup.
std::vector<AnimationData*> getAnimationSet(const std::string& prefix) const { std::vector<AnimationData *>
getAnimationSet(const std::string &prefix) const {
std::vector<AnimationData *> animSet; std::vector<AnimationData *> animSet;
animSet.reserve(animations.size()); animSet.reserve(animations.size());
for (const auto &[id, anim] : animations) { for (const auto &[id, anim] : animations) {
if (id.starts_with(prefix)) animSet.push_back(anim.get()); if (id.starts_with(prefix))
animSet.push_back(anim.get());
} }
animSet.shrink_to_fit(); animSet.shrink_to_fit();
return animSet; return animSet;
} }
void clearData() { scenes.clear(); weapons.clear(); animations.clear(); maps.clear(); tileSets.clear(); } void clearData() {
scenes.clear();
weapons.clear();
animations.clear();
maps.clear();
tileSets.clear();
}
protected: protected:
bool loadXmlScene(const char *xmlFile, SceneData *out); bool loadXmlScene(const char *xmlFile, SceneData *out);
bool loadEntityData(const char *xmlFile, SceneData *out); bool loadEntityData(const char *xmlFile, SceneData *out);
bool loadTile(tinyxml2::XMLElement *tileElement, TileSetData::TileData *out); bool loadTile(tinyxml2::XMLElement *tileElement, TileSetData::TileData *out);
bool loadObject(tinyxml2::XMLElement* objectElement, TileSetData::TileData::ObjectData* out); bool loadObject(tinyxml2::XMLElement *objectElement,
TileSetData::TileData::ObjectData *out);
private: private:
std::unordered_map<std::string, std::unique_ptr<SceneData>> scenes; std::unordered_map<std::string, std::unique_ptr<SceneData>> scenes;
std::unordered_map<std::string, std::unique_ptr<ScriptData>> scripts;
std::unordered_map<std::string, std::unique_ptr<WeaponData>> weapons; std::unordered_map<std::string, std::unique_ptr<WeaponData>> weapons;
std::unordered_map<std::string, std::unique_ptr<AnimationData>> animations; std::unordered_map<std::string, std::unique_ptr<AnimationData>> animations;
std::unordered_map<std::string, std::unique_ptr<MonsterData>> monsters;
std::unordered_map<std::string, std::unique_ptr<MapData>> maps; std::unordered_map<std::string, std::unique_ptr<MapData>> maps;
std::unordered_map<std::string, std::unique_ptr<TileSetData>> tileSets; std::unordered_map<std::string, std::unique_ptr<TileSetData>> tileSets;
std::unordered_map<std::string, std::unique_ptr<SoundData>> sounds; std::unordered_map<std::string, std::unique_ptr<SoundData>> sounds;

View file

@ -101,11 +101,13 @@ void Scene::loadDebugShooterScene() {
} else { } else {
// attach ai // attach ai
if (!entityData.script.empty()) { if (!entityData.script.empty()) {
auto behaviour = resourceManager->loadAIScript(entityData.script); auto behaviour =
resourceManager->loadScript<AIScript>(entityData.script);
auto rayCaster = std::make_unique<Raycaster>( auto rayCaster = std::make_unique<Raycaster>(
40.f, 300.f, map->getCollisionMap(), mapData->tileSize); 40.f, 300.f, map->getCollisionMap(), mapData->tileSize);
auto ai = std::make_shared<AI>(entity.get(), std::move(rayCaster)); auto ai = std::make_shared<AI>(entity.get(), std::move(rayCaster));
ai->setTarget(player.get()); ai->setTarget(player.get());
if (behaviour != nullptr)
ai->attachBehaviourScript(std::move(behaviour)); ai->attachBehaviourScript(std::move(behaviour));
entity->addComponent(std::make_unique<AIComponent>(ai)); entity->addComponent(std::make_unique<AIComponent>(ai));
} }

View file

@ -1,9 +1,8 @@
#include "graphics/texture.h" #include "graphics/texture.h"
#include "utility/logger.h"
#include "util.h" #include "util.h"
#include "utility/logger.h"
bool Texture::loadTexture(const char* imagePath) bool Texture::loadTexture(const char *imagePath) {
{
SDL_Surface *buffer = IMG_Load(imagePath); SDL_Surface *buffer = IMG_Load(imagePath);
if (!buffer) if (!buffer)
ERROR_LOG("Failed to load image file: {}", imagePath); ERROR_LOG("Failed to load image file: {}", imagePath);
@ -19,12 +18,13 @@ bool Texture::loadTexture(const char* imagePath)
glBindTexture(GL_TEXTURE_2D, ID); glBindTexture(GL_TEXTURE_2D, ID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, buffer->w, buffer->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer->pixels); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, buffer->w, buffer->h, 0, GL_RGBA,
GL_UNSIGNED_BYTE, buffer->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glGenerateMipmap(ID); glGenerateMipmap(ID);
textureWidth = buffer->w; textureWidth = buffer->w;
@ -35,31 +35,27 @@ bool Texture::loadTexture(const char* imagePath)
return true; return true;
} }
void Texture::bind() void Texture::bind() {
{
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, ID); glBindTexture(GL_TEXTURE_2D, ID);
} }
Texture::~Texture() { glDeleteTextures(1, &ID); }
Texture::~Texture() bool TextureArray::loadTextures(std::vector<const char *> imagePaths) {
{
glDeleteTextures(1, &ID);
}
bool TextureArray::loadTextures(std::vector<const char*> imagePaths)
{
std::vector<SDL_Surface *> surfaces; std::vector<SDL_Surface *> surfaces;
surfaces.resize(imagePaths.size()); surfaces.resize(imagePaths.size());
// Fill surfaces vector // Fill surfaces vector
for (int i = 0; i < imagePaths.size(); ++i) for (int i = 0; i < imagePaths.size(); ++i) {
{
surfaces[i] = IMG_Load(imagePaths[i]); surfaces[i] = IMG_Load(imagePaths[i]);
if (!surfaces[i]) if (!surfaces[i])
ERROR_LOG("Failed to load image file: {}", imagePaths[i]); ERROR_LOG("Failed to load image file: {}", imagePaths[i]);
} }
if (!adjustCanvasSizes(surfaces)) if (!adjustCanvasSizes(surfaces))
ERROR_LOG("Failed to adjust canvas size of images! \n Make sure to check that every tileset has square dimensions! (512x512, 756x756 ... etc)", NULL); ERROR_LOG(
"Failed to adjust canvas size of images! \n Make sure to check that "
"every tileset has square dimensions! (512x512, 756x756 ... etc)",
NULL);
if (surfaces.empty()) if (surfaces.empty())
ERROR_LOG("No surfaces created!", NULL); ERROR_LOG("No surfaces created!", NULL);
numOfLayers = imagePaths.size(); numOfLayers = imagePaths.size();
@ -68,29 +64,14 @@ bool TextureArray::loadTextures(std::vector<const char*> imagePaths)
glBindTexture(GL_TEXTURE_2D_ARRAY, ID); glBindTexture(GL_TEXTURE_2D_ARRAY, ID);
// Creating the texture array all of our textures will live in. // Creating the texture array all of our textures will live in.
// adjustCanvasSizes makes every image the same size, so we can just use the first // adjustCanvasSizes makes every image the same size, so we can just use the
// surface in the list of surfaces // first surface in the list of surfaces
glTexImage3D(GL_TEXTURE_2D_ARRAY, glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, surfaces[0]->w, surfaces[0]->h,
0, (GLsizei)numOfLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GL_RGBA,
surfaces[0]->w,
surfaces[0]->h,
(GLsizei)numOfLayers,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
nullptr);
for (int layer = 0; layer < numOfLayers; ++layer) for (int layer = 0; layer < numOfLayers; ++layer) {
{ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, surfaces[layer]->w,
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, surfaces[layer]->h, 1, GL_RGBA, GL_UNSIGNED_BYTE,
0,
0, 0, layer,
surfaces[layer]->w,
surfaces[layer]->h,
1,
GL_RGBA,
GL_UNSIGNED_BYTE,
surfaces[layer]->pixels); surfaces[layer]->pixels);
} }
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
@ -107,33 +88,29 @@ bool TextureArray::loadTextures(std::vector<const char*> imagePaths)
return true; return true;
} }
void TextureArray::bind() void TextureArray::bind() {
{
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, ID); glBindTexture(GL_TEXTURE_2D_ARRAY, ID);
} }
bool TextureArray::adjustCanvasSizes(std::vector<SDL_Surface*>& surfaces) bool TextureArray::adjustCanvasSizes(std::vector<SDL_Surface *> &surfaces) {
{
int maxWidth = 0; int maxWidth = 0;
int maxHeight = 0; int maxHeight = 0;
for (auto& surface : surfaces) for (auto &surface : surfaces) {
{
if (surface->w != surface->h) if (surface->w != surface->h)
ERROR_LOG("Image must be a square!", NULL); ERROR_LOG("Image must be a square!", NULL);
if (surface->w > maxWidth) maxWidth = surface->w; if (surface->w > maxWidth)
if (surface->h > maxHeight) maxHeight = surface->h; maxWidth = surface->w;
if (surface->h > maxHeight)
maxHeight = surface->h;
textures.push_back(TextureData({surface->w, surface->h})); textures.push_back(TextureData({surface->w, surface->h}));
} }
for (int i = 0; i < surfaces.size(); ++i) for (int i = 0; i < surfaces.size(); ++i) {
{ SDL_Surface *canvas = SDL_CreateRGBSurface(
SDL_Surface* canvas = SDL_CreateRGBSurface(0, maxWidth, maxHeight, 0, maxWidth, maxHeight, surfaces[i]->format->BitsPerPixel,
surfaces[i]->format->BitsPerPixel, surfaces[i]->format->Rmask, surfaces[i]->format->Gmask,
surfaces[i]->format->Rmask, surfaces[i]->format->Bmask, surfaces[i]->format->Amask);
surfaces[i]->format->Gmask,
surfaces[i]->format->Bmask,
surfaces[i]->format->Amask);
SDL_FillRect(canvas, NULL, SDL_MapRGBA(canvas->format, 0, 0, 0, 0)); SDL_FillRect(canvas, NULL, SDL_MapRGBA(canvas->format, 0, 0, 0, 0));
@ -149,7 +126,4 @@ bool TextureArray::adjustCanvasSizes(std::vector<SDL_Surface*>& surfaces)
return true; return true;
} }
TextureArray::~TextureArray() TextureArray::~TextureArray() { glDeleteTextures(1, &ID); }
{
glDeleteTextures(1, &ID);
}

View file

@ -1,25 +1,26 @@
#include "utility/resourcemanager.h" #include "utility/resourcemanager.h"
#include "gameplay/weapons/weapons.h"
#include "graphics/animation.h"
#include "graphics/background.h"
#include "graphics/shader.h"
#include "graphics/sprite.h" #include "graphics/sprite.h"
#include "util.h" #include "util.h"
#include "utility/script.h" #include "utility/script.h"
#include "graphics/shader.h"
#include "graphics/animation.h"
#include "graphics/background.h"
#include "gameplay/weapons/weapons.h"
SpriteAtlas* ResourceManager::loadSpriteAtlas(const std::string& path, float frameSize, bool isDirectional) SpriteAtlas *ResourceManager::loadSpriteAtlas(const std::string &path,
{ float frameSize,
bool isDirectional) {
auto iterator = sprites.find(path); auto iterator = sprites.find(path);
if (iterator != sprites.end()) if (iterator != sprites.end())
return static_cast<SpriteAtlas *>(iterator->second.get()); return static_cast<SpriteAtlas *>(iterator->second.get());
auto sprite = std::make_unique<SpriteAtlas>(path.c_str(), frameSize, isDirectional); auto sprite =
std::make_unique<SpriteAtlas>(path.c_str(), frameSize, isDirectional);
sprites[path] = std::move(sprite); sprites[path] = std::move(sprite);
SpriteAtlas &l = static_cast<SpriteAtlas &>(*sprites[path].get()); SpriteAtlas &l = static_cast<SpriteAtlas &>(*sprites[path].get());
return static_cast<SpriteAtlas *>(sprites[path].get()); return static_cast<SpriteAtlas *>(sprites[path].get());
} }
Sprite* ResourceManager::loadSpriteStatic(const std::string& path) Sprite *ResourceManager::loadSpriteStatic(const std::string &path) {
{
auto iterator = sprites.find(path); auto iterator = sprites.find(path);
if (iterator != sprites.end()) if (iterator != sprites.end())
return iterator->second.get(); return iterator->second.get();
@ -30,23 +31,13 @@ Sprite* ResourceManager::loadSpriteStatic(const std::string& path)
return sprites[path].get(); return sprites[path].get();
} }
std::unique_ptr<AIScript> ResourceManager::loadAIScript(const std::string& path) const TileSetData *ResourceManager::loadTileSet(const std::string &name) {
{
return std::make_unique<AIScript>(path.c_str());
}
std::unique_ptr<WeaponScript> ResourceManager::loadWeaponScript(const std::string& path)
{
return std::make_unique<WeaponScript>(path.c_str());
}
const TileSetData* ResourceManager::loadTileSet(const std::string& name)
{
return xmlLoader->getTileSetData(name); return xmlLoader->getTileSetData(name);
} }
const unsigned ResourceManager::loadShader(const std::string& name, const std::string& vertexPath, const std::string& fragPath) const unsigned ResourceManager::loadShader(const std::string &name,
{ const std::string &vertexPath,
const std::string &fragPath) {
auto iterator = shaders.find(name); auto iterator = shaders.find(name);
if (iterator != shaders.end()) if (iterator != shaders.end())
return iterator->second->ID; return iterator->second->ID;
@ -60,14 +51,13 @@ const unsigned ResourceManager::loadShader(const std::string& name, const std::s
return id; return id;
} }
Shader* ResourceManager::getShaderByID(unsigned int ID) Shader *ResourceManager::getShaderByID(unsigned int ID) {
{
auto iterator = shaderIDs.find(ID); auto iterator = shaderIDs.find(ID);
return (iterator != shaderIDs.end() ? iterator->second : shaders["__fallback__"].get()); return (iterator != shaderIDs.end() ? iterator->second
: shaders["__fallback__"].get());
} }
Background* ResourceManager::loadBackground(const std::string& path) Background *ResourceManager::loadBackground(const std::string &path) {
{
auto iterator = backgrounds.find(path); auto iterator = backgrounds.find(path);
if (iterator != backgrounds.end()) if (iterator != backgrounds.end())
return iterator->second.get(); return iterator->second.get();
@ -76,23 +66,27 @@ Background* ResourceManager::loadBackground(const std::string& path)
return backgrounds[path].get(); return backgrounds[path].get();
} }
// We attach our script after we create our weapon because we are passing a reference to the weapon into the script and we don't want to pass an // We attach our script after we create our weapon because we are passing a
// reference to the weapon into the script and we don't want to pass an
// incomplete reference to our script. // incomplete reference to our script.
std::unique_ptr<Weapon> ResourceManager::loadWeapon(const std::string& id, const unsigned weaponShaderID, const unsigned bulletShaderID) std::unique_ptr<Weapon>
{ ResourceManager::loadWeapon(const std::string &id,
const unsigned weaponShaderID,
const unsigned bulletShaderID) {
const WeaponData *data = xmlLoader->getWeaponData(id); const WeaponData *data = xmlLoader->getWeaponData(id);
if (!data) { if (!data) {
LOG(ERROR, "Could not load weapon id '{}', falling back to pistol", id); LOG(ERROR, "Could not load weapon id '{}', falling back to pistol", id);
data = xmlLoader->getWeaponData("gun/pistol");// using this as a fallback for now data = xmlLoader->getWeaponData(
"gun/pistol"); // using this as a fallback for now
} }
auto weapon = std::make_unique<Weapon>(data, weaponShaderID, bulletShaderID, this); auto weapon =
std::make_unique<Weapon>(data, weaponShaderID, bulletShaderID, this);
if (!data->script.empty()) if (!data->script.empty())
weapon->attachScript(loadWeaponScript(data->script)); weapon->attachScript(loadScript<WeaponScript>(data->script));
return std::move(weapon); return std::move(weapon);
} }
const unsigned ResourceManager::loadSoundEffect(const std::string& id) const unsigned ResourceManager::loadSoundEffect(const std::string &id) {
{
auto iterator = sounds.find(id); auto iterator = sounds.find(id);
if (iterator != sounds.end()) if (iterator != sounds.end())
return iterator->second->getBuffer(); return iterator->second->getBuffer();
@ -108,42 +102,44 @@ const unsigned ResourceManager::loadSoundEffect(const std::string& id)
return sounds[id]->getBuffer(); return sounds[id]->getBuffer();
} }
const SceneData* ResourceManager::loadScene(const std::string& id) const SceneData *ResourceManager::loadScene(const std::string &id) {
{
return xmlLoader->getSceneData(id); return xmlLoader->getSceneData(id);
} }
std::shared_ptr<AnimationSet> ResourceManager::loadAnimationSet(const std::string& prefix, int entityid) std::shared_ptr<AnimationSet>
{ ResourceManager::loadAnimationSet(const std::string &prefix, int entityid) {
auto animSetData = xmlLoader->getAnimationSet(prefix); auto animSetData = xmlLoader->getAnimationSet(prefix);
return std::make_shared<AnimationSet>(entityid, this, animSetData); return std::make_shared<AnimationSet>(entityid, this, animSetData);
} }
// this will need some work, but for now where there is only one weapon type we can work with it void ResourceManager::clearResources() {
// TODO: Allow different weapon types!
/*
template <typename T>
std::shared_ptr<T> ResourceManager::loadWeapon(const std::string& name, std::shared_ptr<Shader> shader, std::shared_ptr<SpriteComponent> spriteComponent)
{
auto iterator = weapons.find(name);
if (iterator != weapons.end())
return iterator->second;
auto weapon = std::make_shared<T>(T(shader, spriteComponent));
weapons[name] = weapon;
return weapon;
}
*/
void ResourceManager::clearResources()
{
sprites.clear(); sprites.clear();
shaders.clear(); shaders.clear();
shaderIDs.clear(); shaderIDs.clear();
tileSets.clear(); tileSets.clear();
} }
ResourceManager::~ResourceManager() ResourceManager::~ResourceManager() {
{
clearResources(); clearResources();
xmlLoader.reset(); xmlLoader.reset();
} }
bool ResourceManager::loadLuaString(ScriptData *scriptData) {
std::filesystem::path file(scriptData->fileName);
if (file.empty() || !file.has_extension())
return false;
try {
std::ifstream fileStream(file);
std::ostringstream sstr;
sstr << fileStream.rdbuf();
scriptData->script = sstr.str().c_str();
fileStream.close();
} catch (std::exception &e) {
ERROR_LOG("Failed to read script file: '{}' - '{}'", scriptData->fileName,
e.what());
}
return true;
}

View file

@ -8,14 +8,14 @@
Loading every scene in the scene folder and storing in hashmap scenes Loading every scene in the scene folder and storing in hashmap scenes
hashkey is the id of the scene hashkey is the id of the scene
*/ */
bool XMLLoader::loadScenes(const char* sceneFolder) bool XMLLoader::loadScenes(const char *sceneFolder) {
{
std::filesystem::path folder(sceneFolder); std::filesystem::path folder(sceneFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder not found!", sceneFolder); ERROR_LOG("'{}' folder not found!", sceneFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
SceneData scene; SceneData scene;
if (!loadXmlScene((const char *)file.path().string().c_str(), &scene)) if (!loadXmlScene((const char *)file.path().string().c_str(), &scene))
@ -28,8 +28,7 @@ bool XMLLoader::loadScenes(const char* sceneFolder)
/* /*
Loading scene data Loading scene data
*/ */
bool XMLLoader::loadXmlScene(const char* xmlFile, SceneData* out) bool XMLLoader::loadXmlScene(const char *xmlFile, SceneData *out) {
{
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load file: {}", xmlFile); ERROR_LOG("Failed to load file: {}", xmlFile);
@ -62,15 +61,113 @@ bool XMLLoader::loadXmlScene(const char* xmlFile, SceneData* out)
} }
/* /*
Load entity data held in the scene file, store inside of the SceneData out parameter * Load monster definitions
*/ */
bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out) bool XMLLoader::loadMonsters(const char *monsterFolder) {
{ std::filesystem::path folder(monsterFolder);
if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("''{}' folder does not exist!", monsterFolder);
for (auto &file : std::filesystem::directory_iterator(folder)) {
if (!file.path().has_extension() || !file.path().has_filename() ||
!file.exists() || file.is_directory())
continue;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue;
for (tinyxml2::XMLElement *e = doc.FirstChildElement("monster");
e != nullptr; e = e->NextSiblingElement("monster")) {
const char *id, *anim, *weapon, *behaviour;
MonsterData data;
data.aggressive = false;
// The rules for creating a monster is pretty strict, since we can always
// just create entities without creating a definition for them as well.
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("anim", &anim) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("weapon", &weapon) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("behaviour", &behaviour) !=
tinyxml2::XML_SUCCESS ||
e->QueryFloatAttribute("hp", &data.hp) != tinyxml2::XML_SUCCESS)
continue;
e->QueryBoolAttribute("aggressive", &data.aggressive);
data.id = id;
data.anim = anim;
data.weapon = weapon;
data.behaviour = behaviour;
monsters.try_emplace(data.id, std::make_unique<MonsterData>(data));
}
}
return true;
}
/*
* Loading scripts from the xml file then storing the information inside of the
* ScriptData definition struct. This includes storing our script contents. This
* is mostly a test, but I feel like it would be faster to hold the scripts as
* raw string we store at boot then load based on their ID. It certainly makes
* creating definitions easier and cleaner to look at.
*
* It is the responsibilty of the resource manager to open read read the lua
* file via the luaLoader function!
*
*/
bool XMLLoader::loadScripts(const char *scriptFolder,
std::function<bool(ScriptData *)> luaLoader) {
std::filesystem::path folder(scriptFolder);
if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder does not exist!", scriptFolder);
for (auto &file : std::filesystem::directory_iterator(folder)) {
if (!file.path().has_extension() || !file.path().has_filename() ||
!file.exists() || file.is_directory())
continue;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue;
for (tinyxml2::XMLElement *e = doc.FirstChildElement("script");
e != nullptr; e = e->NextSiblingElement("script")) {
ScriptData scriptData;
const char *id, *fileName;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("file", &fileName) != tinyxml2::XML_SUCCESS)
continue;
scriptData.id = id;
scriptData.fileName = fileName;
if (!luaLoader(&scriptData)) {
LOG(ERROR, "Failed to load script '{}'", fileName);
continue;
}
scripts.try_emplace(scriptData.id,
std::make_unique<ScriptData>(scriptData));
}
}
return true;
}
/*
Load entity data held in the scene file, store inside of the SceneData
out parameter
*/
bool XMLLoader::loadEntityData(const char *xmlFile, SceneData *out) {
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load file: {}", xmlFile); ERROR_LOG("Failed to load file: {}", xmlFile);
tinyxml2::XMLElement* entities = doc.FirstChildElement()->FirstChildElement("entities"); tinyxml2::XMLElement *entities =
doc.FirstChildElement()->FirstChildElement("entities");
// Adding the player. Player must be in the scene or the scene will not load! // Adding the player. Player must be in the scene or the scene will not load!
tinyxml2::XMLElement *playerElement = entities->FirstChildElement("player"); tinyxml2::XMLElement *playerElement = entities->FirstChildElement("player");
@ -80,25 +177,29 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
EntityData playData; EntityData playData;
const char *graphic, *weaponName = "pistol"; const char *graphic, *weaponName = "pistol";
if (playerElement->QueryIntAttribute("x", &playData.x) != tinyxml2::XML_SUCCESS || if (playerElement->QueryIntAttribute("x", &playData.x) !=
playerElement->QueryIntAttribute("y", &playData.y) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
ERROR_LOG("Failed to find x or y attribute in 'player' tag. File: {}", xmlFile); playerElement->QueryIntAttribute("y", &playData.y) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to find x or y attribute in 'player' tag. File: {}",
xmlFile);
playerElement->QueryStringAttribute("weapon", &weaponName); playerElement->QueryStringAttribute("weapon", &weaponName);
tinyxml2::XMLElement *anim = playerElement->FirstChildElement("animation"); tinyxml2::XMLElement *anim = playerElement->FirstChildElement("animation");
if (anim == NULL) if (anim == NULL) {
{
playData.animated = false; playData.animated = false;
tinyxml2::XMLElement *sprite = playerElement->FirstChildElement("sprite"); tinyxml2::XMLElement *sprite = playerElement->FirstChildElement("sprite");
if (sprite == NULL) if (sprite == NULL)
ERROR_LOG("Could not find tag 'sprite' in 'player' tag. File: {}", xmlFile); ERROR_LOG("Could not find tag 'sprite' in 'player' tag. File: {}",
xmlFile);
if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS) if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Could not find attribute 'file' in 'player' -> 'sprite' tag. File: {}", xmlFile); ERROR_LOG("Could not find attribute 'file' in 'player' -> 'sprite' tag. "
} "File: {}",
else xmlFile);
{ } else {
playData.animated = true; playData.animated = true;
if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS) if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Could not find 'name' attribute in 'animation' tag. File: {}", xmlFile); ERROR_LOG("Could not find 'name' attribute in 'animation' tag. File: {}",
xmlFile);
} }
playData.isPlayer = true; playData.isPlayer = true;
playData.graphic = graphic; playData.graphic = graphic;
@ -110,35 +211,59 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
// Adding every other entity... // Adding every other entity...
// TODO: Add npcs to game and enable their use with XMLLoader // TODO: Add npcs to game and enable their use with XMLLoader
for (tinyxml2::XMLElement* e = entities->FirstChildElement("entity"); e != NULL; e = e->NextSiblingElement("entity")) for (tinyxml2::XMLElement *e = entities->FirstChildElement("entity");
{ e != NULL; e = e->NextSiblingElement("entity")) {
EntityData data; EntityData data;
const char *graphic, *weaponName = "pistol", *scriptPath; const char *monsterDef, *graphic, *weaponID, *scriptID;
if (e->QueryIntAttribute("x", &data.x) != tinyxml2::XML_SUCCESS || if (e->QueryIntAttribute("x", &data.x) != tinyxml2::XML_SUCCESS ||
e->QueryIntAttribute("y", &data.y) != tinyxml2::XML_SUCCESS) e->QueryIntAttribute("y", &data.y) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Could not load position coordinates for entity. File: {}", xmlFile); ERROR_LOG("Could not load position coordinates for entity. File: {}",
xmlFile);
e->QueryStringAttribute("weapon", &weaponName); e->QueryStringAttribute("weapon", &weaponName);
e->QueryStringAttribute("monster_id", &monsterDef);
// If we find that the entity has a monster definition we will fill in the
// defaults Having a monster definition means we must be animated by default
// and we will prefer the animated default for now.
if (monsterDef != NULL && monsters[monsterDef] != NULL) {
// Setting up the defaults
data.animated = true;
data.monsterDef = monsterDef;
weaponID = monsters[monsterDef]->weapon.c_str();
graphic = monsters[monsterDef]->anim.c_str();
scriptID = monsters[monsterDef]->behaviour.c_str();
}
try {
// So if we look for the animation tag and there is no monster definition
// only then will we take in a sprite as the graphic
tinyxml2::XMLElement *anim = e->FirstChildElement("animation"); tinyxml2::XMLElement *anim = e->FirstChildElement("animation");
if (anim == NULL) if (anim == NULL && data.monsterDef.empty()) {
{
data.animated = false; data.animated = false;
tinyxml2::XMLElement *sprite = e->FirstChildElement("sprite"); tinyxml2::XMLElement *sprite = e->FirstChildElement("sprite");
if (sprite == NULL) if (sprite == NULL)
continue; continue;
if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS) if (sprite->QueryStringAttribute("file", &graphic) !=
tinyxml2::XML_SUCCESS)
continue; continue;
} } else if (data.monsterDef.empty()) {
else
{
data.animated = true; data.animated = true;
if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS) if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS)
continue; continue;
} else if (anim != NULL) {
// We will take alternative animation ids if specified
data.animated = true;
anim->QueryStringAttribute("id", &graphic);
} }
} catch (std::exception &e) {
LOG(ERROR, "Failed to load entity! ''{}'", e.what());
continue;
}
// We just overload the script id if there is an overload given
tinyxml2::XMLElement *script = e->FirstChildElement("script"); tinyxml2::XMLElement *script = e->FirstChildElement("script");
script->QueryStringAttribute("file", &scriptPath); if (script != NULL)
script->QueryStringAttribute("id", &scriptID);
data.isPlayer = false; data.isPlayer = false;
data.graphic = graphic; data.graphic = graphic;
data.script = scriptPath; data.script = scriptID;
data.weapon = weaponName; data.weapon = weaponName;
out->entities.push_back(data); out->entities.push_back(data);
@ -147,40 +272,41 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
return true; return true;
} }
float getFloatIfExists(tinyxml2::XMLElement* e) float getFloatIfExists(tinyxml2::XMLElement *e) {
{
float buf = 0.f; float buf = 0.f;
if (e) if (e) {
{
e->QueryFloatText(&buf); e->QueryFloatText(&buf);
} }
return buf; return buf;
} }
/* /*
Load every weapon file, weapon nodes can be fit together in one file or placed in seperate files. Load every weapon file, weapon nodes can be fit together in one file or
hash key is the weapon name placed in seperate files. hash key is the weapon id
*/ */
bool XMLLoader::loadWeapons(const char* weaponFolder) bool XMLLoader::loadWeapons(const char *weaponFolder) {
{ // We are gonna check every xml file within the weaponFolder, then check every
// We are gonna check every xml file within the weaponFolder, then check every weapon node within each file. // weapon node within each file.
std::filesystem::path folder(weaponFolder); std::filesystem::path folder(weaponFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder does not exist!", weaponFolder); ERROR_LOG("'{}' folder does not exist!", weaponFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue; continue;
tinyxml2::XMLElement *guns = doc.FirstChildElement("weapons"); tinyxml2::XMLElement *guns = doc.FirstChildElement("weapons");
if (guns == NULL) if (guns == NULL)
continue; continue;
for (tinyxml2::XMLElement* weapon = guns->FirstChildElement("weapon"); weapon != NULL; weapon = weapon->NextSiblingElement("weapon")) for (tinyxml2::XMLElement *weapon = guns->FirstChildElement("weapon");
{ weapon != NULL; weapon = weapon->NextSiblingElement("weapon")) {
// populate weapon data into this temp buffer, then push it into our list of weapons // populate weapon data into this temp buffer, then push it into our list
// of weapons
WeaponData data; WeaponData data;
const char *id, *graphic, *bulletGraphic, *script; const char *id, *graphic, *bulletGraphic, *script;
// Getting top level weapon data, attribs in the weapon Node // Getting top level weapon data, attribs in the weapon Node
@ -192,16 +318,14 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
// Getting script file if Node exists // Getting script file if Node exists
tinyxml2::XMLElement *wepScript = weapon->FirstChildElement("script"); tinyxml2::XMLElement *wepScript = weapon->FirstChildElement("script");
if (wepScript) if (wepScript)
wepScript->QueryStringAttribute("file", &script); wepScript->QueryStringAttribute("id", &script);
else else
script = ""; script = "";
// Getting weapon sprite information, held in the sprite node // Getting weapon sprite information, held in the sprite node
tinyxml2::XMLElement *anim = weapon->FirstChildElement("animation"); tinyxml2::XMLElement *anim = weapon->FirstChildElement("animation");
if (anim) if (anim) {
{
data.animated = true; data.animated = true;
if (anim->ChildElementCount() != 0) if (anim->ChildElementCount() != 0) {
{
tinyxml2::XMLElement *size = anim->FirstChildElement("size"); tinyxml2::XMLElement *size = anim->FirstChildElement("size");
size->QueryFloatAttribute("x", &data.sizeX); size->QueryFloatAttribute("x", &data.sizeX);
size->QueryFloatAttribute("y", &data.sizeY); size->QueryFloatAttribute("y", &data.sizeY);
@ -209,17 +333,14 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
offset->QueryFloatAttribute("x", &data.offsetX); offset->QueryFloatAttribute("x", &data.offsetX);
offset->QueryFloatAttribute("y", &data.offsetY); offset->QueryFloatAttribute("y", &data.offsetY);
} }
} } else {
else
{
tinyxml2::XMLElement *wepSprite = weapon->FirstChildElement("sprite"); tinyxml2::XMLElement *wepSprite = weapon->FirstChildElement("sprite");
if (wepSprite == NULL) if (wepSprite == NULL)
continue; continue;
wepSprite->QueryStringAttribute("file", &graphic); wepSprite->QueryStringAttribute("file", &graphic);
data.animated = false; data.animated = false;
if (wepSprite->ChildElementCount() != 0) if (wepSprite->ChildElementCount() != 0) {
{
tinyxml2::XMLElement *size = wepSprite->FirstChildElement("size"); tinyxml2::XMLElement *size = wepSprite->FirstChildElement("size");
size->QueryFloatAttribute("x", &data.sizeX); size->QueryFloatAttribute("x", &data.sizeX);
size->QueryFloatAttribute("y", &data.sizeY); size->QueryFloatAttribute("y", &data.sizeY);
@ -228,25 +349,26 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
offset->QueryFloatAttribute("y", &data.offsetY); offset->QueryFloatAttribute("y", &data.offsetY);
} }
} }
// getting bullet information held in the bullet node and each child node held therein // getting bullet information held in the bullet node and each child node
// held therein
tinyxml2::XMLElement *bullet = weapon->FirstChildElement("bullet"); tinyxml2::XMLElement *bullet = weapon->FirstChildElement("bullet");
if (bullet->FindAttribute("anim")) if (bullet->FindAttribute("anim")) {
{
data.bulletAnimated = true; data.bulletAnimated = true;
if (bullet->QueryStringAttribute("anim", &bulletGraphic) != tinyxml2::XML_SUCCESS) if (bullet->QueryStringAttribute("anim", &bulletGraphic) !=
tinyxml2::XML_SUCCESS)
continue; continue;
} } else {
else
{
data.bulletAnimated = false; data.bulletAnimated = false;
if (bullet->QueryStringAttribute("sprite", &bulletGraphic) != tinyxml2::XML_SUCCESS) if (bullet->QueryStringAttribute("sprite", &bulletGraphic) !=
tinyxml2::XML_SUCCESS)
continue; continue;
} }
if (bullet->FirstChildElement("size")->QueryFloatAttribute(
if (bullet->FirstChildElement("size")->QueryFloatAttribute("x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS || "x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS ||
bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS) bullet->FirstChildElement("size")->QueryFloatAttribute(
"y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS)
continue; continue;
data.id = id; data.id = id;
@ -263,13 +385,13 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
speed->QueryFloatText(&data.bulletSpeed); speed->QueryFloatText(&data.bulletSpeed);
if (drop) if (drop)
drop->QueryFloatText(&data.bulletDrop); drop->QueryFloatText(&data.bulletDrop);
if (modifier) if (modifier) {
{
modifier->QueryFloatAttribute("min", &data.modMin); modifier->QueryFloatAttribute("min", &data.modMin);
modifier->QueryFloatAttribute("max", &data.modMax); modifier->QueryFloatAttribute("max", &data.modMax);
} }
LOG(DEBUG, "Loaded {} from {}", data.id, file.path().filename().generic_string()); LOG(DEBUG, "Loaded {} from {}", data.id,
file.path().filename().generic_string());
weapons.try_emplace(data.id, std::make_unique<WeaponData>(data)); weapons.try_emplace(data.id, std::make_unique<WeaponData>(data));
} }
} }
@ -277,33 +399,37 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
} }
/* /*
* Load each animation node and store inside a hashmap by their id. Each object that needs an animation will be given a prefix <type>/<object>. * Load each animation node and store inside a hashmap by their id. Each object
* Which will be used to look up the corresponding animation * that needs an animation will be given a prefix <type>/<object>. Which will be
* used to look up the corresponding animation
*/ */
bool XMLLoader::loadAnimations(const char* animationFolder) bool XMLLoader::loadAnimations(const char *animationFolder) {
{
std::filesystem::path folder(animationFolder); std::filesystem::path folder(animationFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder does not exist!", animationFolder); ERROR_LOG("'{}' folder does not exist!", animationFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue; continue;
for (tinyxml2::XMLElement* e = doc.FirstChildElement("animation"); e != NULL; e = e->NextSiblingElement("animation")) for (tinyxml2::XMLElement *e = doc.FirstChildElement("animation");
{ e != NULL; e = e->NextSiblingElement("animation")) {
AnimationData animData; AnimationData animData;
const char *id, *spriteAtlas; const char *id, *spriteAtlas;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS) if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS)
continue; continue;
if (e->QueryStringAttribute("atlas", &spriteAtlas) != tinyxml2::XML_SUCCESS || if (e->QueryStringAttribute("atlas", &spriteAtlas) !=
e->QueryFloatAttribute("frameSize", &animData.frameSize) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
e->QueryFloatAttribute("frameSize", &animData.frameSize) !=
tinyxml2::XML_SUCCESS)
continue; continue;
e->QueryFloatAttribute("FPS", &animData.FPS); e->QueryFloatAttribute("FPS", &animData.FPS);
@ -311,7 +437,8 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
animData.id = id; animData.id = id;
animData.spriteAtlas = spriteAtlas; animData.spriteAtlas = spriteAtlas;
animations.try_emplace(animData.id, std::make_unique<AnimationData>(animData)); animations.try_emplace(animData.id,
std::make_unique<AnimationData>(animData));
} }
} }
return true; return true;
@ -321,9 +448,8 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
Start of (LoadTileSets) Start of (LoadTileSets)
Load every tileset and store them in hashmap tileSets Load every tileset and store them in hashmap tileSets
hashkey is the <parent directory>/<filename>, this is due to the expected hashkey is the <parent directory>/<filename>, this is due to the
file structure, being expected file structure, being Resources
Resources
| |
- maps - maps
| |
@ -332,18 +458,19 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
This makes it easier to import maps from the Tiled program. This makes it easier to import maps from the Tiled program.
Refer to the Tiled project file for the file structure Refer to the Tiled project file for the file structure
*/ */
bool XMLLoader::loadTileSets(const char* tileSetFolder) bool XMLLoader::loadTileSets(const char *tileSetFolder) {
{
std::filesystem::path folder(tileSetFolder); std::filesystem::path folder(tileSetFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder failed to load!", tileSetFolder); ERROR_LOG("'{}' folder failed to load!", tileSetFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue; continue;
tinyxml2::XMLElement *tileSet = doc.FirstChildElement("tileset"); tinyxml2::XMLElement *tileSet = doc.FirstChildElement("tileset");
@ -355,10 +482,14 @@ bool XMLLoader::loadTileSets(const char* tileSetFolder)
tileSet->QueryStringAttribute("class", &setType); tileSet->QueryStringAttribute("class", &setType);
// Read attributes of tileset element // Read attributes of tileset element
if (tileSet->QueryStringAttribute("name", &setName) != tinyxml2::XML_SUCCESS || if (tileSet->QueryStringAttribute("name", &setName) !=
tileSet->QueryFloatAttribute("tilewidth", &tileSetData.tileSize) != tinyxml2::XML_SUCCESS || tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("tilecount", &tileSetData.tileCount) != tinyxml2::XML_SUCCESS || tileSet->QueryFloatAttribute("tilewidth", &tileSetData.tileSize) !=
tileSet->QueryIntAttribute("columns", &tileSetData.columns) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("tilecount", &tileSetData.tileCount) !=
tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("columns", &tileSetData.columns) !=
tinyxml2::XML_SUCCESS)
continue; continue;
tinyxml2::XMLElement *image = tileSet->FirstChildElement("image"); tinyxml2::XMLElement *image = tileSet->FirstChildElement("image");
@ -366,76 +497,92 @@ bool XMLLoader::loadTileSets(const char* tileSetFolder)
continue; continue;
// Reading image element attribs // Reading image element attribs
if (image->QueryStringAttribute("source", &setFile) != tinyxml2::XML_SUCCESS || if (image->QueryStringAttribute("source", &setFile) !=
image->QueryIntAttribute("width", &tileSetData.width) != tinyxml2::XML_SUCCESS || tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("height", &tileSetData.height) != tinyxml2::XML_SUCCESS) image->QueryIntAttribute("width", &tileSetData.width) !=
tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("height", &tileSetData.height) !=
tinyxml2::XML_SUCCESS)
continue; continue;
for (tinyxml2::XMLElement* tileElement = tileSet->FirstChildElement("tile"); tileElement != NULL; tileElement = tileElement->NextSiblingElement("tile")) for (tinyxml2::XMLElement *tileElement = tileSet->FirstChildElement("tile");
{ tileElement != NULL;
tileElement = tileElement->NextSiblingElement("tile")) {
TileSetData::TileData tileData; TileSetData::TileData tileData;
if (!loadTile(tileElement, &tileData)) if (!loadTile(tileElement, &tileData))
continue; continue;
tileSetData.tiles.push_back(std::make_shared<TileSetData::TileData>(tileData)); tileSetData.tiles.push_back(
std::make_shared<TileSetData::TileData>(tileData));
} }
tileSetData.name = setName; tileSetData.name = setName;
tileSetData.type = setType; tileSetData.type = setType;
tileSetData.file = file.path().parent_path().string() + "/" + std::string(setFile); tileSetData.file =
std::string key = folder.filename().string() + "/" + file.path().filename().string(); file.path().parent_path().string() + "/" + std::string(setFile);
std::string key =
folder.filename().string() + "/" + file.path().filename().string();
tileSets.try_emplace(key, std::make_unique<TileSetData>(tileSetData)); tileSets.try_emplace(key, std::make_unique<TileSetData>(tileSetData));
} }
return true; return true;
} }
bool XMLLoader::loadTile(tinyxml2::XMLElement* tileElement, TileSetData::TileData* out) bool XMLLoader::loadTile(tinyxml2::XMLElement *tileElement,
{ TileSetData::TileData *out) {
TileSetData::TileData tileData; TileSetData::TileData tileData;
const char *tileType; const char *tileType;
if (tileElement == NULL) if (tileElement == NULL)
ERROR_LOG("Failed to find 'tile' tag.", NULL); ERROR_LOG("Failed to find 'tile' tag.", NULL);
if (tileElement->QueryIntAttribute("id", &tileData.id) != tinyxml2::XML_SUCCESS || if (tileElement->QueryIntAttribute("id", &tileData.id) !=
tileElement->QueryStringAttribute("type", &tileType) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
tileElement->QueryStringAttribute("type", &tileType) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load tile id or type. {}", tileElement->Value()); ERROR_LOG("Failed to load tile id or type. {}", tileElement->Value());
if (std::string(tileType).compare("object") == 0) if (std::string(tileType).compare("object") == 0) {
{
/* /*
Refer to .tsx file of tile set for specific details regarding the layout of the tile element. Refer to .tsx file of tile set for specific details regarding the
You will notice each tile has the potential to be a container for objects, so that is how we are layout of the tile element. You will notice each tile has the potential
handling it here. If the tileType was specified as an object, but it contains no objects, then we don't to be a container for objects, so that is how we are handling it here. If
load the tile. Objects are defined in the Tiled program using the collision editor. the tileType was specified as an object, but it contains no objects, then
we don't load the tile. Objects are defined in the Tiled program using
the collision editor.
*/ */
tinyxml2::XMLElement* objectGroup = tileElement->FirstChildElement("objectgroup"); tinyxml2::XMLElement *objectGroup =
tileElement->FirstChildElement("objectgroup");
if (objectGroup == NULL) if (objectGroup == NULL)
ERROR_LOG("Failed to find tag 'objectgroup' in tile id: {}", tileData.id); ERROR_LOG("Failed to find tag 'objectgroup' in tile id: {}", tileData.id);
for (tinyxml2::XMLElement* obj = objectGroup->FirstChildElement("object"); obj != NULL; obj = obj->NextSiblingElement("object")) for (tinyxml2::XMLElement *obj = objectGroup->FirstChildElement("object");
{ obj != NULL; obj = obj->NextSiblingElement("object")) {
TileSetData::TileData::ObjectData objData; TileSetData::TileData::ObjectData objData;
if (!loadObject(obj, &objData)) if (!loadObject(obj, &objData))
continue; continue;
tileData.objects.push_back(std::make_shared<TileSetData::TileData::ObjectData>(objData)); tileData.objects.push_back(
std::make_shared<TileSetData::TileData::ObjectData>(objData));
} }
if (tileData.objects.empty()) if (tileData.objects.empty())
ERROR_LOG("No objects found", NULL); ERROR_LOG("No objects found", NULL);
} } else {
else
{
/* /*
May support multiple properties in the future with a future property struct to hold any value type May support multiple properties in the future with a future property
But this may not be needed, so we'll just capture the walkable property for now. struct to hold any value type But this may not be needed, so we'll just
Assume default of there is no properties tag or walkable property, just use nested if's capture the walkable property for now. Assume default of there is no
properties tag or walkable property, just use nested if's
*/ */
tinyxml2::XMLElement* properties = tileElement->FirstChildElement("properties"); tinyxml2::XMLElement *properties =
tileElement->FirstChildElement("properties");
if (properties == NULL) { if (properties == NULL) {
LOG(WARN, "No properties found on tile id: {} assuming defaults", tileData.id); LOG(WARN, "No properties found on tile id: {} assuming defaults",
tileData.id);
} else { } else {
tinyxml2::XMLElement* propWalk = properties->FirstChildElement("property"); tinyxml2::XMLElement *propWalk =
properties->FirstChildElement("property");
if (propWalk == NULL) { if (propWalk == NULL) {
LOG(WARN, "No walkable propery found on tile id: {} assuming walkable", tileData.id); LOG(WARN, "No walkable propery found on tile id: {} assuming walkable",
tileData.id);
} else { } else {
for (auto& prop = propWalk; prop != NULL; prop = prop->NextSiblingElement()) { for (auto &prop = propWalk; prop != NULL;
prop = prop->NextSiblingElement()) {
if (prop->Attribute("name", "walkable")) { if (prop->Attribute("name", "walkable")) {
prop->QueryBoolAttribute("value", &tileData.walkable); prop->QueryBoolAttribute("value", &tileData.walkable);
break; break;
@ -451,8 +598,8 @@ bool XMLLoader::loadTile(tinyxml2::XMLElement* tileElement, TileSetData::TileDat
return true; return true;
} }
bool XMLLoader::loadObject(tinyxml2::XMLElement* objElement, TileSetData::TileData::ObjectData* out) bool XMLLoader::loadObject(tinyxml2::XMLElement *objElement,
{ TileSetData::TileData::ObjectData *out) {
TileSetData::TileData::ObjectData objData; TileSetData::TileData::ObjectData objData;
const char *objName; const char *objName;
@ -460,23 +607,32 @@ bool XMLLoader::loadObject(tinyxml2::XMLElement* objElement, TileSetData::TileDa
if (objElement == NULL) if (objElement == NULL)
ERROR_LOG("Failed to find 'object' tag", NULL); ERROR_LOG("Failed to find 'object' tag", NULL);
// load id and name // load id and name
if (objElement->QueryIntAttribute("id", &objData.id) != tinyxml2::XML_SUCCESS || if (objElement->QueryIntAttribute("id", &objData.id) !=
objElement->QueryStringAttribute("name", &objName) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
objElement->QueryStringAttribute("name", &objName) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("No id or name found for object. {}", objElement->Value()); ERROR_LOG("No id or name found for object. {}", objElement->Value());
// load position into vec2 // load position into vec2
if (objElement->QueryFloatAttribute("x", &objData.pos.x) != tinyxml2::XML_SUCCESS || if (objElement->QueryFloatAttribute("x", &objData.pos.x) !=
objElement->QueryFloatAttribute("y", &objData.pos.y) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
ERROR_LOG("Failed to load position coordinates for object: {}", objData.name); objElement->QueryFloatAttribute("y", &objData.pos.y) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load position coordinates for object: {}",
objData.name);
// load size into seperate vec2 // load size into seperate vec2
if (objElement->QueryFloatAttribute("width", &objData.size.x) != tinyxml2::XML_SUCCESS || if (objElement->QueryFloatAttribute("width", &objData.size.x) !=
objElement->QueryFloatAttribute("height", &objData.size.y) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
objElement->QueryFloatAttribute("height", &objData.size.y) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load size of object: {}", objData.name); ERROR_LOG("Failed to load size of object: {}", objData.name);
// refer to comment in XMLLoader::loadTile regarding the properties portion as to why we return true here // refer to comment in XMLLoader::loadTile regarding the properties portion as
tinyxml2::XMLElement* properties = objElement->FirstChildElement("properties"); // to why we return true here
if (properties != NULL) tinyxml2::XMLElement *properties =
{ objElement->FirstChildElement("properties");
tinyxml2::XMLElement* propCollide = properties->FirstChildElement("property"); if (properties != NULL) {
tinyxml2::XMLElement *propCollide =
properties->FirstChildElement("property");
if (propCollide != NULL && propCollide->Attribute("name", "collidable")) if (propCollide != NULL && propCollide->Attribute("name", "collidable"))
propCollide->QueryBoolAttribute("value", &objData.collidable); propCollide->QueryBoolAttribute("value", &objData.collidable);
} }
@ -488,18 +644,19 @@ bool XMLLoader::loadObject(tinyxml2::XMLElement* objElement, TileSetData::TileDa
} }
/* End of (LoadTileSets) */ /* End of (LoadTileSets) */
bool XMLLoader::loadMaps(const char* mapFolder) bool XMLLoader::loadMaps(const char *mapFolder) {
{
std::filesystem::path folder(mapFolder); std::filesystem::path folder(mapFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("'{}' folder not found!", mapFolder); ERROR_LOG("'{}' folder not found!", mapFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue; continue;
tinyxml2::XMLElement *map = doc.FirstChildElement("map"); tinyxml2::XMLElement *map = doc.FirstChildElement("map");
@ -507,17 +664,22 @@ bool XMLLoader::loadMaps(const char* mapFolder)
continue; continue;
MapData mapData; MapData mapData;
if (map->QueryIntAttribute("width", &mapData.width) != tinyxml2::XML_SUCCESS || if (map->QueryIntAttribute("width", &mapData.width) !=
map->QueryIntAttribute("height", &mapData.height) != tinyxml2::XML_SUCCESS || tinyxml2::XML_SUCCESS ||
map->QueryFloatAttribute("tilewidth", &mapData.tileSize) != tinyxml2::XML_SUCCESS) map->QueryIntAttribute("height", &mapData.height) !=
tinyxml2::XML_SUCCESS ||
map->QueryFloatAttribute("tilewidth", &mapData.tileSize) !=
tinyxml2::XML_SUCCESS)
continue; continue;
for (tinyxml2::XMLElement* tileSet = map->FirstChildElement("tileset"); tileSet != NULL; tileSet = tileSet->NextSiblingElement("tileset")) for (tinyxml2::XMLElement *tileSet = map->FirstChildElement("tileset");
{ tileSet != NULL; tileSet = tileSet->NextSiblingElement("tileset")) {
int firstGID = 0; int firstGID = 0;
const char *tileSetPath; const char *tileSetPath;
if (tileSet->QueryStringAttribute("source", &tileSetPath) != tinyxml2::XML_SUCCESS || if (tileSet->QueryStringAttribute("source", &tileSetPath) !=
tileSet->QueryIntAttribute("firstgid", &firstGID) != tinyxml2::XML_SUCCESS) tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("firstgid", &firstGID) !=
tinyxml2::XML_SUCCESS)
continue; continue;
mapData.tileSets.push_back({firstGID, tileSetPath}); mapData.tileSets.push_back({firstGID, tileSetPath});
} }
@ -528,20 +690,18 @@ bool XMLLoader::loadMaps(const char* mapFolder)
mapData.tiles.reserve(10); mapData.tiles.reserve(10);
mapData.tiles.resize(10); mapData.tiles.resize(10);
for (int layer = 0; layer < 10; ++layer) for (int layer = 0; layer < 10; ++layer) {
{
mapData.tiles[layer].reserve(mapData.height); mapData.tiles[layer].reserve(mapData.height);
mapData.tiles[layer].resize(mapData.height); mapData.tiles[layer].resize(mapData.height);
for (int y = 0; y < mapData.height; ++y) for (int y = 0; y < mapData.height; ++y) {
{
mapData.tiles[layer][y].reserve(mapData.width); mapData.tiles[layer][y].reserve(mapData.width);
mapData.tiles[layer][y].resize(mapData.width); mapData.tiles[layer][y].resize(mapData.width);
} }
} }
int layerNumber = 0; int layerNumber = 0;
for (tinyxml2::XMLElement* layer = map->FirstChildElement("layer"); layer != NULL; layer = layer->NextSiblingElement("layer")) for (tinyxml2::XMLElement *layer = map->FirstChildElement("layer");
{ layer != NULL; layer = layer->NextSiblingElement("layer")) {
tinyxml2::XMLElement *data = layer->FirstChildElement("data"); tinyxml2::XMLElement *data = layer->FirstChildElement("data");
if (data == NULL) if (data == NULL)
continue; continue;
@ -555,12 +715,10 @@ bool XMLLoader::loadMaps(const char* mapFolder)
std::stringstream ssidSet(idSet); std::stringstream ssidSet(idSet);
int x = 0, y = -1; int x = 0, y = -1;
while (std::getline(ssidSet, tileRow) && y < mapData.height) while (std::getline(ssidSet, tileRow) && y < mapData.height) {
{
std::stringstream ssid(tileRow); std::stringstream ssid(tileRow);
x = 0; x = 0;
while (std::getline(ssid, tileString, ',') && x < mapData.width) while (std::getline(ssid, tileString, ',') && x < mapData.width) {
{
int id = std::stoi(tileString); int id = std::stoi(tileString);
mapData.tiles[layerNumber][y][x] = id; mapData.tiles[layerNumber][y][x] = id;
x++; x++;
@ -582,22 +740,23 @@ bool XMLLoader::loadMaps(const char* mapFolder)
return true; return true;
} }
bool XMLLoader::loadSoundEffects(const char *soundFolder) bool XMLLoader::loadSoundEffects(const char *soundFolder) {
{
std::filesystem::path folder(soundFolder); std::filesystem::path folder(soundFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) if (!std::filesystem::exists(folder) ||
!std::filesystem::is_directory(folder))
ERROR_LOG("Directory '{}' does not exist!", soundFolder); ERROR_LOG("Directory '{}' does not exist!", soundFolder);
for (auto& file : std::filesystem::directory_iterator(folder)) for (auto &file : std::filesystem::directory_iterator(folder)) {
{ if (!file.path().has_extension() || !file.path().has_filename() ||
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory()) !file.exists() || file.is_directory())
continue; continue;
tinyxml2::XMLDocument doc; tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().string().c_str()) != tinyxml2::XML_SUCCESS) if (doc.LoadFile(file.path().string().c_str()) != tinyxml2::XML_SUCCESS)
continue; continue;
for (tinyxml2::XMLElement *e = doc.FirstChildElement("sound"); e != NULL; e = e->NextSiblingElement("sound")) { for (tinyxml2::XMLElement *e = doc.FirstChildElement("sound"); e != NULL;
e = e->NextSiblingElement("sound")) {
SoundData sound; SoundData sound;
const char *id, *path; const char *id, *path;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS || if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS ||