This commit is contained in:
2026-01-07 10:40:29 +00:00
parent 185d324efe
commit 06dcfb7a9c
8 changed files with 490 additions and 5 deletions

View File

@@ -227,6 +227,46 @@ let beginner = harvestable_db.get_by_level_range(1, 10);
let advanced = harvestable_db.get_by_level_range(50, 100); let advanced = harvestable_db.get_by_level_range(50, 100);
``` ```
## Loading Loot Tables
```rust
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
```rust
// 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 ## Cross-referencing Data
```rust ```rust
@@ -257,6 +297,30 @@ for harvestable in harvestable_db.all_harvestables() {
} }
} }
} }
// 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 ## Statistics from XML Files
@@ -299,6 +363,13 @@ When loaded from `/home/connor/repos/CBAssets/Data/XMLs/`:
- **Level 51-100**: 28 - **Level 51-100**: 28
- **Unique Items from Harvestables**: 98 - **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 ## File Structure
``` ```
@@ -312,12 +383,14 @@ cursebreaker-parser/
│ │ ├── npc.rs # NPC data structures │ │ ├── npc.rs # NPC data structures
│ │ ├── quest.rs # Quest data structures │ │ ├── quest.rs # Quest data structures
│ │ ├── harvestable.rs # Harvestable data structures │ │ ├── harvestable.rs # Harvestable data structures
│ │ ├── loot.rs # Loot table data structures
│ │ └── interactable_resource.rs │ │ └── interactable_resource.rs
│ ├── xml_parser.rs # XML parsing logic (all types) │ ├── xml_parser.rs # XML parsing logic (all types)
│ ├── item_database.rs # ItemDatabase for runtime access │ ├── item_database.rs # ItemDatabase for runtime access
│ ├── npc_database.rs # NpcDatabase for runtime access │ ├── npc_database.rs # NpcDatabase for runtime access
│ ├── quest_database.rs # QuestDatabase for runtime access │ ├── quest_database.rs # QuestDatabase for runtime access
── harvestable_database.rs # HarvestableDatabase for runtime access ── harvestable_database.rs # HarvestableDatabase for runtime access
│ └── loot_database.rs # LootDatabase for runtime access
└── examples/ └── examples/
├── item_database_demo.rs # Items usage example ├── item_database_demo.rs # Items usage example
└── game_data_demo.rs # Full game data example └── game_data_demo.rs # Full game data example
@@ -339,12 +412,12 @@ thiserror = "1.0" # Error handling
- ✅ NPCs (`/XMLs/Npcs/NPCInfo.xml`) - ✅ NPCs (`/XMLs/Npcs/NPCInfo.xml`)
- ✅ Quests (`/XMLs/Quests/Quests.xml`) - ✅ Quests (`/XMLs/Quests/Quests.xml`)
- ✅ Harvestables (`/XMLs/Harvestables/HarvestableInfo.xml`) - ✅ Harvestables (`/XMLs/Harvestables/HarvestableInfo.xml`)
- ✅ Loot tables (`/XMLs/Loot/Loot.xml`)
## Future Enhancements ## Future Enhancements
The same pattern can be extended to parse other XML files: The same pattern can be extended to parse other XML files:
- [ ] Loot tables (`/XMLs/Loot/*.xml`)
- [ ] Maps (`/XMLs/Maps/*.xml`) - [ ] Maps (`/XMLs/Maps/*.xml`)
- [ ] Dialogue (`/XMLs/Dialogue/*.xml`) - [ ] Dialogue (`/XMLs/Dialogue/*.xml`)
- [ ] Events (`/XMLs/Events/*.xml`) - [ ] Events (`/XMLs/Events/*.xml`)

View File

