This commit is contained in:
Hamcha 2020-07-22 17:34:04 +02:00
parent 3d57f3d839
commit b77326eb80
Signed by: hamcha
GPG key ID: 41467804B19A3315
39 changed files with 211 additions and 1624 deletions

View file

@ -185,6 +185,7 @@ rect_scale = Vector2( 0.5, 0.5 )
[node name="ActivationRange" type="Area2D" parent="."] [node name="ActivationRange" type="Area2D" parent="."]
visible = false visible = false
input_pickable = false
script = ExtResource( 6 ) script = ExtResource( 6 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"]

View file

@ -66,6 +66,7 @@ one_shot = true
[node name="ActivationRange" type="Area2D" parent="."] [node name="ActivationRange" type="Area2D" parent="."]
visible = false visible = false
input_pickable = false
script = ExtResource( 3 ) script = ExtResource( 3 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"]
@ -73,6 +74,7 @@ position = Vector2( 16, 16 )
shape = SubResource( 8 ) shape = SubResource( 8 )
[node name="CloseRange" type="Area2D" parent="."] [node name="CloseRange" type="Area2D" parent="."]
input_pickable = false
script = ExtResource( 3 ) script = ExtResource( 3 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="CloseRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="CloseRange"]

View file

@ -52,7 +52,7 @@ func refresh_sprite() -> void:
$Light2D.rotation = rot $Light2D.rotation = rot
$ActivationRange.rotation = rot $ActivationRange.rotation = rot
func _physics_process(delta: float) -> void: func _physics_process(_delta: float) -> void:
if Engine.editor_hint: if Engine.editor_hint:
return return
manager.power_usage = max(1e-3, MAX_USAGE * strength) manager.power_usage = max(1e-3, MAX_USAGE * strength)

View file

@ -14,7 +14,6 @@ extents = Vector2( 72, 72 )
[node name="Engine" type="StaticBody2D"] [node name="Engine" type="StaticBody2D"]
script = ExtResource( 2 ) script = ExtResource( 2 )
max_force = 0.05
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2( 48, 48 ) position = Vector2( 48, 48 )
@ -36,6 +35,7 @@ energy = 0.002
[node name="ActivationRange" type="Area2D" parent="."] [node name="ActivationRange" type="Area2D" parent="."]
visible = false visible = false
input_pickable = false
script = ExtResource( 1 ) script = ExtResource( 1 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"]

View file

@ -38,6 +38,7 @@ __meta__ = {
[node name="ActivationRange" type="Area2D" parent="."] [node name="ActivationRange" type="Area2D" parent="."]
visible = false visible = false
input_pickable = false
script = ExtResource( 4 ) script = ExtResource( 4 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"]

View file

@ -97,6 +97,7 @@ rect_scale = Vector2( 0.5, 0.5 )
[node name="ActivationRange" type="Area2D" parent="."] [node name="ActivationRange" type="Area2D" parent="."]
visible = false visible = false
position = Vector2( 16, 16 ) position = Vector2( 16, 16 )
input_pickable = false
script = ExtResource( 3 ) script = ExtResource( 3 )
[node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"] [node name="CollisionShape2D" type="CollisionShape2D" parent="ActivationRange"]

View file

@ -5,5 +5,5 @@ class_name GameObjectScanner
func serialize(): func serialize():
return {} return {}
func deserialize(data): func deserialize(_data):
pass pass

View file

@ -100,5 +100,5 @@ func draw_circle_arc_poly(center, radius, angle_from, angle_to, color):
points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius) points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)
draw_polygon(points_arc, colors) draw_polygon(points_arc, colors)
func hear(event): func hear(_event):
pass pass

View file

@ -24,7 +24,7 @@ position = Vector2( 0, -3 )
shape = SubResource( 1 ) shape = SubResource( 1 )
[node name="Earing" type="Area2D" parent="."] [node name="Earing" type="Area2D" parent="."]
monitorable = false input_pickable = false
collision_layer = 32 collision_layer = 32
collision_mask = 32 collision_mask = 32

View file

@ -111,3 +111,26 @@ func _draw():
func _area_moved(): func _area_moved():
scout() scout()
update() update()
func serialize():
var subareas = {}
for child in get_children():
subareas[child.name] = {
"transform": child.transform
}
return {
"name": area_name,
"wall_path": wall_tilemap,
"base_path": base_tilemap,
"subareas": subareas
}
func deserialize(data):
for subarea in data.subareas:
var node = Node2D.new()
node.name = subarea
node.transform = data.subareas[subarea].transform
add_child(node)
area_name = data.name
wall_tilemap = data.wall_path
base_tilemap = data.base_path

View file

@ -14,6 +14,7 @@ a = Vector2( -16, 16 )
b = Vector2( 48, 16 ) b = Vector2( 48, 16 )
[node name="ElProbe" type="Area2D"] [node name="ElProbe" type="Area2D"]
input_pickable = false
collision_layer = 4 collision_layer = 4
collision_mask = 4 collision_mask = 4
script = ExtResource( 1 ) script = ExtResource( 1 )

View file

@ -21,7 +21,8 @@ static func to_letter(num: int) -> String:
return "Α" return "Α"
elif num < 0: elif num < 0:
letters = "αβγδεζηθικλμνξοπρστυφχψω" letters = "αβγδεζηθικλμνξοπρστυφχψω"
num = abs(num) if num < 0:
num = -num
var out = "" var out = ""
var base = letters.length() var base = letters.length()
while num > 0: while num > 0:

View file

@ -4,7 +4,7 @@ var thread
var mutex var mutex
var sem var sem
var gotm_mode = false var threaded = true
var time_max = 100 # Milliseconds. var time_max = 100 # Milliseconds.
var queue = [] var queue = []
@ -13,19 +13,15 @@ var pending = {}
func _lock(_caller): func _lock(_caller):
mutex.lock() mutex.lock()
func _unlock(_caller): func _unlock(_caller):
mutex.unlock() mutex.unlock()
func _post(_caller): func _post(_caller):
sem.post() sem.post()
func _wait(_caller): func _wait(_caller):
sem.wait() sem.wait()
func queue_resource(path, p_in_front = false): func queue_resource(path, p_in_front = false):
_lock("queue_resource") _lock("queue_resource")
if path in pending: if path in pending:
@ -48,7 +44,6 @@ func queue_resource(path, p_in_front = false):
_unlock("queue_resource") _unlock("queue_resource")
return return
func cancel_resource(path): func cancel_resource(path):
_lock("cancel_resource") _lock("cancel_resource")
if path in pending: if path in pending:
@ -57,9 +52,8 @@ func cancel_resource(path):
pending.erase(path) pending.erase(path)
_unlock("cancel_resource") _unlock("cancel_resource")
func get_progress(path): func get_progress(path):
if gotm_mode: if not threaded:
return 1.0 return 1.0
_lock("get_progress") _lock("get_progress")
var ret = -1 var ret = -1
@ -71,9 +65,8 @@ func get_progress(path):
_unlock("get_progress") _unlock("get_progress")
return ret return ret
func is_ready(path): func is_ready(path):
if gotm_mode: if not threaded:
return true return true
var ret var ret
_lock("is_ready") _lock("is_ready")
@ -84,9 +77,8 @@ func is_ready(path):
_unlock("is_ready") _unlock("is_ready")
return ret return ret
func _wait_for_resource(res, path): func _wait_for_resource(res, path):
if gotm_mode: if not threaded:
return get_resource(path) return get_resource(path)
_unlock("wait_for_resource") _unlock("wait_for_resource")
while true: while true:
@ -97,9 +89,8 @@ func _wait_for_resource(res, path):
return pending[path] return pending[path]
_unlock("wait_for_resource") _unlock("wait_for_resource")
func get_resource(path): func get_resource(path):
if gotm_mode: if not threaded:
return load(path) return load(path)
_lock("get_resource") _lock("get_resource")
if path in pending: if path in pending:
@ -123,7 +114,6 @@ func get_resource(path):
_unlock("return") _unlock("return")
return ResourceLoader.load(path) return ResourceLoader.load(path)
func thread_process(): func thread_process():
_wait("thread_process") _wait("thread_process")
_lock("process") _lock("process")
@ -143,16 +133,15 @@ func thread_process():
queue.erase(res) queue.erase(res)
_unlock("process") _unlock("process")
func thread_func(_u): func thread_func(_u):
while true: while true:
thread_process() thread_process()
func start(use_threads: bool):
func start(gotm: bool): threaded = use_threads
gotm_mode = gotm if not threaded:
if not gotm: return
mutex = Mutex.new() mutex = Mutex.new()
sem = Semaphore.new() sem = Semaphore.new()
thread = Thread.new() thread = Thread.new()
thread.start(self, "thread_func", 0) thread.start(self, "thread_func", 0)

View file

@ -4,7 +4,6 @@
script/source = "extends Node script/source = "extends Node
func _ready(): func _ready():
$\"/root/Multiplayer\".gotm_mode = true # Faster startup
$\"/root/Multiplayer\".host() $\"/root/Multiplayer\".host()
" "

View file

@ -14,7 +14,6 @@ var writing = false
func _ready() -> void: func _ready() -> void:
randomize() randomize()
ui.connect("command", world, "process_command")
if netgame.hosting: if netgame.hosting:
world.load_map(netgame.get_current_map()) world.load_map(netgame.get_current_map())
world.map.current_ship_position = Vector2(randf() * 1e4, randf() * 1e4) world.map.current_ship_position = Vector2(randf() * 1e4, randf() * 1e4)
@ -58,11 +57,14 @@ master func broadcast_zone(message: String, origin: Vector2, radius: float) -> v
var shape = CircleShape2D.new() var shape = CircleShape2D.new()
shape.radius = radius shape.radius = radius
var query = Physics2DShapeQueryParameters.new() var query = Physics2DShapeQueryParameters.new()
query.collide_with_areas = true
query.collide_with_bodies = false
query.collision_layer = 32 query.collision_layer = 32
query.transform = Transform(world.transform.translated(origin)) query.transform.origin = origin
query.set_shape(shape) query.set_shape(shape)
var res = physics.intersect_shape(query, 100) var res = physics.intersect_shape(query, 100)
print(res) for col in res:
rpc_id(col.collider.get_network_master(), "add_log", message)
master func broadcast(message: String) -> void: master func broadcast(message: String) -> void:
rpc("add_log", message) rpc("add_log", message)

View file

@ -14,7 +14,7 @@ const SYSTEMS_UPDATE_INTERVAL = 10
# Master server data # Master server data
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-a2"
const GOTM_OVERRIDE = false const GOTM_OVERRIDE = false
# Master server entry # Master server entry
@ -22,7 +22,6 @@ 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 = ""
@ -34,8 +33,6 @@ 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")
@ -76,22 +73,17 @@ func host():
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 # 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") else:
else: 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" server_name = player_name + "'s server"
player_info[1] = { "name": player_name } player_info[1] = { "name": player_name }
round_info = { "map": "odyssey" } 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()
peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_FASTLZ peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_FASTLZ
@ -113,9 +105,8 @@ func host():
"res://Scenes/Maps/odyssey.tscn" "res://Scenes/Maps/odyssey.tscn"
]) ])
if not gotm_mode: # Add to master server after hosting
# Add to master server after hosting create_ms_entry()
create_ms_entry()
func join(server): func join(server):
scene_manager.enter_loader() scene_manager.enter_loader()
@ -126,13 +117,9 @@ func join(server):
bind_events() bind_events()
var peer = NetworkedMultiplayerENet.new() var peer = NetworkedMultiplayerENet.new()
peer.compression_mode = NetworkedMultiplayerENet.COMPRESS_FASTLZ
var addr = null var addr = server.address
if gotm_mode:
var success = yield(server.join(), "completed")
addr = Gotm.lobby.host.address
else:
addr = server.address
peer.create_client(addr, SERVER_PORT) peer.create_client(addr, SERVER_PORT)
get_tree().network_peer = peer get_tree().network_peer = peer
@ -193,20 +180,15 @@ 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 http_request = HTTPRequest.new()
var fetch = GotmLobbyFetch.new() add_child(http_request)
var lobbies = yield(fetch.first(), "completed") http_request.connect("request_completed", self, "_ms_response", ["list_games"])
emit_signal("ms_updated", "list_games", lobbies) var error = http_request.request(
else: "https://" + MASTER_SERVER_ADDR + "/" + MS_GAME_CODE,
var http_request = HTTPRequest.new() ["Content-Type: application/json"],
add_child(http_request) true, HTTPClient.METHOD_GET)
http_request.connect("request_completed", self, "_ms_response", ["list_games"]) if error != OK:
var error = http_request.request( push_error("An error occurred in the HTTP 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(): func get_game_data():
return { return {
@ -217,28 +199,17 @@ func get_game_data():
} }
func create_ms_entry(): func create_ms_entry():
if gotm_mode: _ms_request("new", {
Gotm.host_lobby(true) "game_id": MS_GAME_CODE,
Gotm.lobby.hidden = false "data": get_game_data()
else: })
_ms_request("new", {
"game_id": MS_GAME_CODE,
"data": get_game_data()
})
update_ms_entry()
func update_ms_entry(): func update_ms_entry():
if gotm_mode: if ms_active:
var data = get_game_data() _ms_request("update", {
Gotm.lobby.name = data.name "key": ms_key,
for key in data: "data": get_game_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 var time_left = 30
func _process(delta): func _process(delta):
@ -276,6 +247,3 @@ 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)

View file

@ -9,13 +9,16 @@ var loader = preload("res://Scenes/Loader.tscn")
onready var netgame = $"/root/Multiplayer" onready var netgame = $"/root/Multiplayer"
func _ready() -> void: func _ready() -> void:
queue.start(netgame.gotm_mode) var threads_supported = true
if OS.get_name() == "HTML5":
threads_supported = false
queue.start(threads_supported)
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: if queue.threaded:
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:
@ -24,7 +27,7 @@ func load_scene(scene_path: String, dependencies: Array) -> void:
get_tree().change_scene(scene_path) get_tree().change_scene(scene_path)
func _physics_process(_delta: float) -> void: func _physics_process(_delta: float) -> void:
if not netgame.gotm_mode: if queue.threaded:
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:

View file

@ -61,11 +61,8 @@ func _process(delta: float):
if Engine.editor_hint: if Engine.editor_hint:
return return
var current_speed_ratio = 0
var engines = get_engine_count() var engines = get_engine_count()
var max_speed = get_max_speed() var max_speed = get_max_speed()
if engines > 0:
current_speed_ratio = current_ship_speed / max_speed
# Ease ship speed/direction changes # Ease ship speed/direction changes
if abs(current_ship_direction) > PI*2: if abs(current_ship_direction) > PI*2:
@ -163,19 +160,14 @@ func serialize() -> Dictionary:
data.append([cell, tilemap.get_cellv(cell)]) data.append([cell, tilemap.get_cellv(cell)])
tilemapdata[tilemap.name] = data tilemapdata[tilemap.name] = data
# Get objects
var engines = serialize_children($engines)
var objects = serialize_children($objects)
var lights = serialize_children($lights)
var sockets = serialize_children($sockets)
var pois = serialize_children($pois)
return { return {
"tilemaps": tilemapdata, "tilemaps": tilemapdata,
"engines": engines, "engines": serialize_children($engines),
"objects": objects, "objects": serialize_children($objects),
"lights": lights, "lights": serialize_children($lights),
"sockets": sockets, "sockets": serialize_children($sockets),
"pois": pois, "pois": serialize_children($pois),
"areas": serialize_children($areas),
"ship": { "ship": {
"position": current_ship_position, "position": current_ship_position,
"target": current_ship_target, "target": current_ship_target,
@ -201,6 +193,8 @@ func deserialize(data: Dictionary) -> void:
deserialize_children($lights, data["lights"]) deserialize_children($lights, data["lights"])
deserialize_children($pois, data["pois"]) deserialize_children($pois, data["pois"])
deserialize_children($sockets, data["sockets"]) deserialize_children($sockets, data["sockets"])
deserialize_children($sockets, data["sockets"])
deserialize_children($areas, data["areas"], true)
# Set ship parameters # Set ship parameters
current_ship_position = data["ship"]["position"] current_ship_position = data["ship"]["position"]
@ -223,14 +217,17 @@ func serialize_children(node: Node) -> Dictionary:
} }
return data return data
func deserialize_children(node: Node, data: Dictionary) -> void: func deserialize_children(node: Node, data: Dictionary, deserialize_first: bool = false) -> void:
for node_name in data: for node_name in data:
var node_data = data[node_name] as Dictionary var node_data = data[node_name] as Dictionary
var node_scene = load(node_data.filename).instance() var node_scene = load(node_data.filename).instance()
node_scene.name = node_name node_scene.name = node_name
node_scene.transform = node_data.transform node_scene.transform = node_data.transform
if deserialize_first:
node_scene.deserialize(node_data.data)
node.add_child(node_scene, true) node.add_child(node_scene, true)
node_scene.deserialize(node_data.data) if not deserialize_first:
node_scene.deserialize(node_data.data)
# Lighting # Lighting

View file

@ -103,3 +103,5 @@ __meta__ = {
[node name="pois" type="Node2D" parent="."] [node name="pois" type="Node2D" parent="."]
z_index = 999 z_index = 999
[node name="areas" type="Node2D" parent="."]

View file

@ -21,10 +21,6 @@ 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
@ -49,14 +45,9 @@ 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
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.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: func set_scale(val) -> void:
scale = val scale = val
@ -84,7 +75,7 @@ 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
func _manual_join_pressed(): func _manual_join_pressed():
join_server($Popup/MarginContainer/VBoxContainer/HBoxContainer/LineEdit.text) join_server({ "address": $Popup/MarginContainer/VBoxContainer/HBoxContainer/LineEdit.text })
func _server_item_clicked(index): func _server_item_clicked(index):
$"/root/Music/BGM".stop() $"/root/Music/BGM".stop()

View file

@ -2,8 +2,6 @@ extends Control
class_name GameUI class_name GameUI
signal command(command)
enum ServerMenuItem { enum ServerMenuItem {
ServerInfo ServerInfo
} }
@ -16,7 +14,7 @@ enum PopupName {
onready var logs = $Logs onready var logs = $Logs
onready var scene = $"/root/scene" onready var scene = $"/root/scene"
const CHAT_RADIUS = 32*6 const CHAT_RADIUS = 32*10
func _ready() -> void: func _ready() -> void:
# Add options to menu buttons # Add options to menu buttons

View file

@ -1,11 +1,36 @@
[gd_scene load_steps=6 format=2] [gd_scene load_steps=15 format=2]
[ext_resource path="res://Scenes/UI/SpaceMap.tscn" type="PackedScene" id=1] [ext_resource path="res://Scenes/UI/SpaceMap.tscn" type="PackedScene" id=1]
[ext_resource path="res://Scenes/UI.gd" type="Script" id=2] [ext_resource path="res://Scenes/UI.gd" type="Script" id=2]
[ext_resource path="res://Scenes/UI/Panels/Logs.tscn" type="PackedScene" id=3] [ext_resource path="res://Graphics/UI/iosevka-aile-regular.ttf" type="DynamicFontData" id=3]
[ext_resource path="res://Scenes/UI/Panels/ServerInfo.tscn" type="PackedScene" id=4] [ext_resource path="res://Scenes/UI/Panels/ServerInfo.tscn" type="PackedScene" id=4]
[ext_resource path="res://Scenes/UI/Widgets/ResizablePanel.tscn" type="PackedScene" id=5]
[ext_resource path="res://Scenes/UI/Panels/Logs.gd" type="Script" id=6]
[ext_resource path="res://Graphics/UI/iosevka-aile-italic.ttf" type="DynamicFontData" id=7]
[ext_resource path="res://Graphics/UI/iosevka-aile-bold.ttf" type="DynamicFontData" id=8]
[ext_resource path="res://Graphics/UI/iosevka-aile-bolditalic.ttf" type="DynamicFontData" id=9]
[sub_resource type="StyleBoxFlat" id=1] [sub_resource type="DynamicFont" id=1]
size = 14
use_mipmaps = true
font_data = ExtResource( 9 )
[sub_resource type="DynamicFont" id=2]
size = 14
use_mipmaps = true
font_data = ExtResource( 7 )
[sub_resource type="DynamicFont" id=3]
size = 14
use_mipmaps = true
font_data = ExtResource( 8 )
[sub_resource type="DynamicFont" id=4]
size = 14
use_mipmaps = true
font_data = ExtResource( 3 )
[sub_resource type="StyleBoxFlat" id=5]
bg_color = Color( 0.133333, 0.12549, 0.203922, 0.705882 ) bg_color = Color( 0.133333, 0.12549, 0.203922, 0.705882 )
border_width_left = 4 border_width_left = 4
border_width_top = 4 border_width_top = 4
@ -31,11 +56,53 @@ __meta__ = {
[node name="MapPopup" parent="." instance=ExtResource( 1 )] [node name="MapPopup" parent="." instance=ExtResource( 1 )]
[node name="Logs" parent="." instance=ExtResource( 3 )] [node name="Logs" type="Control" parent="."]
margin_left = 10.0 margin_left = 10.0
margin_top = 10.0 margin_top = 10.0
margin_right = -857.0 margin_right = 475.0
margin_bottom = -566.0 margin_bottom = 273.0
script = ExtResource( 6 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ResizablePanel" parent="Logs" instance=ExtResource( 5 )]
title = "Logs"
[node name="RichTextLabel" type="RichTextLabel" parent="Logs/ResizablePanel"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = 40.0
margin_right = -10.0
margin_bottom = -40.0
focus_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/bold_italics_font = SubResource( 1 )
custom_fonts/italics_font = SubResource( 2 )
custom_fonts/bold_font = SubResource( 3 )
custom_fonts/normal_font = SubResource( 4 )
bbcode_enabled = true
selection_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LineEdit" type="LineEdit" parent="Logs/ResizablePanel"]
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 6.0
margin_top = -30.0
margin_right = -16.0
margin_bottom = -6.0
focus_mode = 1
caret_blink = true
caret_blink_speed = 0.5
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Menu" type="PanelContainer" parent="."] [node name="Menu" type="PanelContainer" parent="."]
anchor_left = 1.0 anchor_left = 1.0
@ -48,7 +115,7 @@ grow_horizontal = 0
grow_vertical = 0 grow_vertical = 0
size_flags_horizontal = 8 size_flags_horizontal = 8
size_flags_vertical = 8 size_flags_vertical = 8
custom_styles/panel = SubResource( 1 ) custom_styles/panel = SubResource( 5 )
__meta__ = { __meta__ = {
"_edit_use_anchors_": false "_edit_use_anchors_": false
} }
@ -90,3 +157,9 @@ margin_left = 31.2773
margin_top = 46.9222 margin_top = 46.9222
margin_right = 287.277 margin_right = 287.277
margin_bottom = 214.923 margin_bottom = 214.923
[connection signal="chat_message_sent" from="Logs" to="." method="_send_chat"]
[connection signal="focus_entered" from="Logs/ResizablePanel/LineEdit" to="Logs" method="_chat_bar_status" binds= [ true ]]
[connection signal="focus_exited" from="Logs/ResizablePanel/LineEdit" to="Logs" method="_chat_bar_status" binds= [ false ]]
[connection signal="mouse_entered" from="Logs/ResizablePanel/LineEdit" to="Logs" method="_chat_bar_focus" binds= [ true ]]
[connection signal="mouse_exited" from="Logs/ResizablePanel/LineEdit" to="Logs" method="_chat_bar_focus" binds= [ false ]]
[connection signal="text_entered" from="Logs/ResizablePanel/LineEdit" to="Logs" method="_text_submitted"]

View file

@ -1,5 +1,7 @@
extends Control extends Control
signal chat_message_sent(text)
onready var log_text = $ResizablePanel/RichTextLabel onready var log_text = $ResizablePanel/RichTextLabel
onready var chat_bar = $ResizablePanel/LineEdit onready var chat_bar = $ResizablePanel/LineEdit
@ -7,6 +9,8 @@ func _input(event: InputEvent) -> void:
if event is InputEventMouseButton: if event is InputEventMouseButton:
if not chat_bar_focus: if not chat_bar_focus:
chat_bar.release_focus() chat_bar.release_focus()
if event.is_action_released("ui_chat") and chat_bar_focus == false:
chat_bar.grab_focus()
func add_line(line: String) -> void: func add_line(line: String) -> void:
log_text.append_bbcode(line) log_text.append_bbcode(line)
@ -18,3 +22,8 @@ func _chat_bar_status(editing: bool) -> void:
func _chat_bar_focus(entered: bool) -> void: func _chat_bar_focus(entered: bool) -> void:
chat_bar_focus = entered chat_bar_focus = entered
func _text_submitted(text):
emit_signal("chat_message_sent", text)
chat_bar.text = ""
chat_bar.release_focus()

View file

@ -1,76 +0,0 @@
[gd_scene load_steps=11 format=2]
[ext_resource path="res://Graphics/UI/iosevka-aile-regular.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://Graphics/UI/iosevka-aile-italic.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://Graphics/UI/iosevka-aile-bold.ttf" type="DynamicFontData" id=3]
[ext_resource path="res://Graphics/UI/iosevka-aile-bolditalic.ttf" type="DynamicFontData" id=4]
[ext_resource path="res://Scenes/UI/Panels/Logs.gd" type="Script" id=5]
[ext_resource path="res://Scenes/UI/Widgets/ResizablePanel.tscn" type="PackedScene" id=6]
[sub_resource type="DynamicFont" id=1]
size = 14
use_mipmaps = true
font_data = ExtResource( 4 )
[sub_resource type="DynamicFont" id=2]
size = 14
use_mipmaps = true
font_data = ExtResource( 2 )
[sub_resource type="DynamicFont" id=3]
size = 14
use_mipmaps = true
font_data = ExtResource( 3 )
[sub_resource type="DynamicFont" id=4]
size = 14
use_mipmaps = true
font_data = ExtResource( 1 )
[node name="Logs" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 5 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ResizablePanel" parent="." instance=ExtResource( 6 )]
[node name="RichTextLabel" type="RichTextLabel" parent="ResizablePanel"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = 40.0
margin_right = -10.0
margin_bottom = -40.0
focus_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/bold_italics_font = SubResource( 1 )
custom_fonts/italics_font = SubResource( 2 )
custom_fonts/bold_font = SubResource( 3 )
custom_fonts/normal_font = SubResource( 4 )
bbcode_enabled = true
selection_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LineEdit" type="LineEdit" parent="ResizablePanel"]
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = -30.0
margin_right = -10.0
focus_mode = 1
caret_blink = true
caret_blink_speed = 0.5
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="focus_entered" from="ResizablePanel/LineEdit" to="." method="_chat_bar_status" binds= [ true ]]
[connection signal="focus_exited" from="ResizablePanel/LineEdit" to="." method="_chat_bar_status" binds= [ false ]]
[connection signal="mouse_entered" from="ResizablePanel/LineEdit" to="." method="_chat_bar_focus" binds= [ true ]]
[connection signal="mouse_exited" from="ResizablePanel/LineEdit" to="." method="_chat_bar_focus" binds= [ false ]]

View file

@ -1,6 +1,5 @@
extends WindowDialog extends WindowDialog
func _physics_process(delta): func _physics_process(_delta):
if not visible: if not visible:
return return
multiplayer

View file

@ -6,6 +6,7 @@ export var bgzoom = 50
const BORDER_WIDTH = 4 const BORDER_WIDTH = 4
const BORDER_LENGTH = 30 const BORDER_LENGTH = 30
const RADAR_EFFECT_DELAY = 1 const RADAR_EFFECT_DELAY = 1
const HALF_BORDER = BORDER_LENGTH/2
export(Texture) var background export(Texture) var background
export(Font) var font export(Font) var font
@ -87,13 +88,13 @@ func draw_target(viewport: Rect2, position: Vector2, color: Color):
var relative_pos = position - viewport.position var relative_pos = position - viewport.position
var clamped = Vector2(clamp(relative_pos.x, 0, viewport.size.x), clamp(relative_pos.y, 0, viewport.size.y)) var clamped = Vector2(clamp(relative_pos.x, 0, viewport.size.x), clamp(relative_pos.y, 0, viewport.size.y))
if relative_pos.x < 0: if relative_pos.x < 0:
draw_line(Vector2(0, clamped.y-BORDER_LENGTH/2), Vector2(0, clamped.y+BORDER_LENGTH/2), color, BORDER_WIDTH) draw_line(Vector2(0, clamped.y-HALF_BORDER), Vector2(0, clamped.y+HALF_BORDER), color, BORDER_WIDTH)
elif relative_pos.x > viewport.size.x: elif relative_pos.x > viewport.size.x:
draw_line(Vector2(viewport.size.x, clamped.y-BORDER_LENGTH/2), Vector2(viewport.size.x, clamped.y+BORDER_LENGTH/2), color, BORDER_WIDTH) draw_line(Vector2(viewport.size.x, clamped.y-HALF_BORDER), Vector2(viewport.size.x, clamped.y+HALF_BORDER), color, BORDER_WIDTH)
if relative_pos.y < 0: if relative_pos.y < 0:
draw_line(Vector2(clamped.x-BORDER_LENGTH/2, 0), Vector2(clamped.x+BORDER_LENGTH/2, 0), color, BORDER_WIDTH) draw_line(Vector2(clamped.x-HALF_BORDER, 0), Vector2(clamped.x+HALF_BORDER, 0), color, BORDER_WIDTH)
elif relative_pos.y > viewport.size.y: elif relative_pos.y > viewport.size.y:
draw_line(Vector2(clamped.x-BORDER_LENGTH/2, viewport.size.y), Vector2(clamped.x+BORDER_LENGTH/2, viewport.size.y), color, BORDER_WIDTH) draw_line(Vector2(clamped.x-HALF_BORDER, viewport.size.y), Vector2(clamped.x+HALF_BORDER, viewport.size.y), color, BORDER_WIDTH)
func _input(event): func _input(event):
if event is InputEventMouseButton: if event is InputEventMouseButton:

View file

@ -43,7 +43,7 @@ application/trademarks=""
[preset.1] [preset.1]
name="GOTM" name="HTML5"
platform="HTML5" platform="HTML5"
runnable=true runnable=true
custom_features="" custom_features=""

View file

@ -1,117 +0,0 @@
# 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 = {}

View file

@ -1,65 +0,0 @@
# 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)

View file

@ -1,50 +0,0 @@
# 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

View file

@ -1,155 +0,0 @@
# 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 = {}

View file

@ -1,122 +0,0 @@
# 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 = {}

View file

@ -1,52 +0,0 @@
# 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 = {}

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,30 +0,0 @@
<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.

View file

@ -1,108 +0,0 @@
# 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)

View file

@ -1,586 +0,0 @@
# 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)

View file

@ -1,48 +0,0 @@
# 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)

View file

@ -74,31 +74,6 @@ _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",
@ -143,21 +118,6 @@ _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": "",
@ -173,11 +133,6 @@ _global_script_class_icons={
"GameObjectScanner": "", "GameObjectScanner": "",
"GameUI": "", "GameUI": "",
"GameWorld": "", "GameWorld": "",
"GotmDebug": "",
"GotmFile": "",
"GotmLobby": "",
"GotmLobbyFetch": "",
"GotmUser": "",
"MapTiles": "", "MapTiles": "",
"Occluder": "", "Occluder": "",
"POI": "", "POI": "",
@ -186,10 +141,7 @@ _global_script_class_icons={
"ProbeArea": "", "ProbeArea": "",
"ProbeElectric": "", "ProbeElectric": "",
"ResourceQueue": "", "ResourceQueue": "",
"UICommand": "", "UICommand": ""
"_GotmDebugImpl": "",
"_GotmImpl": "",
"_GotmImplUtility": ""
} }
[application] [application]
@ -205,7 +157,6 @@ 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]
@ -264,6 +215,11 @@ sprint={
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null) "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
] ]
} }
ui_chat={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null)
]
}
[layer_names] [layer_names]