initial commit

This commit is contained in:
2023-05-04 00:32:54 +02:00
commit 44b4d847e5
34 changed files with 3510 additions and 0 deletions

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# MHLIB
A project to make the old classic Moorhuhn Games playable
on modern systems via an engine reimplementation, written
in Rust and Godot.
Most of the Moorhuhn Games are structured quite similarly,
which makes supporting them relatively easy.

2
godot/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

2
godot/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Godot 4+ specific ignores
.godot/

BIN
godot/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

34
godot/icon.png.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c1vqk21x7qa8t"
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

12
godot/mhjnr.gdextension Normal file
View File

@@ -0,0 +1,12 @@
[configuration]
entry_symbol = "gdext_rust_init"
[libraries]
linux.debug.x86_64 = "res://../rust/target/debug/libmhjnr.so"
linux.release.x86_64 = "res://../rust/target/release/libmhjnr.so"
windows.debug.x86_64 = "res://../rust/target/debug/mhjnr.dll"
windows.release.x86_64 = "res://../rust/target/release/mhjnr.dll"
macos.debug = "res://../rust/target/debug/mhjnr.dylib"
macos.release = "res://../rust/target/release/mhjnr.dylib"
macos.debug.arm64 = "res://../rust/target/aarch64-apple-darwin/debug/mhjnr.dylib"
macos.release.arm64 = "res://../rust/target/aarch64-apple-darwin/release/mhjnr.dylib"

60
godot/mhjnr/Camera2D.gd Normal file
View File

@@ -0,0 +1,60 @@
extends CharacterBody2D
@export var max_jumps: int = 2
@export_range(0, 4000, 1, "suffix:px/s") var speed: float = 400
@export_range(0, 4000, 1, "suffix:px/s") var terminal_velocity: float = 2000
@export_range(0, 4000, 1, "suffix:px/s") var jump_speed: float = 900
@export_range(0, 4000, 1, "suffix:px/s²") var acceleration: float = 800
@export_range(0, 4000, 1, "suffix:px/s²") var deceleration: float = 1000
@onready var state_machine: AnimationNodeStateMachinePlayback = %AnimationTree["parameters/playback"]
@onready var jumps: int = max_jumps
@onready var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
func is_running():
return Input.is_action_pressed("Move Left") or Input.is_action_pressed("Move Right")
func is_on_ledge():
return is_on_floor() and not is_running() and not %Slip.is_colliding()
func did_jump():
return Input.is_action_just_pressed("Move Up") and jumps > 0
func _ready() -> void:
apply_floor_snap()
func clamp_dir(value: float, dir: float):
return clampf(value, min(dir, 0.0), max(dir, 0.0))
func _physics_process(delta: float) -> void:
velocity.y += gravity * delta
velocity.y = minf(velocity.y, terminal_velocity)
var vertical: float = Input.get_axis("Move Down", "Move Up")
var horizontal: float = Input.get_axis("Move Left", "Move Right")
velocity
if is_on_floor():
jumps = max_jumps
if did_jump():
if jumps != max_jumps:
state_machine.start(state_machine.get_current_node(), true)
jumps -= 1
velocity.y = -jump_speed
var max_speed: float = speed * horizontal
velocity.x += acceleration * horizontal * delta
if is_running():
velocity.x = clamp_dir(velocity.x, max_speed)
%animations.flip_h = horizontal > 0.0
else:
velocity.x -= clamp_dir(deceleration * velocity.x * delta, velocity.x)
if is_on_ledge():
jumps = 0
var direction: float = 1.0 if %SlipTestFront.is_colliding() else -1.0
velocity.x += 10_000 * delta * direction
if move_and_slide() and is_on_wall():
velocity.x = 0.0

648
godot/mhjnr/Moorhuhn.tscn Normal file
View File

