Initial commit for testing/fixing a deprecated godot-colyseus addon

This commit is contained in:
2026-01-10 11:12:02 -05:00
commit 081a5fcf1a
76 changed files with 3456 additions and 0 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

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

3
.gitignore vendored Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

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

View File

@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/godot_colyseus/demo/blur.png" type="Texture2D" id=1]
[node name="char" type="Sprite2D"]
position = Vector2( 0, -16 )
scale = Vector2( 0.6, 1 )
texture = ExtResource( 1 )

View File

@@ -0,0 +1,32 @@
extends Control
const colyseus = preload("res://addons/godot_colyseus/lib/colyseus.gd")
var room: colyseus.Room
func _ready():
var client = colyseus.Client.new("ws://localhost:2567")
var promise = client.join_or_create(colyseus.Schema, "chat")
await promise.completed
if promise.get_state() == promise.State.Failed:
print("Failed")
return
var room: colyseus.Room = promise.get_data()
room.on_message("messages").on(Callable(self, "_on_messages"))
$label.text += "Connected"
self.room = room
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func _on_messages(data):
$label.text += "\n" + data
func _on_send_pressed():
if $input.text.is_empty():
return
room.send("message", $input.text)
$input.text = ""

View File

@@ -0,0 +1 @@
uid://cj7jhyoe2eluq

View File

