initial commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
CBAssets.rar
|
||||
|
||||
CBAssets
|
||||
|
||||
|
||||
|
||||
56
CMakeLists.txt
Normal file
56
CMakeLists.txt
Normal file
@@ -0,0 +1,56 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(cursebreakerParser)
|
||||
|
||||
# Set C++ standard
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Collect all source files
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
|
||||
# Include FetchContent
|
||||
include(FetchContent)
|
||||
|
||||
message(STATUS "Using rapidyaml")
|
||||
message(STATUS "Using GLM")
|
||||
message(STATUS "Using TinyXML2")
|
||||
|
||||
FetchContent_Declare(
|
||||
rapidyaml
|
||||
GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git
|
||||
GIT_TAG master
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
glm
|
||||
GIT_REPOSITORY https://github.com/g-truc/glm.git
|
||||
GIT_TAG master
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
tinyxml2
|
||||
GIT_REPOSITORY https://github.com/leethomason/tinyxml2.git
|
||||
GIT_TAG master
|
||||
)
|
||||
|
||||
# Enable exceptions in rapidyaml
|
||||
set(RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
set(RYML_DBG OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(rapidyaml glm tinyxml2)
|
||||
|
||||
# Create the main executable
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
|
||||
# Link libraries to our executable
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ryml::ryml)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC tinyxml2::tinyxml2)
|
||||
|
||||
# Add include directories for rapidyaml
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${rapidyaml_SOURCE_DIR}/src)
|
||||
|
||||
# Add include directories for GLM
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${glm_SOURCE_DIR})
|
||||
|
||||
# Include directories
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||
86
include/assets/asset_base.hpp
Normal file
86
include/assets/asset_base.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
|
||||
#include <ryml.hpp>
|
||||
#include <ryml_std.hpp>
|
||||
#include <c4/format.hpp>
|
||||
|
||||
struct AssetBase
|
||||
{
|
||||
public:
|
||||
int64_t ID;
|
||||
};
|
||||
|
||||
struct AssetGUID
|
||||
{
|
||||
AssetGUID() = default;
|
||||
AssetGUID(const char* data) : AssetGUID(std::string_view(data, 32)) {};
|
||||
AssetGUID(std::string_view data)
|
||||
{
|
||||
for (size_t i = 0; i < 16; ++i) Data[0] = (Data[0] << 4) | hexToVal(data[i]);
|
||||
for (size_t i = 16; i < 32; ++i) Data[1] = (Data[1] << 4) | hexToVal(data[i]);
|
||||
}
|
||||
|
||||
static uint64_t hexToVal(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') ? (c - '0') : (c - 'a' + 10);
|
||||
}
|
||||
|
||||
bool operator==(const AssetGUID& other) const
|
||||
{
|
||||
return Data[0] == other.Data[0] && Data[1] == other.Data[1];
|
||||
}
|
||||
|
||||
static constexpr uint64_t DefaultVal = 0xe000000000000000;
|
||||
|
||||
std::array<uint64_t, 2> Data{ 0, DefaultVal };
|
||||
};
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<AssetGUID> {
|
||||
size_t operator()(const AssetGUID& v) const noexcept
|
||||
{
|
||||
auto h1 = std::hash<uint64_t>{}(v.Data[0]);
|
||||
auto h2 = std::hash<uint64_t>{}(v.Data[1]);
|
||||
return h1 ^ (h2 << 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* ParseAssetRef(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
T* asset;
|
||||
node["fileID"] >> asset;
|
||||
return asset;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool LinkAssetRef(const std::unordered_map<int64_t, AssetBase*>& assetsMap, T*& asset)
|
||||
{
|
||||
auto it = assetsMap.find(reinterpret_cast<int64_t>(asset));
|
||||
if (it != assetsMap.end())
|
||||
{
|
||||
asset = reinterpret_cast<T*>(it->second);
|
||||
return true;
|
||||
}
|
||||
asset = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline AssetGUID ParseAssetGUID(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
auto guidStringSpan2 = node["guid"].val();
|
||||
|
||||
assert(guidStringSpan2.size() == 32);
|
||||
|
||||
return AssetGUID{std::string_view(guidStringSpan2.data(), guidStringSpan2.size())};
|
||||
}
|
||||
21
include/assets/custom_assets.hpp
Normal file
21
include/assets/custom_assets.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "asset_base.hpp"
|
||||
|
||||
struct Drop
|
||||
{
|
||||
int itemId;
|
||||
int minAmount;
|
||||
int maxAmount;
|
||||
int dropChance;
|
||||
};
|
||||
|
||||
struct Interactable_Resource : public AssetBase
|
||||
{
|
||||
int maxHealth;
|
||||
int respawnTime;
|
||||
std::vector<Drop> drops;
|
||||
std::vector<int> requiredTools;
|
||||
int xp;
|
||||
int typeId;
|
||||
};
|
||||
54
include/assets/game_object.hpp
Normal file
54
include/assets/game_object.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ryml_std.hpp>
|
||||
|
||||
#include "asset_base.hpp"
|
||||
#include "transform.hpp"
|
||||
|
||||
struct GameObjectAsset : public AssetBase
|
||||
{
|
||||
std::string name;
|
||||
uint8_t layer{ 0 };
|
||||
uint8_t namMeshLayer{ 0 };
|
||||
std::string tag;
|
||||
|
||||
TransformAsset* transform{ nullptr };
|
||||
std::vector<AssetBase*> Components;
|
||||
};
|
||||
|
||||
inline GameObjectAsset ParseGameObject(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
GameObjectAsset gameObject;
|
||||
|
||||
node["m_Name"] >> gameObject.name;
|
||||
node["m_Layer"] >> gameObject.layer;
|
||||
node["m_TagString"] >> gameObject.tag;
|
||||
|
||||
auto components = node["m_Component"];
|
||||
auto it = components.begin();
|
||||
|
||||
gameObject.transform = ParseAssetRef<TransformAsset>((*it)["component"]);
|
||||
++it;
|
||||
for (; it != components.end(); ++it)
|
||||
{
|
||||
gameObject.Components.push_back(ParseAssetRef<AssetBase>((*it)["component"]));
|
||||
}
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
inline void LinkGameObject(const std::unordered_map<int64_t, AssetBase*>& assetsMap, GameObjectAsset& gameObject)
|
||||
{
|
||||
LinkAssetRef<TransformAsset>(assetsMap, gameObject.transform);
|
||||
for (int i = static_cast<int>(gameObject.Components.size()) - 1; i >= 0; i--)
|
||||
{
|
||||
if (!LinkAssetRef<AssetBase>(assetsMap, gameObject.Components[i]))
|
||||
{
|
||||
std::swap(gameObject.Components[i], gameObject.Components.back());
|
||||
gameObject.Components.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
include/assets/mesh_filter.hpp
Normal file
29
include/assets/mesh_filter.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "asset_base.hpp"
|
||||
|
||||
|
||||
struct GameObjectAsset;
|
||||
|
||||
struct MeshFilterAsset : AssetBase
|
||||
{
|
||||
AssetGUID Mesh;
|
||||
GameObjectAsset* GameObject{ nullptr };
|
||||
};
|
||||
|
||||
inline MeshFilterAsset ParseMeshFilter(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
MeshFilterAsset meshFilter;
|
||||
|
||||
meshFilter.Mesh = ParseAssetGUID(node["m_Mesh"]);
|
||||
meshFilter.GameObject = ParseAssetRef<GameObjectAsset>(node["m_GameObject"]);
|
||||
|
||||
return meshFilter;
|
||||
}
|
||||
|
||||
inline void LinkMeshFilter(const std::unordered_map<int64_t, AssetBase*>& assetsMap, MeshFilterAsset& meshFilter)
|
||||
{
|
||||
LinkAssetRef<GameObjectAsset>(assetsMap, meshFilter.GameObject);
|
||||
}
|
||||
25
include/assets/prefab_instance.hpp
Normal file
25
include/assets/prefab_instance.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "asset_base.hpp"
|
||||
|
||||
//struct PrefabInstanceAsset : AssetBase
|
||||
//{
|
||||
// AssetGUID prefabGUID;
|
||||
// ryml::Tree Data{};
|
||||
//};
|
||||
|
||||
struct PrefabAsset
|
||||
{
|
||||
std::filesystem::path path;
|
||||
std::string name;
|
||||
|
||||
std::unordered_map<int64_t, ryml::Tree> Data;
|
||||
};
|
||||
|
||||
//inline PrefabInstanceAsset ParsePrefabInstance(const ryml::ConstNodeRef& node)
|
||||
//{
|
||||
// PrefabInstanceAsset prefabInstance;
|
||||
// prefabInstance.prefabGUID = ParseAssetGUID(node["m_SourcePrefab"]);
|
||||
// // prefabInstance.Data = node;
|
||||
// return prefabInstance;
|
||||
//}
|
||||
26
include/assets/scene.hpp
Normal file
26
include/assets/scene.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "game_object.hpp"
|
||||
#include "transform.hpp"
|
||||
#include "mesh_filter.hpp"
|
||||
#include "prefab_instance.hpp"
|
||||
|
||||
struct SceneAsset
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path;
|
||||
std::string name;
|
||||
|
||||
std::vector<GameObjectAsset> gameObjects;
|
||||
std::vector<TransformAsset> transforms;
|
||||
std::vector<MeshFilterAsset> meshFilters;
|
||||
// std::vector<PrefabInstanceAsset> prefabInstances;
|
||||
|
||||
std::unordered_map<int64_t, void*> IDtoAsset;
|
||||
|
||||
|
||||
};
|
||||
75
include/assets/transform.hpp
Normal file
75
include/assets/transform.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include "asset_base.hpp"
|
||||
|
||||
struct GameObjectAsset;
|
||||
|
||||
struct TransformAsset : public AssetBase
|
||||
{
|
||||
glm::quat Rotation;
|
||||
glm::vec3 Position;
|
||||
glm::vec3 Scale;
|
||||
|
||||
glm::mat4 GlobalMatrix;
|
||||
|
||||
GameObjectAsset* GameObject{ nullptr };
|
||||
std::vector<TransformAsset*> Children;
|
||||
TransformAsset* Parent{ nullptr };
|
||||
};
|
||||
|
||||
inline glm::vec3 ParseVector3(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
glm::vec3 vector;
|
||||
node["x"] >> vector.x;
|
||||
node["y"] >> vector.y;
|
||||
node["z"] >> vector.z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
inline glm::quat ParseQuaternion(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
glm::quat quaternion;
|
||||
node["x"] >> quaternion.x;
|
||||
node["y"] >> quaternion.y;
|
||||
node["z"] >> quaternion.z;
|
||||
node["w"] >> quaternion.w;
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
inline TransformAsset ParseTransform(const ryml::ConstNodeRef& node)
|
||||
{
|
||||
TransformAsset transform;
|
||||
transform.Rotation = ParseQuaternion(node["m_LocalRotation"]);
|
||||
transform.Position = ParseVector3(node["m_LocalPosition"]);
|
||||
transform.Scale = ParseVector3(node["m_LocalScale"]);
|
||||
|
||||
transform.Parent = ParseAssetRef<TransformAsset>(node["m_Father"]);
|
||||
transform.GameObject = ParseAssetRef<GameObjectAsset>(node["m_GameObject"]);
|
||||
|
||||
auto children = node["m_Children"];
|
||||
for (const auto& child : children)
|
||||
{
|
||||
transform.Children.push_back(ParseAssetRef<TransformAsset>(child));
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
inline void LinkTransform(const std::unordered_map<int64_t, AssetBase*>& assetsMap, TransformAsset& transform)
|
||||
{
|
||||
LinkAssetRef<TransformAsset>(assetsMap, transform.Parent);
|
||||
for (int i = static_cast<int>(transform.Children.size()) - 1; i >= 0; i--)
|
||||
{
|
||||
if (!LinkAssetRef<TransformAsset>(assetsMap, transform.Children[i]))
|
||||
{
|
||||
std::swap(transform.Children[i], transform.Children.back());
|
||||
transform.Children.pop_back();
|
||||
}
|
||||
}
|
||||
LinkAssetRef<GameObjectAsset>(assetsMap, transform.GameObject);
|
||||
}
|
||||
208
include/configs/constants.h
Normal file
208
include/configs/constants.h
Normal file
@@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cursebreaker {
|
||||
|
||||
enum class SkillType : uint8_t
|
||||
{
|
||||
woodcutting,
|
||||
fishing,
|
||||
swordsmanship,
|
||||
mining,
|
||||
archery,
|
||||
magic,
|
||||
defence,
|
||||
blacksmithy,
|
||||
tailoring,
|
||||
carpentry,
|
||||
alchemy,
|
||||
cooking,
|
||||
none,
|
||||
};
|
||||
|
||||
enum class WorkbenchType : uint8_t
|
||||
{
|
||||
none,
|
||||
anvil,
|
||||
oven,
|
||||
cooking,
|
||||
carpenter,
|
||||
tailor,
|
||||
forge,
|
||||
alchemist,
|
||||
mystic,
|
||||
};
|
||||
|
||||
enum class StatType : uint8_t
|
||||
{
|
||||
health,
|
||||
accuracyPhysical,
|
||||
accuracyMagical,
|
||||
accuracyRanged,
|
||||
damagePhysical,
|
||||
damageMagical,
|
||||
damageRanged,
|
||||
resistancePhysical,
|
||||
resistanceMagical,
|
||||
resistanceRanged,
|
||||
healing,
|
||||
movementSpeed,
|
||||
mana,
|
||||
manaregen,
|
||||
healthregen,
|
||||
power,
|
||||
critical,
|
||||
DamageVsUndead,
|
||||
DamageVsBeasts,
|
||||
CritterSlaying,
|
||||
none,
|
||||
harvestingSpeedWoodcutting
|
||||
};
|
||||
|
||||
enum class ItemType : uint8_t
|
||||
{
|
||||
weapon,
|
||||
shield,
|
||||
armor,
|
||||
head,
|
||||
resource,
|
||||
consumable,
|
||||
trinket,
|
||||
bracelet
|
||||
};
|
||||
|
||||
enum class ItemCategory : uint8_t
|
||||
{
|
||||
none,
|
||||
bone,
|
||||
bow,
|
||||
crossbow,
|
||||
constructable,
|
||||
torch,
|
||||
blacksmithhammer,
|
||||
questitem,
|
||||
heavyArmor,
|
||||
warhammer,
|
||||
shield,
|
||||
hatchet,
|
||||
blade,
|
||||
armor,
|
||||
pickaxe,
|
||||
fish,
|
||||
fishingrod,
|
||||
shears,
|
||||
hammer,
|
||||
battleaxe,
|
||||
morningstar,
|
||||
wand,
|
||||
staff,
|
||||
dagger
|
||||
};
|
||||
|
||||
enum class Tool : uint8_t
|
||||
{
|
||||
hatchet,
|
||||
pickaxe,
|
||||
broom,
|
||||
fishingrod,
|
||||
none
|
||||
};
|
||||
|
||||
enum class SpellBookType : uint8_t
|
||||
{
|
||||
none,
|
||||
spells,
|
||||
abilities
|
||||
};
|
||||
|
||||
enum class ActionType : uint8_t
|
||||
{
|
||||
NpcDeath,
|
||||
PlayerDeath,
|
||||
PlayerRespawn,
|
||||
NpcInteract,
|
||||
QuestUpdate,
|
||||
QuestTimerEnd,
|
||||
UseItemOnItem,
|
||||
UseItemOnNpc,
|
||||
ConsumeItem,
|
||||
NpcCombatInteract,
|
||||
none,
|
||||
NpcKilledByPlayer,
|
||||
EnterMap,
|
||||
VarUpdated,
|
||||
GrantAchievement,
|
||||
PlayerKilledByNpc,
|
||||
UseItem,
|
||||
CraftItem,
|
||||
HarvestItem,
|
||||
BuyItem,
|
||||
NpcTagKilledByPlayer,
|
||||
UseAbility,
|
||||
LootItem,
|
||||
UseItemCategoryOnItem,
|
||||
};
|
||||
|
||||
enum class Material : uint8_t
|
||||
{
|
||||
none,
|
||||
copper,
|
||||
iron,
|
||||
imbersteel,
|
||||
titanium,
|
||||
spruce,
|
||||
oak,
|
||||
evark,
|
||||
deadwood,
|
||||
sheep,
|
||||
troll,
|
||||
ogre,
|
||||
demon,
|
||||
wool,
|
||||
cotton,
|
||||
flax,
|
||||
jute,
|
||||
};
|
||||
|
||||
enum class GenstatType : uint8_t
|
||||
{
|
||||
none,
|
||||
dagger,
|
||||
broadsword,
|
||||
battleaxe,
|
||||
greatsword,
|
||||
morningstar,
|
||||
hammer,
|
||||
spear,
|
||||
bow,
|
||||
staff,
|
||||
wand,
|
||||
crossbow,
|
||||
woodenshield,
|
||||
wizardhat,
|
||||
wizardrobe,
|
||||
grandwizardhat,
|
||||
grandwizardrobe,
|
||||
leatherhood,
|
||||
leatherbracelet,
|
||||
leatherarmor,
|
||||
studdedleatherhood,
|
||||
studdedleatherbracelet,
|
||||
studdedleatherarmor,
|
||||
helmet,
|
||||
shield,
|
||||
armor,
|
||||
platehelmet,
|
||||
kiteshield,
|
||||
platearmor,
|
||||
};
|
||||
|
||||
struct StatValue
|
||||
{
|
||||
StatType type;
|
||||
uint8_t value;
|
||||
bool isPercentage;
|
||||
};
|
||||
|
||||
};
|
||||
103
include/configs/items.hpp
Normal file
103
include/configs/items.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "constants.h"
|
||||
|
||||
namespace cursebreaker {
|
||||
|
||||
struct CraftingItemEntry
|
||||
{
|
||||
uint16_t itemId{};
|
||||
uint16_t amount{};
|
||||
};
|
||||
|
||||
// Struct for item crafting requirements
|
||||
struct ItemCrafting
|
||||
{
|
||||
WorkbenchType workbench;
|
||||
SkillType craftingskill;
|
||||
std::vector<CraftingItemEntry> craftingitems;
|
||||
std::string checks;
|
||||
|
||||
ItemCrafting() = default;
|
||||
};
|
||||
|
||||
// Main item struct
|
||||
struct Item {
|
||||
uint16_t id = 0;
|
||||
uint16_t price = 0;
|
||||
uint16_t abilityid = 0;
|
||||
uint16_t learnabilityid = 0;
|
||||
|
||||
SkillType skill = SkillType::none;
|
||||
ItemCategory category = ItemCategory::none;
|
||||
ItemType slot = ItemType::resource;
|
||||
Tool tool = Tool::none;
|
||||
GenstatType generation = GenstatType::none;
|
||||
uint8_t level = 0;
|
||||
uint8_t foodlevel = 0;
|
||||
uint8_t foodamount = 0;
|
||||
uint8_t foodfrequency = 0;
|
||||
uint8_t foodtime = 0;
|
||||
uint8_t maxstack = 0;
|
||||
uint8_t book = 0;
|
||||
|
||||
bool stackable : 1;
|
||||
bool twohanded : 1;
|
||||
bool undroppable : 1;
|
||||
bool hidemilestone : 1;
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string comment;
|
||||
|
||||
std::vector<StatValue> stats;
|
||||
std::vector<ItemCrafting> craftings;
|
||||
|
||||
Item() = default;
|
||||
};
|
||||
|
||||
// Helper functions for enum conversions
|
||||
StatType stringToStatType(const std::string& str);
|
||||
ItemCategory stringToItemCategory(const std::string& str);
|
||||
ItemType stringToItemType(const std::string& str);
|
||||
Tool stringToTool(const std::string& str);
|
||||
SkillType stringToSkillType(const std::string& str);
|
||||
WorkbenchType stringToWorkbenchType(const std::string& str);
|
||||
GenstatType stringToGenstatType(const std::string& str);
|
||||
|
||||
// Items configuration manager
|
||||
class ItemsConfig {
|
||||
public:
|
||||
static ItemsConfig& getInstance() {
|
||||
static ItemsConfig instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool loadFromXML(const std::string& filepath);
|
||||
const Item* getItemById(int id) const;
|
||||
const std::unordered_map<int, Item>& getAllItems() const { return m_items; }
|
||||
|
||||
private:
|
||||
ItemsConfig() = default;
|
||||
~ItemsConfig() = default;
|
||||
ItemsConfig(const ItemsConfig&) = delete;
|
||||
ItemsConfig& operator=(const ItemsConfig&) = delete;
|
||||
|
||||
std::unordered_map<int, Item> m_items;
|
||||
|
||||
// Helper methods for parsing
|
||||
void parseItem(tinyxml2::XMLElement* itemElement);
|
||||
void parseStats(tinyxml2::XMLElement* itemElement, Item& item);
|
||||
void parseCraftings(tinyxml2::XMLElement* itemElement, Item& item);
|
||||
std::string getAttributeValue(tinyxml2::XMLElement* element, const char* attribute, const std::string& defaultValue = "");
|
||||
int getAttributeValueInt(tinyxml2::XMLElement* element, const char* attribute, int defaultValue = 0);
|
||||
bool getAttributeValueBool(tinyxml2::XMLElement* element, const char* attribute, bool defaultValue = false);
|
||||
};
|
||||
|
||||
} // namespace cursebreaker
|
||||
38
include/project_parser.h
Normal file
38
include/project_parser.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "assets/asset_base.hpp"
|
||||
#include "assets/scene.hpp"
|
||||
|
||||
struct AssetPath
|
||||
{
|
||||
std::filesystem::path path{};
|
||||
AssetGUID GUID{};
|
||||
};
|
||||
|
||||
struct ParsedProject
|
||||
{
|
||||
std::filesystem::path m_projectRoot;
|
||||
|
||||
std::vector<AssetPath> m_scriptFiles;
|
||||
std::vector<AssetPath> m_imageFiles;
|
||||
std::vector<AssetPath> m_meshFiles;
|
||||
std::vector<AssetPath> m_sceneFiles;
|
||||
std::vector<AssetPath> m_prefabFiles;
|
||||
|
||||
std::vector<PrefabAsset> m_prefabAssets;
|
||||
std::vector<SceneAsset> m_sceneAssets;
|
||||
|
||||
std::unordered_map<AssetGUID, PrefabAsset*> m_prefabsMap;
|
||||
std::unordered_map<AssetGUID, uint32_t> m_scriptToClassHash;
|
||||
|
||||
};
|
||||
|
||||
void ParseProject(const std::filesystem::path& projectRoot);
|
||||
|
||||
extern ParsedProject g_parsedProject;
|
||||
5
include/tree_builder.h
Normal file
5
include/tree_builder.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "assets/scene.hpp"
|
||||
|
||||
void BuildTree(SceneAsset& sceneAsset);
|
||||
334
src/configs/items.cpp
Normal file
334
src/configs/items.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "configs/items.hpp"
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace cursebreaker {
|
||||
|
||||
// Helper functions to convert strings to enums
|
||||
StatType stringToStatType(const std::string& str) {
|
||||
static const std::unordered_map<std::string, StatType> statMap = {
|
||||
{"damagephysical", StatType::damagePhysical},
|
||||
{"damagemagical", StatType::damageMagical},
|
||||
{"damageranged", StatType::damageRanged},
|
||||
{"accuracyphysical", StatType::accuracyPhysical},
|
||||
{"accuracymagical", StatType::accuracyMagical},
|
||||
{"accuracyranged", StatType::accuracyRanged},
|
||||
{"resistancephysical", StatType::resistancePhysical},
|
||||
{"resistancemagical", StatType::resistanceMagical},
|
||||
{"resistanceranged", StatType::resistanceRanged},
|
||||
{"health", StatType::health},
|
||||
{"mana", StatType::mana},
|
||||
{"manaregen", StatType::manaregen},
|
||||
{"healthregen", StatType::healthregen},
|
||||
{"movementSpeed", StatType::movementSpeed},
|
||||
{"power", StatType::power},
|
||||
{"critical", StatType::critical},
|
||||
{"healing", StatType::healing},
|
||||
{"DamageVsUndead", StatType::DamageVsUndead},
|
||||
{"DamageVsBeasts", StatType::DamageVsBeasts},
|
||||
{"CritterSlaying", StatType::CritterSlaying},
|
||||
{"harvestingSpeedWoodcutting", StatType::harvestingSpeedWoodcutting}
|
||||
};
|
||||
|
||||
auto it = statMap.find(str);
|
||||
return (it != statMap.end()) ? it->second : StatType::none;
|
||||
}
|
||||
|
||||
ItemCategory stringToItemCategory(const std::string& str) {
|
||||
static const std::unordered_map<std::string, ItemCategory> categoryMap = {
|
||||
{"bone", ItemCategory::bone},
|
||||
{"bow", ItemCategory::bow},
|
||||
{"crossbow", ItemCategory::crossbow},
|
||||
{"constructable", ItemCategory::constructable},
|
||||
{"torch", ItemCategory::torch},
|
||||
{"blacksmithhammer", ItemCategory::blacksmithhammer},
|
||||
{"questitem", ItemCategory::questitem},
|
||||
{"heavyArmor", ItemCategory::heavyArmor},
|
||||
{"warhammer", ItemCategory::warhammer},
|
||||
{"shield", ItemCategory::shield},
|
||||
{"hatchet", ItemCategory::hatchet},
|
||||
{"blade", ItemCategory::blade},
|
||||
{"armor", ItemCategory::armor},
|
||||
{"pickaxe", ItemCategory::pickaxe},
|
||||
{"fish", ItemCategory::fish},
|
||||
{"fishingrod", ItemCategory::fishingrod},
|
||||
{"shears", ItemCategory::shears},
|
||||
{"hammer", ItemCategory::hammer},
|
||||
{"battleaxe", ItemCategory::battleaxe},
|
||||
{"morningstar", ItemCategory::morningstar},
|
||||
{"wand", ItemCategory::wand},
|
||||
{"staff", ItemCategory::staff},
|
||||
{"dagger", ItemCategory::dagger}
|
||||
};
|
||||
|
||||
auto it = categoryMap.find(str);
|
||||
return (it != categoryMap.end()) ? it->second : ItemCategory::none;
|
||||
}
|
||||
|
||||
ItemType stringToItemType(const std::string& str) {
|
||||
static const std::unordered_map<std::string, ItemType> typeMap = {
|
||||
{"weapon", ItemType::weapon},
|
||||
{"shield", ItemType::shield},
|
||||
{"armor", ItemType::armor},
|
||||
{"head", ItemType::head},
|
||||
{"resource", ItemType::resource},
|
||||
{"consumable", ItemType::consumable},
|
||||
{"trinket", ItemType::trinket},
|
||||
{"bracelet", ItemType::bracelet}
|
||||
};
|
||||
|
||||
auto it = typeMap.find(str);
|
||||
return (it != typeMap.end()) ? it->second : ItemType::resource;
|
||||
}
|
||||
|
||||
Tool stringToTool(const std::string& str) {
|
||||
static const std::unordered_map<std::string, Tool> toolMap = {
|
||||
{"hatchet", Tool::hatchet},
|
||||
{"pickaxe", Tool::pickaxe},
|
||||
{"broom", Tool::broom},
|
||||
{"fishingrod", Tool::fishingrod}
|
||||
};
|
||||
|
||||
auto it = toolMap.find(str);
|
||||
return (it != toolMap.end()) ? it->second : Tool::none;
|
||||
}
|
||||
|
||||
SkillType stringToSkillType(const std::string& str) {
|
||||
static const std::unordered_map<std::string, SkillType> skillMap = {
|
||||
{"woodcutting", SkillType::woodcutting},
|
||||
{"fishing", SkillType::fishing},
|
||||
{"swordsmanship", SkillType::swordsmanship},
|
||||
{"mining", SkillType::mining},
|
||||
{"archery", SkillType::archery},
|
||||
{"magic", SkillType::magic},
|
||||
{"defence", SkillType::defence},
|
||||
{"blacksmithy", SkillType::blacksmithy},
|
||||
{"tailoring", SkillType::tailoring},
|
||||
{"carpentry", SkillType::carpentry},
|
||||
{"alchemy", SkillType::alchemy},
|
||||
{"cooking", SkillType::cooking}
|
||||
};
|
||||
|
||||
auto it = skillMap.find(str);
|
||||
return (it != skillMap.end()) ? it->second : SkillType::none;
|
||||
}
|
||||
|
||||
WorkbenchType stringToWorkbenchType(const std::string& str) {
|
||||
static const std::unordered_map<std::string, WorkbenchType> workbenchMap = {
|
||||
{"none", WorkbenchType::none},
|
||||
{"anvil", WorkbenchType::anvil},
|
||||
{"oven", WorkbenchType::oven},
|
||||
{"cooking", WorkbenchType::cooking},
|
||||
{"carpenter", WorkbenchType::carpenter},
|
||||
{"tailor", WorkbenchType::tailor},
|
||||
{"forge", WorkbenchType::forge},
|
||||
{"alchemist", WorkbenchType::alchemist},
|
||||
{"mystic", WorkbenchType::mystic}
|
||||
};
|
||||
|
||||
auto it = workbenchMap.find(str);
|
||||
return (it != workbenchMap.end()) ? it->second : WorkbenchType::none;
|
||||
}
|
||||
|
||||
GenstatType stringToGenstatType(const std::string& str) {
|
||||
static const std::unordered_map<std::string, GenstatType> genstatMap = {
|
||||
{"dagger", GenstatType::dagger},
|
||||
{"broadsword", GenstatType::broadsword},
|
||||
{"battleaxe", GenstatType::battleaxe},
|
||||
{"greatsword", GenstatType::greatsword},
|
||||
{"morningstar", GenstatType::morningstar},
|
||||
{"hammer", GenstatType::hammer},
|
||||
{"spear", GenstatType::spear},
|
||||
{"bow", GenstatType::bow},
|
||||
{"staff", GenstatType::staff},
|
||||
{"wand", GenstatType::wand},
|
||||
{"crossbow", GenstatType::crossbow},
|
||||
{"woodenshield", GenstatType::woodenshield},
|
||||
{"wizardhat", GenstatType::wizardhat},
|
||||
{"wizardrobe", GenstatType::wizardrobe},
|
||||
{"grandwizardhat", GenstatType::grandwizardhat},
|
||||
{"grandwizardrobe", GenstatType::grandwizardrobe},
|
||||
{"leatherhood", GenstatType::leatherhood},
|
||||
{"leatherbracelet", GenstatType::leatherbracelet},
|
||||
{"leatherarmor", GenstatType::leatherarmor},
|
||||
{"studdedleatherhood", GenstatType::studdedleatherhood},
|
||||
{"studdedleatherbracelet", GenstatType::studdedleatherbracelet},
|
||||
{"studdedleatherarmor", GenstatType::studdedleatherarmor},
|
||||
{"helmet", GenstatType::helmet},
|
||||
{"shield", GenstatType::shield},
|
||||
{"armor", GenstatType::armor},
|
||||
{"platehelmet", GenstatType::platehelmet},
|
||||
{"kiteshield", GenstatType::kiteshield},
|
||||
{"platearmor", GenstatType::platearmor}
|
||||
};
|
||||
|
||||
auto it = genstatMap.find(str);
|
||||
return (it != genstatMap.end()) ? it->second : GenstatType::none;
|
||||
}
|
||||
|
||||
bool ItemsConfig::loadFromXML(const std::string& filepath) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
tinyxml2::XMLError result = doc.LoadFile(filepath.c_str());
|
||||
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
std::cerr << "Failed to load items XML file: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* root = doc.FirstChildElement("items");
|
||||
if (!root) {
|
||||
std::cerr << "Invalid XML structure: missing 'items' root element" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_items.clear();
|
||||
|
||||
// Parse all items
|
||||
for (tinyxml2::XMLElement* itemElement = root->FirstChildElement("item");
|
||||
itemElement != nullptr;
|
||||
itemElement = itemElement->NextSiblingElement("item")) {
|
||||
parseItem(itemElement);
|
||||
}
|
||||
|
||||
std::cout << "Loaded " << m_items.size() << " items from XML" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ItemsConfig::parseItem(tinyxml2::XMLElement* itemElement) {
|
||||
Item item;
|
||||
|
||||
// Parse basic attributes
|
||||
item.id = static_cast<uint16_t>(getAttributeValueInt(itemElement, "id"));
|
||||
item.name = getAttributeValue(itemElement, "name");
|
||||
item.description = getAttributeValue(itemElement, "description");
|
||||
item.category = stringToItemCategory(getAttributeValue(itemElement, "category"));
|
||||
item.slot = stringToItemType(getAttributeValue(itemElement, "slot"));
|
||||
item.tool = stringToTool(getAttributeValue(itemElement, "tool"));
|
||||
item.skill = stringToSkillType(getAttributeValue(itemElement, "skill"));
|
||||
item.comment = getAttributeValue(itemElement, "comment");
|
||||
|
||||
// Parse numeric attributes
|
||||
item.price = static_cast<uint16_t>(getAttributeValueInt(itemElement, "price"));
|
||||
item.abilityid = static_cast<uint16_t>(getAttributeValueInt(itemElement, "abilityid"));
|
||||
item.learnabilityid = static_cast<uint16_t>(getAttributeValueInt(itemElement, "learnabilityid"));
|
||||
item.level = static_cast<uint8_t>(getAttributeValueInt(itemElement, "level"));
|
||||
item.foodlevel = static_cast<uint8_t>(getAttributeValueInt(itemElement, "foodlevel"));
|
||||
item.foodamount = static_cast<uint8_t>(getAttributeValueInt(itemElement, "foodamount"));
|
||||
item.foodfrequency = static_cast<uint8_t>(getAttributeValueInt(itemElement, "foodfrequency"));
|
||||
item.foodtime = static_cast<uint8_t>(getAttributeValueInt(itemElement, "foodtime"));
|
||||
item.maxstack = static_cast<uint8_t>(getAttributeValueInt(itemElement, "maxstack"));
|
||||
item.book = static_cast<uint8_t>(getAttributeValueInt(itemElement, "book"));
|
||||
|
||||
// Parse boolean attributes
|
||||
item.stackable = getAttributeValueBool(itemElement, "stackable");
|
||||
item.twohanded = getAttributeValueBool(itemElement, "twohanded");
|
||||
item.undroppable = getAttributeValueBool(itemElement, "undroppable");
|
||||
item.hidemilestone = getAttributeValueBool(itemElement, "hidemilestone");
|
||||
|
||||
// Parse child elements
|
||||
parseStats(itemElement, item);
|
||||
parseCraftings(itemElement, item);
|
||||
|
||||
m_items[item.id] = std::move(item);
|
||||
}
|
||||
|
||||
void ItemsConfig::parseStats(tinyxml2::XMLElement* itemElement, Item& item) {
|
||||
for (tinyxml2::XMLElement* statElement = itemElement->FirstChildElement("stat");
|
||||
statElement != nullptr;
|
||||
statElement = statElement->NextSiblingElement("stat")) {
|
||||
|
||||
// Get all attributes of the stat element
|
||||
const tinyxml2::XMLAttribute* attribute = statElement->FirstAttribute();
|
||||
while (attribute) {
|
||||
StatType statType = stringToStatType(attribute->Name());
|
||||
std::string valueStr = attribute->Value();
|
||||
bool isPercentage = false;
|
||||
uint8_t value = 0;
|
||||
|
||||
// Check if value ends with '%' to indicate percentage
|
||||
if (!valueStr.empty() && valueStr.back() == '%') {
|
||||
isPercentage = true;
|
||||
valueStr = valueStr.substr(0, valueStr.size() - 1);
|
||||
}
|
||||
|
||||
// Parse the numeric value
|
||||
try {
|
||||
value = static_cast<uint8_t>(std::stoi(valueStr));
|
||||
} catch (const std::exception&) {
|
||||
value = 0; // Default to 0 if parsing fails
|
||||
}
|
||||
|
||||
item.stats.push_back({statType, value, isPercentage});
|
||||
attribute = attribute->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ItemsConfig::parseCraftings(tinyxml2::XMLElement* itemElement, Item& item) {
|
||||
for (tinyxml2::XMLElement* craftElement = itemElement->FirstChildElement("crafting");
|
||||
craftElement != nullptr;
|
||||
craftElement = craftElement->NextSiblingElement("crafting")) {
|
||||
|
||||
ItemCrafting craft;
|
||||
craft.workbench = stringToWorkbenchType(getAttributeValue(craftElement, "workbench"));
|
||||
craft.craftingskill = stringToSkillType(getAttributeValue(craftElement, "craftingskill"));
|
||||
craft.checks = getAttributeValue(craftElement, "checks");
|
||||
|
||||
// Parse crafting items (format: "itemId-amount,itemId-amount,..."
|
||||
std::string craftingItemsStr = getAttributeValue(craftElement, "craftingitems");
|
||||
if (!craftingItemsStr.empty()) {
|
||||
std::stringstream ss(craftingItemsStr);
|
||||
std::string itemEntry;
|
||||
while (std::getline(ss, itemEntry, ',')) {
|
||||
// Remove any whitespace
|
||||
itemEntry.erase(itemEntry.begin(), std::find_if(itemEntry.begin(), itemEntry.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
itemEntry.erase(std::find_if(itemEntry.rbegin(), itemEntry.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), itemEntry.end());
|
||||
|
||||
// Parse "itemId-amount"
|
||||
size_t dashPos = itemEntry.find('-');
|
||||
if (dashPos != std::string::npos) {
|
||||
try {
|
||||
uint16_t itemId = static_cast<uint16_t>(std::stoi(itemEntry.substr(0, dashPos)));
|
||||
uint16_t amount = static_cast<uint16_t>(std::stoi(itemEntry.substr(dashPos + 1)));
|
||||
craft.craftingitems.push_back({itemId, amount});
|
||||
} catch (const std::exception&) {
|
||||
// Skip invalid entries
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.craftings.push_back(std::move(craft));
|
||||
}
|
||||
}
|
||||
|
||||
const Item* ItemsConfig::getItemById(int id) const {
|
||||
auto it = m_items.find(id);
|
||||
return (it != m_items.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
std::string ItemsConfig::getAttributeValue(tinyxml2::XMLElement* element, const char* attribute, const std::string& defaultValue) {
|
||||
const char* value = element->Attribute(attribute);
|
||||
return value ? std::string(value) : defaultValue;
|
||||
}
|
||||
|
||||
int ItemsConfig::getAttributeValueInt(tinyxml2::XMLElement* element, const char* attribute, int defaultValue) {
|
||||
int value = defaultValue;
|
||||
element->QueryIntAttribute(attribute, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool ItemsConfig::getAttributeValueBool(tinyxml2::XMLElement* element, const char* attribute, bool defaultValue) {
|
||||
bool value = defaultValue;
|
||||
element->QueryBoolAttribute(attribute, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace cursebreaker
|
||||
41
src/main.cpp
Normal file
41
src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "project_parser.h"
|
||||
#include "tree_builder.h"
|
||||
#include "assets/scene.hpp"
|
||||
#include "configs/items.hpp"
|
||||
|
||||
int main() {
|
||||
|
||||
ParseProject("../../CBAssets/_GameAssets/");
|
||||
|
||||
// build tree
|
||||
for (auto& scene : g_parsedProject.m_sceneAssets)
|
||||
{
|
||||
BuildTree(scene);
|
||||
}
|
||||
|
||||
// Test items parser
|
||||
std::cout << "Testing items parser..." << std::endl;
|
||||
auto& itemsConfig = cursebreaker::ItemsConfig::getInstance();
|
||||
bool success = itemsConfig.loadFromXML("../../CBAssets/Data/XMLs/Items/Items.xml");
|
||||
if (success) {
|
||||
std::cout << "Successfully loaded items XML!" << std::endl;
|
||||
|
||||
// Test getting a specific item
|
||||
const auto* item = itemsConfig.getItemById(150);
|
||||
if (item) {
|
||||
std::cout << "Found item: " << item->name << " (ID: " << item->id << ")" << std::endl;
|
||||
std::cout << "Description: " << item->description << std::endl;
|
||||
std::cout << "Level: " << item->level << ", Price: " << item->price << std::endl;
|
||||
} else {
|
||||
std::cout << "Item with ID 150 not found" << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "Failed to load items XML!" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
463
src/project_parser.cpp
Normal file
463
src/project_parser.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
#include "project_parser.h"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <regex>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include <ryml.hpp>
|
||||
#include <ryml_std.hpp>
|
||||
#include <c4/format.hpp>
|
||||
|
||||
|
||||
#include "assets/asset_base.hpp"
|
||||
#include "assets/game_object.hpp"
|
||||
#include "assets/transform.hpp"
|
||||
#include "assets/mesh_filter.hpp"
|
||||
#include "assets/prefab_instance.hpp"
|
||||
|
||||
constexpr uint32_t fnv1a_hash(const char* str, uint32_t hash = 2166136261u)
|
||||
{
|
||||
return (*str == '\0') ? hash : fnv1a_hash(str + 1, (hash ^ static_cast<uint32_t>(*str)) * 16777619u);
|
||||
}
|
||||
|
||||
uint32_t fnv1a_hash(c4::csubstr substr)
|
||||
{
|
||||
uint32_t hash = 2166136261u;
|
||||
for (const auto& c : substr)
|
||||
{
|
||||
hash = (hash ^ static_cast<uint32_t>(c)) * 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t fnv1a_hash(std::string_view substr)
|
||||
{
|
||||
uint32_t hash = 2166136261u;
|
||||
for (const auto& c : substr)
|
||||
{
|
||||
hash = (hash ^ static_cast<uint32_t>(c)) * 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
AssetGUID GetAssetGUIDFromFile(const std::filesystem::path& path)
|
||||
{
|
||||
/*
|
||||
// read file as yaml and get "guid"
|
||||
std::ifstream file(path);
|
||||
std::string content{ std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
|
||||
|
||||
try
|
||||
{
|
||||
ryml::Tree tree = ryml::parse_in_place(c4::substr{ content.data(), content.size() });
|
||||
ryml::ConstNodeRef root = tree.crootref();
|
||||
return ParseAssetGUID(root);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
std::cerr << "Error parsing asset GUID: " << e.what() << std::endl;
|
||||
return AssetGUID{};
|
||||
}
|
||||
*/
|
||||
|
||||
// regex match the GUID from the contents of the file
|
||||
static auto pattern = std::regex(R"(guid: (\w+))");
|
||||
std::smatch match{};
|
||||
std::ifstream file(path);
|
||||
std::string content{ std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
|
||||
if (std::regex_search(content, match, pattern))
|
||||
{
|
||||
assert(match[1].length() == 32);
|
||||
AssetGUID guid{ std::string_view{ &*match[1].first, static_cast<size_t>(match[1].length()) } };
|
||||
return guid;
|
||||
}
|
||||
return AssetGUID{};
|
||||
}
|
||||
|
||||
std::unordered_map<int64_t, ryml::Tree> ParseYamlFile(std::string& content, bool in_place)
|
||||
{
|
||||
std::unordered_map<int64_t, ryml::Tree> Data{};
|
||||
|
||||
std::string::size_type start = content.find("---", 0);
|
||||
std::string::size_type end = content.find("---", start + 1);
|
||||
|
||||
while (end != std::string::npos)
|
||||
{
|
||||
auto document = std::string_view(content.data() + start, content.data() + end);
|
||||
|
||||
// if the document contains "stripped", skip it
|
||||
auto str_view = std::string_view(document.data(), document.size());
|
||||
if (str_view.find("stripped") != std::string_view::npos)
|
||||
goto skip;
|
||||
|
||||
{
|
||||
auto secondLine = str_view.find('\n') + 1;
|
||||
auto classStr = std::string_view(document.data() + secondLine, str_view.find(':', secondLine) - secondLine);
|
||||
uint32_t classHash = fnv1a_hash(classStr);
|
||||
|
||||
constexpr std::array<uint32_t, 5> availableClasses = {
|
||||
fnv1a_hash("GameObject"),
|
||||
fnv1a_hash("Transform"),
|
||||
fnv1a_hash("MeshFilter"),
|
||||
fnv1a_hash("PrefabInstance"),
|
||||
fnv1a_hash("MonoBehaviour")
|
||||
};
|
||||
if (std::ranges::find(availableClasses, classHash) == availableClasses.end()) goto skip;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ryml::Tree tree{};
|
||||
|
||||
if (in_place)
|
||||
{
|
||||
tree = ryml::parse_in_place(c4::substr{ const_cast<char*>(document.data()), document.size() });
|
||||
}
|
||||
else
|
||||
{
|
||||
tree = ryml::parse_in_arena(c4::csubstr{ const_cast<char*>(document.data()), document.size() });
|
||||
}
|
||||
auto anchorData = tree.crootref().child(0).val_anchor();
|
||||
int64_t ID{ };
|
||||
auto [ptr, ec] = std::from_chars(anchorData.data(), anchorData.data() + anchorData.size(), ID);
|
||||
assert(ec == std::errc{});
|
||||
|
||||
Data.emplace(ID, std::move(tree));
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
std::cerr << "Error parsing scene asset: " << e.what() << std::endl;
|
||||
std::cerr << std::string_view(document.data(), document.size()) << std::endl;
|
||||
}
|
||||
|
||||
skip:;
|
||||
|
||||
start = end;
|
||||
end = content.find("---", start + 1);
|
||||
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
int64_t GetNewID(const SceneAsset& scene, int64_t lastID = 0)
|
||||
{
|
||||
for (;; ++lastID)
|
||||
{
|
||||
if (scene.IDtoAsset.find(lastID) == scene.IDtoAsset.end())
|
||||
return lastID;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MergeComponents(SceneAsset& scene, std::unordered_map<int64_t, int64_t>& idMap, std::vector<T>& to, const std::vector<T>& from, int64_t& lastID)
|
||||
{
|
||||
for (const auto& asset : from)
|
||||
{
|
||||
// create a copy of the asset
|
||||
auto assetCopy = asset;
|
||||
|
||||
// get a new ID
|
||||
lastID = GetNewID(scene, lastID);
|
||||
assetCopy.ID = lastID;
|
||||
|
||||
// register the new ID to the scene
|
||||
scene.IDtoAsset.emplace(assetCopy.ID, nullptr);
|
||||
|
||||
// add the asset to the scene
|
||||
to.emplace_back(assetCopy);
|
||||
}
|
||||
}
|
||||
|
||||
void MergeScenes(SceneAsset& to, const SceneAsset& from)
|
||||
{
|
||||
std::unordered_map<int64_t, int64_t> newIDMap{};
|
||||
|
||||
// randomize the IDs to avoid conflicts
|
||||
int64_t lastID = std::random_device{}();
|
||||
MergeComponents(to, newIDMap, to.gameObjects, from.gameObjects, lastID);
|
||||
MergeComponents(to, newIDMap, to.transforms, from.transforms, lastID);
|
||||
MergeComponents(to, newIDMap, to.meshFilters, from.meshFilters, lastID);
|
||||
|
||||
auto& IDMapLinked = reinterpret_cast<std::unordered_map<int64_t, AssetBase*>&>(newIDMap);
|
||||
for (auto& gameObject : to.gameObjects) LinkGameObject(IDMapLinked, gameObject);
|
||||
for (auto& transform : to.transforms) LinkTransform(IDMapLinked, transform);
|
||||
for (auto& meshFilter : to.meshFilters) LinkMeshFilter(IDMapLinked, meshFilter);
|
||||
}
|
||||
|
||||
void ParseScene(SceneAsset& asset, const std::unordered_map<int64_t, ryml::Tree>& documents);
|
||||
|
||||
void InstantiatePrefab(SceneAsset& parentScene, const ryml::Tree& instance)
|
||||
{
|
||||
auto instanceData = instance.crootref().child(0).child(0);
|
||||
|
||||
// parse the prebad UID
|
||||
AssetGUID prefabAssetGUID = ParseAssetGUID(instanceData["m_SourcePrefab"]);
|
||||
auto prefabAssetIt = g_parsedProject.m_prefabsMap.find(prefabAssetGUID);
|
||||
if (prefabAssetIt == g_parsedProject.m_prefabsMap.end())
|
||||
{
|
||||
std::cerr << "Prefab asset not found: " << std::endl;
|
||||
return;
|
||||
}
|
||||
const PrefabAsset& prefabAsset = *prefabAssetIt->second;
|
||||
auto prefabAssetTree = prefabAsset.Data;
|
||||
|
||||
// apply the modifications to the prefab asset tree
|
||||
const auto& overrides = instanceData["m_Modification"]["m_Modifications"];
|
||||
for (auto override : overrides)
|
||||
{
|
||||
int64_t ref = reinterpret_cast<int64_t>(ParseAssetRef<void>(override["target"]));
|
||||
auto prefabAssetTreeIt = prefabAssetTree.find(ref);
|
||||
if (prefabAssetTreeIt == prefabAssetTree.end())
|
||||
{
|
||||
std::cerr << "Prefab asset tree not found: " << ref << std::endl;
|
||||
continue;
|
||||
}
|
||||
auto& targetTree = prefabAssetTreeIt->second;
|
||||
|
||||
const auto propertyPath{ override["propertyPath"].val() };
|
||||
const auto& overrideValue = override["value"];
|
||||
|
||||
// Use lookup_path_or_modify to set the value at the property path
|
||||
// Start from the root of the document (child(0) is the document root)
|
||||
auto rootId = targetTree.crootref().child(0).id();
|
||||
targetTree.lookup_path_or_modify(&instance, overrideValue.id(), propertyPath, rootId);
|
||||
}
|
||||
|
||||
// parse the prefab asset tree into a scene
|
||||
SceneAsset scene{};
|
||||
ParseScene(scene, prefabAssetTree);
|
||||
|
||||
// get the index of the added transform
|
||||
size_t addedTransformIndex = scene.transforms.size();
|
||||
|
||||
// merge the scenes
|
||||
MergeScenes(parentScene, scene);
|
||||
|
||||
// find the parent transform and add a the instantiated prebab to the children
|
||||
auto parentID = ParseAssetRef<TransformAsset>(instanceData["m_TransformParent"]);
|
||||
scene.transforms[addedTransformIndex].Parent = parentID;
|
||||
auto parentTransform = std::find_if(scene.transforms.begin(), scene.transforms.end(), [parentID](const TransformAsset& asset)
|
||||
{
|
||||
return asset.ID == reinterpret_cast<int64_t>(parentID);
|
||||
});
|
||||
if (parentTransform != scene.transforms.end())
|
||||
{
|
||||
parentTransform->Children.push_back(reinterpret_cast<TransformAsset*>(scene.transforms[addedTransformIndex].ID));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LinkAssets(SceneAsset& sceneAsset)
|
||||
{
|
||||
std::unordered_map<int64_t, AssetBase*> assetsMap;
|
||||
|
||||
for (auto& gameObject : sceneAsset.gameObjects) assetsMap.emplace(gameObject.ID, &gameObject);
|
||||
for (auto& transform : sceneAsset.transforms) assetsMap.emplace(transform.ID, &transform);
|
||||
for (auto& meshFilter : sceneAsset.meshFilters) assetsMap.emplace(meshFilter.ID, &meshFilter);
|
||||
|
||||
for (auto& gameObject : sceneAsset.gameObjects) LinkGameObject(assetsMap, gameObject);
|
||||
for (auto& transform : sceneAsset.transforms) LinkTransform(assetsMap, transform);
|
||||
for (auto& meshFilter : sceneAsset.meshFilters) LinkMeshFilter(assetsMap, meshFilter);
|
||||
}
|
||||
|
||||
void ParseScene(SceneAsset& asset, const std::unordered_map<int64_t, ryml::Tree>& documents)
|
||||
{
|
||||
for (auto& [ID, tree] : documents)
|
||||
{
|
||||
try
|
||||
{
|
||||
ryml::ConstNodeRef root = tree.crootref();
|
||||
|
||||
// Each document should have one root node
|
||||
if (root.num_children() > 0)
|
||||
{
|
||||
auto node = root.child(0).child(0); // The document root
|
||||
auto assetType = node.key();
|
||||
|
||||
switch (fnv1a_hash(assetType))
|
||||
{
|
||||
case fnv1a_hash("GameObject"): asset.gameObjects.emplace_back(ParseGameObject(node)).ID = ID; break;
|
||||
case fnv1a_hash("Transform"): asset.transforms.emplace_back(ParseTransform(node)).ID = ID; break;
|
||||
case fnv1a_hash("MeshFilter"): asset.meshFilters.emplace_back(ParseMeshFilter(node)).ID = ID; break;
|
||||
case fnv1a_hash("PrefabInstance"):
|
||||
{
|
||||
InstantiatePrefab(asset, tree);
|
||||
break;
|
||||
}
|
||||
case fnv1a_hash("MonoBehaviour"):
|
||||
{
|
||||
auto scriptGuid = ParseAssetGUID(node["m_Script"]);
|
||||
auto it = g_parsedProject.m_scriptToClassHash.find(scriptGuid);
|
||||
if (it != g_parsedProject.m_scriptToClassHash.end())
|
||||
{
|
||||
switch (it->second)
|
||||
{
|
||||
case fnv1a_hash("Interactable_Resource"):
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
std::cerr << assetType << '\n';
|
||||
}
|
||||
|
||||
asset.IDtoAsset.emplace(ID, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
LinkAssets(asset);
|
||||
}
|
||||
|
||||
SceneAsset ParseSceneAsset(const AssetPath& path)
|
||||
{
|
||||
SceneAsset sceneAsset;
|
||||
sceneAsset.path = path.path;
|
||||
sceneAsset.name = path.path.stem().string();
|
||||
|
||||
// read the file
|
||||
std::ifstream file(path.path);
|
||||
std::string content{ std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
|
||||
|
||||
auto objects = ParseYamlFile(content, true);
|
||||
|
||||
ParseScene(sceneAsset, objects);
|
||||
|
||||
return sceneAsset;
|
||||
}
|
||||
|
||||
|
||||
std::vector<SceneAsset> ParseScenes(const std::vector<AssetPath>& sceneFiles)
|
||||
{
|
||||
std::vector<SceneAsset> sceneAssets;
|
||||
for (const auto& sceneFile : sceneFiles)
|
||||
{
|
||||
sceneAssets.emplace_back(ParseSceneAsset(sceneFile));
|
||||
std::cout << "Parsed: " << sceneFile.path.filename() << '\n';
|
||||
}
|
||||
return sceneAssets;
|
||||
}
|
||||
|
||||
PrefabAsset ParsePrefabAsset(const AssetPath& path)
|
||||
{
|
||||
PrefabAsset prefabAsset;
|
||||
prefabAsset.path = path.path;
|
||||
prefabAsset.name = path.path.stem().string();
|
||||
|
||||
{
|
||||
// read the file
|
||||
std::ifstream file(path.path);
|
||||
std::string content{ std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
|
||||
|
||||
prefabAsset.Data = ParseYamlFile(content, false);
|
||||
}
|
||||
|
||||
return prefabAsset;
|
||||
}
|
||||
|
||||
std::vector<PrefabAsset> ParsePrefabs(const std::vector<AssetPath>& prefabFiles, std::unordered_map<AssetGUID, PrefabAsset*>& prefabMap)
|
||||
{
|
||||
std::vector<PrefabAsset> prefabAssets;
|
||||
for (const auto& prefabFile : prefabFiles)
|
||||
{
|
||||
prefabAssets.emplace_back(ParsePrefabAsset(prefabFile));
|
||||
}
|
||||
|
||||
prefabMap.reserve(prefabAssets.size());
|
||||
for (size_t i = 0; i < prefabAssets.size(); ++i)
|
||||
{
|
||||
prefabMap.emplace(prefabFiles[i].GUID, &prefabAssets[i]);
|
||||
}
|
||||
return prefabAssets;
|
||||
}
|
||||
|
||||
void ParseDirectory(ParsedProject& project)
|
||||
{
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(project.m_projectRoot))
|
||||
{
|
||||
std::string extension = entry.path().extension().string();
|
||||
|
||||
if (!entry.is_directory())
|
||||
{
|
||||
if (extension == ".unity")
|
||||
{
|
||||
project.m_sceneFiles.push_back(AssetPath{ entry.path(), GetAssetGUIDFromFile(entry.path().string() + ".meta") });
|
||||
}
|
||||
else if (extension == ".prefab")
|
||||
{
|
||||
project.m_prefabFiles.push_back(AssetPath{ entry.path(), GetAssetGUIDFromFile(entry.path().string() + ".meta") });
|
||||
}
|
||||
else if (extension == ".cs")
|
||||
{
|
||||
auto guid = GetAssetGUIDFromFile(entry.path().string() + ".meta");
|
||||
project.m_scriptFiles.push_back(AssetPath{ entry.path(), guid });
|
||||
project.m_scriptToClassHash.emplace(guid, fnv1a_hash(entry.path().filename().string()));
|
||||
}
|
||||
continue;
|
||||
if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".bmp" || extension == ".tiff" || extension == ".webp")
|
||||
{
|
||||
project.m_imageFiles.push_back(AssetPath{ entry.path(), GetAssetGUIDFromFile(entry.path().string() + ".meta") });
|
||||
}
|
||||
else if (extension == ".fbx" || extension == ".obj" || extension == ".glb" || extension == ".gltf")
|
||||
{
|
||||
project.m_meshFiles.push_back(AssetPath{ entry.path(), GetAssetGUIDFromFile(entry.path().string() + ".meta") });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParsedProject g_parsedProject;
|
||||
|
||||
void ParseProject(const std::filesystem::path& projectRoot)
|
||||
{
|
||||
g_parsedProject = ParsedProject{};
|
||||
ParsedProject& project = g_parsedProject;
|
||||
project.m_projectRoot = projectRoot;
|
||||
|
||||
ParseDirectory(project);
|
||||
|
||||
auto tilesFilesFilter = std::views::filter([](const AssetPath& path)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
static auto pattern = std::regex(R"(Tiles\/10_3)");
|
||||
#else
|
||||
static auto pattern = std::regex(R"(Tiles\/\d+_\d+)");
|
||||
#endif
|
||||
return std::regex_search(path.path.generic_string(), pattern);
|
||||
});
|
||||
auto tilesFiles = project.m_sceneFiles | tilesFilesFilter;
|
||||
|
||||
auto prefabFilter = std::views::filter([](const AssetPath& path)
|
||||
{
|
||||
constexpr std::string_view prefabPath = "_GameAssets/Prefabs";
|
||||
return path.path.generic_string().find(prefabPath) != std::string::npos;
|
||||
});
|
||||
auto prefabFiles = project.m_prefabFiles | prefabFilter;
|
||||
|
||||
project.m_prefabAssets = ParsePrefabs(std::vector<AssetPath>(prefabFiles.begin(), prefabFiles.end()), project.m_prefabsMap);
|
||||
|
||||
project.m_sceneAssets = ParseScenes(std::vector<AssetPath>(tilesFiles.begin(), tilesFiles.end()));
|
||||
}
|
||||
|
||||
54
src/tree_builder.cpp
Normal file
54
src/tree_builder.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "tree_builder.h"
|
||||
#include "assets/scene.hpp"
|
||||
#include "assets/transform.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
glm::mat4 composeMatrix(const glm::vec3& pos, const glm::vec3& scale, const glm::quat& rotation) {
|
||||
// Create translation matrix
|
||||
glm::mat4 translationMatrix = glm::translate(glm::mat4(1.0f), pos);
|
||||
|
||||
// Convert quaternion to rotation matrix
|
||||
glm::mat4 rotationMatrix = glm::mat4_cast(rotation);
|
||||
|
||||
// Create scale matrix
|
||||
glm::mat4 scaleMatrix = glm::scale(glm::mat4(1.0f), scale);
|
||||
|
||||
// Combine: translation * rotation * scale
|
||||
return translationMatrix * rotationMatrix * scaleMatrix;
|
||||
}
|
||||
|
||||
void BuildTreeHelper(TransformAsset& transform, const glm::mat4& parentMatrix)
|
||||
{
|
||||
for (auto& child : transform.Children)
|
||||
{
|
||||
auto childMatrix = composeMatrix(transform.Position, transform.Scale, transform.Rotation);
|
||||
transform.GlobalMatrix = parentMatrix * childMatrix;
|
||||
|
||||
BuildTreeHelper(*child, transform.GlobalMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildTree(SceneAsset& sceneAsset)
|
||||
{
|
||||
// find transforms with no parent
|
||||
std::vector<TransformAsset*> rootTransforms;
|
||||
|
||||
for (auto& transform : sceneAsset.transforms)
|
||||
{
|
||||
if (transform.Parent == nullptr)
|
||||
{
|
||||
rootTransforms.push_back(&transform);
|
||||
}
|
||||
}
|
||||
|
||||
// build tree
|
||||
for (auto rootTransform : rootTransforms)
|
||||
{
|
||||
BuildTreeHelper(*rootTransform, glm::mat4(1.0f));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user