resource icons
This commit is contained in:
@@ -38,7 +38,8 @@
|
||||
"Bash(DATABASE_URL=cursebreaker.db diesel migration:*)",
|
||||
"Bash(DATABASE_URL=../cursebreaker-parser/cursebreaker.db cargo run:*)",
|
||||
"Bash(identify:*)",
|
||||
"Bash(diesel migration revert:*)"
|
||||
"Bash(diesel migration revert:*)",
|
||||
"Bash(xargs:*)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/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
|
||||
//! - Computing world transforms
|
||||
//! - 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 std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use unity_parser::log::DedupLogger;
|
||||
use log::{info, error, warn, LevelFilter};
|
||||
use std::env;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let logger = DedupLogger::new();
|
||||
@@ -98,6 +100,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
info!("✅ Saved {} resources to database", resource_count);
|
||||
log::logger().flush();
|
||||
|
||||
// Process and save item icons
|
||||
info!("🎨 Processing item icons...");
|
||||
process_item_icons(&cb_assets_path, &mut conn, &scene)?;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Parse error: {}", e);
|
||||
@@ -109,3 +115,93 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
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)?;
|
||||
// info!("✅ Loaded {} quests", quest_db.len());
|
||||
|
||||
// let harvestables_path = format!("{}/Data/XMLs/Harvestables/HarvestableInfo.xml", cb_assets_path);
|
||||
// let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?;
|
||||
// info!("✅ Loaded {} harvestables", harvestable_db.len());
|
||||
let harvestables_path = format!("{}/Data/XMLs/Harvestables/HarvestableInfo.xml", cb_assets_path);
|
||||
let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?;
|
||||
info!("✅ Loaded {} harvestables", harvestable_db.len());
|
||||
|
||||
// let loot_path = format!("{}/Data/XMLs/Loot/Loot.xml", cb_assets_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),
|
||||
// }
|
||||
|
||||
// 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 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),
|
||||
|
||||
@@ -135,6 +135,14 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
resource_icons (item_id) {
|
||||
item_id -> Integer,
|
||||
name -> Text,
|
||||
icon_64 -> Binary,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
shops (id) {
|
||||
id -> Nullable<Integer>,
|
||||
@@ -181,6 +189,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
npcs,
|
||||
player_houses,
|
||||
quests,
|
||||
resource_icons,
|
||||
shops,
|
||||
traits,
|
||||
world_resources,
|
||||
|
||||
Reference in New Issue
Block a user