class_name World extends Node const WORLDS_DIR := "user://worlds/" const FILE_EXTENSION := ".yf5" const MAGIC_NUMBER := 0x59463573 # "YF5s" const VERSION_NUMBER := 1 static var chunks_buffer := StreamBuffer.with_capacity(1024 * 1024) var last_saved := -1 # unix timestamp var playtime := 0.0 # in seconds var world_seed := randi() var chunks := Node.new() var generator := GeneratorSimple.new() func _init() -> void: chunks.name = "Chunks" add_child(chunks) func _process(delta: float) -> void: playtime += delta # Does not increase when paused. func get_block(pos: Vector2i) -> Block: return Block.new(self, pos) func get_chunk_or_null(pos: Vector2i) -> Chunk: var chunk_name := "Chunk %s" % pos var chunk := chunks.get_node_or_null(chunk_name) return chunk func get_or_create_chunk(pos: Vector2i) -> Chunk: var chunk_name := "Chunk %s" % pos var chunk := chunks.get_node_or_null(chunk_name) if chunk: return chunk chunk = Chunk.new(pos) chunk.name = chunk_name chunks.add_child(chunk) generator.generate(self, chunk) return chunk static func load(path: String) -> World: var world := World.new() var data := FileAccess.get_file_as_bytes(path) if data.is_empty(): push_error("Could not read file '%s': %s" % [ path, error_string(FileAccess.get_open_error()) ]); return null var file_buffer := StreamBuffer.from_bytes(data) var magic := file_buffer.read_uint32() var version := file_buffer.read_uint32() if magic != MAGIC_NUMBER : push_error("Magic number mismatch"); return null if version != VERSION_NUMBER: push_error("Version number mismatch"); return null chunks_buffer.write_raw_buffer(file_buffer.read_compressed(FileAccess.COMPRESSION_ZSTD)) var chunk_count := chunks_buffer.read_uint16() for i in chunk_count: var chunk := Chunk.load(chunks_buffer) world.chunks.add_child(chunk) chunks_buffer.clear() world.last_saved = FileAccess.get_modified_time(path) return world # TODO: Return whether successful. func save(path: String) -> void: var file := FileAccess.open(path + ".tmp", FileAccess.WRITE) if not file: push_error("Could not write to file '%s.tmp': %s" % [ path, error_string(FileAccess.get_open_error()) ]); return chunks_buffer.write_uint16(chunks.get_child_count()) for chunk: Chunk in chunks.get_children(): chunk.save(chunks_buffer) var file_buffer := StreamBuffer.with_capacity(1024 * 1024) file_buffer.write_uint32(MAGIC_NUMBER) file_buffer.write_uint32(VERSION_NUMBER) file_buffer.write_compressed(chunks_buffer.slice(), FileAccess.COMPRESSION_ZSTD) chunks_buffer.clear() if !file.store_buffer(file_buffer.slice()): push_error("Could not write to file '%s.tmp'" % path); return if DirAccess.rename_absolute(path + ".tmp", path): push_error("Could not rename .tmp to '%s'" % path); return last_saved = floori(Time.get_unix_time_from_system()) func spawn_chunk(chunk: Chunk, peer_id: int) -> void: chunk.save(chunks_buffer) _spawn_chunk.rpc_id(peer_id, chunks_buffer.slice()) chunks_buffer.clear() func despawn_chunk(chunk: Chunk, peer_id: int) -> void: _despawn_chunk.rpc_id(peer_id, chunk.chunk_pos) @rpc("reliable") func _spawn_chunk(bytes: PackedByteArray) -> void: var chunk := Chunk.load(StreamBuffer.from_bytes(bytes)) chunks.add_child(chunk) @rpc("reliable") func _despawn_chunk(chunk_pos: Vector2i) -> void: var chunk := get_chunk_or_null(chunk_pos) if not chunk: return chunks.remove_child(chunk) chunk.queue_free()