Huge cleanup and improvements

This commit is contained in:
Ribbon 2024-08-15 19:25:32 -03:00
parent d895da2671
commit fdeee17337
177 changed files with 5407 additions and 352 deletions

View file

@ -1,18 +1,14 @@
extends Node
var assets: Dictionary
var mutex := Mutex.new()
func _ready() -> void:
load_cd_image("models/gta3.img")
func load_cd_image(path: String) -> void:
var file := open(path.to_lower().trim_suffix(".img") + ".dir")
assert(file != null, "%d" % FileAccess.get_open_error())
while not file.eof_reached():
var entry := DirEntry.new()
entry.img = path
@ -20,11 +16,9 @@ func load_cd_image(path: String) -> void:
entry.size = int(file.get_32()) * 2048
assets[file.get_buffer(24).get_string_from_ascii().to_lower()] = entry
func open(path: String) -> FileAccess:
var diraccess := DirAccess.open(GameManager.gta_path)
var parts := path.replace("\\", "/").split("/")
for part in parts:
if part == parts[parts.size() - 1]:
for file in diraccess.get_files():
@ -35,20 +29,16 @@ func open(path: String) -> FileAccess:
if dir.matchn(part):
diraccess.change_dir(dir)
break
return null
func open_asset(name: String) -> FileAccess:
if name.to_lower() in assets:
var asset = assets[name.to_lower()] as DirEntry
var access := open(assets[name.to_lower()].img)
access.seek(asset.offset)
return access
return open("models/" + name)
class DirEntry:
var img: String
var offset: int

16
scripts/car/car.gd Normal file
View file

@ -0,0 +1,16 @@
extends VehicleBody3D
const MAX_STEER = 0.8
const ENGINE_POWER = 300
func _process(delta):
steering = move_toward(steering, Input.get_axis("right", "left") * MAX_STEER, delta * 2.5)
engine_force = Input.get_axis("break", "run") * ENGINE_POWER
func _enter_tree():
set_multiplayer_authority(name.to_int())
func _physics_process(delta):
if is_multiplayer_authority():
steering = move_toward(steering, Input.get_axis("right", "left") * MAX_STEER, delta * 2.5)
engine_force = Input.get_axis("break", "run") * ENGINE_POWER

12
scripts/car/lights.gd Normal file
View file

@ -0,0 +1,12 @@
extends Node3D
@onready var external_lights = [ $left_light, $right_light ]
@onready var internal_light = $internal_light
func _physics_process(_delta):
if Input.is_action_just_pressed("internal_light"):
internal_light.visible = not internal_light.visible
if Input.is_action_just_pressed("external_lights"):
for light in external_lights:
light.visible = not light.visible

View file

@ -0,0 +1,6 @@
extends AudioStreamPlayer3D
func _physics_process(_delta):
if Input.is_action_pressed("switch_microphone"):
self.playing = not self.playing
self.stream_paused = not self.stream_paused

26
scripts/car/music.gd Normal file
View file

@ -0,0 +1,26 @@
extends Node3D
@onready var speakers = [ $back_left_speaker, $back_right_speaker, $front_left_speaker, $front_right_speaker ]
func _physics_process(_delta):
if Input.is_action_just_pressed("play_pause_music"):
for speaker in speakers:
speaker.playing = not speaker.playing
speaker.stream_paused = not speaker.stream_paused
if Input.is_action_just_pressed("decrease_music_volume"):
for speaker in speakers:
speaker.unit_size = speaker.unit_size - 0.2
if Input.is_action_just_pressed("increase_music_volume"):
for speaker in speakers:
speaker.unit_size = speaker.unit_size + 0.2
#func load_audio_files():
#var dir = DirAccess.open("res://music")
#if dir:
#dir.list_dir_begin()
#var file_name = dir.get_next()
#while file_name != "":
#if ".mp3" in file_name:
#for speaker in speakers:
#speaker.stream = load(file_name)
#file_name = dir.get_next()

View file

