Graphics configuration file and initial configuration layout setup.

pull/28/head
sigonasr2 2 years ago
parent a0916cfe9b
commit f7f29196c5
  1. 86
      Crawler/Crawler.cpp
  2. 6
      Crawler/Crawler.h
  3. 4
      Crawler/Crawler.vcxproj
  4. 16
      Crawler/Crawler.vcxproj.filters
  5. 1
      Crawler/Ranger.cpp
  6. 47
      Crawler/Slime_King_Encounter.txt
  7. 2
      Crawler/Version.h
  8. 2
      Crawler/assets/config/configuration.txt
  9. 33
      Crawler/assets/config/gfx/gfx.txt
  10. 435
      Crawler/olcUTIL_DataFile.h
  11. 78
      Crawler/pge.data
  12. 2
      Crawler/pge.js
  13. BIN
      Crawler/pge.wasm
  14. BIN
      x64/Release/Crawler.exe

@ -42,6 +42,14 @@ Crawler::Crawler()
bool Crawler::OnUserCreate(){ bool Crawler::OnUserCreate(){
utils::datafile::Read(DATA,"assets/config/configuration.txt");
std::string CONFIG_PATH = GetString("config_path");
std::string GFX_CONFIG = CONFIG_PATH + GetString("gfx_config");
utils::datafile::Read(DATA,GFX_CONFIG);
InitializeLevel("assets/Campaigns/1_1.tmx",CAMPAIGN_1_1); InitializeLevel("assets/Campaigns/1_1.tmx",CAMPAIGN_1_1);
player=std::make_unique<Warrior>(); player=std::make_unique<Warrior>();
@ -53,35 +61,41 @@ bool Crawler::OnUserCreate(){
camera.SetWorldBoundary({0,0},WORLD_SIZE*24); camera.SetWorldBoundary({0,0},WORLD_SIZE*24);
camera.EnableWorldBoundary(false); camera.EnableWorldBoundary(false);
#undef LoadImage //Dumb Windows.
auto LoadImage=[&](Renderable&r,std::string key){
r.Load(GetString("GFX_Prefix")+GetString(key));
};
#define LOADIMG(name) LoadImage(name,"Images."#name);
//Graphics //Graphics
GFX_Warrior_Sheet.Load("assets/nico-warrior.png"); LOADIMG(GFX_Warrior_Sheet)
GFX_Slime_Sheet.Load("assets/slime.png"); LOADIMG(GFX_Slime_Sheet)
GFX_Circle.Load("assets/circle.png"); LOADIMG(GFX_Circle)
GFX_Effect_GroundSlam_Back.Load("assets/ground-slam-attack-back.png"); LOADIMG(GFX_Effect_GroundSlam_Back)
GFX_Effect_GroundSlam_Front.Load("assets/ground-slam-attack-front.png"); LOADIMG(GFX_Effect_GroundSlam_Front)
GFX_Heart.Load("assets/heart.png"); LOADIMG(GFX_Heart)
GFX_BLOCK_BUBBLE.Load("assets/block.png"); LOADIMG(GFX_BLOCK_BUBBLE)
GFX_Ranger_Sheet.Load("assets/nico-ranger.png"); LOADIMG(GFX_Ranger_Sheet)
GFX_Wizard_Sheet.Load("assets/nico-wizard.png"); LOADIMG(GFX_Wizard_Sheet)
GFX_Battlecry_Effect.Load("assets/battlecry_effect.png"); LOADIMG(GFX_Battlecry_Effect)
GFX_Mana.Load("assets/mana.png"); LOADIMG(GFX_Mana)
GFX_SonicSlash.Load("assets/sonicslash.png"); LOADIMG(GFX_SonicSlash)
GFX_BulletCircle.Load("assets/circle.png"); LOADIMG(GFX_BulletCircle)
GFX_BulletCircleOutline.Load("assets/circle_outline.png"); LOADIMG(GFX_BulletCircleOutline)
GFX_EnergyBolt.Load("assets/energy_bolt.png"); LOADIMG(GFX_EnergyBolt)
GFX_EnergyParticle.Load("assets/energy_particle.png"); LOADIMG(GFX_EnergyParticle)
GFX_Splash_Effect.Load("assets/splash_effect.png"); LOADIMG(GFX_Splash_Effect)
GFX_LightningBolt.Load("assets/lightning_bolt.png"); LOADIMG(GFX_LightningBolt)
GFX_LightningBoltParticle1.Load("assets/lightning_bolt_part1.png"); LOADIMG(GFX_LightningBoltParticle1)
GFX_LightningBoltParticle2.Load("assets/lightning_bolt_part2.png"); LOADIMG(GFX_LightningBoltParticle2)
GFX_LightningBoltParticle3.Load("assets/lightning_bolt_part3.png"); LOADIMG(GFX_LightningBoltParticle3)
GFX_LightningBoltParticle4.Load("assets/lightning_bolt_part4.png"); LOADIMG(GFX_LightningBoltParticle4)
GFX_ChainLightning.Load("assets/chain_lightning.png"); LOADIMG(GFX_ChainLightning)
GFX_LightningSplash.Load("assets/lightning_splash_effect.png"); LOADIMG(GFX_LightningSplash)
GFX_Meteor.Load("assets/meteor.png"); LOADIMG(GFX_Meteor)
GFX_Arrow.Load("assets/arrow.png"); LOADIMG(GFX_Arrow)
GFX_Laser.Load("assets/laser.png"); LOADIMG(GFX_Laser)
GFX_ChargedArrow.Load("assets/charged_shot_arrow.png"); LOADIMG(GFX_ChargedArrow)
//Animations //Animations
sig::Animation::InitializeAnimations(); sig::Animation::InitializeAnimations();
@ -1082,6 +1096,22 @@ void Crawler::InitializeClassAbilities(){
Witch::InitializeClassAbilities(); Witch::InitializeClassAbilities();
} }
std::string Crawler::GetString(std::string key){
return DATA.GetProperty(key).GetString();
}
int Crawler::GetInt(std::string key){
return DATA.GetProperty(key).GetInt();
}
float Crawler::GetFloat(std::string key){
return DATA.GetProperty(key).GetReal();
}
double Crawler::GetDouble(std::string key){
return DATA.GetProperty(key).GetReal();
}
int main() int main()
{ {
Crawler demo; Crawler demo;

@ -10,6 +10,7 @@
#include "Effect.h" #include "Effect.h"
#include "Map.h" #include "Map.h"
#include "TMXParser.h" #include "TMXParser.h"
#include "olcUTIL_DataFile.h"
struct TilesheetData{ struct TilesheetData{
TilesetData&tileset; TilesetData&tileset;
@ -51,6 +52,7 @@ private:
int bridgeLayerIndex=-1; int bridgeLayerIndex=-1;
float bridgeFadeFactor=0.f; float bridgeFadeFactor=0.f;
void InitializeClassAbilities(); void InitializeClassAbilities();
utils::datafile DATA;
public: public:
Crawler(); Crawler();
bool OnUserCreate() override; bool OnUserCreate() override;
@ -98,4 +100,8 @@ public:
std::map<std::string,std::vector<geom2d::rect<int>>>&GetZoneData(MapName map); std::map<std::string,std::vector<geom2d::rect<int>>>&GetZoneData(MapName map);
void PopulateRenderLists(std::vector<Monster*>&monstersBeforeLower,std::vector<Monster*>&monstersBeforeUpper,std::vector<Monster*>&monstersAfterLower,std::vector<Monster*>&monstersAfterUpper,std::vector<Bullet*>&bulletsLower,std::vector<Bullet*>&bulletsUpper,std::vector<Effect*>&backgroundEffectsLower,std::vector<Effect*>&backgroundEffectsUpper,std::vector<Effect*>&foregroundEffectsLower,std::vector<Effect*>&foregroundEffectsUpper); void PopulateRenderLists(std::vector<Monster*>&monstersBeforeLower,std::vector<Monster*>&monstersBeforeUpper,std::vector<Monster*>&monstersAfterLower,std::vector<Monster*>&monstersAfterUpper,std::vector<Bullet*>&bulletsLower,std::vector<Bullet*>&bulletsUpper,std::vector<Effect*>&backgroundEffectsLower,std::vector<Effect*>&backgroundEffectsUpper,std::vector<Effect*>&foregroundEffectsLower,std::vector<Effect*>&foregroundEffectsUpper);
void ChangePlayerClass(Class cl); void ChangePlayerClass(Class cl);
std::string GetString(std::string key);
int GetInt(std::string key);
float GetFloat(std::string key);
double GetDouble(std::string key);
}; };

@ -189,6 +189,7 @@
<ClInclude Include="olcPixelGameEngine.h" /> <ClInclude Include="olcPixelGameEngine.h" />
<ClInclude Include="olcUTIL_Animate2D.h" /> <ClInclude Include="olcUTIL_Animate2D.h" />
<ClInclude Include="olcUTIL_Camera2D.h" /> <ClInclude Include="olcUTIL_Camera2D.h" />
<ClInclude Include="olcUTIL_DataFile.h" />
<ClInclude Include="olcUTIL_Geometry2D.h" /> <ClInclude Include="olcUTIL_Geometry2D.h" />
<ClInclude Include="Pathfinding.h" /> <ClInclude Include="Pathfinding.h" />
<ClInclude Include="Player.h" /> <ClInclude Include="Player.h" />
@ -234,8 +235,11 @@
<None Include="cpp.hint" /> <None Include="cpp.hint" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Text Include="assets\config\configuration.txt" />
<Text Include="assets\config\gfx\gfx.txt" />
<Text Include="NewClasses.txt" /> <Text Include="NewClasses.txt" />
<Text Include="InitialConcept.txt" /> <Text Include="InitialConcept.txt" />
<Text Include="Slime_King_Encounter.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="assets\heart.ico" /> <Image Include="assets\heart.ico" />

@ -28,6 +28,12 @@
<Filter Include="Source Files\Player Classes"> <Filter Include="Source Files\Player Classes">
<UniqueIdentifier>{715c64c5-956a-4ed3-9205-64110409fbea}</UniqueIdentifier> <UniqueIdentifier>{715c64c5-956a-4ed3-9205-64110409fbea}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Configurations">
<UniqueIdentifier>{44d00400-febd-4904-89a4-3725e6bc6cc7}</UniqueIdentifier>
</Filter>
<Filter Include="Configurations\GFX">
<UniqueIdentifier>{153059cd-0714-4c62-b01c-342f3bcd393d}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="olcPixelGameEngine.h"> <ClInclude Include="olcPixelGameEngine.h">
@ -111,6 +117,9 @@
<ClInclude Include="Emitter.h"> <ClInclude Include="Emitter.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="olcUTIL_DataFile.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Player.cpp"> <ClCompile Include="Player.cpp">
@ -208,6 +217,13 @@
<Text Include="NewClasses.txt"> <Text Include="NewClasses.txt">
<Filter>Documentation</Filter> <Filter>Documentation</Filter>
</Text> </Text>
<Text Include="Slime_King_Encounter.txt" />
<Text Include="assets\config\configuration.txt">
<Filter>Configurations</Filter>
</Text>
<Text Include="assets\config\gfx\gfx.txt">
<Filter>Configurations\GFX</Filter>
</Text>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="assets\heart.ico"> <Image Include="assets\heart.ico">

@ -76,6 +76,7 @@ void Ranger::InitializeClassAbilities(){
vf2d arrowVelocity=util::pointTo(p->GetPos(),game->GetWorldMousePos()); vf2d arrowVelocity=util::pointTo(p->GetPos(),game->GetWorldMousePos());
BULLET_LIST.push_back(std::make_unique<ChargedArrow>(p->GetPos(),arrowVelocity*600,20,p->GetAttack()*2.5,p->OnUpperLevel(),true)); BULLET_LIST.push_back(std::make_unique<ChargedArrow>(p->GetPos(),arrowVelocity*600,20,p->GetAttack()*2.5,p->OnUpperLevel(),true));
p->SetAnimationBasedOnTargetingDirection(atan2(arrowVelocity.y,arrowVelocity.x)); p->SetAnimationBasedOnTargetingDirection(atan2(arrowVelocity.y,arrowVelocity.x));
game->SetupWorldShake(0.3);
return true; return true;
}; };
#pragma endregion #pragma endregion

@ -0,0 +1,47 @@
Test Attributes, to be able to try encounter with the concept Warrior:
HP: 1200
Projectile Attack dmg: 10
Jump Attack dmg: 20
- Jumps need a telegraph on the ground.
Slime King
Base Size 800%
100%
Every 4 Seconds Shoots a ring of projectiles and another 2 rings with each 0.2 seconds delayed and slightly shifted.
after 4 repeats of this pattern, jump in the air and target location player currently at. Lands after 3 seconds.
After landing creats 1 ring of projectiles and takes 2 extra seconds to recover.
repeats behaviour until 75% is reached.
75%:
Size changes to 600%
Spawns 2 Slime adds (Monster C from Concept for now)
shoots every second with 3 projectiles (shoots out the projectiles in same moment with 45 degree shiftet flight location) in players direction.
after 5 shoots it charges for 5 seconds after that the king does 3 rapid jumps aiming for the player.
first should be dodgeable. quite easily.
second needs to be blocked with movement skill or iframe (unless high movespeed, 110% shall not be enough to dodge this, 110% can be achived without any special gear as bard permanently)
third jump is a little slower that should allow a player with 100% movespeed to barely run out of it.
50%:
Size changes to 400%
Spawns 2 Slime adds (Monster C from Concept for now)
1 fast Jump towards player. 3 projectiles attacks with 0.5 seconds between attack.
2 seconds recover.
repeat.
25%:
Size changes to 200%
Spawns 2 Slime adds (Monster B from Concept for now)
Movespeed 50%
King Shoots 5 projectiles, 0.1 sec delay Boss doesnt aim for the player directly instead shoots randomly in the general direction. runs away for 2.5 seconds. stands still for 1 second and shoot again.
Repeat 5 times then instead of running jump up to 1000 Range away from player and repeat.
0%:
instead of dying Slime king Looses 50% size on every hit taken (1 sec iframe after taking dmg) until reaching 0% size and dies.
Slime King only runs away with 50% move speed.

@ -2,7 +2,7 @@
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 2 #define VERSION_MINOR 2
#define VERSION_PATCH 0 #define VERSION_PATCH 0
#define VERSION_BUILD 854 #define VERSION_BUILD 861
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -0,0 +1,2 @@
config_path = assets/config/
gfx_config = gfx/gfx.txt

@ -0,0 +1,33 @@
GFX_Prefix = assets/
Images
{
GFX_Warrior_Sheet = nico-warrior.png
GFX_Slime_Sheet = slime.png
GFX_Circle = circle.png
GFX_Effect_GroundSlam_Back = ground-slam-attack-back.png
GFX_Effect_GroundSlam_Front = ground-slam-attack-front.png
GFX_Heart = heart.png
GFX_BLOCK_BUBBLE = block.png
GFX_Ranger_Sheet = nico-ranger.png
GFX_Wizard_Sheet = nico-wizard.png
GFX_Battlecry_Effect = battlecry_effect.png
GFX_Mana = mana.png
GFX_SonicSlash = sonicslash.png
GFX_BulletCircle = circle.png
GFX_BulletCircleOutline = circle_outline.png
GFX_EnergyBolt = energy_bolt.png
GFX_EnergyParticle = energy_particle.png
GFX_Splash_Effect = splash_effect.png
GFX_LightningBolt = lightning_bolt.png
GFX_LightningBoltParticle1 = lightning_bolt_part1.png
GFX_LightningBoltParticle2 = lightning_bolt_part2.png
GFX_LightningBoltParticle3 = lightning_bolt_part3.png
GFX_LightningBoltParticle4 = lightning_bolt_part4.png
GFX_ChainLightning = chain_lightning.png
GFX_LightningSplash = lightning_splash_effect.png
GFX_Meteor = meteor.png
GFX_Arrow = arrow.png
GFX_Laser = laser.png
GFX_ChargedArrow = charged_shot_arrow.png
}

@ -0,0 +1,435 @@
/*
OneLoneCoder - DataFile v1.00
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An "easy to use" serialisation/deserialisation class that yields
human readable hierachical files.
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 - 2022 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, <EFBFBD>OneLoneCoder 2019, 2020, 2021, 2022
*/
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>
#include <fstream>
#include <stack>
#include <sstream>
namespace olc::utils
{
class datafile
{
public:
inline datafile() = default;
public:
// Sets the String Value of a Property (for a given index)
inline void SetString(const std::string& sString, const size_t nItem = 0)
{
if (nItem >= m_vContent.size())
m_vContent.resize(nItem + 1);
m_vContent[nItem] = sString;
}
// Retrieves the String Value of a Property (for a given index) or ""
inline const std::string GetString(const size_t nItem = 0) const
{
if (nItem >= m_vContent.size())
return "";
else
return m_vContent[nItem];
}
// Retrieves the Real Value of a Property (for a given index) or 0.0
inline const double GetReal(const size_t nItem = 0) const
{
return std::atof(GetString(nItem).c_str());
}
// Sets the Real Value of a Property (for a given index)
inline void SetReal(const double d, const size_t nItem = 0)
{
SetString(std::to_string(d), nItem);
}
// Retrieves the Integer Value of a Property (for a given index) or 0
inline const int32_t GetInt(const size_t nItem = 0) const
{
return std::atoi(GetString(nItem).c_str());
}
// Sets the Integer Value of a Property (for a given index)
inline void SetInt(const int32_t n, const size_t nItem = 0)
{
SetString(std::to_string(n), nItem);
}
// Returns the number of Values a property consists of
inline size_t GetValueCount() const
{
return m_vContent.size();
}
// Checks if a property exists - useful to avoid creating properties
// via reading them, though non-essential
inline bool HasProperty(const std::string& sName) const
{
return m_mapObjects.count(sName) > 0;
}
// Access a datafile via a convenient name - "root.node.something.property"
inline datafile& GetProperty(const std::string& name)
{
size_t x = name.find_first_of('.');
if (x != std::string::npos)
{
std::string sProperty = name.substr(0, x);
if (HasProperty(sProperty))
return operator[](sProperty).GetProperty(name.substr(x + 1, name.size()));
else
return operator[](sProperty);
}
else
{
return operator[](name);
}
}
// Access a numbered element - "node[23]", or "root[56].node"
inline datafile& GetIndexedProperty(const std::string& name, const size_t nIndex)
{
return GetProperty(name + "[" + std::to_string(nIndex) + "]");
}
public:
// Writes a "datafile" node (and all of its child nodes and properties) recursively
// to a file.
inline static bool Write(const datafile& n, const std::string& sFileName, const std::string& sIndent = "\t", const char sListSep = ',')
{
// Cache indentation level
size_t nIndentCount = 0;
// Cache sperator string for convenience
std::string sSeperator = std::string(1, sListSep) + " ";
// Fully specified lambda, because this lambda is recursive!
std::function<void(const datafile&, std::ofstream&)> write = [&](const datafile& n, std::ofstream& file)
{
// Lambda creates string given indentation preferences
auto indent = [&](const std::string& sString, const size_t nCount)
{
std::string sOut;
for (size_t n = 0; n < nCount; n++) sOut += sString;
return sOut;
};
// Iterate through each property of this node
for (auto const& property : n.m_vecObjects)
{
// Does property contain any sub objects?
if (property.second.m_vecObjects.empty())
{
// No, so it's an assigned field and should just be written. If the property
// is flagged as comment, it has no assignment potential. First write the
// property name
file << indent(sIndent, nIndentCount) << property.first << (property.second.m_bIsComment ? "" : " = ");
// Second, write the property value (or values, seperated by provided
// separation charater
size_t nItems = property.second.GetValueCount();
for (size_t i = 0; i < property.second.GetValueCount(); i++)
{
// If the Value being written, in string form, contains the separation
// character, then the value must be written inside quotation marks. Note,
// that if the Value is the last of a list of Values for a property, it is
// not suffixed with the separator
size_t x = property.second.GetString(i).find_first_of(sListSep);
if (x != std::string::npos)
{
// Value contains separator, so wrap in quotes
file << "\"" << property.second.GetString(i) << "\"" << ((nItems > 1) ? sSeperator : "");
}
else
{
// Value does not contain separator, so just write out
file << property.second.GetString(i) << ((nItems > 1) ? sSeperator : "");
}
nItems--;
}
// Property written, move to next line
file << "\n";
}
else
{
// Yes, property has properties of its own, so it's a node
// Force a new line and write out the node's name
file << "\n" << indent(sIndent, nIndentCount) << property.first << "\n";
// Open braces, and update indentation
file << indent(sIndent, nIndentCount) << "{\n";
nIndentCount++;
// Recursively write that node
write(property.second, file);
// Node written, so close braces
file << indent(sIndent, nIndentCount) << "}\n\n";
}
}
// We've finished writing out a node, regardless of state, our indentation
// must decrease, unless we're top level
if (nIndentCount > 0) nIndentCount--;
};
// Start Here! Open the file for writing
std::ofstream file(sFileName);
if (file.is_open())
{
// Write the file starting form the supplied node
write(n, file);
return true;
}
return false;
}
inline static bool Read(datafile& n, const std::string& sFileName, const char sListSep = ',')
{
// Open the file!
std::ifstream file(sFileName);
if (file.is_open())
{
// These variables are outside of the read loop, as we will
// need to refer to previous iteration values in certain conditions
std::string sPropName = "";
std::string sPropValue = "";
// The file is fundamentally structured as a stack, so we will read it
// in a such, but note the data structure in memory is not explicitly
// stored in a stack, but one is constructed implicitly via the nodes
// owning other nodes (aka a tree)
// I dont want to accidentally create copies all over the place, nor do
// I want to use pointer syntax, so being a bit different and stupidly
// using std::reference_wrapper, so I can store references to datafile
// nodes in a std::container.
std::stack<std::reference_wrapper<datafile>> stkPath;
stkPath.push(n);
// Read file line by line and process
while (!file.eof())
{
// Read line
std::string line;
std::getline(file, line);
// This little lambda removes whitespace from
// beginning and end of supplied string
auto trim = [](std::string& s)
{
s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
};
trim(line);
// If line has content
if (!line.empty())
{
// Test if its a comment...
if (line[0] == '#')
{
// ...it is a comment, so ignore
datafile comment;
comment.m_bIsComment = true;
stkPath.top().get().m_vecObjects.push_back({ line, comment });
}
else
{
// ...it is content, so parse. Firstly, find if the line
// contains an assignment. If it does then it's a property...
size_t x = line.find_first_of('=');
if (x != std::string::npos)
{
// ...so split up the property into a name, and its values!
// Extract the property name, which is all characters up to
// first assignment, trim any whitespace from ends
sPropName = line.substr(0, x);
trim(sPropName);
// Extract the property value, which is all characters after
// the first assignment operator, trim any whitespace from ends
sPropValue = line.substr(x + 1, line.size());
trim(sPropValue);
// The value may be in list form: a, b, c, d, e, f etc and some of those
// elements may exist in quotes a, b, c, "d, e", f. So we need to iterate
// character by character and break up the value
bool bInQuotes = false;
std::string sToken;
size_t nTokenCount = 0;
for (const auto c : sPropValue)
{
// Is character a quote...
if (c == '\"')
{
// ...yes, so toggle quote state
bInQuotes = !bInQuotes;
}
else
{
// ...no, so proceed creating token. If we are in quote state
// then just append characters until we exit quote state.
if (bInQuotes)
{
sToken.append(1, c);
}
else
{
// Is the character our seperator? If it is
if (c == sListSep)
{
// Clean up the token
trim(sToken);
// Add it to the vector of values for this property
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
// Reset our token state
sToken.clear();
nTokenCount++;
}
else
{
// It isnt, so just append to token
sToken.append(1, c);
}
}
}
}
// Any residual characters at this point just make up the final token,
// so clean it up and add it to the vector of values
if (!sToken.empty())
{
trim(sToken);
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
}
}
else
{
// ...but if it doesnt, then it's something structural
if (line[0] == '{')
{
// Open brace, so push this node to stack, subsequent properties
// will belong to the new node
stkPath.push(stkPath.top().get()[sPropName]);
}
else
{
if (line[0] == '}')
{
// Close brace, so this node has been defined, pop it from the
// stack
stkPath.pop();
}
else
{
// Line is a property with no assignment. Who knows whether this is useful,
// but we can simply add it as a valueless property...
sPropName = line;
// ...actually it is useful, as valuless properties are typically
// going to be the names of new datafile nodes on the next iteration
}
}
}
}
}
}
// Close and exit!
file.close();
return true;
}
// File not found, so fail
return false;
}
public:
inline datafile& operator[](const std::string& name)
{
// Check if this "node"'s map already contains an object with this name...
if (m_mapObjects.count(name) == 0)
{
// ...it did not! So create this object in the map. First get a vector id
// and link it with the name in the unordered_map
m_mapObjects[name] = m_vecObjects.size();
// then creating the new, blank object in the vector of objects
m_vecObjects.push_back({ name, datafile() });
}
// ...it exists! so return the object, by getting its index from the map, and using that
// index to look up a vector element.
return m_vecObjects[m_mapObjects[name]].second;
}
private:
// The "list of strings" that make up a property value
std::vector<std::string> m_vContent;
// Linkage to create "ordered" unordered_map. We have a vector of
// "properties", and the index to a specific element is mapped.
std::vector<std::pair<std::string, datafile>> m_vecObjects;
std::unordered_map<std::string, size_t> m_mapObjects;
protected:
// Used to identify if a property is a comment or not, not user facing
bool m_bIsComment = false;
};
}

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="192" height="203" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="156"> <map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="192" height="203" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="158">
<tileset firstgid="1" source="../maps/grass_tiles_24x24.tsx"/> <tileset firstgid="1" source="../maps/grass_tiles_24x24.tsx"/>
<tileset firstgid="784" source="../maps/grass_tiles_modded.tsx"/> <tileset firstgid="784" source="../maps/grass_tiles_modded.tsx"/>
<layer id="5" name="Collision Layer" width="192" height="203"> <layer id="5" name="Collision Layer" width="192" height="203">
@ -7414,6 +7414,9 @@
</map> </map>
‰PNG ‰PNG
 
IHDRàw=ø„iCCPICC profile(‘}‘=HÃ@Å_S¥* Š8d¨NÄ/¥ŠE°PÚ
­:˜\úMGÁµààÇbÕÁÅYWWAüquqRt‘ÿ—ZÄxpÜ<EFBFBD>¡^fªÙ1¨še$cQ1“]¯`ÝcZb¦O-¦á9¾îáãë]„gyŸûsô*9“>‘xŽé†E¼A<³iéœ÷‰C¬()ÄçÄc]<EFBFBD>ø‘ë²Ëoœ  <3d¤“óÄ!b±ÐÆr³¢¡OU£|!ã²Ây‹³Z®²æ=ù ƒ9m%ÅušÃˆa q$ BF%”a!B«FŠ‰$íG=üCŽ?A.™\%0r, ’ãÿƒßÝšùÉ 7):_lûcì<EFBFBD>šmÛvãð?WZË_©³Ÿ¤×ZZøèÛ.®[š¼ƒOºdHŽä§)äóÀû}Sè¿zÖÜÞšû8}ÒÔÕò ppŒ({ÝãÝ]í½ý{¦ÙßÑsrÍÕüÇbKGDN¿<ÿí˜ý pHYs.#.#x¥?vtIMEç *%£<qtEXtCommentCreated with GIMPW<EFBFBD>vIDATHÇc`£ >?ý?ÃúãûÿÃ0²8!ÀDk2ë‹€ˆ8Ê 'Îd¤zP­?¾ÿ?±AC· µ€zÁ°|@3K†nùí·ù¯/Îñßo¿ÍÍ "ÚJ¨ÑÎ5ï¯'ýgCºº4£1QvVIEND®B`‚‰PNG

IHDR¤TÍ©C˜…iCCPICC profile(‘}‘=HÃ@Å_Sµ"Q;HqX<EFBFBD>,ˆŠ8J‹`¡´Zu0¹ô š4$).Ž‚kÁÁ<EFBFBD>Ūƒ‹³®®‚ øâêâ¤è"%þ/)´ˆõà¸ïî=îÞB­ÄT³cP5ËHD#b:³*ú^Ñ…ô#ˆ‰™z,¹˜BÛñu_ïÂ<«ý¹?G¯’5à‰ç˜nXÄÄ3›–ÎyŸ8À IHDR¤TÍ©C˜…iCCPICC profile(‘}‘=HÃ@Å_Sµ"Q;HqX<EFBFBD>,ˆŠ8J‹`¡´Zu0¹ô š4$).Ž‚kÁÁ<EFBFBD>Ūƒ‹³®®‚ øâêâ¤è"%þ/)´ˆõà¸ïî=îÞB­ÄT³cP5ËHD#b:³*ú^Ñ…ô#ˆ‰™z,¹˜BÛñu_ïÂ<«ý¹?G¯’5à‰ç˜nXÄÄ3›–ÎyŸ8À
’B|N<nй.»üÆ9ï°À3F*1O ó-,·0+*ñ4qHQ5ÊÒ.+œ·8«¥ ’B|N<nй.»üÆ9ï°À3F*1O ó-,·0+*ñ4qHQ5ÊÒ.+œ·8«¥
kÜ“¿ÐŸÕV’\§9Œ(–C"dTPD ´j¤˜HÐ~¤<EFBFBD>?èøãä’ÉUÊP!9~ð?øÝ­™›št“ü óŶ?Fß.P¯Úö÷±m×Oï3p¥5ýå0ûIzµ©…Ž€¾mà⺩É{Àå0ô¤K†äH^šB.¼ŸÑ7e€Á[ gÍí­±<EFBFBD>Ó E]-߇ÀXž²×Û¼»»µ·Ï4úû<EFBFBD>§r³ê£<EFBFBD>1bKGDQ îbúÏ pHYs.#.#x¥?vtIME絈WrtEXtCommentCreated with GIMPW<EFBFBD> ÂIDATxÚí<EFBFBD>=rT;F=¯^•C‡+ uæex-¬„m8õ2È!#s’A4/šªñ¼{u%õ<EFBFBD>º¥ó¥W­øδZWº»C!„B!„B!„Bè½N¤¡÷z{~<{<EFBFBD>õñõÛôÿ¿|úà–ÏÏ?~MŸÏ§û·|~ýûÛ5Ÿ ›A ˆ‹5l<@)kØx€bD, -žLUI”y”Ì>SUe%³÷®J"Í ¡iá3ór˜åœ÷L{æå0Ë9ï™v&ðxÍ ¡)ÌØ>–½$¯xkÆÙ2c øXö’¼â­gËŒ-àcÙKòŠ÷v€„–ÐÈþ<EFBFBD>×Ø{y³ÐÈþ<EFBFBD>×Ø{y³ÐÈ^’×Ø[ã$”F½æœµ—d÷Ûóãùåû›Èœ³ö’,âþòéÃùåç‘9gí%YÄýtÿpHhêJ*:x"Ïq…^’çWè%Iç<EFBFBD>ÐTæ¼Â{=Vyðê%e„TO¼zI!µ—€„Тy@º€@r@‘,”Ê\<EFBFBD>,‡—ÒÔ¥‡€t…Pš_ûH÷×>ÂL£šcâEQ‘èš8‰®‰S‘$”>Ë]W+ž€`¹³lÅ kÜ“¿ÐŸÕV’\§9Œ(–C"dTPD ´j¤˜HÐ~¤<EFBFBD>?èøãä’ÉUÊP!9~ð?øÝ­™›št“ü óŶ?Fß.P¯Úö÷±m×Oï3p¥5ýå0ûIzµ©…Ž€¾mà⺩É{Àå0ô¤K†äH^šB.¼ŸÑ7e€Á[ gÍí­±<EFBFBD>Ó E]-߇ÀXž²×Û¼»»µ·Ï4úû<EFBFBD>§r³ê£<EFBFBD>1bKGDQ îbúÏ pHYs.#.#x¥?vtIME絈WrtEXtCommentCreated with GIMPW<EFBFBD> ÂIDATxÚí<EFBFBD>=rT;F=¯^•C‡+ uæex-¬„m8õ2È!#s’A4/šªñ¼{u%õ<EFBFBD>º¥ó¥W­øδZWº»C!„B!„B!„Bè½N¤¡÷z{~<{<EFBFBD>õñõÛôÿ¿|úà–ÏÏ?~MŸÏ§û·|~ýûÛ5Ÿ ›A ˆ‹5l<@)kØx€bD, -žLUI”y”Ì>SUe%³÷®J"Í ¡iá3ór˜åœ÷L{æå0Ë9ï™v&ðxÍ ¡)ÌØ>–½$¯xkÆÙ2c øXö’¼â­gËŒ-àcÙKòŠ÷v€„–ÐÈþ<EFBFBD>×Ø{y³ÐÈþ<EFBFBD>×Ø{y³ÐÈ^’×Ø[ã$”F½æœµ—d÷Ûóãùåû›Èœ³ö’,âþòéÃùåç‘9gí%YÄýtÿpHhêJ*:x"Ïq…^’çWè%Iç<EFBFBD>ÐTæ¼Â{=Vyðê%e„TO¼zI!µ—€„Тy@º€@r@‘,”Ê\<EFBFBD>,‡—ÒÔ¥‡€t…Pš_ûH÷×>ÂL£šcâEQ‘èš8‰®‰S‘$”>Ë]W+ž€`¹³lÅ
@ -7854,6 +7857,76 @@ i*hRP
Å€ÞÛþ»@«aÛßǶÝ:üÏÀ•ÖñךÀÜ'é<EFBFBD>Ž9<EFBFBD>‹ëŽ&ï—;ÀГ.’#ùi Å€ÞÛþ»@«aÛßǶÝ:üÏÀ•ÖñךÀÜ'é<EFBFBD>Ž9<EFBFBD>‹ëŽ&ï—;ÀГ.’#ùi
Å"ð~Fß”o<EFBFBD>¾5··ö>N€,uµ|ã%Ê^÷xw°»·Ï´ûû1<EFBFBD>r<EFBFBD>©N%bKGDÿïè?H<EFBFBD>$ pHYs  šœtIMEç b#8IDAT×-ÒÿÚéÁaéóëÿìúäÚúø ûïôýôÝìòëÿðöÒN4<EFBFBD>%8md’IEND®B`‚‰PNG Å"ð~Fß”o<EFBFBD>¾5··ö>N€,uµ|ã%Ê^÷xw°»·Ï´ûû1<EFBFBD>r<EFBFBD>©N%bKGDÿïè?H<EFBFBD>$ pHYs  šœtIMEç b#8IDAT×-ÒÿÚéÁaéóëÿìúäÚúø ûïôýôÝìòëÿðöÒN4<EFBFBD>%8md’IEND®B`‚‰PNG
 
IHDR00Wù‡}zTXtRaw profile type exifxÚí™[v;ŽDÿ9Š_ Ááð¹VÏ †ßÌ”,»ì¾¾åª¿’l<EFBFBD>£Ì<|<EFBFBD>ˆåö?þ÷¸ÿá+Q—¥ji¥x¾rË-vÞ¨¾Æý|¾?ïWzoñûw×Ýç<EFBFBD>È¥ôíÉßë›ë¼ïïí<EFBFBD>$|<ÿ1ÐÇ›Ðy'ßnôþ^ß_ï€Qè]A
ÏÌ~½xJñ]Q~~ŸïŠJÓúÝÖÖ|gÎï%ýö?§‹”P‰fÉÑ×Zï5ú\‰ç²…žÛHž€~^øøýãÑÈšâN!y~¦ŸU&ûŸRçµò3&q<î/9éýYnà=©d ¬´½uÿ̯±ù£_|ýζ<“œmÉÚçë¸ù|~qý…ÁgÖ´¼7Ò÷iõåóõ§×ƒ| ôq#}οάósæï®·¾ …ûšîs–ž»ivÑs!åÝÔÇVî;žÅû©ÂwõÅ<EFBFBD><EFBFBD>}7¾Õw?ÁÔò“J¼o!’ûrX¡‡ö}<EFBFBD>a²Äw¬¼Æ8]L÷¢’¤g20dû'ÖÔÒ1Í‹¡œâçZÂ<EFBFBD>¶ÝéfP¿œ_<EFBFBD>Gc`°ÀGþåo÷»žcµ‚×ÏX±®hHg><EFBFBD>~{á12ÎþøþñËòšÈ Ü0+ì~¸gˆ!á¸ÒMtâAáõ©úP×;!bja1!‘_B’PXQ<EFBFBD>±†@ •u–SŽƒ ‘¸XdÌ)’Cu07Ÿ©á>%>—aÕ”]’T(Z%C<EFBFBD>då,à§fC]’d)RE¥I/©Xå•R‹Ñs¯©æ*µÔZÕÕV»&Í*Z´ªjÓÞbKз4ê´ik­w&íŒÜùtç<EFBFBD>ÞGiä!£Œ:t47ú>3O™eÖ©³Í¾âJ‹_eÕ¥«­¾ÃJ;oÙe×­»í~€ÚI'9åTwô´Ó?³ö¦õŸ¾ÿFÖ›µx3eÖϬqµÖ<EFBFBD>!‚щXÎÈXÌ<EFBFBD>„W²FƶåÌkÈ9Zæ,gèU!‘EŠ%gËÌ;D9á3woæQü·äÍU½y‹š9g©ûÍÌýsÞ~–µe*1oÆž2´ úDõqk<EFBFBD>ÚM^ùêþê<EFBFBD>ß}ýõ@£·ãV¥Ï#m¢ ­[! 0Ê0¡‡^‡¦=\öm<EFBFBD>n€$[å‘h$Àö.­Mö÷úŽe´rBÜ-ù¢«ë¬å”}Œ¯²Ëa6¨Úx5&œÇnÙSÏ¥@<EFBFBD>„ãÇjUG•ÓöŒgËPÝQ÷"ƒFþâ×pð¬ßr?)ô“»Í­Ûã浤²¦=Yâ,[Cj8´1ìiLC>¸8{w®ÕSjd;ÏÝu|o{ÿ<À&y„UvSÂy*³¸ÿoþÑöªc‡VttY‡Âò²äÄ\ImTÒÜY÷T—Þk³Œ<EFBFBD>-WÒf‹~IÛ#uüB¥ªâÜ}<EFBFBD>há3·R ܃.<ÅGªœ¬|´Õ™¤ÔžJ[aMìƒ~cÞFq<EFBFBD>!-õÄpKWŸ÷fRþà~1ƒd¿oþþëî6ýŽ2Ò
àh·¸öŽ±Y=IÜ~–Žu`{çÚaæXú®=X*Ÿ$ø1Ï!†DT]ïs<EFBFBD>3<»+x¹ÝC¯#òÑYƆµ†F‘Q=<EFBFBD>RQï;uÞfüK™¥ƒ7T3Vun˜!Ãø˜¥J8¼ÌQð' AEÈÁ˜ìaJ€…nøÜ*Ÿ›Ët™ü<EFBFBD>œ0·†F<EFBFBD>l͹ê:À"Io-Ï€>D߀Èòç8 ºVêä&|t(¢³#ÂDyÔšFybŠe¡POP‚£Ò£Å¦oa™‰•ˆçY9·™ÛèèDøŒpO»KØC|W…ã¡ìÓìܪmÀ^‡NYs–‡¸%ŒZ©J¬Ø`œ-ˆ`Êö×Ègô- wêçY©’.€³Ÿ|v–N6ï’Ç­J‡„Ö%CJƒ<EFBFBD>¨㣬°¯Ùx@<EFBFBD>x̧Fƒ8Ú?R…¶ Y–Ï·2ŒiÈç­ Åqƒ« ®ÛÇà¥Ë€¥‰¾dèÊ‹uÿ‡¯îóBÍøÙ ø5/}È~^;ëÔRÛ>ðÑÙ4Ä^àçVäjÍΠ]ôuÇKxÑ٠Я³^º<EFBFBD>a<EFBFBD>ûmyŽeœý$Âl‰Ñ²Ã<EFBFBD> dߺY¾õF‚€P»{Ž(¢—»Zÿ[¯îwüòÀÊL€«À3<EFBFBD>²Àöé]=Aþp]е@`„ý>ªRw<EFBFBD>ªô[½|¨ÊÌÕÔ ê€³½™r¿4K<EFBFBD>N©ýˆ_‰îš@iŠJ?ªî`‹ xø‚&Êî!ó–z×XþÈ ²æ™È!ï%Ãóu^…9A®6èæa¥0¸1ܶÏb+?Ö£u{X'<EFBFBD>[2¶/ö×î@ì<EFBFBD>%±?±2/š}K;˜JWhxPŒòˆÎJw.üó¦iTój¸ö¨¼ÌIQÀ@,ã­†î<EFBFBD>ÏîRú#t?lÞ¶n:×༪=¥<EFBFBD>×Ê;ÁJìcq³`¬hUÑþ>þ+N*yž“Õf7…Áz`_ô/ Åýa¥•Î¼Ý¯…õÓbñ‰+býbf1³gþ)Ü놂Ó$V'k8ŸrÄ’¶Ži2¤Çi!'“5o©@V‰Ä:Áå%ëIšàùPæÚ¨ x+)ž,Š¡¬<EFBFBD>¹3ljÍ>ôb­¿¼<EFBFBD>!Ž,sá°”>•/ë~WÍêâã&²AÀž(XŠÙÐGE3>˜žÖç†ÞƒBe/ËpÄL<õ(M¤—%ÖfQïM¬´<EFBFBD>¾„Ž b¿½M*WM<EFBFBD>0Æøš£¦_À”­x¶9Òæ²A,ÒŒ&è%îkÓ€+4"›Í“ Ò’á<EFBFBD>ÆÃð×34!&>Ö„˜¹°ZÀwÕÆË:ŠÊ‚–¹Úk·>½–<EFBFBD>0|u[à¿÷v9ètÙßÜÛ çpOËàÍY[tb0#»"ñ“NŒoÄ0˜AQVœ@vCö¤Õ´Z®éØB’ÃtôŒ•öÆ[¼ØÐ(ƒÑ! ôB Ç È‘ZrÌÈXðEÀƒ)8<EFBFBD> hîcåquJÎ6-U†Œ­˜¤©þná>Þ°W6<EFBFBD>-·>),C¡â2Äß±µdhH=Jw•
<EFBFBD>’®5uã»ÊX<EFBFBD>+ªýh1'<EFBFBD>? pƒ…>Ä·6 mi AÄ4B<EFBFBD>ìçÊziÍ‚ßE­<EFBFBD>eµŽãV×åãTk{T#úÇV¨å´6c½H<EFBFBD>ðÁliÁèϤq;xÿË]#óç¾iÃ#ž—"aŒ9mk!èE 'c±<EFBFBD>û´3>ŒYš>f5À¾MóšÄûY§ÉTg|dNl*„JûƒÍ±vwÑ5†CëK`Ùñ‡åÃ4ÅL‰&¢Mäöµ42˜´`Éf‘@m'öQBpi®°BlËrmGé”|Òˆw¥b9×Î<EFBFBD>ø<EFBFBD>Vb†Ž<EFBFBD>ôWVÈ4°Rú“¤n<EFBFBD>ùø´ŒØZ9n‹Y¡Ã@‚ixá•åSî­,ûÆê —mN”Î/ïÜ;Ì9“[ž6Fp9™½ä ‘ä¾B‚©êǧŠ^_}+CÍÿ6·&m?L¯)ùԼè2;<EFBFBD>8-ã:«†@aÐÔ{Š¬\K'ÓÛx³&:]ÅàûÙ'Ö6ÓxVšKG0qüÚ&5…_Ñ ƒPÆZ2–µ1Æ£sð1vhLÌ<EFBFBD>Òý?x£M‰NªÖŸ·Àq„CóØÏn,ªC0£UfÒæÃmÀ°ñÌOŸázëW€ˆµHP;&Þô„9ÆaàDãŽÑ@WÜtɉRÓ]|¦¯Ã<EFBFBD>P5,No̼¼qu7KzN€à \šØv³ÁfSÔŽQkôœ
ÍÊ–eGwÙ±f ñÏ=²ûáŠõ(šñzhÀ<EFBFBD>%ÙíJ#Ô¿¨ðz¬—+†qäÝsÍ€ùæbQ¥Ë2A¾LqØÝ_¸=­™Tyüw<<EFBFBD>ÿ¦½¤<EFBFBD>GRêÐ(zGj Öé؈òˆ¢ û+Š£Æ[ZK¬ö‘U²ƲcŸIç±#„<EFBFBD>õž™7&<EFBFBD>ËŽ’pu;Y}®mŽT3´ì¢»$ë:„ñ¯wÚß^Ý/Zñ q<EFBFBD>íËÊæF{ΣŽ"f=j@u
X‡Û|<EFBFBD>˜ÜÃ<EFBFBD>éê$JZqªf½ Ö»µ(Ê7ÑÉ·i_ x
  <€ÚS¤$+r n&ª˜|W1Ê:Ñî½/†žÛp°ÌÿžÀ>Í ù5Wä0Så—fjÛùÒ†yX4Ídñ™Æw,ü=÷0@ö••†¥au˜ƒÒ(‡Œž­Ï£ûGÖ~N .}0hÀ6JÒe; 8àõj<EFBFBD>…‰‘M÷öY¯<EFBFBD> Iògû™î˜ h2ÛçCœkŽ€o·µ5,<EFBFBD>G'XrÏ<EFBFBD>ÇÆ/yê³ýùn_ïöÑ(÷ì_†­Šý“
/S`0ŒQÆb qÃÝlSÜC:/Õ çÛ· ±ÈÜ)z˜43±bœË¤mæìsÜ—ë‡9¶¤§åUÁ
<EFBFBD>ÝÅŽ.ÐÞ™ßKv摃{¢WÂý+B‚Åì¼"«jv<EFBFBD>ÎÎŽ!’K#Ó˜}”HóÿŽ
±“ÑŸÜ@À¢!¨V˜€ÀË0[¡¢³Šp%­[±c¶AGŠÿu>?l¬Q5ƒs.•<EFBFBD>>°±X2~½tÁÙÒ5€o²n.qKåöÇ”Ûr0î‰y)‚Ž„LÐ<EFBFBD>õ¥ž£KKî<{yáI}_7‚÷zûD°‘ž˾‘qJo³2ú¸$tkUC<EFBFBD>klá=¥‹ç°û ‚«õÃùÉ;º$¿»ù‘<EFBFBD>ôÐ@ør¦‰ÓlÄ
R´:§C›?©J;Jk&Fe^ û ¬QpþÄ<EFBFBD>þ<ð;@¸9ÉnQ SŒ&íO¸zÏ>c&)ùTó~oRð©Ç2òž„èÛâP_¸<EFBFBD>UCÃgãY,3Ö£‰ÎWò¯:Õˆ=þÝã÷7ÏE¼TT*1_C7<EFBFBD>*dd,§Ô$\Javºiž¦<EFBFBD>£Õ¬*=vÄ~â°Ö³X;:<EFBFBD>k¦¢q cÂâ¥:mU<$zÍÎÐMw¬ÖO;ù²¿Û<EFBFBD>6>IVØ0ÃZ`wØ'F)it¶Ü<EFBFBD>ýá§Y7©_ŽT [q5ºPìüJt–ØùÛQCIëZÂÆ‘îâ°u9ÊkŸ²<EFBFBD>~+–ßúƒ -ƒ<EFBFBD>Äpí˜Tú{ µË(î9å¡•†ö¥X;;;k<¦¡Š‘žRF‘厼¬¹ôO .Wuähúç0ñv—Á÷]í"þif¬•ÖVÆC¦'®„5ˇ‡¼aź¿Ò†“ë!éí¤Ùë~àŒTˆu2 §Ê„õ=q,%A™û›(úåëúï@ÿ¹<EFBFBD> Ô…»ÿˆgü >vU…iCCPICC profilexœ}‘=HÃ@Å_S¥"QqÈPÄ‚¨ˆ£T±J[¡U“K?„& IŠ‹£àZpðc±êà⬫ƒ« ~€¸º8)ºH‰ÿK
-b<8îÇ»{<EFBFBD>»w€P/3ÕìTÍ2Rñ˜˜Í­ˆ<EFBFBD>W0€>Œ"$1SO¤_÷ðñõ.ʳ¼Ïý9z”¼ÉŸH<ËtÃ"^'žÞ´tÎûÄaV’âsâ1ƒ.HüÈuÙå7ÎE‡ž62©9â0±Xlc¹<EFBFBD>YÉP‰§ˆ#ŠªQ¾<EFBFBD>uYá¼ÅY-WYóžü…Á¼¶œæ:Í!ıˆ!£Š ”a!J«FŠ‰íÇ<üƒŽ?I.™\`ä˜G*$Çþ¿»5 nR0t¾ØöÇ0Ø5Ûþ>¶íÆ à®´–¿Rf>I¯µ´Èл \\·4y¸ÜúŸtÉ<EFBFBD>ÉOS(€÷3ú¦ººWÝÞšû8}2ÔÕÒ ppŒ){ÍãÝ]í½ý{¦Ùß±frÀŒªÐ› viTXtXML:com.adobe.xmp<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:GIMP="http://www.gimp.org/xmp/"
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmpMM:DocumentID="gimp:docid:gimp:188daf72-590b-4b82-b86f-1c005627c742"
xmpMM:InstanceID="xmp.iid:a767a94b-a093-410f-bc1f-e8d277b72e5c"
xmpMM:OriginalDocumentID="xmp.did:fe94365e-b65b-473f-8735-ccd81d38e27e"
dc:Format="image/png"
GIMP:API="2.0"
GIMP:Platform="Windows"
GIMP:TimeStamp="1690325328110044"
GIMP:Version="2.10.32"
tiff:Orientation="1"
xmp:CreatorTool="GIMP 2.10"
xmp:MetadataDate="2023:07:25T17:48:46-05:00"
xmp:ModifyDate="2023:07:25T17:48:46-05:00">
<xmpMM:History>
<rdf:Seq>
<rdf:li
stEvt:action="saved"
stEvt:changed="/"
stEvt:instanceID="xmp.iid:38da74d2-a352-41db-a746-2ecd8ca9c2cd"
stEvt:softwareAgent="Gimp 2.10 (Windows)"
stEvt:when="2023-07-25T17:48:48"/>
</rdf:Seq>
</xmpMM:History>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>CxIbKGDN¿<ÿí˜ý pHYs.#.#x¥?vtIMEç00¹<EFBFBD>œtEXtCommentCreated with GIMPW<EFBFBD>ÓIDAThÞíÕ±
‚PÆñÏè¢GhjlÌ­Z¤6ÇÞĨÁ¡È7il+\ªÍWhjlŒÞÀ†TD(3þ¿E½xàÏ= Ê(ÜïXat}ú†$íƒó:/Æ6Ç«t|û<EFBFBD>VÓ+Ð.ÿr±jl>­ZmG(oƒÛ£“Hd1õ–emú¯Ž <EFBFBD>@<EFBFBD>¡,QóFM›^·MÿwYžô@‹0ŽP•ðÌS(I»Û,±>ï$IN0aTÚÄCkð¾êžýB f4ñÅ}„ñç‘Û5%zþ«5·´<EFBFBD>BHIEND®B`‚‰PNG

IHDRV(µ¿„iCCPICC profile(‘}‘=HÃ@Å_Sµ";qÈP<EFBFBD>,ˆŠ8J‹`¡´Zu0¹ô š4$).Ž‚kÁÁ<EFBFBD>Ūƒ‹³®®‚ øâêâ¤è"%þ/)´ˆñà¸ïî=îÞB£ÂT³kP5ËHÅcb6·*^у0DDb¦žH/fà9¾îáãë]”gyŸûsô+y“>‘xŽé†E¼A<³iéœ÷‰C¬$)ÄçÄã]<EFBFBD>ø‘ë²Ëoœ‹ <3ddRóÄ!b±ØÁr³’¡OGU£|!ë²Ây‹³Z©±Ö=ù ƒym%Íuš#ˆc $!BF eT`!J«FŠ‰íÇ<üÃŽ?I.™\e0r,  IHDRV(µ¿„iCCPICC profile(‘}‘=HÃ@Å_Sµ";qÈP<EFBFBD>,ˆŠ8J‹`¡´Zu0¹ô š4$).Ž‚kÁÁ<EFBFBD>Ūƒ‹³®®‚ øâêâ¤è"%þ/)´ˆñà¸ïî=îÞB£ÂT³kP5ËHÅcb6·*^у0DDb¦žH/fà9¾îáãë]”gyŸûsô+y“>‘xŽé†E¼A<³iéœ÷‰C¬$)ÄçÄã]<EFBFBD>ø‘ë²Ëoœ‹ <3ddRóÄ!b±ØÁr³’¡OGU£|!ë²Ây‹³Z©±Ö=ù ƒym%Íuš#ˆc $!BF eT`!J«FŠ‰íÇ<üÃŽ?I.™\e0r, 
’ãÿƒßÝš…©I7)º_lûcìͺmÛvóð?WZÛ_m³Ÿ¤×ÛZäØ.®Ûš¼á']2$GòÓ ’ãÿƒßÝš…©I7)º_lûcìͺmÛvóð?WZÛ_m³Ÿ¤×ÛZäØ.®Ûš¼á']2$GòÓ
àýŒ¾) Ý}kno­}œ>êjù88ÆŠ”½îñîÞÎÞþ=Óêïrµr§Ø¶ bKGDQ îbúÏ pHYs  šœtIMEç22*<EFBFBD>§IDAT×c````øÿÿÿF`ùG öy›YIEND®B`‚‰PNG àýŒ¾) Ý}kno­}œ>êjù88ÆŠ”½îñîÞÎÞþ=Óêïrµr§Ø¶ bKGDQ îbúÏ pHYs  šœtIMEç22*<EFBFBD>§IDAT×c````øÿÿÿF`ùG öy›YIEND®B`‚‰PNG
@ -8049,6 +8122,9 @@ k
 