@@ -0,0 +1,648 @@
[gd_scene load_steps=61 format=3 uid="uid://ctoj2a102rs6f"]
[ext_resource type="Script" path="res://mhjnr/Camera2D.gd" id="1_nngds"]
[ext_resource type="SpriteFrames" path="datafile://data/player/sprites.txt" id="2_valkm"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_mob27"]
size = Vector2(96, 85)
[sub_resource type="Animation" id="Animation_7sfno"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"mh_runW"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [0]
}
[sub_resource type="Animation" id="Animation_v01jo"]
resource_name = "crawl"
length = 1.00001
loop_mode = 1
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_crawlWCycle"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333, 0.6, 0.666667, 0.733333, 0.8, 0.866667, 0.933333, 1),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
}
[sub_resource type="Animation" id="Animation_jmosv"]
resource_name = "crawl_end"
length = 0.400007
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_crawlWEnd"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6]
}
[sub_resource type="Animation" id="Animation_8i52u"]
resource_name = "crawl_start"
length = 0.266673
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_crawlWStart"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4]
}
[sub_resource type="Animation" id="Animation_f4p3b"]
resource_name = "idle"
length = 4.00001
loop_mode = 1
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_stayW_idle"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1, 1.06667, 1.13333, 1.2, 1.26667, 1.33333, 2.26667, 2.33333, 2.4, 2.46667, 2.53333, 2.6, 2.66667, 3.6, 3.66667, 3.73333, 3.8, 3.86667, 3.93333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20]
}
[sub_resource type="Animation" id="Animation_kugay"]
resource_name = "jump"
length = 0.266673
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_stayW_jumpCycle"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4]
}
[sub_resource type="Animation" id="Animation_kbk86"]
resource_name = "jump_end"
length = 0.53334
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_stayW_jumpEnd+particle"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8]
}
[sub_resource type="Animation" id="Animation_0uslx"]
resource_name = "jump_start"
length = 0.33334
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_stayW_jumpStart"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5]
}
[sub_resource type="Animation" id="Animation_bifhq"]
resource_name = "run"
length = 0.600007
loop_mode = 1
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"mh_runW"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333, 0.6),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
[sub_resource type="Animation" id="Animation_517vc"]
resource_name = "run_end"
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_runW_stop+particle"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333, 0.6, 0.666667, 0.733333, 0.8, 0.866667, 0.933333, 1),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
}
[sub_resource type="Animation" id="Animation_1mxgl"]
resource_name = "run_end_fall"
length = 1.13334
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"mh_fall_down"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333, 0.6, 0.666667, 0.733333, 0.8, 0.866667, 0.933333, 1, 1.06667, 1.13333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
}
[sub_resource type="Animation" id="Animation_fmxky"]
resource_name = "run_jump"
length = 0.466673
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_runW_jump"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7]
}
[sub_resource type="Animation" id="Animation_l4g2q"]
resource_name = "run_smash_wall"
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"mh_hit_smashedWall"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333, 0.6, 0.666667, 0.733333, 0.8, 0.866667, 0.933333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]
}
[sub_resource type="Animation" id="Animation_khe4g"]
resource_name = "run_start"
length = 0.53334
step = 0.0666667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0666667, 0.133333, 0.2, 0.266667, 0.333333, 0.4, 0.466667, 0.533333),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".:animation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"MH_runW_1.stepStart"]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_jvkhi"]
_data = {
"RESET": SubResource("Animation_7sfno"),
"crawl": SubResource("Animation_v01jo"),
"crawl_end": SubResource("Animation_jmosv"),
"crawl_start": SubResource("Animation_8i52u"),
"idle": SubResource("Animation_f4p3b"),
"jump": SubResource("Animation_kugay"),
"jump_end": SubResource("Animation_kbk86"),
"jump_start": SubResource("Animation_0uslx"),
"run": SubResource("Animation_bifhq"),
"run_end": SubResource("Animation_517vc"),
"run_end_fall": SubResource("Animation_1mxgl"),
"run_jump": SubResource("Animation_fmxky"),
"run_smash_wall": SubResource("Animation_l4g2q"),
"run_start": SubResource("Animation_khe4g")
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_u60at"]
animation = &"RESET"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_qt2l3"]
animation = &"run"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_ohkqh"]
animation = &"run_end"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_s2hko"]
animation = &"run_smash_wall"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_t7lts"]
animation = &"run_start"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_xgmeb"]
advance_mode = 2
advance_expression = "not is_running()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_x0rjg"]
advance_mode = 2
advance_expression = "is_on_wall()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_orvkk"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_mtp52"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_yfk7a"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_p4gp5"]
advance_mode = 2
advance_expression = "not is_running() or is_on_wall()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_suygg"]
advance_mode = 2
advance_expression = "is_running()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_750mf"]
advance_mode = 2
advance_expression = "velocity.x != 0"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_0ymww"]
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_gi4av"]
advance_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_h8tv8"]
states/End/position = Vector2(668, 134)
states/RESET/node = SubResource("AnimationNodeAnimation_u60at")
states/RESET/position = Vector2(367, -131)
states/Start/position = Vector2(258, -131)
states/run/node = SubResource("AnimationNodeAnimation_qt2l3")
states/run/position = Vector2(668, -56)
states/run_end/node = SubResource("AnimationNodeAnimation_ohkqh")
states/run_end/position = Vector2(668, 32)
states/run_smash_wall/node = SubResource("AnimationNodeAnimation_s2hko")
states/run_smash_wall/position = Vector2(865, -131)
states/run_start/node = SubResource("AnimationNodeAnimation_t7lts")
states/run_start/position = Vector2(495, -131)
transitions = ["run", "run_end", SubResource("AnimationNodeStateMachineTransition_xgmeb"), "run", "run_smash_wall", SubResource("AnimationNodeStateMachineTransition_x0rjg"), "run_smash_wall", "End", SubResource("AnimationNodeStateMachineTransition_orvkk"), "run_end", "End", SubResource("AnimationNodeStateMachineTransition_mtp52"), "run_start", "run", SubResource("AnimationNodeStateMachineTransition_yfk7a"), "run_start", "End", SubResource("AnimationNodeStateMachineTransition_p4gp5"), "run_end", "run_start", SubResource("AnimationNodeStateMachineTransition_suygg"), "run_smash_wall", "run_start", SubResource("AnimationNodeStateMachineTransition_750mf"), "Start", "RESET", SubResource("AnimationNodeStateMachineTransition_0ymww"), "RESET", "run_start", SubResource("AnimationNodeStateMachineTransition_gi4av")]
graph_offset = Vector2(77, -267)
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_wubix"]
animation = &"jump"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_1p554"]
animation = &"idle"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_q376h"]
animation = &"jump_start"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_kxajv"]
animation = &"jump_end"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_io5l5"]
animation = &"run_jump"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_mgepe"]
animation = &"run_end_fall"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_khcbs"]
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_6878b"]
advance_mode = 2
advance_expression = "velocity.y > 0.0"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_1lxrg"]
advance_mode = 2
advance_expression = "is_on_floor()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_5me5o"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_nrhuk"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_og56a"]
advance_mode = 2
advance_expression = "is_running() and velocity.x != 0.0"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_do5xa"]
advance_mode = 2
advance_expression = "is_on_ledge()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_okk45"]
switch_mode = 2
advance_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_r33lm"]
advance_mode = 2
advance_expression = "is_on_floor() and not is_on_ledge()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_mqs3h"]
advance_mode = 2
advance_expression = "did_jump()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_jd8pa"]
advance_mode = 2
advance_expression = "velocity.y > 0.0"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_c8u27"]
advance_mode = 2
advance_expression = "did_jump()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_3lran"]
advance_mode = 2
advance_expression = "not is_running()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_8e8p2"]
advance_mode = 2
advance_expression = "is_on_floor()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_aivqa"]
advance_mode = 2
advance_expression = "is_running()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_pp0po"]
advance_mode = 2
advance_expression = "is_on_ledge()"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_rh1ge"]
advance_mode = 2
advance_expression = "did_jump()"
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_lv3mp"]
states/End/position = Vector2(893, 119)
states/Run/node = SubResource("AnimationNodeStateMachine_h8tv8")
states/Run/position = Vector2(592, 119)
states/Start/position = Vector2(73, 119)
states/fall/node = SubResource("AnimationNodeAnimation_wubix")
states/fall/position = Vector2(430, -168)
states/idle/node = SubResource("AnimationNodeAnimation_1p554")
states/idle/position = Vector2(235, 119)
states/jump/node = SubResource("AnimationNodeAnimation_q376h")
states/jump/position = Vector2(144, -19)
states/jump_end/node = SubResource("AnimationNodeAnimation_kxajv")
states/jump_end/position = Vector2(235, -168)
states/run_jump/node = SubResource("AnimationNodeAnimation_io5l5")
states/run_jump/position = Vector2(592, -40)
states/slip/node = SubResource("AnimationNodeAnimation_mgepe")
states/slip/position = Vector2(421, 58)
transitions = ["Start", "idle", SubResource("AnimationNodeStateMachineTransition_khcbs"), "jump", "fall", SubResource("AnimationNodeStateMachineTransition_6878b"), "fall", "jump_end", SubResource("AnimationNodeStateMachineTransition_1lxrg"), "jump_end", "idle", SubResource("AnimationNodeStateMachineTransition_5me5o"), "Run", "idle", SubResource("AnimationNodeStateMachineTransition_nrhuk"), "idle", "Run", SubResource("AnimationNodeStateMachineTransition_og56a"), "idle", "slip", SubResource("AnimationNodeStateMachineTransition_do5xa"), "slip", "fall", SubResource("AnimationNodeStateMachineTransition_okk45"), "slip", "jump_end", SubResource("AnimationNodeStateMachineTransition_r33lm"), "idle", "jump", SubResource("AnimationNodeStateMachineTransition_mqs3h"), "idle", "fall", SubResource("AnimationNodeStateMachineTransition_jd8pa"), "Run", "run_jump", SubResource("AnimationNodeStateMachineTransition_c8u27"), "run_jump", "fall", SubResource("AnimationNodeStateMachineTransition_3lran"), "run_jump", "Run", SubResource("AnimationNodeStateMachineTransition_8e8p2"), "jump_end", "Run", SubResource("AnimationNodeStateMachineTransition_aivqa"), "jump_end", "slip", SubResource("AnimationNodeStateMachineTransition_pp0po"), "jump_end", "jump", SubResource("AnimationNodeStateMachineTransition_rh1ge")]
graph_offset = Vector2(-106, -229)
[sub_resource type="AnimationNodeStateMachinePlayback" id="AnimationNodeStateMachinePlayback_udssx"]
[sub_resource type="AnimationNodeStateMachinePlayback" id="AnimationNodeStateMachinePlayback_n5wvc"]
[node name="Moorhuhn" type="CharacterBody2D"]
script = ExtResource("1_nngds")
jump_speed = 610.0
[node name="Slip" type="RayCast2D" parent="."]
unique_name_in_owner = true
target_position = Vector2(0, 100)
[node name="SlipTestFront" type="RayCast2D" parent="."]
unique_name_in_owner = true
position = Vector2(-55, 0)
target_position = Vector2(0, 100)
[node name="Collision" type="CollisionShape2D" parent="."]
unique_name_in_owner = true
position = Vector2(0, 40)
shape = SubResource("RectangleShape2D_mob27")
[node name="animations" type="AnimatedSprite2D" parent="."]
unique_name_in_owner = true
sprite_frames = ExtResource("2_valkm")
animation = &"mh_runW"
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
root_node = NodePath("../animations")
playback_process_mode = 0
libraries = {
"": SubResource("AnimationLibrary_jvkhi")
}
[node name="AnimationTree" type="AnimationTree" parent="."]
unique_name_in_owner = true
tree_root = SubResource("AnimationNodeStateMachine_lv3mp")
anim_player = NodePath("../AnimationPlayer")
advance_expression_base_node = NodePath("..")
active = true
process_callback = 0
parameters/playback = SubResource("AnimationNodeStateMachinePlayback_udssx")
parameters/Run/playback = SubResource("AnimationNodeStateMachinePlayback_n5wvc")

7
godot/mhjnr/camera.gd Normal file
View File

@@ -0,0 +1,7 @@
extends Camera2D
var player: CharacterBody2D
func _process(delta: float) -> void:
position = player.position
pass

61
godot/mhjnr/level.gd Normal file
View File

@@ -0,0 +1,61 @@
@tool
extends Node2D
@export var level_id: int = 1
func _ready() -> void:
var player = preload("res://mhjnr/Moorhuhn.tscn").instantiate()
add_child(player)
%Camera.player = player
%HudLevel.text = "Level %d" % level_id
player.position = Vector2(200, 10)
var camera_rect: Rect2i
var level: ObjectScript = load("datafile://data/level%02d/settings/level.txt" % level_id)
for data in level.static_objects:
match data.resource_type:
"LevelSettings":
%LevelTimer.start(data.props["levelTime"])
"TiledLayer":
var scene = load("datafile://data/level%02d/layers/%s.dat" % [level_id, data.name.to_lower()])
var tiles: TileMap = scene.instantiate()
tiles.name = data.name
tiles.visible = data.props["is visible"] == 1
var used = tiles.get_used_rect()
used.position *= tiles.cell_quadrant_size
used.size *= tiles.cell_quadrant_size
camera_rect = used if camera_rect == null else camera_rect.merge(used)
var scroll_speed: Vector2 = data.props["scroll speed"]
if scroll_speed.is_equal_approx(Vector2(1, 1)):
add_child(tiles)
else:
var parallax = ParallaxLayer.new()
parallax.name = data.name
parallax.visible = data.props["is visible"] == 1
parallax.motion_scale = data.props["scroll speed"]
%parallax.add_child(parallax)
parallax.add_child(tiles)
%Camera.limit_left = camera_rect.position.x
%Camera.limit_top = camera_rect.position.y
%Camera.limit_right = camera_rect.position.x + camera_rect.size.x
%Camera.limit_bottom = camera_rect.position.y + camera_rect.size.y
%WorldBoundLeft.position.x = camera_rect.position.x
%WorldBoundTop.position.y = camera_rect.position.y
%WorldBoundRight.position.x = camera_rect.position.x + camera_rect.size.x
%WorldBoundBottom.position.y = camera_rect.position.y + camera_rect.size.y
#var enemies: ObjectScript = load("datafile://data/level%02d/layers/%s.dat" % [level_id, name.to_lower()])
#for object in enemies.dynamic_objects:
# match object.props["subType"]:
## 1: create_movable(object)
# 0: create_enemy(object)
func create_enemy(data: ObjectData):
pass
func create_movable(data: ObjectData):
pass