@@ -2,7 +2,7 @@
//! //!
//! Run with: cargo run --example game_data_demo //! Run with: cargo run --example game_data_demo
use cursebreaker_parser::{ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase}; use cursebreaker_parser::{ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase, LootDatabase};
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🎮 Cursebreaker Game Data Demo\n"); println!("🎮 Cursebreaker Game Data Demo\n");
@@ -13,11 +13,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let npc_db = NpcDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Npcs/NPCInfo.xml")?; let npc_db = NpcDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Npcs/NPCInfo.xml")?;
let quest_db = QuestDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Quests/Quests.xml")?; let quest_db = QuestDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Quests/Quests.xml")?;
let harvestable_db = HarvestableDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Harvestables/HarvestableInfo.xml")?; let harvestable_db = HarvestableDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Harvestables/HarvestableInfo.xml")?;
let loot_db = LootDatabase::load_from_xml("/home/connor/repos/CBAssets/Data/XMLs/Loot/Loot.xml")?;
println!("✅ Loaded {} items", item_db.len()); println!("✅ Loaded {} items", item_db.len());
println!("✅ Loaded {} NPCs", npc_db.len()); println!("✅ Loaded {} NPCs", npc_db.len());
println!("✅ Loaded {} quests", quest_db.len()); println!("✅ Loaded {} quests", quest_db.len());
println!("✅ Loaded {} harvestables\n", harvestable_db.len()); println!("✅ Loaded {} harvestables", harvestable_db.len());
println!("✅ Loaded {} loot tables\n", loot_db.len());
// ======================================================================= // =======================================================================
// Items // Items
@@ -191,6 +193,74 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" • Level 51-100: {}", high_level.len()); println!(" • Level 51-100: {}", high_level.len());
println!(); println!();
// =======================================================================
// Loot Tables
// =======================================================================
println!("=== Loot Tables ===");
let all_tables = loot_db.all_tables();
let conditional_tables = loot_db.get_conditional_tables();
let guaranteed_tables = loot_db.get_tables_with_guaranteed_drops();
println!("Statistics:");
println!(" • Total loot tables: {}", loot_db.len());
println!(" • NPCs with loot: {}", loot_db.get_all_npcs_with_loot().len());
println!(" • Droppable items: {}", loot_db.get_all_droppable_items().len());
println!(" • Tables with conditional drops: {}", conditional_tables.len());
println!(" • Tables with guaranteed drops: {}", guaranteed_tables.len());
// Sample loot table
if let Some(table) = all_tables.first() {
println!("\nSample loot table:");
if let Some(name) = &table.name {
println!(" Name: {}", name);
}
println!(" NPCs: {:?}", table.npc_ids);
println!(" Drops: {} items", table.drops.len());
// Show first few drops
println!(" Sample drops:");
for drop in table.drops.iter().take(3) {
if let Some(item) = item_db.get_by_id(drop.item) {
let rate_str = drop.rate.map(|r| r.to_string()).unwrap_or_else(|| "N/A".to_string());
let amount_str = if let (Some(min), Some(max)) = (drop.minamount, drop.maxamount) {
format!("{}x{}", min, max)
} else {
"1x1".to_string()
};
println!(" - {} ({}, rate: {})", item.name, amount_str, rate_str);
}
}
}
// Cross-reference: Find what an NPC drops
println!("\nSample NPC drops:");
if let Some(npc) = npc_db.get_hostile().first() {
println!(" NPC: {} (ID: {})", npc.name, npc.id);
let drops = loot_db.get_drops_for_npc(npc.id);
if !drops.is_empty() {
println!(" Drops {} different items:", drops.len());
for drop in drops.iter().take(5) {
if let Some(item) = item_db.get_by_id(drop.item) {
println!(" - {}", item.name);
}
}
} else {
println!(" No drops configured");
}
}
// Cross-reference: Find what NPCs drop an item
if let Some(item) = item_db.get_by_id(180) {
println!("\nItem '{}' drops from:", item.name);
let npcs = loot_db.get_npcs_dropping_item(180);
for npc_id in npcs.iter().take(5) {
if let Some(npc) = npc_db.get_by_id(*npc_id) {
println!("{}", npc.name);
}
}
}
println!();
// ======================================================================= // =======================================================================
// Cross-referencing data // Cross-referencing data
// ======================================================================= // =======================================================================

View File

@@ -55,15 +55,18 @@ mod item_database;
mod npc_database; mod npc_database;
mod quest_database; mod quest_database;
mod harvestable_database; mod harvestable_database;
mod loot_database;
pub use item_database::ItemDatabase; pub use item_database::ItemDatabase;
pub use npc_database::NpcDatabase; pub use npc_database::NpcDatabase;
pub use quest_database::QuestDatabase; pub use quest_database::QuestDatabase;
pub use harvestable_database::HarvestableDatabase; pub use harvestable_database::HarvestableDatabase;
pub use loot_database::LootDatabase;
pub use types::{ pub use types::{
Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule, InteractableResource, Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule, InteractableResource,
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet, Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet,
Quest, QuestPhase, QuestReward, Quest, QuestPhase, QuestReward,
Harvestable, HarvestableDrop, Harvestable, HarvestableDrop,
LootTable, LootDrop,
}; };
pub use xml_parser::XmlParseError; pub use xml_parser::XmlParseError;

