Initial commit for testing/fixing a deprecated godot-colyseus addon
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
BIN
addons/godot_colyseus/demo/blur.png
Normal file
BIN
addons/godot_colyseus/demo/blur.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
34
addons/godot_colyseus/demo/blur.png.import
Normal file
34
addons/godot_colyseus/demo/blur.png.import
Normal 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
|
||||
8
addons/godot_colyseus/demo/char.tscn
Normal file
8
addons/godot_colyseus/demo/char.tscn
Normal 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 )
|
||||
32
addons/godot_colyseus/demo/chat.gd
Normal file
32
addons/godot_colyseus/demo/chat.gd
Normal 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 = ""
|
||||
1
addons/godot_colyseus/demo/chat.gd.uid
Normal file
1
addons/godot_colyseus/demo/chat.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cj7jhyoe2eluq
|
||||
48
addons/godot_colyseus/demo/chat.tscn
Normal file
48
addons/godot_colyseus/demo/chat.tscn
Normal 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"]
|
||||
27
addons/godot_colyseus/demo/control.tscn
Normal file
27
addons/godot_colyseus/demo/control.tscn
Normal 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")
|
||||
68
addons/godot_colyseus/demo/main.gd
Normal file
68
addons/godot_colyseus/demo/main.gd
Normal 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 });
|
||||
1
addons/godot_colyseus/demo/main.gd.uid
Normal file
1
addons/godot_colyseus/demo/main.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ddjkvt52o2ggh
|
||||
6
addons/godot_colyseus/demo/main.tscn
Normal file
6
addons/godot_colyseus/demo/main.tscn
Normal 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")
|
||||
10
addons/godot_colyseus/init.gd
Normal file
10
addons/godot_colyseus/init.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
pass
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
pass
|
||||
1
addons/godot_colyseus/init.gd.uid
Normal file
1
addons/godot_colyseus/init.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b13nsgfmclyf6
|
||||
132
addons/godot_colyseus/lib/client.gd
Normal file
132
addons/godot_colyseus/lib/client.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/client.gd.uid
Normal file
1
addons/godot_colyseus/lib/client.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://mutuixu84pvj
|
||||
180
addons/godot_colyseus/lib/collections.gd
Normal file
180
addons/godot_colyseus/lib/collections.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/collections.gd.uid
Normal file
1
addons/godot_colyseus/lib/collections.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ciu14viqsjpjg
|
||||
32
addons/godot_colyseus/lib/colyseus.gd
Normal file
32
addons/godot_colyseus/lib/colyseus.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/colyseus.gd.uid
Normal file
1
addons/godot_colyseus/lib/colyseus.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dni1bp1qice02
|
||||
68
addons/godot_colyseus/lib/decoder.gd
Normal file
68
addons/godot_colyseus/lib/decoder.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/decoder.gd.uid
Normal file
1
addons/godot_colyseus/lib/decoder.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://btjicixj57gl5
|
||||
67
addons/godot_colyseus/lib/encoder.gd
Normal file
67
addons/godot_colyseus/lib/encoder.gd
Normal 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)
|
||||
1
addons/godot_colyseus/lib/encoder.gd.uid
Normal file
1
addons/godot_colyseus/lib/encoder.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://drl5m0b1yqxig
|
||||
23
addons/godot_colyseus/lib/frame_runner.gd
Normal file
23
addons/godot_colyseus/lib/frame_runner.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/frame_runner.gd.uid
Normal file
1
addons/godot_colyseus/lib/frame_runner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b2ng861vtoeih
|
||||
229
addons/godot_colyseus/lib/http.gd
Normal file
229
addons/godot_colyseus/lib/http.gd
Normal 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])
|
||||
1
addons/godot_colyseus/lib/http.gd.uid
Normal file
1
addons/godot_colyseus/lib/http.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://co8kfm0yu3vui
|
||||
62
addons/godot_colyseus/lib/listener.gd
Normal file
62
addons/godot_colyseus/lib/listener.gd
Normal 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)
|
||||
1
addons/godot_colyseus/lib/listener.gd.uid
Normal file
1
addons/godot_colyseus/lib/listener.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dtgvuxvgbn08y
|
||||
487
addons/godot_colyseus/lib/msgpack.gd
Normal file
487
addons/godot_colyseus/lib/msgpack.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/msgpack.gd.uid
Normal file
1
addons/godot_colyseus/lib/msgpack.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dlclaop7pocqd
|
||||
9
addons/godot_colyseus/lib/operations.gd
Normal file
9
addons/godot_colyseus/lib/operations.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/operations.gd.uid
Normal file
1
addons/godot_colyseus/lib/operations.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bucnjhgajvk8x
|
||||
107
addons/godot_colyseus/lib/promises.gd
Normal file
107
addons/godot_colyseus/lib/promises.gd
Normal 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)
|
||||
1
addons/godot_colyseus/lib/promises.gd.uid
Normal file
1
addons/godot_colyseus/lib/promises.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d4lhtnts1yq1k
|
||||
198
addons/godot_colyseus/lib/room.gd
Normal file
198
addons/godot_colyseus/lib/room.gd
Normal 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()
|
||||
1
addons/godot_colyseus/lib/room.gd.uid
Normal file
1
addons/godot_colyseus/lib/room.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://jpi6ohybugst
|
||||
18
addons/godot_colyseus/lib/room_info.gd
Normal file
18
addons/godot_colyseus/lib/room_info.gd
Normal 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')
|
||||
1
addons/godot_colyseus/lib/room_info.gd.uid
Normal file
1
addons/godot_colyseus/lib/room_info.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b8wn1vytiodw3
|
||||
308
addons/godot_colyseus/lib/schema.gd
Normal file
308
addons/godot_colyseus/lib/schema.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/schema.gd.uid
Normal file
1
addons/godot_colyseus/lib/schema.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vujiy070i53c
|
||||
54
addons/godot_colyseus/lib/schema_interface.gd
Normal file
54
addons/godot_colyseus/lib/schema_interface.gd
Normal 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>"
|
||||
1
addons/godot_colyseus/lib/schema_interface.gd.uid
Normal file
1
addons/godot_colyseus/lib/schema_interface.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://7hqsu5vttoy2
|
||||
113
addons/godot_colyseus/lib/serializer.gd
Normal file
113
addons/godot_colyseus/lib/serializer.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/serializer.gd.uid
Normal file
1
addons/godot_colyseus/lib/serializer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dsp61m3okjbm
|
||||
97
addons/godot_colyseus/lib/type_info.gd
Normal file
97
addons/godot_colyseus/lib/type_info.gd
Normal 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))
|
||||
1
addons/godot_colyseus/lib/type_info.gd.uid
Normal file
1
addons/godot_colyseus/lib/type_info.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bga0qc00bmx6a
|
||||
42
addons/godot_colyseus/lib/types.gd
Normal file
42
addons/godot_colyseus/lib/types.gd
Normal 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
|
||||
1
addons/godot_colyseus/lib/types.gd.uid
Normal file
1
addons/godot_colyseus/lib/types.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cgnlylx00jd6k
|
||||
7
addons/godot_colyseus/plugin.cfg
Normal file
7
addons/godot_colyseus/plugin.cfg
Normal 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
7
default_env.tres
Normal 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
1
icon.svg
Normal 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
37
icon.svg.import
Normal 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
20
project.godot
Normal 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
6
scenes/HUD.tscn
Normal 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
6
scenes/HotasMapper.tscn
Normal 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
31
scenes/Jet.tscn
Normal 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
44
scenes/Main.tscn
Normal 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")
|
||||
8
scenes/MultiplayerMain.tscn
Normal file
8
scenes/MultiplayerMain.tscn
Normal 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")
|
||||
8
scenes/MultiplayerMain.tscn13588459406.tmp
Normal file
8
scenes/MultiplayerMain.tscn13588459406.tmp
Normal 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
5
scenes/node.gd
Normal 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
1
scenes/node.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://df4m8mwaap8kh
|
||||
315
scripts/hotas_mapper.gd
Normal file
315
scripts/hotas_mapper.gd
Normal 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"
|
||||
1
scripts/hotas_mapper.gd.uid
Normal file
1
scripts/hotas_mapper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bj8dpyx6m80px
|
||||
130
scripts/hud.gd
Normal file
130
scripts/hud.gd
Normal 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
1
scripts/hud.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csqjx18o6oblo
|
||||
194
scripts/jet_controller.gd
Normal file
194
scripts/jet_controller.gd
Normal 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)
|
||||
1
scripts/jet_controller.gd.uid
Normal file
1
scripts/jet_controller.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bs6rfdeujndbp
|
||||
109
scripts/network.gd
Normal file
109
scripts/network.gd
Normal 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
1
scripts/network.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d0amrs41uhwql
|
||||
10
scripts/room_state.gd
Normal file
10
scripts/room_state.gd
Normal 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),
|
||||
]
|
||||
1
scripts/room_state.gd.uid
Normal file
1
scripts/room_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dlh5cvfm6xapu
|
||||
22
scripts/vector_line_3d.gd
Normal file
22
scripts/vector_line_3d.gd
Normal 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()
|
||||
1
scripts/vector_line_3d.gd.uid
Normal file
1
scripts/vector_line_3d.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3ierf3uelmp8
|
||||
Reference in New Issue
Block a user