142
godot/mhjnr/level.tscn Normal file
View File

@@ -0,0 +1,142 @@
[gd_scene load_steps=14 format=3 uid="uid://bgb4avgjexp4t"]
[ext_resource type="Script" path="res://mhjnr/level.gd" id="1_dfqgf"]
[ext_resource type="Theme" uid="uid://ks2uyxqg6u4k" path="res://mhjnr/theme.tres" id="3_a2fmg"]
[ext_resource type="Script" path="res://mhjnr/camera.gd" id="3_e6xoo"]
[ext_resource type="Texture2D" path="datafile://data/set1/sprites/hud_live.bmp" id="4_4bu8b"]
[ext_resource type="Texture2D" path="datafile://data/set1/sprites/hud_shield.bmp" id="5_6bu8b"]
[ext_resource type="Texture2D" path="datafile://data/set1/sprites/hud_bullet.bmp" id="5_rxbck"]
[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_j78b3"]
normal = Vector2(0, 1)
[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_3mxdl"]
normal = Vector2(-1, 0)
[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_p4tp1"]
normal = Vector2(1, 0)
[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_ixcvh"]
[sub_resource type="GDScript" id="GDScript_nmpfh"]
resource_name = "TimeLabel"
script/source = "extends Label
func _process(delta: float) -> void:
var time_left = %LevelTimer.time_left
text = \"%02d:%02d\" % [int(time_left / 60), int(time_left) % 60]
"
[sub_resource type="AtlasTexture" id="AtlasTexture_glgkj"]
atlas = ExtResource("5_6bu8b")
region = Rect2(0, 0, 136, 42)
filter_clip = true
[sub_resource type="AtlasTexture" id="AtlasTexture_2nrpq"]
atlas = ExtResource("4_4bu8b")
region = Rect2(0, 0, 36, 32)
filter_clip = true
[node name="level" type="Node2D"]
script = ExtResource("1_dfqgf")
level_id = 2
[node name="Camera" type="Camera2D" parent="."]
unique_name_in_owner = true
process_callback = 0
limit_smoothed = true
position_smoothing_enabled = true
position_smoothing_speed = 10.0
drag_horizontal_enabled = true
drag_vertical_enabled = true
script = ExtResource("3_e6xoo")
[node name="parallax" type="ParallaxBackground" parent="."]
unique_name_in_owner = true
[node name="WorldBounds" type="StaticBody2D" parent="."]
[node name="WorldBoundTop" type="CollisionShape2D" parent="WorldBounds"]
unique_name_in_owner = true
shape = SubResource("WorldBoundaryShape2D_j78b3")
[node name="WorldBoundRight" type="CollisionShape2D" parent="WorldBounds"]
unique_name_in_owner = true
shape = SubResource("WorldBoundaryShape2D_3mxdl")
[node name="WorldBoundLeft" type="CollisionShape2D" parent="WorldBounds"]
unique_name_in_owner = true
shape = SubResource("WorldBoundaryShape2D_p4tp1")
[node name="KillFloor" type="Area2D" parent="."]
[node name="WorldBoundBottom" type="CollisionShape2D" parent="KillFloor"]
unique_name_in_owner = true
shape = SubResource("WorldBoundaryShape2D_ixcvh")
[node name="LevelTimer" type="Timer" parent="."]
unique_name_in_owner = true
[node name="HUD" type="CanvasLayer" parent="."]
[node name="MarginContainer" type="MarginContainer" parent="HUD"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 32
theme_override_constants/margin_top = 32
theme_override_constants/margin_right = 32
theme_override_constants/margin_bottom = 32
[node name="GridContainer" type="GridContainer" parent="HUD/MarginContainer"]
layout_mode = 2
columns = 3
[node name="VBoxContainer" type="VBoxContainer" parent="HUD/MarginContainer/GridContainer"]
layout_mode = 2
[node name="Score" type="Label" parent="HUD/MarginContainer/GridContainer/VBoxContainer"]
layout_mode = 2
text = "0035570"
[node name="Time" type="Label" parent="HUD/MarginContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 6
theme = ExtResource("3_a2fmg")
text = "04:36"
horizontal_alignment = 1
vertical_alignment = 1
script = SubResource("GDScript_nmpfh")
[node name="HudLevel" type="Label" parent="HUD/MarginContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Level 4"
[node name="shield" type="TextureRect" parent="HUD/MarginContainer/GridContainer"]
layout_mode = 2
size_flags_vertical = 10
texture = SubResource("AtlasTexture_glgkj")
[node name="Lives" type="HBoxContainer" parent="HUD/MarginContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 6
size_flags_vertical = 8
[node name="Label" type="Label" parent="HUD/MarginContainer/GridContainer/Lives"]
layout_mode = 2
text = "4"
[node name="TextureRect" type="TextureRect" parent="HUD/MarginContainer/GridContainer/Lives"]
layout_mode = 2
texture = SubResource("AtlasTexture_2nrpq")
[node name="HBoxContainer" type="HBoxContainer" parent="HUD/MarginContainer/GridContainer"]
layout_mode = 2
size_flags_vertical = 8
[node name="TextureRect" type="TextureRect" parent="HUD/MarginContainer/GridContainer/HBoxContainer"]
layout_mode = 2
texture = ExtResource("5_rxbck")

6
godot/mhjnr/theme.tres Normal file
View File

@@ -0,0 +1,6 @@
[gd_resource type="Theme" format=3 uid="uid://ks2uyxqg6u4k"]
[ext_resource type="FontFile" path="datafile://data/fonts/menufont.bmp" id="9_6bx8b"]
[resource]
/fonts/menufont = ExtResource("9_6bx8b")

50
godot/project.godot Normal file
View File

@@ -0,0 +1,50 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="MHJNR"
run/main_scene="res://mhjnr/level.tscn"
config/features=PackedStringArray("4.0", "GL Compatibility")
boot_splash/bg_color=Color(0, 0, 0, 1)
boot_splash/image="res://icon.png"
boot_splash/fullsize=false
[input]
"Move Up"={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
"Move Down"={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
"Move Right"={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
"Move Left"={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
[physics]
2d/default_gravity=2000.0
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"

BIN
icon.blend Normal file

Binary file not shown.

3
rust/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/target
.idea
mhjnr.iml

832
rust/Cargo.lock generated Normal file
View File

@@ -0,0 +1,832 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "binrw"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "exr"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide 0.6.2",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fdeflate"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide 0.7.1",
]
[[package]]
name = "flume"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
name = "gif"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "glam"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
[[package]]
name = "godot"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"godot-core",
"godot-macros",
]
[[package]]
name = "godot-bindings"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"godot4-prebuilt",
]
[[package]]
name = "godot-codegen"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"godot-bindings",
"heck",
"nanoserde",
"proc-macro2",
"quote",
]
[[package]]
name = "godot-core"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"glam",
"godot-codegen",
"godot-ffi",
]
[[package]]
name = "godot-ffi"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"godot-bindings",
"godot-codegen",
"paste",
]
[[package]]
name = "godot-macros"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#5d5132cffecc584f9c9ed1b55306dcbb60ec3972"
dependencies = [
"proc-macro2",
"quote",
"venial",
]
[[package]]
name = "godot4-prebuilt"
version = "0.0.0"
source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=4.0.1#f9e8cfec0ec565201801b02160ef836800746617"
[[package]]
name = "half"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
dependencies = [
"crunchy",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "image"
version = "0.24.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-rational",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memoffset"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [
"autocfg",
]
[[package]]
name = "mhjnr"
version = "0.1.0"
dependencies = [
"base64",
"binrw",
"godot",
"image",
"itertools",
"serde",
"serde-xml-rs",
"unicode-segmentation",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom",
]
[[package]]
name = "nanoserde"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "755e7965536bc54d7c9fba2df5ada5bf835b0443fd613f0a53fa199a301839d3"
dependencies = [
"nanoserde-derive",
]
[[package]]
name = "nanoserde-derive"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7a94da6c6181c35d043fc61c43ac96d3a5d739e7b8027f77650ba41504d6ab"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "paste"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pin-project"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "png"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.7.1",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-xml-rs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
dependencies = [
"log",
"serde",
"thiserror",
"xml-rs",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "simd-adler32"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "tiff"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "venial"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]

22
rust/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "mhjnr"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "mhjnr-viewer"
path = "src/main.rs"
[lib]
name = "mhjnr"
crate-type = ["cdylib", "lib"]
[dependencies]
itertools = "0.10.5"
unicode-segmentation = "1.10.1"
binrw = "0.11.1"
serde = {version = "1.0.160", features = ["derive"]}
serde-xml-rs = "0.6.0"
image = "0.24.6"
base64 = "0.21.0"
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }

View File

@@ -0,0 +1,46 @@
use binrw::{binread, NullString};
use std::collections::HashMap;
#[binread]
#[br(little, magic = b"MHJNR")]
#[derive(Debug)]
pub struct Datafile {
#[br(align_after = 0x20)]
pub edition: Edition,
#[br(temp)]
pub count: u32,
#[br(align_after = 0x20)]
pub unk1: u32,
#[br(count = count)]
pub files: Vec<FileEntry>,
}
#[binread]
#[derive(Debug)]
pub enum Edition {
#[br(magic = b"-XS")]
Xs,
#[br(magic = b"-XXL")]
Xxl,
}
#[derive(Debug)]
#[binread]
pub struct FileEntry {
#[br(pad_size_to = 0x68)]
pub name: NullString,
pub pos: u32,
#[br(pad_after = 0x10)]
pub len: u32,
}
impl Datafile {
pub fn into_index(self) -> HashMap<String, FileEntry> {
self.files
.into_iter()
.map(|entry| (entry.name.to_string(), entry))
.collect()
}
}

46
rust/src/formats/level.rs Normal file
View File

@@ -0,0 +1,46 @@
use binrw::prelude::*;
use binrw::{BinRead, Error};
use image;
use image::error::{DecodingError, ImageFormatHint};
use image::{ImageError, ImageResult, Rgb, RgbImage};
use std::io::Cursor;
#[binrw]
#[brw(little)]
pub struct LevelTile {
pub index: u8,
pub id: u8,
}
#[binrw]
#[brw(little)]
pub struct LevelLayer {
pub tile_count: u32,
pub width: u32,
pub height: u32,
pub unknown_2: u32,
#[br(count = width * height)]
pub tiles: Vec<LevelTile>,
}
pub fn level_tile_data_to_image(tile_data: &[u8]) -> ImageResult<RgbImage> {
let mut cursor = Cursor::new(tile_data);
let layer = LevelLayer::read(&mut cursor).map_err(to_decoding_err)?;
let mut image = RgbImage::new(layer.width, layer.height);
for y in 0..layer.height {
for x in 0..layer.width {
let tile = LevelTile::read(&mut cursor).map_err(to_decoding_err)?;
image.put_pixel(x, y, Rgb([tile.id, tile.index, 0]));
}
}
Ok(image)
}
fn to_decoding_err(err: Error) -> ImageError {
ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name(String::from("mhjnr_layer")),
err,
))
}

115
rust/src/formats/mod.rs Normal file
View File

@@ -0,0 +1,115 @@
use crate::formats::datafile::FileEntry;
use crate::formats::level::LevelLayer;
use crate::formats::rle::RleImage;
use crate::formats::sprites::Sprites;
use crate::formats::txt::{decrypt_txt, DecryptError};
use crate::formats::ui_xml::UiTag;
use binrw::BinRead;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::path::Path;
pub mod datafile;
pub mod level;
pub mod rle;
pub mod sprites;
pub mod txt;
pub mod ui_xml;
pub enum DatafileFile {
Txt(String),
Level(LevelLayer),
Sprites(Vec<Sprites>),
RleSprite(Box<RleImage>),
Bitmap(Vec<u8>),
Vorbis(Vec<u8>),
TileCollision(String),
Ui(UiTag),
}
pub enum Error {
Deserialization,
UnknownFormat(String),
UnknownError,
Custom(String),
DecryptError(DecryptError),
}
fn custom_err<T>(e: T) -> Error
where
T: Debug,
{
Error::Custom(format!("{:#?}", e))
}
pub fn load_data<R>(entry: &FileEntry, reader: &mut R) -> Result<DatafileFile, Error>
where
R: Read + Seek,
{
reader
.seek(SeekFrom::Start(entry.pos as u64))
.map_err(custom_err)?;
let mut data = vec![0u8; entry.len as usize];
reader.read_exact(&mut data).map_err(custom_err)?;
let name = entry.name.to_string();
let path = Path::new(&name);
match path
.extension()
.and_then(OsStr::to_str)
.ok_or(Error::Custom("No extension".to_string()))?
{
"dat" => Ok(DatafileFile::Level(
LevelLayer::read(&mut Cursor::new(data)).map_err(custom_err)?,
)),
"rle" => Ok(DatafileFile::RleSprite(Box::new(
RleImage::read(&mut Cursor::new(data)).map_err(custom_err)?,
))),
"bmp" => Ok(DatafileFile::Bitmap(data)),
"ogg" => Ok(DatafileFile::Vorbis(data)),
"xml" => {
serde_xml_rs::from_str::<UiTag>(String::from_utf8(data).map_err(custom_err)?.as_str())
.map_err(custom_err)
.map(DatafileFile::Ui)
}
"txt" => {
let stem = path
.file_stem()
.and_then(OsStr::to_str)
.ok_or(Error::Custom("Stem".to_string()))?;
let decr = decrypt_txt(data.into_iter()).map_err(|e| Error::DecryptError(e))?;
if stem.starts_with("tile_collision") {
Ok(DatafileFile::TileCollision(decr))
} else if stem == "sprites" {
Ok(DatafileFile::Sprites(
Sprites::parse(decr.as_str()).map_err(custom_err)?,
))
} else {
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();
}*/
ext => Err(Error::UnknownFormat(ext.to_string())),
}
}

131
rust/src/formats/rle.rs Normal file
View File

@@ -0,0 +1,131 @@
use binrw::prelude::*;
use binrw::Endian;
use image::error::{DecodingError, ImageFormatHint};
use image::{AnimationDecoder, Delay, Frame, Frames, ImageBuffer, ImageError};
use std::io::{Read, Seek};
use std::time::Duration;
#[binread]
#[br(little, magic = 0x67u32)]
pub struct RleImage {
pub hash: u64,
pub color_table: [[u8; 4]; 512],
pub width: u32,
pub height: u32,
pub numerator: u32,
pub denominator: u32,
#[br(temp)]
pub frame_count: u32,
#[br(count = frame_count)]
pub frames: Vec<RleLayer>,
}
#[binread]
#[br(little)]
pub struct RleLayer {
pub width: u32,
pub height: u32,
pub left: u32,
pub top: u32,
pub numerator: u32,
pub denominator: u32,
pub data_size: u32,
pub unknown3: u32,
#[br(args(width * height), parse_with = parse_rle)]
pub data: Vec<u8>,
}
pub fn parse_rle<R: Read + Seek>(
reader: &mut R,
endian: Endian,
(size,): (u32,),
) -> BinResult<Vec<u8>> {
let mut data = Vec::with_capacity(size as usize);
while data.len() != size as usize {
let count: i8 = reader.read_type(endian)?;
if count > 0 {
let value: u8 = reader.read_type(endian)?;
for _ in 0..count {
data.push(value);
}
} else {
for _ in 0..-count {
data.push(reader.read_type(endian)?);
}
}
}
Ok(data)
}
impl RleImage {
pub fn get_image_data(&self, layer: &RleLayer) -> Vec<u8> {
let mut data = Vec::<u8>::with_capacity(self.width as usize * self.height as usize * 4);
let mut i = 0;
for y in 0..self.height {
for x in 0..self.width {
if y < layer.top
|| y >= layer.top + layer.height
|| x < layer.left
|| x >= layer.left + layer.width
{
data.push(0);
data.push(0);
data.push(0);
data.push(0);
} else {
let color = self.color_table[layer.data[i] as usize];
i += 1;
data.push(color[2]);
data.push(color[1]);
data.push(color[0]);
data.push(if color[2] == 0 && color[1] == 0 && color[0] == 0 {
0
} else {
255
});
}
}
}
data
}
}
impl<'a> AnimationDecoder<'a> for RleImage {
fn into_frames(self) -> Frames<'a> {
Frames::new(Box::new(self.frames.into_iter().map(move |frame| {
let buffer = ImageBuffer::from_raw(
frame.width,
frame.height,
frame
.data
.into_iter()
.flat_map(|it| bgra_to_rgba(self.color_table[it as usize]))
.collect(),
)
.ok_or(to_rle_image_err(std::fmt::Error::default()))?;
Ok(Frame::from_parts(
buffer,
frame.left,
frame.top,
Delay::from_saturating_duration(Duration::from_millis(80)),
))
})))
}
}
pub fn bgra_to_rgba(pixel: [u8; 4]) -> [u8; 4] {
[pixel[2], pixel[1], pixel[0], pixel[3]]
}
fn to_rle_image_err<T>(err: T) -> ImageError
where
T: Into<Box<dyn std::error::Error + Send + Sync>>,
{
ImageError::Decoding(DecodingError::new(
ImageFormatHint::Name(String::from("mhjnr_rle")),
err,
))
}

View File

@@ -0,0 +1,75 @@
#[derive(Debug)]
pub struct Sprites {
pub name: String,
pub sprite_type: SpriteType,
pub file_name: String,
pub render_mode: RenderMode,
pub frames: Option<CropMode>,
}
#[derive(Debug)]
pub enum Error {
InvalidData,
UnknownEnum(String),
}
impl Sprites {
pub fn parse(string: &str) -> Result<Vec<Self>, Error> {
string
.split('\n')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(Sprites::parse_single)
.collect()
}
pub fn parse_single(string: &str) -> Result<Self, Error> {
let mut components = string.split_whitespace();
Ok(Sprites {
file_name: components.next().ok_or(Error::InvalidData)?.to_string(),
sprite_type: match components.next().ok_or(Error::InvalidData)? {
"anim_rle" => SpriteType::AnimRle,
"anim" => SpriteType::Anim,
"static" => SpriteType::Static,
e => return Err(Error::UnknownEnum(e.to_string())),
},
name: components.next().ok_or(Error::InvalidData)?.to_string(),
render_mode: match components.next().ok_or(Error::InvalidData)? {
"normx" => RenderMode::NormX,
"flipx" => RenderMode::FlipX,
e => return Err(Error::UnknownEnum(e.to_string())),
},
frames: if let Some(c) = components.next() {
Some(match c {
"nocrop" => CropMode::NoCrop,
x => x
.parse::<i32>()
.map(CropMode::FrameCount)
.map_err(|e| Error::UnknownEnum(e.to_string()))?,
})
} else {
None
},
})
}
}
#[derive(Debug)]
pub enum CropMode {
FrameCount(i32),
NoCrop,
}
#[derive(Debug)]
pub enum RenderMode {
NormX,
FlipX,
}
#[derive(Debug)]
pub enum SpriteType {
Static,
Anim,
AnimRle,
}

68
rust/src/formats/txt.rs Normal file
View File

@@ -0,0 +1,68 @@
use std::num::ParseIntError;
use std::string::FromUtf8Error;
#[derive(Debug)]
pub enum DecryptError {
FromUtf8Error(FromUtf8Error),
ParseIntError(ParseIntError),
}
impl From<FromUtf8Error> for DecryptError {
fn from(e: FromUtf8Error) -> DecryptError {
DecryptError::FromUtf8Error(e)
}
}
impl From<ParseIntError> for DecryptError {
fn from(e: ParseIntError) -> DecryptError {
DecryptError::ParseIntError(e)
}
}
/// Decrypts txt files contained inside the dat file
pub fn decrypt_txt<I>(buffer: I) -> Result<String, DecryptError>
where
I: Iterator<Item = u8>,
{
let mut key = 0x1234u16;
String::from_utf8(
buffer
.map(|char| {
let decr = char ^ key as u8;
key = key.wrapping_mul(3).wrapping_add(2);
decr
})
.map(|char| (((char >> 1) ^ (char << 1)) & 0x55) ^ (char << 1))
.collect(),
)
.map_err(DecryptError::from)
}
/// Parses a hex string to a Vec<u8>
fn from_hex(line: &str) -> Result<Vec<u8>, ParseIntError> {
(0..line.len())
.step_by(2)
.map(|i| u8::from_str_radix(line.get(i..=i + 1).unwrap_or(""), 16))
.collect()
}
/// This function is applied to *exposed* txt files,
/// such as the player profile or high scores
///
/// If the file is contained in the datafile, it has
/// to first be decrypted normally and then again
/// with this function.
pub fn decrypt_exposed_txt(contents: String) -> Result<String, DecryptError> {
contents
.split_terminator("\r\n")
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.map(from_hex)
.map(|line| decrypt_txt(line.map_err(DecryptError::from)?.into_iter()))
.collect::<Result<Vec<String>, _>>()
.map(|l| l.join("\r\n"))
}
#[cfg(test)]
mod tests {}

View File

@@ -0,0 +1,77 @@
use serde::de::Error;
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
pub enum UiTag {
Menu(UiMenu),
Image(UiImage),
TextButton(UiTextButton),
}
#[derive(Debug, Deserialize)]
pub struct UiMenu {
pub selected: String,
#[serde(rename = "OnBack")]
pub on_back: String,
#[serde(rename = "$value")]
pub children: Vec<UiTag>,
}
#[derive(Debug, Deserialize)]
pub struct UiImage {
pub texture: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(deserialize_with = "deserialize_vec2")]
pub size: [i32; 2],
#[serde(rename = "fademode")]
pub fade_mode: FadeMode,
}
#[derive(Debug, Deserialize)]
pub struct UiTextButton {
pub name: Option<String>,
pub text: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(rename = "halign")]
pub horizontal_align: HorizontalAlign,
#[serde(rename = "fademode")]
pub fade_mode: FadeMode,
#[serde(rename = "OnSelect")]
pub on_select: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HorizontalAlign {
Center,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FadeMode {
None,
}
fn deserialize_vec2<'de, D>(deserializer: D) -> Result<[i32; 2], D::Error>
where
D: Deserializer<'de>,
{
let buf = String::deserialize(deserializer)?;
let mut values: Vec<Result<i32, D::Error>> = buf
.split(',')
.into_iter()
.map(|value| {
// there's some typos so we have to cover that...
value.split_ascii_whitespace().collect::<Vec<&str>>()[0]
.trim()
.parse::<i32>()
.map_err(|err| Error::custom(err.to_string()))
})
.collect();
let y = values.pop().ok_or(Error::custom("InvalidField"))??;
let x = values.pop().ok_or(Error::custom("InvalidField"))??;
Ok([x, y])
}

272
rust/src/godot/datafile.rs Normal file
View File

@@ -0,0 +1,272 @@
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;
use crate::godot::image::{load_bmp_as_image_texture, load_rle_as_sprite_frames};
use crate::godot::sprites::load_sprite_frames;
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::{ResourceFormatLoader, ResourceSaver};
use godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
use godot::prelude::*;
use itertools::Itertools;
use std::collections::HashMap;
use std::fs::File;
use std::str::FromStr;
const DAT_PATH: &str = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
#[derive(GodotClass)]
#[class(base=ResourceFormatLoader)]
pub struct DatafileLoader {
pub datafile_table: HashMap<String, FileEntry>,
#[base]
pub base: Base<ResourceFormatLoader>,
}
fn convert_path(path: &GodotString) -> String {
path.to_string()
.strip_prefix("datafile://")
.map(|it| it.replace('/', "\\"))
.expect("Invalid path")
}
#[godot_api]
impl DatafileLoader {
fn save_to_cache(&self, resource: Gd<Resource>, path: String) {
let cache_path = self.get_cache_path(path);
match DirAccess::make_dir_recursive_absolute(cache_path.rsplit_once('/').unwrap().0.into())
{
Error::OK => (),
error => printerr(error.to_variant(), &[]),
}
ResourceSaver::singleton().save(resource, cache_path.into(), SaverFlags::FLAG_NONE);
}
fn get_cache_path(&self, path: String) -> String {
format!(
"{}/.cache/{}",
DAT_PATH
.replace('\\', "/")
.strip_suffix("datafile.dat")
.unwrap(),
path.replace('\\', "/")
)
}
fn retrieve_cache<T>(&self, path: String) -> Option<Gd<T>>
where
T: GodotClass + Inherits<Resource>,
{
let cache_path = self.get_cache_path(path);
let type_hint = T::CLASS_NAME;
if !ResourceLoader::singleton().exists(cache_path.clone().into(), type_hint.into()) {
return None;
}
ResourceLoader::singleton()
.load(
cache_path.into(),
type_hint.into(),
CacheMode::CACHE_MODE_REUSE,
)
.map(|it| it.cast())
}
}
#[godot_api]
impl ResourceFormatLoaderVirtual for DatafileLoader {
fn init(base: Base<Self::Base>) -> Self {
let mut file = File::open(DAT_PATH).unwrap();
let datafile_table = Datafile::read(&mut file).unwrap().into_index();
DatafileLoader {
base,
datafile_table,
}
}
fn get_recognized_extensions(&self) -> PackedStringArray {
PackedStringArray::from(&[
"xml".into(),
"txt".into(),
"rle".into(),
"bmp".into(),
"dat".into(),
])
}
fn recognize_path(&self, path: GodotString, _type: StringName) -> bool {
path.to_string().starts_with("datafile://")
}
fn get_resource_type(&self, path: GodotString) -> GodotString {
if path.to_string().ends_with(".dat") {
"PackedScene".into()
} else {
"Resource".into()
}
}
fn get_resource_script_class(&self, _path: GodotString) -> GodotString {
GodotString::from_str("").unwrap()
}
fn exists(&self, path: GodotString) -> bool {
self.datafile_table
.contains_key(convert_path(&path).as_str())
}
fn get_classes_used(&self, _path: GodotString) -> PackedStringArray {
PackedStringArray::from(&[])
}
fn load(
&self,
path: GodotString,
_original_path: GodotString,
_use_sub_threads: bool,
_cache_mode: i64,
) -> Variant {
let datafile_path = convert_path(&path);
if let Some(resource) = self.retrieve_cache::<Resource>(format!(
"{}.{}",
datafile_path,
if datafile_path.ends_with(".xml") || datafile_path.ends_with("dat") {
"scn"
} else {
"res"
}
)) {
return resource.to_variant();
}
if let Some(target) = self.datafile_table.get(datafile_path.as_str()) {
let mut file = File::open(DAT_PATH).unwrap();
match load_data(target, &mut file) {
Ok(DatafileFile::Level(level)) => {
let level_id = datafile_path
.split_terminator('\\')
.find(|i| i.starts_with("level"))
.map(|lvl| u32::from_str(lvl.strip_prefix("level").unwrap()).unwrap())
.unwrap();
let tile_map = create_tile_map(level, level_id);
self.save_to_cache(tile_map.share().upcast(), format!("{}.scn", datafile_path));
tile_map.to_variant()
}
Ok(DatafileFile::Txt(txt)) => {
let game_object = parse_game_object(txt);
self.save_to_cache(
game_object.share().upcast(),
format!("{}.res", datafile_path),
);
game_object.to_variant()
}
Ok(DatafileFile::Ui(ui)) => {
let ui = convert_ui(ui);
let mut scene = PackedScene::new();
scene.pack(ui.upcast());
self.save_to_cache(scene.share().upcast(), format!("{}.scn", datafile_path));
scene.to_variant()
}
Ok(DatafileFile::Vorbis(vorbis)) => {
let mut audio = AudioStreamOggVorbis::new();
audio.set_loop(true);
let mut packet = OggPacketSequence::new();
packet.set_packet_data(Array::from(&[Array::from(&[PackedByteArray::from(
vorbis.as_slice(),
)
.to_variant()])]));
audio.set_packet_sequence(packet);
audio.to_variant()
}
Ok(DatafileFile::RleSprite(rle)) => load_rle_as_sprite_frames(*rle).to_variant(),
Ok(DatafileFile::Sprites(sprites)) => {
let sprite_frames = load_sprite_frames(sprites, path);
self.save_to_cache(
sprite_frames.share().upcast(),
format!("{}.res", datafile_path),
);
sprite_frames.to_variant()
}
Ok(DatafileFile::Bitmap(data)) => {
let gd_image = match load_bmp_as_image_texture(data) {
Ok(image) => image,
Err(err) => return err.to_variant(),
};
if datafile_path.contains("\\fonts\\") {
let font = load_bitmap_font(gd_image);
self.save_to_cache(
font.share().upcast(),
format!("{}.tres", datafile_path),
);
font.to_variant()
} else {
let mut texture = ImageTexture::new();
texture.set_image(gd_image);
self.save_to_cache(
texture.share().upcast(),
format!("{}.res", datafile_path),
);
texture.to_variant()
}
}
Ok(DatafileFile::TileCollision(collision)) => {
let tile_collision = Gd::new(TileCollision {
collision: collision
.chars()
.filter_map(|c| c.to_digit(10))
.map(|d| d as u8)
.collect(),
});
// No need to save this to cache, we only use this internally
/*self.save_to_cache(
tile_collision.share().upcast(),
format!("{}.res", datafile_path),
);*/
tile_collision.to_variant()
}
Err(formats::Error::UnknownFormat(ext)) => {
printerr(format!("Unknown format <{}>", ext).to_variant(), &[]);
Error::ERR_FILE_UNRECOGNIZED.to_variant()
}
Err(formats::Error::Deserialization) => {
printerr("Failed to deserialize".to_variant(), &[]);
Error::ERR_FILE_CORRUPT.to_variant()
}
Err(formats::Error::Custom(message)) => {
printerr(message.to_variant(), &[]);
Error::ERR_BUG.to_variant()
}
_ => {
printerr("Unknown error".to_variant(), &[]);
Error::ERR_BUG.to_variant()
}
}
} else {
printerr("File not found".to_variant(), &[]);
Error::ERR_FILE_NOT_FOUND.to_variant()
}
}
}

88
rust/src/godot/font.rs Normal file
View File

@@ -0,0 +1,88 @@
use godot::builtin::{Rect2, Vector2, Vector2i};
use godot::engine::{FontFile, Image};
use godot::prelude::utilities::{print_verbose, prints};
use godot::prelude::{Color, Gd, Share, ToVariant};
use std::ops::Index;
use unicode_segmentation::UnicodeSegmentation;
const CHARSET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß0123456789,;.:!?\
+-*/=<>()[]{}\"$%&#~_^@|¡¿™©®º¹²³ªÀÁÂÃÅÆÇÈÉÊËÌÍÎÏIÐGÑÒÓÔÕŒØSŠÙÚÛÝÞŸŽàáâãåæçèéêëìíî\
ïiðgñòóôõœøsšùúûýþÿž£¥ƒ¤¯¦¬¸¨·§×¢±÷µ«»";
pub fn load_bitmap_font(image: Gd<Image>) -> Gd<FontFile> {
let mut font_chars = CHARSET.as_bytes().iter();
let mut font_file = FontFile::new();
let chroma_key = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
let mut was_empty_column = true;
let mut char_x = 0;
let mut char_width = 0;
let char_height = image.get_height();
let char_y = 0;
let base_size = Vector2i { x: 16, y: 0 };
font_file.set_texture_image(0, base_size, 0, image.share());
for x in 0..image.get_width() {
let is_empty_column = (0..image.get_height()).all(|y| image.get_pixel(x, y).a == 0.0);
if !was_empty_column && is_empty_column {
let char = font_chars.next().expect("Font has too many characters!");
let glyph = *char as i64;
/*let mut glyph = 0i64;
for (i, c) in char.bytes().rev().enumerate() {
glyph |= (c as i64) << (i * 8);
}*/
let glyph_offset = Vector2 {
x: char_x as f32,
y: char_y as f32,
};
let glyph_size = Vector2 {
x: char_width as f32,
y: char_height as f32,
};
prints(
"Glyph".to_variant(),
&[
(*char as char).to_string().to_variant(),
glyph_offset.to_variant(),
glyph_size.to_variant(),
],
);
// font_file.set_glyph_offset(0, base_size, glyph, glyph_offset);
font_file.set_glyph_size(0, base_size, glyph, glyph_size);
font_file.set_glyph_uv_rect(
0,
base_size,
glyph,
Rect2 {
position: glyph_offset,
size: glyph_size,
},
);
font_file.set_glyph_texture_idx(0, base_size, glyph, 0);
} else if was_empty_column && !is_empty_column {
char_x = x;
char_width = 0;
}
char_width += 1;
was_empty_column = is_empty_column;
}
font_file.set_font_name("menufont".into());
// font_file.set_cache_ascent(0, base_size.x, )
font_file
}

View File

@@ -0,0 +1,138 @@
use godot::engine::Resource;
use godot::prelude::*;
use itertools::Itertools;
use std::str::FromStr;
#[derive(GodotClass)]
#[class(base=Resource, init)]
pub struct ObjectScript {
#[export]
pub dynamic_objects: Array<Gd<ObjectData>>,
#[export]
pub static_objects: Array<Gd<ObjectData>>,
#[base]
base: Base<Resource>,
}
#[godot_api]
impl ObjectScript {}
#[derive(GodotClass)]
#[class(base=Resource, init)]
pub struct ObjectData {
#[export]
pub class_type: GodotString,
#[export]
pub resource_type: GodotString,
#[export]
pub name: GodotString,
#[export]
pub props: Dictionary,
#[export]
pub children: Array<Gd<ObjectData>>,
#[base]
base: Base<Resource>,
}
#[godot_api]
impl ObjectData {}
pub fn parse_game_object(contents: String) -> Gd<ObjectScript> {
Gd::<ObjectScript>::with_base(|base| {
let mut object_script = ObjectScript {
dynamic_objects: Array::new(),
static_objects: Array::new(),
base,
};
let mut lines = contents
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.filter(|l| !l.starts_with('#'));
while let Some(line) = lines.next() {
match line {
"DYNAMIC OBJECT START" => {
object_script.dynamic_objects.push(read_object(&mut lines))
}
"OBJECT START" => object_script.static_objects.push(read_object(&mut lines)),
l => eprintln!("TODO: {}", l),
};
}
object_script
})
}
pub fn read_object<'s, I>(lines: &mut I) -> Gd<ObjectData>
where
I: Iterator<Item = &'s str>,
{
let class_type = lines
.next()
.unwrap()
.strip_prefix("class type:")
.unwrap()
.trim()
.trim_matches('"');
let (resource_type, name) = lines
.next()
.unwrap()
.splitn(2, ']')
.map(|x| x.trim())
.collect_tuple::<(&str, &str)>()
.unwrap();
Gd::<ObjectData>::with_base(|base| {
let mut object_data = ObjectData {
class_type: class_type.into(),
resource_type: resource_type
.trim_start_matches('[')
.trim_end_matches(']')
.into(),
name: name.trim_matches('"').into(),
props: Dictionary::new(),
children: Array::new(),
base,
};
lines.next();
loop {
match lines.next().unwrap() {
"}" => break,
l => {
let (_, key, value) = l
.splitn(3, '"')
.map(|x| x.trim())
.collect_tuple::<(&str, &str, &str)>()
.unwrap();
let values = value
.split_whitespace()
.map(|s| f32::from_str(s).unwrap())
.collect_vec();
object_data.props.insert(
key,
match values.len() {
1 => values[0].to_variant(),
2 => Vector2 {
x: values[0],
y: values[1],
}
.to_variant(),
3 => Vector3 {
x: values[0],
y: values[1],
z: values[2],
}
.to_variant(),
_ => panic!(),
},
);
}
}
}
object_data
})
}

57
rust/src/godot/image.rs Normal file
View File

@@ -0,0 +1,57 @@
use crate::formats::rle::RleImage;
use godot::builtin::{Color, PackedByteArray};
use godot::engine::global::Error;
use godot::engine::image::Format;
use godot::engine::{Image, ImageTexture, SpriteFrames};
use godot::obj::Gd;
const FPS: f64 = 15.0;
pub fn load_rle_as_sprite_frames(rle: RleImage) -> Gd<SpriteFrames> {
let mut frames = SpriteFrames::new();
frames.set_animation_loop("default".into(), true);
frames.set_animation_speed("default".into(), FPS);
for frame in rle.frames.iter() {
let mut image = Image::new();
image.set_data(
rle.width as i64,
rle.height as i64,
false,
Format::FORMAT_RGBA8,
PackedByteArray::from(rle.get_image_data(frame).as_slice()),
);
image.fix_alpha_edges();
let mut texture = ImageTexture::new();
texture.set_image(image);
frames.add_frame("default".into(), texture.upcast(), 1.0, 0);
}
frames
}
pub fn load_bmp_as_image_texture(data: Vec<u8>) -> Result<Gd<Image>, Error> {
let mut image = Image::new();
match image.load_bmp_from_buffer(data.as_slice().into()) {
Error::OK => {
for x in 0..image.get_width() {
for y in 0..image.get_height() {
if image.get_pixel(x, y).is_equal_approx(Color {
r: 1.0,
g: 0.0,
b: 1.0,
a: 1.0,
}) {
image.set_pixel(x, y, Color::TRANSPARENT_BLACK);
}
}
}
image.fix_alpha_edges();
Ok(image)
}
error => Err(error),
}
}

7
rust/src/godot/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod datafile;
pub mod font;
pub mod game_object;
pub mod image;
pub mod sprites;
pub mod tile_map;
pub mod ui;

133
rust/src/godot/sprites.rs Normal file
View File

@@ -0,0 +1,133 @@
use crate::formats::sprites::{CropMode, RenderMode, Sprites};
use godot::builtin::{GodotString, Rect2, StringName, ToVariant, Vector2};
use godot::engine::utilities::printerr;
use godot::engine::{
load, AtlasTexture, ImageTexture, PlaceholderTexture2D, ResourceLoader, SpriteFrames,
};
use godot::obj::{Gd, Share};
use godot::prelude::GodotClass;
const FPS: f64 = 15.0;
const SPRITE_EXTENSIONS: &[&str] = &["bmp", "rle"];
pub fn load_sprite_frames(sprites: Vec<Sprites>, path: GodotString) -> Gd<SpriteFrames> {
let dir = path
.to_string()
.strip_suffix("/sprites.txt")
.unwrap()
.to_string();
let mut sprite_frames = SpriteFrames::new();
for sprite in sprites.into_iter() {
if let RenderMode::FlipX = sprite.render_mode {
continue;
}
sprite_frames.add_animation(StringName::from(&sprite.name));
sprite_frames.set_animation_speed(StringName::from(&sprite.name), FPS);
match select_from_extensions(&dir, &sprite.file_name) {
Some((path, "rle")) => extract_rle_frames(&mut sprite_frames, &sprite, path),
Some((path, "bmp")) => extract_bitmap_frames(&mut sprite_frames, &sprite, path),
Some(_) | None => {
printerr(
format!("Missing sprite '{}'", sprite.file_name).to_variant(),
&[],
);
let texture = PlaceholderTexture2D::new();
sprite_frames.add_frame(
StringName::from(&sprite.name),
texture.upcast(),
60.0 / FPS,
0,
);
}
}
}
sprite_frames
}
/// Loads an RLE file as SpriteFrames and extracts
/// its frames into `sprite_frames`
fn extract_rle_frames(sprite_frames: &mut SpriteFrames, sprite: &Sprites, path: String) {
let frames: Gd<SpriteFrames> = load(path);
for frame_idx in 0..frames.get_frame_count("default".into()) {
sprite_frames.add_frame(
StringName::from(&sprite.name),
frames
.get_frame_texture("default".into(), frame_idx)
.unwrap(),
60.0 / FPS,
0,
);
}
}
/// Loads a bitmap and extracts its frames into `sprite_frames`
/// creates an atlas if there are multiple frames.
fn extract_bitmap_frames(sprite_frames: &mut SpriteFrames, sprite: &Sprites, path: String) {
let texture: Gd<ImageTexture> = load(path);
let frame_count = if let Some(CropMode::FrameCount(frame_count)) = sprite.frames {
frame_count
} else {
1
};
if frame_count > 1 {
let height = texture.get_height();
let width = texture.get_width();
let frame_height = height / frame_count as i64;
for i in 0..frame_count as i64 {
let mut atlas = AtlasTexture::new();
atlas.set_atlas(texture.share().upcast());
atlas.set_region(Rect2 {
position: Vector2 {
x: 0.0,
y: (i * frame_height) as f32,
},
size: Vector2 {
x: width as f32,
y: frame_height as f32,
},
});
sprite_frames.add_frame(
StringName::from(&sprite.name),
atlas.upcast(),
60.0 / FPS,
0,
);
}
} else {
sprite_frames.add_frame(
StringName::from(&sprite.name),
texture.upcast(),
60.0 / FPS,
0,
);
}
}
/// Selects the extension based on which file exists
fn select_from_extensions(dir: &str, file_name: &str) -> Option<(String, &'static str)> {
SPRITE_EXTENSIONS
.iter()
.map(|ext| {
(
format!("{}/sprites/{}.{}", dir, file_name.to_lowercase(), ext),
*ext,
)
})
.find(|(path, ext)| {
ResourceLoader::singleton().exists(
path.clone().into(),
match *ext {
"rle" => SpriteFrames::CLASS_NAME.to_string(),
"bmp" => ImageTexture::CLASS_NAME.to_string(),
_ => panic!(),
}
.into(),
)
})
}

139
rust/src/godot/tile_map.rs Normal file
View File

@@ -0,0 +1,139 @@
use crate::formats::level::LevelLayer;
use godot::engine::global::Error;
use godot::engine::utilities::{clampi, printerr};
use godot::engine::{load, PackedScene};
use godot::engine::{ImageTexture, TileSet};
use godot::engine::{TileMap, TileSetAtlasSource};
use godot::prelude::*;
use godot::prelude::{Gd, PackedByteArray, Share, ToVariant};
pub fn create_tile_map(layer: LevelLayer, level_id: u32) -> Gd<PackedScene> {
let mut tile_set = TileSet::new();
tile_set.set_tile_size(Vector2i { x: 32, y: 32 });
tile_set.add_physics_layer(0);
let mut map = TileMap::new_alloc();
map.set_tileset(tile_set.share());
map.set_quadrant_size(32);
for x in 0..layer.width {
for y in 0..layer.height {
let tile = &layer.tiles[(y * layer.width + x) as usize];
if tile.id == 0 {
continue;
}
if !tile_set.has_source(tile.id as i64) {
let atlas_id = tile.id as u32 + 1;
let atlas = load_atlas(1, atlas_id, layer.tile_count);
tile_set.add_source(atlas.share().upcast(), tile.id as i64);
add_collision(atlas, level_id, atlas_id);
}
map.set_cell(
0,
Vector2i {
x: x as i32,
y: y as i32,
},
tile.id as i64,
Vector2i {
x: clampi(tile.index as i64 % 16, 0, 15) as i32,
y: clampi(tile.index as i64 / 16, 0, 15) as i32,
},
0,
);
}
}
let mut scene = PackedScene::new();
let error = scene.pack(map.upcast());
match error {
Error::OK => (),
e => printerr(e.to_variant(), &[]),
}
scene
}
#[derive(GodotClass)]
#[class(base=Resource, init)]
pub struct TileCollision {
#[export]
pub collision: PackedByteArray,
}
#[godot_api]
impl TileCollision {}
fn add_collision(atlas: Gd<TileSetAtlasSource>, level_id: u32, atlas_id: u32) {
let tile_collision: Gd<TileCollision> = load(format!(
"datafile://data/level{:02}/tile_collision_{:02}.txt",
level_id, atlas_id
));
let width = atlas.get_atlas_grid_size().x;
let height = atlas.get_atlas_grid_size().y;
let tile_width = atlas.get_texture_region_size().x as f32 / 2.0;
let tile_height = atlas.get_texture_region_size().y as f32 / 2.0;
let collision = &[
Vector2 {
x: -tile_width,
y: -tile_height,
},
Vector2 {
x: -tile_width,
y: tile_height,
},
Vector2 {
x: tile_width,
y: tile_height,
},
Vector2 {
x: tile_width,
y: -tile_height,
},
];
for x in 0..width {
for y in 0..height {
let collision_data = tile_collision
.bind()
.collision
.get((y * width + x) as usize);
let mut data = atlas.get_tile_data(Vector2i { x, y }, 0).unwrap();
if collision_data & 0x1 != 0 {
data.add_collision_polygon(0);
data.set_collision_polygon_points(0, 0, PackedVector2Array::from(collision));
} else if collision_data & 0xfe != 0 {
printerr(
format!("Missing collision info for {}", collision_data).to_variant(),
&[],
);
}
}
}
}
fn load_atlas(set_id: u32, atlas_id: u32, tile_count: u32) -> Gd<TileSetAtlasSource> {
let mut atlas = TileSetAtlasSource::new();
let tex: Gd<ImageTexture> = load(format!(
"datafile://data/set{}/sprites/tiles_{:02}.bmp",
set_id, atlas_id,
));
let region_size = (tile_count as f32).sqrt();
debug_assert_eq!(tex.get_width(), tex.get_height());
debug_assert_eq!(region_size, region_size.trunc());
let tile_size = (tex.get_width() / region_size as i64) as i32;
atlas.set_texture(tex.upcast());
atlas.set_texture_region_size(Vector2i {
x: tile_size,
y: tile_size,
});
for x in 0..region_size as i32 {
for y in 0..region_size as i32 {
atlas.create_tile(Vector2i { x, y }, Vector2i { x: 1, y: 1 });
}
}
atlas
}

58
rust/src/godot/ui.rs Normal file
View File

@@ -0,0 +1,58 @@
use crate::formats::ui_xml::{HorizontalAlign, UiTag};
use godot::builtin::{GodotString, Vector2};
use godot::engine::global::HorizontalAlignment;
use godot::engine::node::InternalMode;
use godot::engine::{Button, Container, Control, TextureRect};
use godot::prelude::*;
pub fn convert_ui(ui: UiTag) -> Gd<Control> {
match ui {
UiTag::Menu(menu) => {
let mut gd_menu = Container::new_alloc();
for child in menu.children {
gd_menu.add_child(
convert_ui(child).upcast(),
false,
InternalMode::INTERNAL_MODE_FRONT,
);
}
gd_menu.upcast()
}
UiTag::Image(image) => {
let mut gd_image = TextureRect::new_alloc();
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.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,
});
if let Some(name) = button.name {
gd_button.set_name(GodotString::from(name));
}
gd_button.set_text(GodotString::from(button.text));
gd_button.upcast()
}
}
}

40
rust/src/lib.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::godot::datafile::DatafileLoader;
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};
pub mod formats;
pub mod godot;
struct Main {}
#[gdextension]
unsafe impl ExtensionLibrary for Main {
fn load_library(handle: &mut InitHandle) -> bool {
handle.register_layer(InitLevel::Editor, ResourceLoaderLayer { datafile: None });
true
}
}
struct ResourceLoaderLayer {
pub datafile: Option<Gd<DatafileLoader>>,
}
impl ExtensionLayer for ResourceLoaderLayer {
fn initialize(&mut self) {
auto_register_classes();
self.datafile = Some(Gd::<DatafileLoader>::with_base(DatafileLoader::init));
ResourceLoader::singleton()
.add_resource_format_loader(self.datafile.as_ref().unwrap().share().upcast(), true);
}
fn deinitialize(&mut self) {
if let Some(datafile) = &self.datafile {
ResourceLoader::singleton().remove_resource_format_loader(datafile.share().upcast());
self.datafile = None;
}
}
}

