resource icons

This commit is contained in:
2026-01-12 06:06:44 +00:00
parent 50c28932e3
commit e53deb57bb
7 changed files with 166 additions and 10 deletions

View File

@@ -38,7 +38,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(DATABASE_URL=../cursebreaker-parser/cursebreaker.db cargo run:*)",
"Bash(identify:*)", "Bash(identify:*)",
"Bash(diesel migration revert:*)" "Bash(diesel migration revert:*)",
"Bash(xargs:*)"
], ],
"additionalDirectories": [ "additionalDirectories": [
"/home/connor/repos/CBAssets/" "/home/connor/repos/CBAssets/"

View File

@@ -0,0 +1,2 @@
-- Drop resource_icons table
DROP TABLE resource_icons;

View File

@@ -0,0 +1,8 @@
-- Create resource_icons table to store processed item icons for world resources
CREATE TABLE resource_icons (
item_id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
icon_64 BLOB NOT NULL
);
CREATE INDEX idx_resource_icons_name ON resource_icons(name);

View File

@@ -6,15 +6,17 @@
//! - Extracting Interactable_Resource components only //! - Extracting Interactable_Resource components only
//! - Computing world transforms //! - Computing world transforms
//! - Saving resource locations to the database //! - Saving resource locations to the database
//! - Processing and saving item icons for resources
use cursebreaker_parser::InteractableResource; use cursebreaker_parser::{InteractableResource, ImageProcessor, OutlineConfig};
use unity_parser::{UnityProject, TypeFilter}; use unity_parser::{UnityProject, TypeFilter};
use std::path::Path; use std::path::{Path, PathBuf};
use unity_parser::log::DedupLogger; use unity_parser::log::DedupLogger;
use log::{info, error, warn, LevelFilter}; use log::{info, error, warn, LevelFilter};
use std::env; use std::env;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sqlite::SqliteConnection; use diesel::sqlite::SqliteConnection;
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let logger = DedupLogger::new(); let logger = DedupLogger::new();
@@ -98,6 +100,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("✅ Saved {} resources to database", resource_count); info!("✅ Saved {} resources to database", resource_count);
log::logger().flush(); log::logger().flush();
// Process and save item icons
info!("🎨 Processing item icons...");
process_item_icons(&cb_assets_path, &mut conn, &scene)?;
} }
Err(e) => { Err(e) => {
error!("Parse error: {}", e); error!("Parse error: {}", e);
@@ -109,3 +115,93 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
/// Process item icons for all resources in the scene
fn process_item_icons(
cb_assets_path: &str,
conn: &mut SqliteConnection,
scene: &unity_parser::UnityScene,
) -> Result<(), Box<dyn std::error::Error>> {
use cursebreaker_parser::schema::{resource_icons, items};
// Collect unique item IDs from resources
let mut unique_items: HashMap<i32, String> = HashMap::new();
scene.world
.query_all::<(&InteractableResource, &unity_parser::GameObject)>()
.for_each(|(resource, object)| {
unique_items.entry(resource.type_id as i32)
.or_insert_with(|| object.name.to_string());
});
info!(" Found {} unique resource types", unique_items.len());
// Clear existing resource icons (regenerated each run)
diesel::delete(resource_icons::table).execute(conn)?;
// Create image processor with white outline
let processor = ImageProcessor::default();
let outline_config = OutlineConfig::white(1);
let mut processed_count = 0;
let mut failed_count = 0;
// Process each unique item
for (item_id, default_name) in unique_items.iter() {
// Try to get the actual item name from the items table
let item_name: String = items::table
.filter(items::id.eq(item_id))
.select(items::name)
.first(conn)
.unwrap_or_else(|_| default_name.clone());
// Construct icon path
let icon_path = PathBuf::from(cb_assets_path)
.join("Data/Textures/ItemIcons")
.join(format!("{}.png", item_id));
if !icon_path.exists() {
warn!(" ⚠️ Icon not found for item {} ({}): {}", item_id, item_name, icon_path.display());
failed_count += 1;
continue;
}
// Process the icon: resize to 64px with white outline
match processor.process_image(&icon_path, &[64], None, Some(&outline_config)) {
Ok(processed) => {
if let Some(icon_data) = processed.get(64) {
// Insert into database
match diesel::insert_into(resource_icons::table)
.values((
resource_icons::item_id.eq(item_id),
resource_icons::name.eq(&item_name),
resource_icons::icon_64.eq(icon_data.as_slice()),
))
.execute(conn)
{
Ok(_) => {
info!(" ✓ Processed icon for item {} ({}): {} bytes",
item_id, item_name, icon_data.len());
processed_count += 1;
}
Err(e) => {
warn!(" ⚠️ Failed to insert icon for item {} ({}): {}",
item_id, item_name, e);
failed_count += 1;
}
}
}
}
Err(e) => {
warn!(" ⚠️ Failed to process icon for item {} ({}): {}",
item_id, item_name, e);
failed_count += 1;
}
}
}
info!("✅ Processed {} item icons ({} succeeded, {} failed)",
unique_items.len(), processed_count, failed_count);
Ok(())
}

View File

@@ -0,0 +1,40 @@
//! Verify resource_icons table
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
use cursebreaker_parser::schema::resource_icons;
let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "../cursebreaker.db".to_string());
let mut conn = SqliteConnection::establish(&database_url)?;
// Count total icons
let count: i64 = resource_icons::table.count().get_result(&mut conn)?;
println!("✅ Database contains {} resource icons\n", count);
// Get all icons and show details
#[derive(Queryable, Debug)]
struct ResourceIcon {
item_id: i32,
name: String,
icon_64: Vec<u8>,
}
let icons: Vec<ResourceIcon> = resource_icons::table
.load(&mut conn)?;
if icons.is_empty() {
println!(" No resource icons found in database.");
println!(" This is expected if no item icons were available during scene parsing.");
} else {
println!("Resource icons:");
for icon in icons {
println!(" • Item {}: {} ({} bytes)",
icon.item_id, icon.name, icon.icon_64.len());
}
}
Ok(())
}

