different commands
This commit is contained in:
@@ -7,10 +7,26 @@ edition = "2021"
|
|||||||
name = "cursebreaker_parser"
|
name = "cursebreaker_parser"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
# Main binary - runs all parsers
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cursebreaker-parser"
|
name = "cursebreaker-parser"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# XML Parser - loads game data from XML files and populates database
|
||||||
|
[[bin]]
|
||||||
|
name = "xml-parser"
|
||||||
|
path = "src/bin/xml-parser.rs"
|
||||||
|
|
||||||
|
# Scene Parser - parses Unity scenes and extracts game objects
|
||||||
|
[[bin]]
|
||||||
|
name = "scene-parser"
|
||||||
|
path = "src/bin/scene-parser.rs"
|
||||||
|
|
||||||
|
# Image Parser - processes minimap tiles
|
||||||
|
[[bin]]
|
||||||
|
name = "image-parser"
|
||||||
|
path = "src/bin/image-parser.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
unity-parser = { path = "../unity-parser" }
|
unity-parser = { path = "../unity-parser" }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
|||||||
@@ -20,6 +20,53 @@ Cursebreaker Parser is designed to:
|
|||||||
- **XML Parsing**: Robust XML parsing with error handling
|
- **XML Parsing**: Robust XML parsing with error handling
|
||||||
- **SQL Export**: Prepare data for SQL database insertion
|
- **SQL Export**: Prepare data for SQL database insertion
|
||||||
|
|
||||||
|
## Binaries
|
||||||
|
|
||||||
|
The project provides multiple binaries to handle different parsing tasks. This allows you to run only the parts you need, avoiding long load times for unnecessary operations.
|
||||||
|
|
||||||
|
### Available Binaries
|
||||||
|
|
||||||
|
1. **xml-parser** - Loads game data from XML files and populates the SQLite database
|
||||||
|
- Fast execution
|
||||||
|
- Run this when XML files change
|
||||||
|
```bash
|
||||||
|
cargo run --bin xml-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **scene-parser** - Parses Unity scenes and extracts game objects
|
||||||
|
- Slow execution (Unity project initialization)
|
||||||
|
- Run this when scene files change
|
||||||
|
```bash
|
||||||
|
cargo run --bin scene-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **image-parser** - Processes minimap tiles
|
||||||
|
- Slow execution (image processing and compression)
|
||||||
|
- Run this when minimap images change
|
||||||
|
```bash
|
||||||
|
cargo run --bin image-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **cursebreaker-parser** - All-in-one binary (runs all parsers)
|
||||||
|
- Slowest execution (runs everything)
|
||||||
|
- Use when you need to regenerate the entire database
|
||||||
|
```bash
|
||||||
|
cargo run --bin cursebreaker-parser
|
||||||
|
# or simply
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
Build specific binaries for release:
|
||||||
|
```bash
|
||||||
|
cargo build --release --bin xml-parser
|
||||||
|
cargo build --release --bin scene-parser
|
||||||
|
cargo build --release --bin image-parser
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled binaries will be in `target/release/`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Loading Items from XML
|
### Loading Items from XML
|
||||||
@@ -69,19 +116,26 @@ for (id, name, json) in sql_data.iter().take(5) {
|
|||||||
cursebreaker-parser/
|
cursebreaker-parser/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── lib.rs # Library entry point and public API
|
│ ├── lib.rs # Library entry point and public API
|
||||||
│ ├── main.rs # Binary entry point
|
│ ├── main.rs # Main binary (all-in-one parser)
|
||||||
|
│ ├── bin/ # Separate parser binaries
|
||||||
|
│ │ ├── xml-parser.rs # XML parsing only
|
||||||
|
│ │ ├── scene-parser.rs # Unity scene parsing only
|
||||||
|
│ │ └── image-parser.rs # Image processing only
|
||||||
│ ├── xml_parser.rs # XML parsing utilities
|
│ ├── xml_parser.rs # XML parsing utilities
|
||||||
|
│ ├── image_processor.rs # Image processing utilities
|
||||||
│ ├── item_loader.rs # Item loading logic
|
│ ├── item_loader.rs # Item loading logic
|
||||||
│ ├── databases/ # Database implementations
|
│ ├── databases/ # Database implementations
|
||||||
│ │ ├── item_database.rs
|
│ │ ├── item_database.rs
|
||||||
│ │ ├── npc_database.rs
|
│ │ ├── npc_database.rs
|
||||||
│ │ ├── quest_database.rs
|
│ │ ├── quest_database.rs
|
||||||
│ │ ├── harvestable_database.rs
|
│ │ ├── harvestable_database.rs
|
||||||
│ │ └── loot_database.rs
|
│ │ ├── loot_database.rs
|
||||||
|
│ │ └── minimap_database.rs
|
||||||
│ └── types/ # Type definitions
|
│ └── types/ # Type definitions
|
||||||
│ ├── cursebreaker/ # Game-specific types (Items, NPCs, Quests, etc.)
|
│ ├── cursebreaker/ # Game-specific types (Items, NPCs, Quests, etc.)
|
||||||
│ └── monobehaviours/ # Unity MonoBehaviour types
|
│ └── monobehaviours/ # Unity MonoBehaviour types
|
||||||
├── examples/ # Example usage
|
├── examples/ # Example usage
|
||||||
|
├── migrations/ # Database migrations
|
||||||
├── Cargo.toml # Package configuration
|
├── Cargo.toml # Package configuration
|
||||||
└── XML_PARSING.md # XML parsing documentation
|
└── XML_PARSING.md # XML parsing documentation
|
||||||
|
|
||||||
|
|||||||
49
cursebreaker-parser/src/bin/image-parser.rs
Normal file
49
cursebreaker-parser/src/bin/image-parser.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//! Image Parser - Processes minimap tiles
|
||||||
|
//!
|
||||||
|
//! This binary handles:
|
||||||
|
//! - Loading minimap tile images
|
||||||
|
//! - Converting PNG to WebP format
|
||||||
|
//! - Storing tiles in the SQLite database
|
||||||
|
//! - Generating statistics about storage and compression
|
||||||
|
|
||||||
|
use cursebreaker_parser::MinimapDatabase;
|
||||||
|
use log::{info, error, LevelFilter};
|
||||||
|
use unity_parser::log::DedupLogger;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let logger = DedupLogger::new();
|
||||||
|
log::set_boxed_logger(Box::new(logger))
|
||||||
|
.map(|()| log::set_max_level(LevelFilter::Trace))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("🎮 Cursebreaker - Image Parser");
|
||||||
|
|
||||||
|
// Process minimap tiles
|
||||||
|
info!("\n🗺️ Processing minimap tiles...");
|
||||||
|
let minimap_db = MinimapDatabase::new("cursebreaker.db".to_string());
|
||||||
|
|
||||||
|
let minimap_path = "/home/connor/repos/CBAssets/Data/Textures/MinimapSquares";
|
||||||
|
match minimap_db.load_from_directory(minimap_path) {
|
||||||
|
Ok(count) => {
|
||||||
|
info!("✅ Processed {} minimap tiles", count);
|
||||||
|
|
||||||
|
if let Ok(stats) = minimap_db.get_storage_stats() {
|
||||||
|
info!(" Storage Statistics:");
|
||||||
|
info!(" • Original PNG total: {} MB", stats.total_original_size / 1_048_576);
|
||||||
|
info!(" • WebP total: {} MB", stats.total_webp_size() / 1_048_576);
|
||||||
|
info!(" • Compression ratio: {:.2}%", stats.compression_ratio());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(bounds) = minimap_db.get_map_bounds() {
|
||||||
|
info!(" Map Bounds:");
|
||||||
|
info!(" • Min (x,y): {:?}", bounds.0);
|
||||||
|
info!(" • Max (x,y): {:?}", bounds.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to process minimap tiles: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
72
cursebreaker-parser/src/bin/scene-parser.rs
Normal file
72
cursebreaker-parser/src/bin/scene-parser.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
//! Scene Parser - Parses Unity scenes and extracts game objects
|
||||||
|
//!
|
||||||
|
//! This binary handles:
|
||||||
|
//! - Initializing the Unity project
|
||||||
|
//! - Parsing Unity scenes
|
||||||
|
//! - Extracting Interactable_Resource components
|
||||||
|
//! - Computing world transforms
|
||||||
|
|
||||||
|
use cursebreaker_parser::InteractableResource;
|
||||||
|
use unity_parser::UnityProject;
|
||||||
|
use std::path::Path;
|
||||||
|
use unity_parser::log::DedupLogger;
|
||||||
|
use log::{info, error, LevelFilter};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let logger = DedupLogger::new();
|
||||||
|
log::set_boxed_logger(Box::new(logger))
|
||||||
|
.map(|()| log::set_max_level(LevelFilter::Trace))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("🎮 Cursebreaker - Scene Parser");
|
||||||
|
|
||||||
|
// Initialize Unity project once - scans entire project for GUID mappings
|
||||||
|
let project_root = Path::new("/home/connor/repos/CBAssets");
|
||||||
|
info!("\n📦 Initializing Unity project from: {}", project_root.display());
|
||||||
|
|
||||||
|
let project = UnityProject::from_path(project_root)?;
|
||||||
|
|
||||||
|
// Now parse the scene using the pre-built GUID resolvers
|
||||||
|
let scene_path = "_GameAssets/Scenes/Tiles/10_3.unity";
|
||||||
|
info!("📁 Parsing scene: {}", scene_path);
|
||||||
|
|
||||||
|
log::logger().flush();
|
||||||
|
|
||||||
|
// Parse the scene using the project
|
||||||
|
match project.parse_scene(scene_path) {
|
||||||
|
Ok(mut scene) => {
|
||||||
|
info!("✅ Scene parsed successfully!");
|
||||||
|
info!(" Total entities: {}", scene.entity_map.len());
|
||||||
|
|
||||||
|
// Post-processing: Compute world transforms
|
||||||
|
info!("🔄 Computing world transforms...");
|
||||||
|
unity_parser::compute_world_transforms(&mut scene.world, &scene.entity_map);
|
||||||
|
info!(" ✓ World transforms computed");
|
||||||
|
|
||||||
|
// Find all entities that have Interactable_Resource
|
||||||
|
log::logger().flush();
|
||||||
|
|
||||||
|
scene.world
|
||||||
|
.query_all::<(&InteractableResource, &unity_parser::WorldTransform, &unity_parser::GameObject)>()
|
||||||
|
.for_each(|(resource, transform, object)| {
|
||||||
|
info!(" 📦 Resource: \"{}\"", object.name);
|
||||||
|
info!(" • typeId: {}", resource.type_id);
|
||||||
|
|
||||||
|
// Extract world position from WorldTransform
|
||||||
|
let world_pos = transform.position();
|
||||||
|
info!(" • Position: ({:.2}, {:.2}, {:.2})", world_pos.x, world_pos.y, world_pos.z);
|
||||||
|
log::logger().flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
log::logger().flush();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Parse error: {}", e);
|
||||||
|
return Err(Box::new(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::logger().flush();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
145
cursebreaker-parser/src/bin/xml-parser.rs
Normal file
145
cursebreaker-parser/src/bin/xml-parser.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
//! XML Parser - Loads game data from XML files and populates the SQLite database
|
||||||
|
//!
|
||||||
|
//! This binary handles:
|
||||||
|
//! - Loading all game data from XML files
|
||||||
|
//! - Populating the SQLite database with the parsed data
|
||||||
|
//! - Generating statistics about the loaded data
|
||||||
|
|
||||||
|
use cursebreaker_parser::{
|
||||||
|
ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase, LootDatabase,
|
||||||
|
MapDatabase, FastTravelDatabase, PlayerHouseDatabase, TraitDatabase, ShopDatabase
|
||||||
|
};
|
||||||
|
use log::{info, warn, LevelFilter};
|
||||||
|
use unity_parser::log::DedupLogger;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let logger = DedupLogger::new();
|
||||||
|
log::set_boxed_logger(Box::new(logger))
|
||||||
|
.map(|()| log::set_max_level(LevelFilter::Trace))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("🎮 Cursebreaker - XML Parser");
|
||||||
|
info!("📚 Loading game data from XML...");
|
||||||
|
|
||||||
|
// Load items from XML
|
||||||
|
let items_path = "/home/connor/repos/CBAssets/Data/XMLs/Items/Items.xml";
|
||||||
|
let item_db = ItemDatabase::load_from_xml(items_path)?;
|
||||||
|
info!("✅ Loaded {} items", item_db.len());
|
||||||
|
|
||||||
|
let npcs_path = "/home/connor/repos/CBAssets/Data/XMLs/Npcs/NPCInfo.xml";
|
||||||
|
let npc_db = NpcDatabase::load_from_xml(npcs_path)?;
|
||||||
|
info!("✅ Loaded {} NPCs", npc_db.len());
|
||||||
|
|
||||||
|
let quests_path = "/home/connor/repos/CBAssets/Data/XMLs/Quests/Quests.xml";
|
||||||
|
let quest_db = QuestDatabase::load_from_xml(quests_path)?;
|
||||||
|
info!("✅ Loaded {} quests", quest_db.len());
|
||||||
|
|
||||||
|
let harvestables_path = "/home/connor/repos/CBAssets/Data/XMLs/Harvestables/HarvestableInfo.xml";
|
||||||
|
let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?;
|
||||||
|
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());
|
||||||
|
|
||||||
|
let maps_path = "/home/connor/repos/CBAssets/Data/XMLs/Maps/Maps.xml";
|
||||||
|
let map_db = MapDatabase::load_from_xml(maps_path)?;
|
||||||
|
info!("✅ Loaded {} maps", map_db.len());
|
||||||
|
|
||||||
|
let fast_travel_dir = "/home/connor/repos/CBAssets/Data/XMLs";
|
||||||
|
let fast_travel_db = FastTravelDatabase::load_from_directory(fast_travel_dir)?;
|
||||||
|
info!("✅ Loaded {} fast travel locations", fast_travel_db.len());
|
||||||
|
|
||||||
|
let player_houses_path = "/home/connor/repos/CBAssets/Data/XMLs/PlayerHouses/PlayerHouses.xml";
|
||||||
|
let player_house_db = PlayerHouseDatabase::load_from_xml(player_houses_path)?;
|
||||||
|
info!("✅ Loaded {} player houses", player_house_db.len());
|
||||||
|
|
||||||
|
let traits_path = "/home/connor/repos/CBAssets/Data/XMLs/Traits/Traits.xml";
|
||||||
|
let trait_db = TraitDatabase::load_from_xml(traits_path)?;
|
||||||
|
info!("✅ Loaded {} traits", trait_db.len());
|
||||||
|
|
||||||
|
let shops_path = "/home/connor/repos/CBAssets/Data/XMLs/Shops/Shops.xml";
|
||||||
|
let shop_db = ShopDatabase::load_from_xml(shops_path)?;
|
||||||
|
info!("✅ Loaded {} shops", shop_db.len());
|
||||||
|
|
||||||
|
// Save to SQLite database
|
||||||
|
info!("\n💾 Saving game data to SQLite database...");
|
||||||
|
let mut conn = SqliteConnection::establish("cursebreaker.db")?;
|
||||||
|
|
||||||
|
match item_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} items to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save items: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match npc_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} NPCs to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save NPCs: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match quest_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} quests to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save quests: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match harvestable_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} harvestables to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save harvestables: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match loot_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} loot tables to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save loot tables: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match map_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} maps to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save maps: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match fast_travel_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} fast travel locations to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save fast travel locations: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match player_house_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} player houses to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save player houses: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match trait_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} traits to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save traits: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
match shop_db.save_to_db(&mut conn) {
|
||||||
|
Ok(count) => info!("✅ Saved {} shops to database", count),
|
||||||
|
Err(e) => warn!("⚠️ Failed to save shops: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print statistics
|
||||||
|
info!("\n📊 Game Data Statistics:");
|
||||||
|
info!(" Items:");
|
||||||
|
info!(" • Weapons: {}", item_db.get_by_slot("weapon").len());
|
||||||
|
info!(" • Consumables: {}", item_db.get_by_slot("consumable").len());
|
||||||
|
info!(" NPCs:");
|
||||||
|
info!(" • Hostile: {}", npc_db.get_hostile().len());
|
||||||
|
info!(" • Interactable: {}", npc_db.get_interactable().len());
|
||||||
|
info!(" Quests:");
|
||||||
|
info!(" • Main quests: {}", quest_db.get_main_quests().len());
|
||||||
|
info!(" • Side quests: {}", quest_db.get_side_quests().len());
|
||||||
|
info!(" Harvestables:");
|
||||||
|
info!(" • Trees: {}", harvestable_db.get_trees().len());
|
||||||
|
info!(" • Woodcutting: {}", harvestable_db.get_by_skill("Woodcutting").len());
|
||||||
|
info!(" • Mining: {}", harvestable_db.get_by_skill("mining").len());
|
||||||
|
info!(" • Fishing: {}", harvestable_db.get_by_skill("Fishing").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());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user