This commit is contained in:
2023-05-06 02:26:58 +02:00
parent b16f76bcec
commit 45c130f3d7
19 changed files with 792 additions and 98 deletions

View File

@@ -5,6 +5,8 @@ use crate::formats::sprites::Sprites;
use crate::formats::txt::{decrypt_txt, DecryptError};
use crate::formats::ui_xml::UiTag;
use binrw::BinRead;
use itertools::Itertools;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::io::{Cursor, Read, Seek, SeekFrom};
@@ -26,6 +28,7 @@ pub enum DatafileFile {
Vorbis(Vec<u8>),
TileCollision(String),
Ui(UiTag),
Translations(HashMap<String, Vec<String>>),
}
pub enum Error {
@@ -90,26 +93,20 @@ where
Ok(DatafileFile::Txt(decr))
}
}
/*Some("rle") => {
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
let path = Path::new(dat_path).with_file_name("res.gif");
println!("{:?}", path);
let mut encoder = GifEncoder::new(
OpenOptions::new()
.create(true)
.write(true)
.open(path)
.unwrap(),
);
encoder.set_repeat(Repeat::Infinite).unwrap();
encoder.try_encode_frames(image.into_frames()).unwrap();
}
Some("dat") => {
let image = level_tile_data_to_image(&data).unwrap();
let path = Path::new(dat_path).with_file_name("res.png");
println!("{:?}", path);
image.save_with_format(path, ImageFormat::Png).unwrap();
}*/
"csv" => Ok(DatafileFile::Translations(
String::from_utf8(data)
.unwrap()
.split('\n')
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.map(|l| {
l.splitn(2, ';')
.map(|s| s.to_string())
.collect_tuple::<(String, String)>()
.expect("Invalid csv")
})
.into_group_map(),
)),
ext => Err(Error::UnknownFormat(ext.to_string())),
}
}

View File

@@ -16,7 +16,7 @@ pub struct UiMenu {
pub selected: String,
#[serde(rename = "OnBack")]
pub on_back: Option<String>,
#[serde(rename = "$value")]
#[serde(rename = "$value", default)]
pub children: Vec<UiTag>,
}
@@ -45,13 +45,14 @@ pub struct UiTextButton {
pub on_select: String,
}
/// This sometimes appears completely empty
#[derive(Debug, Deserialize)]
pub struct UiTextArea {
#[serde(deserialize_with = "deserialize_vec2")]
#[serde(deserialize_with = "deserialize_vec2", default)]
pub position: [i32; 2],
#[serde(deserialize_with = "deserialize_vec2")]
#[serde(deserialize_with = "deserialize_vec2", default)]
pub size: [i32; 2],
#[serde(rename = "$value")]
#[serde(rename = "$value", default)]
pub children: Vec<UiTag>,
}

View File