View File

@@ -36,9 +36,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// let quest_db = QuestDatabase::load_from_xml(quests_path)?; // let quest_db = QuestDatabase::load_from_xml(quests_path)?;
// info!("✅ Loaded {} quests", quest_db.len()); // info!("✅ Loaded {} quests", quest_db.len());
// let harvestables_path = format!("{}/Data/XMLs/Harvestables/HarvestableInfo.xml", cb_assets_path); let harvestables_path = format!("{}/Data/XMLs/Harvestables/HarvestableInfo.xml", cb_assets_path);
// 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 = format!("{}/Data/XMLs/Loot/Loot.xml", cb_assets_path); // let loot_path = format!("{}/Data/XMLs/Loot/Loot.xml", cb_assets_path);
// let loot_db = LootDatabase::load_from_xml(loot_path)?; // let loot_db = LootDatabase::load_from_xml(loot_path)?;
@@ -91,10 +91,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Err(e) => warn!("⚠️ Failed to save quests: {}", e), // Err(e) => warn!("⚠️ Failed to save quests: {}", e),
// } // }
// match harvestable_db.save_to_db(&mut conn) { match harvestable_db.save_to_db(&mut conn) {
// Ok(count) => info!("✅ Saved {} harvestables to database", count), Ok(count) => info!("✅ Saved {} harvestables to database", count),
// Err(e) => warn!("⚠️ Failed to save harvestables: {}", e), Err(e) => warn!("⚠️ Failed to save harvestables: {}", e),
// } }
// match loot_db.save_to_db(&mut conn) { // match loot_db.save_to_db(&mut conn) {
// Ok(count) => info!("✅ Saved {} loot tables to database", count), // Ok(count) => info!("✅ Saved {} loot tables to database", count),

View File

@@ -135,6 +135,14 @@ diesel::table! {
} }
} }
diesel::table! {
resource_icons (item_id) {
item_id -> Integer,
name -> Text,
icon_64 -> Binary,
}
}
diesel::table! { diesel::table! {
shops (id) { shops (id) {
id -> Nullable<Integer>, id -> Nullable<Integer>,
@@ -181,6 +189,7 @@ diesel::allow_tables_to_appear_in_same_query!(
npcs, npcs,
player_houses, player_houses,
quests, quests,
resource_icons,
shops, shops,
traits, traits,
world_resources, world_resources,