IHDR@Y³X%ÀPLTE7_<EFBFBD>'Ç07çO0÷h÷¿ˆ÷׸ï×b÷·RßA·_!GO'/'//O .wV—$N¿<V߉·÷¸^ïÈ¿¯Ÿ€w_W?G'//O( g8ŽAA¾9pÎQ îh×ïÇ÷ïè¨öÀ€î˜YÞˆ9¾h)¦Po(G'/?7GOO_og‡—<EFBFBD>¯Ç¿ÿïè÷°ßÐI¶§0x~_O?7GGw_!¯wA÷ŸpѦ pHYsÄÄ•+IIDAT™c``dbfaecçàäâæáåã<EFBFBD>”’–‘•“WPTRVQUS×ÐÔÒÖÑÕÓ70426153·°´²¶±µ³ªááXIEND®B`‚‰PNG IHDR@Y³X%ÀPLTE7_<EFBFBD>'Ç07çO0÷h÷¿ˆ÷׸ï×b÷·RßA·_!GO'/'//O .wV—$N¿<V߉·÷¸^ïÈ¿¯Ÿ€w_W?G'//O( g8ŽAA¾9pÎQ îh×ïÇ÷ïè¨öÀ€î˜YÞˆ9¾h)¦Po(G'/?7GOO_og‡—<EFBFBD>¯Ç¿ÿïè÷°ßÐI¶§0x~_O?7GGw_!¯wA÷ŸpѦ pHYsÄÄ•+IIDAT™c``dbfaecçàäâæáåã<EFBFBD>”’–‘•“WPTRVQUS×ÐÔÒÖÑÕÓ70426153·°´²¶±µ³ªááXIEND®B`‚‰PNG
 
