From 50c28932e39d073879b04f19a8a42892e2105595 Mon Sep 17 00:00:00 2001 From: Connor Date: Mon, 12 Jan 2026 04:32:38 +0000 Subject: [PATCH] world resources to DB --- .claude/settings.local.json | 3 +- .../examples/query_world_resources.rs | 59 +++++++++++++++++++ .../down.sql | 1 + .../up.sql | 14 +++++ .../down.sql | 16 +++++ .../up.sql | 13 ++++ cursebreaker-parser/src/bin/scene-parser.rs | 53 +++++++++++++---- cursebreaker-parser/src/schema.rs | 9 +++ .../monobehaviours/interactable_resource.rs | 2 - 9 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 cursebreaker-parser/examples/query_world_resources.rs create mode 100644 cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/down.sql create mode 100644 cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/up.sql create mode 100644 cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/down.sql create mode 100644 cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/up.sql diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8bdfdb9..89de8ba 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -37,7 +37,8 @@ "Bash(DATABASE_URL=../cursebreaker.db diesel migration:*)", "Bash(DATABASE_URL=cursebreaker.db diesel migration:*)", "Bash(DATABASE_URL=../cursebreaker-parser/cursebreaker.db cargo run:*)", - "Bash(identify:*)" + "Bash(identify:*)", + "Bash(diesel migration revert:*)" ], "additionalDirectories": [ "/home/connor/repos/CBAssets/" diff --git a/cursebreaker-parser/examples/query_world_resources.rs b/cursebreaker-parser/examples/query_world_resources.rs new file mode 100644 index 0000000..28656bf --- /dev/null +++ b/cursebreaker-parser/examples/query_world_resources.rs @@ -0,0 +1,59 @@ +//! Example: Query world resources from the database +//! +//! Run with: cargo run --example query_world_resources + +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; +use std::env; + +fn main() -> Result<(), Box> { + // Connect to database + let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "../cursebreaker.db".to_string()); + let mut conn = SqliteConnection::establish(&database_url)?; + + // Use the schema + use cursebreaker_parser::schema::world_resources::dsl::*; + + // Query all resources + #[derive(Queryable, Debug)] + struct WorldResource { + item_id: i32, + pos_x: f32, + pos_y: f32, + } + + let results = world_resources + .limit(10) + .load::(&mut conn)?; + + println!("Found {} resources (showing first 10):", results.len()); + println!(); + + for resource in results { + println!("Resource:"); + println!(" Item ID: {}", resource.item_id); + println!(" Position: ({:.2}, {:.2})", resource.pos_x, resource.pos_y); + println!(); + } + + // Query all resources + println!("\n--- All world resources ---"); + let all_results = world_resources + .load::(&mut conn)?; + + println!("Found {} total resources", all_results.len()); + + // Group by item_id + use std::collections::HashMap; + let mut item_counts: HashMap = HashMap::new(); + for resource in &all_results { + *item_counts.entry(resource.item_id).or_insert(0) += 1; + } + + println!("\nResource counts by item ID:"); + for (i_id, count) in item_counts { + println!(" Item {}: {} instances", i_id, count); + } + + Ok(()) +} diff --git a/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/down.sql b/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/down.sql new file mode 100644 index 0000000..148a03d --- /dev/null +++ b/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/down.sql @@ -0,0 +1 @@ +DROP TABLE world_resources; diff --git a/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/up.sql b/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/up.sql new file mode 100644 index 0000000..378a819 --- /dev/null +++ b/cursebreaker-parser/migrations/2026-01-12-031446-0000_create_world_resources/up.sql @@ -0,0 +1,14 @@ +-- World resources table - stores harvestable resources from Unity scenes +CREATE TABLE world_resources ( + id INTEGER PRIMARY KEY, + item_id INTEGER NOT NULL, + scene_name TEXT NOT NULL, + pos_x REAL NOT NULL, + pos_y REAL NOT NULL, + pos_z REAL NOT NULL, + object_name TEXT NOT NULL +); + +CREATE INDEX idx_world_resources_item_id ON world_resources(item_id); +CREATE INDEX idx_world_resources_scene ON world_resources(scene_name); +CREATE INDEX idx_world_resources_position ON world_resources(pos_x, pos_z); diff --git a/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/down.sql b/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/down.sql new file mode 100644 index 0000000..a461469 --- /dev/null +++ b/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/down.sql @@ -0,0 +1,16 @@ +-- Revert to original structure +DROP TABLE world_resources; + +CREATE TABLE world_resources ( + id INTEGER PRIMARY KEY, + item_id INTEGER NOT NULL, + scene_name TEXT NOT NULL, + pos_x REAL NOT NULL, + pos_y REAL NOT NULL, + pos_z REAL NOT NULL, + object_name TEXT NOT NULL +); + +CREATE INDEX idx_world_resources_item_id ON world_resources(item_id); +CREATE INDEX idx_world_resources_scene ON world_resources(scene_name); +CREATE INDEX idx_world_resources_position ON world_resources(pos_x, pos_z); diff --git a/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/up.sql b/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/up.sql new file mode 100644 index 0000000..cf1e369 --- /dev/null +++ b/cursebreaker-parser/migrations/2026-01-12-040001-0000_simplify_world_resources/up.sql @@ -0,0 +1,13 @@ +-- Drop the old table +DROP TABLE world_resources; + +-- Recreate with simplified structure - no id, no scene_name, no object_name, only 2D coordinates +CREATE TABLE world_resources ( + item_id INTEGER NOT NULL, + pos_x REAL NOT NULL, + pos_y REAL NOT NULL, + PRIMARY KEY (item_id, pos_x, pos_y) +) WITHOUT ROWID; + +CREATE INDEX idx_world_resources_item_id ON world_resources(item_id); +CREATE INDEX idx_world_resources_position ON world_resources(pos_x, pos_y); diff --git a/cursebreaker-parser/src/bin/scene-parser.rs b/cursebreaker-parser/src/bin/scene-parser.rs index 0b7f852..17ca768 100644 --- a/cursebreaker-parser/src/bin/scene-parser.rs +++ b/cursebreaker-parser/src/bin/scene-parser.rs @@ -5,13 +5,16 @@ //! - Parsing Unity scenes with type filtering //! - Extracting Interactable_Resource components only //! - Computing world transforms +//! - Saving resource locations to the database use cursebreaker_parser::InteractableResource; use unity_parser::{UnityProject, TypeFilter}; use std::path::Path; use unity_parser::log::DedupLogger; -use log::{info, error, LevelFilter}; +use log::{info, error, warn, LevelFilter}; use std::env; +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; fn main() -> Result<(), Box> { let logger = DedupLogger::new(); @@ -55,21 +58,45 @@ fn main() -> Result<(), Box> { 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(); + // Save resources to database + info!("💾 Saving resources to database..."); + let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "../cursebreaker.db".to_string()); + let mut conn = SqliteConnection::establish(&database_url)?; - 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); + // Use diesel schema + use cursebreaker_parser::schema::world_resources; - // 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(); - }); + // Clear the entire table (it's regenerated each run) + diesel::delete(world_resources::table).execute(&mut conn)?; + let mut resource_count = 0; + + // Insert all resources in a transaction + conn.transaction::<_, diesel::result::Error, _>(|conn| { + scene.world + .query_all::<(&InteractableResource, &unity_parser::WorldTransform, &unity_parser::GameObject)>() + .for_each(|(resource, transform, object)| { + let world_pos = transform.position(); + + info!(" 📦 Resource: \"{}\"", object.name); + info!(" • typeId: {}", resource.type_id); + info!(" • Position: ({:.2}, {:.2})", world_pos.x, world_pos.z); + + // Insert: store x and z (as y) coordinates only + let _ = diesel::insert_into(world_resources::table) + .values(( + world_resources::item_id.eq(resource.type_id as i32), + world_resources::pos_x.eq(world_pos.x as f32), + world_resources::pos_y.eq(world_pos.z as f32), + )) + .execute(conn); + + resource_count += 1; + }); + Ok(()) + })?; + + info!("✅ Saved {} resources to database", resource_count); log::logger().flush(); } Err(e) => { diff --git a/cursebreaker-parser/src/schema.rs b/cursebreaker-parser/src/schema.rs index 788b92c..8e43bc5 100644 --- a/cursebreaker-parser/src/schema.rs +++ b/cursebreaker-parser/src/schema.rs @@ -155,6 +155,14 @@ diesel::table! { } } +diesel::table! { + world_resources (item_id, pos_x, pos_y) { + item_id -> Integer, + pos_x -> Float, + pos_y -> Float, + } +} + diesel::joinable!(crafting_recipe_items -> crafting_recipes (recipe_id)); diesel::joinable!(crafting_recipe_items -> items (item_id)); diesel::joinable!(crafting_recipes -> items (product_item_id)); @@ -175,4 +183,5 @@ diesel::allow_tables_to_appear_in_same_query!( quests, shops, traits, + world_resources, ); diff --git a/cursebreaker-parser/src/types/monobehaviours/interactable_resource.rs b/cursebreaker-parser/src/types/monobehaviours/interactable_resource.rs index 697e0c2..bcda09c 100644 --- a/cursebreaker-parser/src/types/monobehaviours/interactable_resource.rs +++ b/cursebreaker-parser/src/types/monobehaviours/interactable_resource.rs @@ -15,14 +15,12 @@ use serde_yaml::Mapping; #[derive(Debug, Clone)] pub struct InteractableResource { - pub max_health: i64, pub type_id: i64, } impl UnityComponent for InteractableResource { fn parse(yaml: &Mapping, _ctx: &ComponentContext) -> Option { Some(Self { - max_health: unity_parser::yaml_helpers::get_i64(yaml, "maxHealth").unwrap_or(0), type_id: unity_parser::yaml_helpers::get_i64(yaml, "typeId").unwrap_or(0), }) }