Files
cursebreaker-parser-rust/cursebreaker-parser/XML_PARSING.md
2026-01-07 10:40:29 +00:00

11 KiB

XML Parsing in Cursebreaker Parser

This document describes the XML parsing functionality added to the cursebreaker-parser project.

Overview

The parser now supports loading game data from Cursebreaker's XML files and storing them in efficient data structures for runtime access and SQL database serialization.

Features

  • Parse Items, NPCs, Quests, and Harvestables XML files with full attribute and nested element support
  • In-memory databases with fast lookups by ID, name, and various filters
  • JSON serialization for SQL database storage
  • Type-safe data structures with serde support
  • Easy-to-use API with query methods
  • Cross-referencing support between different data types

Quick Start

Loading Items

use cursebreaker_parser::ItemDatabase;

let item_db = ItemDatabase::load_from_xml("Data/XMLs/Items/Items.xml")?;
println!("Loaded {} items", item_db.len());

Querying Items

// Get by ID
if let Some(item) = item_db.get_by_id(150) {
    println!("Found: {}", item.name);
}

// Get by category
let bows = item_db.get_by_category("bow");

// Get by slot
let weapons = item_db.get_by_slot("weapon");

// Get by skill requirement
let magic_items = item_db.get_by_skill("magic");

// Get all items
for item in item_db.all_items() {
    println!("{}: {}", item.id, item.name);
}

SQL Serialization

// Prepare items for SQL insertion
let sql_data = item_db.prepare_for_sql();

for (id, name, json_data) in sql_data {
    // INSERT INTO items (id, name, data) VALUES (?, ?, ?)
    // Use your preferred SQL library to insert
}

Data Structures

Item

The main Item struct contains all item attributes from the XML:

pub struct Item {
    // Required
    pub id: i32,
    pub name: String,

    // Optional attributes
    pub level: Option<i32>,
    pub description: Option<String>,
    pub price: Option<i32>,
    pub slot: Option<String>,
    pub category: Option<String>,
    pub skill: Option<String>,

    // ... many more fields

    // Nested elements
    pub stats: Vec<ItemStat>,
    pub crafting_recipes: Vec<CraftingRecipe>,
    pub animations: Option<AnimationSet>,
    pub generate_rules: Vec<GenerateRule>,
}

ItemStat

Represents item statistics:

pub struct ItemStat {
    // Damage
    pub damagephysical: Option<i32>,
    pub damagemagical: Option<i32>,
    pub damageranged: Option<i32>,

    // Accuracy
    pub accuracyphysical: Option<i32>,
    pub accuracymagical: Option<i32>,
    pub accuracyranged: Option<i32>,

    // Resistance
    pub resistancephysical: Option<i32>,
    pub resistancemagical: Option<i32>,
    pub resistanceranged: Option<i32>,

    // Core stats
    pub health: Option<i32>,
    pub mana: Option<i32>,
    pub manaregen: Option<i32>,
    pub healing: Option<i32>,

    // Harvesting
    pub harvestingspeedwoodcutting: Option<i32>,
}

Example Programs

Run the demos to see all features in action:

# Items only
cargo run --example item_database_demo

# All game data (Items, NPCs, Quests, Harvestables)
cargo run --example game_data_demo

Loading NPCs

use cursebreaker_parser::NpcDatabase;

let npc_db = NpcDatabase::load_from_xml("Data/XMLs/Npcs/NPCInfo.xml")?;
println!("Loaded {} NPCs", npc_db.len());

Querying NPCs

// Get by ID
if let Some(npc) = npc_db.get_by_id(1) {
    println!("Found: {}", npc.name);
}

// Get hostile NPCs
let hostile = npc_db.get_hostile();

// Get interactable NPCs
let interactable = npc_db.get_interactable();

// Get NPCs by tag
let undead = npc_db.get_by_tag("Undead");

// Get shopkeepers
let shops = npc_db.get_shopkeepers();

Loading Quests

use cursebreaker_parser::QuestDatabase;

let quest_db = QuestDatabase::load_from_xml("Data/XMLs/Quests/Quests.xml")?;
println!("Loaded {} quests", quest_db.len());

Querying Quests