@@ -1,6 +1,5 @@
use crate::formats;
use crate::formats::datafile::{Datafile, FileEntry};
use crate::formats::sprites::{CropMode, RenderMode, SpriteType};
use crate::formats::{load_data, DatafileFile};
use crate::godot::font::load_bitmap_font;
use crate::godot::game_object::parse_game_object;
@@ -10,16 +9,11 @@ use crate::godot::tile_map::{create_tile_map, TileCollision};
use crate::godot::ui::convert_ui;
use binrw::BinRead;
use godot::engine::global::Error;
use godot::engine::image::Format;
use godot::engine::resource_loader::CacheMode;
use godot::engine::resource_saver::SaverFlags;
use godot::engine::utilities::{printerr, prints};
use godot::engine::{
AtlasTexture, AudioStream, AudioStreamOggVorbis, DirAccess, OggPacketSequence,
PlaceholderTexture2D, SpriteFrames,
};
use godot::engine::{Image, PckPacker};
use godot::engine::{ImageTexture, ProjectSettings};
use godot::engine::utilities::printerr;
use godot::engine::ImageTexture;
use godot::engine::{AudioStreamOggVorbis, DirAccess, OggPacketSequence, Translation};
use godot::engine::{ResourceFormatLoader, ResourceSaver};
use godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
use godot::prelude::*;
@@ -137,12 +131,12 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
fn load(
&self,
path: GodotString,
virtual_path: GodotString,
_original_path: GodotString,
_use_sub_threads: bool,
_cache_mode: i64,
) -> Variant {
let datafile_path = convert_path(&path);
let datafile_path = convert_path(&virtual_path);
if let Some(resource) = self.retrieve_cache::<Resource>(format!(
"{}.{}",
datafile_path,
@@ -178,13 +172,25 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
game_object.to_variant()
}
Ok(DatafileFile::Ui(ui)) => {
let ui = convert_ui(ui, None);
let full_path = virtual_path.to_string();
let (_, _, base_path) = full_path
.rsplitn(3, '/')
.collect_tuple()
.expect("Illegal path for UI");
let ui = convert_ui(ui, None, base_path);
let mut scene = PackedScene::new();
scene.pack(ui);
self.save_to_cache(scene.share().upcast(), format!("{}.scn", datafile_path));
scene.to_variant()
}
Ok(DatafileFile::Translations(translations)) => {
let mut translation = Translation::new();
for (key, message) in translations {
translation.add_message(key.into(), message.join("\n").into(), "".into());
}
translation.to_variant()
}
Ok(DatafileFile::Vorbis(vorbis)) => {
let mut audio = AudioStreamOggVorbis::new();
audio.set_loop(true);
@@ -198,7 +204,7 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
}
Ok(DatafileFile::RleSprite(rle)) => load_rle_as_sprite_frames(*rle).to_variant(),
Ok(DatafileFile::Sprites(sprites)) => {
let sprite_frames = load_sprite_frames(sprites, path);
let sprite_frames = load_sprite_frames(sprites, virtual_path);
self.save_to_cache(
sprite_frames.share().upcast(),

View File

@@ -1,85 +1,143 @@
use crate::formats::ui_xml::{HorizontalAlign, UiTag};
use godot::builtin::{Array, Dictionary, GodotString, Signal, ToVariant, Vector2};
use godot::builtin::{Array, Dictionary, GodotString, ToVariant, Vector2};
use godot::engine::control::{LayoutPreset, SizeFlags};
use godot::engine::global::HorizontalAlignment;
use godot::engine::node::InternalMode;
use godot::engine::{Button, Control, Node, TextureRect};
use godot::obj::{Gd, Share};
use godot::sys::GDEXTENSION_VARIANT_TYPE_STRING;
use godot::engine::{load, Button, Control, Label, Node, SpinBox, TextureRect};
use godot::obj::{Gd, Inherits, Share};
use itertools::Itertools;
const ACTION_META_NAME: &str = "action";
pub fn convert_ui(ui: UiTag, owner: Option<Gd<Node>>) -> Gd<Node> {
pub fn convert_ui(ui: UiTag, owner: Option<Gd<Node>>, base_path: &str) -> Gd<Node> {
match ui {
UiTag::Menu(menu) => {
let mut gd_menu = Control::new_alloc();
let owner_node = owner.unwrap_or_else(|| gd_menu.share().upcast());
for child in menu.children {
let mut child = convert_ui(child, Some(owner_node.share()));
gd_menu.add_child(child.share(), false, InternalMode::INTERNAL_MODE_FRONT);
child.set_owner(owner_node.share());
}
gd_menu.set_anchors_preset(LayoutPreset::PRESET_FULL_RECT, false);
attach_children(&mut gd_menu, owner, menu.children, base_path);
gd_menu.upcast()
}
UiTag::Image(image) => {
let mut gd_image = TextureRect::new_alloc();
let texture = load(format!("{}/sprites/{}.bmp", base_path, image.texture));
gd_image.set_texture(texture);
gd_image.set_name(image.texture.into());
gd_image.set_position(
Vector2 {
x: image.position[0] as f32,
y: image.position[1] as f32,
},
false,
);
gd_image.set_size(
Vector2 {
x: image.size[0] as f32,
y: image.size[1] as f32,
},
false,
);
gd_image.set_position(to_vec2(image.position), false);
gd_image.set_size(to_vec2(image.size), false);
gd_image.upcast()
}
UiTag::StaticText(text) => {
let mut label = Label::new_alloc();
label.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
label.set_position(to_vec2(text.position), false);
label.set_horizontal_alignment(text.horizontal_align.into());
label.set_text(text.text.into());
label.upcast()
}
UiTag::TextArea(area) => {
let mut text_area = Control::new_alloc();
text_area.set_anchors_preset(LayoutPreset::PRESET_FULL_RECT, false);
text_area.set_position(to_vec2(area.position), false);
text_area.set_size(to_vec2(area.size), false);
attach_children(&mut text_area, owner, area.children, base_path);
text_area.upcast()
}
UiTag::ToggleButton(toggle) => {
let mut spin_box = SpinBox::new_alloc();
spin_box.set_position(to_vec2(toggle.position), false);
spin_box.set_min(toggle.min_value as f64);
spin_box.set_max(toggle.max_value as f64);
spin_box.set_step(toggle.value_step as f64);
if let Some(name) = toggle.name {
spin_box.set_name(GodotString::from(name));
}
spin_box.set_meta("text".into(), toggle.text.to_variant());
spin_box.set_meta("target".into(), toggle.target.to_variant());
spin_box.set_meta(
"no_sound".into(),
toggle.no_sound.unwrap_or(false).to_variant(),
);
attach_call_meta(&mut spin_box, toggle.on_change);
spin_box.upcast()
}
UiTag::TextButton(button) => {
let mut gd_button = Button::new_alloc();
gd_button.set_position(
Vector2 {
x: button.position[0] as f32,
y: button.position[1] as f32,
},
false,
);
gd_button.set_text_alignment(match button.horizontal_align {
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
});
gd_button.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
gd_button.set_flat(true);
gd_button.set_position(to_vec2(button.position), false);
gd_button.set_text_alignment(button.horizontal_align.into());
if let Some(name) = button.name {
gd_button.set_name(GodotString::from(name));
}
gd_button.set_text(GodotString::from(button.text));
let mut call = button.on_select.split_whitespace().collect_vec();
if let Some((name,)) = call.drain(..1).collect_tuple() {
gd_button.set_meta(
ACTION_META_NAME.into(),
Dictionary::from([
(&"name".to_variant(), &name.to_variant()),
(
&"args".to_variant(),
&Array::from(
call.into_iter()
.map(GodotString::from)
.collect::<Vec<GodotString>>()
.as_slice(),
)
.to_variant(),
),
])
.to_variant(),
);
}
attach_call_meta(&mut gd_button, button.on_select);
gd_button.upcast()
}
}
}
impl Into<HorizontalAlignment> for HorizontalAlign {
fn into(self) -> HorizontalAlignment {
match self {
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
HorizontalAlign::Left => HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT,
HorizontalAlign::Right => HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT,
}
}
}
fn attach_children<T>(
node: &mut Gd<T>,
owner: Option<Gd<Node>>,
children: Vec<UiTag>,
base_path: &str,
) where
T: Inherits<Node>,
{
let owner_node = owner.unwrap_or_else(|| node.share().upcast());
for child in children {
let mut child = convert_ui(child, Some(owner_node.share()), base_path);
node.share()
.upcast()
.add_child(child.share(), false, InternalMode::INTERNAL_MODE_FRONT);
child.set_owner(owner_node.share());
}
}
fn to_vec2(vec: [i32; 2]) -> Vector2 {
Vector2 {
x: vec[0] as f32,
y: vec[1] as f32,
}
}
fn attach_call_meta<T>(button: &mut Gd<T>, call_string: String)
where
T: Inherits<Node>,
{
let mut call = call_string.split_whitespace().collect_vec();
if call.is_empty() {
return;
}
if let Some((name,)) = call.drain(..1).collect_tuple() {
button.share().upcast().set_meta(
ACTION_META_NAME.into(),
Dictionary::from([
(&"name".to_variant(), &name.to_variant()),
(
&"args".to_variant(),
&Array::from(
call.into_iter()
.map(GodotString::from)
.collect::<Vec<GodotString>>()
.as_slice(),
)
.to_variant(),
),
])
.to_variant(),
);
}
}