initial commit

This commit is contained in:
cdemeyer-teachx
2025-11-12 06:29:59 +09:00
commit 998313be3c
17 changed files with 1624 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
CBAssets.rar
CBAssets

56
CMakeLists.txt Normal file
View 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)

View 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())};
}

View 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;
};

View 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();
}
}
}

View 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);
}

View 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
View 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;
};

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
#pragma once
#include "assets/scene.hpp"
void BuildTree(SceneAsset& sceneAsset);

334
src/configs/items.cpp Normal file
View 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
View 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
View 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
View 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));
}
}