// Get by ID
if let Some(quest) = quest_db.get_by_id(1) {
    println!("Quest: {}", quest.name);
    println!("Phases: {}", quest.phase_count());
}

// Get main quests
let main_quests = quest_db.get_main_quests();

// Get side quests
let side_quests = quest_db.get_side_quests();

// Get hidden quests
let hidden = quest_db.get_hidden_quests();

Loading Harvestables

use cursebreaker_parser::HarvestableDatabase;

let harvestable_db = HarvestableDatabase::load_from_xml("Data/XMLs/Harvestables/HarvestableInfo.xml")?;
println!("Loaded {} harvestables", harvestable_db.len());

Querying Harvestables

// Get by type ID
if let Some(harvestable) = harvestable_db.get_by_typeid(1) {
    println!("Found: {}", harvestable.name);
}

// Get by skill
let woodcutting = harvestable_db.get_by_skill("Woodcutting");
let mining = harvestable_db.get_by_skill("mining");
let fishing = harvestable_db.get_by_skill("Fishing");

// Get trees (harvestables with tree=1)
let trees = harvestable_db.get_trees();

// Get by tool requirement
let hatchet_nodes = harvestable_db.get_by_tool("hatchet");
let pickaxe_nodes = harvestable_db.get_by_tool("pickaxe");

// Get by level range
let beginner = harvestable_db.get_by_level_range(1, 10);
let advanced = harvestable_db.get_by_level_range(50, 100);

Loading Loot Tables

use cursebreaker_parser::LootDatabase;

let loot_db = LootDatabase::load_from_xml("Data/XMLs/Loot/Loot.xml")?;
println!("Loaded {} loot tables", loot_db.len());

Querying Loot Tables

// Get all loot tables for a specific NPC
let npc_id = 45;
let tables = loot_db.get_tables_for_npc(npc_id);

// Get all drops for a specific NPC
let drops = loot_db.get_drops_for_npc(npc_id);
for drop in drops {
    println!("Item ID: {}, Rate: {:?}", drop.item, drop.rate);
}

// Find which NPCs drop a specific item
let item_id = 180;
let npcs = loot_db.get_npcs_dropping_item(item_id);
println!("Item {} drops from {} NPCs", item_id, npcs.len());

// Get all tables with conditional drops (checks field)
let conditional = loot_db.get_conditional_tables();

// Get all tables with guaranteed drops (rate = 1)
let guaranteed = loot_db.get_tables_with_guaranteed_drops();

// Get all unique item IDs that can drop
let droppable_items = loot_db.get_all_droppable_items();

// Get all NPCs that have loot tables
let npcs_with_loot = loot_db.get_all_npcs_with_loot();

Cross-referencing Data

// Find items rewarded by quests
for quest in quest_db.all_quests() {
    for reward in &quest.rewards {
        if let Some(item_id) = reward.item {
            if let Some(item) = item_db.get_by_id(item_id) {
                println!("Quest '{}' rewards: {}", quest.name, item.name);
            }
        }
    }
}

// Find NPCs that give quests
for npc in npc_db.all_npcs() {
    if !npc.questmarkers.is_empty() {
        println!("NPC '{}' has {} quest markers", npc.name, npc.questmarkers.len());
    }
}

// Find items that drop from harvestables
for harvestable in harvestable_db.all_harvestables() {
    for drop in &harvestable.drops {
        if let Some(item) = item_db.get_by_id(drop.id) {
            println!("'{}' drops: {} (rate: {})",
                     harvestable.name, item.name, drop.droprate.unwrap_or(0));
        }
    }
}

// Find what items an NPC drops
let npc_id = 45;
if let Some(npc) = npc_db.get_by_id(npc_id) {
    let drops = loot_db.get_drops_for_npc(npc_id);
    println!("NPC '{}' drops {} items:", npc.name, drops.len());
    for drop in drops {
        if let Some(item) = item_db.get_by_id(drop.item) {
            println!("  - {}", item.name);
        }
    }
}