@ -1,45 +1,35 @@
class_name ColFile
extends RefCounted
var fourcc: String
var file_size: int
var model_name: String
var model_id: int
var tbounds: TBounds
var collisions: Array[TBase]
var vertices: PackedVector3Array
func _init(file: FileAccess):
fourcc = file.get_buffer(4).get_string_from_ascii()
assert(fourcc == "COLL")
file_size = file.get_32()
model_name = file.get_buffer(22).get_string_from_ascii()
model_id = file.get_16()
tbounds = TBounds.new(file)
for i in file.get_32():
collisions.append(TSphere.new(file))
file.get_32()
for i in file.get_32():
collisions.append(TBox.new(file))
var unsorted := PackedVector3Array()
for i in file.get_32():
unsorted.append(TVertex.new(file).position)
for i in file.get_32():
var face := TFace.new(file)
vertices.append(unsorted[face.a])
vertices.append(unsorted[face.b])
vertices.append(unsorted[face.c])
class TBase:
func read_vector3(file: FileAccess) -> Vector3:
var result := Vector3()
@ -48,14 +38,12 @@ class TBase:
result.z = file.get_float()
return result
class TBounds extends TBase:
var radius: float
var center: Vector3
var min: Vector3
var max: Vector3
func _init(file: FileAccess):
radius = file.get_float()
@ -63,61 +51,50 @@ class TBounds extends TBase:
min = read_vector3(file)
max = read_vector3(file)
class TSurface extends TBase:
var material: int
var flag: int
var brightness: int
var light: int
func _init(file: FileAccess):
material = file.get_8()
flag = file.get_8()
brightness = file.get_8()
light = file.get_8()
class TSphere extends TBase:
var radius: float
var center: Vector3
var surface: TSurface
func _init(file: FileAccess):
radius = file.get_float()
center = read_vector3(file)
surface = TSurface.new(file)
class TBox extends TBase:
var min: Vector3
var max: Vector3
var surface: TSurface
func _init(file: FileAccess):
min = read_vector3(file)
max = read_vector3(file)
surface = TSurface.new(file)
class TVertex extends TBase:
var position: Vector3
func _init(file: FileAccess):
position = read_vector3(file)
class TFace extends TBase:
var a: int
var b: int
var c: int
var surface: TSurface
func _init(file: FileAccess):
a = file.get_32()
b = file.get_32()

View file

@ -1,11 +1,9 @@
class_name ItemDef
extends RefCounted
var model_name: String
var txd_name: String
var render_distance: float
var flags: int
var childs: Array[TDFX]
var colfile: ColFile

View file

@ -1,7 +1,6 @@
class_name ItemPlacement
extends RefCounted
var id: int
var model_name: String
var position: Vector3

View file

@ -1,22 +1,17 @@
class_name StreamedMesh
extends MeshInstance3D
var _idef: ItemDef
var _thread := Thread.new()
var _mesh_buf: Mesh
func _init(idef: ItemDef):
_idef = idef
func _exit_tree():
if _thread.is_alive():
_thread.wait_to_finish()
func _process(delta: float) -> void:
if _thread.is_started() == false:
var dist := get_viewport().get_camera_3d().global_position.distance_to(global_position)
@ -29,15 +24,12 @@ func _process(delta: float) -> void:
elif dist > visibility_range_end and mesh != null:
mesh = null
func _load_mesh() -> void:
AssetLoader.mutex.lock()
if _idef.flags & 0x40:
return
var access := AssetLoader.open_asset(_idef.model_name + ".dff")
var glist := RWClump.new(access).geometry_list
for geometry in glist.geometries:
_mesh_buf = geometry.mesh
for surf_id in _mesh_buf.get_surface_count():
@ -46,22 +38,16 @@ func _load_mesh() -> void:
if _idef.flags & 0x08:
material.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
if material.has_meta("texture_name"):
var txd := RWTextureDict.new(AssetLoader.open_asset(_idef.txd_name + ".txd"))
var texture_name = material.get_meta("texture_name")
for raster in txd.textures:
if texture_name.matchn(raster.name):
material.albedo_texture = ImageTexture.create_from_image(raster.image)
if raster.has_alpha:
material.transparency = (
BaseMaterial3D.TRANSPARENCY_ALPHA_HASH if _idef.flags & 0x04 and not _idef.flags & 0x08
else BaseMaterial3D.TRANSPARENCY_ALPHA
)
else BaseMaterial3D.TRANSPARENCY_ALPHA )
break
_mesh_buf.surface_set_material(surf_id, material)
AssetLoader.mutex.unlock()

