This commit is contained in:
2023-05-30 22:21:45 +02:00
parent 1ad383448b
commit 4fc1ced4ec
11 changed files with 408 additions and 177 deletions

View File

@@ -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

6
godot/starforce/mhk3.gd Normal file
View File

@@ -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)

View File

@@ -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
);
}*/
}

View File

@@ -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"]

View File

@@ -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<ResourceFormatLoader>,
}
#[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<Gd<DatafileLoader>>,
pub sarc: Option<Gd<SarLoader>>,
pub editor_pck: Option<Gd<EditorPck>>,
}
impl ExtensionLayer for ResourceLoaderLayer {
@@ -37,12 +82,17 @@ impl ExtensionLayer for ResourceLoaderLayer {
auto_register_classes();
self.datafile = Some(Gd::<DatafileLoader>::with_base(DatafileLoader::init));
self.sarc = Some(Gd::<SarLoader>::with_base(SarLoader::init));
self.editor_pck = Some(Gd::<EditorPck>::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;
}
}
}

View File

@@ -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<ImageTexture> {
let mut image: Gd<Image> = 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<ImageTexture> {
let mut image: Gd<ImageTexture> = load(path);
godot_print!("ResPath: {}", image.get_path());
let target_path = image
.get_meta("target_path".into(), Variant::nil())
.to::<GodotString>();
image.set_path(target_path);
godot_print!(
"NowResPath: {} - {}",
image.is_local_to_scene(),
image.get_path()
);
image
}
pub fn collect_clip(target: &mut HashMap<u32, Gd<Texture2D>>, clip: ImageClip) {
@@ -22,11 +26,11 @@ pub fn collect_clip(target: &mut HashMap<u32, Gd<Texture2D>>, 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<u32, Gd<Texture2D>>, 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);
}

View File

@@ -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<PackedScene> {
lightwave_to_gd(LightWaveObject::read_file(path.to_string()).unwrap())
}
}

View File

@@ -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<PackedScene> {
pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Vec<Gd<ArrayMesh>> {
let mut materials = HashMap::<u16, MaterialUvInfo>::new();
let mut textures = HashMap::<u32, Gd<Texture2D>>::new();
let mut layers = vec![];
@@ -22,7 +21,6 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd<PackedScene> {
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<PackedScene> {
.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<PackedScene> {
}
}
/*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()
}

View File

@@ -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::<SarLoader>::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::<Resource>(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
}
}

View File

@@ -1 +1,2 @@
pub mod mhk3_map;
pub mod sar_archive;

View File

@@ -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<T: ToString>(path: &T) -> String {
format!(
"{}{}.res",
INSTALL_PATH,
path.to_string()
.replace(['\\', ':'], "/")
.replace("//", "/")
)
}
fn files_recursive(path: String) -> Vec<String> {
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<u8>,
pub target_path: String,
#[base]
pub base: Base<ResourceFormatLoader>,
}
#[godot_api]
impl SarLoader {}
impl SarLoader {
pub fn list_installed_files() -> Vec<String> {
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::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(),