resource icons
This commit is contained in:
@@ -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/"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Drop resource_icons table
|
||||||
|
DROP TABLE resource_icons;
|
||||||
@@ -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);
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
40
cursebreaker-parser/src/bin/verify-resource-icons.rs
Normal file
40
cursebreaker-parser/src/bin/verify-resource-icons.rs
Normal 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(())
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user