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 (length(cable_color) == 0.) {
|
||||
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);
|
||||
} else {
|
||||
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
||||
}
|
||||
} 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);
|
||||
} else {
|
||||
col.rgb = vec3(0.04, 0.58, 0.98) * length(col.rgb);
|
||||
|
@ -32,7 +32,6 @@ void fragment() {
|
|||
}
|
||||
COLOR = col;
|
||||
}"
|
||||
custom_defines = ""
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
shader = SubResource( 1 )
|
||||
|
|
|
@ -4,6 +4,7 @@ var thread
|
|||
var mutex
|
||||
var sem
|
||||
|
||||
var gotm_mode = false
|
||||
var time_max = 100 # Milliseconds.
|
||||
|
||||
var queue = []
|
||||
|
@ -58,6 +59,8 @@ func cancel_resource(path):
|
|||
|
||||
|
||||
func get_progress(path):
|
||||
if gotm_mode:
|
||||
return 1.0
|
||||
_lock("get_progress")
|
||||
var ret = -1
|
||||
if path in pending:
|
||||
|
@ -70,6 +73,8 @@ func get_progress(path):
|
|||
|
||||
|
||||
func is_ready(path):
|
||||
if gotm_mode:
|
||||
return true
|
||||
var ret
|
||||
_lock("is_ready")
|
||||
if path in pending:
|
||||
|
@ -81,6 +86,8 @@ func is_ready(path):
|
|||
|
||||
|
||||
func _wait_for_resource(res, path):
|
||||
if gotm_mode:
|
||||
return get_resource(path)
|
||||
_unlock("wait_for_resource")
|
||||
while true:
|
||||
VisualServer.sync()
|
||||
|
@ -92,6 +99,8 @@ func _wait_for_resource(res, path):
|
|||
|
||||
|
||||
func get_resource(path):
|
||||
if gotm_mode:
|
||||
return load(path)
|
||||
_lock("get_resource")
|
||||
if path in pending:
|
||||
if pending[path] is ResourceInteractiveLoader:
|
||||
|
@ -140,8 +149,10 @@ func thread_func(_u):
|
|||
thread_process()
|
||||
|
||||
|
||||
func start():
|
||||
mutex = Mutex.new()
|
||||
sem = Semaphore.new()
|
||||
thread = Thread.new()
|
||||
thread.start(self, "thread_func", 0)
|
||||
func start(gotm: bool):
|
||||
gotm_mode = gotm
|
||||
if not gotm:
|
||||
mutex = Mutex.new()
|
||||
sem = Semaphore.new()
|
||||
thread = Thread.new()
|
||||
thread.start(self, "thread_func", 0)
|
||||
|
|
|
@ -15,12 +15,14 @@ const SYSTEMS_UPDATE_INTERVAL = 10
|
|||
const MASTER_SERVER_ADDR = "fgms.zyg.ovh"
|
||||
const MASTER_SERVER_UDP_PORT = 9434
|
||||
const MS_GAME_CODE = "odyssey-0-a1"
|
||||
const GOTM_OVERRIDE = false
|
||||
|
||||
# Master server entry
|
||||
var ms_active = false
|
||||
var ms_key = ""
|
||||
var server_name = ""
|
||||
|
||||
onready var gotm_mode = Gotm.is_live() or GOTM_OVERRIDE
|
||||
var hosting = false
|
||||
|
||||
export var player_name = ""
|
||||
|
@ -32,6 +34,8 @@ onready var scene_manager = $"/root/SceneManager"
|
|||
|
||||
func _ready():
|
||||
player_name = "tider-" + str(randi() % 1000)
|
||||
if gotm_mode:
|
||||
Gotm.connect("lobby_changed", self, "_lobby_changed")
|
||||
|
||||
func bind_events():
|
||||
get_tree().connect("network_peer_connected", self, "_player_connected")
|
||||
|
@ -71,13 +75,23 @@ func host():
|
|||
# Wait just a sec to draw
|
||||
yield(get_tree().create_timer(0.3), "timeout")
|
||||
|
||||
print("Running UPNP magicks")
|
||||
if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS:
|
||||
print("UPNP mapping added")
|
||||
else:
|
||||
push_warning("UPNP magicks fail, punching NAT in the face")
|
||||
yield(punch_nat(), "completed")
|
||||
# Run port forwarding/nat punchthrough if the platform doesn't do it already
|
||||
if not gotm_mode:
|
||||
print("Running UPNP magicks")
|
||||
if discover_upnp() == UPNP.UPNP_RESULT_SUCCESS:
|
||||
print("UPNP mapping added")
|
||||
else:
|
||||
push_warning("UPNP magicks fail, punching NAT in the face")
|
||||
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()
|
||||
var peer = NetworkedMultiplayerENet.new()
|
||||
var server_res = peer.create_server(port, MAX_PLAYERS)
|
||||
|
@ -91,9 +105,6 @@ func host():
|
|||
get_tree().network_peer = peer
|
||||
print("Hosting")
|
||||
hosting = true
|
||||
server_name = player_name + "'s server"
|
||||
player_info[1] = { "name": player_name }
|
||||
round_info = { "map": "odyssey" }
|
||||
|
||||
scene_manager.loading_text = null
|
||||
|
||||
|
@ -101,18 +112,27 @@ func host():
|
|||
"res://Scenes/Maps/odyssey.tscn"
|
||||
])
|
||||
|
||||
# Add to master server
|
||||
create_ms_entry()
|
||||
if not gotm_mode:
|
||||
# Add to master server after hosting
|
||||
create_ms_entry()
|
||||
|
||||
func join(addr: String):
|
||||
func join(server):
|
||||
scene_manager.enter_loader()
|
||||
scene_manager.loading_text = "Joining server " + str(addr)
|
||||
scene_manager.loading_text = "Joining server"
|
||||
|
||||
# Wait just a sec to draw
|
||||
yield(get_tree().create_timer(0.3), "timeout")
|
||||
|
||||
bind_events()
|
||||
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)
|
||||
get_tree().network_peer = peer
|
||||
print("Connecting to ", addr)
|
||||
|
@ -172,15 +192,20 @@ func _ms_request(endpoint: String, data):
|
|||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
func ms_get_entries():
|
||||
var http_request = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.connect("request_completed", self, "_ms_response", ["list_games"])
|
||||
var error = http_request.request(
|
||||
"https://" + MASTER_SERVER_ADDR + "/" + MS_GAME_CODE,
|
||||
["Content-Type: application/json"],
|
||||
true, HTTPClient.METHOD_GET)
|
||||
if error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
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()
|
||||
add_child(http_request)
|
||||
http_request.connect("request_completed", self, "_ms_response", ["list_games"])
|
||||
var error = http_request.request(
|
||||
"https://" + MASTER_SERVER_ADDR + "/" + MS_GAME_CODE,
|
||||
["Content-Type: application/json"],
|
||||
true, HTTPClient.METHOD_GET)
|
||||
if error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
func get_game_data():
|
||||
return {
|
||||
|
@ -191,18 +216,28 @@ func get_game_data():
|
|||
}
|
||||
|
||||
func create_ms_entry():
|
||||
_ms_request("new", {
|
||||
"game_id": MS_GAME_CODE,
|
||||
"data": get_game_data()
|
||||
})
|
||||
if gotm_mode:
|
||||
Gotm.host_lobby(true)
|
||||
Gotm.lobby.hidden = false
|
||||
else:
|
||||
_ms_request("new", {
|
||||
"game_id": MS_GAME_CODE,
|
||||
"data": get_game_data()
|
||||
})
|
||||
update_ms_entry()
|
||||
|
||||
func update_ms_entry():
|
||||
if ms_active:
|
||||
_ms_request("update", {
|
||||
"key": ms_key,
|
||||
"data": get_game_data()
|
||||
})
|
||||
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:
|
||||
_ms_request("update", {
|
||||
"key": ms_key,
|
||||
"data": get_game_data()
|
||||
})
|
||||
|
||||
var time_left = 30
|
||||
func _process(delta):
|
||||
|
@ -240,3 +275,6 @@ func get_current_map():
|
|||
return GameWorld.Map.RUNTIME
|
||||
_:
|
||||
return GameWorld.Map.EMPTY
|
||||
|
||||
func _lobby_changed():
|
||||
print("Lobby changed ", Gotm.lobby)
|
||||
|
|
|
@ -6,27 +6,33 @@ var loading_text = null
|
|||
|
||||
var loader = preload("res://Scenes/Loader.tscn")
|
||||
|
||||
onready var netgame = $"/root/Multiplayer"
|
||||
|
||||
func _ready() -> void:
|
||||
queue.start()
|
||||
queue.start(netgame.gotm_mode)
|
||||
|
||||
func enter_loader() -> void:
|
||||
get_tree().change_scene_to(loader)
|
||||
|
||||
func load_scene(scene_path: String, dependencies: Array) -> void:
|
||||
target_scene = scene_path
|
||||
queue.queue_resource(scene_path)
|
||||
for dep in dependencies:
|
||||
queue.queue_resource(dep)
|
||||
if not netgame.gotm_mode:
|
||||
target_scene = scene_path
|
||||
queue.queue_resource(scene_path)
|
||||
for dep in dependencies:
|
||||
queue.queue_resource(dep)
|
||||
else:
|
||||
get_tree().change_scene(scene_path)
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if target_scene != null:
|
||||
var remaining = queue.pending.size()
|
||||
for path in queue.pending:
|
||||
if queue.is_ready(path):
|
||||
remaining -= 1
|
||||
if remaining == 0:
|
||||
get_tree().change_scene_to(queue.get_resource(target_scene))
|
||||
target_scene = null
|
||||
if not netgame.gotm_mode:
|
||||
if target_scene != null:
|
||||
var remaining = queue.pending.size()
|
||||
for path in queue.pending:
|
||||
if queue.is_ready(path):
|
||||
remaining -= 1
|
||||
if remaining == 0:
|
||||
get_tree().change_scene_to(queue.get_resource(target_scene))
|
||||
target_scene = null
|
||||
|
||||
func get_progress() -> String:
|
||||
if loading_text != null:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
[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]
|
||||
|
||||
[sub_resource type="Shader" id=2]
|
||||
[sub_resource type="Shader" id=1]
|
||||
code = "shader_type canvas_item;
|
||||
|
||||
uniform vec2 tex_size;
|
||||
|
@ -14,23 +14,22 @@ void fragment() {
|
|||
vec2 uv_adjusted = (UV - (uv_rect.xy / tex_size)) * (tex_size / uv_rect.zw);
|
||||
float dist = distance(uv_adjusted, vec2(0.5));
|
||||
if (dist < 0.26) {
|
||||
COLOR = vec4(1);
|
||||
COLOR = vec4(1.);
|
||||
} else {
|
||||
COLOR = texture(TEXTURE, UV);
|
||||
}
|
||||
}"
|
||||
custom_defines = ""
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=3]
|
||||
shader = SubResource( 2 )
|
||||
[sub_resource type="ShaderMaterial" id=2]
|
||||
shader = SubResource( 1 )
|
||||
shader_param/tex_size = Vector2( 240, 180 )
|
||||
shader_param/uv_rect = Plane( 126, 16, 82, 84 )
|
||||
|
||||
[sub_resource type="AtlasTexture" id=1]
|
||||
[sub_resource type="AtlasTexture" id=3]
|
||||
atlas = ExtResource( 1 )
|
||||
region = Rect2( 126, 16, 82, 84 )
|
||||
|
||||
[sub_resource type="Shader" id=5]
|
||||
[sub_resource type="Shader" id=4]
|
||||
code = "shader_type canvas_item;
|
||||
|
||||
uniform vec2 tex_size;
|
||||
|
@ -47,23 +46,21 @@ void fragment() {
|
|||
COLOR = vec4(tex.aaa, 1.-tex.a);
|
||||
}
|
||||
}"
|
||||
custom_defines = ""
|
||||
|
||||
[sub_resource type="ShaderMaterial" id=6]
|
||||
shader = SubResource( 5 )
|
||||
[sub_resource type="ShaderMaterial" id=5]
|
||||
shader = SubResource( 4 )
|
||||
shader_param/tex_size = Vector2( 240, 180 )
|
||||
shader_param/uv_rect = Plane( 146, 39, 39, 38 )
|
||||
|
||||
[sub_resource type="AtlasTexture" id=4]
|
||||
[sub_resource type="AtlasTexture" id=6]
|
||||
atlas = ExtResource( 1 )
|
||||
region = Rect2( 146, 39, 39, 38 )
|
||||
|
||||
[sub_resource type="DynamicFont" id=8]
|
||||
[sub_resource type="DynamicFont" id=7]
|
||||
size = 32
|
||||
font_data = ExtResource( 2 )
|
||||
|
||||
[sub_resource type="Animation" id=7]
|
||||
resource_name = "spinner"
|
||||
[sub_resource type="Animation" id=8]
|
||||
length = 7.0
|
||||
loop = true
|
||||
tracks/0/type = "value"
|
||||
|
@ -102,26 +99,26 @@ __meta__ = {
|
|||
}
|
||||
|
||||
[node name="logo-temp-pixel2" type="TextureRect" parent="BottomRight"]
|
||||
material = SubResource( 3 )
|
||||
material = SubResource( 2 )
|
||||
margin_left = -7.11853
|
||||
margin_top = -6.38116
|
||||
margin_right = 92.8815
|
||||
margin_bottom = 93.6188
|
||||
rect_pivot_offset = Vector2( 50, 50 )
|
||||
texture = SubResource( 1 )
|
||||
texture = SubResource( 3 )
|
||||
stretch_mode = 4
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="logo-temp-pixel" type="TextureRect" parent="BottomRight"]
|
||||
material = SubResource( 6 )
|
||||
material = SubResource( 5 )
|
||||
margin_left = -1.5
|
||||
margin_top = -1.0
|
||||
margin_right = 88.5
|
||||
margin_bottom = 89.0
|
||||
rect_pivot_offset = Vector2( 79, 59 )
|
||||
texture = SubResource( 4 )
|
||||
texture = SubResource( 6 )
|
||||
stretch_mode = 4
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
|
@ -133,10 +130,10 @@ margin_top = 63.0
|
|||
margin_right = 174.0
|
||||
margin_bottom = 104.0
|
||||
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 )
|
||||
align = 2
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
autoplay = "spinner"
|
||||
anims/spinner = SubResource( 7 )
|
||||
anims/spinner = SubResource( 8 )
|
||||
|
|
|
@ -21,6 +21,10 @@ func _ready() -> void:
|
|||
$"/root/Music/BGM".play()
|
||||
netgame.connect("ms_updated", self, "_ms_update")
|
||||
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:
|
||||
refresh_server_remaining -= delta
|
||||
|
@ -45,9 +49,14 @@ func _ms_update(action, result):
|
|||
if action == "list_games":
|
||||
# Reset server list
|
||||
server_list.clear()
|
||||
servers = result
|
||||
for server in servers:
|
||||
server_list.add_item(server.data.name + " (" + server.address + ") - " + str(server.data.players) + "/" + str(server.data.max_players) + " players")
|
||||
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
|
||||
for server in servers:
|
||||
server_list.add_item(server.data.name + " (" + server.address + ") - " + str(server.data.players) + "/" + str(server.data.max_players) + " players")
|
||||
|
||||
func set_scale(val) -> void:
|
||||
scale = val
|
||||
|
@ -67,9 +76,9 @@ func _host_pressed() -> void:
|
|||
func _join_pressed() -> void:
|
||||
$Popup.popup_centered_ratio()
|
||||
|
||||
func join_server(addr: String) -> void:
|
||||
func join_server(server) -> void:
|
||||
$"/root/Music/BGM".stop()
|
||||
$"/root/Multiplayer".join(addr)
|
||||
netgame.join(server)
|
||||
|
||||
func _server_addr_changed(new_text: String) -> void:
|
||||
$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)
|
||||
|
||||
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"
|
||||
|
||||
[node name="Popup" type="PopupDialog" parent="."]
|
||||
visible = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 100.0
|
||||
|
|
|
@ -43,7 +43,7 @@ application/trademarks=""
|
|||
|
||||
[preset.1]
|
||||
|
||||
name="HTML5"
|
||||
name="GOTM"
|
||||
platform="HTML5"
|
||||
runnable=true
|
||||
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",
|
||||
"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",
|
||||
"class": "MapTiles",
|
||||
"language": "GDScript",
|
||||
|
@ -103,6 +128,21 @@ _global_script_classes=[ {
|
|||
"class": "UICommand",
|
||||
"language": "GDScript",
|
||||
"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={
|
||||
"ActivationRange": "",
|
||||
|
@ -116,6 +156,11 @@ _global_script_class_icons={
|
|||
"GameObjectPowerStorage": "",
|
||||
"GameObjectScanner": "",
|
||||
"GameWorld": "",
|
||||
"GotmDebug": "",
|
||||
"GotmFile": "",
|
||||
"GotmLobby": "",
|
||||
"GotmLobbyFetch": "",
|
||||
"GotmUser": "",
|
||||
"MapTiles": "",
|
||||
"Occluder": "",
|
||||
"POI": "",
|
||||
|
@ -123,7 +168,10 @@ _global_script_class_icons={
|
|||
"PowerNetwork": "",
|
||||
"ProbeElectric": "",
|
||||
"ResourceQueue": "",
|
||||
"UICommand": ""
|
||||
"UICommand": "",
|
||||
"_GotmDebugImpl": "",
|
||||
"_GotmImpl": "",
|
||||
"_GotmImplUtility": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
@ -139,6 +187,7 @@ config/icon="res://icon.png"
|
|||
Music="*res://Scenes/Global/Music.tscn"
|
||||
Multiplayer="*res://Scenes/Global/Multiplayer.gd"
|
||||
SceneManager="*res://Scenes/Global/SceneManager.gd"
|
||||
Gotm="*res://gotm/Gotm.gd"
|
||||
|
||||
[display]
|
||||
|
||||
|
|
Reference in a new issue