View File

@@ -0,0 +1,168 @@
use crate::types::{LootTable, LootDrop};
use crate::xml_parser::{parse_loot_xml, XmlParseError};
use std::collections::HashMap;
use std::path::Path;
/// A database for managing Loot Tables loaded from XML files
#[derive(Debug, Clone)]
pub struct LootDatabase {
tables: Vec<LootTable>,
// Map NPC ID -> list of table indices that apply to this NPC
tables_by_npc: HashMap<i32, Vec<usize>>,
// Map item ID -> list of table indices that drop this item
tables_by_item: HashMap<i32, Vec<usize>>,
}
impl LootDatabase {
/// Create a new empty LootDatabase
pub fn new() -> Self {
Self {
tables: Vec::new(),
tables_by_npc: HashMap::new(),
tables_by_item: HashMap::new(),
}
}
/// Load loot tables from an XML file
pub fn load_from_xml<P: AsRef<Path>>(path: P) -> Result<Self, XmlParseError> {
let tables = parse_loot_xml(path)?;
let mut db = Self::new();
db.add_tables(tables);
Ok(db)
}
/// Add loot tables to the database
pub fn add_tables(&mut self, tables: Vec<LootTable>) {
for table in tables {
let index = self.tables.len();
// Index by NPC IDs
for &npc_id in &table.npc_ids {
self.tables_by_npc
.entry(npc_id)
.or_insert_with(Vec::new)
.push(index);
}
// Index by item IDs
for drop in &table.drops {
self.tables_by_item
.entry(drop.item)
.or_insert_with(Vec::new)
.push(index);
}
self.tables.push(table);
}
}
/// Get all loot tables that apply to a specific NPC ID
pub fn get_tables_for_npc(&self, npc_id: i32) -> Vec<&LootTable> {
self.tables_by_npc
.get(&npc_id)
.map(|indices| {
indices
.iter()
.filter_map(|&idx| self.tables.get(idx))
.collect()
})
.unwrap_or_default()
}
/// Get all loot tables that drop a specific item
pub fn get_tables_with_item(&self, item_id: i32) -> Vec<&LootTable> {
self.tables_by_item
.get(&item_id)
.map(|indices| {
indices
.iter()
.filter_map(|&idx| self.tables.get(idx))
.collect()
})
.unwrap_or_default()
}
/// Get all possible drops for a specific NPC
pub fn get_drops_for_npc(&self, npc_id: i32) -> Vec<&LootDrop> {
let mut drops = Vec::new();
for table in self.get_tables_for_npc(npc_id) {
drops.extend(&table.drops);
}
drops
}
/// Get all NPCs that can drop a specific item
pub fn get_npcs_dropping_item(&self, item_id: i32) -> Vec<i32> {
let mut npcs = std::collections::HashSet::new();
if let Some(table_indices) = self.tables_by_item.get(&item_id) {
for &idx in table_indices {
if let Some(table) = self.tables.get(idx) {
npcs.extend(&table.npc_ids);
}
}
}
npcs.into_iter().collect()
}
/// Get all loot tables
pub fn all_tables(&self) -> &[LootTable] {
&self.tables
}
/// Get tables with conditional drops (that have checks)
pub fn get_conditional_tables(&self) -> Vec<&LootTable> {
self.tables
.iter()
.filter(|t| !t.get_conditional_drops().is_empty())
.collect()
}
/// Get tables with guaranteed drops (rate = 1)
pub fn get_tables_with_guaranteed_drops(&self) -> Vec<&LootTable> {
self.tables
.iter()
.filter(|t| !t.get_guaranteed_drops().is_empty())
.collect()
}
/// Get all unique item IDs that can drop
pub fn get_all_droppable_items(&self) -> Vec<i32> {
self.tables_by_item.keys().copied().collect()
}
/// Get all unique NPC IDs that have loot tables
pub fn get_all_npcs_with_loot(&self) -> Vec<i32> {
self.tables_by_npc.keys().copied().collect()
}
/// Get number of loot tables in database
pub fn len(&self) -> usize {
self.tables.len()
}
/// Check if database is empty
pub fn is_empty(&self) -> bool {
self.tables.is_empty()
}
/// Prepare loot tables for SQL insertion
/// Returns a vector of tuples (npc_ids_json, name, json_data)
pub fn prepare_for_sql(&self) -> Vec<(String, Option<String>, String)> {
self.tables
.iter()
.map(|table| {
let npc_ids_json = serde_json::to_string(&table.npc_ids).unwrap_or_else(|_| "[]".to_string());
let json = serde_json::to_string(table).unwrap_or_else(|_| "{}".to_string());
(npc_ids_json, table.name.clone(), json)
})
.collect()
}
}
impl Default for LootDatabase {
fn default() -> Self {
Self::new()
}
}

