Initial commit
This commit is contained in:
158
src-tauri/src/commands/gump_commands.rs
Normal file
158
src-tauri/src/commands/gump_commands.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user