View file

@ -1,7 +1,6 @@
class_name TDFX
extends RefCounted
var parent: int
var position: Vector3
var color: Color

View file

@ -1,7 +1,6 @@
class_name TDFXLight
extends TDFX
var render_distance: float
var range: float
var shadow_intensity: int

View file

@ -1,16 +1,10 @@
extends Node
var gta_path: String
func _ready() -> void:
if OS.has_feature("editor"):
gta_path = ProjectSettings.globalize_path("res://gta/")
else:
gta_path = OS.get_executable_path().get_base_dir() + "/"
print("GTA path: %s" % gta_path)
var err := get_tree().change_scene_to_file("res://scenes/main_menu/main_menu.tscn")
assert(err == OK, "failed to load main menu")

View file

@ -1,20 +1,15 @@
extends Node
var items: Dictionary
var itemchilds: Array[TDFX]
var placements: Array[ItemPlacement]
var collisions: Array[ColFile]
var map: Node3D
var _loaded := false
func _ready() -> void:
var file := FileAccess.open(GameManager.gta_path + "data/gta3.dat", FileAccess.READ)
assert(file != null, "%d" % FileAccess.get_open_error())
while not file.eof_reached():
var line := file.get_line()
if not line.begins_with("#"):
@ -34,10 +29,8 @@ func _ready() -> void:
AssetLoader.load_cd_image(tokens[1])
_:
push_warning("implement %s" % tokens[0])
for child in itemchilds:
items[child.parent].childs.append(child)
for colfile in collisions:
if colfile.model_id in items:
items[colfile.model_id].colfile = colfile
@ -47,7 +40,6 @@ func _ready() -> void:
if item.model_name.matchn(colfile.model_name):
items[k].colfile = colfile
func _read_ide_line(section: String, tokens: Array[String]):
var item := ItemDef.new()
var id := tokens[0].to_int()
@ -57,27 +49,22 @@ func _read_ide_line(section: String, tokens: Array[String]):
item.txd_name = tokens[2]
item.render_distance = tokens[4].to_float()
item.flags = tokens[tokens.size() - 1].to_int()
items[id] = item
"tobj":
# TODO: Timed objects
item.model_name = tokens[1]
item.txd_name = tokens[2]
items[id] = item
"2dfx":
var parent := tokens[0].to_int()
var position := Vector3(
tokens[1].to_float(),
tokens[2].to_float(),
tokens[3].to_float()
)
tokens[3].to_float() )
var color := Color(
tokens[4].to_float() / 255,
tokens[5].to_float() / 255,
tokens[6].to_float() / 255
)
tokens[6].to_float() / 255 )
match tokens[8].to_int():
0:
var lightdef := TDFXLight.new()
@ -87,122 +74,91 @@ func _read_ide_line(section: String, tokens: Array[String]):
lightdef.render_distance = tokens[11].to_float()
lightdef.range = tokens[12].to_float()
lightdef.shadow_intensity = tokens[15].to_int()
itemchilds.append(lightdef)
var type:
push_warning("implement 2DFX type %d" % type)
func _read_ipl_line(section: String, tokens: Array[String]):
match section:
"inst":
var placement := ItemPlacement.new()
placement.id = tokens[0].to_int()
placement.model_name = tokens[1].to_lower()
placement.position = Vector3(
tokens[2].to_float(),
tokens[3].to_float(),
tokens[4].to_float(),
)
tokens[4].to_float(), )
placement.scale = Vector3(
tokens[5].to_float(),
tokens[6].to_float(),
tokens[7].to_float(),
)
tokens[7].to_float(), )
placement.rotation = Quaternion(
-tokens[8].to_float(),
-tokens[9].to_float(),
-tokens[10].to_float(),
tokens[11].to_float(),
)
tokens[11].to_float(), )
placements.append(placement)
func _read_map_data(path: String, line_handler: Callable) -> void:
var file := AssetLoader.open(path)
assert(file != null, "%d" % FileAccess.get_open_error())
assert(file != null, "%d" % FileAccess.get_open_error() )
var section: String
while not file.eof_reached():
var line := file.get_line()
if line.length() == 0 or line.begins_with("#"):
continue
var tokens := line.replace(" ", "").split(",", false)
if tokens.size() == 1:
section = tokens[0]
else:
line_handler.call(section, tokens)
func clear_map() -> void:
map = Node3D.new()
map.rotation.x = deg_to_rad(-90.0)
func spawn_placement(ipl: ItemPlacement) -> Node3D:
return spawn(ipl.id, ipl.model_name, ipl.position, ipl.scale, ipl.rotation)
func spawn(id: int, model_name: String, position: Vector3, scale: Vector3, rotation: Quaternion) -> Node3D:
var item := items[id] as ItemDef
if item.flags & 0x40:
return Node3D.new()
var instance := StreamedMesh.new(item)
instance.position = position
instance.scale = scale
instance.quaternion = rotation
instance.visibility_range_end = item.render_distance
for child in item.childs:
if child is TDFXLight:
var light := OmniLight3D.new()
light.position = child.position
light.light_color = child.color
light.distance_fade_enabled = true
# TODO: Remove half distance when https://github.com/godotengine/godot/issues/56657 is solved
light.distance_fade_begin = child.render_distance / 2.0
light.omni_range = child.range
light.light_energy = float(child.shadow_intensity) / 20.0
# light.shadow_enabled = true
instance.add_child(light)
var sb := StaticBody3D.new()
if item.colfile != null:
for collision in item.colfile.collisions:
var colshape := CollisionShape3D.new()
if collision is ColFile.TBox:
var aabb := AABB()
aabb.position = collision.min
aabb.end = collision.max
var shape := BoxShape3D.new()
shape.size = aabb.size
colshape.shape = shape
colshape.position = aabb.get_center()
sb.add_child(colshape)
if item.colfile.vertices.size() > 0:
var colshape := CollisionShape3D.new()
var shape := ConcavePolygonShape3D.new()
shape.set_faces(item.colfile.vertices)
colshape.shape = shape
sb.add_child(colshape)
instance.add_child(sb)
return instance

