diff options
Diffstat (limited to 'extractor')
-rw-r--r-- | extractor/action_format.gd | 184 | ||||
-rw-r--r-- | extractor/actions.gd | 1 | ||||
-rw-r--r-- | extractor/extractor_interface.gd | 45 | ||||
-rw-r--r-- | extractor/extractor_interface.tscn | 98 | ||||
-rw-r--r-- | extractor/gat_format.gd | 26 | ||||
-rw-r--r-- | extractor/gnd_format.gd | 431 | ||||
-rw-r--r-- | extractor/grf.gd | 262 | ||||
-rw-r--r-- | extractor/map.gd | 34 | ||||
-rw-r--r-- | extractor/rsm_format.gd | 653 | ||||
-rw-r--r-- | extractor/rsw_format.gd | 699 | ||||
-rw-r--r-- | extractor/version.gd | 20 |
11 files changed, 2248 insertions, 205 deletions
diff --git a/extractor/action_format.gd b/extractor/action_format.gd index 97f6771..25a794e 100644 --- a/extractor/action_format.gd +++ b/extractor/action_format.gd @@ -49,7 +49,7 @@ func get_byte_length() -> int: static func from_bytes(bytes: ByteStream) -> ActionFormat: var action_format = ActionFormat.new() - bytes.seek(2) + bytes.advance(action_format.signature.length()) @warning_ignore("shadowed_variable") var version = Version.new() @@ -82,6 +82,188 @@ static func from_bytes(bytes: ByteStream) -> ActionFormat: return action_format +func convert(name: String, sprites_path: String) -> Node2D: + var node := Node2D.new() + node.name = name + node.set_script(load("res://extractor/actions.gd")) + + var animation_player := AnimationPlayer.new() + animation_player.name = "AnimationPlayer" + animation_player.unique_name_in_owner = true + node.add_child(animation_player) + animation_player.owner = node + + var sprite_layers := CanvasGroup.new() + sprite_layers.name = "SpriteLayers" + sprite_layers.unique_name_in_owner = true + + node.add_child(sprite_layers) + sprite_layers.owner = node + + var track_properties = [ + "animation", + "frame", + "speed_scale", + "position", + "self_modulate", + "scale", + "rotation_degrees", + "flip_h", + "visible", + ] + + var sprite_frames := SpriteFrames.new() + #sprite_frames.add_animation("default") + for img_file_path in DirAccess.get_files_at(sprites_path): + if img_file_path.ends_with(".png"): + sprite_frames.add_frame("default", load("%s/%s" % [sprites_path, img_file_path])) + + var animation_library := AnimationLibrary.new() + + # get max number of sprite layers for all actions + var action_sprite_layers_max_count = actions.reduce(func(accum, action: ActionData): + return max(accum, action.motions.reduce(func(accum2, motion: Motion): + return max(accum2, motion.sprite_layer_count) + , 0)) + , 0) + + # add Nodes for each sprite layer + for sprite_layer_idx in action_sprite_layers_max_count: + var sprite = AnimatedSprite2D.new() + sprite.centered = false # 必要!! + sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + sprite.sprite_frames = sprite_frames + sprite.name = str(sprite_layer_idx).pad_zeros(3) + sprite_layers.add_child(sprite) + sprite.owner = node + + for action_idx in actions.size(): + var action: ActionData = actions[action_idx] + var frame_timing_base := ((frame_times[action_idx] * 24) / 1000) + + # TODO: check why this is necessary + if sprites_path.contains("cursors") and action_idx == 0: + frame_timing_base = ((frame_times[action_idx] * 24 * 2) / 1000) + + # add animation for each action + var animation := Animation.new() + animation.loop_mode = Animation.LOOP_LINEAR + animation.length = frame_timing_base * action.motion_count + animation_library.add_animation(str(action_idx).pad_zeros(3), animation) + + # get max number of sprite layers for current action motions + var motion_sprite_layers_max_count = action.motions.reduce(func(accum, motion: Motion): + return max(accum, motion.sprite_layer_count) + , 0) + + # add animation tracks for each sprite layer + for sprite_layer_idx in motion_sprite_layers_max_count: + var sprite := sprite_layers.get_child(sprite_layer_idx) + for property_idx in track_properties.size(): + var track_idx = (sprite_layer_idx * track_properties.size()) + property_idx + animation.add_track(Animation.TYPE_VALUE, track_idx) + animation.value_track_set_update_mode(track_idx, Animation.UPDATE_DISCRETE) + animation.track_set_path( + track_idx, + "%s:%s" % ["SpriteLayers/" + sprite.name, track_properties[property_idx]] + ) + + for i in range(motion_sprite_layers_max_count, action_sprite_layers_max_count): + var sprite := sprite_layers.get_child(i) + var track_idx = animation.add_track(Animation.TYPE_VALUE) + animation.track_set_path( + track_idx, + "%s:visible" % ["SpriteLayers/" + sprite.name] + ) + animation.track_insert_key(track_idx, 0.0, false) + + # add animation tracks + for motion_idx in action.motions.size(): + var motion: Motion = action.motions[motion_idx] + + # TODO: no animations to speak of available ? sprite_index = -1 + #if motion.event_id == -1: + #continue + + var timing = motion_idx * frame_timing_base + var visible_key = 0 + + # add visible = false animation tracks to other sprite_layers + for i in motion_sprite_layers_max_count: + var track_idx = i * track_properties.size() + track_properties.find("visible") + visible_key = animation.track_insert_key(track_idx, timing, false) + + for sprite_layer_idx in motion.sprite_layers.size(): + var layer: SpriteLayer = motion.sprite_layers[sprite_layer_idx] + + if layer.sprite_index == -1: + continue + + var track_base_idx = sprite_layer_idx * track_properties.size() + + animation.track_insert_key( + track_base_idx + track_properties.find("animation"), + timing, + "default" + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("frame"), + timing, + layer.sprite_index + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("speed_scale"), + timing, + 1.0 + ) + + var layer_image := sprite_frames.get_frame_texture("default", layer.sprite_index) + var position: Vector2 = layer.get_position() - ceil(layer_image.get_size() / 2) # for fixing half pixel drawing + var rotated = layer_image.get_size().rotated(deg_to_rad(layer.rotation_degrees)) + var distance = layer_image.get_size() - rotated + animation.track_insert_key( + track_base_idx + track_properties.find("position"), + timing, + position + (distance / 2) + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("self_modulate"), + timing, + layer.get_color() + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("scale"), + timing, + layer.get_scale() + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("rotation_degrees"), + timing, + layer.rotation_degrees + ) + + animation.track_insert_key( + track_base_idx + track_properties.find("flip_h"), + timing, + layer.flip_h + ) + + animation.track_set_key_value( + track_base_idx + track_properties.find("visible"), + visible_key, + true + ) + + animation_player.add_animation_library("", animation_library) + + return node + + class ActionData: ## Byte Type: u32 [br] ## Byte Length: 4 diff --git a/extractor/actions.gd b/extractor/actions.gd index 5c94cef..e5dafda 100644 --- a/extractor/actions.gd +++ b/extractor/actions.gd @@ -1,3 +1,4 @@ +class_name Actions extends Node2D diff --git a/extractor/extractor_interface.gd b/extractor/extractor_interface.gd new file mode 100644 index 0000000..4d74a20 --- /dev/null +++ b/extractor/extractor_interface.gd @@ -0,0 +1,45 @@ +extends Control + + +func _ready() -> void: + pass + + var grf = GRF.open("res://client_data/data.grf") + #grf.extract("res://client_data") + grf.convert("res://client_data") + + #Sprite.from_bytes(FileAccess.get_file_as_bytes("res://client_data/data/sprite/cursors.spr")) + #ActionFormat.from_bytes( + #ByteStream.from_bytes( + #FileAccess.get_file_as_bytes("res://client_data/data/sprite/cursors.act") + #) + #) + #GATFormat.from_bytes( + #ByteStream.from_bytes( + #FileAccess.get_file_as_bytes("res://client_data/data/int_land02.gat") + #) + #) + #GNDFormat.from_bytes( + #ByteStream.from_bytes( + #FileAccess.get_file_as_bytes("res://client_data/data/int_land02.gnd") + #) + #) + #var rsw = RSWFormat.from_bytes( + #ByteStream.from_bytes( + ##FileAccess.get_file_as_bytes("res://client_data/data/int_land02.rsw") + #FileAccess.get_file_as_bytes("res://client_data/data/pay_dun00.rsw") + #) + #) + #RSMFormat.from_bytes( + #ByteStream.from_bytes( + ##FileAccess.get_file_as_bytes("res://client_data/data/model/prontera/chair_01.rsm") + #FileAccess.get_file_as_bytes("res://client_data/data/model/izlude/iz_academy.rsm") + ## TODO: not parseable + ##FileAccess.get_file_as_bytes("res://client_data/data/model/graywolf/bridge_e_01.rsm2") + #) + #) + + #var scene_root := rsw.convert("pay_dun00", "res://client_data") + #var scene := PackedScene.new() + #scene.pack(scene_root) + #ResourceSaver.save(scene, "res://extractor/test/pay_dun00.tscn") diff --git a/extractor/extractor_interface.tscn b/extractor/extractor_interface.tscn new file mode 100644 index 0000000..7220d5c --- /dev/null +++ b/extractor/extractor_interface.tscn @@ -0,0 +1,98 @@ +[gd_scene load_steps=3 format=3 uid="uid://bfi14ujn6fe1n"] + +[ext_resource type="Script" uid="uid://db2yelol3joq0" path="res://extractor/extractor_interface.gd" id="1_hyoq1"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hyoq1"] +bg_color = Color(0.287085, 0.287085, 0.287085, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.47166, 0.47166, 0.471659, 1) + +[node name="ExtractorInterface" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_hyoq1") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +offset_left = 387.0 +offset_top = 307.0 +offset_right = 765.0 +offset_bottom = 340.0 + +[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_hyoq1") + +[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/PanelContainer"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="LineEdit" type="LineEdit" parent="HBoxContainer/PanelContainer/HBoxContainer"] +layout_mode = 2 +text = "res://client_data/data.grf" +expand_to_text_length = true + +[node name="Button" type="Button" parent="HBoxContainer/PanelContainer/HBoxContainer"] +layout_mode = 2 +text = "Select File" + +[node name="Button" type="Button" parent="HBoxContainer"] +layout_mode = 2 +text = "Open GRF" + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 0 +offset_left = 386.0 +offset_top = 349.0 +offset_right = 754.0 +offset_bottom = 389.0 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/HBoxContainer2"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_hyoq1") + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer2/PanelContainer2"] +layout_mode = 2 +theme_override_constants/separation = 0 + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer2/PanelContainer2/HBoxContainer"] +layout_mode = 2 +text = "res://client_data" +expand_to_text_length = true + +[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer2/PanelContainer2/HBoxContainer"] +layout_mode = 2 +text = "Select Directory" + +[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Extract GRF" + +[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Button" type="Button" parent="."] +layout_mode = 0 +offset_left = 272.0 +offset_top = 433.0 +offset_right = 476.0 +offset_bottom = 464.0 +text = "Convert Sprites & Actions" + +[node name="Button2" type="Button" parent="."] +layout_mode = 0 +offset_left = 498.0 +offset_top = 434.0 +offset_right = 614.0 +offset_bottom = 465.0 +text = "Convert Maps" diff --git a/extractor/gat_format.gd b/extractor/gat_format.gd index 213972a..7192cbc 100644 --- a/extractor/gat_format.gd +++ b/extractor/gat_format.gd @@ -42,33 +42,41 @@ static func from_bytes(bytes: ByteStream) -> GATFormat: return gat_format +enum TileFlags { + Walkable = 0b00000001, + Water = 0b00000010, + Snipable = 0b00000100, + Cliff = 0b00001000, +} + + class Tile: ## Byte Type: f32 [br] ## Byte Length: 4 [br] - ## Orignal Coordinates_ (0, 0) + ## Orignal Coordinates: (0, 0) var bottom_left_altitude: int ## Byte Type: f32 [br] ## Byte Length: 4 [br] - ## Orignal Coordinates_ (1, 0) + ## Orignal Coordinates: (1, 0) var bottom_right_altitude: int ## Byte Type: f32 [br] ## Byte Length: 4 [br] - ## Orignal Coordinates_ (0, 1) + ## Orignal Coordinates: (0, 1) var top_left_altitude: int ## Byte Type: f32 [br] ## Byte Length: 4 [br] - ## Orignal Coordinates_ (1, 1) + ## Orignal Coordinates: (1, 1) var top_right_altitude: int - ## Byte Type: u32 ?[br] - ## Byte Length: 4 ? + ## Byte Type: u8 [br] + ## Byte Length: 1 var terrain_type: int - func get_height_map() -> Dictionary: # Dictionary[Vector2, int] + func get_height_map() -> Dictionary[Vector2, int]: return { Vector2(0, 0): top_left_altitude, Vector2(1, 0): top_right_altitude, @@ -84,6 +92,8 @@ class Tile: tile.bottom_right_altitude = bytes.decode_float() tile.top_left_altitude = bytes.decode_float() tile.top_right_altitude = bytes.decode_float() - tile.terrain_type = bytes.decode_u32() + tile.terrain_type = bytes.decode_u8() + + bytes.advance(3) # unused return tile diff --git a/extractor/gnd_format.gd b/extractor/gnd_format.gd new file mode 100644 index 0000000..c49bce6 --- /dev/null +++ b/extractor/gnd_format.gd @@ -0,0 +1,431 @@ +class_name GNDFormat + + +const MAP_TILE_SIZE := 10.0 + + +## Byte Length: 4 [br] +## GRAT +var signature: String = "GRGN" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: i32 [br] +## Byte Length: 4 +var width: int + +## Byte Type: i32 [br] +## Byte Length: 4 +var height: int + +## Byte Type: f32 [br] +## Byte Length: 4 [br] +## Always 10. +var scale: float + +## Byte Type: i32 [br] +## Byte Length: 4 +var texture_count: int + +## Byte Type: i32 [br] +## Byte Length: 4 +## Always 80. +var texture_path_length: int + +## Byte Length: [member texture_count] * [member texture_path_length] [br] +var texture_paths: Array + +## Byte Type: i32 [br] +## Byte Length: 4 +var light_map_slice_count: int + +## Byte Type: i32 [br] +## Byte Length: 4 +var light_map_width: int + +## Byte Type: i32 [br] +## Byte Length: 4 +var light_map_slice_height: int + +## Byte Type: i32 [br] +## Byte Length: 4 +var light_map_cells_per_grid: int + +## Length: [member light_map_slice_count] +var light_map_slices: Array + +## Byte Type: i32 [br] +## Byte Length: 4 +var surface_count: int + +## Length: [member surface_count] +var surfaces: Array[Surface] + +## Length: [member width] * [member height] +var ground_mesh_cubes: Array[GroundMeshCube] + + +static func from_bytes(bytes: ByteStream) -> GNDFormat: + var gnd_format = GNDFormat.new() + + bytes.advance(gnd_format.signature.length()) + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.major = bytes.decode_u8() + version.minor = bytes.decode_u8() + gnd_format.version = version + + if version.minor > 7: + print(version) + return gnd_format + + gnd_format.width = bytes.decode_s32() + gnd_format.height = bytes.decode_s32() + gnd_format.scale = bytes.decode_float() + gnd_format.texture_count = bytes.decode_s32() + gnd_format.texture_path_length = bytes.decode_s32() + + gnd_format.texture_paths = [] + for _n in gnd_format.texture_count: + gnd_format.texture_paths.append( + bytes.get_string_from_ro(gnd_format.texture_path_length) + ) + + gnd_format.light_map_slice_count = bytes.decode_s32() + gnd_format.light_map_width = bytes.decode_s32() + gnd_format.light_map_slice_height = bytes.decode_s32() + gnd_format.light_map_cells_per_grid = bytes.decode_s32() + + #gnd_format.light_map_slices = [] + #for _n in gnd_format.light_map_slice_count: + #gnd_format.light_map_slices.append(LightMapSlice.from_bytes(bytes)) + bytes.advance( + gnd_format.light_map_slice_count * + gnd_format.light_map_width * + gnd_format.light_map_slice_height * + 4 + ) + + gnd_format.surface_count = bytes.decode_s32() + + gnd_format.surfaces = [] as Array[Surface] + for _n in gnd_format.surface_count: + gnd_format.surfaces.append(Surface.from_bytes(bytes)) + + gnd_format.ground_mesh_cubes = [] as Array[GroundMeshCube] + for _n in (gnd_format.width * gnd_format.height): + gnd_format.ground_mesh_cubes.append(GroundMeshCube.from_bytes(bytes)) + + #print(inst_to_dict(gnd_format)) + return gnd_format + + +func get_cubes() -> Array: + # TODO: return custom type with helper functions for getting neighboring cubes and building a full cube + + var cubes := [] + for cube in ground_mesh_cubes: + cubes.append({ + "cube": cube, + Vector3(0, 1, 0): { + "surface": surfaces[cube.upwards_facing_surface_index], + "mesh": surfaces[cube.upwards_facing_surface_index].get_mesh({ + Vector2(0, 0): Vector3(0, cube.top_left_height, 0), + Vector2(1, 0): Vector3(MAP_TILE_SIZE, cube.top_right_height, 0), + Vector2(1, 1): Vector3(MAP_TILE_SIZE, cube.bottom_right_height, MAP_TILE_SIZE), + Vector2(0, 1): Vector3(0, cube.bottom_left_height, MAP_TILE_SIZE), + }), + }, + }) + + return cubes + + +func convert(data_path: String) -> GridMap: + var grid_map := GridMap.new() + grid_map.cell_size = Vector3(GNDFormat.MAP_TILE_SIZE, GNDFormat.MAP_TILE_SIZE, GNDFormat.MAP_TILE_SIZE) + grid_map.cell_center_x = false + grid_map.cell_center_y = false + grid_map.cell_center_z = false + + grid_map.position = Vector3( + (width * MAP_TILE_SIZE) / 2.0, + 0, + -(height * MAP_TILE_SIZE) / 2.0, + ) + + var library := MeshLibrary.new() + grid_map.mesh_library = library + + var cubes := get_cubes() + + var cache := {} + # TODO: use texture_index and surface uvs as key + # TODO: for deduplication of cell items (as long as other sides aren't accounted for..) + + for x in width: + for y in height: + var cube = cubes[x + y * width] + + # TODO: get all sides of a cube and add that mesh to the library + # TODO: so a single mesh per cube + for surface_type in [Vector3(0, 1, 0)]: + var mesh: ArrayMesh = cube[surface_type].mesh + var material := StandardMaterial3D.new() + material.albedo_texture = load( + "%s/data/texture/%s" % [ + data_path, + texture_paths[cube[surface_type].surface.texture_index] + ] + ) + + mesh.surface_set_material(0, material) + + var id := library.get_last_unused_item_id() + library.create_item(id) + library.set_item_mesh(id, mesh) + + grid_map.set_cell_item(Vector3(-x, 0, y), id) + + return grid_map + + +class LightMapSlice: + ## Byte Type: i32 [br] + ## Byte Length: 4 + var pixel_format: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var width: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var height: int + + ## Byte Length: [member width] * [member height] + var shadow_map_pixels: ByteStream + + ## Byte Length: [member width] * [member height] + var light_map_pixels: ByteStream + + + static func from_bytes(bytes: ByteStream) -> LightMapSlice: + var slice = LightMapSlice.new() + + slice.pixel_format = bytes.decode_s32() + slice.width = bytes.decode_s32() + slice.height = bytes.decode_s32() + slice.shadow_map_pixels = bytes.get_buffer(slice.width * slice.height) + slice.light_map_pixels = bytes.get_buffer(slice.width * slice.height) + + #print(inst_to_dict(slice)) + return slice + + +class Surface: + ## Byte Type: f32 [br] + ## Byte Length: 4 + var u_bottom_left: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var u_bottom_right: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var u_top_left: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var u_top_right: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var v_bottom_left: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var v_bottom_right: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var v_top_left: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var v_top_right: float + + ## Byte Type: i16 [br] + ## Byte Length: 2 + var texture_index: int + + ## Byte Type: i16 [br] + ## Byte Length: 2 + var light_map_index: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var vertex_color_blue: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var vertex_color_green: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var vertex_color_red: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var vertex_color_alpha: int + + + func get_uvs() -> Dictionary[Vector2, Vector2]: + return { + Vector2(0, 0): Vector2(u_top_left, v_top_left), + Vector2(1, 0): Vector2(u_top_right, v_top_right), + Vector2(0, 1): Vector2(u_bottom_left, v_bottom_left), + Vector2(1, 1): Vector2(u_bottom_right, v_bottom_right), + } + + + func get_vertex_color() -> Color: + return Color8( + vertex_color_red, + vertex_color_green, + vertex_color_blue, + vertex_color_alpha + ) + + + func get_mesh(vertices: Dictionary[Vector2, Vector3]) -> ArrayMesh: + var surface_tool := SurfaceTool.new() + surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES) + + var uvs = get_uvs() + var color = get_vertex_color() + + surface_tool.add_triangle_fan( + PackedVector3Array([ + vertices[Vector2(0, 0)], + vertices[Vector2(1, 0)], + vertices[Vector2(1, 1)], + ]), + PackedVector2Array([ + uvs[Vector2(0, 0)], + uvs[Vector2(1, 0)], + uvs[Vector2(1, 1)], + ]), + PackedColorArray([ + color, color, color, + ]) + ) + surface_tool.add_triangle_fan( + PackedVector3Array([ + vertices[Vector2(0, 0)], + vertices[Vector2(1, 1)], + vertices[Vector2(0, 1)], + ]), + PackedVector2Array([ + uvs[Vector2(0, 0)], + uvs[Vector2(1, 1)], + uvs[Vector2(0, 1)], + ]), + PackedColorArray([ + color, color, color, + ]) + ) + + return surface_tool.commit() + + + func get_uv_id() -> String: + # TODO: add more data? + return ( + "%d %d %d %d %d %d %d %d" % [ + u_top_left, v_top_left, + u_top_right, v_top_right, + u_bottom_right, v_bottom_right, + u_bottom_left, v_bottom_left + ] + ).md5_text() + + + static func from_bytes(bytes: ByteStream) -> Surface: + var surface = Surface.new() + + surface.u_bottom_left = bytes.decode_float() + surface.u_bottom_right = bytes.decode_float() + surface.u_top_left = bytes.decode_float() + surface.u_top_right = bytes.decode_float() + surface.v_bottom_left = bytes.decode_float() + surface.v_bottom_right = bytes.decode_float() + surface.v_top_left = bytes.decode_float() + surface.v_top_right = bytes.decode_float() + surface.texture_index = bytes.decode_s16() + surface.light_map_index = bytes.decode_s16() + surface.vertex_color_blue = bytes.decode_u8() + surface.vertex_color_green = bytes.decode_u8() + surface.vertex_color_red = bytes.decode_u8() + surface.vertex_color_alpha = bytes.decode_u8() + + #print(inst_to_dict(surface)) + return surface + + +class GroundMeshCube: + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## upper left? + var bottom_left_height: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## upper right? + var bottom_right_height: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## lower left? + var top_left_height: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## lower right? + var top_right_height: float + + ## Byte Type: i32 [br] + ## Byte Length: 4 + ## top surface index? + var upwards_facing_surface_index: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + ## front surface index? + var northern_wall_surface_index: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + ## right surface index? + var eastern_wall_surface_index: int + + + static func from_bytes(bytes: ByteStream) -> GroundMeshCube: + var mesh = GroundMeshCube.new() + + mesh.bottom_left_height = bytes.decode_float() + mesh.bottom_right_height = bytes.decode_float() + mesh.top_left_height = bytes.decode_float() + mesh.top_right_height = bytes.decode_float() + mesh.upwards_facing_surface_index = bytes.decode_s32() + mesh.northern_wall_surface_index = bytes.decode_s32() + mesh.eastern_wall_surface_index = bytes.decode_s32() + + #print(inst_to_dict(mesh)) + return mesh + + # TODO: convert function to build cube mesh? or at least with neighbor input build full cube struct diff --git a/extractor/grf.gd b/extractor/grf.gd index 9917a74..5b7046f 100644 --- a/extractor/grf.gd +++ b/extractor/grf.gd @@ -156,7 +156,7 @@ class FileEntry: var file_access: FileAccess -static func open(path: String): +static func open(path: String) -> GRF: var grf = GRF.new() grf.file_access = FileAccess.open(path, FileAccess.ModeFlags.READ) @@ -190,23 +190,30 @@ static func open(path: String): return grf -func extract(destination: String = "res://data"): - for file_entry in file_entries: +signal extracted_file(index: int) + +func extract(destination: String = "user://client_data"): + DirAccess.make_dir_recursive_absolute(destination) + + for idx in file_entries.size(): + var file_entry := file_entries[idx] var file_path: String = file_entry.get_file_path() var base_directory = DirAccess.open(destination) - base_directory.make_dir_recursive("extracted/" + file_path.get_base_dir()) + base_directory.make_dir_recursive(file_path.get_base_dir()) - var file = FileAccess.open("%s/extracted/%s" % [destination, file_path], FileAccess.WRITE_READ) + var file = FileAccess.open("%s/%s" % [destination, file_path], FileAccess.WRITE_READ) file.store_buffer(file_entry.get_contents(file_access)) + + extracted_file.emit(idx) -func convert(destination: String = "res://data"): +func convert(destination: String = "res://client_data"): for file_entry in file_entries: var file_path: String = file_entry.get_file_path() var base_directory = DirAccess.open(destination) - var base_directory_path = "extracted/%s" % file_path.get_base_dir() + var base_directory_path = "%s" % file_path.get_base_dir() base_directory.make_dir_recursive(base_directory_path) base_directory.change_dir(base_directory_path) @@ -217,208 +224,71 @@ func convert(destination: String = "res://data"): #DirAccess.make_dir_recursive_absolute(base_file_directory_path) - var player_head_path_part = "¸Ó¸®Åë" - var player_body_path_part = "¸öÅë" - if file_path.ends_with(".spr") and file_path.contains(player_head_path_part): + # BMP + if file_path.ends_with(".bmp"): + #continue + if not FileAccess.file_exists("%s/%s" % [destination, file_path]): + continue + + # load existing bmp files, so language specific overwrites are kept + + var texture: CompressedTexture2D = load("%s/%s" % [destination, file_path]) + if not texture: + # TODO: check if .godot/imported file is there (alrdy sufficient?) + continue + + var texture_image := texture.get_image() + texture_image.decompress() + var image := BMPTexture.convert_image( + texture_image, + [Color.MAGENTA] + ) + image.save_png("%s/%s" % [destination, file_path.replace(".bmp", ".png")]) + + + continue + # Sprite.spr and Action.act + + var player_head_path_part = "머리통" + var player_body_path_part = "몸통" + + if file_path.ends_with(".spr"): #and (file_path.contains(player_head_path_part) or file_path.contains(player_body_path_part)): + continue var sprite = SpriteFormat.from_bytes(file_entry.get_contents(file_access)) sprite.save_to_file(base_file_directory_path) - elif file_path.ends_with(".act") and file_path.contains(player_head_path_part): + elif file_path.ends_with(".act") and file_path.contains("cursors"): #and (file_path.contains(player_head_path_part) or file_path.contains(player_body_path_part)): + #continue if not FileAccess.file_exists("%s/000.png.import" % base_file_directory_path): continue - var scene := PackedScene.new() - var scene_root := Node2D.new() - scene_root.name = "Actions" - scene_root.set_script(load("res://extractor/actions.gd")) - - var animation_player := AnimationPlayer.new() - animation_player.name = "AnimationPlayer" - animation_player.unique_name_in_owner = true - scene_root.add_child(animation_player) - animation_player.owner = scene_root - - var sprite_layers := CanvasGroup.new() - sprite_layers.name = "SpriteLayers" - sprite_layers.unique_name_in_owner = true + var action := ActionFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access))) + var scene_root := action.convert(file_name, base_file_directory_path) - scene_root.add_child(sprite_layers) - sprite_layers.owner = scene_root - - var track_properties = [ - "animation", - "frame", - "speed_scale", - "position", - "self_modulate", - "scale", - "rotation_degrees", - "flip_h", - "visible", - ] - - var sprite_frames := SpriteFrames.new() - #sprite_frames.add_animation("default") - for img_file_path in DirAccess.get_files_at(base_file_directory_path): - if img_file_path.ends_with(".png"): - sprite_frames.add_frame("default", load("%s/%s" % [base_file_directory_path, img_file_path])) - - var animation_library := AnimationLibrary.new() - var action_data := ActionFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access))) - - # get max number of sprite layers for all actions - var action_sprite_layers_max_count = action_data.actions.reduce(func(accum, action: ActionFormat.ActionData): - return max(accum, action.motions.reduce(func(accum2, motion: ActionFormat.Motion): - return max(accum2, motion.sprite_layer_count) - , 0)) - , 0) - - # add Nodes for each sprite layer - for sprite_layer_idx in action_sprite_layers_max_count: - var sprite = AnimatedSprite2D.new() - sprite.centered = false # 必要!! - sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - sprite.sprite_frames = sprite_frames - sprite.name = str(sprite_layer_idx).pad_zeros(3) - sprite_layers.add_child(sprite) - sprite.owner = scene_root - - for action_idx in action_data.actions.size(): - var action: ActionFormat.ActionData = action_data.actions[action_idx] - var frame_timing_base := ((action_data.frame_times[action_idx] * 24) / 1000) - - if file_path.contains("cursors") and action_idx == 0: - frame_timing_base = ((action_data.frame_times[action_idx] * 24 * 2) / 1000) - - # add animation for each action - var animation := Animation.new() - animation.loop_mode = Animation.LOOP_LINEAR - animation.length = frame_timing_base * action.motion_count - animation_library.add_animation(str(action_idx).pad_zeros(3), animation) - - # get max number of sprite layers for current action motions - var motion_sprite_layers_max_count = action.motions.reduce(func(accum, motion: ActionFormat.Motion): - return max(accum, motion.sprite_layer_count) - , 0) - - # add animation tracks for each sprite layer - for sprite_layer_idx in motion_sprite_layers_max_count: - var sprite := sprite_layers.get_child(sprite_layer_idx) - for property_idx in track_properties.size(): - var track_idx = (sprite_layer_idx * track_properties.size()) + property_idx - animation.add_track(Animation.TYPE_VALUE, track_idx) - animation.value_track_set_update_mode(track_idx, Animation.UPDATE_DISCRETE) - animation.track_set_path( - track_idx, - "%s:%s" % ["SpriteLayers/" + sprite.name, track_properties[property_idx]] - ) - - for i in range(motion_sprite_layers_max_count, action_sprite_layers_max_count): - var sprite := sprite_layers.get_child(i) - var track_idx = animation.add_track(Animation.TYPE_VALUE) - animation.track_set_path( - track_idx, - "%s:visible" % ["SpriteLayers/" + sprite.name] - ) - animation.track_insert_key(track_idx, 0.0, false) - - # add animation tracks - for motion_idx in action.motions.size(): - var motion: ActionFormat.Motion = action.motions[motion_idx] - - # TODO: no animations to speak of available ? - if motion.event_id == -1: - continue - - var timing = motion_idx * frame_timing_base - var visible_key = 0 - - # add visible = false animation tracks to other sprite_layers - for i in motion_sprite_layers_max_count: - var track_idx = i * track_properties.size() + track_properties.find("visible") - visible_key = animation.track_insert_key(track_idx, timing, false) - - for sprite_layer_idx in motion.sprite_layers.size(): - var layer: ActionFormat.SpriteLayer = motion.sprite_layers[sprite_layer_idx] - - var track_base_idx = sprite_layer_idx * track_properties.size() - - animation.track_insert_key( - track_base_idx + track_properties.find("animation"), - timing, - "default" - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("frame"), - timing, - layer.sprite_index - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("speed_scale"), - timing, - 1.0 - ) - - var layer_image := sprite_frames.get_frame_texture("default", layer.sprite_index) - var position: Vector2 = layer.get_position() - ceil(layer_image.get_size() / 2) # for fixing half pixel drawing - var rotated = layer_image.get_size().rotated(deg_to_rad(layer.rotation_degrees)) - var distance = layer_image.get_size() - rotated - animation.track_insert_key( - track_base_idx + track_properties.find("position"), - timing, - position + (distance / 2) - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("self_modulate"), - timing, - layer.get_color() - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("scale"), - timing, - layer.get_scale() - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("rotation_degrees"), - timing, - layer.rotation_degrees - ) - - animation.track_insert_key( - track_base_idx + track_properties.find("flip_h"), - timing, - layer.flip_h - ) - - animation.track_set_key_value( - track_base_idx + track_properties.find("visible"), - visible_key, - true - ) - - animation_player.add_animation_library("", animation_library) + var scene := PackedScene.new() scene.pack(scene_root) # TODO: doesn't work if png is not imported via editor focus => run game twice ResourceSaver.save(scene, "%s/actions.tscn" % base_file_directory_path) + + + continue + # Map.rsw and .gnd and .gat + + if file_path.ends_with(".rsw") and (file_path.contains("pay_dun") or file_path.contains("iz_int") or file_path.contains("int_land")): + var rsw = RSWFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access))) + var scene_root := rsw.convert(file_name, "res://client_data") + + var scene := PackedScene.new() + scene.pack(scene_root) + ResourceSaver.save(scene, "%s/%s/%s.tscn" % [destination, base_directory_path, file_name]) static func decode_string(bytes: PackedByteArray): - return bytes.get_string_from_ascii() - @warning_ignore("unreachable_code") - # TODO: check unicode codepoints and parse accordingly - var string = bytes.get_string_from_utf32() - if string == "": - string = bytes.get_string_from_utf16() - if string == "": - string = bytes.get_string_from_utf8() - if string == "": - string = bytes.get_string_from_ascii() - + Engine.print_error_messages = false + var string := bytes.get_string_from_multibyte_char("EUC-KR") + Engine.print_error_messages = true + if string.is_empty(): + return bytes.get_string_from_ascii() return string diff --git a/extractor/map.gd b/extractor/map.gd new file mode 100644 index 0000000..c3a9713 --- /dev/null +++ b/extractor/map.gd @@ -0,0 +1,34 @@ +extends Node3D + + +func _ready() -> void: + # add player + var map_server_login_success_packet: MapServerLoginSuccessPacket = Network.map_server.received_packets[MapServerLoginSuccessPacket.HEADER][0] + var initial_player_position: Vector2 = map_server_login_success_packet.get_position() + + var player = preload("res://entities/player.tscn").instantiate() + player.position = Vector3(initial_player_position.x, 0, initial_player_position.y) + add_child(player) + + # listen to packets + Network.map_server.received_packet.connect(func(packet: Packet): + if packet is ChangeMapPacket: + player.position.x = packet.get_position().x + player.position.z = packet.get_position().y + ) + + # play audio + for node: AudioStreamPlayer3D in find_children("se_*"): + node.play() + + # add HUD TODO: add all HUD as HUD scene + var chat_window = preload("res://ui/chat_window.tscn").instantiate() + add_child(chat_window) + + # TODO: load map. + # TODO: whatever else needs to be loaded after converting from rsw + + var map_loaded_packet := MapLoadedPacket.new() + Network.map_server.send(map_loaded_packet) + + # TODO: check which map server packets to send next diff --git a/extractor/rsm_format.gd b/extractor/rsm_format.gd new file mode 100644 index 0000000..7f16368 --- /dev/null +++ b/extractor/rsm_format.gd @@ -0,0 +1,653 @@ +class_name RSMFormat + + +## Byte Length: 4 [br] +## GRAT +var signature: String = "GRSM" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: u32 [br] +## Byte Length: 4 +var animation_length: int + +## Byte Type: u32 [br] +## Byte Length: 4 +var shade_type: int + +## Byte Type: u8 [br] +## Byte Length: 1 +## Versions: [>=1.4] +var alpha: int + +## Byte Type: u8 [br] +## Byte Length: 16 +## Versions: [<2.2] +var reserved: PackedByteArray + +## Byte Type: f32 [br] +## Byte Length: 4 +## Versions: [>=2.2] +var frames_per_second: float + +## Byte Type: u32 [br] +## Byte Length: 4 +## Versions: [<2.3] +var texture_count: int + +## Length: [member texture_count] +## Byte Length: 40 +## Versions: [<2.3] +var texture_names: Array[String] + +## Byte Type: u8 +## Byte Length: 40 +## Versions: [<2.2] +var root_node_name: String + +## Byte Type: u32 [br] +## Byte Length: 4 +## Versions: [>=2.2] +var root_node_count: int + +## Length: [member root_node_count] +## Byte Length: 40 +var root_node_names: Array[String] + +## Byte Type: u32 [br] +## Byte Length: 4 +var node_count: int + +## Length: [member node_count] +var nodes: Array[ModelNode] + + +static func from_bytes(bytes: ByteStream) -> RSMFormat: + var rsm_format = RSMFormat.new() + + bytes.advance(rsm_format.signature.length()) + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.major = bytes.decode_u8() + version.minor = bytes.decode_u8() + rsm_format.version = version + + rsm_format.animation_length = bytes.decode_u32() + rsm_format.shade_type = bytes.decode_u32() + + if version.higher_than(1, 3): # >= 1.4 + rsm_format.alpha = bytes.decode_u8() + + if version.lower_than(2, 2): # < 2.2 + rsm_format.reserved = bytes.get_buffer(16).bytes + + if version.higher_than(2, 1): # >= 2.2 + rsm_format.frames_per_second = bytes.decode_float() + + if version.lower_than(2, 3): # < 2.3 + rsm_format.texture_count = bytes.decode_u32() + + rsm_format.texture_names = [] as Array[String] + for _n in rsm_format.texture_count: + rsm_format.texture_names.append(bytes.get_string_from_ro(40)) + + if version.lower_than(2, 2): + rsm_format.root_node_name = bytes.get_string_from_ro(40) + + if version.higher_than(2, 1): # >= 2.2 + rsm_format.root_node_count = bytes.decode_u32() + + rsm_format.root_node_names = [] as Array[String] + for _n in rsm_format.root_node_count: + rsm_format.root_node_names.append(bytes.get_string_from_ro(40)) + + rsm_format.node_count = bytes.decode_u32() + rsm_format.nodes = [] as Array[ModelNode] + for _n in rsm_format.node_count: + rsm_format.nodes.append(ModelNode.from_bytes(bytes, version)) + + #print(inst_to_dict(rsm_format)) + #print(inst_to_dict(rsm_format.nodes[0].texture_coordinates[0])) + #rsm_format.nodes[0].texture_coordinates.clear() + #print(inst_to_dict(rsm_format.nodes[0].faces[0])) + #rsm_format.nodes[0].faces.clear() + #print(inst_to_dict(rsm_format.nodes[0])) + return rsm_format + + +func convert(data_path: String) -> Node3D: + var root_node := Node3D.new() + root_node.name = root_node_name + #node.set_script(load("res://extractor/model.gd")) + + for model_node in nodes: + var node: Node = model_node.convert(texture_names, data_path) + root_node.add_child(node) + + return root_node + + +class ModelNode: + ## Byte Type: u8 [br] + ## Byte Length: 40 + var node_name: String + + ## Byte Type: u8 [br] + ## Byte Length: 40 + var parent_node_name: String + + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + ## Versions: [<2.3] + var texture_count: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + ## Length: [member texture_count] [br] + ## Versions: [<2.3] + var texture_indices: Array[int] + + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + ## Versions: [>=2.3] + var texture_name_count: int + + ## Byte Length: 40 [br] + ## Length: [member texture_name_count] [br] + ## Versions: [>=2.3] + var texture_names: Array[String] + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 9 [br] + ## Length: 9 [br] + ## 3 x 3 Matrix. Each element represents a column. + var offset_matrix: Array[Vector3] + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + ## Versions: [<2.2] + var translation_1: Vector3 + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + var translation_2: Vector3 + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## Versions: [<2.2] + ## Type: Radiants + var rotation_angle: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + ## Versions: [<2.2] + var rotation_axis: Vector3 + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + ## Versions: [<2.2] + var scale: Vector3 + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var vertex_position_count: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 * [member vertex_position_count] [br] + ## Length: [member vertex_position_count] + var vertex_positions: Array[Vector3] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var texture_coordinate_count: int + + ## Length: [member texture_coordinate_count] + var texture_coordinates: Array[TextureCoordinate] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var face_count: int + + ## Length: [member face_count] + var faces: Array[Face] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + ## Versions: [>=1.6] + var scale_keyframe_count: int + + ## Length: [member scale_keyframe_count] + ## Versions: [>=1.6] + var scale_keyframes: Array[ScaleKeyframe] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var rotation_keyframe_count: int + + ## Length: [member scale_keyframe_count] + var rotation_keyframes: Array[RotationKeyframe] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + ## Versions: [>=2.2] + var translation_keyframe_count: int + + ## Length: [member scale_keyframe_count] + ## Versions: [>=2.2] + var translation_keyframes: Array[TranslationKeyframe] + + ## Byte Type: u32 [br] + ## Byte Length: 4 + ## Versions: [>=2.3] + var textures_keyframe_count: int + + ## Length: [member scale_keyframe_count] + ## Versions: [>=2.3] + var textures_keyframes: Array[TexturesKeyframe] + + + static func from_bytes(bytes: ByteStream, version: Version) -> ModelNode: + var node = ModelNode.new() + + node.node_name = bytes.get_string_from_utf8(40) + node.parent_node_name = bytes.get_string_from_ro(40) + + if version.lower_than(2, 3): # < 2.3 + node.texture_count = bytes.decode_u32() + + node.texture_indices = [] as Array[int] + for _n in node.texture_count: + node.texture_indices.append(bytes.decode_u32()) + + if version.higher_than(2, 2): # >= 2.3 + node.texture_name_count = bytes.decode_u32() + + node.texture_names = [] as Array[String] + for _n in node.texture_name_count: + node.texture_names.append(bytes.get_string_from_ro(40)) + + node.offset_matrix = [] as Array[Vector3] + for _in in 3: + node.offset_matrix.append(Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + )) + + if version.lower_than(2, 2): + node.translation_1 = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + + node.translation_2 = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + + if version.lower_than(2, 2): + node.rotation_angle = bytes.decode_float() + node.rotation_axis = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + node.scale = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + + node.vertex_position_count = bytes.decode_u32() + node.vertex_positions = [] as Array[Vector3] + + for _n in node.vertex_position_count: + node.vertex_positions.append(Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + )) + + node.texture_coordinate_count = bytes.decode_u32() + node.texture_coordinates = [] as Array[TextureCoordinate] + + for _n in node.texture_coordinate_count: + node.texture_coordinates.append(TextureCoordinate.from_bytes(bytes, version)) + + node.face_count = bytes.decode_u32() + node.faces = [] as Array[Face] + + for _n in node.face_count: + node.faces.append(Face.from_bytes(bytes, version)) + + if version.higher_than(1, 5): # >= 1.6 + node.scale_keyframe_count = bytes.decode_u32() + node.scale_keyframes = [] as Array[ScaleKeyframe] + + for _n in node.scale_keyframe_count: + node.scale_keyframes.append(ScaleKeyframe.from_bytes(bytes)) + + node.rotation_keyframe_count = bytes.decode_u32() + node.rotation_keyframes = [] as Array[RotationKeyframe] + + for _n in node.rotation_keyframe_count: + node.rotation_keyframes.append(RotationKeyframe.from_bytes(bytes)) + + if version.higher_than(2, 1): # >= 2.2 + node.translation_keyframe_count = bytes.decode_u32() + node.translation_keyframes = [] as Array[ScaleKeyframe] + + for _n in node.translation_keyframe_count: + node.translation_keyframes.append(TranslationKeyframe.from_bytes(bytes)) + + if version.higher_than(2, 2): # >= 2.3 + node.textures_keyframe_count = bytes.decode_u32() + node.textures_keyframes = [] as Array[TexturesKeyframe] + + for _n in node.textures_keyframe_count: + node.textures_keyframes.append(TexturesKeyframe.from_bytes(bytes)) + + return node + + + func convert(textures: Array[String], data_path: String): + var node := MeshInstance3D.new() + node.name = node_name + + node.translate(translation_2) + + if rotation_axis != Vector3.ZERO: + node.rotation = (rotation_axis * rotation_angle) * Vector3(1,-1,1) + + node.scale = scale + + var surface_tool := SurfaceTool.new() + surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES) + + var mesh := ArrayMesh.new() + for idx in faces.size(): + var face := faces[idx] + + surface_tool.add_triangle_fan( + PackedVector3Array(face.vertex_position_indices.map(func(item_idx): + return vertex_positions[item_idx]) + ), + PackedVector2Array(face.texture_coordinate_indices.map(func(item_idx): + return texture_coordinates[item_idx].coordinates) + ) + ) + surface_tool.commit(mesh) + + var material := StandardMaterial3D.new() + material.albedo_texture = load("%s/data/texture/%s" % [data_path, textures[face.texture_index]]) + mesh.surface_set_material(idx, material) + + node.mesh = mesh + + return node + + +class TextureCoordinate: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + ## Versions: [>=1.2] + var color: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 2 [br] + ## Note: possibly wrong if version < 1.2 + var coordinates: Vector2 + + + static func from_bytes(bytes: ByteStream, version: Version) -> TextureCoordinate: + var data = TextureCoordinate.new() + + if version.higher_than(1, 1): + data.color = bytes.decode_u32() + + data.coordinates = Vector2( + bytes.decode_float(), + bytes.decode_float() + ) + + return data + + +class Face: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + ## Versions: [>=2.2] + var length: int + + ## Byte Type: u16 [br] + ## Byte Length: 2 * 3 + ## Length: 3 + var vertex_position_indices: Array[int] + + ## Byte Type: u16 [br] + ## Byte Length: 2 * 3 + ## Length: 3 + var texture_coordinate_indices: Array[int] + + ## Byte Type: u16 [br] + ## Byte Length: 2 + var texture_index: int + + ## Byte Type: u16 [br] + ## Byte Length: 2 + var padding: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var two_sided: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var smooth_group: int + + ## Byte Type: i32 [br] + ## Length: ([member length] - 24) / 4 [br] + ## Versions: [>=2.2] + # TODO: saturating_sub? + var smooth_group_extra: Array[int] + + + static func from_bytes(bytes: ByteStream, version: Version) -> Face: + var data = Face.new() + + if version.higher_than(2, 1): + data.length = bytes.decode_u32() + + data.vertex_position_indices = [] as Array[int] + for _in in 3: + data.vertex_position_indices.append(bytes.decode_u16()) + + data.texture_coordinate_indices = [] as Array[int] + for _in in 3: + data.texture_coordinate_indices.append(bytes.decode_u16()) + + data.texture_index = bytes.decode_u16() + data.padding = bytes.decode_u16() + data.two_sided = bytes.decode_s32() + data.smooth_group = bytes.decode_s32() + + if version.higher_than(2, 1): + data.smooth_group_extra = [] as Array[int] + + for _n in ((data.length - 24) / 4): + data.smooth_group_extra.append(bytes.decode_s32()) + + return data + + +class ScaleKeyframe: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + var frame: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + var scale: Vector3 + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + var reserved: float + + + static func from_bytes(bytes: ByteStream) -> ScaleKeyframe: + var data = ScaleKeyframe.new() + + data.frame = bytes.decode_u32() + data.scale = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + data.reserved = bytes.decode_float() + + return data + + +class RotationKeyframe: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + var frame: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + var rotation: Vector4 + + + static func from_bytes(bytes: ByteStream) -> RotationKeyframe: + var data = RotationKeyframe.new() + + data.frame = bytes.decode_u32() + data.rotation = Vector4( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + + return data + + +class TranslationKeyframe: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + var frame: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 * 3 [br] + var translation: Vector3 + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + var reserved: float + + + static func from_bytes(bytes: ByteStream) -> ScaleKeyframe: + var data = ScaleKeyframe.new() + + data.frame = bytes.decode_u32() + data.translation = Vector3( + bytes.decode_float(), + bytes.decode_float(), + bytes.decode_float() + ) + data.reserved = bytes.decode_float() + + return data + + +enum TextureOperation { + ## Texture translation on the X axis. The texture is tiled. + Translation_X, + + ## Texture translation on the Y axis. The texture is tiled. + Translation_Y, + + ## Texture multiplication on the X axis. The texture is tiled. + Scale_X, + + ## Texture multiplication on the Y axis. The texture is tiled. + Scale_Y, + + ## Texture rotation around (0, 0). The texture is not tiled. + Rotation, +} + + +class TexturesKeyframe: + ## Byte Type: u32 [br] + ## Byte Length: 4 + var texture_index: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var texture_keyframe_count: int + + ## Length: [member texture_keyframe_count] + var texture_keyframes: Array[TextureKeyframe] + + + static func from_bytes(bytes: ByteStream) -> TexturesKeyframe: + var data = TexturesKeyframe.new() + + data.texture_index = bytes.decode_u32() + data.texture_keyframe_count = bytes.decode_u32() + + data.texture_keyframes = [] as Array[TextureKeyframe] + for _n in data.texture_keyframe_count: + data.texture_keyframes.append(TextureKeyframe.from_bytes(bytes)) + + return data + + +class TextureKeyframe: + ## Byte Type: u8 + var operation_type: TextureOperation + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var frame_count: int + + ## Length: [member frame_count] + var texture_frames: Array[TextureFrame] + + + static func from_bytes(bytes: ByteStream) -> TextureKeyframe: + var data = TextureKeyframe.new() + + data.operation_type = bytes.decode_u8() + data.frame_count = bytes.decode_u32() + + data.texture_frames = [] as Array[TextureFrame] + for _n in data.frame_count: + data.texture_frames.append(TextureFrame.from_bytes(bytes)) + + return data + + +class TextureFrame: + ## Byte Type: u32 [br] + ## Byte Length: 4 [br] + var frame: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + var translation: float + + + static func from_bytes(bytes: ByteStream) -> TextureFrame: + var data = TextureFrame.new() + + data.frame = bytes.decode_u32() + data.translation = bytes.decode_float() + + return data diff --git a/extractor/rsw_format.gd b/extractor/rsw_format.gd new file mode 100644 index 0000000..7e70850 --- /dev/null +++ b/extractor/rsw_format.gd @@ -0,0 +1,699 @@ +class_name RSWFormat + + +## Byte Length: 4 [br] +## GRAT +var signature: String = "GRSW" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: u8, u32 [br] +## Byte Length: 1, 4 +## Versions: [2.2, 2.5] +var build_number: int + +## Byte Type: u8 [br] +## Byte Length: 1 +## Versions: [2.5] +var unknown_render_flag: int + +## Byte Type: u8 [br] +## Byte Length: 40 +var ini_file: String + +## Byte Type: u8 [br] +## Byte Length: 40 +var gnd_file: String + +## Byte Type: u8 [br] +## Byte Length: 40 +var gat_file: String + +## Byte Type: u8 [br] +## Byte Length: 40 +## Versions: [1.4] +var source_file: String + +var water_configuration: WaterConfiguration +var lighting_parameters: LightingParameters + +## Byte Type: u32 [br] +## Byte Length: 4 +var map_resource_count: int + +## Length: [member map_resource_count] +var map_resources: Array[MapResource] + +var quad_tree + + +static func from_bytes(bytes: ByteStream) -> RSWFormat: + var rsw_format = RSWFormat.new() + + bytes.advance(rsw_format.signature.length()) + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.major = bytes.decode_u8() + version.minor = bytes.decode_u8() + rsw_format.version = version + + if version.major < 2: + print(version) + return rsw_format + + if version.major >= 2 and version.minor >= 5: + rsw_format.build_number = bytes.decode_u32() + rsw_format.unknown_render_flag = bytes.decode_u8() + elif version.major >= 2 and version.minor >= 2: + rsw_format.build_number = bytes.decode_u8() + + rsw_format.ini_file = bytes.get_string_from_utf8(40) + rsw_format.gnd_file = bytes.get_string_from_utf8(40) + rsw_format.gat_file = bytes.get_string_from_utf8(40) + rsw_format.source_file = bytes.get_string_from_utf8(40) + + if version.major >= 2 and version.minor <= 5: + rsw_format.water_configuration = WaterConfiguration.from_bytes(bytes) + + rsw_format.lighting_parameters = LightingParameters.from_bytes(bytes) + + bytes.advance(16) # map boundaries - bounding box # TODO + + rsw_format.map_resource_count = bytes.decode_u32() + rsw_format.map_resources = [] as Array[MapResource] + for _n in rsw_format.map_resource_count: + var resource_type = bytes.decode_u32() + var resource: MapResource + + if resource_type == MapResourceType.Animated3DModel: + resource = Animated3DModel.from_bytes(bytes) + elif resource_type == MapResourceType.DynamicLightSource: + resource = DynamicLightSource.from_bytes(bytes) + elif resource_type == MapResourceType.SpatialAudioSource: + resource = SpatialAudioSource.from_bytes(bytes) + elif resource_type == MapResourceType.ParticleEffectEmitter: + resource = ParticleEffectEmitter.from_bytes(bytes) + + rsw_format.map_resources.append(resource) + + # TODO: quadtree + + #print(inst_to_dict(rsw_format)) + return rsw_format + + +func convert(name: String, data_path: String) -> Node3D: + var gnd_file_path = "%s/data/%s" % [data_path, gnd_file] + var gnd = GNDFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gnd_file_path))) + + var gat_file_path = "%s/data/%s" % [data_path, gat_file] + var gat = GATFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gat_file_path))) + + var node := Node3D.new() + node.name = name + node.set_script(load("res://extractor/map.gd")) + + for resource in map_resources: + if resource is Animated3DModel: + var model_file_path := "%s/data/model/%s" % [data_path, resource.model_file] + if not FileAccess.file_exists(model_file_path): + continue + + var rsm := RSMFormat.from_bytes(ByteStream.from_bytes( + FileAccess.get_file_as_bytes(model_file_path) + )) + + var model_root := Node3D.new() + model_root.name = resource.name + model_root.position = resource.get_position() * Vector3(-1, 1, 1) + model_root.rotation_degrees = resource.get_rotation() * Vector3(-1,1,-1) + model_root.scale = resource.get_scale() + + node.add_child(model_root) + model_root.owner = node + + var model := rsm.convert(data_path) + model_root.add_child(model) + model.owner = node + for child in model.get_children(): + child.owner = node + + elif resource is DynamicLightSource: + continue + var light := OmniLight3D.new() + light.name = resource.name + light.position = resource.get_position() + light.light_color = resource.get_diffuse_color() + light.omni_range = resource.light_range + + node.add_child(light) + light.owner = node + + elif resource is SpatialAudioSource: + continue + var audio_file_path := "%s/data/wav/%s" % [data_path, resource.audio_file] + if not FileAccess.file_exists(audio_file_path): + continue + + var audio = AudioStreamPlayer3D.new() + audio.stream = load(audio_file_path) + audio.name = resource.audio_file + audio.position = resource.get_position() + audio.volume_linear = resource.volume_gain + audio.max_distance = resource.audio_range + + node.add_child(audio, true) + audio.owner = node + + elif resource is ParticleEffectEmitter: + continue + var particles := CPUParticles3D.new() + particles.name = resource.name + particles.position = resource.get_position() + + node.add_child(particles, true) + particles.owner = node + + var grid_map := gnd.convert(data_path) + grid_map.name = "Ground" + node.add_child(grid_map) + grid_map.owner = node + + return node + + +class WaterConfiguration: + ## Byte Type: f32 [br] + ## Byte Length: 4 + var water_level: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var water_type: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var wave_height: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var wave_speed: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var wave_pitch: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var water_animation_speed: int + + + static func from_bytes(bytes: ByteStream) -> WaterConfiguration: + var config = WaterConfiguration.new() + + config.water_level = bytes.decode_float() + config.water_type = bytes.decode_u32() + config.wave_height = bytes.decode_float() + config.wave_speed = bytes.decode_float() + config.wave_pitch = bytes.decode_float() + config.water_animation_speed = bytes.decode_u32() + + return config + + +class LightingParameters: + ## Byte Type: i32 [br] + ## Byte Length: 4 + var longitude: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var latitude: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_r: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_g: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_b: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var ambient_color_r: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var ambient_color_g: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var ambient_color_b: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## Shadow Map Alpha + var intensity: int + + + func get_diffuse_color() -> Color: + return Color(diffuse_color_r, diffuse_color_g, diffuse_color_b) + + + func get_ambient_color() -> Color: + return Color(ambient_color_r, ambient_color_g, ambient_color_b) + + + static func from_bytes(bytes: ByteStream) -> LightingParameters: + var params = LightingParameters.new() + + params.longitude = bytes.decode_s32() + params.latitude = bytes.decode_s32() + params.diffuse_color_r = bytes.decode_float() + params.diffuse_color_g = bytes.decode_float() + params.diffuse_color_b = bytes.decode_float() + params.ambient_color_r = bytes.decode_float() + params.ambient_color_g = bytes.decode_float() + params.ambient_color_b = bytes.decode_float() + params.intensity = bytes.decode_float() + + return params + + +class MapResource: + pass + +enum MapResourceType { + Animated3DModel = 1, + DynamicLightSource = 2, + SpatialAudioSource = 3, + ParticleEffectEmitter = 4, +} + +enum ModelAnimationType { + None = 0, + Looping = 2, +} + +enum ParticlePresetEffect { + None = -1, + Hit_1 = 1, + Hit_2 = 2, + Hit_3 = 3, + Torch = 47, + TunaParty = 1097, +} + +enum QuadTreeQuadrant { + BottomLeft = 0, + BottomRight = 1, + TopLeft = 2, + TopRight = 3, +} + + +class Animated3DModel extends MapResource: + ## Byte Type: u8 [br] + ## Byte Length: 40 + var name: String + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var animation_type: ModelAnimationType + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var animation_speed_percent: float + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var collision_flags: int + + ## Byte Type: u8 [br] + ## Byte Length: 80 + var model_file: String + + ## Byte Type: u8 [br] + ## Byte Length: 80 + var root_node_name: String + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## Type: Degrees + var rotation_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## Type: Degrees + var rotation_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + ## Type: Degrees + var rotation_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var scale_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var scale_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var scale_z: float + + + func get_position() -> Vector3: + return Vector3(position_x, position_y, position_z) + + + func get_rotation() -> Vector3: + return Vector3(rotation_x, rotation_y, rotation_z) + + + func get_scale() -> Vector3: + return Vector3(scale_x, scale_y, scale_z) + + + static func from_bytes(bytes: ByteStream) -> Animated3DModel: + var resource = Animated3DModel.new() + + resource.name = bytes.get_string_from_ro(40) + resource.animation_type = bytes.decode_u32() + resource.animation_speed_percent = bytes.decode_float() + resource.collision_flags = bytes.decode_u32() + resource.model_file = bytes.get_string_from_ro(80) + resource.root_node_name = bytes.get_string_from_utf8(80) + resource.position_x = bytes.decode_float() + resource.position_y = bytes.decode_float() + resource.position_z = bytes.decode_float() + resource.rotation_x = bytes.decode_float() + resource.rotation_y = bytes.decode_float() + resource.rotation_z = bytes.decode_float() + resource.scale_x = bytes.decode_float() + resource.scale_y = bytes.decode_float() + resource.scale_z = bytes.decode_float() + + #print(inst_to_dict(resource)) + return resource + + +class DynamicLightSource extends MapResource: + ## Byte Type: u8 [br] + ## Byte Length: 80 + var name: String + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_r: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_g: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var diffuse_color_b: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var light_range: float + + + func get_position() -> Vector3: + return Vector3(position_x, position_y, position_z) + + + func get_diffuse_color() -> Color: + return Color(diffuse_color_r, diffuse_color_g, diffuse_color_b) + + + static func from_bytes(bytes: ByteStream) -> DynamicLightSource: + var resource = DynamicLightSource.new() + + resource.name = bytes.get_string_from_ro(80) + resource.position_x = bytes.decode_float() + resource.position_y = bytes.decode_float() + resource.position_z = bytes.decode_float() + resource.diffuse_color_r = bytes.decode_float() + resource.diffuse_color_g = bytes.decode_float() + resource.diffuse_color_b = bytes.decode_float() + resource.light_range = bytes.decode_float() + + #print(inst_to_dict(resource)) + return resource + + +class SpatialAudioSource extends MapResource: + ## Byte Type: u8 [br] + ## Byte Length: 80 + var name: String + + ## Byte Type: u8 [br] + ## Byte Length: 80 + var audio_file: String + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var volume_gain: float + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var width: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var height: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var audio_range: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var duration: float + + + func get_position() -> Vector3: + return Vector3(position_x, position_y, position_z) + + + func get_size() -> Vector2: + return Vector2(width, height) + + + static func from_bytes(bytes: ByteStream) -> SpatialAudioSource: + var resource = SpatialAudioSource.new() + + resource.name = bytes.get_string_from_ro(80) + resource.audio_file = bytes.get_string_from_ro(80) + resource.position_x = bytes.decode_float() + resource.position_y = bytes.decode_float() + resource.position_z = bytes.decode_float() + resource.volume_gain = bytes.decode_float() + resource.width = bytes.decode_u32() + resource.height = bytes.decode_u32() + resource.audio_range = bytes.decode_float() + resource.duration = bytes.decode_float() + + return resource + + +class ParticleEffectEmitter extends MapResource: + ## Byte Type: u8 [br] + ## Byte Length: 80 + var name: String + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var position_z: float + + @warning_ignore("enum_variable_without_default") + ## Byte Type: u32 [br] + ## Byte Length: 4 + var preset_effect: ParticlePresetEffect + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## in frames; 60 means 1 second + var emission_delay: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var launch_parameter_a: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var launch_parameter_b: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var launch_parameter_c: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var launch_parameter_d: float + + + func get_position() -> Vector3: + return Vector3(position_x, position_y, position_z) + + + static func from_bytes(bytes: ByteStream) -> ParticleEffectEmitter: + var resource = ParticleEffectEmitter.new() + + resource.name = bytes.get_string_from_ro(80) + resource.position_x = bytes.decode_float() + resource.position_y = bytes.decode_float() + resource.position_z = bytes.decode_float() + resource.preset_effect = bytes.decode_u32() + resource.emission_delay = bytes.decode_float() + resource.launch_parameter_a = bytes.decode_float() + resource.launch_parameter_b = bytes.decode_float() + resource.launch_parameter_c = bytes.decode_float() + resource.launch_parameter_d = bytes.decode_float() + + return resource + + +class QuadTree: + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## coordinate of the corner at the lowest altitude + var bottom_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var bottom_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var bottom_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## coordinate of the corner at the highest altitude + var top_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var top_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var top_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## width of the bounding box (when interpreted as a cuboid) + var diameter_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## height of the bounding box (when interpreted as a cuboid) + var diameter_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## depth of the bounding box (when interpreted as a cuboid) + var diameter_z: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 [br] + ## coordinate of the point in the exact center of the box + var center_x: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var center_y: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var center_z: float + + ## Length: 4 [br] + ## null if leaf node at sub-tree level 5 [br] + ## indexes are [enum QuadTreeQuadrant] + var quadrants: Array[QuadTree] + + + func get_bottom() -> Vector3: + return Vector3(bottom_x, bottom_y, bottom_z) + + + func get_top() -> Vector3: + return Vector3(top_x, top_y, top_z) + + + func get_diameter() -> Vector3: + return Vector3(diameter_x, diameter_y, diameter_z) + + + func get_center() -> Vector3: + return Vector3(center_x, center_y, center_z) + + + static func from_bytes(bytes: ByteStream) -> QuadTree: + var tree = QuadTree.new() + + tree.bottom_x = bytes.decode_float() + + return tree + + + static func from_bytes_recursive(bytes: ByteStream, tree: QuadTree, depth: int): + if depth == 5 or tree.quadrants.size() == 4: + pass diff --git a/extractor/version.gd b/extractor/version.gd index 679a8f0..ae97a10 100644 --- a/extractor/version.gd +++ b/extractor/version.gd @@ -11,5 +11,25 @@ var major: int var minor: int +func lower_than(compare_major: int, compare_minor: int) -> bool: + if (major > compare_major): + return false + + if (major == compare_major): + return minor < compare_minor + + return true + + +func higher_than(compare_major: int, compare_minor: int) -> bool: + if (major > compare_major): + return true + + if (major == compare_major): + return minor > compare_minor + + return false + + func _to_string() -> String: return "%s.%s" % [major, minor] |