From 4fc1ced4ec4f735d584a8d4d94fb6303cb03409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Tue, 30 May 2023 22:21:45 +0200 Subject: [PATCH] stuff --- godot/main.tscn | 80 +++++----- godot/starforce/mhk3.gd | 6 + godot/starforce/starforce.gdshader | 10 +- godot/starforce/test.tscn | 22 +-- rust/mhgd/src/lib.rs | 68 +++++++-- rust/mhgd/src/lwo/clips.rs | 40 ++--- rust/mhgd/src/lwo/mod.rs | 19 --- rust/mhgd/src/lwo/object.rs | 47 +----- rust/mhgd/src/starforce/mhk3_map.rs | 99 +++++++++++++ rust/mhgd/src/starforce/mod.rs | 1 + rust/mhgd/src/starforce/sar_archive.rs | 193 +++++++++++++++++++++---- 11 files changed, 408 insertions(+), 177 deletions(-) create mode 100644 godot/starforce/mhk3.gd create mode 100644 rust/mhgd/src/starforce/mhk3_map.rs diff --git a/godot/main.tscn b/godot/main.tscn index 8085ab3..d814bde 100644 --- a/godot/main.tscn +++ b/godot/main.tscn @@ -1,25 +1,25 @@ [gd_scene load_steps=21 format=3 uid="uid://cmqfu6cc780h4"] -[ext_resource type="Theme" uid="uid://ks2uyxqg6u4k" path="res://mhjnr/theme.tres" id="1_24af1"] -[ext_resource type="Script" path="res://main.gd" id="2_3ufyn"] -[ext_resource type="Texture2D" uid="uid://dyga5qn124307" path="res://cover_art/schatzjaeger.webp" id="3_dq726"] -[ext_resource type="Texture2D" uid="uid://by4wug5r7311q" path="res://cover_art/schatzjaeger_2.jpg" id="4_qurcc"] -[ext_resource type="Texture2D" uid="uid://bkn3cdrm1fj8b" path="res://cover_art/schatzjaeger_3.jpg" id="5_wceoy"] -[ext_resource type="Texture2D" uid="uid://8vn1dpq37mve" path="res://cover_art/atlantis.webp" id="6_mwmag"] -[ext_resource type="Texture2D" uid="uid://bk5a3xob0raqj" path="res://cover_art/mh_kart.jpg" id="7_kndjp"] -[ext_resource type="Texture2D" uid="uid://dkhygfyylbjov" path="res://cover_art/mh_kart2.jpg" id="8_r2mlu"] -[ext_resource type="Texture2D" uid="uid://l8groudaf385" path="res://cover_art/mh_kart3.webp" id="9_yfa8e"] -[ext_resource type="Texture2D" uid="uid://cjp0ilsodyu4j" path="res://cover_art/mh_kart4.jpg" id="10_s4ouh"] -[ext_resource type="Texture2D" uid="uid://wetnflcj1b0w" path="res://cover_art/mh1.jpg" id="11_0icjw"] -[ext_resource type="Texture2D" uid="uid://baqjofchj6yaw" path="res://cover_art/mhw.webp" id="12_8qgke"] -[ext_resource type="Texture2D" uid="uid://brovl1cel1uiw" path="res://cover_art/mh2.jpg" id="13_dypyq"] -[ext_resource type="Texture2D" uid="uid://ix132vewff0h" path="res://cover_art/mh3.jpg" id="14_tc2rn"] -[ext_resource type="Texture2D" uid="uid://k8i84cvnuun" path="res://cover_art/mhx.jpg" id="15_gxg0f"] -[ext_resource type="Texture2D" uid="uid://bst48atkwn7r1" path="res://cover_art/mhrem.png" id="16_dtmhv"] -[ext_resource type="Texture2D" uid="uid://l37kev18676o" path="res://cover_art/mhwant.jpg" id="17_pcn4f"] -[ext_resource type="Texture2D" uid="uid://cpigwqlll0kbf" path="res://cover_art/mhpir.jpg" id="18_w72rq"] -[ext_resource type="Texture2D" uid="uid://chcjw51coc53r" path="res://cover_art/mhinv.jpg" id="19_31858"] -[ext_resource type="Texture2D" uid="uid://bmybnhi2i2ep" path="res://cover_art/mhdir.jpg" id="20_b2doc"] +[ext_resource type="Theme" uid="uid://ks2uyxqg6u4k" path="res://mhjnr/theme.tres" id="1_knjtf"] +[ext_resource type="Script" path="res://main.gd" id="2_p7swk"] +[ext_resource type="Texture2D" uid="uid://dyga5qn124307" path="res://cover_art/schatzjaeger.webp" id="3_5nh86"] +[ext_resource type="Texture2D" uid="uid://by4wug5r7311q" path="res://cover_art/schatzjaeger_2.jpg" id="4_f32r0"] +[ext_resource type="Texture2D" uid="uid://bkn3cdrm1fj8b" path="res://cover_art/schatzjaeger_3.jpg" id="5_wx0lg"] +[ext_resource type="Texture2D" uid="uid://8vn1dpq37mve" path="res://cover_art/atlantis.webp" id="6_hvil1"] +[ext_resource type="Texture2D" uid="uid://bk5a3xob0raqj" path="res://cover_art/mh_kart.jpg" id="7_2e20o"] +[ext_resource type="Texture2D" uid="uid://dkhygfyylbjov" path="res://cover_art/mh_kart2.jpg" id="8_txpwp"] +[ext_resource type="Texture2D" uid="uid://l8groudaf385" path="res://cover_art/mh_kart3.webp" id="9_lxivp"] +[ext_resource type="Texture2D" uid="uid://cjp0ilsodyu4j" path="res://cover_art/mh_kart4.jpg" id="10_7ea4r"] +[ext_resource type="Texture2D" uid="uid://wetnflcj1b0w" path="res://cover_art/mh1.jpg" id="11_jg4b8"] +[ext_resource type="Texture2D" uid="uid://baqjofchj6yaw" path="res://cover_art/mhw.webp" id="12_rg2iw"] +[ext_resource type="Texture2D" uid="uid://brovl1cel1uiw" path="res://cover_art/mh2.jpg" id="13_q5tum"] +[ext_resource type="Texture2D" uid="uid://ix132vewff0h" path="res://cover_art/mh3.jpg" id="14_pi7qg"] +[ext_resource type="Texture2D" uid="uid://k8i84cvnuun" path="res://cover_art/mhx.jpg" id="15_gwira"] +[ext_resource type="Texture2D" uid="uid://bst48atkwn7r1" path="res://cover_art/mhrem.png" id="16_mgcbp"] +[ext_resource type="Texture2D" uid="uid://l37kev18676o" path="res://cover_art/mhwant.jpg" id="17_xtbol"] +[ext_resource type="Texture2D" uid="uid://cpigwqlll0kbf" path="res://cover_art/mhpir.jpg" id="18_s6b4p"] +[ext_resource type="Texture2D" uid="uid://chcjw51coc53r" path="res://cover_art/mhinv.jpg" id="19_f2jto"] +[ext_resource type="Texture2D" uid="uid://bmybnhi2i2ep" path="res://cover_art/mhdir.jpg" id="20_bo8ks"] [node name="main" type="ScrollContainer"] anchors_preset = 15 @@ -29,8 +29,8 @@ offset_right = 20.0 offset_bottom = 154.0 grow_horizontal = 2 grow_vertical = 2 -theme = ExtResource("1_24af1") -script = ExtResource("2_3ufyn") +theme = ExtResource("1_knjtf") +script = ExtResource("2_p7swk") [node name="margins" type="MarginContainer" parent="."] layout_mode = 2 @@ -55,7 +55,7 @@ layout_mode = 2 [node name="Schatzjaeger1" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("3_dq726") +icon = ExtResource("3_5nh86") flat = true icon_alignment = 1 expand_icon = true @@ -63,7 +63,7 @@ expand_icon = true [node name="Schatzjaeger2" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("4_qurcc") +icon = ExtResource("4_f32r0") flat = true icon_alignment = 1 expand_icon = true @@ -71,7 +71,7 @@ expand_icon = true [node name="Schatzjaeger3" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("5_wceoy") +icon = ExtResource("5_wx0lg") flat = true icon_alignment = 1 expand_icon = true @@ -80,7 +80,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("6_mwmag") +icon = ExtResource("6_hvil1") flat = true icon_alignment = 1 expand_icon = true @@ -100,7 +100,7 @@ layout_mode = 2 custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("7_kndjp") +icon = ExtResource("7_2e20o") flat = true icon_alignment = 1 expand_icon = true @@ -109,7 +109,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("8_r2mlu") +icon = ExtResource("8_txpwp") flat = true icon_alignment = 1 expand_icon = true @@ -118,7 +118,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("9_yfa8e") +icon = ExtResource("9_lxivp") flat = true icon_alignment = 1 expand_icon = true @@ -127,7 +127,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("10_s4ouh") +icon = ExtResource("10_7ea4r") flat = true icon_alignment = 1 expand_icon = true @@ -147,7 +147,7 @@ layout_mode = 2 custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("11_0icjw") +icon = ExtResource("11_jg4b8") flat = true icon_alignment = 1 expand_icon = true @@ -156,7 +156,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("12_8qgke") +icon = ExtResource("12_rg2iw") flat = true icon_alignment = 1 expand_icon = true @@ -165,7 +165,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("13_dypyq") +icon = ExtResource("13_q5tum") flat = true icon_alignment = 1 expand_icon = true @@ -174,7 +174,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("14_tc2rn") +icon = ExtResource("14_pi7qg") flat = true icon_alignment = 1 expand_icon = true @@ -183,7 +183,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("15_gxg0f") +icon = ExtResource("15_gwira") flat = true icon_alignment = 1 expand_icon = true @@ -192,7 +192,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("16_dtmhv") +icon = ExtResource("16_mgcbp") flat = true icon_alignment = 1 expand_icon = true @@ -201,7 +201,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("17_pcn4f") +icon = ExtResource("17_xtbol") flat = true icon_alignment = 1 expand_icon = true @@ -210,7 +210,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("18_w72rq") +icon = ExtResource("18_s6b4p") flat = true icon_alignment = 1 expand_icon = true @@ -219,7 +219,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("19_31858") +icon = ExtResource("19_f2jto") flat = true icon_alignment = 1 expand_icon = true @@ -228,7 +228,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("20_b2doc") +icon = ExtResource("20_bo8ks") flat = true icon_alignment = 1 expand_icon = true diff --git a/godot/starforce/mhk3.gd b/godot/starforce/mhk3.gd new file mode 100644 index 0000000..a886001 --- /dev/null +++ b/godot/starforce/mhk3.gd @@ -0,0 +1,6 @@ +@tool +extends EditorScript + +func _run(): + var result = Mhk3Map.install("/home/theaninova/Projects/mhlib/games/Moorhuhn Kart 3/data.sar", "mhk3") + print(result) diff --git a/godot/starforce/starforce.gdshader b/godot/starforce/starforce.gdshader index fdfa7db..bf3e7d7 100644 --- a/godot/starforce/starforce.gdshader +++ b/godot/starforce/starforce.gdshader @@ -52,15 +52,15 @@ vec3 project( case 3: vec3 p = (transform * vec4(position, 1.0)).xyz; vec3 n = normalize(abs(mat3(transform) * normal)); - vec2 uv2 = (n.x > n.y && n.x > n.z) ? vec2(p.z, p.y) - : ((n.y > n.x && n.y > n.z) ? vec2(p.x, p.z) : vec2(p.x, p.y)); + vec2 uv2 = (n.x > n.y && n.x > n.z) ? p.zy + : ((n.y > n.x && n.y > n.z) ? p.zx : p.xy); return texture(tex, uv2 + 0.5).rgb; case 2: // Spherical return vec3(0.0, 0.0, 1.0); case 1: // Cylindrical return vec3(1.0, 1.0, 0.0); case 0: // Planar - return texture(tex, (transform * vec4(position, 1.0)).xz).rgb; + return texture(tex, (transform * vec4(position, 1.0)).zx + 0.5).rgb; default: return vec3(0.0); } @@ -92,7 +92,7 @@ void fragment() { ); } -/*void light() { +void light() { DIFFUSE_LIGHT =project( tex_diffuse, tex_diffuse_projection, @@ -102,4 +102,4 @@ void fragment() { tex_diffuse_projection_world_coords, UV ); -}*/ +} diff --git a/godot/starforce/test.tscn b/godot/starforce/test.tscn index 07489ff..546b854 100644 --- a/godot/starforce/test.tscn +++ b/godot/starforce/test.tscn @@ -2,26 +2,14 @@ [sub_resource type="GDScript" id="GDScript_jhapx"] resource_name = "load" -script/source = "@tool -extends Node3D +script/source = "extends Node3D -@export var load_list = [ - \"anlagen\", - \"boden\", - \"bok\", - \"fensterscheiben\", - \"gelaender\", - \"kraftfelder\", - \"rundgang\", - \"stampfer\", - \"timetunnel\", - \"waende\", -] +@export var perform_install_on_start = true func _ready(): - for item in load_list: - var scene = load(\"sar://D:/Moorhuhnkart/3dobjects_tracks/track07_ufo/%s.lwo\" % item).instantiate() - add_child(scene) + if perform_install_on_start: + var result = Mhk3Map.install(\"/home/theaninova/Projects/mhlib/games/Moorhuhn Kart 3/data.sar\", \"mhk3\") + print(result) " [node name="test" type="Node3D"] diff --git a/rust/mhgd/src/lib.rs b/rust/mhgd/src/lib.rs index 5fde8f8..982d248 100644 --- a/rust/mhgd/src/lib.rs +++ b/rust/mhgd/src/lib.rs @@ -1,9 +1,17 @@ use crate::sproing::datafile::DatafileLoader; -use crate::starforce::sar_archive::SarLoader; +use crate::starforce::sar_archive::{GAMES_PATH, INSTALL_PATH}; use ::godot::engine::class_macros::auto_register_classes; use ::godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader}; use ::godot::init::{gdextension, ExtensionLayer}; use ::godot::prelude::{ExtensionLibrary, Gd, InitHandle, InitLevel, Share}; +use godot::bind::godot_api; +use godot::builtin::{GodotString, StringName, ToVariant, Variant}; +use godot::engine::global::Error; +use godot::engine::resource_loader::CacheMode; +use godot::engine::{Engine, ResourceFormatLoader}; +use godot::log::godot_error; +use godot::obj::EngineEnum; +use godot::prelude::{Base, GodotClass}; pub mod data_installer; pub mod lwo; @@ -20,16 +28,53 @@ unsafe impl ExtensionLibrary for Main { InitLevel::Editor, ResourceLoaderLayer { datafile: None, - sarc: None, + editor_pck: None, }, ); true } } +#[derive(GodotClass)] +#[class(base=ResourceFormatLoader)] +struct EditorPck { + #[base] + pub base: Base, +} +#[godot_api] +impl ResourceFormatLoaderVirtual for EditorPck { + fn recognize_path(&self, path: GodotString, type_: StringName) -> bool { + path.to_string().starts_with(GAMES_PATH) + } + + fn load( + &self, + path: GodotString, + original_path: GodotString, + use_sub_threads: bool, + cache_mode: i64, + ) -> Variant { + let original_path = format!( + "{}{}", + INSTALL_PATH, + original_path.to_string().strip_prefix(GAMES_PATH).unwrap() + ); + if let Some(resource) = ResourceLoader::singleton().load( + original_path.clone().into(), + "".into(), + CacheMode::from_ord(cache_mode as i32), + ) { + resource.to_variant() + } else { + godot_error!("Could not find {}", original_path); + Error::ERR_FILE_NOT_FOUND.to_variant() + } + } +} + struct ResourceLoaderLayer { pub datafile: Option>, - pub sarc: Option>, + pub editor_pck: Option>, } impl ExtensionLayer for ResourceLoaderLayer { @@ -37,12 +82,17 @@ impl ExtensionLayer for ResourceLoaderLayer { auto_register_classes(); self.datafile = Some(Gd::::with_base(DatafileLoader::init)); - self.sarc = Some(Gd::::with_base(SarLoader::init)); + self.editor_pck = Some(Gd::::with_base(|base| EditorPck { base })); + + if Engine::singleton().is_editor_hint() { + ResourceLoader::singleton().add_resource_format_loader( + self.editor_pck.as_ref().unwrap().share().upcast(), + true, + ) + } ResourceLoader::singleton() .add_resource_format_loader(self.datafile.as_ref().unwrap().share().upcast(), true); - ResourceLoader::singleton() - .add_resource_format_loader(self.sarc.as_ref().unwrap().share().upcast(), true); } fn deinitialize(&mut self) { @@ -50,9 +100,9 @@ impl ExtensionLayer for ResourceLoaderLayer { ResourceLoader::singleton().remove_resource_format_loader(datafile.share().upcast()); self.datafile = None; } - if let Some(sarc) = &self.sarc { - ResourceLoader::singleton().remove_resource_format_loader(sarc.share().upcast()); - self.sarc = None; + if let Some(editor_pck) = &self.editor_pck { + ResourceLoader::singleton().remove_resource_format_loader(editor_pck.share().upcast()); + self.editor_pck = None; } } } diff --git a/rust/mhgd/src/lwo/clips.rs b/rust/mhgd/src/lwo/clips.rs index 1953d15..8a63014 100644 --- a/rust/mhgd/src/lwo/clips.rs +++ b/rust/mhgd/src/lwo/clips.rs @@ -1,20 +1,24 @@ +use crate::starforce::sar_archive::sarc_path_to_gd; +use godot::builtin::{GodotString, Variant}; use godot::engine::{load, AnimatedTexture, Image, ImageTexture, Texture2D}; -use godot::log::godot_error; +use godot::log::{godot_error, godot_print}; use godot::obj::Gd; use lightwave_3d::lwo2::tags::image_clip::{ImageClip, ImageClipSubChunk}; use std::collections::HashMap; -fn convert_path(path: &str) -> String { - path.replace('\\', "/").replace(':', ":/") -} - -fn load_texture(path: &str, name: &str) -> Gd { - let mut image: Gd = load(path); - image.set_name(name.into()); - let mut texture = ImageTexture::new(); - texture.set_name(name.into()); - texture.set_image(image); - texture +fn load_texture(path: &str) -> Gd { + let mut image: Gd = load(path); + godot_print!("ResPath: {}", image.get_path()); + let target_path = image + .get_meta("target_path".into(), Variant::nil()) + .to::(); + image.set_path(target_path); + godot_print!( + "NowResPath: {} - {}", + image.is_local_to_scene(), + image.get_path() + ); + image } pub fn collect_clip(target: &mut HashMap>, clip: ImageClip) { @@ -22,11 +26,11 @@ pub fn collect_clip(target: &mut HashMap>, clip: ImageClip) { match attributes.next().unwrap() { ImageClipSubChunk::StillImage(still) => { - let path = format!("sar://{}", convert_path(&still.name)); + let path = sarc_path_to_gd(&still.name); for meta in attributes { godot_error!("TODO: {:?}", meta) } - target.insert(clip.index, load_texture(&path, &still.name).upcast()); + target.insert(clip.index, load_texture(&path).upcast()); } ImageClipSubChunk::ImageSequence(sequence) => { let mut texture = AnimatedTexture::new(); @@ -47,15 +51,17 @@ pub fn collect_clip(target: &mut HashMap>, clip: ImageClip) { for i in sequence.data.start..sequence.data.end { let path = format!( - "sar://{}{:0width$}{}", - convert_path(&sequence.data.prefix), + "{}{:0width$}{}.res", + sarc_path_to_gd(&sequence.data.prefix) + .strip_suffix(".res") + .unwrap(), i, sequence.data.suffix, width = sequence.data.num_digits as usize ); let frame = i as i64 - sequence.data.start as i64; - texture.set_frame_texture(frame, load_texture(&path, &i.to_string()).upcast()); + texture.set_frame_texture(frame, load_texture(&path).upcast()); texture.set_frame_duration(frame, frame_duration); } diff --git a/rust/mhgd/src/lwo/mod.rs b/rust/mhgd/src/lwo/mod.rs index a707b2c..7666843 100644 --- a/rust/mhgd/src/lwo/mod.rs +++ b/rust/mhgd/src/lwo/mod.rs @@ -1,10 +1,3 @@ -use crate::lwo::object::lightwave_to_gd; -use godot::bind::{godot_api, GodotClass}; -use godot::builtin::GodotString; -use godot::engine::{ArrayMesh, PackedScene}; -use godot::obj::Gd; -use lightwave_3d::LightWaveObject; - pub(crate) mod clips; pub(crate) mod intermediate_layer; pub(crate) mod mapping; @@ -12,15 +5,3 @@ pub(crate) mod material; pub(crate) mod object; pub(crate) mod surface_info; pub(crate) mod unique_vertex; - -#[derive(GodotClass)] -#[class(init)] -struct Lwo {} - -#[godot_api] -impl Lwo { - #[func] - pub fn get_mesh(path: GodotString) -> Gd { - lightwave_to_gd(LightWaveObject::read_file(path.to_string()).unwrap()) - } -} diff --git a/rust/mhgd/src/lwo/object.rs b/rust/mhgd/src/lwo/object.rs index f3adf11..bd338fc 100644 --- a/rust/mhgd/src/lwo/object.rs +++ b/rust/mhgd/src/lwo/object.rs @@ -3,16 +3,15 @@ use crate::lwo::intermediate_layer::IntermediateLayer; use crate::lwo::mapping::{collect_discontinuous_mappings, collect_mappings}; use crate::lwo::material::MaterialUvInfo; use godot::builtin::{Vector2, Vector3}; -use godot::engine::node::InternalMode; -use godot::engine::{MeshInstance3D, Node3D, PackedScene, Texture2D}; -use godot::log::{godot_error, godot_print, godot_warn}; -use godot::obj::{Gd, Share}; +use godot::engine::{ArrayMesh, Texture2D}; +use godot::log::{godot_error, godot_warn}; +use godot::obj::Gd; use itertools::Itertools; use lightwave_3d::lwo2::tags::Tag; use lightwave_3d::LightWaveObject; use std::collections::HashMap; -pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { +pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Vec> { let mut materials = HashMap::::new(); let mut textures = HashMap::>::new(); let mut layers = vec![]; @@ -22,7 +21,6 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { match tag { Tag::TagStrings(it) => { tag_strings = it.data.tag_strings.into_iter().collect(); - godot_print!("{:?}", tag_strings); } Tag::Layer(layer_tag) => { layers.push(IntermediateLayer { @@ -143,7 +141,6 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { .iter() .find_position(|name| name == &&surf_name) .expect("Invalid File"); - godot_print!("'{}': {}", surf_name, tag_index); materials.insert( tag_index as u16, @@ -157,36 +154,8 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { } } - /*godot_print!( - "{:?}", - surfaces - .iter() - .map(|(k, v)| ( - k, - tag_strings[*k as usize].clone(), - v.material.get_shader_parameter("tex_diffuse".into()), - v.material.get_shader_parameter("tex_color".into()) - )) - .collect_vec() - );*/ - - let mut root_node = Node3D::new_alloc(); - - for layer in layers { - let mut instance = MeshInstance3D::new_alloc(); - instance.set_name(layer.name.clone().into()); - instance.set_mesh(layer.commit(&materials).upcast()); - - root_node.add_child( - instance.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); - instance.set_owner(root_node.share().upcast()); - } - - let mut scene = PackedScene::new(); - scene.pack(root_node.share().upcast()); - root_node.queue_free(); - scene + layers + .into_iter() + .map(|layer| layer.commit(&materials)) + .collect() } diff --git a/rust/mhgd/src/starforce/mhk3_map.rs b/rust/mhgd/src/starforce/mhk3_map.rs new file mode 100644 index 0000000..e240fae --- /dev/null +++ b/rust/mhgd/src/starforce/mhk3_map.rs @@ -0,0 +1,99 @@ +use crate::starforce::sar_archive::{sarc_path_to_gd, SarLoader, GAMES_PATH}; +use godot::bind::godot_api; +use godot::builtin::GodotString; +use godot::engine::file_access::ModeFlags; +use godot::engine::global::Error; +use godot::engine::{try_load, DirAccess, FileAccess, PckPacker, Resource, ResourceLoader}; +use godot::log::godot_print; +use godot::obj::{Gd, Share}; +use godot::prelude::GodotClass; +use itertools::Itertools; +use starforcelib::sarc::SarcArchive; +use std::collections::HashSet; +use std::io::Cursor; +use std::iter::{Chain, FlatMap}; +use std::path::Iter; +use std::vec::IntoIter; + +/// This is supposedly to be the default. +const KEY: &str = "0000000000000000000000000000000000000000000000000000000000000000"; + +#[derive(GodotClass)] +#[class(init)] +pub struct Mhk3Map {} + +#[godot_api] +impl Mhk3Map { + #[func] + pub fn get_available_maps() {} + + #[func] + pub fn test() {} + + #[func] + pub fn install(path: GodotString, game: GodotString) -> Error { + let file = if let Some(file) = FileAccess::open(path, ModeFlags::READ) { + file + } else { + return Error::ERR_FILE_NOT_FOUND; + }; + + let data = file.get_buffer(file.get_length()).to_vec(); + let mut archive = match SarcArchive::read(&mut Cursor::new(&data)) { + Ok(archive) => archive, + Err(error) => return Error::ERR_FILE_CORRUPT, + }; + let mut files_to_convert = HashSet::new(); + for file in archive.files.iter_mut() { + let convert = file.path.ends_with(".lwo"); + file.path = sarc_path_to_gd(&file.path); + if convert { + files_to_convert.insert(file.path.clone()); + } + } + + files_to_convert.remove( + "user://.install/D/Moorhuhnkart/3dobjects_tracks/track01_steinzeit/pflanzen.lwo.res", + ); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch2.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch3.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch4.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch5.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch6.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch7.lwo.res"); + files_to_convert.remove("user://.install/D/Moorhuhnkart/3dobjects_extras/rauch8.lwo.res"); + files_to_convert + .remove("user://.install/D/Moorhuhnkart/3dobjects_extras/schutzschild.lwo.res"); + files_to_convert.remove( + "user://.install/D/Moorhuhnkart/menu/kart_select/menu_character_moorhuhn.lwo.res", + ); + + let sar_loader = Gd::::with_base(|base| SarLoader { + archive, + data, + target_path: game.to_string(), + base, + }); + + ResourceLoader::singleton().add_resource_format_loader(sar_loader.share().upcast(), true); + for (i, file) in files_to_convert.into_iter().sorted().enumerate() { + godot_print!("{}x Next up: {}", i, file); + try_load::(file); + } + ResourceLoader::singleton().remove_resource_format_loader(sar_loader.upcast()); + + let mut packer = PckPacker::new(); + packer.pck_start(format!("user://{}.pck", game).into(), 32, KEY.into(), false); + for file in SarLoader::list_installed_files() { + packer.add_file( + SarLoader::resource_path_at(file.clone(), &game), + file.clone().into(), + false, + ); + } + packer.flush(true); + + Error::OK + } +} diff --git a/rust/mhgd/src/starforce/mod.rs b/rust/mhgd/src/starforce/mod.rs index 8e63c4d..4e66f8b 100644 --- a/rust/mhgd/src/starforce/mod.rs +++ b/rust/mhgd/src/starforce/mod.rs @@ -1 +1,2 @@ +pub mod mhk3_map; pub mod sar_archive; diff --git a/rust/mhgd/src/starforce/sar_archive.rs b/rust/mhgd/src/starforce/sar_archive.rs index a26be52..4f84bf9 100644 --- a/rust/mhgd/src/starforce/sar_archive.rs +++ b/rust/mhgd/src/starforce/sar_archive.rs @@ -1,41 +1,81 @@ use crate::lwo::object::lightwave_to_gd; -use dds::{Compression, PixelFormat, DDS}; +use dds::DDS; use godot::bind::{godot_api, GodotClass}; use godot::builtin::{GodotString, PackedByteArray, StringName, ToVariant, Variant}; use godot::engine::global::Error; use godot::engine::image::Format; -use godot::engine::{Image, ImageTexture, ResourceFormatLoader, ResourceFormatLoaderVirtual}; -use godot::obj::{Base, Gd}; +use godot::engine::resource_loader::CacheMode; +use godot::engine::resource_saver::SaverFlags; +use godot::engine::{ + DirAccess, FileAccess, Image, ImageTexture, Resource, ResourceFormatLoader, + ResourceFormatLoaderVirtual, ResourceLoader, ResourceSaver, Texture2D, +}; +use godot::log::godot_error; +use godot::obj::{Base, Share}; +use godot::prelude::godot_print; use lightwave_3d::LightWaveObject; use starforcelib::sarc::SarcArchive; -use std::fs::File; use std::io::Cursor; -const SAR_PATH: &str = r#"../games/Moorhuhn Kart 3/data.sar"#; +pub const GAMES_PATH: &str = "res://games/"; +pub const INSTALL_PATH: &str = "user://.install/"; + +pub fn sarc_path_to_gd(path: &T) -> String { + format!( + "{}{}.res", + INSTALL_PATH, + path.to_string() + .replace(['\\', ':'], "/") + .replace("//", "/") + ) +} + +fn files_recursive(path: String) -> Vec { + DirAccess::get_directories_at(path.clone().into()) + .to_vec() + .into_iter() + .map(|it| format!("{}/{}", path, it)) + .flat_map(files_recursive) + .chain( + DirAccess::get_files_at(path.clone().into()) + .to_vec() + .into_iter() + .map(|it| format!("{}/{}", path, it)), + ) + .collect() +} #[derive(GodotClass)] #[class(base=ResourceFormatLoader)] pub struct SarLoader { pub archive: SarcArchive, + pub data: Vec, + pub target_path: String, #[base] pub base: Base, } #[godot_api] -impl SarLoader {} +impl SarLoader { + pub fn list_installed_files() -> Vec { + files_recursive(INSTALL_PATH.strip_suffix('/').unwrap().into()) + } + + pub fn resource_path_at(path: String, game: &GodotString) -> GodotString { + format!( + "{}/{}", + GAMES_PATH, + path.strip_prefix(INSTALL_PATH).unwrap() + ) + .into() + } +} #[godot_api] impl ResourceFormatLoaderVirtual for SarLoader { - fn init(base: Base) -> Self { - let mut file = File::open(SAR_PATH).unwrap(); - let archive = SarcArchive::read(&mut file).unwrap(); - - Self { base, archive } - } - fn recognize_path(&self, path: GodotString, type_: StringName) -> bool { - path.to_string().starts_with("sar://") + path.to_string().starts_with(INSTALL_PATH) /*|| path.to_string().starts_with(GAMES_PATH)*/ } fn load( @@ -45,32 +85,84 @@ impl ResourceFormatLoaderVirtual for SarLoader { use_sub_threads: bool, cache_mode: i64, ) -> Variant { - let internal_path = original_path - .to_string() - .strip_prefix("sar://") - .unwrap() - .replace('/', "\\"); + let path = original_path.to_string(); + let original_path = if path.starts_with(GAMES_PATH) { + path.strip_prefix(GAMES_PATH) + .unwrap() + .strip_prefix(&self.target_path) + .unwrap() + .strip_prefix('/') + .unwrap() + } else { + path.strip_prefix(INSTALL_PATH).unwrap() + }; + let internal_path: GodotString = format!("{}{}", INSTALL_PATH, original_path).into(); + let original_path: GodotString = format!( + "{}{}/{}", + INSTALL_PATH, + self.target_path, + original_path + .strip_prefix(&format!("{}/", self.target_path)) + .unwrap_or(original_path) + ) + .into(); - match path.to_string().rsplit_once('.') { + match internal_path + .to_string() + .strip_suffix(".res") + .unwrap_or(&internal_path.to_string()) + .rsplit_once('.') + { Some((_, "lwo")) => { - let mut f = File::open(SAR_PATH).unwrap(); let data = self .archive - .extract(&mut f, internal_path.as_str()) + .extract(&mut Cursor::new(&self.data), &internal_path.to_string()) .unwrap(); let obj = LightWaveObject::read(&mut Cursor::new(data)).unwrap(); - lightwave_to_gd(obj).to_variant() + + let directory = original_path + .to_string() + .strip_suffix(".res") + .unwrap() + .to_string(); + + DirAccess::make_dir_recursive_absolute(directory.clone().into()); + for mut mesh in lightwave_to_gd(obj) { + let name = mesh.get_name(); + let path = format!("{}/{}.res", directory, name); + + mesh.set_path(Self::resource_path_at( + path.to_string(), + &self.target_path.clone().into(), + )); + println!("{}", path); + ResourceSaver::singleton().save( + mesh.upcast(), + path.into(), + SaverFlags::FLAG_CHANGE_PATH, + ); + } + + Resource::new().to_variant() } - Some((_, "bmp" | "dds")) => { - let mut f = File::open(SAR_PATH).unwrap(); + Some((base, "bmp" | "dds")) => { + if FileAccess::file_exists(original_path.clone()) { + godot_print!("Reusing {}", original_path); + return ResourceLoader::singleton() + .load(original_path, "".into(), CacheMode::CACHE_MODE_REUSE) + .to_variant(); + } let mut image = Image::new(); - if let Ok(bmp) = self.archive.extract(&mut f, internal_path.as_str()) { - image.load_bmp_from_buffer(PackedByteArray::from(bmp.as_slice())); - } else if let Ok(dds) = self + if let Ok(bmp) = self .archive - .extract(&mut f, format!("{}.dds", internal_path).as_str()) + .extract(&mut Cursor::new(&self.data), &internal_path.to_string()) { + image.load_bmp_from_buffer(PackedByteArray::from(bmp.as_slice())); + } else if let Ok(dds) = self.archive.extract( + &mut Cursor::new(&self.data), + format!("{}.bmp.dds.res", &base).as_str(), + ) { let data = DDS::decode(&mut Cursor::new(dds)).unwrap(); image.set_data( data.header.width as i64, @@ -81,9 +173,48 @@ impl ResourceFormatLoaderVirtual for SarLoader { .iter() .flat_map(|px| [px.r, px.g, px.b, px.a]) .collect(), - ) + ); + image.decompress(); + } else { + godot_error!("Could not find {} or {}.bmp.dds.res", original_path, base) } - image.to_variant() + + image.generate_mipmaps(true); + + image.set_name( + original_path + .to_string() + .rsplit_once('/') + .unwrap() + .1 + .strip_suffix(".res") + .unwrap() + .into(), + ); + + println!("{}", original_path); + // image.set_path(cache_path.to_string().into()); + DirAccess::make_dir_recursive_absolute( + original_path.to_string().rsplit_once('/').unwrap().0.into(), + ); + + let mut texture = ImageTexture::new(); + texture.set_name(image.get_name()); + texture.set_image(image); + + let target_path = Self::resource_path_at( + original_path.clone().into(), + &self.target_path.clone().into(), + ); + texture.set_meta("target_path".into(), target_path.to_variant()); + texture.set_path(target_path); + ResourceSaver::singleton().save( + texture.share().upcast(), + original_path, + SaverFlags::FLAG_COMPRESS, + ); + + texture.to_variant() } None => Error::ERR_FILE_UNRECOGNIZED.to_variant(), _ => Error::ERR_BUG.to_variant(),