28
scripts/map_test.gd Normal file
View file

@ -0,0 +1,28 @@
extends Node
@onready var world := Node3D.new()
var suzanne := preload("res://prefabs/suzanne.tscn")
func _ready() -> void:
world.rotation.x = deg_to_rad(-90.0)
var start := Time.get_ticks_msec()
var target = MapBuilder.placements.size()
var count := 0
var start_t := Time.get_ticks_msec()
# add_child(MapBuilder.map)
for ipl in MapBuilder.placements:
world.add_child(MapBuilder.spawn_placement(ipl))
count += 1
if Time.get_ticks_msec() - start > (1.0 / 30.0) * 1000:
start = Time.get_ticks_msec()
print("%f" % (float(count) / float(target)))
await get_tree().physics_frame
print("Map load completed in %f seconds" % ((Time.get_ticks_msec() - start_t) / 1000))
add_child(world)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey:
if event.physical_keycode == KEY_SPACE and event.pressed:
var node := suzanne.instantiate() as RigidBody3D
add_child(node)
node.global_position = get_viewport().get_camera_3d().global_position

19
scripts/menu.gd Normal file
View file

@ -0,0 +1,19 @@
extends Control
@onready var container := $VBoxContainer as VBoxContainer
const scenes := {
"Texture viewer": "res://scenes/txd.tscn",
"Flycam test": "res://scenes/flycam/flycam.tscn",
"DFF Test": "res://scenes/model_test.tscn",
"Map loader test": "res://scenes/map_test.tscn", }
func _ready() -> void:
for k in scenes:
var button := Button.new()
button.text = k
button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
button.size_flags_vertical = Control.SIZE_SHRINK_CENTER
button.pressed.connect(func _load_scene():
get_tree().change_scene_to_file(scenes[k]) )
container.add_child(button)

40
scripts/model_test.gd Normal file
View file

