Add WebGL and GOTM support
This commit is contained in:
parent
d021b3ab3b
commit
5ca3c191cd
20 changed files with 1545 additions and 80 deletions
|
@ -14,13 +14,13 @@ void fragment() {
|
||||||
if (col.r/col.g > 2.) {
|
if (col.r/col.g > 2.) {
|
||||||
if (length(cable_color) == 0.) {
|
if (length(cable_color) == 0.) {
|
||||||
if (UV.y > 0.6 && UV.y < 0.85) {
|
if (UV.y > 0.6 && UV.y < 0.85) {
|
||||||
if (int(UV.x * 200.) % 12 > 4) {
|
if (float(int(UV.x * 200.) % 12) > 4.) {
|
||||||
col.rgb = vec3(0.94, 0.08, 0.08) * length(col.rgb);
|
col.rgb = vec3(0.94, 0.08, 0.08) * length(col.rgb);
|
||||||
} else {
|
} else {
|
||||||
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (int(UV.y * 200.) % 12 > 4) {
|
if (float(int(UV.y * 200.) % 12) > 4.) {
|
||||||
col.rgb = vec3(0.94, 0.08, 0.08) * length(col.rgb);
|
col.rgb = vec3(0.94, 0.08, 0.08) * length(col.rgb);
|
||||||
} else {
|
} else {
|
||||||
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
||||||
|
@ -32,7 +32,6 @@ void fragment() {
|
||||||
}
|
}
|
||||||
COLOR = col;
|
COLOR = col;
|
||||||
}"
|
}"
|
||||||
custom_defines = ""
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id=2]
|
[sub_resource type="ShaderMaterial" id=2]
|
||||||
shader = SubResource( 1 )
|
shader = SubResource( 1 )
|
||||||
|
|
|
@ -4,6 +4,7 @@ var thread
|
||||||
var mutex
|
var mutex
|
||||||
var sem
|
var sem
|
||||||
|
|
||||||
|
var gotm_mode = false
|
||||||
var time_max = 100 # Milliseconds.
|
var time_max = 100 # Milliseconds.
|
||||||
|
|
||||||
var queue = []
|
var queue = []
|
||||||
|
@ -58,6 +59,8 @@ func cancel_resource(path):
|
||||||
|
|
||||||
|
|
||||||
func get_progress(path):
|
func get_progress(path):
|
||||||
|
if gotm_mode:
|
||||||
|
return 1.0
|
||||||
_lock("get_progress")
|
_lock("get_progress")
|
||||||
var ret = -1
|
var ret = -1
|
||||||
if path in pending:
|
if path in pending:
|
||||||
|
@ -70,6 +73,8 @@ func get_progress(path):
|
||||||
|
|
||||||
|
|
||||||
func is_ready(path):
|
func is_ready(path):
|
||||||
|
if gotm_mode:
|
||||||
|
return true
|
||||||
var ret
|
var ret
|
||||||
_lock("is_ready")
|
_lock("is_ready")
|
||||||
if path in pending:
|
if path in pending:
|
||||||
|
@ -81,6 +86,8 @@ func is_ready(path):
|
||||||
|
|
||||||
|
|
||||||
func _wait_for_resource(res, path):
|
func _wait_for_resource(res, path):
|
||||||
|
if gotm_mode:
|
||||||
|
return get_resource(path)
|
||||||
_unlock("wait_for_resource")
|
_unlock("wait_for_resource")
|
||||||
while true:
|
while true:
|
||||||
VisualServer.sync()
|
VisualServer.sync()
|
||||||
|
@ -92,6 +99,8 @@ func _wait_for_resource(res, path):
|
||||||
|
|
||||||
|
|
||||||
func get_resource(path):
|
func get_resource(path):
|
||||||
|
if gotm_mode:
|
||||||
|
return load(path)
|
||||||
_lock("get_resource")
|
_lock("get_resource")
|
||||||
if path in pending:
|
if path in pending:
|
||||||
if pending[path] is ResourceInteractiveLoader:
|
if pending[path] is ResourceInteractiveLoader:
|
||||||
|
@ -140,7 +149,9 @@ func thread_func(_u):
|
||||||
thread_process()
|
thread_process()
|
||||||
|
|
||||||
|
|
||||||
func start():
|
func start(gotm: bool):
|
||||||
|
gotm_mode = gotm
|
||||||
|
if not gotm:
|
||||||
mutex = Mutex.new()
|
mutex = Mutex.new()
|
||||||
sem = Semaphore.new()
|
sem = Semaphore.new()
|
||||||
thread = Thread.new()
|
thread = Thread.new()
|
||||||
|
|
|
@ -15,12 +15,14 @@ const SYSTEMS_UPDATE_INTERVAL = 10
|
||||||
const MASTER_SERVER_ADDR = "fgms.zyg.ovh"
|
const MASTER_SERVER_ADDR = "fgms.zyg.ovh"
|
||||||
const MASTER_SERVER_UDP_PORT = 9434
|
const MASTER_SERVER_UDP_PORT = 9434
|
||||||
const MS_GAME_CODE = "odyssey-0-a1"
|
const MS_GAME_CODE = "odyssey-0-a1"
|
||||||
|
const GOTM_OVERRIDE = false
|
||||||
|
|
||||||
# Master server entry
|
# Master server entry
|
||||||
var ms_active = false
|
var ms_active = false
|
||||||
var ms_key = ""
|
var ms_key = ""
|
||||||
var server_name = ""
|
var server_name = ""
|
||||||
|
|
||||||
|
onready var gotm_mode = Gotm.is_live() or GOTM_OVERRIDE
|
||||||
var hosting = false
|
var hosting = false
|
||||||
|
|
||||||
export var player_name = ""
|
export var player_name = ""
|
||||||
|
@ -32,6 +34,8 @@ onready var scene_manager = $"/root/SceneManager"
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
player_name = "tider-" + str(randi() % 1000)
|
player_name = "tider-" + str(randi() % 1000)
|
||||||
|
if gotm_mode:
|
||||||
|
Gotm.connect("lobby_changed", self, "_lobby_changed")
|
||||||
|
|
||||||
func bind_events():
|
func bind_events():
|
||||||
get_tree().connect("network_peer_connected", self, "_player_connected")
|
get_tree().connect("network_peer_connected", self, "_player_connected")
|
||||||
|
@ -71,6 +75,8 @@ func host():
|
||||||
# Wait just a sec to draw
|
# Wait just a sec to draw
|
||||||
yield(get_tree().create_timer(0.3), "timeout")
|
yield(get_tree().create_timer(0.3), "timeout")
|
||||||
|
|
||||||
|
# Run port forwarding/nat punchthrough if the platform doesn't do it already
|
||||||
|
if not gotm_mode:
|
||||||
print("Running UPNP magicks")
|
print("Running UPNP magicks")
|
||||||
if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS:
|
if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS:
|
||||||
print("UPNP mapping added")
|
print("UPNP mapping added")
|
||||||
|
@ -78,6 +84,14 @@ func host():
|
||||||
push_warning("UPNP magicks fail, punching NAT in the face")
|
push_warning("UPNP magicks fail, punching NAT in the face")
|
||||||
yield(punch_nat(), "completed")
|
yield(punch_nat(), "completed")
|
||||||
|
|
||||||
|
server_name = player_name + "'s server"
|
||||||
|
player_info[1] = { "name": player_name }
|
||||||
|
round_info = { "map": "odyssey" }
|
||||||
|
|
||||||
|
if gotm_mode:
|
||||||
|
# Add to master server before hosting (in GOTM mode)
|
||||||
|
create_ms_entry()
|
||||||
|
|
||||||
bind_events()
|
bind_events()
|
||||||
var peer = NetworkedMultiplayerENet.new()
|
var peer = NetworkedMultiplayerENet.new()
|
||||||
var server_res = peer.create_server(port, MAX_PLAYERS)
|
var server_res = peer.create_server(port, MAX_PLAYERS)
|
||||||
|
@ -91,9 +105,6 @@ func host():
|
||||||
get_tree().network_peer = peer
|
get_tree().network_peer = peer
|
||||||
print("Hosting")
|
print("Hosting")
|
||||||
hosting = true
|
hosting = true
|
||||||
server_name = player_name + "'s server"
|
|
||||||
player_info[1] = { "name": player_name }
|
|
||||||
round_info = { "map": "odyssey" }
|
|
||||||
|
|
||||||
scene_manager.loading_text = null
|
scene_manager.loading_text = null
|
||||||
|
|
||||||
|
@ -101,18 +112,27 @@ func host():
|
||||||
"res://Scenes/Maps/odyssey.tscn"
|
"res://Scenes/Maps/odyssey.tscn"
|
||||||
])
|
])
|
||||||
|
|
||||||
# Add to master server
|
if not gotm_mode:
|
||||||
|
# Add to master server after hosting
|
||||||
create_ms_entry()
|
create_ms_entry()
|
||||||
|
|
||||||
func join(addr: String):
|
func join(server):
|
||||||
scene_manager.enter_loader()
|
scene_manager.enter_loader()
|
||||||
scene_manager.loading_text = "Joining server " + str(addr)
|
scene_manager.loading_text = "Joining server"
|
||||||
|
|
||||||
# Wait just a sec to draw
|
# Wait just a sec to draw
|
||||||
yield(get_tree().create_timer(0.3), "timeout")
|
yield(get_tree().create_timer(0.3), "timeout")
|
||||||
|
|
||||||
bind_events()
|
bind_events()
|
||||||
var peer = NetworkedMultiplayerENet.new()
|
var peer = NetworkedMultiplayerENet.new()
|
||||||
|
|
||||||
|
var addr = null
|
||||||
|
if gotm_mode:
|
||||||
|
var success = yield(server.join(), "completed")
|
||||||
|
addr = Gotm.lobby.host.address
|
||||||
|
else:
|
||||||
|
addr = server.addr
|
||||||
|
|
||||||
peer.create_client(addr, SERVER_PORT)
|
peer.create_client(addr, SERVER_PORT)
|
||||||
get_tree().network_peer = peer
|
get_tree().network_peer = peer
|
||||||
print("Connecting to ", addr)
|
print("Connecting to ", addr)
|
||||||
|
@ -172,6 +192,11 @@ func _ms_request(endpoint: String, data):
|
||||||
push_error("An error occurred in the HTTP request.")
|
push_error("An error occurred in the HTTP request.")
|
||||||
|
|
||||||
func ms_get_entries():
|
func ms_get_entries():
|
||||||
|
if gotm_mode:
|
||||||
|
var fetch = GotmLobbyFetch.new()
|
||||||
|
var lobbies = yield(fetch.first(), "completed")
|
||||||
|
emit_signal("ms_updated", "list_games", lobbies)
|
||||||
|
else:
|
||||||
var http_request = HTTPRequest.new()
|
var http_request = HTTPRequest.new()
|
||||||
add_child(http_request)
|
add_child(http_request)
|
||||||
http_request.connect("request_completed", self, "_ms_response", ["list_games"])
|
http_request.connect("request_completed", self, "_ms_response", ["list_games"])
|
||||||
|
@ -191,6 +216,10 @@ func get_game_data():
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_ms_entry():
|
func create_ms_entry():
|
||||||
|
if gotm_mode:
|
||||||
|
Gotm.host_lobby(true)
|
||||||
|
Gotm.lobby.hidden = false
|
||||||
|
else:
|
||||||
_ms_request("new", {
|
_ms_request("new", {
|
||||||
"game_id": MS_GAME_CODE,
|
"game_id": MS_GAME_CODE,
|
||||||
"data": get_game_data()
|
"data": get_game_data()
|
||||||
|
@ -198,6 +227,12 @@ func create_ms_entry():
|
||||||
update_ms_entry()
|
update_ms_entry()
|
||||||
|
|
||||||
func update_ms_entry():
|
func update_ms_entry():
|
||||||
|
if gotm_mode:
|
||||||
|
var data = get_game_data()
|
||||||
|
Gotm.lobby.name = data.name
|
||||||
|
for key in data:
|
||||||
|
Gotm.lobby.set_property(key, data[key])
|
||||||
|
else:
|
||||||
if ms_active:
|
if ms_active:
|
||||||
_ms_request("update", {
|
_ms_request("update", {
|
||||||
"key": ms_key,
|
"key": ms_key,
|
||||||
|
@ -240,3 +275,6 @@ func get_current_map():
|
||||||
return GameWorld.Map.RUNTIME
|
return GameWorld.Map.RUNTIME
|
||||||
_:
|
_:
|
||||||
return GameWorld.Map.EMPTY
|
return GameWorld.Map.EMPTY
|
||||||
|
|
||||||
|
func _lobby_changed():
|
||||||
|
print("Lobby changed ", Gotm.lobby)
|
||||||
|
|
|
@ -6,19 +6,25 @@ var loading_text = null
|
||||||
|
|
||||||
var loader = preload("res://Scenes/Loader.tscn")
|
var loader = preload("res://Scenes/Loader.tscn")
|
||||||
|
|
||||||
|
onready var netgame = $"/root/Multiplayer"
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
queue.start()
|
queue.start(netgame.gotm_mode)
|
||||||
|
|
||||||
func enter_loader() -> void:
|
func enter_loader() -> void:
|
||||||
get_tree().change_scene_to(loader)
|
get_tree().change_scene_to(loader)
|
||||||
|
|
||||||
func load_scene(scene_path: String, dependencies: Array) -> void:
|
func load_scene(scene_path: String, dependencies: Array) -> void:
|
||||||
|
if not netgame.gotm_mode:
|
||||||
target_scene = scene_path
|
target_scene = scene_path
|
||||||
queue.queue_resource(scene_path)
|
queue.queue_resource(scene_path)
|
||||||
for dep in dependencies:
|
for dep in dependencies:
|
||||||
queue.queue_resource(dep)
|
queue.queue_resource(dep)
|
||||||
|
else:
|
||||||
|
get_tree().change_scene(scene_path)
|
||||||
|
|
||||||
func _physics_process(_delta: float) -> void:
|
func _physics_process(_delta: float) -> void:
|
||||||
|
if not netgame.gotm_mode:
|
||||||
if target_scene != null:
|
if target_scene != null:
|
||||||
var remaining = queue.pending.size()
|
var remaining = queue.pending.size()
|
||||||
for path in queue.pending:
|
for path in queue.pending:
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
[ext_resource path="res://Graphics/UI/iosevka-aile-regular.ttf" type="DynamicFontData" id=2]
|
[ext_resource path="res://Graphics/UI/iosevka-aile-regular.ttf" type="DynamicFontData" id=2]
|
||||||
[ext_resource path="res://Scenes/Loader.gd" type="Script" id=3]
|
[ext_resource path="res://Scenes/Loader.gd" type="Script" id=3]
|
||||||
|
|
||||||
[sub_resource type="Shader" id=2]
|
[sub_resource type="Shader" id=1]
|
||||||
code = "shader_type canvas_item;
|
code = "shader_type canvas_item;
|
||||||
|
|
||||||
uniform vec2 tex_size;
|
uniform vec2 tex_size;
|
||||||
|
@ -14,23 +14,22 @@ void fragment() {
|
||||||
vec2 uv_adjusted = (UV - (uv_rect.xy / tex_size)) * (tex_size / uv_rect.zw);
|
vec2 uv_adjusted = (UV - (uv_rect.xy / tex_size)) * (tex_size / uv_rect.zw);
|
||||||
float dist = distance(uv_adjusted, vec2(0.5));
|
float dist = distance(uv_adjusted, vec2(0.5));
|
||||||
if (dist < 0.26) {
|
if (dist < 0.26) {
|
||||||
COLOR = vec4(1);
|
COLOR = vec4(1.);
|
||||||
} else {
|
} else {
|
||||||
COLOR = texture(TEXTURE, UV);
|
COLOR = texture(TEXTURE, UV);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
custom_defines = ""
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id=3]
|
[sub_resource type="ShaderMaterial" id=2]
|
||||||
shader = SubResource( 2 )
|
shader = SubResource( 1 )
|
||||||
shader_param/tex_size = Vector2( 240, 180 )
|
shader_param/tex_size = Vector2( 240, 180 )
|
||||||
shader_param/uv_rect = Plane( 126, 16, 82, 84 )
|
shader_param/uv_rect = Plane( 126, 16, 82, 84 )
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id=1]
|
[sub_resource type="AtlasTexture" id=3]
|
||||||
atlas = ExtResource( 1 )
|
atlas = ExtResource( 1 )
|
||||||
region = Rect2( 126, 16, 82, 84 )
|
region = Rect2( 126, 16, 82, 84 )
|
||||||
|
|
||||||
[sub_resource type="Shader" id=5]
|
[sub_resource type="Shader" id=4]
|
||||||
code = "shader_type canvas_item;
|
code = "shader_type canvas_item;
|
||||||
|
|
||||||
uniform vec2 tex_size;
|
uniform vec2 tex_size;
|
||||||
|
@ -47,23 +46,21 @@ void fragment() {
|
||||||
COLOR = vec4(tex.aaa, 1.-tex.a);
|
COLOR = vec4(tex.aaa, 1.-tex.a);
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
custom_defines = ""
|
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id=6]
|
[sub_resource type="ShaderMaterial" id=5]
|
||||||
shader = SubResource( 5 )
|
shader = SubResource( 4 )
|
||||||
shader_param/tex_size = Vector2( 240, 180 )
|
shader_param/tex_size = Vector2( 240, 180 )
|
||||||
shader_param/uv_rect = Plane( 146, 39, 39, 38 )
|
shader_param/uv_rect = Plane( 146, 39, 39, 38 )
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id=4]
|
[sub_resource type="AtlasTexture" id=6]
|
||||||
atlas = ExtResource( 1 )
|
atlas = ExtResource( 1 )
|
||||||
region = Rect2( 146, 39, 39, 38 )
|
region = Rect2( 146, 39, 39, 38 )
|
||||||
|
|
||||||
[sub_resource type="DynamicFont" id=8]
|
[sub_resource type="DynamicFont" id=7]
|
||||||
size = 32
|
size = 32
|
||||||
font_data = ExtResource( 2 )
|
font_data = ExtResource( 2 )
|
||||||
|
|
||||||
[sub_resource type="Animation" id=7]
|
[sub_resource type="Animation" id=8]
|
||||||
resource_name = "spinner"
|
|
||||||
length = 7.0
|
length = 7.0
|
||||||
loop = true
|
loop = true
|
||||||
tracks/0/type = "value"
|
tracks/0/type = "value"
|
||||||
|
@ -102,26 +99,26 @@ __meta__ = {
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="logo-temp-pixel2" type="TextureRect" parent="BottomRight"]
|
[node name="logo-temp-pixel2" type="TextureRect" parent="BottomRight"]
|
||||||
material = SubResource( 3 )
|
material = SubResource( 2 )
|
||||||
margin_left = -7.11853
|
margin_left = -7.11853
|
||||||
margin_top = -6.38116
|
margin_top = -6.38116
|
||||||
margin_right = 92.8815
|
margin_right = 92.8815
|
||||||
margin_bottom = 93.6188
|
margin_bottom = 93.6188
|
||||||
rect_pivot_offset = Vector2( 50, 50 )
|
rect_pivot_offset = Vector2( 50, 50 )
|
||||||
texture = SubResource( 1 )
|
texture = SubResource( 3 )
|
||||||
stretch_mode = 4
|
stretch_mode = 4
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="logo-temp-pixel" type="TextureRect" parent="BottomRight"]
|
[node name="logo-temp-pixel" type="TextureRect" parent="BottomRight"]
|
||||||
material = SubResource( 6 )
|
material = SubResource( 5 )
|
||||||
margin_left = -1.5
|
margin_left = -1.5
|
||||||
margin_top = -1.0
|
margin_top = -1.0
|
||||||
margin_right = 88.5
|
margin_right = 88.5
|
||||||
margin_bottom = 89.0
|
margin_bottom = 89.0
|
||||||
rect_pivot_offset = Vector2( 79, 59 )
|
rect_pivot_offset = Vector2( 79, 59 )
|
||||||
texture = SubResource( 4 )
|
texture = SubResource( 6 )
|
||||||
stretch_mode = 4
|
stretch_mode = 4
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
|
@ -133,10 +130,10 @@ margin_top = 63.0
|
||||||
margin_right = 174.0
|
margin_right = 174.0
|
||||||
margin_bottom = 104.0
|
margin_bottom = 104.0
|
||||||
rect_scale = Vector2( 0.5, 0.5 )
|
rect_scale = Vector2( 0.5, 0.5 )
|
||||||
custom_fonts/font = SubResource( 8 )
|
custom_fonts/font = SubResource( 7 )
|
||||||
custom_colors/font_color = Color( 1, 1, 1, 1 )
|
custom_colors/font_color = Color( 1, 1, 1, 1 )
|
||||||
align = 2
|
align = 2
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
autoplay = "spinner"
|
autoplay = "spinner"
|
||||||
anims/spinner = SubResource( 7 )
|
anims/spinner = SubResource( 8 )
|
||||||
|
|
|
@ -21,6 +21,10 @@ func _ready() -> void:
|
||||||
$"/root/Music/BGM".play()
|
$"/root/Music/BGM".play()
|
||||||
netgame.connect("ms_updated", self, "_ms_update")
|
netgame.connect("ms_updated", self, "_ms_update")
|
||||||
request_servers()
|
request_servers()
|
||||||
|
if netgame.gotm_mode:
|
||||||
|
# Hide manual connect
|
||||||
|
$Popup/MarginContainer/VBoxContainer/Label2.visible = false
|
||||||
|
$Popup/MarginContainer/VBoxContainer/HBoxContainer.visible = false
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
refresh_server_remaining -= delta
|
refresh_server_remaining -= delta
|
||||||
|
@ -45,6 +49,11 @@ func _ms_update(action, result):
|
||||||
if action == "list_games":
|
if action == "list_games":
|
||||||
# Reset server list
|
# Reset server list
|
||||||
server_list.clear()
|
server_list.clear()
|
||||||
|
if netgame.gotm_mode:
|
||||||
|
servers = result
|
||||||
|
for server in servers:
|
||||||
|
server_list.add_item(server.name + " (" + server.id + ") - " + str(server.peers.size()) + "/" + str(server.get_property("max_players")) + " players")
|
||||||
|
else:
|
||||||
servers = result
|
servers = result
|
||||||
for server in servers:
|
for server in servers:
|
||||||
server_list.add_item(server.data.name + " (" + server.address + ") - " + str(server.data.players) + "/" + str(server.data.max_players) + " players")
|
server_list.add_item(server.data.name + " (" + server.address + ") - " + str(server.data.players) + "/" + str(server.data.max_players) + " players")
|
||||||
|
@ -67,9 +76,9 @@ func _host_pressed() -> void:
|
||||||
func _join_pressed() -> void:
|
func _join_pressed() -> void:
|
||||||
$Popup.popup_centered_ratio()
|
$Popup.popup_centered_ratio()
|
||||||
|
|
||||||
func join_server(addr: String) -> void:
|
func join_server(server) -> void:
|
||||||
$"/root/Music/BGM".stop()
|
$"/root/Music/BGM".stop()
|
||||||
$"/root/Multiplayer".join(addr)
|
netgame.join(server)
|
||||||
|
|
||||||
func _server_addr_changed(new_text: String) -> void:
|
func _server_addr_changed(new_text: String) -> void:
|
||||||
$Popup/MarginContainer/VBoxContainer/HBoxContainer/Button.disabled = new_text.length() < 1
|
$Popup/MarginContainer/VBoxContainer/HBoxContainer/Button.disabled = new_text.length() < 1
|
||||||
|
@ -78,4 +87,5 @@ func _manual_join_pressed():
|
||||||
join_server($Popup/MarginContainer/VBoxContainer/HBoxContainer/LineEdit.text)
|
join_server($Popup/MarginContainer/VBoxContainer/HBoxContainer/LineEdit.text)
|
||||||
|
|
||||||
func _server_item_clicked(index):
|
func _server_item_clicked(index):
|
||||||
join_server(servers[index].address)
|
$"/root/Music/BGM".stop()
|
||||||
|
join_server(servers[index])
|
||||||
|
|
|
@ -168,6 +168,7 @@ custom_fonts/font = SubResource( 6 )
|
||||||
text = "Join an existing game"
|
text = "Join an existing game"
|
||||||
|
|
||||||
[node name="Popup" type="PopupDialog" parent="."]
|
[node name="Popup" type="PopupDialog" parent="."]
|
||||||
|
visible = true
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
margin_left = 100.0
|
margin_left = 100.0
|
||||||
|
|
|
@ -43,7 +43,7 @@ application/trademarks=""
|
||||||
|
|
||||||
[preset.1]
|
[preset.1]
|
||||||
|
|
||||||
name="HTML5"
|
name="GOTM"
|
||||||
platform="HTML5"
|
platform="HTML5"
|
||||||
runnable=true
|
runnable=true
|
||||||
custom_features=""
|
custom_features=""
|
||||||
|
|
117
gotm/Gotm.gd
Normal file
117
gotm/Gotm.gd
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
extends Node
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
|
||||||
|
# Official GDScript API for games on gotm.io
|
||||||
|
# This plugin serves as a polyfill while developing against the API locally.
|
||||||
|
|
||||||
|
# The 'real' API calls are only available when running the game live on gotm.io.
|
||||||
|
# Running the game in the web player (gotm.io/web-player) also counts as live.
|
||||||
|
|
||||||
|
# Add this script as a global autoload. Make sure the global autoload is named
|
||||||
|
# "Gotm". It must be named "Gotm" for it to work.
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# SIGNALS
|
||||||
|
##############################################################
|
||||||
|
# You connected or disconnected from a lobby. Access it at 'Gotm.lobby'
|
||||||
|
signal lobby_changed()
|
||||||
|
|
||||||
|
# Files were drag'n'dropped into the screen.
|
||||||
|
# The 'files' argument is an array of 'GotmFile'.
|
||||||
|
signal files_dropped(files, screen)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
# These are all read only.
|
||||||
|
|
||||||
|
# Player information.
|
||||||
|
var user: GotmUser = GotmUser.new()
|
||||||
|
|
||||||
|
# Current lobby you are in.
|
||||||
|
# Is null when not in a lobby.
|
||||||
|
var lobby: GotmLobby = null
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# METHODS
|
||||||
|
##############################################################
|
||||||
|
|
||||||
|
# The API is live when the game runs on gotm.io.
|
||||||
|
# Running the game in the web player (gotm.io/web-player) also counts as live.
|
||||||
|
func is_live() -> bool:
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
# Create a new lobby and join it.
|
||||||
|
#
|
||||||
|
# If 'show_invitation' is true, show an invitation link in a popup.
|
||||||
|
#
|
||||||
|
# By default, the lobby is hidden and is only accessible directly through
|
||||||
|
# its 'invite_link'.
|
||||||
|
# Set 'lobby.hidden' to false to make it fetchable with 'GotmLobbyFetch'.
|
||||||
|
#
|
||||||
|
# Returns the hosted lobby (also accessible at 'Gotm.lobby').
|
||||||
|
static func host_lobby(show_invitation: bool = true) -> GotmLobby:
|
||||||
|
return _GotmImpl._host_lobby(GotmLobby.new())
|
||||||
|
|
||||||
|
|
||||||
|
# Play an audio snippet with 'message' as a synthesized voice.
|
||||||
|
# 'language' is in BCP 47 format (e.g. "en-US" for american english).
|
||||||
|
# If specified language is not available "en-US" is used.
|
||||||
|
# Return true if playback succeeded.
|
||||||
|
func text_to_speech(message: String, language: String = "en-US") -> bool:
|
||||||
|
return true # pretend it worked
|
||||||
|
|
||||||
|
|
||||||
|
# Asynchronously open up the browser's file picker.
|
||||||
|
#
|
||||||
|
# If 'types' is specified, limit the file picker to files with matching file
|
||||||
|
# types (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers).
|
||||||
|
# If 'only_one' is true, only allow the user to pick one file.
|
||||||
|
#
|
||||||
|
# If a picking-session is already in progress, an empty
|
||||||
|
# array is asynchronously returned.
|
||||||
|
#
|
||||||
|
# Asynchronously return an array of 'GotmFile'.
|
||||||
|
# Use 'yield(pick_files(), "completed")' to retrieve the return value.
|
||||||
|
func pick_files(types: Array = Array(), only_one: bool = false) -> Array:
|
||||||
|
yield(get_tree().create_timer(0.25), "timeout")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PRIVATE
|
||||||
|
##############################################################
|
||||||
|
func _ready() -> void:
|
||||||
|
_GotmImpl._initialize(GotmLobby, GotmUser)
|
||||||
|
func _process(delta) -> void:
|
||||||
|
_GotmImpl._process()
|
||||||
|
var _impl: Dictionary = {}
|
65
gotm/GotmDebug.gd
Normal file
65
gotm/GotmDebug.gd
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name GotmDebug
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
# Helper library for testing against the API locally, as if it would be live.
|
||||||
|
#
|
||||||
|
# These functions do not make real API calls. They fake operations and
|
||||||
|
# trigger relevant signals as if they happened live.
|
||||||
|
#
|
||||||
|
# These functions do nothing when the game is running live on gotm.io.
|
||||||
|
# Running the game in the web player (gotm.io/web-player) also counts as live.
|
||||||
|
|
||||||
|
# Host a lobby without joining it.
|
||||||
|
# Note that the lobby is hidden by default and not fetchable with
|
||||||
|
# 'GotmLobbyFetch'. To make it fetchable, set 'hidden' to false.
|
||||||
|
# The lobby is only fetchable and joinable in this game process.
|
||||||
|
# Returns added lobby.
|
||||||
|
static func add_lobby() -> GotmLobby:
|
||||||
|
return _GotmDebugImpl._add_lobby(GotmLobby.new())
|
||||||
|
|
||||||
|
|
||||||
|
# Remove a lobby created with 'add_lobby', as if its host (you) disconnected from it.
|
||||||
|
# Triggers 'lobby_changed' if you are in that lobby.
|
||||||
|
static func remove_lobby(lobby: GotmLobby) -> void:
|
||||||
|
_GotmDebugImpl._remove_lobby(lobby)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove all lobbies.
|
||||||
|
static func clear_lobbies() -> void:
|
||||||
|
_GotmDebugImpl._clear_lobbies()
|
||||||
|
|
||||||
|
|
||||||
|
# Add yourself to the lobby, without joining it.
|
||||||
|
# Triggers 'peer_joined' if you are in that lobby.
|
||||||
|
# Returns joined peer.
|
||||||
|
static func add_lobby_peer(lobby: GotmLobby) -> GotmUser:
|
||||||
|
return _GotmDebugImpl._add_lobby_player(lobby, GotmUser.new())
|
||||||
|
|
||||||
|
|
||||||
|
# Remove a peer created with 'add_lobby_peer' from the lobby, as if the peer (you) disconnected
|
||||||
|
# from the lobby.
|
||||||
|
# Triggers 'peer_left' if you are in that lobby.
|
||||||
|
static func remove_lobby_peer(lobby: GotmLobby, peer: GotmUser) -> void:
|
||||||
|
_GotmDebugImpl._remove_lobby_player(lobby, peer)
|
50
gotm/GotmFile.gd
Normal file
50
gotm/GotmFile.gd
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name GotmFile
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
# A simple in-memory file descriptor used by 'Gotm.pick_files' and
|
||||||
|
# 'Gotm.files_dropped'.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
# File name.
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
# File data.
|
||||||
|
var data: PoolByteArray
|
||||||
|
|
||||||
|
# Last time the file was modified in unix time (seconds since epoch).
|
||||||
|
var modified_time: int
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# METHODS
|
||||||
|
##############################################################
|
||||||
|
# Save the file to the browser's download folder.
|
||||||
|
func download() -> void:
|
||||||
|
pass
|
155
gotm/GotmLobby.gd
Normal file
155
gotm/GotmLobby.gd
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name GotmLobby
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
# A lobby is a way of connecting players with eachother as if they
|
||||||
|
# were on the same local network.
|
||||||
|
#
|
||||||
|
# Lobbies can be joined either directly through an 'invite_link', or by
|
||||||
|
# joining lobbies fetched with the 'GotmLobbyFetch' class.
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# SIGNALS
|
||||||
|
##############################################################
|
||||||
|
# Peer joined the lobby.
|
||||||
|
# 'peer_user' is a 'GotmUser' instance.
|
||||||
|
# This is only emitted if you are in this lobby.
|
||||||
|
signal peer_joined(peer_user)
|
||||||
|
|
||||||
|
# Peer left the lobby.
|
||||||
|
# 'peer_user' is a 'GotmUser' instance.
|
||||||
|
# This is only emitted if you are in this lobby.
|
||||||
|
signal peer_left(peer_user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# READ-ONLY PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
# Globally unique identifier.
|
||||||
|
var id: String
|
||||||
|
|
||||||
|
# Other peers in the lobby with addresses.
|
||||||
|
# Is an array of 'GotmUser'.
|
||||||
|
var peers: Array = []
|
||||||
|
|
||||||
|
# You with address.
|
||||||
|
var me: GotmUser = GotmUser.new()
|
||||||
|
|
||||||
|
# Host user with address.
|
||||||
|
var host: GotmUser = GotmUser.new()
|
||||||
|
|
||||||
|
# Peers can join the lobby directly through this link.
|
||||||
|
var invite_link: String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# WRITABLE PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
# Note that only the host can write to these properties.
|
||||||
|
|
||||||
|
# Name that is searchable using 'GotmLobbyFetch'
|
||||||
|
# Names longer than 64 characters are truncated.
|
||||||
|
var name: String = ""
|
||||||
|
|
||||||
|
# Prevent the lobby from showing up in fetches?
|
||||||
|
# Peers may still join directly through 'invite_link'
|
||||||
|
var hidden: bool = true
|
||||||
|
|
||||||
|
# Prevent new peers from joining?
|
||||||
|
# Also prevents the lobby from showing up in fetches.
|
||||||
|
var locked: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# METHODS
|
||||||
|
##############################################################
|
||||||
|
# Asynchronously join this lobby after leaving current lobby.
|
||||||
|
#
|
||||||
|
# Use 'var success = yield(lobby.join(), "completed")' to wait for the call to complete
|
||||||
|
# and retrieve the return value.
|
||||||
|
#
|
||||||
|
# Sets 'Gotm.lobby' to the joined lobby if successful.
|
||||||
|
#
|
||||||
|
# Asyncronously returns true if successful, else false.
|
||||||
|
func join() -> bool:
|
||||||
|
return yield(_GotmImpl._join_lobby(self), "completed")
|
||||||
|
|
||||||
|
|
||||||
|
# Leave this lobby.
|
||||||
|
func leave() -> void:
|
||||||
|
_GotmImpl._leave_lobby(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Am I the host of this lobby?
|
||||||
|
func is_host() -> bool:
|
||||||
|
return _GotmImpl._is_lobby_host(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Get a custom property.
|
||||||
|
func get_property(name: String):
|
||||||
|
return _GotmImpl._get_lobby_property(self, name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################################
|
||||||
|
# Host-only methods
|
||||||
|
################################
|
||||||
|
# Kick peer from this lobby.
|
||||||
|
# Returns true if successful, else false.
|
||||||
|
func kick(peer: GotmUser) -> bool:
|
||||||
|
return _GotmImpl._kick_lobby_peer(self, peer)
|
||||||
|
|
||||||
|
|
||||||
|
# Store up to 10 of your own custom properties in the lobby.
|
||||||
|
# These are visible to other peers when fetching lobbies.
|
||||||
|
# Only properties of types String, int, float or bool are allowed.
|
||||||
|
# Integers are converted to floats.
|
||||||
|
# Strings longer than 64 characters are truncated.
|
||||||
|
# Setting 'value' to null removes the property.
|
||||||
|
func set_property(name: String, value) -> void:
|
||||||
|
_GotmImpl._set_lobby_property(self, name, value)
|
||||||
|
|
||||||
|
|
||||||
|
# Make this lobby filterable by a custom property.
|
||||||
|
# Filtering is done when fetching lobbies with 'GotmLobbyFetch'.
|
||||||
|
# Up to 3 properties can be set as filterable at once.
|
||||||
|
func set_filterable(property_name: String, filterable: bool = true) -> void:
|
||||||
|
_GotmImpl._set_lobby_filterable(self, property_name, filterable)
|
||||||
|
|
||||||
|
|
||||||
|
# Make this lobby sortable by a custom property.
|
||||||
|
# Sorting is done when fetching lobbies with 'GotmLobbyFetch'.
|
||||||
|
# Up to 3 properties can be set as sortable at once.
|
||||||
|
func set_sortable(property_name: String, sortable: bool = true) -> void:
|
||||||
|
_GotmImpl._set_lobby_sortable(self, property_name, sortable)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################################
|
||||||
|
# PRIVATE
|
||||||
|
################################
|
||||||
|
var _impl: Dictionary = {}
|
122
gotm/GotmLobbyFetch.gd
Normal file
122
gotm/GotmLobbyFetch.gd
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name GotmLobbyFetch
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
# Used for fetching non-hidden and non-locked lobbies.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
################
|
||||||
|
# Filter options
|
||||||
|
################
|
||||||
|
# If not empty, fetch lobbies whose 'Lobby.name' contains 'name'.
|
||||||
|
var filter_name: String = ""
|
||||||
|
|
||||||
|
# If not empty, fetch lobbies whose filterable custom properties
|
||||||
|
# matches those in 'filter_properties'.
|
||||||
|
#
|
||||||
|
# For example, setting 'filter_properties.difficulty = 2' will
|
||||||
|
# only fetch lobbies that have been set up with both 'lobby.set_property("difficulty", 2)'
|
||||||
|
# and 'lobby.set_filterable("difficulty", true)'.
|
||||||
|
#
|
||||||
|
# If your lobby has multiple filterable props, you must provide every filterable
|
||||||
|
# prop in 'filter_properties'. Setting a prop's value to 'null' will match any
|
||||||
|
# value of that prop.
|
||||||
|
var filter_properties: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
# Sort options
|
||||||
|
################
|
||||||
|
# If not empty, sort by a sortable custom property.
|
||||||
|
#
|
||||||
|
# For example, setting 'sort_property = "difficulty"' will
|
||||||
|
# only fetch lobbies that have been set up with both 'lobby.set_property("difficulty", some_value)'
|
||||||
|
# and 'lobby.set_sortable("difficulty", true)'.
|
||||||
|
#
|
||||||
|
# If your lobby has a sortable prop, you must always provide a 'sort_property'.
|
||||||
|
var sort_property: String = ""
|
||||||
|
|
||||||
|
# Sort results in ascending order?
|
||||||
|
var sort_ascending: bool = false
|
||||||
|
|
||||||
|
# If not null, fetch lobbies whose sort property's value is equal to or greater than 'sort_min'.
|
||||||
|
var sort_min = null
|
||||||
|
|
||||||
|
# If not null, fetch lobbies whose sort property's value is equal to or lesser than 'sort_max'.
|
||||||
|
var sort_max = null
|
||||||
|
|
||||||
|
# If true, and 'sort_min' is provided, exclude lobbies whose sort property's value is equal to 'sort_min'.
|
||||||
|
var sort_min_exclusive = false
|
||||||
|
|
||||||
|
# If true, and 'sort_max' is provided, exclude lobbies whose sort property's value is equal to 'sort_max'.
|
||||||
|
var sort_max_exclusive = false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# METHODS
|
||||||
|
##############################################################
|
||||||
|
# All these methods asynchronously fetch up to 8 non-hidden
|
||||||
|
# and non-locked lobbies.
|
||||||
|
#
|
||||||
|
# Modifying any filtering or sorting option resets the state of this
|
||||||
|
# 'GotmLobbyFetch' instance and causes the next fetch call to
|
||||||
|
# fetch the first lobbies.
|
||||||
|
#
|
||||||
|
# All calls asynchronously return an array of fetched lobbies.
|
||||||
|
# Use 'yield(fetch.next(), "completed")' to retrieve it.
|
||||||
|
|
||||||
|
|
||||||
|
# Fetch the next lobbies, starting after the last lobby fetched
|
||||||
|
# in the previous call.
|
||||||
|
func next(count: int = 8) -> Array:
|
||||||
|
return yield(_GotmImpl._fetch_lobbies(self, count, "next"), "completed")
|
||||||
|
|
||||||
|
|
||||||
|
# Fetch the previous lobbies, ending before the first lobby
|
||||||
|
# that was fetched in the previous call.
|
||||||
|
func previous(count: int = 8) -> Array:
|
||||||
|
return yield(_GotmImpl._fetch_lobbies(self, count, "previous"), "completed")
|
||||||
|
|
||||||
|
|
||||||
|
# Fetch the first lobbies.
|
||||||
|
func first(count: int = 8) -> Array:
|
||||||
|
return yield(_GotmImpl._fetch_lobbies(self, count, "first"), "completed")
|
||||||
|
|
||||||
|
|
||||||
|
# Fetch lobbies at the current position.
|
||||||
|
# Useful for refreshing lobbies without changing the page.
|
||||||
|
func current(count: int = 8) -> Array:
|
||||||
|
return yield(_GotmImpl._fetch_lobbies(self, count, "current"), "completed")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PRIVATE
|
||||||
|
##############################################################
|
||||||
|
var _impl: Dictionary = {}
|
52
gotm/GotmUser.gd
Normal file
52
gotm/GotmUser.gd
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name GotmUser
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
# Holds information about a Gotm user.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PROPERTIES
|
||||||
|
##############################################################
|
||||||
|
# These are all read-only.
|
||||||
|
|
||||||
|
# Globally unique ID.
|
||||||
|
# Is empty if user is not logged in.
|
||||||
|
var id: String = ""
|
||||||
|
|
||||||
|
# Current nickname.
|
||||||
|
var display_name: String = ""
|
||||||
|
|
||||||
|
# The IP address of the user.
|
||||||
|
# Is empty if you are not in the same lobby.
|
||||||
|
var address: String = ""
|
||||||
|
|
||||||
|
# Is user logged in?
|
||||||
|
var is_logged_in: bool = false
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
# PRIVATE
|
||||||
|
##############################################################
|
||||||
|
var _impl: Dictionary = {}
|
21
gotm/LICENSE
Normal file
21
gotm/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
|
||||||
|
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.
|
30
gotm/README.md
Normal file
30
gotm/README.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://gotm.io"><img src="https://i.imgur.com/mc8HAgS.png" alt="gotm.io"></a>
|
||||||
|
<br/>
|
||||||
|
Access Gotm's API with GDScript
|
||||||
|
<br />
|
||||||
|
<a href="https://gotm.io/about/plugin">Learn more</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
_Supports versions 3.1.0 and newer._
|
||||||
|
|
||||||
|
## 1. Download
|
||||||
|
|
||||||
|
Download the plugin from the [AssetLib](https://docs.godotengine.org/en/stable/tutorials/assetlib/using_assetlib.html#in-the-editor) in the Godot Editor and follow its instructions.
|
||||||
|
|
||||||
|
You can also download it directly [here](https://github.com/PlayGotm/GDGotm/archive/master.zip). Extract the contents into your project's directory.
|
||||||
|
|
||||||
|
## 2. Setup
|
||||||
|
|
||||||
|
Add Gotm.gd to your autoloads at "Project Settings -> AutoLoad". Make sure the global autoload is named "Gotm". It must be named "Gotm" for it to work.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
[Examples](https://github.com/PlayGotM/game-examples)
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
This plugin serves as a polyfill when developing against the API locally.
|
||||||
|
The "real" API calls are only available when running the game live on gotm.io.
|
108
gotm/_impl/_GotmDebugImpl.gd
Normal file
108
gotm/_impl/_GotmDebugImpl.gd
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name _GotmDebugImpl
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
static func _login() -> void:
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return
|
||||||
|
|
||||||
|
_logout()
|
||||||
|
|
||||||
|
g.user_id = _GotmImpl._generate_id()
|
||||||
|
g.emit_signal("user_changed")
|
||||||
|
|
||||||
|
|
||||||
|
static func _logout() -> void:
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return
|
||||||
|
|
||||||
|
if !g.has_user():
|
||||||
|
return
|
||||||
|
|
||||||
|
g.user_id = ""
|
||||||
|
g.emit_signal("user_changed")
|
||||||
|
|
||||||
|
|
||||||
|
static func _add_lobby(lobby):
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return lobby
|
||||||
|
|
||||||
|
lobby = _GotmImpl._add_lobby(lobby)
|
||||||
|
lobby._impl.address = "127.0.0.1"
|
||||||
|
lobby._impl.host_id = g.user._impl.id
|
||||||
|
lobby.host._impl.id = g.user._impl.id
|
||||||
|
lobby.peers = []
|
||||||
|
return lobby
|
||||||
|
|
||||||
|
|
||||||
|
static func _remove_lobby(lobby) -> void:
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return
|
||||||
|
|
||||||
|
_GotmImpl._leave_lobby(lobby)
|
||||||
|
g._impl.lobbies.erase(lobby)
|
||||||
|
|
||||||
|
|
||||||
|
static func _clear_lobbies() -> void:
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return
|
||||||
|
|
||||||
|
for lobby in g._impl.lobbies.duplicate():
|
||||||
|
_remove_lobby(lobby)
|
||||||
|
|
||||||
|
|
||||||
|
static func _add_lobby_player(lobby, peer):
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return null
|
||||||
|
|
||||||
|
peer.address = "127.0.0.1"
|
||||||
|
peer._impl.id = peer._impl.id
|
||||||
|
lobby.peers.push_back(peer)
|
||||||
|
if lobby == g.lobby:
|
||||||
|
lobby.emit_signal("peer_joined", peer)
|
||||||
|
return peer
|
||||||
|
|
||||||
|
|
||||||
|
static func _remove_lobby_player(lobby, peer) -> void:
|
||||||
|
var g = _GotmImpl._get_gotm()
|
||||||
|
if g.is_live():
|
||||||
|
return
|
||||||
|
|
||||||
|
for p in lobby.peers.duplicate():
|
||||||
|
if peer._impl.id != p._impl.id:
|
||||||
|
continue
|
||||||
|
if peer._impl.id == lobby.host._impl.id:
|
||||||
|
_remove_lobby(lobby)
|
||||||
|
else:
|
||||||
|
lobby.peers.erase(p)
|
||||||
|
if lobby == g.lobby:
|
||||||
|
lobby.emit_signal("peer_left", p)
|
||||||
|
|
||||||
|
|
586
gotm/_impl/_GotmImpl.gd
Normal file
586
gotm/_impl/_GotmImpl.gd
Normal file
|
@ -0,0 +1,586 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name _GotmImpl
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
|
||||||
|
# Utility sorter for 'sort_custom'.
|
||||||
|
class LobbySorter:
|
||||||
|
var fetch
|
||||||
|
var g
|
||||||
|
|
||||||
|
func sort(lhs, rhs) -> bool:
|
||||||
|
var a
|
||||||
|
var b
|
||||||
|
if fetch.sort_property.empty():
|
||||||
|
a = lhs._impl.created
|
||||||
|
b = rhs._impl.created
|
||||||
|
else:
|
||||||
|
a = lhs._impl.props[fetch.sort_property]
|
||||||
|
b = rhs._impl.props[fetch.sort_property]
|
||||||
|
|
||||||
|
if fetch.sort_ascending:
|
||||||
|
return _GotmImplUtility.is_less(a, b)
|
||||||
|
else:
|
||||||
|
return _GotmImplUtility.is_greater(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
# Generate 20 characters long random string.
|
||||||
|
static func _generate_id() -> String:
|
||||||
|
var g = _get_gotm()
|
||||||
|
var id: String = ""
|
||||||
|
for i in range(20):
|
||||||
|
id += g._impl.chars[g._impl.rng.randi() % g._impl.chars.length()]
|
||||||
|
return id
|
||||||
|
|
||||||
|
|
||||||
|
# Retrieve scene statically via Engine singleton.
|
||||||
|
static func _get_tree() -> SceneTree:
|
||||||
|
return Engine.get_main_loop() as SceneTree
|
||||||
|
|
||||||
|
|
||||||
|
# Get autoloaded Gotm instance from scene's root.
|
||||||
|
static func _get_gotm() -> Node:
|
||||||
|
return _get_tree().root.get_node("Gotm")
|
||||||
|
|
||||||
|
|
||||||
|
# Simplify string somewhat. We want exact matches, but with some reasonable fuzziness.
|
||||||
|
static func _init_search_string_encoders() -> Array:
|
||||||
|
var encoders: Array = [
|
||||||
|
["[àáâãäå]", "a"],
|
||||||
|
["[èéêë]", "e"],
|
||||||
|
["[ìíîï]", "i"],
|
||||||
|
["[òóôõöő]", "o"],
|
||||||
|
["[ùúûüű]", "u"],
|
||||||
|
["[ýŷÿ]", "y"],
|
||||||
|
["ñ", "n"],
|
||||||
|
["[çc]", "k"],
|
||||||
|
["ß", "s"],
|
||||||
|
["[-/]", " "],
|
||||||
|
["[^a-z0-9 ]", ""],
|
||||||
|
["\\s+", " "],
|
||||||
|
["^\\s+", ""],
|
||||||
|
["\\s+$", ""]
|
||||||
|
]
|
||||||
|
for encoder in encoders:
|
||||||
|
var regex: RegEx = RegEx.new()
|
||||||
|
regex.compile(encoder[0])
|
||||||
|
encoder[0] = regex
|
||||||
|
|
||||||
|
return encoders
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize socket for fetching lobbies on local network.
|
||||||
|
static func _init_socket() -> void:
|
||||||
|
var g = _get_gotm()
|
||||||
|
if not g._impl.sockets:
|
||||||
|
g._impl.sockets = []
|
||||||
|
var is_listening = false
|
||||||
|
for i in range(5):
|
||||||
|
var socket = PacketPeerUDP.new()
|
||||||
|
if socket.has_method("set_broadcast_enabled"):
|
||||||
|
socket.set_broadcast_enabled(true)
|
||||||
|
if not is_listening:
|
||||||
|
is_listening = socket.listen(8075 + i) == OK
|
||||||
|
socket.set_dest_address("255.255.255.255", 8075 + i)
|
||||||
|
g._impl.sockets.push_back(socket)
|
||||||
|
|
||||||
|
if not is_listening:
|
||||||
|
push_error("Failed to listen for lobbies. All ports 8075-8079 are busy.")
|
||||||
|
|
||||||
|
|
||||||
|
# Attach some global state to autoloaded Gotm instance.
|
||||||
|
static func _initialize(GotmLobbyT, GotmUserT) -> void:
|
||||||
|
var g = _get_gotm()
|
||||||
|
|
||||||
|
g._impl = {
|
||||||
|
"lobbies": [],
|
||||||
|
"chars": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-",
|
||||||
|
"rng": RandomNumberGenerator.new(),
|
||||||
|
"search_string_encoders": _init_search_string_encoders(),
|
||||||
|
"sockets": null,
|
||||||
|
"lobby_requests": {},
|
||||||
|
"GotmLobbyT": GotmLobbyT,
|
||||||
|
"GotmUserT": GotmUserT,
|
||||||
|
"is_listening": false
|
||||||
|
}
|
||||||
|
g._impl.rng.randomize()
|
||||||
|
g.user._impl.id = _generate_id()
|
||||||
|
g.user.address = "localhost"
|
||||||
|
|
||||||
|
|
||||||
|
static func _process() -> void:
|
||||||
|
var g = _get_gotm()
|
||||||
|
|
||||||
|
if g.lobby:
|
||||||
|
if OS.get_system_time_msecs() - g.lobby._impl.last_heartbeat > 2500:
|
||||||
|
_init_socket()
|
||||||
|
_put_sockets({
|
||||||
|
"op": "peer_heartbeat",
|
||||||
|
"data": {
|
||||||
|
"lobby_id": g.lobby.id,
|
||||||
|
"id": g.user._impl.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.lobby._impl.last_heartbeat = OS.get_system_time_msecs()
|
||||||
|
|
||||||
|
if g._impl.sockets:
|
||||||
|
for socket in g._impl.sockets:
|
||||||
|
while socket.get_available_packet_count() > 0:
|
||||||
|
var v = socket.get_var()
|
||||||
|
if v.op == "get_lobbies":
|
||||||
|
var data = null
|
||||||
|
if g.lobby and g.lobby.is_host():
|
||||||
|
data = {
|
||||||
|
"id": g.lobby.id,
|
||||||
|
"name": g.lobby.name,
|
||||||
|
"peers": [],
|
||||||
|
"invite_link": g.lobby.invite_link,
|
||||||
|
"_impl": g.lobby._impl
|
||||||
|
}
|
||||||
|
|
||||||
|
_put_sockets({"op": "lobby", "data": data, "id": v.id})
|
||||||
|
elif v.op == "leave_lobby":
|
||||||
|
if g.lobby and v.data.lobby_id == g.lobby.id:
|
||||||
|
if g.lobby.is_host():
|
||||||
|
_put_sockets({
|
||||||
|
"op": "peer_left",
|
||||||
|
"data": {
|
||||||
|
"lobby_id": g.lobby.id,
|
||||||
|
"id": v.data.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
elif v.data.id == g.lobby.host._impl.id:
|
||||||
|
_leave_lobby(g.lobby)
|
||||||
|
elif v.op == "join_lobby":
|
||||||
|
var data = null
|
||||||
|
if g.lobby and g.lobby.is_host() and v.data.lobby_id == g.lobby.id:
|
||||||
|
_put_sockets({
|
||||||
|
"op": "peer_joined",
|
||||||
|
"data": {
|
||||||
|
"lobby_id": g.lobby.id,
|
||||||
|
"address": socket.get_packet_ip(),
|
||||||
|
"id": v.data.id
|
||||||
|
},
|
||||||
|
"id": v.id
|
||||||
|
})
|
||||||
|
|
||||||
|
var peers = []
|
||||||
|
for peer in g.lobby.peers:
|
||||||
|
peers.push_back({"address": peer.address, "_impl": peer._impl})
|
||||||
|
data = {
|
||||||
|
"id": g.lobby.id,
|
||||||
|
"name": g.lobby.name,
|
||||||
|
"peers": peers,
|
||||||
|
"invite_link": g.lobby.invite_link,
|
||||||
|
"_impl": g.lobby._impl
|
||||||
|
}
|
||||||
|
_put_sockets({"op": "lobby", "data": data, "id": v.id})
|
||||||
|
elif v.op == "peer_left":
|
||||||
|
if g.lobby and v.data.lobby_id == g.lobby.id:
|
||||||
|
for peer in g.lobby.peers.duplicate():
|
||||||
|
if peer._impl.id == v.data.id:
|
||||||
|
g.lobby.peers.erase(peer)
|
||||||
|
g.lobby.emit_signal("peer_left", peer)
|
||||||
|
elif v.op == "peer_joined":
|
||||||
|
if g.lobby and v.data.lobby_id == g.lobby.id and not g._impl.lobby_requests.has(v.id):
|
||||||
|
var peer = g._impl.GotmUserT.new()
|
||||||
|
peer.address = v.data.address
|
||||||
|
peer._impl.id = v.data.id
|
||||||
|
g.lobby.peers.push_back(peer)
|
||||||
|
g.lobby.emit_signal("peer_joined", peer)
|
||||||
|
if g.lobby.is_host():
|
||||||
|
g.lobby._impl.heartbeats[v.data.id] = OS.get_system_time_msecs()
|
||||||
|
elif v.op == "peer_heartbeat":
|
||||||
|
if g.lobby and v.data.lobby_id == g.lobby.id:
|
||||||
|
g.lobby._impl.heartbeats[v.data.id] = OS.get_system_time_msecs()
|
||||||
|
elif v.op == "lobby":
|
||||||
|
if v.data and g._impl.lobby_requests.has(v.id):
|
||||||
|
var lobby = g._impl.GotmLobbyT.new()
|
||||||
|
var peers = []
|
||||||
|
if not v.data._impl.host_id.empty():
|
||||||
|
v.data.peers.push_back({
|
||||||
|
"address": socket.get_packet_ip(),
|
||||||
|
"_impl": {
|
||||||
|
"id": v.data._impl.host_id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for peer in v.data.peers:
|
||||||
|
var p = g._impl.GotmUserT.new()
|
||||||
|
p.address = peer.address
|
||||||
|
p._impl = peer._impl
|
||||||
|
peers.push_back(p)
|
||||||
|
|
||||||
|
lobby.hidden = false
|
||||||
|
lobby.locked = false
|
||||||
|
lobby.id = v.data.id
|
||||||
|
lobby.name = v.data.name
|
||||||
|
lobby.peers = peers
|
||||||
|
lobby.invite_link = v.data.invite_link
|
||||||
|
lobby._impl = v.data._impl
|
||||||
|
lobby._impl.address = socket.get_packet_ip()
|
||||||
|
lobby.me.address = "127.0.0.1"
|
||||||
|
lobby.host.address = socket.get_packet_ip()
|
||||||
|
lobby.host._impl.id = v.data._impl.host_id
|
||||||
|
g._impl.lobby_requests[v.id].push_back(lobby)
|
||||||
|
|
||||||
|
if g.lobby:
|
||||||
|
for peer_id in g.lobby._impl.heartbeats.duplicate():
|
||||||
|
if OS.get_system_time_msecs() - g.lobby._impl.heartbeats[peer_id] > 10000:
|
||||||
|
if g.lobby.is_host():
|
||||||
|
_put_sockets({
|
||||||
|
"op": "peer_left",
|
||||||
|
"data": {
|
||||||
|
"lobby_id": g.lobby.id,
|
||||||
|
"id": peer_id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.lobby._impl.heartbeats.erase(peer_id)
|
||||||
|
elif peer_id == g.lobby.host._impl.id:
|
||||||
|
_leave_lobby(g.lobby)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Improve search experience a little by adding fuzziness.
|
||||||
|
static func _encode_search_string(s: String) -> String:
|
||||||
|
s = s.to_lower()
|
||||||
|
var encoders: Array = _get_gotm()._impl.search_string_encoders
|
||||||
|
for encoder in encoders:
|
||||||
|
s = encoder[0].sub(s, encoder[1], true)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
# Return true if 'lobby' matches filter options in 'fetch'.
|
||||||
|
static func _match_lobby(lobby, fetch) -> bool:
|
||||||
|
if lobby.locked or lobby.hidden:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not fetch.filter_name.empty():
|
||||||
|
var name: String = _encode_search_string(lobby.name)
|
||||||
|
var query: String = _encode_search_string(fetch.filter_name)
|
||||||
|
if not query.empty() and name.find(query) < 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var lobby_props: Dictionary = {}
|
||||||
|
for key in lobby._impl.filterable_props:
|
||||||
|
if not lobby._impl.props.has(key):
|
||||||
|
return false
|
||||||
|
if not fetch.filter_properties.has(key):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var lhs = fetch.filter_properties[key]
|
||||||
|
var rhs = lobby._impl.props[key]
|
||||||
|
if lhs != null and lhs != rhs:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# Used to detect changes.
|
||||||
|
static func _stringify_fetch_state(fetch) -> String:
|
||||||
|
var d: Array = [
|
||||||
|
fetch.filter_name,
|
||||||
|
fetch.filter_properties,
|
||||||
|
fetch.sort_property,
|
||||||
|
fetch.sort_property,
|
||||||
|
fetch.sort_ascending,
|
||||||
|
fetch.sort_min,
|
||||||
|
fetch.sort_max,
|
||||||
|
fetch.sort_min_exclusive,
|
||||||
|
fetch.sort_max_exclusive
|
||||||
|
]
|
||||||
|
return JSON.print(d)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Return sorted copy of 'lobbies' using sort options in 'fetch'.
|
||||||
|
static func _sort_lobbies(lobbies: Array, fetch) -> Array:
|
||||||
|
var sorted: Array = []
|
||||||
|
var g = _get_gotm()
|
||||||
|
for lobby in lobbies:
|
||||||
|
if fetch.sort_property.empty():
|
||||||
|
sorted.push_back(lobby)
|
||||||
|
|
||||||
|
var v = lobby._impl.props.get(fetch.sort_property)
|
||||||
|
if v == null:
|
||||||
|
continue
|
||||||
|
if fetch.sort_min != null:
|
||||||
|
if _GotmImplUtility.is_less(v, fetch.sort_min):
|
||||||
|
continue
|
||||||
|
if fetch.sort_min_exclusive and not _GotmImplUtility.is_greater(v, fetch.sort_min):
|
||||||
|
continue
|
||||||
|
if fetch.sort_max != null:
|
||||||
|
if _GotmImplUtility.is_greater(v, fetch.sort_max):
|
||||||
|
continue
|
||||||
|
if fetch.sort_max_exclusive and not _GotmImplUtility.is_less(v, fetch.sort_max):
|
||||||
|
continue
|
||||||
|
|
||||||
|
sorted.push_back(lobby)
|
||||||
|
|
||||||
|
|
||||||
|
var sorter: LobbySorter = LobbySorter.new()
|
||||||
|
sorter.fetch = fetch
|
||||||
|
sorter.g = g
|
||||||
|
sorted.sort_custom(sorter, "sort")
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
|
||||||
|
|
||||||
|
static func _put_sockets(v: Dictionary):
|
||||||
|
_init_socket()
|
||||||
|
for socket in _get_gotm()._impl.sockets:
|
||||||
|
socket.put_var(v)
|
||||||
|
|
||||||
|
|
||||||
|
static func _request_lobbies() -> Array:
|
||||||
|
var g = _get_gotm()
|
||||||
|
var request_id: String = _generate_id()
|
||||||
|
g._impl.lobby_requests[request_id] = []
|
||||||
|
_put_sockets({"op": "get_lobbies", "id": request_id})
|
||||||
|
yield(g.get_tree().create_timer(0.5), "timeout")
|
||||||
|
|
||||||
|
var lobbies = g._impl.lobby_requests[request_id]
|
||||||
|
g._impl.lobby_requests.erase(request_id)
|
||||||
|
return lobbies
|
||||||
|
|
||||||
|
|
||||||
|
static func _request_join(lobby_id: String):
|
||||||
|
var g = _get_gotm()
|
||||||
|
var request_id: String = _generate_id()
|
||||||
|
g._impl.lobby_requests[request_id] = []
|
||||||
|
_put_sockets({
|
||||||
|
"op": "join_lobby",
|
||||||
|
"id": request_id,
|
||||||
|
"data": {
|
||||||
|
"lobby_id": lobby_id,
|
||||||
|
"id": g.user._impl.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
yield(g.get_tree().create_timer(0.5), "timeout")
|
||||||
|
|
||||||
|
var lobbies = g._impl.lobby_requests[request_id]
|
||||||
|
g._impl.lobby_requests.erase(request_id)
|
||||||
|
for lobby in lobbies:
|
||||||
|
if lobby:
|
||||||
|
return lobby
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
static func _fetch_lobbies(fetch, count: int, type: String) -> Array:
|
||||||
|
var g = _get_gotm()
|
||||||
|
|
||||||
|
# Reset fetch state if user has modified any options.
|
||||||
|
var stringified_state: String = _stringify_fetch_state(fetch)
|
||||||
|
if not fetch._impl.has("last_state") or stringified_state != fetch._impl.last_state:
|
||||||
|
fetch._impl.last_state = stringified_state
|
||||||
|
fetch._impl.last_lobby = -1
|
||||||
|
fetch._impl.start_lobby = -1
|
||||||
|
|
||||||
|
|
||||||
|
# Apply filter options
|
||||||
|
var lobbies: Array = []
|
||||||
|
for lobby in yield(_request_lobbies(), "completed") + g._impl.lobbies:
|
||||||
|
if _match_lobby(lobby, fetch):
|
||||||
|
lobbies.push_back(lobby)
|
||||||
|
|
||||||
|
# Apply sort options
|
||||||
|
lobbies = _sort_lobbies(lobbies, fetch)
|
||||||
|
count = min(8, count)
|
||||||
|
var index: int = 0
|
||||||
|
if type == "first":
|
||||||
|
index = 0
|
||||||
|
elif type == "next":
|
||||||
|
index = fetch._impl.last_lobby + 1
|
||||||
|
elif type == "current":
|
||||||
|
index = fetch._impl.start_lobby + 1
|
||||||
|
elif type == "previous":
|
||||||
|
index = max(fetch._impl.start_lobby - count, 0)
|
||||||
|
|
||||||
|
# Get 'count' lobbies.
|
||||||
|
var result: Array = []
|
||||||
|
for i in range(index, min(index + count, lobbies.size())):
|
||||||
|
result.push_back(lobbies[i])
|
||||||
|
|
||||||
|
# Write down last lobby for subsequent 'next' calls.
|
||||||
|
if not result.empty():
|
||||||
|
var start: int = lobbies.find(result.front()) - 1
|
||||||
|
fetch._impl.start_lobby = max(start, -1)
|
||||||
|
fetch._impl.last_lobby = lobbies.find(result.back())
|
||||||
|
elif index > 0:
|
||||||
|
fetch._impl.start_lobby = fetch._impl.last_lobby
|
||||||
|
|
||||||
|
yield(_get_tree().create_timer(0.25), "timeout") # fake delay
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Common initialization.
|
||||||
|
static func _add_lobby(lobby):
|
||||||
|
var g = _get_gotm()
|
||||||
|
|
||||||
|
lobby.id = _generate_id()
|
||||||
|
lobby.invite_link = "https://gotm.io/my-studio/my-game/"
|
||||||
|
lobby.invite_link += "?connectToken=" + _generate_id()
|
||||||
|
lobby._impl = {
|
||||||
|
# Not exposed to user, so doesn't have to be a real timestamp.
|
||||||
|
"created": OS.get_system_time_msecs(),
|
||||||
|
"props": {},
|
||||||
|
"sortable_props": [],
|
||||||
|
"filterable_props": [],
|
||||||
|
"heartbeats": {},
|
||||||
|
"last_heartbeat": 0,
|
||||||
|
"host_id": "",
|
||||||
|
"address": ""
|
||||||
|
}
|
||||||
|
lobby.me._impl.id = g.user._impl.id
|
||||||
|
|
||||||
|
g._impl.lobbies.push_back(lobby)
|
||||||
|
return lobby
|
||||||
|
|
||||||
|
|
||||||
|
static func _host_lobby(lobby):
|
||||||
|
var g = _get_gotm()
|
||||||
|
_leave_lobby(g.lobby)
|
||||||
|
|
||||||
|
lobby = _add_lobby(lobby)
|
||||||
|
lobby._impl.address = "127.0.0.1"
|
||||||
|
lobby.host.address = "127.0.0.1"
|
||||||
|
lobby._impl.host_id = g.user._impl.id
|
||||||
|
lobby.host._impl.id = g.user._impl.id
|
||||||
|
lobby.me.address = "127.0.0.1"
|
||||||
|
g.lobby = lobby
|
||||||
|
g.emit_signal("lobby_changed")
|
||||||
|
|
||||||
|
_init_socket()
|
||||||
|
|
||||||
|
return lobby
|
||||||
|
|
||||||
|
|
||||||
|
static func _join_lobby(lobby) -> bool:
|
||||||
|
var g = _get_gotm()
|
||||||
|
_leave_lobby(g.lobby)
|
||||||
|
|
||||||
|
if not g._impl.lobbies.has(lobby):
|
||||||
|
lobby = yield(_request_join(lobby.id), "completed")
|
||||||
|
else:
|
||||||
|
yield(g.get_tree().create_timer(0.25), "timeout")
|
||||||
|
|
||||||
|
if not lobby or lobby.locked:
|
||||||
|
return false
|
||||||
|
|
||||||
|
lobby.host.address = lobby._impl.address
|
||||||
|
lobby.host._impl.id = lobby._impl.host_id
|
||||||
|
lobby.me.address = "127.0.0.1"
|
||||||
|
g.lobby = lobby
|
||||||
|
g.emit_signal("lobby_changed")
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
static func _is_lobby_host(lobby) -> bool:
|
||||||
|
var g = _get_gotm()
|
||||||
|
return lobby.host._impl.id == g.user._impl.id
|
||||||
|
|
||||||
|
|
||||||
|
static func _kick_lobby_peer(lobby, peer) -> bool:
|
||||||
|
var g = _get_gotm()
|
||||||
|
|
||||||
|
if not lobby.is_host():
|
||||||
|
return false
|
||||||
|
|
||||||
|
if g.user._impl.id == peer._impl.id:
|
||||||
|
_leave_lobby(lobby)
|
||||||
|
else:
|
||||||
|
for p in lobby.peers.duplicate():
|
||||||
|
if p._impl.id != peer._impl.id:
|
||||||
|
continue
|
||||||
|
lobby.peers.erase(p)
|
||||||
|
if lobby == g.lobby:
|
||||||
|
lobby.emit_signal("peer_left", p)
|
||||||
|
break
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
static func _leave_lobby(lobby) -> void:
|
||||||
|
if not lobby:
|
||||||
|
return
|
||||||
|
|
||||||
|
var g = _get_gotm()
|
||||||
|
if g.lobby == lobby:
|
||||||
|
if lobby.host.address == lobby.me.address:
|
||||||
|
g._impl.lobbies.erase(lobby)
|
||||||
|
lobby.me.address = ""
|
||||||
|
lobby.host.address = ""
|
||||||
|
_put_sockets({
|
||||||
|
"op": "leave_lobby",
|
||||||
|
"data": {
|
||||||
|
"lobby_id": lobby.id,
|
||||||
|
"id": g.user._impl.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.lobby = null
|
||||||
|
g.emit_signal("lobby_changed")
|
||||||
|
|
||||||
|
|
||||||
|
static func _truncate_string(s: String) -> String:
|
||||||
|
return s.substr(0, 64)
|
||||||
|
|
||||||
|
static func _set_lobby_property(lobby, name: String, value) -> void:
|
||||||
|
name = _truncate_string(name)
|
||||||
|
if value == null:
|
||||||
|
lobby._impl.props.erase(name)
|
||||||
|
|
||||||
|
match typeof(value):
|
||||||
|
TYPE_BOOL:
|
||||||
|
pass
|
||||||
|
TYPE_INT:
|
||||||
|
pass
|
||||||
|
TYPE_REAL:
|
||||||
|
pass
|
||||||
|
TYPE_STRING:
|
||||||
|
value = _truncate_string(value)
|
||||||
|
_:
|
||||||
|
push_error("Invalid lobby property type.")
|
||||||
|
return
|
||||||
|
|
||||||
|
lobby._impl.props[name] = value
|
||||||
|
|
||||||
|
|
||||||
|
static func _get_lobby_property(lobby, name: String):
|
||||||
|
return lobby._impl.props[_truncate_string(name)]
|
||||||
|
|
||||||
|
static func _set_lobby_filterable(lobby, property_name: String, filterable: bool) -> void:
|
||||||
|
property_name = _truncate_string(property_name)
|
||||||
|
if not filterable:
|
||||||
|
lobby._impl.filterable_props.erase(property_name)
|
||||||
|
elif not lobby._impl.filterable_props.has(property_name):
|
||||||
|
lobby._impl.filterable_props.push_back(property_name)
|
||||||
|
|
||||||
|
|
||||||
|
static func _set_lobby_sortable(lobby, property_name: String, sortable: bool) -> void:
|
||||||
|
property_name = _truncate_string(property_name)
|
||||||
|
if not sortable:
|
||||||
|
lobby._impl.sortable_props.erase(property_name)
|
||||||
|
elif not lobby._impl.sortable_props.has(property_name):
|
||||||
|
lobby._impl.sortable_props.push_back(property_name)
|
48
gotm/_impl/_GotmImplUtility.gd
Normal file
48
gotm/_impl/_GotmImplUtility.gd
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020-2020 Macaroni Studios AB
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
class_name _GotmImplUtility
|
||||||
|
#warnings-disable
|
||||||
|
|
||||||
|
|
||||||
|
static func _fuzzy_compare(a, b, compare_less: bool) -> bool:
|
||||||
|
if typeof(a) == typeof(b):
|
||||||
|
return a < b if compare_less else a > b
|
||||||
|
|
||||||
|
# GDScript doesn't handle comparison of different types very well.
|
||||||
|
# Abuse Array's min and max functions instead.
|
||||||
|
var m = [a, b].min() if compare_less else [a, b].max()
|
||||||
|
if m != null or a == null or b == null:
|
||||||
|
return m == a
|
||||||
|
|
||||||
|
# Array method failed. Go with strings instead.
|
||||||
|
a = String(a)
|
||||||
|
b = String(b)
|
||||||
|
return a < b if compare_less else a > b
|
||||||
|
|
||||||
|
|
||||||
|
static func is_less(a, b) -> bool:
|
||||||
|
return _fuzzy_compare(a, b, true)
|
||||||
|
|
||||||
|
|
||||||
|
static func is_greater(a, b) -> bool:
|
||||||
|
return _fuzzy_compare(a, b, false)
|
|
@ -64,6 +64,31 @@ _global_script_classes=[ {
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
"path": "res://Scenes/World.gd"
|
"path": "res://Scenes/World.gd"
|
||||||
}, {
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "GotmDebug",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/GotmDebug.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "GotmFile",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/GotmFile.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "GotmLobby",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/GotmLobby.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "GotmLobbyFetch",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/GotmLobbyFetch.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "GotmUser",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/GotmUser.gd"
|
||||||
|
}, {
|
||||||
"base": "TileMap",
|
"base": "TileMap",
|
||||||
"class": "MapTiles",
|
"class": "MapTiles",
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
|
@ -103,6 +128,21 @@ _global_script_classes=[ {
|
||||||
"class": "UICommand",
|
"class": "UICommand",
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
"path": "res://Classes/UICommand.gd"
|
"path": "res://Classes/UICommand.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "_GotmDebugImpl",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/_impl/_GotmDebugImpl.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "_GotmImpl",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/_impl/_GotmImpl.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "_GotmImplUtility",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://gotm/_impl/_GotmImplUtility.gd"
|
||||||
} ]
|
} ]
|
||||||
_global_script_class_icons={
|
_global_script_class_icons={
|
||||||
"ActivationRange": "",
|
"ActivationRange": "",
|
||||||
|
@ -116,6 +156,11 @@ _global_script_class_icons={
|
||||||
"GameObjectPowerStorage": "",
|
"GameObjectPowerStorage": "",
|
||||||
"GameObjectScanner": "",
|
"GameObjectScanner": "",
|
||||||
"GameWorld": "",
|
"GameWorld": "",
|
||||||
|
"GotmDebug": "",
|
||||||
|
"GotmFile": "",
|
||||||
|
"GotmLobby": "",
|
||||||
|
"GotmLobbyFetch": "",
|
||||||
|
"GotmUser": "",
|
||||||
"MapTiles": "",
|
"MapTiles": "",
|
||||||
"Occluder": "",
|
"Occluder": "",
|
||||||
"POI": "",
|
"POI": "",
|
||||||
|
@ -123,7 +168,10 @@ _global_script_class_icons={
|
||||||
"PowerNetwork": "",
|
"PowerNetwork": "",
|
||||||
"ProbeElectric": "",
|
"ProbeElectric": "",
|
||||||
"ResourceQueue": "",
|
"ResourceQueue": "",
|
||||||
"UICommand": ""
|
"UICommand": "",
|
||||||
|
"_GotmDebugImpl": "",
|
||||||
|
"_GotmImpl": "",
|
||||||
|
"_GotmImplUtility": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
@ -139,6 +187,7 @@ config/icon="res://icon.png"
|
||||||
Music="*res://Scenes/Global/Music.tscn"
|
Music="*res://Scenes/Global/Music.tscn"
|
||||||
Multiplayer="*res://Scenes/Global/Multiplayer.gd"
|
Multiplayer="*res://Scenes/Global/Multiplayer.gd"
|
||||||
SceneManager="*res://Scenes/Global/SceneManager.gd"
|
SceneManager="*res://Scenes/Global/SceneManager.gd"
|
||||||
|
Gotm="*res://gotm/Gotm.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
|
Reference in a new issue