diff --git a/CMakeLists.txt b/CMakeLists.txt index 519d28e..4f3a00d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,11 +47,17 @@ FetchContent_Declare( GIT_TAG master ) +FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.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 sqlitecpp rapidjson) +FetchContent_MakeAvailable(rapidyaml glm tinyxml2 sqlitecpp rapidjson nlohmann_json) # Create the main executable add_executable(${PROJECT_NAME} ${SOURCES}) @@ -60,6 +66,7 @@ add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC ryml::ryml) target_link_libraries(${PROJECT_NAME} PUBLIC tinyxml2::tinyxml2) target_link_libraries(${PROJECT_NAME} PUBLIC SQLiteCpp) +target_link_libraries(${PROJECT_NAME} PUBLIC nlohmann_json::nlohmann_json) # Add include directories for rapidyaml target_include_directories(${PROJECT_NAME} PRIVATE ${rapidyaml_SOURCE_DIR}/src) diff --git a/include/database_generation/populate_configs.h b/include/database_generation/populate_configs.h new file mode 100644 index 0000000..a17dbe5 --- /dev/null +++ b/include/database_generation/populate_configs.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +#include "configs/achievements.h" +#include "configs/harvestables.h" +#include "configs/items.h" +#include "configs/loot.h" +#include "configs/npcs.h" +#include "configs/shops.h" + +namespace cursebreaker { + +// Items +void CreateItemTables(SQLite::Database& db); +void PopulateItemEntry(SQLite::Database& db, const Item& item); +void PopulateItemDB(SQLite::Database& db, const std::vector& items); + +// Achievements +void CreateAchievementTables(SQLite::Database& db); +void PopulateAchievementEntry(SQLite::Database& db, const Achievement& achievement); +void PopulateAchievementDB(SQLite::Database& db, const std::vector& achievements); + +// Harvestables +void CreateHarvestableTables(SQLite::Database& db); +void PopulateHarvestableEntry(SQLite::Database& db, const Harvestable& harvestable); +void PopulateHarvestableDB(SQLite::Database& db, const std::vector& harvestables); + +// Loot +void CreateLootTables(SQLite::Database& db); +void PopulateLootTableEntry(SQLite::Database& db, const LootTable& lootTable); +void PopulateLootDB(SQLite::Database& db, const std::vector& lootTables); + +// NPCs +void CreateNPCTables(SQLite::Database& db); +void PopulateNPCEntry(SQLite::Database& db, const NPC& npc); +void PopulateNPCDB(SQLite::Database& db, const std::vector& npcs); + +// Shops +void CreateShopTables(SQLite::Database& db); +void PopulateShopEntry(SQLite::Database& db, const Shop& shop); +void PopulateShopDB(SQLite::Database& db, const std::vector& shops); + +} // namespace cursebreaker \ No newline at end of file diff --git a/src/database_generation/populate_configs.cpp b/src/database_generation/populate_configs.cpp new file mode 100644 index 0000000..3f0ddee --- /dev/null +++ b/src/database_generation/populate_configs.cpp @@ -0,0 +1,506 @@ +#include "database_generation/populate_configs.h" + +#include +#include + +namespace cursebreaker { + +void CreateItemTables(SQLite::Database& db) { + // Main items table + std::string createItemsTableSQL = R"( +CREATE TABLE IF NOT EXISTS items ( + id INTEGER PRIMARY KEY, + price INTEGER, + abilityid INTEGER, + learnabilityid INTEGER, + skill INTEGER, + category INTEGER, + slot INTEGER, + tool INTEGER, + generation INTEGER, + level INTEGER, + foodlevel INTEGER, + foodamount INTEGER, + foodfrequency INTEGER, + foodtime INTEGER, + maxstack INTEGER, + book INTEGER, + stackable BOOLEAN, + twohanded BOOLEAN, + undroppable BOOLEAN, + hidemilestone BOOLEAN, + name TEXT, + description TEXT, + comment TEXT +); +)"; + + // Item stats table + std::string createItemStatsTableSQL = R"( +CREATE TABLE IF NOT EXISTS item_stats ( + item_id INTEGER, + stat_type INTEGER, + value INTEGER, + is_percentage BOOLEAN, + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE +); +)"; + + // Item craftings table + std::string createItemCraftingsTableSQL = R"( +CREATE TABLE IF NOT EXISTS item_craftings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + item_id INTEGER, + workbench INTEGER, + crafting_skill INTEGER, + checks TEXT, + FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE +); +)"; + + // Crafting items table + std::string createCraftingItemsTableSQL = R"( +CREATE TABLE IF NOT EXISTS crafting_items ( + crafting_id INTEGER, + required_item_id INTEGER, + amount INTEGER, + FOREIGN KEY (crafting_id) REFERENCES item_craftings(id) ON DELETE CASCADE +); +)"; + + db.exec(createItemsTableSQL); + db.exec(createItemStatsTableSQL);// TODO: add indices + db.exec(createItemCraftingsTableSQL); + db.exec(createCraftingItemsTableSQL); + std::cout << "Created items-related tables." << std::endl; +} + +void PopulateItemEntry(SQLite::Database& db, const Item& item) { + // Insert into main items table + SQLite::Statement insertItem(db, R"( +INSERT INTO items ( + id, price, abilityid, learnabilityid, skill, category, slot, tool, generation, + level, foodlevel, foodamount, foodfrequency, foodtime, maxstack, book, + stackable, twohanded, undroppable, hidemilestone, name, description, comment +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); +)"); + + insertItem.bind(1, item.id); + insertItem.bind(2, item.price); + insertItem.bind(3, item.abilityid); + insertItem.bind(4, item.learnabilityid); + insertItem.bind(5, static_cast(item.skill)); + insertItem.bind(6, static_cast(item.category)); + insertItem.bind(7, static_cast(item.slot)); + insertItem.bind(8, static_cast(item.tool)); + insertItem.bind(9, static_cast(item.generation)); + insertItem.bind(10, item.level); + insertItem.bind(11, item.foodlevel); + insertItem.bind(12, item.foodamount); + insertItem.bind(13, item.foodfrequency); + insertItem.bind(14, item.foodtime); + insertItem.bind(15, item.maxstack); + insertItem.bind(16, item.book); + insertItem.bind(17, item.stackable); + insertItem.bind(18, item.twohanded); + insertItem.bind(19, item.undroppable); + insertItem.bind(20, item.hidemilestone); + insertItem.bind(21, item.name); + insertItem.bind(22, item.description); + insertItem.bind(23, item.comment); + + insertItem.exec(); + + // Insert stats + SQLite::Statement insertStat(db, "INSERT INTO item_stats (item_id, stat_type, value, is_percentage) VALUES (?, ?, ?, ?);"); + for (const auto& stat : item.stats) { + insertStat.bind(1, item.id); + insertStat.bind(2, static_cast(stat.type)); + insertStat.bind(3, stat.value); + insertStat.bind(4, stat.isPercentage); + insertStat.exec(); + insertStat.reset(); + } + + // Insert craftings + SQLite::Statement insertCrafting(db, "INSERT INTO item_craftings (item_id, workbench, crafting_skill, checks) VALUES (?, ?, ?, ?);"); + SQLite::Statement insertCraftingItem(db, "INSERT INTO crafting_items (crafting_id, required_item_id, amount) VALUES (?, ?, ?);"); + for (const auto& crafting : item.craftings) { + insertCrafting.bind(1, item.id); + insertCrafting.bind(2, static_cast(crafting.workbench)); + insertCrafting.bind(3, static_cast(crafting.craftingskill)); + insertCrafting.bind(4, crafting.checks); + insertCrafting.exec(); + long long craftingId = db.getLastInsertRowid(); + insertCrafting.reset(); + + for (const auto& reqItem : crafting.craftingitems) { + insertCraftingItem.bind(1, static_cast(craftingId)); + insertCraftingItem.bind(2, reqItem.itemId); + insertCraftingItem.bind(3, reqItem.amount); + insertCraftingItem.exec(); + insertCraftingItem.reset(); + } + } +} + +void PopulateItemDB(SQLite::Database& db, const std::vector& items) { + CreateItemTables(db); + for (const auto& item : items) { + PopulateItemEntry(db, item); + } + std::cout << "Populated " << items.size() << " items into the database." << std::endl; +} + +void CreateAchievementTables(SQLite::Database& db) { + // Main achievements table + std::string createAchievementsTableSQL = R"( +CREATE TABLE IF NOT EXISTS achievements ( + id INTEGER PRIMARY KEY, + points INTEGER, + category INTEGER, + name TEXT, + description TEXT, + icon TEXT +); +)"; + + // Achievement requirements table + std::string createAchievementRequirementsTableSQL = R"( +CREATE TABLE IF NOT EXISTS achievement_requirements ( + achievement_id INTEGER, + action INTEGER, + target_id INTEGER, + count INTEGER, + description TEXT, + FOREIGN KEY (achievement_id) REFERENCES achievements(id) ON DELETE CASCADE +); +)"; + + db.exec(createAchievementsTableSQL); + db.exec(createAchievementRequirementsTableSQL); + std::cout << "Created achievement-related tables." << std::endl; +} + +void PopulateAchievementEntry(SQLite::Database& db, const Achievement& achievement) { + // Insert into main achievements table + SQLite::Statement insertAchievement(db, R"( +INSERT INTO achievements ( + id, points, category, name, description, icon +) VALUES (?, ?, ?, ?, ?, ?); +)"); + + insertAchievement.bind(1, achievement.id); + insertAchievement.bind(2, achievement.points); + insertAchievement.bind(3, achievement.category); + insertAchievement.bind(4, achievement.name); + insertAchievement.bind(5, achievement.description); + insertAchievement.bind(6, achievement.icon); + + insertAchievement.exec(); + + // Insert requirements + SQLite::Statement insertRequirement(db, "INSERT INTO achievement_requirements (achievement_id, action, target_id, count, description) VALUES (?, ?, ?, ?, ?);"); + for (const auto& req : achievement.requirements) { + insertRequirement.bind(1, achievement.id); + insertRequirement.bind(2, static_cast(req.action)); + insertRequirement.bind(3, req.targetId); + insertRequirement.bind(4, req.count); + insertRequirement.bind(5, req.description); + insertRequirement.exec(); + insertRequirement.reset(); + } +} + +void PopulateAchievementDB(SQLite::Database& db, const std::vector& achievements) { + CreateAchievementTables(db); + for (const auto& achievement : achievements) { + PopulateAchievementEntry(db, achievement); + } + std::cout << "Populated " << achievements.size() << " achievements into the database." << std::endl; +} + +void CreateHarvestableTables(SQLite::Database& db) { + // Main harvestables table + std::string createHarvestablesTableSQL = R"( +CREATE TABLE IF NOT EXISTS harvestables ( + id INTEGER PRIMARY KEY, + level INTEGER, + respawn_time INTEGER, + skill INTEGER, + name TEXT, + description TEXT +); +)"; + + // Harvestable loot table + std::string createHarvestableLootTableSQL = R"( +CREATE TABLE IF NOT EXISTS harvestable_loot ( + harvestable_id INTEGER, + item_id INTEGER, + amount INTEGER, + chance INTEGER, + FOREIGN KEY (harvestable_id) REFERENCES harvestables(id) ON DELETE CASCADE +); +)"; + + db.exec(createHarvestablesTableSQL); + db.exec(createHarvestableLootTableSQL); + std::cout << "Created harvestable-related tables." << std::endl; +} + +void PopulateHarvestableEntry(SQLite::Database& db, const Harvestable& harvestable) { + // Insert into main harvestables table + SQLite::Statement insertHarvestable(db, R"( +INSERT INTO harvestables ( + id, level, respawn_time, skill, name, description +) VALUES (?, ?, ?, ?, ?, ?); +)"); + + insertHarvestable.bind(1, harvestable.id); + insertHarvestable.bind(2, harvestable.level); + insertHarvestable.bind(3, harvestable.respawnTime); + insertHarvestable.bind(4, static_cast(harvestable.skill)); + insertHarvestable.bind(5, harvestable.name); + insertHarvestable.bind(6, harvestable.description); + + insertHarvestable.exec(); + + // Insert loot + SQLite::Statement insertLoot(db, "INSERT INTO harvestable_loot (harvestable_id, item_id, amount, chance) VALUES (?, ?, ?, ?);"); + for (const auto& entry : harvestable.loot) { + insertLoot.bind(1, harvestable.id); + insertLoot.bind(2, entry.itemId); + insertLoot.bind(3, entry.amount); + insertLoot.bind(4, entry.chance); + insertLoot.exec(); + insertLoot.reset(); + } +} + +void PopulateHarvestableDB(SQLite::Database& db, const std::vector& harvestables) { + CreateHarvestableTables(db); + for (const auto& harvestable : harvestables) { + PopulateHarvestableEntry(db, harvestable); + } + std::cout << "Populated " << harvestables.size() << " harvestables into the database." << std::endl; +} + +void CreateLootTables(SQLite::Database& db) { + // Main loot_tables table + std::string createLootTablesTableSQL = R"( +CREATE TABLE IF NOT EXISTS loot_tables ( + id INTEGER PRIMARY KEY, + name TEXT, + description TEXT +); +)"; + + // Loot entries table + std::string createLootEntriesTableSQL = R"( +CREATE TABLE IF NOT EXISTS loot_entries ( + loot_table_id INTEGER, + item_id INTEGER, + min_amount INTEGER, + max_amount INTEGER, + chance INTEGER, + level INTEGER, + FOREIGN KEY (loot_table_id) REFERENCES loot_tables(id) ON DELETE CASCADE +); +)"; + + db.exec(createLootTablesTableSQL); + db.exec(createLootEntriesTableSQL); + std::cout << "Created loot-related tables." << std::endl; +} + +void PopulateLootTableEntry(SQLite::Database& db, const LootTable& lootTable) { + // Insert into main loot_tables table + SQLite::Statement insertLootTable(db, R"( +INSERT INTO loot_tables ( + id, name, description +) VALUES (?, ?, ?); +)"); + + insertLootTable.bind(1, lootTable.id); + insertLootTable.bind(2, lootTable.name); + insertLootTable.bind(3, lootTable.description); + + insertLootTable.exec(); + + // Insert entries + SQLite::Statement insertEntry(db, "INSERT INTO loot_entries (loot_table_id, item_id, min_amount, max_amount, chance, level) VALUES (?, ?, ?, ?, ?, ?);"); + for (const auto& entry : lootTable.entries) { + insertEntry.bind(1, lootTable.id); + insertEntry.bind(2, entry.itemId); + insertEntry.bind(3, entry.minAmount); + insertEntry.bind(4, entry.maxAmount); + insertEntry.bind(5, entry.chance); + insertEntry.bind(6, entry.level); + insertEntry.exec(); + insertEntry.reset(); + } +} + +void PopulateLootDB(SQLite::Database& db, const std::vector& lootTables) { + CreateLootTables(db); + for (const auto& lootTable : lootTables) { + PopulateLootTableEntry(db, lootTable); + } + std::cout << "Populated " << lootTables.size() << " loot tables into the database." << std::endl; +} + +void CreateNPCTables(SQLite::Database& db) { + // Main npcs table + std::string createNPCsTableSQL = R"( +CREATE TABLE IF NOT EXISTS npcs ( + id INTEGER PRIMARY KEY, + level INTEGER, + health INTEGER, + mana INTEGER, + experience INTEGER, + loot_table_id INTEGER, + name TEXT, + description TEXT, + faction TEXT +); +)"; + + // NPC stats table + std::string createNPCStatsTableSQL = R"( +CREATE TABLE IF NOT EXISTS npc_stats ( + npc_id INTEGER, + stat_type INTEGER, + value INTEGER, + FOREIGN KEY (npc_id) REFERENCES npcs(id) ON DELETE CASCADE +); +)"; + + // NPC loot items table + std::string createNPCLootItemsTableSQL = R"( +CREATE TABLE IF NOT EXISTS npc_loot_items ( + npc_id INTEGER, + item_id INTEGER, + FOREIGN KEY (npc_id) REFERENCES npcs(id) ON DELETE CASCADE +); +)"; + + db.exec(createNPCsTableSQL); + db.exec(createNPCStatsTableSQL); + db.exec(createNPCLootItemsTableSQL); + std::cout << "Created NPC-related tables." << std::endl; +} + +void PopulateNPCEntry(SQLite::Database& db, const NPC& npc) { + // Insert into main npcs table + SQLite::Statement insertNPC(db, R"( +INSERT INTO npcs ( + id, level, health, mana, experience, loot_table_id, name, description, faction +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); +)"); + + insertNPC.bind(1, npc.id); + insertNPC.bind(2, npc.level); + insertNPC.bind(3, npc.health); + insertNPC.bind(4, npc.mana); + insertNPC.bind(5, npc.experience); + insertNPC.bind(6, npc.lootTableId); + insertNPC.bind(7, npc.name); + insertNPC.bind(8, npc.description); + insertNPC.bind(9, npc.faction); + + insertNPC.exec(); + + // Insert stats + SQLite::Statement insertStat(db, "INSERT INTO npc_stats (npc_id, stat_type, value) VALUES (?, ?, ?);"); + for (const auto& stat : npc.stats) { + insertStat.bind(1, npc.id); + insertStat.bind(2, static_cast(stat.type)); + insertStat.bind(3, stat.value); + insertStat.exec(); + insertStat.reset(); + } + + // Insert loot items + SQLite::Statement insertLootItem(db, "INSERT INTO npc_loot_items (npc_id, item_id) VALUES (?, ?);"); + for (const auto& itemId : npc.lootItems) { + insertLootItem.bind(1, npc.id); + insertLootItem.bind(2, itemId); + insertLootItem.exec(); + insertLootItem.reset(); + } +} + +void PopulateNPCDB(SQLite::Database& db, const std::vector& npcs) { + CreateNPCTables(db); + for (const auto& npc : npcs) { + PopulateNPCEntry(db, npc); + } + std::cout << "Populated " << npcs.size() << " NPCs into the database." << std::endl; +} + +void CreateShopTables(SQLite::Database& db) { + // Main shops table + std::string createShopsTableSQL = R"( +CREATE TABLE IF NOT EXISTS shops ( + id INTEGER PRIMARY KEY, + name TEXT, + description TEXT, + location TEXT +); +)"; + + // Shop inventory table + std::string createShopInventoryTableSQL = R"( +CREATE TABLE IF NOT EXISTS shop_inventory ( + shop_id INTEGER, + item_id INTEGER, + price INTEGER, + stock INTEGER, + level_required INTEGER, + FOREIGN KEY (shop_id) REFERENCES shops(id) ON DELETE CASCADE +); +)"; + + db.exec(createShopsTableSQL); + db.exec(createShopInventoryTableSQL); + std::cout << "Created shop-related tables." << std::endl; +} + +void PopulateShopEntry(SQLite::Database& db, const Shop& shop) { + // Insert into main shops table + SQLite::Statement insertShop(db, R"( +INSERT INTO shops ( + id, name, description, location +) VALUES (?, ?, ?, ?); +)"); + + insertShop.bind(1, shop.id); + insertShop.bind(2, shop.name); + insertShop.bind(3, shop.description); + insertShop.bind(4, shop.location); + + insertShop.exec(); + + // Insert inventory + SQLite::Statement insertItem(db, "INSERT INTO shop_inventory (shop_id, item_id, price, stock, level_required) VALUES (?, ?, ?, ?, ?);"); + for (const auto& item : shop.inventory) { + insertItem.bind(1, shop.id); + insertItem.bind(2, item.itemId); + insertItem.bind(3, item.price); + insertItem.bind(4, item.stock); + insertItem.bind(5, item.levelRequired); + insertItem.exec(); + insertItem.reset(); + } +} + +void PopulateShopDB(SQLite::Database& db, const std::vector& shops) { + CreateShopTables(db); + for (const auto& shop : shops) { + PopulateShopEntry(db, shop); + } + std::cout << "Populated " << shops.size() << " shops into the database." << std::endl; +} + +} // namespace cursebreaker \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index a7af5a7..8be651a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,19 @@ #include #include +#include +#include #include "project_parser.h" #include "tree_builder.h" #include "assets/scene.hpp" #include "configs/items.h" +#include "configs/achievements.h" +#include "configs/harvestables.h" +#include "configs/loot.h" +#include "configs/npcs.h" +#include "configs/shops.h" +#include "database_generation/populate_configs.h" using namespace cursebreaker; @@ -19,5 +27,70 @@ int main() { BuildTree(scene); } + // Load configs from XML + std::string configPath = "../../CBAssets/_GameAssets/"; + loadItemsFromXML(configPath + "items.xml"); + loadAchievementsFromXML(configPath + "achievements.xml"); + loadHarvestablesFromXML(configPath + "harvestables.xml"); + loadLootTablesFromXML(configPath + "loot.xml"); + loadNPCsFromXML(configPath + "npcs.xml"); + loadShopsFromXML(configPath + "shops.xml"); + + // Create in-memory database + SQLite::Database memDB(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + + // Populate database with configs + std::vector itemsVec; + for (const auto& pair : getAllItems()) { + itemsVec.push_back(pair.second); + } + PopulateItemDB(memDB, itemsVec); + + std::vector achievementsVec; + for (const auto& pair : getAllAchievements()) { + achievementsVec.push_back(pair.second); + } + PopulateAchievementDB(memDB, achievementsVec); + + std::vector harvestablesVec; + for (const auto& pair : getAllHarvestables()) { + harvestablesVec.push_back(pair.second); + } + PopulateHarvestableDB(memDB, harvestablesVec); + + std::vector lootVec; + for (const auto& pair : getAllLootTables()) { + lootVec.push_back(pair.second); + } + PopulateLootDB(memDB, lootVec); + + std::vector npcsVec; + for (const auto& pair : getAllNPCs()) { + npcsVec.push_back(pair.second); + } + PopulateNPCDB(memDB, npcsVec); + + std::vector shopsVec; + for (const auto& pair : getAllShops()) { + shopsVec.push_back(pair.second); + } + PopulateShopDB(memDB, shopsVec); + + // Save to file + { + SQLite::Database fileDB("cursebreaker.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + sqlite3* srcHandle = memDB.getHandle(); + sqlite3* dstHandle = fileDB.getHandle(); + sqlite3_backup* backup = sqlite3_backup_init(dstHandle, "main", srcHandle, "main"); + if (backup) { + sqlite3_backup_step(backup, -1); + sqlite3_backup_finish(backup); + } else { + std::cerr << "Failed to initialize backup" << std::endl; + } + } + + std::cout << "Database created and saved to cursebreaker.db" << std::endl; + return 0; } diff --git a/src/project_parser.cpp b/src/project_parser.cpp index 8b509df..5520c16 100644 --- a/src/project_parser.cpp +++ b/src/project_parser.cpp @@ -462,4 +462,4 @@ void ParseProject(const std::filesystem::path& projectRoot) project.m_prefabAssets = ParsePrefabs(std::vector(prefabFiles.begin(), prefabFiles.end()), project.m_prefabsMap); project.m_sceneAssets = ParseScenes(std::vector(tilesFiles.begin(), tilesFiles.end())); -} \ No newline at end of file +}} // namespace cursebreaker