@ -0,0 +1,40 @@
extends Node
@onready var spinbox: SpinBox = $GUI/VBoxContainer/HBoxContainer/SpinBox
@onready var meshinstance: MeshInstance3D = $mesh
var dff: RWClump
var misc: RWTextureDict
func _ready() -> void:
spinbox.rounded = true
spinbox.max_value = 0
misc = RWTextureDict.new(GameManager.open_file("models/misc.txd", FileAccess.READ))
meshinstance.rotation.x = deg_to_rad(-90.0)
func _ld_dff() -> void:
var dialog := FileDialog.new()
dialog.access = FileDialog.ACCESS_FILESYSTEM
dialog.current_dir = GameManager.gta_path
dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
dialog.add_filter("*.dff")
add_child(dialog)
dialog.popup_centered(Vector2i(600, 400))
var file_path := (await dialog.file_selected) as String
remove_child(dialog)
var file := FileAccess.open(file_path, FileAccess.READ)
dff = RWClump.new(file)
spinbox.value = 0
spinbox.max_value = dff.geometry_list.geometry_count - 1
_ld_model(0)
func _ld_model(value: float) -> void:
var geometry := dff.geometry_list.geometries[int(value)]
meshinstance.mesh = geometry.mesh
var material := geometry.material_list.materials[0] as RWMaterial
meshinstance.material_override = material.material
if material.is_textured:
var texname := material.texture.texture_name.string
for raster in misc.textures:
if texname.to_lower() == raster.name:
meshinstance.material_override.albedo_texture = ImageTexture.create_from_image(raster.image)
break

25
scripts/multiplayer.gd Normal file
View file

@ -0,0 +1,25 @@
extends Node3D
@onready var host = $host
@onready var join = $join
var peer = ENetMultiplayerPeer.new()
@export var player_scene: PackedScene
func _on_host_pressed():
host.visible = false
join.visible = false
peer.create_server(6006)
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_add_player)
_add_player()
func _add_player(id = 1):
var player = player_scene.instantiate()
player.name = str(id)
call_deferred("add_child",player)
func _on_join_pressed():
host.visible = false
join.visible = false
peer.create_client("localhost", 6006)
multiplayer.multiplayer_peer = peer

14
scripts/player/player.gd Normal file
View file

@ -0,0 +1,14 @@
extends CharacterBody3D
@onready var pivot = $cam_origin
var mouse_sens = 0.4
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event):
if event is InputEventMouseMotion:
pivot.rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
pivot.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
pivot.rotation.z = 0

View file

@ -4,7 +4,6 @@ extends RefCounted
##
## [url]https://gtamods.com/wiki/RenderWare_binary_stream_file[/url]
enum ChunkType {
STRING = 0x2,
TEXTURE = 0x6,
@ -33,16 +32,13 @@ var build: int:
if library_id & 0xffff0000:
return library_id & 0xffff
return 0
var _start: int
func _init(file: FileAccess):
type = file.get_32()
size = file.get_32()
library_id = file.get_32()
_start = file.get_position()
func skip(file: FileAccess) -> void:
file.seek(_start + size)

View file

@ -1,24 +1,19 @@
class_name RWClump
extends RWChunk
var atomic_count: int
var light_count: int
var camera_count: int
var frame_list: RWFrameList
var geometry_list: RWGeometryList
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.CLUMP)
RWChunk.new(file)
var atomic_count = file.get_32()
if version > 0x33000:
light_count = file.get_32()
camera_count = file.get_32()
frame_list = RWFrameList.new(file)
geometry_list = RWGeometryList.new(file)

View file

@ -1,44 +1,33 @@
class_name RWFrameList
extends RWChunk
var frame_count: int
var frames: Array[Frame]
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.FRAME_LIST)
frame_count = file.get_32()
frames.resize(frame_count)
for frame_i in frame_count:
var frame := Frame.new()
frame.rotation_matrix.resize(3)
for vec_i in 3:
var x := file.get_float()
var y := file.get_float()
var z := file.get_float()
frame.rotation_matrix[vec_i] = Vector3(x, y, z)
var x := file.get_float()
var y := file.get_float()
var z := file.get_float()
frame.position.x = x
frame.position.y = y
frame.position.z = z
frame.index = file.get_32()
frame.flags = file.get_32()
frames[frame.index] = frame
skip(file)
class Frame:
var rotation_matrix: Array[Vector3]
var position: Vector3

View file

