This commit is contained in:
cdemeyer-teachx
2025-11-14 17:39:40 +09:00
parent ff1b4a93b3
commit 121eb8eb25
5 changed files with 634 additions and 2 deletions

View File

@@ -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)

View File

@@ -0,0 +1,46 @@
#pragma once
#include <vector>
#include <SQLiteCpp/SQLiteCpp.h>
#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<Item>& items);
// Achievements
void CreateAchievementTables(SQLite::Database& db);
void PopulateAchievementEntry(SQLite::Database& db, const Achievement& achievement);
void PopulateAchievementDB(SQLite::Database& db, const std::vector<Achievement>& achievements);
// Harvestables
void CreateHarvestableTables(SQLite::Database& db);
void PopulateHarvestableEntry(SQLite::Database& db, const Harvestable& harvestable);
void PopulateHarvestableDB(SQLite::Database& db, const std::vector<Harvestable>& harvestables);
// Loot
void CreateLootTables(SQLite::Database& db);
void PopulateLootTableEntry(SQLite::Database& db, const LootTable& lootTable);
void PopulateLootDB(SQLite::Database& db, const std::vector<LootTable>& lootTables);
// NPCs
void CreateNPCTables(SQLite::Database& db);
void PopulateNPCEntry(SQLite::Database& db, const NPC& npc);
void PopulateNPCDB(SQLite::Database& db, const std::vector<NPC>& npcs);
// Shops
void CreateShopTables(SQLite::Database& db);
void PopulateShopEntry(SQLite::Database& db, const Shop& shop);
void PopulateShopDB(SQLite::Database& db, const std::vector<Shop>& shops);
} // namespace cursebreaker

View File

@@ -0,0 +1,506 @@
#include "database_generation/populate_configs.h"
#include <iostream>
#include <sstream>
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<int>(item.skill));
insertItem.bind(6, static_cast<int>(item.category));
insertItem.bind(7, static_cast<int>(item.slot));
insertItem.bind(8, static_cast<int>(item.tool));
insertItem.bind(9, static_cast<int>(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<int>(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<int>(crafting.workbench));
insertCrafting.bind(3, static_cast<int>(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<int64_t>(craftingId));
insertCraftingItem.bind(2, reqItem.itemId);
insertCraftingItem.bind(3, reqItem.amount);
insertCraftingItem.exec();
insertCraftingItem.reset();
}
}
}
void PopulateItemDB(SQLite::Database& db, const std::vector<Item>& 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<int>(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<Achievement>& 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<int>(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<Harvestable>& 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<LootTable>& 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<int>(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<NPC>& 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<Shop>& shops) {
CreateShopTables(db);
for (const auto& shop : shops) {
PopulateShopEntry(db, shop);
}
std::cout << "Populated " << shops.size() << " shops into the database." << std::endl;
}
} // namespace cursebreaker

View File

@@ -1,11 +1,19 @@
#include <iostream>
#include <vector>
#include <sqlite3.h>
#include <SQLiteCpp/SQLiteCpp.h>
#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<Item> itemsVec;
for (const auto& pair : getAllItems()) {
itemsVec.push_back(pair.second);
}
PopulateItemDB(memDB, itemsVec);
std::vector<Achievement> achievementsVec;
for (const auto& pair : getAllAchievements()) {
achievementsVec.push_back(pair.second);
}
PopulateAchievementDB(memDB, achievementsVec);
std::vector<Harvestable> harvestablesVec;
for (const auto& pair : getAllHarvestables()) {
harvestablesVec.push_back(pair.second);
}
PopulateHarvestableDB(memDB, harvestablesVec);
std::vector<LootTable> lootVec;
for (const auto& pair : getAllLootTables()) {
lootVec.push_back(pair.second);
}
PopulateLootDB(memDB, lootVec);
std::vector<NPC> npcsVec;
for (const auto& pair : getAllNPCs()) {
npcsVec.push_back(pair.second);
}
PopulateNPCDB(memDB, npcsVec);
std::vector<Shop> 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;
}

View File

@@ -462,4 +462,4 @@ void ParseProject(const std::filesystem::path& projectRoot)
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()));
}
}} // namespace cursebreaker