159 lines
5.0 KiB
Rust
159 lines
5.0 KiB
Rust
use crate::assets::gumpart;
|
|
use crate::assets::art::ArtImage;
|
|
use crate::commands::asset_commands::ArtImageResult;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::Path;
|
|
|
|
/// A single entry from gumps.xml (art gump with numeric ID).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct GumpEntry {
|
|
pub id: u32,
|
|
pub name: String,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
/// A single entry from script_gumps.xml (C# Gump class from ServUO Scripts).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ScriptGumpEntry {
|
|
pub class_name: String,
|
|
pub file_path: String,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
/// Parse gumps.xml from the "UO Gumps" folder and return all entries.
|
|
/// Supports both decimal IDs and 0x-prefixed hex IDs.
|
|
#[tauri::command]
|
|
pub fn list_gumps(app: tauri::AppHandle) -> Result<Vec<GumpEntry>, String> {
|
|
let gump_dir = gumpart::find_gump_dir(&app)
|
|
.ok_or_else(|| "UO Gumps folder not found".to_string())?;
|
|
|
|
let xml_path = gump_dir.join("gumps.xml");
|
|
if !xml_path.exists() {
|
|
return Err("gumps.xml not found in UO Gumps folder".to_string());
|
|
}
|
|
|
|
let content = std::fs::read_to_string(&xml_path)
|
|
.map_err(|e| format!("Failed to read gumps.xml: {}", e))?;
|
|
|
|
let mut entries = Vec::new();
|
|
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
if !trimmed.starts_with("<Gump ") {
|
|
continue;
|
|
}
|
|
|
|
let id = parse_attr(trimmed, "id").and_then(|s| parse_gump_id(&s));
|
|
let name = parse_attr(trimmed, "name");
|
|
let tags = parse_attr(trimmed, "tags");
|
|
|
|
if let (Some(id), Some(name)) = (id, name) {
|
|
let tags_vec: Vec<String> = if let Some(t) = tags {
|
|
t.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
entries.push(GumpEntry { id, name, tags: tags_vec });
|
|
}
|
|
}
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
fn parse_attr(line: &str, attr: &str) -> Option<String> {
|
|
let needle = format!("{}=\"", attr);
|
|
let start = line.find(&needle)? + needle.len();
|
|
let rest = &line[start..];
|
|
let end = rest.find('"')?;
|
|
Some(rest[..end].to_string())
|
|
}
|
|
|
|
fn parse_gump_id(s: &str) -> Option<u32> {
|
|
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
|
|
u32::from_str_radix(hex, 16).ok()
|
|
} else {
|
|
s.parse::<u32>().ok()
|
|
}
|
|
}
|
|
|
|
/// Get a gump image by gump ID.
|
|
/// Priority: bundled BMP → gumpart.mul → gumpartLegacyMUL.uop
|
|
#[tauri::command]
|
|
pub fn get_gump_art(
|
|
app: tauri::AppHandle,
|
|
gump_id: u32,
|
|
uo_root: String,
|
|
) -> Result<ArtImageResult, String> {
|
|
// 1. Bundled BMP
|
|
if let Some(gump_dir) = gumpart::find_gump_dir(&app) {
|
|
if let Ok(img) = gumpart::decode_gump_bmp(&gump_dir, gump_id as usize) {
|
|
return Ok(art_image_to_result(img));
|
|
}
|
|
}
|
|
|
|
// 2. Fall back to MUL / UOP client files
|
|
let root = Path::new(&uo_root);
|
|
let mul_path = root.join("gumpart.mul");
|
|
let idx_path = root.join("gumpartidx.mul");
|
|
let uop_path = root.join("gumpartLegacyMUL.uop");
|
|
|
|
let img = if mul_path.exists() && idx_path.exists() {
|
|
gumpart::decode_gump(&mul_path, &idx_path, gump_id as usize)
|
|
.map_err(|e| e.to_string())?
|
|
} else if uop_path.exists() {
|
|
gumpart::decode_gump_uop(&uop_path, gump_id as usize)
|
|
.map_err(|e| e.to_string())?
|
|
} else {
|
|
return Err("No gump art source found".to_string());
|
|
};
|
|
|
|
Ok(art_image_to_result(img))
|
|
}
|
|
|
|
/// Parse script_gumps.xml from the "UO Gumps" folder and return all script gump entries.
|
|
#[tauri::command]
|
|
pub fn list_script_gumps(app: tauri::AppHandle) -> Result<Vec<ScriptGumpEntry>, String> {
|
|
let gump_dir = gumpart::find_gump_dir_for_scripts(&app)
|
|
.ok_or_else(|| "UO Gumps folder not found".to_string())?;
|
|
|
|
let xml_path = gump_dir.join("script_gumps.xml");
|
|
if !xml_path.exists() {
|
|
return Err(format!("script_gumps.xml not found — searched: {}", gump_dir.display()));
|
|
}
|
|
|
|
let content = std::fs::read_to_string(&xml_path)
|
|
.map_err(|e| format!("Failed to read script_gumps.xml: {}", e))?;
|
|
|
|
let mut entries = Vec::new();
|
|
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
if !trimmed.starts_with("<Gump ") {
|
|
continue;
|
|
}
|
|
|
|
let class_name = parse_attr(trimmed, "class");
|
|
let file_path = parse_attr(trimmed, "file");
|
|
let tags = parse_attr(trimmed, "tags");
|
|
|
|
if let (Some(class_name), Some(file_path)) = (class_name, file_path) {
|
|
let tags_vec: Vec<String> = if let Some(t) = tags {
|
|
t.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
entries.push(ScriptGumpEntry { class_name, file_path, tags: tags_vec });
|
|
}
|
|
}
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
fn art_image_to_result(img: ArtImage) -> ArtImageResult {
|
|
ArtImageResult {
|
|
width: img.width,
|
|
height: img.height,
|
|
pixels: img.pixels,
|
|
}
|
|
}
|