View File

@@ -6,7 +6,7 @@
//! 3. Extracting typeId and transform positions //! 3. Extracting typeId and transform positions
//! 4. Writing resource data to an output file //! 4. Writing resource data to an output file
use cursebreaker_parser::{ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase, InteractableResource}; use cursebreaker_parser::{ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase, LootDatabase, InteractableResource};
use unity_parser::UnityProject; use unity_parser::UnityProject;
use std::path::Path; use std::path::Path;
use unity_parser::log::DedupLogger; use unity_parser::log::DedupLogger;
@@ -41,6 +41,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?; let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?;
info!("✅ Loaded {} harvestables", harvestable_db.len()); info!("✅ Loaded {} harvestables", harvestable_db.len());
let loot_path = "/home/connor/repos/CBAssets/Data/XMLs/Loot/Loot.xml";
let loot_db = LootDatabase::load_from_xml(loot_path)?;
info!("✅ Loaded {} loot tables", loot_db.len());
// Print statistics // Print statistics
info!("\n📊 Game Data Statistics:"); info!("\n📊 Game Data Statistics:");
info!(" Items:"); info!(" Items:");
@@ -58,6 +62,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(" • Mining: {}", harvestable_db.get_by_skill("mining").len()); info!(" • Mining: {}", harvestable_db.get_by_skill("mining").len());
info!(" • Fishing: {}", harvestable_db.get_by_skill("Fishing").len()); info!(" • Fishing: {}", harvestable_db.get_by_skill("Fishing").len());
info!(" • Alchemy: {}", harvestable_db.get_by_skill("Alchemy").len()); info!(" • Alchemy: {}", harvestable_db.get_by_skill("Alchemy").len());
info!(" Loot:");
info!(" • Total tables: {}", loot_db.len());
info!(" • NPCs with loot: {}", loot_db.get_all_npcs_with_loot().len());
info!(" • Droppable items: {}", loot_db.get_all_droppable_items().len());
info!(" • Tables with conditional drops: {}", loot_db.get_conditional_tables().len());
// Initialize Unity project once - scans entire project for GUID mappings // Initialize Unity project once - scans entire project for GUID mappings
let project_root = Path::new("/home/connor/repos/CBAssets"); let project_root = Path::new("/home/connor/repos/CBAssets");

View File