@ -1,7 +1,6 @@
class_name RWGeometry
extends RWChunk
enum {
rpGEOMETRYTRISTRIP = 0x00000001,
rpGEOMETRYPOSITIONS = 0x00000002,
@ -23,7 +22,6 @@ var morph_target_count: int
var ambient: float
var specular: float
var diffuse: float
var uv_count: int
var uvs: Array[PackedVector2Array]
var tris: Array[Triangle]
@ -37,14 +35,12 @@ var mesh: ArrayMesh:
var morph_t := morph_targets[0]
var st := SurfaceTool.new()
var surfaces: Dictionary
# Split tris by their material ID
for tri in tris:
var mat_id := tri.material_id
if not mat_id in surfaces:
surfaces[mat_id] = []
surfaces[mat_id].append(tri)
for surf_id in surfaces:
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var surface = surfaces[surf_id] as Array[Triangle]
@ -54,31 +50,23 @@ var mesh: ArrayMesh:
st.set_normal(morph_t.normals[tri["vertex_%d" % i]])
if uvs.size() > 0:
st.set_uv(uvs[0][tri["vertex_%d" % i]])
st.add_vertex(morph_t.vertices[tri["vertex_%d" % i]])
var rwmaterial := material_list.materials[tri.material_id]
var material := rwmaterial.material
if rwmaterial.is_textured:
material.set_meta("texture_name", rwmaterial.texture.texture_name)
st.set_material(material)
if format & rpGEOMETRYTRISTRIP == 0 and morph_t.has_normals == false:
st.generate_normals()
if mesh == null:
mesh = st.commit()
else:
st.commit(mesh)
return mesh
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.GEOMETRY)
RWChunk.new(file)
format = file.get_32()
tri_count = file.get_32()
@ -88,18 +76,15 @@ func _init(file: FileAccess):
ambient = file.get_float()
specular = file.get_float()
diffuse = file.get_float()
if format & rpGEOMETRYNATIVE == 0:
if format & rpGEOMETRYPRELIT:
file.seek(file.get_position() + (vert_count * 4)) # Skip
uv_count = (format & 0x00ff0000) >> 16
if uv_count == 0:
if format & rpGEOMETRYTEXTURED2:
uv_count = 2
elif format & rpGEOMETRYTEXTURED:
uv_count = 1
for i in uv_count:
var coords := PackedVector2Array()
for j in vert_count:
@ -107,7 +92,6 @@ func _init(file: FileAccess):
var v := file.get_float()
coords.append(Vector2(u, v))
uvs.append(coords)
for i in tri_count:
var tri := Triangle.new()
tri.vertex_2 = file.get_16()
@ -115,7 +99,6 @@ func _init(file: FileAccess):
tri.material_id = file.get_16()
tri.vertex_3 = file.get_16()
tris.append(tri)
for i in morph_target_count:
var morph_t := MorphTarget.new()
morph_t.bounding_sphere = Sphere.new()
@ -125,7 +108,6 @@ func _init(file: FileAccess):
morph_t.bounding_sphere.radius = file.get_float()
morph_t.has_vertices = file.get_32() != 0
morph_t.has_normals = file.get_32() != 0
if morph_t.has_vertices:
for j in vert_count:
var vert := Vector3()
@ -133,7 +115,6 @@ func _init(file: FileAccess):
vert.y = file.get_float()
vert.z = file.get_float()
morph_t.vertices.append(vert)
if morph_t.has_normals:
for j in vert_count:
var normal := Vector3()
@ -141,21 +122,16 @@ func _init(file: FileAccess):
normal.y = file.get_float()
normal.z = file.get_float()
morph_t.normals.append(normal)
morph_targets.append(morph_t)
material_list = RWMaterialList.new(file)
skip(file)
class Triangle:
var vertex_2: int
var vertex_1: int
var material_id: int
var vertex_3: int
class MorphTarget:
var bounding_sphere: Sphere
var has_vertices: bool
@ -163,7 +139,6 @@ class MorphTarget:
var vertices: Array[Vector3]
var normals: Array[Vector3]
class Sphere:
var x: float
var y: float

View file

@ -1,17 +1,13 @@
class_name RWGeometryList
extends RWChunk
var geometry_count: int
var geometries: Array[RWGeometry]
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.GEOMETRY_LIST)
RWChunk.new(file)
geometry_count = file.get_32()
for i in geometry_count:
geometries.append(RWGeometry.new(file))