130
rust/src/main.rs Normal file
View File

@@ -0,0 +1,130 @@
use binrw::{BinRead, NullString};
use image::codecs::gif::{GifEncoder, Repeat};
use image::{AnimationDecoder, ImageFormat};
use mhjnr::formats::datafile::Datafile;
use mhjnr::formats::level::level_tile_data_to_image;
use mhjnr::formats::rle::RleImage;
use mhjnr::formats::sprites::Sprites;
use mhjnr::formats::txt::{decrypt_exposed_txt, decrypt_txt};
use mhjnr::formats::ui_xml::UiTag;
use serde_xml_rs::from_str;
use std::ffi::OsStr;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
fn extract(datafile: &Datafile, file: &mut File) {
let target = "E:\\Games\\Schatzjäger\\data3";
for entry in &datafile.files {
let file_name = format!("{}\\{}", target, entry.name);
fs::create_dir_all(file_name.rsplit_once('\\').unwrap().0).unwrap();
file.seek(SeekFrom::Start(entry.pos as u64)).unwrap();
let mut data = vec![0u8; entry.len as usize];
file.read_exact(&mut data).unwrap();
if entry.name.to_string().ends_with(".txt") {
let mut contents = decrypt_txt(data.into_iter()).unwrap();
/*if entry
.name
.to_string()
.split('\\')
.collect::<Vec<&str>>()
.len()
== 1
{
contents = decrypt_exposed_txt(contents).unwrap();
}*/
File::create(file_name)
.unwrap()
.write_all(contents.as_bytes())
.unwrap();
} else if entry.name.to_string().ends_with(".rle") {
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
let mut encoder = GifEncoder::new(
OpenOptions::new()
.create(true)
.write(true)
.open(format!(
"{}.{}",
file_name.strip_suffix(".rle").unwrap(),
".gif"
))
.unwrap(),
);
encoder.set_repeat(Repeat::Infinite).unwrap();
encoder.try_encode_frames(image.into_frames()).unwrap();
} else {
File::create(file_name)
.unwrap()
.write_all(data.as_slice())
.unwrap();
}
}
}
fn main() {
let file_name = Some(NullString::from("data\\loading\\sprites.txt"));
let dat_path = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
let mut file = File::open(dat_path).unwrap();
let dat: Datafile = Datafile::read(&mut file).unwrap();
println!("{:#?}", dat);
extract(&dat, &mut file);
/*if let Some(file_name) = file_name {
let target = dat.files.iter().find(|it| it.name == file_name).unwrap();
file.seek(SeekFrom::Start(target.pos as u64)).unwrap();
let mut data = vec![0u8; target.len as usize];
file.read_exact(&mut data).unwrap();
match Path::new(&file_name.to_string())
.extension()
.and_then(OsStr::to_str)
{
Some("xml") => println!(
"{:#?}",
from_str::<UiTag>(String::from_utf8(data).unwrap().as_str())
),
Some("txt") => {
if false {
/*let decr = decrypt_txt(&mut data);
let entries: String = decrypt_exposed_txt(decr);*/
let decr = decrypt_txt(data.into_iter()).unwrap();
println!("{}", &decr);
let sprites = Sprites::parse(decr.as_str()).unwrap();
println!("{:#?}", sprites);
} else {
println!("{}", decrypt_txt(data.into_iter()).unwrap())
}
}
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();
}
Some(ext) => eprintln!("Unknown file extension <{}>", ext),
None => eprintln!("Failed to read"),
}
}*/
}
// pub fn decr2()