@@ -0,0 +1,48 @@
[gd_scene load_steps=2 format=3 uid="uid://dpxco2ygtk2bx"]
[ext_resource type="Script" path="res://addons/godot_colyseus/demo/chat.gd" id="1"]
[node name="Control" 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")
[node name="input" type="TextEdit" parent="."]
layout_mode = 1
anchors_preset = -1
anchor_top = 0.927
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -0.200012
offset_right = -110.0
[node name="send" type="Button" parent="."]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -104.0
offset_top = -42.0
offset_right = 1.0
offset_bottom = -4.0
grow_horizontal = 0
grow_vertical = 0
text = "Send"
[node name="label" type="Label" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -2.0
offset_bottom = -54.0
grow_horizontal = 2
grow_vertical = 2
[connection signal="pressed" from="send" to="." method="_on_send_pressed"]

View File

@@ -0,0 +1,27 @@
[gd_scene load_steps=2 format=3 uid="uid://cxtq2mh35wwc5"]
[sub_resource type="GDScript" id="GDScript_uosag"]
script/source = "extends Control
# Called when the node enters the scene tree for the first time.
func _ready():
print(\"test \", await test_await())
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func test_await():
return 2
"
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_uosag")

View File

@@ -0,0 +1,68 @@
extends Node2D
const colyseus = preload("res://addons/godot_colyseus/lib/colyseus.gd")
const Char = preload("./char.tscn")
class Player extends colyseus.Schema:
static func define_fields():
return [
colyseus.Field.new("x", colyseus.NUMBER),
colyseus.Field.new("y", colyseus.NUMBER)
]
var node
func _to_string():
return str("(",self.x,",",self.y,")")
class RoomState extends colyseus.Schema:
static func define_fields():
return [
colyseus.Field.new("players", colyseus.MAP, Player),
]
var room: colyseus.Room
# Called when the node enters the scene tree for the first time.
func _ready():
var client = colyseus.Client.new("ws://localhost:2567")
var promise = client.join_or_create(RoomState, "state_handler")
await promise.completed
if promise.get_state() == promise.State.Failed:
print("Failed")
return
var room: colyseus.Room = promise.get_data()
var state: RoomState = room.get_state()
state.listen('players:add').on(Callable(self, "_on_players_add"))
room.on_state_change.on(Callable(self, "_on_state"))
room.on_message("hello").on(Callable(self, "_on_message"))
self.room = room
func _on_message(data):
print(str("hello:", data))
func _on_state(state):
pass
func _on_players_add(target, value, key):
print("Add:", " key:", key, " ", value)
var ch = Char.instantiate()
ch.position = Vector2(value.x, value.y)
add_child(ch)
value.node = ch
value.listen(":change").on(Callable(self, "_on_player"))
func _on_player(target):
print("Change ", target)
var ch = target.node
ch.position = Vector2(target.x, target.y)
func _physics_process(delta):
if Input.is_action_pressed("ui_up"):
room.send("move", { y = -1 });
elif Input.is_action_pressed("ui_down"):
room.send("move", { y = 1 });
elif Input.is_action_pressed("ui_left"):
room.send("move", { x = -1 });
elif Input.is_action_pressed("ui_right"):
room.send("move", { x = 1 });

View File

@@ -0,0 +1 @@
uid://ddjkvt52o2ggh

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://ceqab8i8yqmjj"]
[ext_resource type="Script" path="res://addons/godot_colyseus/demo/main.gd" id="1"]
[node name="Node2D" type="Node2D"]
script = ExtResource("1")

View File

@@ -0,0 +1,10 @@
@tool
extends EditorPlugin
func _enter_tree():
pass
func _exit_tree():
pass

View File

@@ -0,0 +1 @@
uid://b13nsgfmclyf6

View File

@@ -0,0 +1,132 @@
extends RefCounted
const promises = preload("res://addons/godot_colyseus/lib/promises.gd")
const Promise = promises.Promise;
const RunPromise = promises.RunPromise;
const HTTP = preload("res://addons/godot_colyseus/lib/http.gd")
const CRoom = preload("res://addons/godot_colyseus/lib/room.gd")
const RoomInfo = preload("res://addons/godot_colyseus/lib/room_info.gd")
var endpoint: String
func _init(endpoint: String):
self.endpoint = endpoint
func join_or_create(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["joinOrCreate", room_name, options, schema_type])
func create(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["create", room_name, options, schema_type])
func join(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["join", room_name, options, schema_type])
func join_by_id(schema_type: GDScript, room_id: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["joinById", room_id, options, schema_type])
func reconnect(schema_type: GDScript, reconnection_token: String) -> Promise:
var arr = reconnection_token.split(":")
if arr.size() == 2:
var room_id = arr[0]
var token = arr[1]
return RunPromise.new(
_create_match_make_request,
["reconnect", room_id, {"reconnectionToken": token}, schema_type])
else:
var fail = Promise.new()
fail.reject("Invalidate `reconnection_token`")
return fail
func get_available_rooms(room_name:String) -> Promise:
var path = "/matchmake/" + room_name
return RunPromise.new(
_http_get,
[path, {"Accept": "application/json"}]
).then(_process_rooms)
func _create_match_make_request(
promise: Promise,
method: String,
room_name: String,
options: Dictionary,
schema_type: GDScript):
var path: String = "/matchmake/" + method + "/" + room_name
var server = endpoint
if server.begins_with("ws"):
server = server.replace("ws", "http")
if options == null:
options = {}
var http = HTTP.new(server)
var req = HTTP.RequestInfo.new("POST", path)
req.add_header("Accept", "application/json")
req.add_header("Content-Type", "application/json")
req.body = options
var resp = http.fetch(req)
if resp.get_state() == Promise.State.Waiting:
await resp.completed
if resp.get_state() == Promise.State.Failed:
promise.reject(resp.get_error())
return
var res: HTTP.Response = resp.get_data()
var response = res.json()
if response == null:
promise.reject("Server unreadable!")
return
if response.get('code') != null:
promise.reject(response['error'])
return
var room = CRoom.new(response["room"]["name"], schema_type)
room.room_id = response["room"]["roomId"]
room.session_id = response["sessionId"]
room.connect_remote(_build_endpoint(response["room"], { "sessionId": room.session_id }))
room.on_join.once(Callable(self, "_room_joined"), [promise, room])
room.on_error.once(Callable(self, "_room_error"), [promise, room])
func _room_joined(promise: Promise, room: CRoom):
room.on_error.off(Callable(self, "_room_error"))
promise.resolve(room)
func _room_error(code: int, message: String, promise: Promise, room: CRoom):
promise.reject(str("[", code, "]", message))
func _build_endpoint(room: Dictionary, options: Dictionary = {}):
var params = PackedStringArray()
for name in options.keys():
params.append(name + "=" + options[name])
return endpoint + "/" + room["processId"] + "/" + room["roomId"] + "?" + "&".join(params)
func _http_get(promise: Promise, path: String, headers: Dictionary):
var server = endpoint
if server.begins_with("ws"):
server = server.replace("ws", "http")
var http = HTTP.new(server)
var req = HTTP.RequestInfo.new("GET", path)
for key in headers.keys():
req.add_header(key, headers[key])
var resp = http.fetch(req)
if resp.get_state() == Promise.State.Waiting:
await resp.completed
if resp.get_state() == Promise.State.Failed:
promise.reject(resp.get_error())
return
var res: HTTP.Response = resp.get_data()
promise.resolve(res.json())
func _process_rooms(result, promise: Promise):
var list = []
for data in result:
list.append(RoomInfo.new(data))
return list

View File

@@ -0,0 +1 @@
uid://mutuixu84pvj

View File

@@ -0,0 +1,180 @@
extends Object
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const SchemaInterface = preload("res://addons/godot_colyseus/lib/schema_interface.gd")
class Collection extends SchemaInterface:
var sub_type
func meta_get_subtype(index):
return sub_type
class ArraySchema extends Collection:
var items = []
func clear(decoding: bool = false):
items.clear()
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
assert(items.size() > index)
items.remove_at(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func meta_set_self(value):
items = value
func at(index: int):
return items[index]
func size() -> int:
return items.size()
func _to_string():
return JSON.stringify(items)
func to_object():
return items
class MapSchema extends Collection:
var _keys = {}
var items = {}
var _counter = 0
func clear(decoding: bool = false):
items.clear()
_keys.clear()
_counter = 0
func meta_get(index):
if _keys.has(index):
return items[_keys[index]]
return null
func meta_get_key(index):
if not _keys.has(index):
return index
return _keys[index]
func meta_set(index, key, value):
_keys[index] = key
items[key] = value
func meta_remove(index):
if not _keys.has(index):
return
items.erase(_keys[index])
_keys.erase(index)
func at(key: String):
return items.get(key)
func put(key: String, value):
_keys[_counter] = key
items[key] = value
++_counter
func _to_string():
return JSON.stringify(items)
func to_object():
return items
func keys():
var list = []
for k in _keys:
list.append(_keys[k])
return list
func size():
return _keys.size()
func has(key: String):
return items.has(key)
class SetSchema extends Collection:
var _counter = 0
var items = {}
func clear(decoding: bool = false):
items.clear()
_counter = 0
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
items.erase(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func _to_string():
return JSON.stringify(items)
func to_object():
return items
class CollectionSchema extends Collection:
var items = []
func clear(decoding: bool = false):
items.clear()
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
items.erase(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func _to_string():
return JSON.stringify(items)
func to_object():
return items

View File

@@ -0,0 +1 @@
uid://ciu14viqsjpjg

View File

@@ -0,0 +1,32 @@
extends Object
const Client = preload("res://addons/godot_colyseus/lib/client.gd")
const Schema = preload("res://addons/godot_colyseus/lib/schema.gd")
const Room = preload("res://addons/godot_colyseus/lib/room.gd")
const types = preload("res://addons/godot_colyseus/lib/types.gd")
const Field = Schema.Field
const REF = types.REF
const MAP = types.MAP
const ARRAY = types.ARRAY
const STRING = types.STRING
const NUMBER = types.NUMBER
const BOOLEAN = types.BOOLEAN
const INT8 = types.INT8
const UINT8 = types.UINT8
const INT16 = types.INT16
const UINT16 = types.UINT16
const INT32 = types.INT32
const UINT32 = types.UINT32
const INT64 = types.INT64
const UINT64 = types.UINT64
const FLOAT32 = types.FLOAT32
const FLOAT64 = types.UINT32
const collections = preload("res://addons/godot_colyseus/lib/collections.gd")
const ArraySchema = collections.ArraySchema
const MapSchema = collections.MapSchema
const SetSchema = collections.SetSchema
const CollectionSchema = collections.CollectionSchema
const RoomInfo = preload("res://addons/godot_colyseus/lib/room_info.gd")
const Promise = preload("res://addons/godot_colyseus/lib/promises.gd").Promise

View File

@@ -0,0 +1 @@
uid://dni1bp1qice02

View File

@@ -0,0 +1,68 @@
extends RefCounted
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
var reader: StreamPeerBuffer
func _init(reader: StreamPeerBuffer):
reader.big_endian = false
self.reader = reader
func read_utf8() -> String:
var prefix = reader.get_u8()
var length = -1
if prefix < 0xc0:
length = prefix & 0x1f
elif prefix == 0xd9:
length = reader.get_u8()
elif prefix == 0xda:
length = reader.get_u16()
elif prefix == 0xdb:
length = reader.get_u32()
return reader.get_utf8_string(length)
func number():
var prefix = reader.get_u8()
if prefix < 0x80:
return prefix
elif prefix == 0xca:
return reader.get_float()
elif prefix == 0xcb:
return reader.get_double()
elif prefix == 0xcc:
return reader.get_u8()
elif prefix == 0xcd:
return reader.get_u16()
elif prefix == 0xce:
return reader.get_u32()
elif prefix == 0xcf:
return reader.get_u64()
elif prefix == 0xd0:
return reader.get_8()
elif prefix == 0xd1:
return reader.get_16()
elif prefix == 0xd2:
return reader.get_32()
elif prefix == 0xd3:
return reader.get_64()
elif prefix > 0xdf:
return (0xff - prefix + 1) * -1
func has_more() -> bool:
return reader.get_position() < reader.get_size()
func current_bit() -> int:
return reader.data_array[reader.get_position()]
func is_number() -> bool:
var prefix = current_bit()
return prefix < 0x80 || (prefix >= 0xca && prefix <= 0xd3)
func unpack():
var result = MsgPack.decode(reader)
if result.error == OK:
return result.result
return null

View File

@@ -0,0 +1 @@
uid://btjicixj57gl5

View File

@@ -0,0 +1,67 @@
extends RefCounted
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
var writer: StreamPeerBuffer
func _init(writer: StreamPeerBuffer):
writer.big_endian = false
self.writer = writer
func string(v: String):
var bytes = v.to_utf8_buffer()
var length = bytes.size()
if length < 0x20:
writer.put_u8(length | 0xa0)
elif length < 0x100:
writer.put_u8(0xd9)
writer.put_u8(length)
elif length < 0x10000:
writer.put_u8(0xda)
writer.put_u16(length)
elif length < 0x100000000:
writer.put_u8(0xdb)
writer.put_u32(length)
else:
assert(false) #,"String too long")
writer.put_data(bytes)
func number(v):
if v == NAN:
return number(0)
elif v != abs(v):
writer.put_u8(0xcb)
writer.put_double(v)
elif v >= 0:
if v < 0x80:
writer.put_u8(v)
elif v < 0x100:
writer.put_u8(0xcc)
writer.put_u8(v)
elif v < 0x10000:
writer.put_u8(0xcd)
writer.put_u16(v)
elif v < 0x100000000:
writer.put_u8(0xce)
writer.put_u32(v)
else:
writer.put_u8(0xcf)
writer.put_u32(v)
else:
if v >= -0x20:
writer.put_u8(0xe0 | (v + 0x20))
elif v >= -0x80:
writer.put_u8(0xd0)
writer.put_8(v)
elif v >= -0x8000:
writer.put_u8(0xd1)
writer.put_16(v)
elif v >= -0x80000000:
writer.put_u8(0xd2)
writer.put_32(v)
else:
writer.put_u8(0xd3)
writer.put_64(v)

View File

@@ -0,0 +1 @@
uid://drl5m0b1yqxig

View File

@@ -0,0 +1,23 @@
extends RefCounted
var _running = false
var fn: Callable
var argv: Array
# Called when the node enters the scene tree for the first time.
func _init(fn: Callable,argv: Array = []):
self.fn = fn
self.argv = argv
func start():
if not _running:
_running = true
var root: SceneTree = Engine.get_main_loop()
while true:
await root.process_frame
if not _running:
return
fn.callv(argv)
func stop():
_running = false

View File

@@ -0,0 +1 @@
uid://b2ng861vtoeih

View File

@@ -0,0 +1,229 @@
extends RefCounted
const promises = preload("res://addons/godot_colyseus/lib/promises.gd")
const Promise = promises.Promise
const RunPromise = promises.RunPromise
var _connected = false
var _client_promise: promises.Promise
class RequestInfo:
var method: String = "GET"
var path: String = "/"
var headers: PackedStringArray = []
var body
func _init(method: String = "GET",path: String = "/"):
self.method = method
self.path = path
func add_header(key: String, value):
headers.append(str(key, ": ", value))
return self
func http_method():
match method.to_upper():
"GET":
return HTTPClient.METHOD_GET
"POST":
return HTTPClient.METHOD_POST
"PUT":
return HTTPClient.METHOD_PUT
"DELETE":
return HTTPClient.METHOD_DELETE
"HEAD":
return HTTPClient.METHOD_HEAD
"OPTIONS":
return HTTPClient.METHOD_OPTIONS
func get_body():
if body == null:
body = ""
if body is Dictionary or body is Array:
body = JSON.stringify(body)
return body
class Response:
var _response_chunks: Array = []
var _body
var _has_response = false
var _status_code: int = 0
var _content_length: int = 0
var _headers
func body() -> PackedByteArray:
if _body == null:
_body = PackedByteArray()
for chunk in _response_chunks:
_body.append_array(chunk)
return _body
func text() -> String:
return body().get_string_from_utf8()
func json():
var test_json_conv = JSON.new()
var err = test_json_conv.parse(text())
if err == OK:
return test_json_conv.get_data()
print(str(test_json_conv.get_error_message(), ":", test_json_conv.get_error_line()))
return null
func headers() -> Dictionary:
return _headers
func status_code() -> int:
return _status_code
func content_length() -> int:
return _content_length
func _to_string():
var lines = PackedStringArray()
lines.append(str("StatusCode: ", status_code()))
lines.append(str("ContentLength: ", content_length()))
lines.append(str("Headers: "))
var header = headers()
for key in header.keys():
lines.append(str(" ", key, ": ", header[key]))
lines.append(str("Body: [", body().size(), "]"))
return "\n".join(lines)
var _old_status
func _init(server: String):
var url = parseUrl(server)
_client_promise = promises.RunPromise.new(Callable(self, "_setup"), [url.host, url.port, url.ssl]);
static func parseUrl(url) -> Dictionary:
var regex = RegEx.new()
regex.compile("(\\w+):\\/\\/([^\\/:]+)(:(\\d+))?")
var result = regex.search(url)
var scheme = result.get_string(1)
var ssl = scheme == "https"
var host = result.get_string(2)
var portstr = result.get_string(4)
var port = -1
if portstr != "":
port = int(portstr)
return {
"host": host,
"ssl": ssl,
"port": port,
}
var host: String
func _setup(promise: promises.Promise, host, port, ssl):
var client = HTTPClient.new()
self.host = host
var error = client.connect_to_host(host, port, TLSOptions.client() if ssl else null)
if error != OK:
promise.reject(str("ErrorCode: ", error))
var root = Engine.get_main_loop()
while true:
await root.process_frame
client.poll()
var status = client.get_status()
match status:
HTTPClient.STATUS_CONNECTED:
promise.resolve(client)
break
HTTPClient.STATUS_DISCONNECTED:
promise.reject("Disconnected from Host")
break
HTTPClient.STATUS_CANT_CONNECT:
promise.reject("Can't Connect to Host")
break
func _request(promise: Promise, request: RequestInfo):
if _client_promise.get_state() == promises.Promise.State.Waiting:
await _client_promise.completed
if _client_promise.get_state() == Promise.State.Failed:
promise.reject(_client_promise.get_error())
return
var client: HTTPClient = _client_promise.get_data()
var body = request.get_body()
var error
if body is String:
error = client.request(request.http_method(), request.path, request.headers, body)
elif body is PackedByteArray:
error = client.request_raw(request.http_method(), request.path, request.headers, body)
else:
promise.reject("Unsupport body type")
return
if error != OK:
promise.reject(str("Error code ", error))
return
var root = Engine.get_main_loop()
var response = Response.new()
while true:
await root.process_frame
error = client.poll()
var status = client.get_status()
match status:
HTTPClient.STATUS_DISCONNECTED:
if response._has_response:
promise.resolve(response)
else:
promise.reject("Disconnected from Host")
return
HTTPClient.STATUS_CANT_CONNECT:
promise.reject("Can't Connect to Host")
return
HTTPClient.STATUS_CONNECTION_ERROR:
promise.reject("Connection Error")
return
HTTPClient.STATUS_BODY:
if not response._has_response:
response._has_response = true
response._status_code = client.get_response_code()
response._content_length = client.get_response_body_length()
response._headers = client.get_response_headers_as_dictionary()
var chunk = client.read_response_body_chunk()
if chunk.is_empty():
continue
response._response_chunks.append(chunk)
HTTPClient.STATUS_CONNECTED:
promise.resolve(response)
return
pass
func _resolve(promise: Promise, request: RequestInfo, response: Response):
if response.status_code() == 301:
var new_request = RequestInfo.new(request.method, request.path)
new_request.headers = request.headers
new_request.body = request.body
var path: String = response.headers()["Location"]
if path.begins_with("http://") or path.begins_with("https://"):
var idx = path.find('/', 8)
var host
if idx >= 0:
host = path.substr(0, idx)
path = path.substr(idx)
else:
host = path
path = '/'
var url = parseUrl(host)
if url.host != self.host:
printerr("Cannot connect to new host ", host, self.host)
promise.resolve(response)
return
else:
new_request.path = path
promise.resolve(fetch(new_request))
pass
else:
promise.resolve(response)
func fetch(request = null) -> Promise:
if request == null:
request = RequestInfo.new()
elif request is String:
var path = request
request = RequestInfo.new()
request.path = path
return RunPromise.new(_request, [request])

View File

@@ -0,0 +1 @@
uid://co8kfm0yu3vui

View File

@@ -0,0 +1,62 @@
extends RefCounted
const ps = preload("res://addons/godot_colyseus/lib/promises.gd")
class Callback:
var fn: Callable
var args
var once = false
func emit(arg: Array):
var parmas = []
parmas.append_array(arg)
parmas.append_array(args)
fn.callv(parmas)
var cbs = []
func on(fn: Callable, args: Array = []):
var cb = Callback.new()
cb.fn = fn
cb.args = args
cbs.append(cb)
func off(fn: Callable):
var willremove = []
for cb in cbs:
if cb.fn == fn:
willremove.append(cb)
for cb in willremove:
cbs.erase(cb)
func once(fn: Callable, args: Array = []):
var cb = Callback.new()
cb.fn = fn
cb.args = args
cb.once = true
cbs.append(cb)
func wait() -> ps.Promise:
var promise = ps.Promise.new()
once(Callable(self, "_on_event"), [promise])
return promise
func _on_event(data, promise: ps.Promise):
promise.resolve(data)
func emit(argv: Array = []):
var willremove = []
var size = cbs.size()
for i in range(size):
var cb = cbs[i]
if !is_instance_valid(cb):
cbs.remove_at(i)
i -= 1
size -= 1
continue
cb.emit(argv)
if cb.once:
willremove.append(cb)
for cb in willremove:
cbs.erase(cb)

View File

@@ -0,0 +1 @@
uid://dtgvuxvgbn08y

View File

@@ -0,0 +1,487 @@
# Copyright (C) 2019 Tintin Ho
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# godot-msgpack
#
# This is a MessagePack serializer written in pure GDSciprt. To install this
# library in your project simply and copy and paste this file inside your
# project (e.g. res://msgpack.gd).
#
#
# class Msgpack
# static Dictionary encode(Variant value)
# Convert a value (number, string, array and dictionary) into their
# counterparts in messagepack. Returns dictionary with three fields:
# `result` which is the packed data (a PackedByteArray); `error` which is the
# error code; and `error_string` which is a human readable error message
#
# static Dictionary decode(PackedByteArray bytes)
# Convert a packed data (a PackedByteArray) into a value, the reverse of the
# encode function. The return value is similar to the one in the encode
# method
static func encode(value, buffer: StreamPeerBuffer):
var ctx = {error = OK, error_string = ""}
buffer.big_endian = true
_encode(buffer, value, ctx)
if ctx.error == OK:
return {
result = buffer.data_array,
error = OK,
error_string = "",
}
else:
return {
result = PackedByteArray(),
error = ctx.error,
error_string = ctx.error_string,
}
static func decode(buffer: StreamPeerBuffer):
buffer.big_endian = true
var ctx = {error = OK, error_string = ""}
var value = _decode(buffer, ctx)
if ctx.error == OK:
if buffer.get_position() == buffer.get_size():
return {result = value, error = OK, error_string = ""}
else:
var msg = "excess buffer %s bytes" % [buffer.get_size() - buffer.get_position()]
return {result = null, error = FAILED, error_string = msg}
else:
return {result = null, error = ctx.error, error_string = ctx.error_string}
static func _encode(buf, value, ctx):
match typeof(value):
TYPE_NIL:
buf.put_u8(0xc0)
TYPE_BOOL:
if value:
buf.put_u8(0xc3)
else:
buf.put_u8(0xc2)
TYPE_INT:
if -(1 << 5) <= value and value <= (1 << 7) - 1:
# fixnum (positive and negative)
buf.put_8(value)
elif -(1 << 7) <= value and value <= (1 << 7):
buf.put_u8(0xd0)
buf.put_8(value)
elif -(1 << 15) <= value and value <= (1 << 15):
buf.put_u8(0xd1)
buf.put_16(value)
elif -(1 << 31) <= value and value <= (1 << 31):
buf.put_u8(0xd2)
buf.put_32(value)
else:
buf.put_u8(0xd3)
buf.put_64(value)
TYPE_FLOAT:
buf.put_u8(0xcb)
buf.put_double(value)
TYPE_STRING:
var bytes = value.to_utf8_buffer()
var size = bytes.size()
if size <= (1 << 5) - 1:
# type fixstr [101XXXXX]
buf.put_u8(0xa0 | size)
elif size <= (1 << 8) - 1:
# type str 8
buf.put_u8(0xd9)
buf.put_u8(size)
elif size <= (1 << 16) - 1:
# type str 16
buf.put_u8(0xda)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type str 32
buf.put_u8(0xdb)
buf.put_u32(size)
else:
assert(false)
buf.put_data(bytes)
TYPE_PACKED_BYTE_ARRAY:
var size = value.size()
if size <= (1 << 8) - 1:
buf.put_u8(0xc4)
buf.put_u8(size)
elif size <= (1 << 16) - 1:
buf.put_u8(0xc5)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
buf.put_u8(0xc6)
buf.put_u32(size)
else:
assert(false)
buf.put_data(value)
TYPE_ARRAY:
var size = value.size()
if size <= 15:
# type fixarray [1001XXXX]
buf.put_u8(0x90 | size)
elif size <= (1 << 16) - 1:
# type array 16
buf.put_u8(0xdc)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type array 32
buf.put_u8(0xdd)
buf.put_u32(size)
else:
assert(false)
for obj in value:
_encode(buf, obj, ctx)
if ctx.error != OK:
return
TYPE_DICTIONARY:
var size = value.size()
if size <= 15:
# type fixmap [1000XXXX]
buf.put_u8(0x80 | size)
elif size <= (1 << 16) - 1:
# type map 16
buf.put_u8(0xde)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type map 32
buf.put_u8(0xdf)
buf.put_u32(size)
else:
assert(false)
for key in value:
_encode(buf, key, ctx)
if ctx.error != OK:
return
_encode(buf, value[key], ctx)
if ctx.error != OK:
return
_:
ctx.error = FAILED
ctx.error_string = "unsupported data type %s" % [typeof(value)]
static func _decode(buffer, ctx):
if buffer.get_position() == buffer.get_size():
ctx.error = FAILED
ctx.error_string = "unexpected end of input"
return null
var head = buffer.get_u8()
if head == 0xc0:
return null
elif head == 0xc2:
return false
elif head == 0xc3:
return true
# Integers
elif head & 0x80 == 0:
# positive fixnum
return head
elif (~head) & 0xe0 == 0:
# negative fixnum
return head - 256
elif head == 0xcc:
# uint 8
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint8"
return null
return buffer.get_u8()
elif head == 0xcd:
# uint 16
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint16"
return null
return buffer.get_u16()
elif head == 0xce:
# uint 32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint32"
return null
return buffer.get_u32()
elif head == 0xcf:
# uint 64
if buffer.get_size() - buffer.get_position() < 8:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint64"
return null
return buffer.get_u64()
elif head == 0xd0:
# int 8
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enogh buffer for int8"
return null
return buffer.get_8()
elif head == 0xd1:
# int 16
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enogh buffer for int16"
return null
return buffer.get_16()
elif head == 0xd2:
# int 32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for int32"
return null
return buffer.get_32()
elif head == 0xd3:
# int 64
if buffer.get_size() - buffer.get_position() < 8:
ctx.error = FAILED
ctx.error_string = "not enough buffer for int64"
return null
return buffer.get_64()
# Float
elif head == 0xca:
# float32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for float32"
return null
return buffer.get_float()
elif head == 0xcb:
# float64
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for float64"
return null
return buffer.get_double()
# String
elif (~head) & 0xa0 == 0:
var size = head & 0x1f
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for fixstr required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xd9:
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str8 size"
return null
var size = buffer.get_u8()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str8 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xda:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str16 size"
return null
var size = buffer.get_u16()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str16 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xdb:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str32 size"
return null
var size = buffer.get_u32()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str32 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
# Binary
elif head == 0xc4:
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin8 size"
return null
var size = buffer.get_u8()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin8 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
elif head == 0xc5:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin16 size"
return null
var size = buffer.get_u16()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin16 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
elif head == 0xc6:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin32 size"
return null
var size = buffer.get_u32()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin32 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
# Array
elif head & 0xf0 == 0x90:
var size = head & 0x0f
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
elif head == 0xdc:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for array16 size"
return null
var size = buffer.get_u16()
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
elif head == 0xdd:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for array32 size"
return null
var size = buffer.get_u32()
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
# Map
elif head & 0xf0 == 0x80:
var size = head & 0x0f
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
elif head == 0xde:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for map16 size"
return null
var size = buffer.get_u16()
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
elif head == 0xdf:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for map32 size"
return null
var size = buffer.get_u32()
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
else:
ctx.error = FAILED
ctx.error_string = "invalid byte tag %02X at pos %s" % [head, buffer.get_position()]
return null

View File

@@ -0,0 +1 @@
uid://dlclaop7pocqd

View File

@@ -0,0 +1,9 @@
extends Node
const ADD = 128
const REPLACE = 0
const DELETE = 64
const DELETE_AND_ADD = 192
const TOUCH = 1
const CLEAR = 10
const SWITCH_TO_STRUCTURE = 255

View File

@@ -0,0 +1 @@
uid://bucnjhgajvk8x

View File

@@ -0,0 +1,107 @@
extends RefCounted
class Promise:
enum State {
Waiting,
Success,
Failed
}
var result
signal completed
var _state: State = State.Waiting
func get_state() -> State:
return _state
func resolve(res = null):
if res is Promise:
await res.completed
result = res.result
_state = res.get_state()
emit_signal("completed")
else:
result = res
_state = State.Success
emit_signal("completed")
func reject(error = null):
result = error
_state = State.Failed
emit_signal("completed")
var data:get = get_data
func get_data():
if _state == State.Success:
return result
return null
var error:get = get_error
func get_error():
if _state == State.Failed:
return result
return null
var is_failed : bool :
get():
return _state == State.Failed
func wait():
if _state == State.Waiting:
await self.completed
return self
func _to_string():
match _state:
State.Waiting:
return "[Waiting]"
State.Success:
return str("[Success:",result,"]")
State.Failed:
return str("[Failed:",result,"]")
func _next(promise, callback: Callable, argv: Array):
await wait()
if _state == State.Success:
var arr = [get_data(), promise]
arr.append_array(argv)
var ret = await callback.callv(arr)
promise.resolve(ret)
func then(callback: Callable, argv: Array = []) -> Promise:
return RunPromise.new(Callable(self, "_next"), [callback, argv])
class FramePromise extends Promise:
var cb: Callable
var argv: Array
func _init(callback: Callable,argv: Array = []):
cb = callback
self.argv = argv
_run()
func _run():
var root = Engine.get_main_loop()
while true:
if root is SceneTree:
await root.process_frame
var arr = [self]
arr.append_array(argv)
cb.callv(arr)
if get_state() != State.Waiting:
break
class RunPromise extends Promise:
var cb: Callable
var argv: Array
func _init(callback: Callable,argv: Array = []):
cb = callback
self.argv = argv
_run()
func _run():
var arr = [self]
arr.append_array(argv)
await cb.callv(arr)

View File

@@ -0,0 +1 @@
uid://d4lhtnts1yq1k

View File

@@ -0,0 +1,198 @@
extends RefCounted
const FrameRunner = preload("res://addons/godot_colyseus/lib/frame_runner.gd")
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const ser = preload("res://addons/godot_colyseus/lib/serializer.gd")
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const Encoder = preload("res://addons/godot_colyseus/lib/encoder.gd")
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
const Schema = preload("./schema.gd")
const CODE_HANDSHAKE = 9
const CODE_JOIN_ROOM = 10
const CODE_ERROR = 11
const CODE_LEAVE_ROOM = 12
const CODE_ROOM_DATA = 13
const CODE_ROOM_STATE = 14
const CODE_ROOM_STATE_PATCH = 15
const CODE_ROOM_DATA_SCHEMA = 16
const ERROR_MATCHMAKE_NO_HANDLER = 4210
const ERROR_MATCHMAKE_INVALID_CRITERIA = 4211
const ERROR_MATCHMAKE_INVALID_ROOM_ID = 4212
const ERROR_MATCHMAKE_UNHANDLED = 4213
const ERROR_MATCHMAKE_EXPIRED = 4214
const ERROR_AUTH_FAILED = 4215
const ERROR_APPLICATION_ERROR = 4216
var room_name: String
var room_id: String
var session_id: String
var serializer: ser.Serializer
var ws: WebSocketPeer
var frame_runner: FrameRunner
var reconnection_token: String
var schema_type: GDScript
var _has_joined = false
func has_joined() -> bool:
return _has_joined
# [code: int, message: String]
var on_error: EventListener = EventListener.new()
# []
var on_leave: EventListener = EventListener.new()
# []
var on_join: EventListener = EventListener.new()
# [state: Schema]
var on_state_change: EventListener = EventListener.new()
# [data]
var _messages = {}
func on_message(event: String, new_listener: bool = true) -> EventListener:
var listener
if not _messages.has(event) or new_listener:
listener = EventListener.new()
_messages[event] = listener
else:
listener = _messages[event]
return listener
func _init(room_name: String,schema_type: GDScript):
self.room_name = room_name
self.schema_type = schema_type
ws = WebSocketPeer.new()
#ws.connect("connection_established",Callable(self,"_connection_established"))
#ws.connect("connection_error",Callable(self,"_connection_error"))
#ws.connect("connection_closed",Callable(self,"_connection_closed"))
#ws.connect("data_received",Callable(self,"_on_data"))
frame_runner = FrameRunner.new(_on_frame)
func _connection_established(protocol):
pass
func _connection_error():
frame_runner.stop()
func _connection_closed(was_clean: bool):
frame_runner.stop()
func _on_data():
var data = ws.get_packet()
var reader = StreamPeerBuffer.new()
reader.data_array = data
var decoder = Decoder.new(reader)
var code = reader.get_u8()
match code:
CODE_JOIN_ROOM:
var token = reader.get_string(reader.get_u8())
var serializer_id = reader.get_string(reader.get_u8())
if serializer == null:
serializer = ser.getSerializer(serializer_id, schema_type)
if decoder.has_more():
if serializer:
serializer.handshake(decoder)
else:
on_error.emit([1, "Can not find serializer"])
return
self.reconnection_token = str(room_id, ":", token)
_has_joined = true
on_join.emit()
send_raw([CODE_JOIN_ROOM])
CODE_ERROR:
var message = decoder.read_utf8()
on_error.emit([0, message])
CODE_LEAVE_ROOM:
leave()
CODE_ROOM_DATA:
var type
if decoder.is_number():
type = str('i', decoder.number())
else:
type = decoder.read_utf8()
var listener = on_message(type, false)
if listener != null:
var ret = decoder.unpack()
if ret == null:
ret = {}
listener.emit([ret])
CODE_ROOM_STATE:
serializer.set_state(decoder)
on_state_change.emit([serializer.get_state()])
CODE_ROOM_STATE_PATCH:
serializer.patch(decoder)
on_state_change.emit([serializer.get_state()])
CODE_ROOM_DATA_SCHEMA:
print("Receive message CODE_ROOM_DATA_SCHEMA")
func connect_remote(url: String):
var _url = url
if url.begins_with("http:"):
_url = url.replace("http:", "ws:")
elif url.begins_with("https:"):
_url = url.replace("https:", "wss:")
#_url = _url.replace("/colyseus", "")
ws.connect_to_url(_url)
frame_runner.start()
func _on_frame():
ws.poll()
var state = ws.get_ready_state()
match state:
WebSocketPeer.STATE_OPEN:
while ws.get_available_packet_count() > 0:
_on_data()
WebSocketPeer.STATE_CLOSED:
var code = ws.get_close_code()
var reason = ws.get_close_reason()
_connection_closed(true)
if _has_joined:
leave()
func send_raw(bytes: PackedByteArray):
ws.send(bytes)
func send(type: String, message = null):
var buffer = StreamPeerBuffer.new()
buffer.put_u8(CODE_ROOM_DATA)
var encoder = Encoder.new(buffer)
if typeof(type) == TYPE_STRING:
encoder.string(type)
else:
encoder.number(type)
if message != null:
var result = MsgPack.encode(message, buffer)
assert(result.error == OK)
send_raw(buffer.data_array)
func leave(consented = true):
_has_joined = false
if not room_id.is_empty():
if consented:
send_raw([CODE_LEAVE_ROOM])
else:
ws.disconnect_from_host()
on_leave.emit()
var state : Schema : get = get_state
func get_state() -> Schema:
return serializer.get_state()

View File

@@ -0,0 +1 @@
uid://jpi6ohybugst

View File

@@ -0,0 +1,18 @@
extends RefCounted
var clients: int
var created_at: String
var max_clients: int
var name: String
var process_id: String
var room_id: String
func _init(dic):
clients = dic.get('clients')
created_at = dic.get('createdAt')
var num = dic.get('maxClients')
if num != null:
max_clients = num
name = dic.get('name')
process_id = dic.get('processId')
room_id = dic.get('roomId')

View File

@@ -0,0 +1 @@
uid://b8wn1vytiodw3

View File

@@ -0,0 +1,308 @@
extends "./schema_interface.gd"
const col = preload("res://addons/godot_colyseus/lib/collections.gd")
const OP = preload("res://addons/godot_colyseus/lib/operations.gd")
const TypeInfo = preload("res://addons/godot_colyseus/lib/type_info.gd")
const END_OF_STRUCTURE = 0xc1
const NIL = 0xc0
const INDEX_CHANGE = 0xd4
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const SchemaInterface = preload("res://addons/godot_colyseus/lib/schema_interface.gd")
class Field:
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
var index: int
var name: String
var value
var current_type: TypeInfo
func _init(name: String,type: String,schema_type = null):
current_type = TypeInfo.new(type)
if schema_type is String:
current_type.sub_type = TypeInfo.new(schema_type)
elif schema_type is GDScript:
if type == Types.REF:
current_type.sub_type = schema_type
else:
current_type.sub_type = TypeInfo.new(Types.REF, schema_type)
elif schema_type is TypeInfo:
current_type.sub_type = schema_type
self.name = name
func _to_string():
if current_type:
return current_type.to_string()
else:
return 'null'
var _fields: Array = []
var _field_index = {}
var _refs = {}
var _change_listeners = {}
func _get_property_list():
var result = []
for field in _fields:
result.append({
name = field.name,
type = Types.to_gd_type(field.current_type.type),
usage = PROPERTY_USAGE_DEFAULT
})
return result
func _get(property):
if _field_index.has(property):
var value = _field_index[property].value
if value is SchemaInterface:
pass
return value
return null
func _set(property, value):
if _field_index.has(property):
var field = _field_index[property]
var old = field.value
if old is SchemaInterface:
pass
field.value = value
return true
return false
# [event: String, target, key_or_index]
# path format {path}:{action}
# {action} is one of:
# add Create sub object, paramaters [current, new_value, key]
# remove Delete sub object paramaters [current, old_value, key]
# replace Replace sub object paramaters [current, new_value, key]
# delete Current object is deleted, paramaters [current]
# create Current object is created, paramaters [current]
# change Current object's attributes has changed, paramaters [current]
# clear Current Array or Map has cleared, paramaters [current]
func listen(path: String) -> EventListener:
if not _change_listeners.has(path):
_change_listeners[path] = EventListener.new()
return _change_listeners[path]
static func define_fields() -> Array:
return []
func _init():
_fields = self.get_script().define_fields()
var counter = 0
for field in _fields:
field.index = counter
_setup_field(field)
counter += 1
func _setup_field(field: Field):
_field_index[field.name] = field
var type = field.current_type
match type.type:
Types.MAP:
assert(type.sub_type != null) #,"Schema type is requested")
Types.ARRAY:
assert(type.sub_type != null) #,"Schema type is requested")
field.value = col.Collection.new()
Types.SET:
assert(type.sub_type != null) #,"Schema type is requested")
Types.COLLECTION:
assert(type.sub_type != null) #,"Schema type is requested")
Types.REF:
assert(type.sub_type != null) #,"Schema type is requested")
Types.NUMBER, Types.FLOAT32, Types.FLOAT64:
field.value = 0.0
Types.INT8, Types.UINT8, Types.INT16, Types.UINT16, Types.INT32, Types.UINT32, Types.INT64, Types.UINT64:
field.value = 0
Types.STRING:
field.value = ""
func get_fields():
return _fields
func decode(decoder: Decoder) -> int:
var ref_id = 0
var ref: Ref = Ref.new(self, TypeInfo.new(Types.REF))
_refs[ref_id] = ref
var changes = []
var changed_objects = {}
while decoder.has_more():
var byte = decoder.reader.get_u8()
if byte == OP.SWITCH_TO_STRUCTURE:
ref_id = decoder.number()
var next_ref = _refs[ref_id]
assert(next_ref != null) #,str('"refId" not found:', ref_id))
ref = next_ref
continue
var is_schema = ref.type_info.type == Types.REF
var operation = byte
if is_schema:
operation = (byte >> 6) << 6
if operation == OP.CLEAR:
ref.value.clear(true)
if ref.value is SchemaInterface:
changes.append({
target = ref.value,
event = "clear",
argv = []
})
continue
var field_index = byte % _re_replace(operation)
if not is_schema:
field_index = decoder.number()
var ref_value = ref.value
if ref_value is SchemaInterface:
var old = ref_value.meta_get(field_index)
var new
var key = field_index
if ref.type_info.type != Types.MAP:
key = ref_value.meta_get_key(field_index)
if operation == OP.DELETE:
if ref.type_info.type == Types.MAP:
key = ref_value.meta_get_key(field_index)
ref_value.meta_remove(field_index)
else:
if ref.type_info.type == Types.MAP:
key = decoder.read_utf8()
var type: TypeInfo = ref_value.meta_get_subtype(field_index)
if type.is_schema_type():
var new_ref_id = decoder.number()
if _refs.has(new_ref_id):
new = _refs[new_ref_id].value
else:
if operation != OP.REPLACE:
new = type.create()
new.id = new_ref_id
_refs[new_ref_id] = Ref.new(new, type)
else:
new = type.decode(decoder)
if old != new:
if old == null:
changes.append({
target = ref_value,
event = "add",
argv = [new, key]
})
elif new == null:
changes.append({
target = ref_value,
event = "remove",
argv = [old, key]
})
else:
changes.append({
target = ref_value,
event = "replace",
argv = [new, key]
})
if old != null:
if old is SchemaInterface && old.id != null:
changes.append({
target = old,
event = "delete",
argv = []
})
_refs.erase(old.id)
if new != null:
ref_value.meta_set(field_index, key, new)
if new is SchemaInterface:
changes.append({
target = new,
event = "create",
argv = []
})
new.set_parent(ref_value, field_index)
elif old != null:
ref_value.meta_remove(field_index)
changed_objects[ref_value] = true
for change in changes:
var target = change.target
target.trigger(change.event, change.argv)
for target in changed_objects.keys():
target.trigger("change", [])
return 0
func _re_replace(operation):
if operation == OP.REPLACE:
return 255
return operation
func clear(decoding: bool = false):
pass
func meta_get(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.value
func meta_get_key(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.name
func meta_get_subtype(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.current_type
func meta_set(index, key, value):
assert(_fields.size() > index)
var field : Field = _fields[index]
field.value = value
func meta_remove(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
var old = field.value
field.value = null
return old
func _to_string():
var obj = to_object()
return JSON.stringify(obj)
func trigger(event: String, argv: Array = [], path: PackedStringArray = PackedStringArray(), target: Object = self):
var path_copy = PackedStringArray(path)
path_copy.reverse()
var path_str = '/'.join(path_copy) + ":" + event
if _change_listeners.has(path_str):
var ls: EventListener = _change_listeners[path_str]
argv.insert(0, target)
ls.emit(argv)
else:
super.trigger(event, argv, path, target)
func to_object():
var dic = {}
for field in _fields:
if field.value is SchemaInterface:
dic[field.name] = field.value.to_object()
else:
dic[field.name] = field.value
return dic

View File

@@ -0,0 +1 @@
uid://vujiy070i53c

View File

@@ -0,0 +1,54 @@
extends RefCounted
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
class Ref:
var value
var type_info
func _init(value,type_info):
self.value = value
self.type_info = type_info
var id
var parent
var parent_index: int
var parent_key
func clear(decoding: bool = false):
assert(false)
func meta_get(index):
assert(false)
func meta_get_key(index) -> String:
assert(false)
return ""
func meta_get_subtype(index):
assert(false)
func meta_set(index, key, value):
assert(false)
return null
func meta_remove(index):
assert(false)
func set_parent(np, pindex):
if parent == np and parent_index == pindex:
return
if parent != null:
parent.meta_remove(parent_index)
parent = np
parent_index = pindex
parent_key = parent.meta_get_key(parent_index)
func trigger(event: String, argv: Array = [], path: PackedStringArray = PackedStringArray(), target: Object = self):
if parent == null:
return
path.append(parent_key)
parent.trigger(event, argv, path, target)
func to_object():
return "<null>"

View File

@@ -0,0 +1 @@
uid://7hqsu5vttoy2

View File

@@ -0,0 +1,113 @@
extends RefCounted
const Schema = preload("res://addons/godot_colyseus/lib/schema.gd")
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
class Serializer:
func set_state(decoder):
pass
func get_state():
pass
func patch(decoder):
pass
func teardown():
pass
func handshake(decoder):
pass
class NoneSerializer extends Serializer:
pass
class ReflectionField extends Schema:
static func define_fields():
return [
Schema.Field.new("name", Schema.Types.STRING),
Schema.Field.new("type", Schema.Types.STRING),
Schema.Field.new("referenced_type", Schema.Types.NUMBER),
]
func test(field, reflection: Reflection) -> bool:
if self.type != field.current_type.to_string() or self.name != field.name:
var str1 = str(field.name, '-', self.name)
var str2 = str(field.current_type, '-', self.type)
printerr("Field not match ", str1, " : ", str2)
return false
if self.type == Schema.Types.REF:
var type = reflection.types.at(self.referenced_type)
return type.test(field.schema_type, reflection)
return true
class ReflectionType extends Schema:
static func define_fields():
return [
Schema.Field.new("id", Schema.Types.NUMBER),
Schema.Field.new("extendsId", Schema.Types.NUMBER),
Schema.Field.new("fields", Schema.Types.ARRAY, ReflectionField),
]
func test(schema_type, reflection: Reflection) -> bool:
if not schema_type is GDScript:
printerr("Type schema_type not match ", self.id)
return false
var fields = schema_type.define_fields()
var length = fields.size()
if length != self.fields.size():
printerr("Type fields count not match ", self.id)
return false
for i in range(length):
var field = self.fields.at(i)
if not field.test(fields[i], reflection):
return false
return true
class Reflection extends Schema:
static func define_fields():
return [
Schema.Field.new("types", Schema.Types.ARRAY, ReflectionType),
Schema.Field.new("root_type", Schema.Types.NUMBER),
]
func test(schema_type: GDScript) -> bool:
return self.types.at(self.root_type).test(schema_type, self)
class SchemaSerializer extends Serializer:
var state
var schema_type: GDScript
func _init(schema_type):
self.schema_type = schema_type
self.state = schema_type.new()
func handshake(decoder):
var reflection = Reflection.new()
reflection.decode(decoder)
assert(reflection.test(schema_type),"Can not detect schema type")
func set_state(decoder):
state.decode(decoder)
func get_state():
return state
func patch(decoder):
state.decode(decoder)
static func getSerializer(id: String, schema_type: GDScript = null) -> Serializer:
match id:
"schema":
return SchemaSerializer.new(schema_type)
"none":
return NoneSerializer.new()
return null

View File

@@ -0,0 +1 @@
uid://dsp61m3okjbm

View File

@@ -0,0 +1,97 @@
extends Object
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const types = preload("res://addons/godot_colyseus/lib/types.gd")
const collections = preload("res://addons/godot_colyseus/lib/collections.gd")
var type: String
var sub_type
func _init(type: String,sub_type = null):
self.type = type
self.sub_type = sub_type
func is_schema_type():
return type == types.REF or type == types.MAP or type == types.ARRAY or type == types.COLLECTION or type == types.SET
func _to_string():
var ret = type
if sub_type and sub_type.type != types.REF:
ret = str(ret, ':', sub_type.type)
return ret
func create():
match type:
types.REF:
return sub_type.new()
types.MAP:
var obj = collections.MapSchema.new()
obj.sub_type = sub_type
return obj
types.ARRAY:
var obj = collections.ArraySchema.new()
obj.sub_type = sub_type
return obj
types.SET:
var obj = collections.SetSchema.new()
obj.sub_type = sub_type
return obj
types.COLLECTION:
var obj = collections.CollectionSchema.new()
obj.sub_type = sub_type
return obj
func decode(decoder: Decoder):
match type:
types.REF:
var obj = sub_type.new()
obj.id = decoder.number()
return obj
types.MAP:
var obj = collections.MapSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.ARRAY:
var obj = collections.ArraySchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.SET:
var obj = collections.SetSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.COLLECTION:
var obj = collections.CollectionSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.STRING:
return decoder.read_utf8()
types.NUMBER:
return decoder.number()
types.BOOLEAN:
return decoder.reader.get_u8() > 0
types.INT8:
return decoder.reader.get_8()
types.UINT8:
return decoder.reader.get_u8()
types.INT16:
return decoder.reader.get_16()
types.UINT16:
return decoder.reader.get_u16()
types.INT32:
return decoder.reader.get_32()
types.UINT32:
return decoder.reader.get_u32()
types.INT64:
return decoder.reader.get_64()
types.UINT64:
return decoder.reader.get_u64()
types.FLOAT32:
return decoder.reader.get_float()
types.FLOAT64:
return decoder.reader.get_double()
_:
assert(true) #,str("Unkown support type:", type))

View File

@@ -0,0 +1 @@
uid://bga0qc00bmx6a

View File

@@ -0,0 +1,42 @@
extends Object
const REF = "ref"
const MAP = "map"
const ARRAY = "array"
const SET = "set"
const COLLECTION = "collection"
const STRING = "string"
const NUMBER = "number"
const BOOLEAN = "boolean"
const INT8 = "int8"
const UINT8 = "uint8"
const INT16 = "int16"
const UINT16 = "uint16"
const INT32 = "int32"
const UINT32 = "uint32"
const INT64 = "int64"
const UINT64 = "uint64"
const FLOAT32 = "float32"
const FLOAT64 = "float64"
static func to_gd_type(type: String) -> int:
match type:
REF:
return TYPE_OBJECT
MAP:
return TYPE_OBJECT
ARRAY:
return TYPE_OBJECT
SET:
return TYPE_OBJECT
COLLECTION:
return TYPE_OBJECT
STRING:
return TYPE_STRING
NUMBER, FLOAT32, FLOAT64:
return TYPE_FLOAT
BOOLEAN:
return TYPE_BOOL
INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64:
return TYPE_INT
return TYPE_NIL

View File

@@ -0,0 +1 @@
uid://cgnlylx00jd6k

View File

@@ -0,0 +1,7 @@
[plugin]
name="godot-colyseus"
description=""
author="gsioteam"
version="0.1.0"
script="init.gd"

7
default_env.tres Normal file
View File

@@ -0,0 +1,7 @@
[gd_resource type="Environment" load_steps=2 format=2]
[sub_resource type="Sky" id=1]
[resource]
background_mode = 2
background_sky = SubResource( 1 )

1
icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
icon.svg.import Normal file
View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d2437bvwtxh8o"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

20
project.godot Normal file
View File

@@ -0,0 +1,20 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="airplane mode"
run/main_scene="uid://dqpkgbu4i5nhe"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
Network="*res://scripts/network.gd"

6
scenes/HUD.tscn Normal file
View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bt0w3qm5krugw"]
[ext_resource type="Script" uid="uid://csqjx18o6oblo" path="res://scripts/hud.gd" id="1"]
[node name="HUD" type="CanvasLayer"]
script = ExtResource("1")

6
scenes/HotasMapper.tscn Normal file
View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=1 format=3]
[ext_resource type="Script" path="res://scripts/hotas_mapper.gd" id="1"]
[node name="HotasMapper" type="CanvasLayer"]
script = ExtResource("1")

31
scenes/Jet.tscn Normal file
View File

@@ -0,0 +1,31 @@
[gd_scene load_steps=5 format=3 uid="uid://bgns8vvuehatu"]
[ext_resource type="Script" uid="uid://bs6rfdeujndbp" path="res://scripts/jet_controller.gd" id="1"]
[ext_resource type="Script" uid="uid://3ierf3uelmp8" path="res://scripts/vector_line_3d.gd" id="2"]
[sub_resource type="BoxMesh" id="1"]
size = Vector3(2, 0.6, 6)
[sub_resource type="BoxShape3D" id="2"]
size = Vector3(2, 0.6, 6)
[node name="Jet" type="RigidBody3D"]
mass = 1000.0
gravity_scale = 0.0
can_sleep = false
linear_damp = 0.05
angular_damp = 0.1
script = ExtResource("1")
[node name="Mesh" type="MeshInstance3D" parent="."]
mesh = SubResource("1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("2")
[node name="DisplacementVector" type="MeshInstance3D" parent="."]
script = ExtResource("2")
[node name="ChaseCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.995004, 0.0998334, 0, -0.0998334, 0.995004, 0, 2, 10)
current = true

44
scenes/Main.tscn Normal file
View File

@@ -0,0 +1,44 @@
[gd_scene load_steps=8 format=3 uid="uid://cck7qvy27rihs"]
[ext_resource type="PackedScene" uid="uid://bgns8vvuehatu" path="res://scenes/Jet.tscn" id="1"]
[ext_resource type="PackedScene" uid="uid://bt0w3qm5krugw" path="res://scenes/HUD.tscn" id="2"]
[ext_resource type="PackedScene" path="res://scenes/HotasMapper.tscn" id="3"]
[sub_resource type="StandardMaterial3D" id="2"]
albedo_color = Color(0.1, 0.12, 0.14, 1)
[sub_resource type="PlaneMesh" id="1"]
size = Vector2(400, 400)
[sub_resource type="StandardMaterial3D" id="4"]
albedo_color = Color(1, 0.55, 0.1, 1)
roughness = 0.3
[sub_resource type="BoxMesh" id="3"]
size = Vector3(12, 12, 1)
[node name="Main" type="Node3D"]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.696707, -0.462133, 0.548664, 0, 0.764842, 0.644218, -0.717356, -0.448831, 0.532871, 0, 0, 0)
light_energy = 2.5
[node name="Ground" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -5, 0)
material_override = SubResource("2")
mesh = SubResource("1")
[node name="Target" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 12, -120)
material_override = SubResource("4")
mesh = SubResource("3")
[node name="Jet" parent="." instance=ExtResource("1")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 0)
[node name="HUD" parent="." instance=ExtResource("2")]
player_path = NodePath("../Jet")
camera_path = NodePath("../Jet/ChaseCamera")
[node name="HotasMapper" parent="." instance=ExtResource("3")]
jet_path = NodePath("../Jet")

View File

@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://dqpkgbu4i5nhe"]
[ext_resource type="Script" uid="uid://df4m8mwaap8kh" path="res://scenes/node.gd" id="1_g1sxi"]
[node name="MultiplayerMain" type="Node3D"]
[node name="Node" type="Node" parent="."]
script = ExtResource("1_g1sxi")

View File

@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://dqpkgbu4i5nhe"]
[ext_resource type="Script" uid="uid://df4m8mwaap8kh" path="res://scenes/node.gd" id="1_g1sxi"]
[node name="MultiplayerMain" type="Node3D"]
[node name="Node" type="Node" parent="."]
script = ExtResource("1_g1sxi")

5
scenes/node.gd Normal file
View File

@@ -0,0 +1,5 @@
extends Node
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and event.keycode == KEY_C:
Network.connect_and_join("ws://prospera:2567", "my_room")

1
scenes/node.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://df4m8mwaap8kh

315
scripts/hotas_mapper.gd Normal file
View File

@@ -0,0 +1,315 @@
extends CanvasLayer
@export var jet_path: NodePath
@export var config_path: String = "user://hotas_mapping.cfg"
@export var axis_capture_threshold: float = 0.35
@export var toggle_action: String = "toggle_hotas_mapper"
@export var panel_position: Vector2 = Vector2(12, 12)
@export var panel_size: Vector2 = Vector2(520, 360)
const AXIS_CONFIG = [
{
"name": "Roll",
"axis_prop": "roll_axis",
"invert_prop": "roll_invert",
"device_prop": "roll_device_id",
},
{
"name": "Pitch",
"axis_prop": "pitch_axis",
"invert_prop": "pitch_invert",
"device_prop": "pitch_device_id",
},
{
"name": "Yaw",
"axis_prop": "yaw_axis",
"invert_prop": "yaw_invert",
"device_prop": "yaw_device_id",
},
{
"name": "Throttle",
"axis_prop": "throttle_axis",
"invert_prop": "throttle_invert",
"signed_prop": "throttle_signed",
"device_prop": "throttle_device_id",
},
{
"name": "Strafe",
"axis_prop": "strafe_axis",
"invert_prop": "strafe_invert",
"device_prop": "strafe_device_id",
},
{
"name": "Lift",
"axis_prop": "lift_axis",
"invert_prop": "lift_invert",
"device_prop": "lift_device_id",
},
]
var _jet: Node
var _panel: PanelContainer
var _status_label: Label
var _device_label: Label
var _rows := {}
var _listening_axis: String = ""
func _ready() -> void:
_jet = get_node_or_null(jet_path)
_build_ui()
_load_config()
_refresh_labels()
func _unhandled_input(event: InputEvent) -> void:
if _is_toggle_event(event):
visible = not visible
if not visible:
_listening_axis = ""
_refresh_labels()
get_viewport().set_input_as_handled()
func _input(event: InputEvent) -> void:
if not visible:
return
if _listening_axis.is_empty():
return
if event is InputEventJoypadMotion:
if abs(event.axis_value) < axis_capture_threshold:
return
_apply_axis_mapping(_listening_axis, event.axis, event.device)
_listening_axis = ""
_status_label.text = "Axis captured."
_refresh_labels()
func _build_ui() -> void:
_panel = PanelContainer.new()
_panel.name = "HotasMapper"
_panel.anchor_left = 0.0
_panel.anchor_top = 0.0
_panel.anchor_right = 0.0
_panel.anchor_bottom = 0.0
_panel.offset_left = panel_position.x
_panel.offset_top = panel_position.y
_panel.offset_right = panel_position.x + panel_size.x
_panel.offset_bottom = panel_position.y + panel_size.y
add_child(_panel)
var root := VBoxContainer.new()
root.size_flags_vertical = Control.SIZE_EXPAND_FILL
root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
root.add_theme_constant_override("separation", 8)
_panel.add_child(root)
var header := HBoxContainer.new()
header.size_flags_horizontal = Control.SIZE_EXPAND_FILL
root.add_child(header)
var title := Label.new()
title.text = "HOTAS Axis Mapper"
header.add_child(title)
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(spacer)
var close_button := Button.new()
close_button.text = "Close"
close_button.pressed.connect(_on_close_pressed)
header.add_child(close_button)
_device_label = Label.new()
_device_label.text = _get_device_text()
root.add_child(_device_label)
_status_label = Label.new()
_status_label.text = "Click Map, then move the desired axis. Toggle: %s" % _get_toggle_hint()
root.add_child(_status_label)
for config in AXIS_CONFIG:
root.add_child(_build_axis_row(config))
func _build_axis_row(config: Dictionary) -> Control:
var row := HBoxContainer.new()
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_theme_constant_override("separation", 8)
var name_label := Label.new()
name_label.text = config.name
name_label.custom_minimum_size = Vector2(120, 0)
row.add_child(name_label)
var map_button := Button.new()
map_button.text = "Map"
map_button.pressed.connect(_on_map_pressed.bind(config.axis_prop))
row.add_child(map_button)
var axis_label := Label.new()
axis_label.text = "Axis: ?"
axis_label.custom_minimum_size = Vector2(120, 0)
row.add_child(axis_label)
var device_label := Label.new()
device_label.text = "Dev: ?"
device_label.custom_minimum_size = Vector2(160, 0)
row.add_child(device_label)
var invert_check := CheckBox.new()
invert_check.text = "Invert"
invert_check.toggled.connect(_on_invert_toggled.bind(config.invert_prop))
row.add_child(invert_check)
var signed_check: CheckBox = null
if config.has("signed_prop"):
signed_check = CheckBox.new()
signed_check.text = "Signed"
signed_check.toggled.connect(_on_signed_toggled.bind(config.signed_prop))
row.add_child(signed_check)
_rows[config.axis_prop] = {
"axis_label": axis_label,
"device_label": device_label,
"map_button": map_button,
"invert_check": invert_check,
"signed_check": signed_check,
}
return row
func _on_map_pressed(axis_prop: String) -> void:
if _listening_axis == axis_prop:
_listening_axis = ""
_status_label.text = "Mapping cancelled."
else:
_listening_axis = axis_prop
_status_label.text = "Listening for %s axis..." % axis_prop
_refresh_labels()
func _on_close_pressed() -> void:
_listening_axis = ""
visible = false
func _on_invert_toggled(pressed: bool, invert_prop: String) -> void:
if _jet == null:
return
_jet.set(invert_prop, pressed)
_save_config()
func _on_signed_toggled(pressed: bool, signed_prop: String) -> void:
if _jet == null:
return
_jet.set(signed_prop, pressed)
_save_config()
func _apply_axis_mapping(axis_prop: String, axis_index: int, device_id: int) -> void:
if _jet == null:
return
_jet.set(axis_prop, axis_index)
if device_id >= 0:
var device_prop = _get_device_prop(axis_prop)
if not device_prop.is_empty():
_jet.set(device_prop, device_id)
_jet.set("joy_device_id", device_id)
_save_config()
_device_label.text = _get_device_text()
func _refresh_labels() -> void:
if _jet == null:
return
_device_label.text = _get_device_text()
for config in AXIS_CONFIG:
var axis_prop: String = config.axis_prop
var row = _rows.get(axis_prop, null)
if row == null:
continue
row["axis_label"].text = "Axis: %d" % int(_jet.get(axis_prop))
var device_prop: String = config.device_prop
var device_id: int = int(_jet.get(device_prop))
row["device_label"].text = _get_device_label(device_id)
var invert_prop: String = config.invert_prop
row["invert_check"].set_pressed_no_signal(bool(_jet.get(invert_prop)))
if config.has("signed_prop") and row["signed_check"] != null:
row["signed_check"].set_pressed_no_signal(bool(_jet.get(config.signed_prop)))
var map_button: Button = row["map_button"]
if _listening_axis == axis_prop:
map_button.text = "Listening..."
else:
map_button.text = "Map"
func _load_config() -> void:
if _jet == null:
return
var config := ConfigFile.new()
var err = config.load(config_path)
if err != OK:
return
for entry in AXIS_CONFIG:
var axis_prop: String = entry.axis_prop
if config.has_section_key("axes", axis_prop):
_jet.set(axis_prop, int(config.get_value("axes", axis_prop)))
var invert_prop: String = entry.invert_prop
if config.has_section_key("invert", invert_prop):
_jet.set(invert_prop, bool(config.get_value("invert", invert_prop)))
var device_prop: String = entry.device_prop
if config.has_section_key("devices", device_prop):
_jet.set(device_prop, int(config.get_value("devices", device_prop)))
if entry.has("signed_prop"):
var signed_prop: String = entry.signed_prop
if config.has_section_key("flags", signed_prop):
_jet.set(signed_prop, bool(config.get_value("flags", signed_prop)))
if config.has_section_key("device", "joy_device_id"):
_jet.set("joy_device_id", int(config.get_value("device", "joy_device_id")))
func _save_config() -> void:
if _jet == null:
return
var config := ConfigFile.new()
for entry in AXIS_CONFIG:
var axis_prop: String = entry.axis_prop
config.set_value("axes", axis_prop, int(_jet.get(axis_prop)))
var invert_prop: String = entry.invert_prop
config.set_value("invert", invert_prop, bool(_jet.get(invert_prop)))
var device_prop: String = entry.device_prop
config.set_value("devices", device_prop, int(_jet.get(device_prop)))
if entry.has("signed_prop"):
var signed_prop: String = entry.signed_prop
config.set_value("flags", signed_prop, bool(_jet.get(signed_prop)))
config.set_value("device", "joy_device_id", int(_jet.get("joy_device_id")))
config.save(config_path)
func _get_device_text() -> String:
if _jet == null:
return "Device: none"
var device_id: int = int(_jet.get("joy_device_id"))
if device_id < 0:
var pads = Input.get_connected_joypads()
if pads.is_empty():
return "Device: none"
device_id = pads[0]
var name = Input.get_joy_name(device_id)
return "Device: %s (id %d)" % [name, device_id]
func _get_device_label(device_id: int) -> String:
if device_id < 0:
return "Dev: auto"
var name = Input.get_joy_name(device_id)
if name.is_empty():
return "Dev: %d" % device_id
return "Dev: %s (%d)" % [name, device_id]
func _get_device_prop(axis_prop: String) -> String:
for entry in AXIS_CONFIG:
if entry.axis_prop == axis_prop:
return entry.device_prop
return ""
func _is_toggle_event(event: InputEvent) -> bool:
if InputMap.has_action(toggle_action):
return event.is_action_pressed(toggle_action)
if event is InputEventKey and event.pressed and not event.echo:
return event.keycode == KEY_F1
return false
func _get_toggle_hint() -> String:
if InputMap.has_action(toggle_action):
return toggle_action
return "F1"

View File

@@ -0,0 +1 @@
uid://bj8dpyx6m80px

130
scripts/hud.gd Normal file
View File

@@ -0,0 +1,130 @@
extends CanvasLayer
@export var player_path: NodePath
@export var camera_path: NodePath
@export var vector_scale: float = 2.5
@export var max_radius: float = 140.0
@export var gizmo_margin: Vector2 = Vector2(140, 140)
@export var gizmo_axis_length: float = 55.0
@export var gizmo_axis_width: float = 2.0
@export var velocity_line_width: float = 3.0
@export var show_axis_debug: bool = true
var _velocity_line: Line2D
var _axis_x: Line2D
var _axis_y: Line2D
var _axis_z: Line2D
var _vector_label: Label
var _axis_label: Label
var _player: RigidBody3D
var _camera: Camera3D
func _ready() -> void:
_velocity_line = _get_or_create_velocity_line()
_axis_x = _get_or_create_axis_line("AxisX", Color(0.95, 0.2, 0.2, 0.9))
_axis_y = _get_or_create_axis_line("AxisY", Color(0.2, 0.9, 0.4, 0.9))
_axis_z = _get_or_create_axis_line("AxisZ", Color(0.2, 0.5, 1.0, 0.9))
_vector_label = _get_or_create_vector_label()
_axis_label = _get_or_create_axis_label()
_player = get_node_or_null(player_path)
_camera = get_node_or_null(camera_path)
_axis_label.visible = show_axis_debug
func _process(_delta: float) -> void:
if _player == null or _camera == null:
return
var velocity = _player.linear_velocity
var cam_basis = _camera.global_transform.basis
var local_velocity = cam_basis.inverse() * velocity
var arrow = Vector2(local_velocity.x, -local_velocity.y) * vector_scale
if arrow.length() > max_radius:
arrow = arrow.normalized() * max_radius
var viewport_size = get_viewport().get_visible_rect().size
var center = Vector2(
viewport_size.x - gizmo_margin.x,
viewport_size.y - gizmo_margin.y
)
_velocity_line.points = PackedVector2Array([center, center + arrow])
var player_basis = _player.global_transform.basis
var rel_basis = cam_basis.inverse() * player_basis
_axis_x.points = PackedVector2Array([
center,
center + Vector2(rel_basis.x.x, -rel_basis.x.y) * gizmo_axis_length
])
_axis_y.points = PackedVector2Array([
center,
center + Vector2(rel_basis.y.x, -rel_basis.y.y) * gizmo_axis_length
])
_axis_z.points = PackedVector2Array([
center,
center + Vector2(rel_basis.z.x, -rel_basis.z.y) * gizmo_axis_length
])
_vector_label.text = "Velocity: (%.1f, %.1f, %.1f) m/s | Speed: %.1f" % [
velocity.x,
velocity.y,
velocity.z,
velocity.length(),
]
if show_axis_debug and _axis_label != null:
if _player.has_method("get_axis_debug_text"):
_axis_label.text = _player.get_axis_debug_text()
func _get_or_create_velocity_line() -> Line2D:
var existing: Line2D = get_node_or_null("VelocityLine")
if existing != null:
return existing
var line := Line2D.new()
line.name = "VelocityLine"
line.width = velocity_line_width
line.default_color = Color(1, 0.7, 0.2, 0.9)
line.antialiased = true
add_child(line)
return line
func _get_or_create_axis_line(name: String, color: Color) -> Line2D:
var existing: Line2D = get_node_or_null(name)
if existing != null:
return existing
var line := Line2D.new()
line.name = name
line.width = gizmo_axis_width
line.default_color = color
line.antialiased = true
add_child(line)
return line
func _get_or_create_vector_label() -> Label:
var existing: Label = get_node_or_null("VectorLabel")
if existing != null:
return existing
var label := Label.new()
label.name = "VectorLabel"
label.text = "Velocity:"
label.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
label.offset_left = 12.0
label.offset_top = 12.0
label.offset_right = 520.0
label.offset_bottom = 40.0
add_child(label)
return label
func _get_or_create_axis_label() -> Label:
var existing: Label = get_node_or_null("AxisLabel")
if existing != null:
return existing
var label := Label.new()
label.name = "AxisLabel"
label.text = "Axis:"
label.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT)
label.offset_left = 12.0
label.offset_top = -60.0
label.offset_right = 520.0
label.offset_bottom = -12.0
add_child(label)
return label

1
scripts/hud.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://csqjx18o6oblo

194
scripts/jet_controller.gd Normal file
View File

@@ -0,0 +1,194 @@
extends RigidBody3D
@export var joy_device_id: int = -1
@export var pitch_axis: int = 1
@export var pitch_invert: bool = true
@export var pitch_device_id: int = -1
@export var roll_axis: int = 0
@export var roll_invert: bool = false
@export var roll_device_id: int = -1
@export var yaw_axis: int = 2
@export var yaw_invert: bool = false
@export var yaw_device_id: int = -1
@export var throttle_axis: int = 3
@export var throttle_invert: bool = true
@export var throttle_signed: bool = true
@export var throttle_device_id: int = -1
@export var strafe_axis: int = 4
@export var strafe_invert: bool = false
@export var strafe_device_id: int = -1
@export var lift_axis: int = 5
@export var lift_invert: bool = false
@export var lift_device_id: int = -1
@export var stick_deadzone: float = 0.08
@export var throttle_deadzone: float = 0.02
@export var max_thrust: float = 7500.0
@export var strafe_thrust: float = 3500.0
@export var lift_thrust: float = 3500.0
@export var torque_strength: float = 2000.0
@export var throttle_response: float = 3.0
@export var stick_response: float = 6.0
@export var stick_expo: float = 1.6
@export var max_speed: float = 180.0
@export var max_angular_speed: float = 2.6
@export var vector_line_path: NodePath = NodePath("DisplacementVector")
@export var vector_scale: float = 0.05
@export var max_vector_length: float = 20.0
var _device_id: int = -1
var _current_throttle: float = 0.0
var _vector_line: Node
var _axis_state := {
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0,
"throttle": 0.0,
"strafe": 0.0,
"lift": 0.0,
}
var _axis_smoothed := {
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0,
"strafe": 0.0,
"lift": 0.0,
}
func _ready() -> void:
_vector_line = get_node_or_null(vector_line_path)
_device_id = _resolve_joypad()
if _device_id == -1:
push_warning("No HOTAS detected. Connect the X56 or set joy_device_id.")
func _physics_process(delta: float) -> void:
_device_id = _resolve_joypad()
if _device_id == -1:
_update_vector_line()
return
var roll = _shape_axis(_read_axis(_resolve_axis_device(roll_device_id), roll_axis, stick_deadzone, roll_invert))
var pitch = _shape_axis(_read_axis(_resolve_axis_device(pitch_device_id), pitch_axis, stick_deadzone, pitch_invert))
var yaw = _shape_axis(_read_axis(_resolve_axis_device(yaw_device_id), yaw_axis, stick_deadzone, yaw_invert))
var strafe = _shape_axis(_read_axis(_resolve_axis_device(strafe_device_id), strafe_axis, stick_deadzone, strafe_invert))
var lift = _shape_axis(_read_axis(_resolve_axis_device(lift_device_id), lift_axis, stick_deadzone, lift_invert))
var raw_throttle = _read_axis(_resolve_axis_device(throttle_device_id), throttle_axis, throttle_deadzone, throttle_invert)
var target_throttle: float
if throttle_signed:
target_throttle = clamp(raw_throttle, -1.0, 1.0)
else:
target_throttle = clamp((raw_throttle + 1.0) * 0.5, 0.0, 1.0)
_current_throttle = lerp(
_current_throttle,
target_throttle,
clamp(throttle_response * delta, 0.0, 1.0)
)
roll = _smooth_axis("roll", roll, delta)
pitch = _smooth_axis("pitch", pitch, delta)
yaw = _smooth_axis("yaw", yaw, delta)
strafe = _smooth_axis("strafe", strafe, delta)
lift = _smooth_axis("lift", lift, delta)
_axis_state["roll"] = roll
_axis_state["pitch"] = pitch
_axis_state["yaw"] = yaw
_axis_state["throttle"] = _current_throttle
_axis_state["strafe"] = strafe
_axis_state["lift"] = lift
var basis = global_transform.basis
var forward = -basis.z
var right = basis.x
var up = basis.y
var thrust_force = forward * (_current_throttle * max_thrust)
var strafe_force = right * (strafe * strafe_thrust)
var lift_force = up * (lift * lift_thrust)
apply_central_force(thrust_force + strafe_force + lift_force)
var local_angular = basis.inverse() * angular_velocity
var desired_angular = Vector3(pitch, yaw, -roll) * max_angular_speed
var torque_local = (desired_angular - local_angular) * torque_strength
apply_torque(basis * torque_local)
if max_speed > 0.0 and linear_velocity.length() > max_speed:
linear_velocity = linear_velocity.limit_length(max_speed)
if max_angular_speed > 0.0 and angular_velocity.length() > max_angular_speed:
angular_velocity = angular_velocity.limit_length(max_angular_speed)
_update_vector_line()
func get_axis_debug_text() -> String:
if _device_id == -1:
return "No HOTAS detected"
return "Roll: %.2f (d%d) Pitch: %.2f (d%d) Yaw: %.2f (d%d)\nThrottle: %.2f (d%d) Strafe: %.2f (d%d) Lift: %.2f (d%d)" % [
_axis_state["roll"],
_resolve_axis_device(roll_device_id),
_axis_state["pitch"],
_resolve_axis_device(pitch_device_id),
_axis_state["yaw"],
_resolve_axis_device(yaw_device_id),
_axis_state["throttle"],
_resolve_axis_device(throttle_device_id),
_axis_state["strafe"],
_resolve_axis_device(strafe_device_id),
_axis_state["lift"],
_resolve_axis_device(lift_device_id),
]
func get_joypad_id() -> int:
return _device_id
func _resolve_joypad() -> int:
if joy_device_id >= 0:
return joy_device_id
var pads = Input.get_connected_joypads()
if pads.is_empty():
return -1
return pads[0]
func _resolve_axis_device(axis_device_id: int) -> int:
if axis_device_id >= 0:
return axis_device_id
return _device_id
func _read_axis(device_id: int, axis: int, deadzone: float, invert: bool) -> float:
if axis < 0 or device_id < 0:
return 0.0
var value = Input.get_joy_axis(device_id, axis)
if invert:
value = -value
var magnitude = abs(value)
if magnitude <= deadzone:
return 0.0
var scaled = (magnitude - deadzone) / (1.0 - deadzone)
return scaled * sign(value)
func _shape_axis(value: float) -> float:
if stick_expo <= 1.0:
return value
return sign(value) * pow(abs(value), stick_expo)
func _smooth_axis(name: String, value: float, delta: float) -> float:
if stick_response <= 0.0:
_axis_smoothed[name] = value
return value
var t = clamp(stick_response * delta, 0.0, 1.0)
_axis_smoothed[name] = lerp(_axis_smoothed[name], value, t)
return _axis_smoothed[name]
func _update_vector_line() -> void:
if _vector_line == null:
return
if not _vector_line.has_method("set_vector"):
return
var local_velocity = global_transform.basis.inverse() * linear_velocity
var clamped = local_velocity.limit_length(max_vector_length)
_vector_line.set_vector(clamped * vector_scale)

View File

@@ -0,0 +1 @@
uid://bs6rfdeujndbp

109
scripts/network.gd Normal file
View File

@@ -0,0 +1,109 @@
# res://scripts/network.gd
extends Node
signal connected(room_id: String)
signal disconnected(reason: String)
signal state_changed()
signal message_received(type: String, payload: Variant)
signal error(msg: String)
const colyseus := preload("res://addons/godot_colyseus/lib/colyseus.gd")
const RoomState := preload("res://scripts/room_state.gd")
const Room := preload("res://addons/godot_colyseus/lib/room.gd")
var client: colyseus.Client
var room: Room
var state: RoomState
var _connecting := false
var _endpoint := ""
#func is_connected() -> bool:
#return room != null
func connect_and_join(endpoint: String, room_name: String = "my_room") -> void:
if _connecting:
return
if room != null:
emit_signal("error", "Already connected.")
return
_connecting = true
_endpoint = endpoint
client = colyseus.Client.new(endpoint)
var promise = client.join_or_create(RoomState, room_name)
await promise.completed
_connecting = false
if promise.get_state() == promise.State.Failed:
var msg := "Join failed: %s" % str(promise.get_error())
emit_signal("error", msg)
return
# old addon: result is a property
room = promise.result
if room == null:
emit_signal("error", "Join succeeded but room is null (promise.result).")
return
state = room.get_state() as RoomState
if state == null:
emit_signal("error", "Joined but could not cast state to RoomState.")
return
# --- wire listeners ---
room.on_state_change.on(Callable(self, "_on_state_change"))
# if addon supports on_leave / on_error, hook them too (safe-guarded)
if "on_leave" in room:
room.on_leave.on(Callable(self, "_on_room_left"))
if "on_error" in room:
room.on_error.on(Callable(self, "_on_room_error"))
emit_signal("connected", room.room_id)
func leave() -> void:
if room == null:
return
# some addons use room.leave(), some room.disconnect()
if room.has_method("leave"):
room.leave()
elif room.has_method("disconnect"):
pass
#room.disconnect()
_cleanup("left")
func send(type: String, payload: Variant = null) -> void:
if room == null:
emit_signal("error", "Cannot send, not connected.")
return
room.send(type, payload)
func listen_message(type: String) -> void:
if room == null:
emit_signal("error", "Cannot listen, not connected.")
return
room.on_message(type).on(Callable(self, "_on_message").bind(type))
func reconnect(room_name: String = "my_room") -> void:
_cleanup("reconnect")
await connect_and_join(_endpoint, room_name)
func _on_state_change(_new_state = null) -> void:
emit_signal("state_changed")
func _on_message(payload: Variant, type: String) -> void:
emit_signal("message_received", type, payload)
func _on_room_left(code = null) -> void:
_cleanup("room_left %s" % str(code))
func _on_room_error(code = null, message = null) -> void:
_cleanup("room_error %s %s" % [str(code), str(message)])
func _cleanup(reason: String) -> void:
room = null
state = null
emit_signal("disconnected", reason)

1
scripts/network.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://d0amrs41uhwql

10
scripts/room_state.gd Normal file
View File

@@ -0,0 +1,10 @@
# res://scripts/room_state.gd
extends "res://addons/godot_colyseus/lib/schema.gd"
class_name RoomState
var mySynchronizedProperty: String = "Hello world"
static func define_fields():
return [
Field.new("mySynchronizedProperty", Types.STRING),
]

View File

@@ -0,0 +1 @@
uid://dlh5cvfm6xapu

22
scripts/vector_line_3d.gd Normal file
View File

@@ -0,0 +1,22 @@
extends MeshInstance3D
@export var color: Color = Color(0.1, 0.8, 1.0, 0.9)
var _mesh: ImmediateMesh
var _material: StandardMaterial3D
func _ready() -> void:
_mesh = ImmediateMesh.new()
mesh = _mesh
_material = StandardMaterial3D.new()
_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_material.albedo_color = color
func set_vector(end_point: Vector3) -> void:
if _mesh == null:
return
_mesh.clear_surfaces()
_mesh.surface_begin(Mesh.PRIMITIVE_LINES, _material)
_mesh.surface_add_vertex(Vector3.ZERO)
_mesh.surface_add_vertex(end_point)
_mesh.surface_end()

View File

@@ -0,0 +1 @@
uid://3ierf3uelmp8