View file

@ -1,7 +1,6 @@
class_name RWMaterial
extends RWChunk
var color: Color
var is_textured: bool
var texture: RWTexture
@ -9,21 +8,17 @@ var material: StandardMaterial3D:
get:
var mat := StandardMaterial3D.new()
mat.albedo_color = color
if version > 0x30400:
mat.roughness = 1.0 - specular
return mat
var ambient: float
var specular: float
var diffuse: float
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.MATERIAL)
RWChunk.new(file)
file.get_32()
color.r8 = file.get_8()
@ -31,14 +26,11 @@ func _init(file: FileAccess):
color.b8 = file.get_8()
color.a8 = file.get_8()
file.get_32()
is_textured = file.get_32() > 0
if version > 0x30400:
ambient = file.get_float()
specular = file.get_float()
diffuse = file.get_float()
if is_textured:
texture = RWTexture.new(file)
skip(file)

View file

@ -1,27 +1,21 @@
class_name RWMaterialList
extends RWChunk
var material_count: int
var indices: Array[int]
var materials: Array[RWMaterial]
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.MATERIAL_LIST)
RWChunk.new(file)
material_count = file.get_32()
for i in material_count:
# For fuck's sake, someone PR a code to do this into Godot.
indices.append((file.get_32() + (1 << 31)) % (1 << 32) - (1 << 31))
indices.append((file.get_32() + (1 << 31)) % (1 << 32) - (1 << 31) )
for i in indices:
if i == -1:
materials.append(RWMaterial.new(file))
else:
assert(false, "implement")
skip(file)

View file

@ -1,7 +1,6 @@
class_name RWRaster
extends RWChunk
enum {
FORMAT_DEFAULT = 0x0000,
FORMAT_1555 = 0x0100,
@ -11,7 +10,6 @@ enum {
FORMAT_8888 = 0x0500,
FORMAT_888 = 0x0600,
FORMAT_555 = 0x0A00,
FORMAT_EXT_AUTO_MIPMAP = 0x1000,
FORMAT_EXT_PAL8 = 0x2000,
FORMAT_EXT_PAL4 = 0x4000,
@ -24,7 +22,6 @@ var u_addressing: int
var v_addressing: int
var name: String
var mask_name: String
var raster_format: int
var has_alpha: bool
var width: int
@ -33,24 +30,19 @@ var depth: int
var num_levels: int
var raster_type: int
var compression: int
var _file: FileAccess
var _image_start: int
var image: Image: get = _load_image
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.RASTER)
RWChunk.new(file)
platform_id = file.get_32()
filter_mode = file.get_8()
var uv_addressing = file.get_8()
u_addressing = uv_addressing >> 4
v_addressing = uv_addressing & 0xf
file.get_16()
name = file.get_buffer(32).get_string_from_ascii()
mask_name = file.get_buffer(32).get_string_from_ascii()
@ -62,7 +54,6 @@ func _init(file: FileAccess):
num_levels = file.get_8()
raster_type = file.get_8()
compression = file.get_8()
_file = file
_image_start = file.get_position()
skip(file)
@ -72,7 +63,6 @@ func _load_image():
var result: Image
var format: Image.Format
var read: int
match raster_format & 0x0f00:
# FORMAT_1555:
# format = FORMAT_1555
@ -91,11 +81,9 @@ func _load_image():
read = 3
_:
assert(false)
if raster_format & (FORMAT_EXT_PAL8 | FORMAT_EXT_PAL4):
var psize := (16 if raster_format & FORMAT_EXT_PAL4 else 256)
var palette := Image.create_from_data(psize, 1, false, format, _unpad(psize, read))
result = Image.create(width, height, raster_format & 0x8000, format)
_file.get_32()
for i in width * height:
@ -108,17 +96,14 @@ func _load_image():
# _file.get_32()
# var unpadded := _unpad(width * height, read)
# var data := PackedInt32Array()
#
# for i in unpadded.size() / 2:
# var x := int(i % width)
# var y := int(i / width)
#
# var pixel := unpadded[i] | unpadded[i + 1] << 16
# var a := (pixel & 0x8000) >> 15
# var r := (pixel & 0x7c00) >> 10
# var g := (pixel & 0x03e0) >> 5
# var b := pixel & 0x001f
#
# result.set_pixel(
# x, y, Color(
# r / 0x1f,
@ -129,7 +114,6 @@ func _load_image():
# )
else:
var data := PackedByteArray()
var mip_width := width
var mip_height := height
for i in num_levels:
@ -137,28 +121,22 @@ func _load_image():
data.append_array(_unpad(mip_width * mip_height, read))
mip_width /= 2
mip_height /= 2
result = Image.create_from_data(width, height, raster_format & FORMAT_EXT_MIPMAP, format, data)
if raster_format & FORMAT_EXT_AUTO_MIPMAP:
image.generate_mipmaps()
# Perform color conversion
for i in width * height:
var x := int(i % width)
var y := int(i / width)
var old := result.get_pixel(x, y)
result.set_pixel(x, y, Color(old.b, old.g, old.r, old.a))
return result
func _unpad(length: int, read: int) -> PackedByteArray:
var result := PackedByteArray()
for i in length:
for j in read:
result.append(_file.get_8())
for j in 4 - read:
_file.get_8()
return result

View file

@ -1,14 +1,11 @@
class_name RWString
extends RWChunk
var string: String
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.STRING)
var chars: PackedByteArray
while true:
var char := file.get_8()
@ -16,5 +13,4 @@ func _init(file: FileAccess):
break
chars.append(char)
string = chars.get_string_from_ascii()
skip(file)