IHDRþdó…iCCPICC profile(‘}‘=HÃ@Å_S¥"QqÈPÄ‚¨ˆ£T±J[¡U“K?„& IŠ‹£àZpðc±êà⬫ƒ« ~€¸º8)ºH‰ÿK
-b<8îÇ»{<EFBFBD>»w€P/3ÕìTÍ2Rñ˜˜Í­ˆ<EFBFBD>W0€>Œ"$1SO¤_÷ðñõ.ʳ¼Ïý9z”¼ÉŸH<ËtÃ"^'žÞ´tÎûÄaV’âsâ1ƒ.HüÈuÙå7ÎE‡ž62©9â0±Xlc¹<EFBFBD>YÉP‰§ˆ#ŠªQ¾<EFBFBD>uYá¼ÅY-WYóžü…Á¼¶œæ:Í!ıˆ!£Š ”a!J«FŠ‰íÇ<üƒŽ?I.™\`ä˜G*$Çþ¿»5 nR0t¾ØöÇ0Ø5Ûþ>¶íÆ à®´–¿Rf>I¯µ´Èл \\·4y¸ÜúŸtÉ<EFBFBD>ÉOS(€÷3ú¦ººWÝÞšû8}2ÔÕÒ ppŒ){ÍãÝ]í½ý{¦Ùß±frÀ„y\bKGDN¿<ÿí˜ý pHYs.#.#x¥?vtIMEç4ÉOÏEtEXtCommentCreated with GIMPW<EFBFBD>IDAT×c<ÐðΕ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>!†<EFBFBD><EFBFBD>a1C^þ7¬IEND®B`‚‰PNG

IHDRx1<EFBFBD>:zTXtRaw profile type exifxÚ¥šY–c)–EÿE <EFBFBD>öá]«f<EFBFBD>ÃÏ}<EFBFBD>Ü*Ü3~"Ëä.ÉÔðà6§sç_ÿ{ÝÿðSbÊ.—Ú¬›y~rÏ=ž4ÿùï>øüîßOýõ^øýuy)ñ˜>¿6û~þ×ëág€ÏÃàYùË@m}ߘ¿¿ÑówüöÇ@ß %Í(òdêß<EFBFBD>Rü¼¾ŒÏ²¼õVÿº„y><EFBFBD>ßïÂÀ§»TßØ?ƒüù{®Do^L1ž’ç>¥ø™@ÒÿäÒàIå>¤ ò¨çåsÿ<EFBFBD> ù»8ýütft5Õü·ú-+?ÏþÈVúÆÌý™­¿IÙ~ÿöuÊßgå…þ/WÎíû,þþºbñ™ÑÑ×ÿ{w»oͬbd#Ôö]Ô¯%¾g|nr ÔS3_ù_¢¾[çÖ¨êEÖ¶_~r[¡‡ÈµoÈa‡n8ïq…Ås<.VžÄ¸˜¢^l©Æ n¬©§<EFBFBD>]/í9ÅŸ¹„wÙî—{Wk\y>ƒ¾ò<EFBFBD>oîŸ~á^µB¯<EFBFBD>Ó‹÷1*ØLC™Ó=##á~ƒZ^€ÝþüQ^,Š²Z¤Øùb–ðH<EFBFBD>,<~Ú%ÔýqéÂdè‰È<EFBFBD>c <EFBFBD>@64˜:h'¥ÄÍ$cNÉÈM‹º4_©á}4–ÈËŽ×32Q’Ñs<EFBFBD> ’•s¡~jnÔÐ(©äRŠ•ZZéeX²lÅ̪ GM5»ZªÕZ[íu´Ôr+ÍZm­õ6zì Ð,Ýzí­÷>׌<øöàcÌ8Ó̳¸i³Î6û‹òYy•e«®¶ú;î´Á<EFBFBD>m»î¶û'JéäSŽ<EFBFBD>zÚég\Jí&wó-×n½íö;~²öMëÜþAÖÂ7kñeJ¬?YãÕZ 'E9#aÑå@Æ«R@AGåÌ·<EFBFBD>sTæ”3ß<EFBFBD>¿T"“,ÊÙÊÌ'Ärïܹøɨ2÷ÿÊ›«ù·¼Åÿ6sN©û‡™ûϼý]Ö¶hhùO;ª TŸè>Þ?<EFBFBD>éd•ö8 ˜¸~mýXÅ<EFBFBD>caÕ0ºÖëÛs?;ÓÔ¥­ñÝ©ì=ëc<EFBFBD>Ô +›õ&?j<EFBFBD>~r«ÏVãìA·M~Æ>±ì2Wº£ßÝë!WJÀog=—¹¥y7«›ý¦™Úž·.ïöl+œ¾ ÷,)*$e]¿k£g­Jgk,g<EFBFBD>mLjUż†…'û‚ }\³<EFBFBD>DÄ|©™Êˆu:aëÙeÁwEè2ð¼0Á»ö<EFBFBD>IŒ˜§¯½Ïiv®»¥åSÃ,uÔ¼îÜ1ÜœZ$ª÷<EFBFBD>ã@RFª<EFBFBD>eóùÛYT¼gv—¥ÔÜ^Þ*á¹ÔÌ<EFBFBD> ÿhƒR?dµÏÜw"Æ{êRi‚a IHDRx1<EFBFBD>:zTXtRaw profile type exifxÚ¥šY–c)–EÿE <EFBFBD>öá]«f<EFBFBD>ÃÏ}<EFBFBD>Ü*Ü3~"Ëä.ÉÔðà6§sç_ÿ{ÝÿðSbÊ.—Ú¬›y~rÏ=ž4ÿùï>øüîßOýõ^øýuy)ñ˜>¿6û~þ×ëág€ÏÃàYùË@m}ߘ¿¿ÑówüöÇ@ß %Í(òdêß<EFBFBD>Rü¼¾ŒÏ²¼õVÿº„y><EFBFBD>ßïÂÀ§»TßØ?ƒüù{®Do^L1ž’ç>¥ø™@ÒÿäÒàIå>¤ ò¨çåsÿ<EFBFBD> ù»8ýütft5Õü·ú-+?ÏþÈVúÆÌý™­¿IÙ~ÿöuÊßgå…þ/WÎíû,þþºbñ™ÑÑ×ÿ{w»oͬbd#Ôö]Ô¯%¾g|nr ÔS3_ù_¢¾[çÖ¨êEÖ¶_~r[¡‡ÈµoÈa‡n8ïq…Ås<.VžÄ¸˜¢^l©Æ n¬©§<EFBFBD>]/í9ÅŸ¹„wÙî—{Wk\y>ƒ¾ò<EFBFBD>oîŸ~á^µB¯<EFBFBD>Ó‹÷1*ØLC™Ó=##á~ƒZ^€ÝþüQ^,Š²Z¤Øùb–ðH<EFBFBD>,<~Ú%ÔýqéÂdè‰È<EFBFBD>c <EFBFBD>@64˜:h'¥ÄÍ$cNÉÈM‹º4_©á}4–ÈËŽ×32Q’Ñs<EFBFBD> ’•s¡~jnÔÐ(©äRŠ•ZZéeX²lÅ̪ GM5»ZªÕZ[íu´Ôr+ÍZm­õ6zì Ð,Ýzí­÷>׌<øöàcÌ8Ó̳¸i³Î6û‹òYy•e«®¶ú;î´Á<EFBFBD>m»î¶û'JéäSŽ<EFBFBD>zÚég\Jí&wó-×n½íö;~²öMëÜþAÖÂ7kñeJ¬?YãÕZ 'E9#aÑå@Æ«R@AGåÌ·<EFBFBD>sTæ”3ß<EFBFBD>¿T"“,ÊÙÊÌ'Ärïܹøɨ2÷ÿÊ›«ù·¼Åÿ6sN©û‡™ûϼý]Ö¶hhùO;ª TŸè>Þ?<EFBFBD>éd•ö8 ˜¸~mýXÅ<EFBFBD>caÕ0ºÖëÛs?;ÓÔ¥­ñÝ©ì=ëc<EFBFBD>Ô +›õ&?j<EFBFBD>~r«ÏVãìA·M~Æ>±ì2Wº£ßÝë!WJÀog=—¹¥y7«›ý¦™Úž·.ïöl+œ¾ ÷,)*$e]¿k£g­Jgk,g<EFBFBD>mLjUż†…'û‚ }\³<EFBFBD>DÄ|©™Êˆu:aëÙeÁwEè2ð¼0Á»ö<EFBFBD>IŒ˜§¯½Ïiv®»¥åSÃ,uÔ¼îÜ1ÜœZ$ª÷<EFBFBD>ã@RFª<EFBFBD>eóùÛYT¼gv—¥ÔÜ^Þ*á¹ÔÌ<EFBFBD> ÿhƒR?dµÏÜw"Æ{êRi‚a
û¹ë2åɈÎÖ bjËäâ­<EFBFBD>"öyRã´Ø(sïµm¦U©£4ç¢&OŠäs<EFBFBD>ê÷d~~·´‚[5>²T¨:&@@À û¹ë2åɈÎÖ bjËäâ­<EFBFBD>"öyRã´Ø(sïµm¦U©£4ç¢&OŠäs<EFBFBD>ê÷d~~·´‚[5>²T¨:&@@À
’r#5ÉLlM~½f»|½®» §õt¨›¶mQbÅÆì=TD/52½·É|ußêîšz¨}žL”Òšé”Û{£¦˜tÞ»á]ƒ+å4e€šÕã¼tŸ~<EFBFBD>›€zjëðŠëUìÞy­i…Ä<EFBFBD>É®ºÏžë.7*íN³2Ccesñ?–±n Ø)ï ”K‘ìz(AK\S<EFBFBD>˜–'*Gçºy:õKeìŠ\›ÊA¯‰82!J9ßh¬ƒL„°ˆê£¬›~‚2Œ3çÎãPÓ(€muŽÙ{J3)ž³º<EFBFBD>Ô0ÙsK~«„Gï:­èæ=iä¬ÙЭ¦ëÙmVÛzNFEšSxY¯d`¿epyR«éŸC汤Æâ"¶$‘W<EFBFBD>¨$M" üÌ<EFBFBD>p÷U ’r#5ÉLlM~½f»|½®» §õt¨›¶mQbÅÆì=TD/52½·É|ußêîšz¨}žL”Òšé”Û{£¦˜tÞ»á]ƒ+å4e€šÕã¼tŸ~<EFBFBD>›€zjëðŠëUìÞy­i…Ä<EFBFBD>É®ºÏžë.7*íN³2Ccesñ?–±n Ø)ï ”K‘ìz(AK\S<EFBFBD>˜–'*Gçºy:õKeìŠ\›ÊA¯‰82!J9ßh¬ƒL„°ˆê£¬›~‚2Œ3çÎãPÓ(€muŽÙ{J3)ž³º<EFBFBD>Ô0ÙsK~«„Gï:­èæ=iä¬ÙЭ¦ëÙmVÛzNFEšSxY¯d`¿epyR«éŸC汤Æâ"¶$‘W<EFBFBD>¨$M" üÌ<EFBFBD>p÷U

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save