// Find which NPCs drop a specific item
let item_id = 180;
if let Some(item) = item_db.get_by_id(item_id) {
    let npcs = loot_db.get_npcs_dropping_item(item_id);
    println!("Item '{}' drops from:", item.name);
    for npc_id in npcs {
        if let Some(npc) = npc_db.get_by_id(npc_id) {
            println!("  - {}", npc.name);
        }
    }
}

Statistics from XML Files

When loaded from /home/connor/repos/CBAssets/Data/XMLs/:

Items.xml

  • Total Items: 1,360
  • Weapons: 166
  • Armor: 148
  • Consumables: 294
  • Trinkets: 59
  • Bows: 18
  • Magic Items: 76

NPCs/NPCInfo.xml

  • Total NPCs: 1,242
  • Hostile NPCs: 328
  • Interactable NPCs: 512
  • Undead: 71
  • Predators: 13
  • Quest Givers: 108

Quests/Quests.xml

  • Total Quests: 108
  • Main Quests: 19
  • Side Quests: 89
  • Hidden Quests: 2
  • Unique Quest Reward Items: 70

Harvestables/HarvestableInfo.xml

  • Total Harvestables: 96
  • Trees: 9
  • Woodcutting: 10
  • Mining: 11
  • Fishing: 11
  • Alchemy: 50
  • Level 1-10: 31
  • Level 11-50: 37
  • Level 51-100: 28
  • Unique Items from Harvestables: 98

Loot/Loot.xml

  • Total Loot Tables: 175
  • NPCs with Loot: 267
  • Droppable Items: 405
  • Tables with Conditional Drops: 33
  • Tables with Guaranteed Drops: Multiple tables include guaranteed (rate=1) drops

File Structure

cursebreaker-parser/
├── src/
│   ├── lib.rs                    # Library exports
│   ├── main.rs                   # Main binary (Unity + XML parsing)
│   ├── types/
│   │   ├── mod.rs
│   │   ├── item.rs               # Item data structures
│   │   ├── npc.rs                # NPC data structures
│   │   ├── quest.rs              # Quest data structures
│   │   ├── harvestable.rs        # Harvestable data structures
│   │   ├── loot.rs               # Loot table data structures
│   │   └── interactable_resource.rs
│   ├── xml_parser.rs             # XML parsing logic (all types)
│   ├── item_database.rs          # ItemDatabase for runtime access
│   ├── npc_database.rs           # NpcDatabase for runtime access
│   ├── quest_database.rs         # QuestDatabase for runtime access
│   ├── harvestable_database.rs   # HarvestableDatabase for runtime access
│   └── loot_database.rs          # LootDatabase for runtime access
└── examples/
    ├── item_database_demo.rs     # Items usage example
    └── game_data_demo.rs         # Full game data example

Dependencies Added

quick-xml = "0.37"              # XML parsing
serde = { version = "1.0", features = ["derive"] }  # Serialization
serde_json = "1.0"              # JSON serialization
diesel = { version = "2.2", features = ["sqlite"], optional = true }  # SQL (optional)
thiserror = "1.0"               # Error handling

Completed Features

  • Items (/XMLs/Items/Items.xml)
  • NPCs (/XMLs/Npcs/NPCInfo.xml)
  • Quests (/XMLs/Quests/Quests.xml)
  • Harvestables (/XMLs/Harvestables/HarvestableInfo.xml)
  • Loot tables (/XMLs/Loot/Loot.xml)

Future Enhancements

The same pattern can be extended to parse other XML files:

  • Maps (/XMLs/Maps/*.xml)
  • Dialogue (/XMLs/Dialogue/*.xml)
  • Events (/XMLs/Events/*.xml)
  • Achievements (/XMLs/Achievements/*.xml)
  • Traits (/XMLs/Traits/*.xml)
  • Shops (/XMLs/Shops/*.xml)

Each follows the same pattern:

  1. Define data structures in src/types/
  2. Create parser in src/xml_parser.rs
  3. Create database wrapper for runtime access
  4. Add to lib.rs exports

Integration with Unity Parser

The main binary (src/main.rs) demonstrates integration of both systems:

  1. Load game data from XML files (Items, etc.)
  2. Parse Unity scenes for game objects
  3. Cross-reference data (e.g., item IDs in loot spawners)

This creates a complete game data pipeline from source files to runtime.