@@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LootTable {
// NPC IDs this table applies to (can be multiple, comma-separated in XML)
pub npc_ids: Vec<i32>,
// Optional name/description of the loot table
pub name: Option<String>,
// List of possible drops
pub drops: Vec<LootDrop>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LootDrop {
// Item ID that can drop
pub item: i32,
// Drop rate (higher = rarer, e.g., rate=1 means common, rate=100 means very rare)
pub rate: Option<i32>,
// Amount range
pub minamount: Option<i32>,
pub maxamount: Option<i32>,
// Optional requirements/checks
pub checks: Option<String>,
// Optional comment/description
pub comment: Option<String>,
}
impl LootTable {
pub fn new(npc_ids: Vec<i32>) -> Self {
Self {
npc_ids,
name: None,
drops: Vec::new(),
}
}
/// Check if this loot table applies to a given NPC ID
pub fn applies_to_npc(&self, npc_id: i32) -> bool {
self.npc_ids.contains(&npc_id)
}
/// Get all item IDs that can drop from this table
pub fn get_drop_item_ids(&self) -> Vec<i32> {
self.drops.iter().map(|d| d.item).collect()
}
/// Get drops that have conditional checks
pub fn get_conditional_drops(&self) -> Vec<&LootDrop> {
self.drops.iter().filter(|d| d.checks.is_some()).collect()
}
/// Get guaranteed drops (rate = 1)
pub fn get_guaranteed_drops(&self) -> Vec<&LootDrop> {
self.drops.iter().filter(|d| d.rate == Some(1)).collect()
}
}
impl LootDrop {
/// Check if this drop has requirements
pub fn has_requirements(&self) -> bool {
self.checks.is_some()
}
/// Get the average drop amount
pub fn average_amount(&self) -> f32 {
let min = self.minamount.unwrap_or(1) as f32;
let max = self.maxamount.unwrap_or(1) as f32;
(min + max) / 2.0
}
}

View File

@@ -3,9 +3,11 @@ mod item;
mod npc; mod npc;
mod quest; mod quest;
mod harvestable; mod harvestable;
mod loot;
pub use interactable_resource::InteractableResource; pub use interactable_resource::InteractableResource;
pub use item::{Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule}; pub use item::{Item, ItemStat, CraftingRecipe, AnimationSet, GenerateRule};
pub use npc::{Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet}; pub use npc::{Npc, NpcStat, NpcLevel, RightClick, BarkGroup, Bark, QuestMarker, NpcAnimationSet};
pub use quest::{Quest, QuestPhase, QuestReward}; pub use quest::{Quest, QuestPhase, QuestReward};
pub use harvestable::{Harvestable, HarvestableDrop}; pub use harvestable::{Harvestable, HarvestableDrop};
pub use loot::{LootTable, LootDrop};

View File

@@ -3,6 +3,7 @@ use crate::types::{
Npc, NpcStat, NpcLevel, RightClick, BarkGroup, QuestMarker, NpcAnimationSet, Npc, NpcStat, NpcLevel, RightClick, BarkGroup, QuestMarker, NpcAnimationSet,
Quest, QuestPhase, QuestReward, Quest, QuestPhase, QuestReward,
Harvestable, HarvestableDrop, Harvestable, HarvestableDrop,
LootTable, LootDrop,
}; };
use quick_xml::events::Event; use quick_xml::events::Event;
use quick_xml::reader::Reader; use quick_xml::reader::Reader;
@@ -649,3 +650,86 @@ pub fn parse_harvestables_xml<P: AsRef<Path>>(path: P) -> Result<Vec<Harvestable
Ok(harvestables) Ok(harvestables)
} }
// ============================================================================
// Loot Parser
// ============================================================================
pub fn parse_loot_xml<P: AsRef<Path>>(path: P) -> Result<Vec<LootTable>, XmlParseError> {
let file = File::open(path)?;
let buf_reader = BufReader::new(file);
let mut reader = Reader::from_reader(buf_reader);
reader.config_mut().trim_text(true);
let mut loot_tables = Vec::new();
let mut buf = Vec::new();
let mut current_table: Option<LootTable> = None;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) | Ok(Event::Empty(ref e)) => {
match e.name().as_ref() {
b"table" => {
let attrs = parse_attributes(e)?;
// Parse npcid - can be comma-separated like "45,459"
let npc_ids = if let Some(npcid_str) = attrs.get("npcid") {
npcid_str
.split(',')
.filter_map(|s| s.trim().parse::<i32>().ok())
.collect::<Vec<i32>>()
} else {
Vec::new()
};
let mut table = LootTable::new(npc_ids);
// Parse optional name
if let Some(v) = attrs.get("name") {
table.name = Some(v.clone());
}
current_table = Some(table);
}
b"drop" if current_table.is_some() => {
if let Some(ref mut table) = current_table {
let attrs = parse_attributes(e)?;
// Parse item ID (required for a drop)
if let Some(item_str) = attrs.get("item") {
if let Ok(item) = item_str.parse::<i32>() {
let drop = LootDrop {
item,
rate: attrs.get("rate").and_then(|v| v.parse().ok()),
minamount: attrs.get("minamount").and_then(|v| v.parse().ok()),
maxamount: attrs.get("maxamount").and_then(|v| v.parse().ok()),
checks: attrs.get("checks").cloned(),
comment: attrs.get("comment").cloned(),
};
table.drops.push(drop);
}
}
}
}
_ => {}
}
}
Ok(Event::End(ref e)) => {
match e.name().as_ref() {
b"table" => {
if let Some(table) = current_table.take() {
loot_tables.push(table);
}
}
_ => {}
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(XmlParseError::XmlError(e)),
_ => {}
}
buf.clear();
}
Ok(loot_tables)
}