View file

@ -1,15 +1,12 @@
class_name RWTexture
extends RWChunk
var texture_name: String
var mask_name: String
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.TEXTURE)
RWChunk.new(file).skip(file)
texture_name = RWString.new(file).string
mask_name = RWString.new(file).string

View file

@ -2,22 +2,17 @@ class_name RWTextureDict
extends RWChunk
## RenderWare texture dictionary
var texture_count: int
var device_id: int
var textures: Array[RWRaster]
func _init(file: FileAccess):
super(file)
assert(type == ChunkType.TEXTURE_DICT)
RWChunk.new(file)
texture_count = file.get_16()
device_id = file.get_16()
for i in texture_count:
var raster := RWRaster.new(file)
textures.append(raster)
file.seek(_start + size)

25
scripts/txd.gd Normal file
View file

@ -0,0 +1,25 @@
extends Control
var txd: RWTextureDict
func _load_image(index: int):
$VBoxContainer/TextureRect.texture = null
$VBoxContainer/TextureRect.texture = ImageTexture.create_from_image(txd.textures[index].image)
func _select_file():
var dialog := FileDialog.new()
dialog.access = FileDialog.ACCESS_FILESYSTEM
dialog.current_dir = GameManager.gta_path
dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
dialog.add_filter("*.txd", "Texture Dictionary")
add_child(dialog)
dialog.popup_centered(Vector2i(600, 400))
var file_path := (await dialog.file_selected) as String
remove_child(dialog)
var file := FileAccess.open(file_path, FileAccess.READ)
assert(file_path != null)
txd = RWTextureDict.new(file)
$VBoxContainer/HBoxContainer/OptionButton.clear()
for raster in txd.textures:
$VBoxContainer/HBoxContainer/OptionButton.add_item(raster.name)
_load_image(0)

27
scripts/world.gd Normal file
View file

@ -0,0 +1,27 @@
extends Node
@onready var world := Node3D.new()
var car := preload("res://scenes/car.tscn")
func _ready() -> void:
world.rotation.x = deg_to_rad(-90.0)
var start := Time.get_ticks_msec()
var target = MapBuilder.placements.size()
var count := 0
var start_t := Time.get_ticks_msec()
# add_child(MapBuilder.map)
for ipl in MapBuilder.placements:
world.add_child(MapBuilder.spawn_placement(ipl))
count += 1
if Time.get_ticks_msec() - start > (1.0 / 30.0) * 1000:
start = Time.get_ticks_msec()
print("%f" % (float(count) / float(target)))
await get_tree().physics_frame
print("Map load completed in %f seconds" % ((Time.get_ticks_msec() - start_t) / 1000))
add_child(world)
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_pressed("spawn"):
var car_node := car.instantiate()
add_child(car_node)
car_node.global_position = get_viewport().get_camera_3d().global_position