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">
<map name="newmap"/>
<entities>
<player x="7" y="5" weapon="gun/machine">
<player x="7" y="5" weapon="gun/shotgun">
<animation id="character/player"/>
</player>
<entity x="10" y="3" weapon="gun/pistol">
<animation id="character/player"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
<script id="script/behaviour/grunt"/>
</entity>
<entity x="6" y="3" weapon="gun/pistol">
<animation id="character/tmp"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
</entity>
<entity x="5" y="3" weapon="gun/pistol">
<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" monster_id="monster/shooty/bighead"/>
</entities>
</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 id="gun/shotgun" fireSpeed="1750.0" maxAmmo="64" clipSize="4">
<script file="scripts/weapons/shotgun_script.lua"/>
<script id="script/weapon/shotgun"/>
<animation>
<size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/>

View file

@ -1,52 +1,55 @@
#ifndef _H_RESOURCEMANAGER_H
#define _H_RESOURCEMANAGER_H
#include <unordered_map>
#include <memory>
#include <string>
#include <unordered_map>
#include "sound/soundeffect.h"
#include "utility/xmlloader.h"
#include "graphics/background.h"
#include "graphics/shader.h"
#include "graphics/sprite.h"
#include "graphics/background.h"
#include "sound/soundeffect.h"
#include "utility/script.h"
#include "utility/xmlloader.h"
#include <cassert>
class Weapon;
class Script;
class AnimationSet;
class AIScript;
class WeaponScript;
class SpriteComponent;
class ResourceManager
{
class ResourceManager {
public:
ResourceManager() :
xmlLoader(std::make_shared<XMLLoader>())
{
ResourceManager() : xmlLoader(std::make_shared<XMLLoader>()) {
xmlLoader->loadWeapons("weapons");
xmlLoader->loadAnimations("animations");
xmlLoader->loadMonsters("monsters");
xmlLoader->loadTileSets("maps/tilesets");
xmlLoader->loadMaps("maps");
xmlLoader->loadScripts("scripts", loadLuaString);
xmlLoader->loadScenes("scenes");
xmlLoader->loadSoundEffects("sounds");
shaders["__fallback__"] = std::make_unique<GenericShader>();
};
// 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
Sprite *loadSpriteStatic(const std::string &path);
// Returns a NON-OWNING pointer to a background
Background *loadBackground(const std::string &path);
const unsigned loadSoundEffect(const std::string &id);
std::unique_ptr<AIScript> loadAIScript (const std::string& path);
std::unique_ptr<WeaponScript> loadWeaponScript (const std::string& path);
std::unique_ptr<Weapon> loadWeapon (const std::string& name, const unsigned weaponShaderID, const unsigned bulletShaderID);
std::shared_ptr<AnimationSet> loadAnimationSet (const std::string& name, int entityid = 0);
template <typename T = Script>
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::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 TileSetData *loadTileSet(const std::string &name);
@ -62,16 +65,26 @@ private:
std::unordered_map<unsigned, Shader *> shaderIDs;
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<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::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;
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

View file

@ -3,22 +3,22 @@
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include <chrono>
#include <memory>
#include <sol/sol.hpp>
class Script {
public:
sol::state lua;
Script(const std::string& path);
Script(const std::string &str);
~Script();
private:
bool loadScript(const std::string& path) {
auto result = lua.script_file(path);
if (!result.valid())
{
bool loadScript(const std::string &str) {
auto result = lua.script(str);
if (!result.valid()) {
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();
}
@ -27,19 +27,19 @@ private:
class AIScript : public Script {
public:
AIScript(const std::string& path) : Script(path) { registerUserTypes(); }
AIScript(const std::string &str) : Script(str) { registerUserTypes(); }
~AIScript();
private:
void registerUserTypes();
};
class WeaponScript : public Script {
public:
WeaponScript(const std::string& path) : Script(path) { registerUserTypes(); }
WeaponScript(const std::string &str) : Script(str) { registerUserTypes(); }
~WeaponScript();
private:
void registerUserTypes();
};

View file

@ -1,25 +1,30 @@
#ifndef _H_XMLLOADER_H
#define _H_XMLLOADER_H
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
#include <functional>
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <fstream>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <tinyxml2.h>
struct Tile;
// Except for the player, these definitions are mostly overrides of the monster
// data
struct EntityData {
bool isPlayer;
bool animated;
int x = 0, y = 0;
float hp;
std::string monsterDef;
std::string graphic;
std::string weapon = "pistol";
std::string weapon = "gun/pistol";
std::string script;
};
@ -71,6 +76,13 @@ struct SceneData {
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 {
std::string id;
float fireSpeed = 250.0f;
@ -102,14 +114,24 @@ struct AnimationData {
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 {
std::string id; // <type>/<object>/<state>
std::string path;
bool spatial;
};
class XMLLoader
{
class XMLLoader {
public:
XMLLoader() {}
bool loadScenes(const char *sceneFolder);
@ -117,13 +139,25 @@ public:
bool loadAnimations(const char *animationFolder);
bool loadTileSets(const char *tileSetFolder);
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);
const SceneData *getSceneData(const std::string &id) const {
try {
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;
}
}
@ -131,8 +165,7 @@ public:
const MapData *getMapData(const std::string &name) const {
try {
return maps.at(name).get();
}
catch (std::exception&) {
} catch (std::exception &) {
return nullptr;
}
}
@ -140,8 +173,7 @@ public:
const WeaponData *getWeaponData(const std::string &id) const {
try {
return weapons.at(id).get();
}
catch (std::exception&) {
} catch (std::exception &) {
return nullptr;
}
}
@ -149,8 +181,7 @@ public:
const AnimationData *getAnimationData(const std::string &id) const {
try {
return animations.at(id).get();
}
catch (std::exception&) {
} catch (std::exception &) {
return nullptr;
}
}
@ -158,8 +189,7 @@ public:
const TileSetData *getTileSetData(const std::string &name) const {
try {
return tileSets.at(name).get();
}
catch (std::exception&) {
} catch (std::exception &) {
return nullptr;
}
}
@ -167,35 +197,47 @@ public:
const SoundData *getSoundData(const std::string &id) const {
try {
return sounds.at(id).get();
}
catch (std::exception&) {
} catch (std::exception &) {
return nullptr;
}
}
// 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,
// hash table lookup.
std::vector<AnimationData*> getAnimationSet(const std::string& prefix) const {
// 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, hash table lookup.
std::vector<AnimationData *>
getAnimationSet(const std::string &prefix) const {
std::vector<AnimationData *> animSet;
animSet.reserve(animations.size());
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();
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:
bool loadXmlScene(const char *xmlFile, SceneData *out);
bool loadEntityData(const char *xmlFile, SceneData *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:
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<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<TileSetData>> tileSets;
std::unordered_map<std::string, std::unique_ptr<SoundData>> sounds;

View file

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

View file

@ -1,9 +1,8 @@
#include "graphics/texture.h"
#include "utility/logger.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);
if (!buffer)
ERROR_LOG("Failed to load image file: {}", imagePath);
@ -19,12 +18,13 @@ bool Texture::loadTexture(const char* imagePath)
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_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glGenerateMipmap(ID);
textureWidth = buffer->w;
@ -35,31 +35,27 @@ bool Texture::loadTexture(const char* imagePath)
return true;
}
void Texture::bind()
{
void Texture::bind() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, ID);
}
Texture::~Texture() { glDeleteTextures(1, &ID); }
Texture::~Texture()
{
glDeleteTextures(1, &ID);
}
bool TextureArray::loadTextures(std::vector<const char*> imagePaths)
{
bool TextureArray::loadTextures(std::vector<const char *> imagePaths) {
std::vector<SDL_Surface *> surfaces;
surfaces.resize(imagePaths.size());
// 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]);
if (!surfaces[i])
ERROR_LOG("Failed to load image file: {}", imagePaths[i]);
}
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())
ERROR_LOG("No surfaces created!", NULL);
numOfLayers = imagePaths.size();
@ -68,29 +64,14 @@ bool TextureArray::loadTextures(std::vector<const char*> imagePaths)
glBindTexture(GL_TEXTURE_2D_ARRAY, ID);
// 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
// surface in the list of surfaces
glTexImage3D(GL_TEXTURE_2D_ARRAY,
0,
GL_RGBA,
surfaces[0]->w,
surfaces[0]->h,
(GLsizei)numOfLayers,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
nullptr);
// adjustCanvasSizes makes every image the same size, so we can just use the
// first surface in the list of surfaces
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, surfaces[0]->w, surfaces[0]->h,
(GLsizei)numOfLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
for (int layer = 0; layer < numOfLayers; ++layer)
{
glTexSubImage3D(GL_TEXTURE_2D_ARRAY,
0,
0, 0, layer,
surfaces[layer]->w,
surfaces[layer]->h,
1,
GL_RGBA,
GL_UNSIGNED_BYTE,
for (int layer = 0; layer < numOfLayers; ++layer) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, surfaces[layer]->w,
surfaces[layer]->h, 1, GL_RGBA, GL_UNSIGNED_BYTE,
surfaces[layer]->pixels);
}
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;
}
void TextureArray::bind()
{
void TextureArray::bind() {
glActiveTexture(GL_TEXTURE0);
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 maxHeight = 0;
for (auto& surface : surfaces)
{
for (auto &surface : surfaces) {
if (surface->w != surface->h)
ERROR_LOG("Image must be a square!", NULL);
if (surface->w > maxWidth) maxWidth = surface->w;
if (surface->h > maxHeight) maxHeight = surface->h;
if (surface->w > maxWidth)
maxWidth = surface->w;
if (surface->h > maxHeight)
maxHeight = surface->h;
textures.push_back(TextureData({surface->w, surface->h}));
}
for (int i = 0; i < surfaces.size(); ++i)
{
SDL_Surface* canvas = SDL_CreateRGBSurface(0, maxWidth, maxHeight,
surfaces[i]->format->BitsPerPixel,
surfaces[i]->format->Rmask,
surfaces[i]->format->Gmask,
surfaces[i]->format->Bmask,
surfaces[i]->format->Amask);
for (int i = 0; i < surfaces.size(); ++i) {
SDL_Surface *canvas = SDL_CreateRGBSurface(
0, maxWidth, maxHeight, surfaces[i]->format->BitsPerPixel,
surfaces[i]->format->Rmask, surfaces[i]->format->Gmask,
surfaces[i]->format->Bmask, surfaces[i]->format->Amask);
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;
}
TextureArray::~TextureArray()
{
glDeleteTextures(1, &ID);
}
TextureArray::~TextureArray() { glDeleteTextures(1, &ID); }

View file

@ -1,25 +1,26 @@
#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 "util.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);
if (iterator != sprites.end())
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);
SpriteAtlas &l = 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);
if (iterator != sprites.end())
return iterator->second.get();
@ -30,23 +31,13 @@ Sprite* ResourceManager::loadSpriteStatic(const std::string& path)
return sprites[path].get();
}
std::unique_ptr<AIScript> ResourceManager::loadAIScript(const std::string& path)
{
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)
{
const TileSetData *ResourceManager::loadTileSet(const std::string &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);
if (iterator != shaders.end())
return iterator->second->ID;
@ -60,14 +51,13 @@ const unsigned ResourceManager::loadShader(const std::string& name, const std::s
return id;
}
Shader* ResourceManager::getShaderByID(unsigned int ID)
{
Shader *ResourceManager::getShaderByID(unsigned int 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);
if (iterator != backgrounds.end())
return iterator->second.get();
@ -76,23 +66,27 @@ Background* ResourceManager::loadBackground(const std::string& path)
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.
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);
if (!data) {
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())
weapon->attachScript(loadWeaponScript(data->script));
weapon->attachScript(loadScript<WeaponScript>(data->script));
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);
if (iterator != sounds.end())
return iterator->second->getBuffer();
@ -108,42 +102,44 @@ const unsigned ResourceManager::loadSoundEffect(const std::string& id)
return sounds[id]->getBuffer();
}
const SceneData* ResourceManager::loadScene(const std::string& id)
{
const SceneData *ResourceManager::loadScene(const std::string &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);
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
// 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()
{
void ResourceManager::clearResources() {
sprites.clear();
shaders.clear();
shaderIDs.clear();
tileSets.clear();
}
ResourceManager::~ResourceManager()
{
ResourceManager::~ResourceManager() {
clearResources();
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
hashkey is the id of the scene
*/
bool XMLLoader::loadScenes(const char* sceneFolder)
{
bool XMLLoader::loadScenes(const char *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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
for (auto &file : std::filesystem::directory_iterator(folder)) {
if (!file.path().has_extension() || !file.path().has_filename() ||
!file.exists() || file.is_directory())
continue;
SceneData scene;
if (!loadXmlScene((const char *)file.path().string().c_str(), &scene))
@ -28,8 +28,7 @@ bool XMLLoader::loadScenes(const char* sceneFolder)
/*
Loading scene data
*/
bool XMLLoader::loadXmlScene(const char* xmlFile, SceneData* out)
{
bool XMLLoader::loadXmlScene(const char *xmlFile, SceneData *out) {
tinyxml2::XMLDocument doc;
if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
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;
if (doc.LoadFile(xmlFile) != tinyxml2::XML_SUCCESS)
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!
tinyxml2::XMLElement *playerElement = entities->FirstChildElement("player");
@ -80,25 +177,29 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
EntityData playData;
const char *graphic, *weaponName = "pistol";
if (playerElement->QueryIntAttribute("x", &playData.x) != tinyxml2::XML_SUCCESS ||
playerElement->QueryIntAttribute("y", &playData.y) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to find x or y attribute in 'player' tag. File: {}", xmlFile);
if (playerElement->QueryIntAttribute("x", &playData.x) !=
tinyxml2::XML_SUCCESS ||
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);
tinyxml2::XMLElement *anim = playerElement->FirstChildElement("animation");
if (anim == NULL)
{
if (anim == NULL) {
playData.animated = false;
tinyxml2::XMLElement *sprite = playerElement->FirstChildElement("sprite");
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)
ERROR_LOG("Could not find attribute 'file' in 'player' -> 'sprite' tag. File: {}", xmlFile);
}
else
{
ERROR_LOG("Could not find attribute 'file' in 'player' -> 'sprite' tag. "
"File: {}",
xmlFile);
} else {
playData.animated = true;
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.graphic = graphic;
@ -110,35 +211,59 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
// Adding every other entity...
// 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;
const char *graphic, *weaponName = "pistol", *scriptPath;
const char *monsterDef, *graphic, *weaponID, *scriptID;
if (e->QueryIntAttribute("x", &data.x) != 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("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");
if (anim == NULL)
{
if (anim == NULL && data.monsterDef.empty()) {
data.animated = false;
tinyxml2::XMLElement *sprite = e->FirstChildElement("sprite");
if (sprite == NULL)
continue;
if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS)
if (sprite->QueryStringAttribute("file", &graphic) !=
tinyxml2::XML_SUCCESS)
continue;
}
else
{
} else if (data.monsterDef.empty()) {
data.animated = true;
if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS)
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");
script->QueryStringAttribute("file", &scriptPath);
if (script != NULL)
script->QueryStringAttribute("id", &scriptID);
data.isPlayer = false;
data.graphic = graphic;
data.script = scriptPath;
data.script = scriptID;
data.weapon = weaponName;
out->entities.push_back(data);
@ -147,40 +272,41 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
return true;
}
float getFloatIfExists(tinyxml2::XMLElement* e)
{
float getFloatIfExists(tinyxml2::XMLElement *e) {
float buf = 0.f;
if (e)
{
if (e) {
e->QueryFloatText(&buf);
}
return buf;
}
/*
Load every weapon file, weapon nodes can be fit together in one file or placed in seperate files.
hash key is the weapon name
Load every weapon file, weapon nodes can be fit together in one file or
placed in seperate files. hash key is the weapon id
*/
bool XMLLoader::loadWeapons(const char* weaponFolder)
{
// We are gonna check every xml file within the weaponFolder, then check every weapon node within each file.
bool XMLLoader::loadWeapons(const char *weaponFolder) {
// We are gonna check every xml file within the weaponFolder, then check every
// weapon node within each file.
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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
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)
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement *guns = doc.FirstChildElement("weapons");
if (guns == NULL)
continue;
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
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
WeaponData data;
const char *id, *graphic, *bulletGraphic, *script;
// 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
tinyxml2::XMLElement *wepScript = weapon->FirstChildElement("script");
if (wepScript)
wepScript->QueryStringAttribute("file", &script);
wepScript->QueryStringAttribute("id", &script);
else
script = "";
// Getting weapon sprite information, held in the sprite node
tinyxml2::XMLElement *anim = weapon->FirstChildElement("animation");
if (anim)
{
if (anim) {
data.animated = true;
if (anim->ChildElementCount() != 0)
{
if (anim->ChildElementCount() != 0) {
tinyxml2::XMLElement *size = anim->FirstChildElement("size");
size->QueryFloatAttribute("x", &data.sizeX);
size->QueryFloatAttribute("y", &data.sizeY);
@ -209,17 +333,14 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
offset->QueryFloatAttribute("x", &data.offsetX);
offset->QueryFloatAttribute("y", &data.offsetY);
}
}
else
{
} else {
tinyxml2::XMLElement *wepSprite = weapon->FirstChildElement("sprite");
if (wepSprite == NULL)
continue;
wepSprite->QueryStringAttribute("file", &graphic);
data.animated = false;
if (wepSprite->ChildElementCount() != 0)
{
if (wepSprite->ChildElementCount() != 0) {
tinyxml2::XMLElement *size = wepSprite->FirstChildElement("size");
size->QueryFloatAttribute("x", &data.sizeX);
size->QueryFloatAttribute("y", &data.sizeY);
@ -228,25 +349,26 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
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");
if (bullet->FindAttribute("anim"))
{
if (bullet->FindAttribute("anim")) {
data.bulletAnimated = true;
if (bullet->QueryStringAttribute("anim", &bulletGraphic) != tinyxml2::XML_SUCCESS)
if (bullet->QueryStringAttribute("anim", &bulletGraphic) !=
tinyxml2::XML_SUCCESS)
continue;
}
else
{
} else {
data.bulletAnimated = false;
if (bullet->QueryStringAttribute("sprite", &bulletGraphic) != tinyxml2::XML_SUCCESS)
if (bullet->QueryStringAttribute("sprite", &bulletGraphic) !=
tinyxml2::XML_SUCCESS)
continue;
}
if (bullet->FirstChildElement("size")->QueryFloatAttribute("x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS ||
bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS)
if (bullet->FirstChildElement("size")->QueryFloatAttribute(
"x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS ||
bullet->FirstChildElement("size")->QueryFloatAttribute(
"y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS)
continue;
data.id = id;
@ -263,13 +385,13 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
speed->QueryFloatText(&data.bulletSpeed);
if (drop)
drop->QueryFloatText(&data.bulletDrop);
if (modifier)
{
if (modifier) {
modifier->QueryFloatAttribute("min", &data.modMin);
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));
}
}
@ -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>.
* Which will be used to look up the corresponding animation
* 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>. 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);
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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
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)
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
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;
const char *id, *spriteAtlas;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS)
continue;
if (e->QueryStringAttribute("atlas", &spriteAtlas) != tinyxml2::XML_SUCCESS ||
e->QueryFloatAttribute("frameSize", &animData.frameSize) != tinyxml2::XML_SUCCESS)
if (e->QueryStringAttribute("atlas", &spriteAtlas) !=
tinyxml2::XML_SUCCESS ||
e->QueryFloatAttribute("frameSize", &animData.frameSize) !=
tinyxml2::XML_SUCCESS)
continue;
e->QueryFloatAttribute("FPS", &animData.FPS);
@ -311,7 +437,8 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
animData.id = id;
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;
@ -321,9 +448,8 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
Start of (LoadTileSets)
Load every tileset and store them in hashmap tileSets
hashkey is the <parent directory>/<filename>, this is due to the expected
file structure, being
Resources
hashkey is the <parent directory>/<filename>, this is due to the
expected file structure, being Resources
|
- maps
|
@ -332,18 +458,19 @@ bool XMLLoader::loadAnimations(const char* animationFolder)
This makes it easier to import maps from the Tiled program.
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);
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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
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)
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement *tileSet = doc.FirstChildElement("tileset");
@ -355,10 +482,14 @@ bool XMLLoader::loadTileSets(const char* tileSetFolder)
tileSet->QueryStringAttribute("class", &setType);
// Read attributes of tileset element
if (tileSet->QueryStringAttribute("name", &setName) != tinyxml2::XML_SUCCESS ||
tileSet->QueryFloatAttribute("tilewidth", &tileSetData.tileSize) != tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("tilecount", &tileSetData.tileCount) != tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("columns", &tileSetData.columns) != tinyxml2::XML_SUCCESS)
if (tileSet->QueryStringAttribute("name", &setName) !=
tinyxml2::XML_SUCCESS ||
tileSet->QueryFloatAttribute("tilewidth", &tileSetData.tileSize) !=
tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("tilecount", &tileSetData.tileCount) !=
tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("columns", &tileSetData.columns) !=
tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement *image = tileSet->FirstChildElement("image");
@ -366,76 +497,92 @@ bool XMLLoader::loadTileSets(const char* tileSetFolder)
continue;
// Reading image element attribs
if (image->QueryStringAttribute("source", &setFile) != tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("width", &tileSetData.width) != tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("height", &tileSetData.height) != tinyxml2::XML_SUCCESS)
if (image->QueryStringAttribute("source", &setFile) !=
tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("width", &tileSetData.width) !=
tinyxml2::XML_SUCCESS ||
image->QueryIntAttribute("height", &tileSetData.height) !=
tinyxml2::XML_SUCCESS)
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;
if (!loadTile(tileElement, &tileData))
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.type = setType;
tileSetData.file = file.path().parent_path().string() + "/" + std::string(setFile);
std::string key = folder.filename().string() + "/" + file.path().filename().string();
tileSetData.file =
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));
}
return true;
}
bool XMLLoader::loadTile(tinyxml2::XMLElement* tileElement, TileSetData::TileData* out)
{
bool XMLLoader::loadTile(tinyxml2::XMLElement *tileElement,
TileSetData::TileData *out) {
TileSetData::TileData tileData;
const char *tileType;
if (tileElement == NULL)
ERROR_LOG("Failed to find 'tile' tag.", NULL);
if (tileElement->QueryIntAttribute("id", &tileData.id) != tinyxml2::XML_SUCCESS ||
tileElement->QueryStringAttribute("type", &tileType) != tinyxml2::XML_SUCCESS)
if (tileElement->QueryIntAttribute("id", &tileData.id) !=
tinyxml2::XML_SUCCESS ||
tileElement->QueryStringAttribute("type", &tileType) !=
tinyxml2::XML_SUCCESS)
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.
You will notice each tile has the potential to be a container for objects, so that is how we are
handling it here. If 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.
Refer to .tsx file of tile set for specific details regarding the
layout of the tile element. You will notice each tile has the potential
to be a container for objects, so that is how we are handling it here. If
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)
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;
if (!loadObject(obj, &objData))
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())
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
But this may not be needed, so we'll just capture the walkable property for now.
Assume default of there is no properties tag or walkable property, just use nested if's
May support multiple properties in the future with a future property
struct to hold any value type But this may not be needed, so we'll just
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) {
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 {
tinyxml2::XMLElement* propWalk = properties->FirstChildElement("property");
tinyxml2::XMLElement *propWalk =
properties->FirstChildElement("property");
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 {
for (auto& prop = propWalk; prop != NULL; prop = prop->NextSiblingElement()) {
for (auto &prop = propWalk; prop != NULL;
prop = prop->NextSiblingElement()) {
if (prop->Attribute("name", "walkable")) {
prop->QueryBoolAttribute("value", &tileData.walkable);
break;
@ -451,8 +598,8 @@ bool XMLLoader::loadTile(tinyxml2::XMLElement* tileElement, TileSetData::TileDat
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;
const char *objName;
@ -460,23 +607,32 @@ bool XMLLoader::loadObject(tinyxml2::XMLElement* objElement, TileSetData::TileDa
if (objElement == NULL)
ERROR_LOG("Failed to find 'object' tag", NULL);
// load id and name
if (objElement->QueryIntAttribute("id", &objData.id) != tinyxml2::XML_SUCCESS ||
objElement->QueryStringAttribute("name", &objName) != tinyxml2::XML_SUCCESS)
if (objElement->QueryIntAttribute("id", &objData.id) !=
tinyxml2::XML_SUCCESS ||
objElement->QueryStringAttribute("name", &objName) !=
tinyxml2::XML_SUCCESS)
ERROR_LOG("No id or name found for object. {}", objElement->Value());
// load position into vec2
if (objElement->QueryFloatAttribute("x", &objData.pos.x) != tinyxml2::XML_SUCCESS ||
objElement->QueryFloatAttribute("y", &objData.pos.y) != tinyxml2::XML_SUCCESS)
ERROR_LOG("Failed to load position coordinates for object: {}", objData.name);
if (objElement->QueryFloatAttribute("x", &objData.pos.x) !=
tinyxml2::XML_SUCCESS ||
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
if (objElement->QueryFloatAttribute("width", &objData.size.x) != tinyxml2::XML_SUCCESS ||
objElement->QueryFloatAttribute("height", &objData.size.y) != tinyxml2::XML_SUCCESS)
if (objElement->QueryFloatAttribute("width", &objData.size.x) !=
tinyxml2::XML_SUCCESS ||
objElement->QueryFloatAttribute("height", &objData.size.y) !=
tinyxml2::XML_SUCCESS)
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
tinyxml2::XMLElement* properties = objElement->FirstChildElement("properties");
if (properties != NULL)
{
tinyxml2::XMLElement* propCollide = properties->FirstChildElement("property");
// refer to comment in XMLLoader::loadTile regarding the properties portion as
// to why we return true here
tinyxml2::XMLElement *properties =
objElement->FirstChildElement("properties");
if (properties != NULL) {
tinyxml2::XMLElement *propCollide =
properties->FirstChildElement("property");
if (propCollide != NULL && propCollide->Attribute("name", "collidable"))
propCollide->QueryBoolAttribute("value", &objData.collidable);
}
@ -488,18 +644,19 @@ bool XMLLoader::loadObject(tinyxml2::XMLElement* objElement, TileSetData::TileDa
}
/* End of (LoadTileSets) */
bool XMLLoader::loadMaps(const char* mapFolder)
{
bool XMLLoader::loadMaps(const char *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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
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)
if (doc.LoadFile(file.path().generic_string().c_str()) !=
tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement *map = doc.FirstChildElement("map");
@ -507,17 +664,22 @@ bool XMLLoader::loadMaps(const char* mapFolder)
continue;
MapData mapData;
if (map->QueryIntAttribute("width", &mapData.width) != tinyxml2::XML_SUCCESS ||
map->QueryIntAttribute("height", &mapData.height) != tinyxml2::XML_SUCCESS ||
map->QueryFloatAttribute("tilewidth", &mapData.tileSize) != tinyxml2::XML_SUCCESS)
if (map->QueryIntAttribute("width", &mapData.width) !=
tinyxml2::XML_SUCCESS ||
map->QueryIntAttribute("height", &mapData.height) !=
tinyxml2::XML_SUCCESS ||
map->QueryFloatAttribute("tilewidth", &mapData.tileSize) !=
tinyxml2::XML_SUCCESS)
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;
const char *tileSetPath;
if (tileSet->QueryStringAttribute("source", &tileSetPath) != tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("firstgid", &firstGID) != tinyxml2::XML_SUCCESS)
if (tileSet->QueryStringAttribute("source", &tileSetPath) !=
tinyxml2::XML_SUCCESS ||
tileSet->QueryIntAttribute("firstgid", &firstGID) !=
tinyxml2::XML_SUCCESS)
continue;
mapData.tileSets.push_back({firstGID, tileSetPath});
}
@ -528,20 +690,18 @@ bool XMLLoader::loadMaps(const char* mapFolder)
mapData.tiles.reserve(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].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].resize(mapData.width);
}
}
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");
if (data == NULL)
continue;
@ -555,12 +715,10 @@ bool XMLLoader::loadMaps(const char* mapFolder)
std::stringstream ssidSet(idSet);
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);
x = 0;
while (std::getline(ssid, tileString, ',') && x < mapData.width)
{
while (std::getline(ssid, tileString, ',') && x < mapData.width) {
int id = std::stoi(tileString);
mapData.tiles[layerNumber][y][x] = id;
x++;
@ -582,22 +740,23 @@ bool XMLLoader::loadMaps(const char* mapFolder)
return true;
}
bool XMLLoader::loadSoundEffects(const char *soundFolder)
{
bool XMLLoader::loadSoundEffects(const char *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);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
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().string().c_str()) != tinyxml2::XML_SUCCESS)
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;
const char *id, *path;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS ||