diff --git a/.gitattributes b/.gitattributes
index 627661a..3183cf5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -29,13 +29,6 @@
*.webm filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
-# Godot
-*.tscn filter=lfs diff=lfs merge=lfs -text
-*.tres filter=lfs diff=lfs merge=lfs -text
-*.scn filter=lfs diff=lfs merge=lfs -text
-*.res filter=lfs diff=lfs merge=lfs -text
-*.import filter=lfs diff=lfs merge=lfs -text
-
# Unity
*.anim filter=lfs diff=lfs merge=lfs -text
*.asset filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index 15b07f8..8a41f40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,12 @@
# Godot-specific ignores
.import/
export.cfg
-export_presets.cfg
+
+# Godot 4.1 can let export_presets be commited. This can be changed on an
+# example level if any future examples work with Godot <4.1.
+# https://github.com/github/gitignore/pull/2827
+# https://github.com/godotengine/godot/pull/76165
+# export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
diff --git a/godot/bomber/Dockerfile b/godot/bomber/Dockerfile
deleted file mode 100644
index 1fee748..0000000
--- a/godot/bomber/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-FROM ubuntu:22.04 AS builder
-
-# Install Godot & templates
-ENV GODOT_VERSION="4.0.2"
-RUN apt update -y \
- && apt install -y wget unzip \
- && wget https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip \
- && wget https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_export_templates.tpz
-
-RUN mkdir -p ~/.cache ~/.config/godot ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable \
- && unzip Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip \
- && mv Godot_v${GODOT_VERSION}-stable_linux.x86_64 /usr/local/bin/godot \
- && unzip Godot_v${GODOT_VERSION}-stable_export_templates.tpz \
- && mv templates/* ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable \
- && rm Godot_v${GODOT_VERSION}-stable_export_templates.tpz Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip
-
-# Build application
-WORKDIR /app
-COPY . .
-RUN mkdir -p build/linux \
- && godot -v --export-release "Linux/X11" --headless ./build/linux/game.x86_64
-
-# ===
-
-FROM ubuntu:22.04
-RUN apt update -y \
- && apt install -y expect-dev \
- && rm -rf /var/lib/apt/lists/*
-COPY --from=builder /app/build/linux/ /app
-
-# Unbuffer output so the logs get flushed
-CMD ["sh", "-c", "unbuffer /app/game.x86_64 --verbose --headless -- --server | cat"]
-
diff --git a/godot/bomber/LICENSE b/godot/bomber/LICENSE
index 8108a07..cfa3809 100644
--- a/godot/bomber/LICENSE
+++ b/godot/bomber/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Rivet
+Copyright (c) 2023 Rivet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/godot/bomber/README.md b/godot/bomber/README.md
index 10cda46..f02da87 100644
--- a/godot/bomber/README.md
+++ b/godot/bomber/README.md
@@ -1,12 +1,14 @@
-# Bomber
+# Multiplayer Bomber
-
-
-
+A multiplayer implementation of the classical bomberman game.
+One of the players should press "host", while the other
+should type in his address and press "play".
+Language: GDScript
-[Visit Tutorial](https://rivet.gg/learn/godot/tutorials/crash-course)
+Renderer: GLES 2
+Check out this demo on the asset library: https://godotengine.org/asset-library/asset/139
@@ -31,3 +33,4 @@
[Documentation](https://rivet.gg/learn/godot/tutorials/crash-course#step-4-deploy-to-rivet)
+![Screenshot](screenshots/bomber.png)
diff --git a/godot/bomber/addons/rivet/api/rivet_api.gd b/godot/bomber/addons/rivet/api/rivet_api.gd
new file mode 100644
index 0000000..22488a3
--- /dev/null
+++ b/godot/bomber/addons/rivet/api/rivet_api.gd
@@ -0,0 +1,132 @@
+class_name RivetApi
+const RivetRequest = preload("rivet_request.gd")
+
+static var CONFIGURATION_CACHE
+
+static func _get_configuration():
+ if CONFIGURATION_CACHE:
+ return CONFIGURATION_CACHE
+
+ if FileAccess.file_exists(RivetPluginBridge.RIVET_CONFIGURATION_FILE_PATH):
+ var config_file = ResourceLoader.load(RivetPluginBridge.RIVET_CONFIGURATION_FILE_PATH)
+ if config_file and 'new' in config_file:
+ CONFIGURATION_CACHE = config_file.new()
+ return CONFIGURATION_CACHE
+
+ if FileAccess.file_exists(RivetPluginBridge.RIVET_DEPLOYED_CONFIGURATION_FILE_PATH):
+ var deployed_config_file = ResourceLoader.load(RivetPluginBridge.RIVET_DEPLOYED_CONFIGURATION_FILE_PATH)
+ if deployed_config_file and 'new' in deployed_config_file:
+ CONFIGURATION_CACHE = deployed_config_file.new()
+ return CONFIGURATION_CACHE
+
+ push_warning("Rivet configuration file not found")
+ CONFIGURATION_CACHE = null
+ return CONFIGURATION_CACHE
+
+static func _get_api_url():
+ # Use plugin config if available
+ var plugin = RivetPluginBridge.get_plugin()
+ if plugin:
+ return plugin.api_endpoint
+
+ # Override shipped configuration endpoint
+ var url_env = OS.get_environment("RIVET_API_ENDPOINT")
+ if url_env:
+ return url_env
+
+ # Use configuration shipped with game
+ var config = _get_configuration()
+ if config:
+ return config.api_endpoint
+
+ # Fallback
+ return "https://api.rivet.gg"
+
+## Get authorization token used from within only the plugin for cloud-specific
+## actions.
+static func _get_cloud_token():
+ # Use plugin config if available
+ var plugin = RivetPluginBridge.get_plugin()
+ if plugin:
+ return plugin.cloud_token
+
+ OS.crash("Rivet cloud token not found, this should only be called within the plugin")
+
+## Get authorization token used for making requests from within the game.
+##
+## The priority of tokens is:
+##
+## - If in editor, use the plugin token
+## - If provided by environment, then use that (allows for testing)
+## - Assume config is provided by the game client
+static func _get_runtime_token():
+ # Use plugin config if available
+ var plugin = RivetPluginBridge.get_plugin()
+ if plugin:
+ return plugin.namespace_token
+
+ # Use configuration shipped with game
+ var token_env = OS.get_environment("RIVET_TOKEN")
+ if token_env:
+ return token_env
+
+ # Use configuration shipped with game
+ var config = _get_configuration()
+ if config:
+ return config.namespace_token
+
+ OS.crash("Rivet token not found, validate a config is shipped with the game in the .rivet folder")
+
+## Builds the headers for a request, including the authorization token
+static func _build_headers(service: String) -> PackedStringArray:
+ var token = _get_cloud_token() if service == "cloud" else _get_runtime_token()
+ return [
+ "Authorization: Bearer " + token,
+ ]
+
+## Builds a URL to Rivet cloud services
+static func _build_url(path: String, service: String) -> String:
+ var path_segments := path.split("/", false)
+ path_segments.remove_at(0)
+ return _get_api_url() + "/%s/%s" % [service, "/".join(path_segments)]
+
+## Gets service name from a path (e.g. /users/123 -> users)
+static func _get_service_from_path(path: String) -> String:
+ var path_segments := path.split("/", false)
+ return path_segments[0]
+
+## Creates a POST request to Rivet cloud services
+## @experimental
+static func POST(owner: Node, path: String, body: Dictionary) -> RivetRequest:
+ var service := _get_service_from_path(path)
+ var url := _build_url(path, service)
+ var body_json := JSON.stringify(body)
+
+ return RivetRequest.new(owner, HTTPClient.METHOD_POST, url, {
+ "headers": _build_headers(service),
+ "body": body_json
+ })
+
+## Creates a GET request to Rivet cloud services
+## @experimental
+static func GET(owner: Node, path: String, body: Dictionary) -> RivetRequest:
+ var service := _get_service_from_path(path)
+ var url := _build_url(path, service)
+ var body_json := JSON.stringify(body)
+
+ return RivetRequest.new(owner, HTTPClient.METHOD_GET, url, {
+ "headers": _build_headers(service),
+ "body": body_json
+ })
+
+## Creates a PUT request to Rivet cloud services
+## @experimental
+static func PUT(owner: Node, path: String, body: Dictionary) -> RivetRequest:
+ var service := _get_service_from_path(path)
+ var url := _build_url(path, service)
+ var body_json := JSON.stringify(body)
+
+ return RivetRequest.new(owner, HTTPClient.METHOD_PUT, url, {
+ "headers": _build_headers(service),
+ "body": body_json
+ })
diff --git a/godot/bomber/addons/rivet/api/rivet_packages.gd b/godot/bomber/addons/rivet/api/rivet_packages.gd
new file mode 100644
index 0000000..5f0bcd7
--- /dev/null
+++ b/godot/bomber/addons/rivet/api/rivet_packages.gd
@@ -0,0 +1,97 @@
+const _RivetResponse = preload("rivet_response.gd")
+
+## Lobbies
+## @experimental
+class Lobbies:
+ ## Finds a lobby based on the given criteria. If a lobby is not found and
+ ## prevent_auto_create_lobby is true, a new lobby will be created.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/find}[/url]
+ func find(body: Dictionary = {}):
+ return await Rivet.POST("matchmaker/lobbies/find", body).wait_completed()
+
+ ## Joins a specific lobby. This request will use the direct player count
+ ## configured for the lobby group.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/join}[/url]
+ func join(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.POST("matchmaker/lobbies/join", body).wait_completed()
+
+ ## Marks the current lobby as ready to accept connections. Players will not
+ ## be able to connect to this lobby until the lobby is flagged as ready.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/ready}[/url]
+ func ready(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.POST("matchmaker/lobbies/ready", body).wait_completed()
+
+ ## If is_closed is true, the matchmaker will no longer route players to the
+ ## lobby. Players can still join using the /join endpoint (this can be disabled
+ ## by the developer by rejecting all new connections after setting the lobby
+ ## to closed). Does not shutdown the lobby.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/set-closed}[/url]
+ func setClosed(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.PUT("matchmaker/lobbies/set_closed", body).wait_completed()
+
+ ## Creates a custom lobby.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/create}[/url]
+ func create(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.POST("matchmaker/lobbies/create", body).wait_completed()
+
+ ## Lists all open lobbies.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/list}[/url]
+ func list(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.GET("matchmaker/lobbies/list", body).wait_completed()
+
+ ##
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/set-state}[/url]
+ func setState(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.PUT("matchmaker/lobbies/state", body).wait_completed()
+
+ ##
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/lobbies/get-state}[/url]
+ func getState(lobby_id, body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.GET("matchmaker/lobbies/{lobby_id}/state".format({"lobby_id": lobby_id}), body).wait_completed()
+
+## Players
+## @experimental
+class Players:
+ ## Validates the player token is valid and has not already been consumed then
+ ## marks the player as connected.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/players/connected}[/url]
+ func connected(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.POST("matchmaker/players/connected", body).wait_completed()
+
+ ## Marks a player as disconnected. # Ghost Players.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/players/disconnected}[/url]
+ func disconnected(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.POST("matchmaker/players/disconnected", body).wait_completed()
+
+ ## Gives matchmaker statistics about the players in game.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/players/statistics}[/url]
+ func getStatistics(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.GET("matchmaker/players/statistics", body).wait_completed()
+
+class Regions:
+ ## Returns a list of regions available to this namespace.
+ ## Regions are sorted by most optimal to least optimal.
+ ## The player's IP address is used to calculate the regions' optimality.
+ ##
+ ## [url]{https://rivet.gg/docs/matchmaker/api/regions/list}[/url]
+ func list(body: Dictionary = {}) -> _RivetResponse:
+ return await Rivet.GET("matchmaker/regions", body).wait_completed()
+
+## Matchmaker
+## @experimental
+## @tutorial: https://rivet.gg/docs/matchmaker
+class Matchmaker:
+ static var lobbies: Lobbies = Lobbies.new()
+ static var players: Players = Players.new()
+ static var regions: Regions = Regions.new()
diff --git a/godot/bomber/addons/rivet/api/rivet_request.gd b/godot/bomber/addons/rivet/api/rivet_request.gd
new file mode 100644
index 0000000..95bb483
--- /dev/null
+++ b/godot/bomber/addons/rivet/api/rivet_request.gd
@@ -0,0 +1,57 @@
+extends RefCounted
+## A wrapper around HTTPRequest that emits a signal when the request is completed.
+## This is a workaround for the fact that `HTTPRequest.request()` is blocking.
+## To run a request, create a new RivetRequest, connect to the completed signal,
+## and call `request().wait_completed()` to wait for the request to complete.
+
+
+const _RivetResponse := preload("rivet_response.gd")
+const _RivetRequest := preload("rivet_request.gd")
+
+var response: _RivetResponse = null
+var _opts: Dictionary
+var _http_request: HTTPRequest
+
+var _success_callback: Callable
+var _failure_callback: Callable
+
+signal completed(response: _RivetResponse)
+signal succeeded(response: _RivetResponse)
+signal failed(response: _RivetResponse)
+
+func _init(owner: Node, method: HTTPClient.Method, url: String, opts: Variant = null):
+ self._http_request = HTTPRequest.new()
+ self._http_request.request_completed.connect(_on_request_completed)
+ self._opts = {
+ "method": method,
+ "url": url,
+ "body": opts.body,
+ "headers": opts.headers,
+ }
+ owner.add_child(self._http_request)
+ self._http_request.request(_opts.url, _opts.headers, _opts.method, _opts.body)
+
+func set_success_callback(callback: Callable) -> _RivetRequest:
+ self._success_callback = callback
+ return self
+
+func set_failure_callback(callback: Callable) -> _RivetRequest:
+ self._failure_callback = callback
+ return self
+
+func _on_request_completed(result, response_code, headers, body):
+ self.response = _RivetResponse.new(result, response_code, headers, body)
+ if result == OK:
+ succeeded.emit(response)
+ if self._success_callback:
+ self._success_callback.call(response)
+ else:
+ failed.emit(response)
+ if self._failure_callback:
+ self._failure_callback.call(response)
+ completed.emit(response)
+
+## Waits for the request to complete and returns the response in non-blocking way
+func wait_completed() -> _RivetResponse:
+ await completed
+ return response
diff --git a/godot/bomber/addons/rivet/api/rivet_response.gd b/godot/bomber/addons/rivet/api/rivet_response.gd
new file mode 100644
index 0000000..be954f9
--- /dev/null
+++ b/godot/bomber/addons/rivet/api/rivet_response.gd
@@ -0,0 +1,27 @@
+extends RefCounted
+## A response from the server. Contains the result, response code, headers, and body.
+## The body is a dictionary of the JSON response.
+##
+## @experimental
+
+## The result of the request. 0 is success, 1 is failure.
+var result: HTTPClient.Status
+
+## The response code from the server.
+var response_code: HTTPClient.ResponseCode
+
+## The headers from the server.
+var headers: PackedStringArray
+
+## The body of the response, as a JSON dictionary, could be a null.
+var body: Variant
+
+func _init(result: int, response_code: int, headers: PackedStringArray, response_body: PackedByteArray) -> void:
+ self.result = result
+ self.response_code = response_code
+ self.headers = headers
+
+ var json = JSON.new()
+ json.parse(response_body.get_string_from_utf8())
+ body = json.get_data()
+
diff --git a/godot/bomber/addons/rivet/devtools/dock/deploy_tab.gd b/godot/bomber/addons/rivet/devtools/dock/deploy_tab.gd
new file mode 100644
index 0000000..c2654ac
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/deploy_tab.gd
@@ -0,0 +1,33 @@
+@tool extends MarginContainer
+
+@onready var namespace_selector: OptionButton = %DeployNamespaceSelector
+@onready var manage_versions_button: Button = %ManageVersionButton
+@onready var build_deploy_button: Button = %BuildDeployButton
+
+func _ready() -> void:
+ manage_versions_button.pressed.connect(_on_manage_versions_button_pressed)
+ build_deploy_button.pressed.connect(_on_build_deploy_button_pressed)
+
+func _on_manage_versions_button_pressed() -> void:
+ _all_actions_set_disabled(true)
+
+ var result = await RivetPluginBridge.get_plugin().cli.run_command(["sidekick", "get-version", "--namespace", namespace_selector.current_value.namespace_id])
+ if result.exit_code != 0 or !("Ok" in result.output):
+ RivetPluginBridge.display_cli_error(self, result)
+
+ OS.shell_open(result.output["Ok"]["output"])
+ _all_actions_set_disabled(false)
+
+func _on_build_deploy_button_pressed() -> void:
+ _all_actions_set_disabled(true)
+
+ var result = await RivetPluginBridge.get_plugin().cli.run_command(["sidekick", "--show-terminal", "deploy", "--namespace", namespace_selector.current_value.name_id])
+ if result.exit_code != 0:
+ RivetPluginBridge.display_cli_error(self, result)
+
+ _all_actions_set_disabled(false)
+
+func _all_actions_set_disabled(disabled: bool) -> void:
+ namespace_selector.disabled = disabled
+ manage_versions_button.disabled = disabled
+ build_deploy_button.disabled = disabled
diff --git a/godot/bomber/addons/rivet/devtools/dock/deploy_tab.tscn b/godot/bomber/addons/rivet/devtools/dock/deploy_tab.tscn
new file mode 100644
index 0000000..d4608b5
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/deploy_tab.tscn
@@ -0,0 +1,56 @@
+[gd_scene load_steps=4 format=3 uid="uid://soum1c8oyrso"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/deploy_tab.gd" id="1_7k6ip"]
+[ext_resource type="PackedScene" uid="uid://bogw8dj8rr202" path="res://addons/rivet/devtools/dock/elements/namespace_menu_button.tscn" id="2_5hxk2"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dfadg"]
+
+[node name="Deploy" type="MarginContainer"]
+offset_right = 734.0
+offset_bottom = 109.0
+script = ExtResource("1_7k6ip")
+
+[node name="Deployment Fieds" type="VBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Deployment Label" type="RichTextLabel" parent="Deployment Fieds"]
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_dfadg")
+bbcode_enabled = true
+text = "[b]Build & Deploy Server[/b]"
+fit_content = true
+scroll_active = false
+shortcut_keys_enabled = false
+
+[node name="DeployNamespaceSelector" parent="Deployment Fieds" instance=ExtResource("2_5hxk2")]
+layout_mode = 2
+selected = -1
+current_value = {
+"create_ts": "2023-11-19T05:46:56.378Z",
+"display_name": "Production",
+"name_id": "prod",
+"namespace_id": "215bd313-b4c7-4932-b9bd-64f8f09b7fe8",
+"version": {
+"create_ts": "2023-11-19T05:46:56.171Z",
+"display_name": "0.0.1",
+"version_id": "73881376-21fa-4fb2-93f2-2f76fb3bac19"
+},
+"version_id": "73881376-21fa-4fb2-93f2-2f76fb3bac19"
+}
+
+[node name="Actions" type="HBoxContainer" parent="Deployment Fieds"]
+layout_mode = 2
+
+[node name="BuildDeployButton" type="Button" parent="Deployment Fieds/Actions"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_default_cursor_shape = 2
+text = "Build & Deploy"
+
+[node name="ManageVersionButton" type="Button" parent="Deployment Fieds/Actions"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_default_cursor_shape = 2
+text = "Manage Versions"
diff --git a/godot/bomber/addons/rivet/devtools/dock/dock.gd b/godot/bomber/addons/rivet/devtools/dock/dock.gd
new file mode 100644
index 0000000..5eac679
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/dock.gd
@@ -0,0 +1,28 @@
+@tool extends Control
+## Mainpoint of the plugin's UI
+
+## Enum representing indexes of the children of this node
+enum Screen {
+ Login,
+ Settings,
+ Loading,
+ Installer,
+}
+
+func _ready() -> void:
+ change_current_screen(Screen.Installer)
+
+
+func reload() -> void:
+ var instance = load("res://addons/rivet/devtools/dock/dock.tscn").instantiate()
+ replace_by(instance)
+ instance.grab_focus()
+
+
+func change_current_screen(scene: Screen):
+ for idx in get_child_count():
+ var child := get_child(idx)
+ if "visible" in child:
+ child.visible = idx == scene
+ if idx == scene and child.has_method("prepare"):
+ child.prepare()
diff --git a/godot/bomber/addons/rivet/devtools/dock/dock.tscn b/godot/bomber/addons/rivet/devtools/dock/dock.tscn
new file mode 100644
index 0000000..ba952fd
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/dock.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=6 format=3 uid="uid://l8cfdaru7ibw"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/dock.gd" id="1_xspk7"]
+[ext_resource type="PackedScene" uid="uid://mag2n5yvyus8" path="res://addons/rivet/devtools/dock/login.tscn" id="2_qo12a"]
+[ext_resource type="PackedScene" uid="uid://ceovepvn1782o" path="res://addons/rivet/devtools/dock/settings.tscn" id="3_8srmc"]
+[ext_resource type="PackedScene" uid="uid://cpiafwq88eamc" path="res://addons/rivet/devtools/dock/loading.tscn" id="4_mdhqv"]
+[ext_resource type="PackedScene" uid="uid://d3l0arylk0h43" path="res://addons/rivet/devtools/dock/installer.tscn" id="5_gdmi1"]
+
+[node name="Rivet" type="MarginContainer"]
+offset_top = 92.0
+offset_right = 1152.0
+offset_bottom = 92.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/margin_left = 2
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 2
+theme_override_constants/margin_bottom = 8
+script = ExtResource("1_xspk7")
+
+[node name="Login" parent="." instance=ExtResource("2_qo12a")]
+visible = false
+layout_mode = 2
+
+[node name="Settings" parent="." instance=ExtResource("3_8srmc")]
+visible = false
+layout_mode = 2
+
+[node name="Loading" parent="." instance=ExtResource("4_mdhqv")]
+visible = false
+layout_mode = 2
+
+[node name="Installer" parent="." instance=ExtResource("5_gdmi1")]
+layout_mode = 2
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.gd b/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.gd
new file mode 100644
index 0000000..2a1cecd
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.gd
@@ -0,0 +1,38 @@
+@tool extends HBoxContainer
+
+signal selected()
+
+@export var tab_container: TabContainer
+
+var disabled: bool = false:
+ set(value):
+ disabled = value
+ for i in get_child_count():
+ var child = get_child(i)
+ if child is Button:
+ child.disabled = disabled
+
+var current = 0
+
+func _ready() -> void:
+ for i in get_child_count():
+ var child = get_child(i)
+ if child is Button:
+ child.toggle_mode = true
+ child.pressed.connect(_select_button.bind(i))
+ if i == 0:
+ child.set_pressed_no_signal(true)
+
+func _select_button(curr: int) -> void:
+ current = curr
+ if tab_container:
+ tab_container.set_current_tab(curr)
+ for i in get_child_count():
+ var child = get_child(i)
+ if child is Button:
+ child.set_pressed_no_signal(curr==i)
+ selected.emit()
+
+
+func set_current_button(button: int) -> void:
+ _select_button(button)
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.tscn b/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.tscn
new file mode 100644
index 0000000..6dc4e64
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/buttons_bar.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://bdpu38hakasqq"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/elements/buttons_bar.gd" id="1_mg5dk"]
+
+[node name="ButtonsBar" type="HBoxContainer"]
+script = ExtResource("1_mg5dk")
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/links_container.tscn b/godot/bomber/addons/rivet/devtools/dock/elements/links_container.tscn
new file mode 100644
index 0000000..11e5e2c
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/links_container.tscn
@@ -0,0 +1,20 @@
+[gd_scene format=3 uid="uid://bk1uwgw1hhq2p"]
+
+[node name="LinksContainer" type="HBoxContainer"]
+theme_override_constants/separation = 16
+alignment = 1
+
+[node name="HubLink" type="LinkButton" parent="."]
+layout_mode = 2
+text = "Hub"
+uri = "https://hub.rivet.gg/"
+
+[node name="DocsLink" type="LinkButton" parent="."]
+layout_mode = 2
+text = "Docs"
+uri = "https://rivet.gg/docs"
+
+[node name="DiscordLink" type="LinkButton" parent="."]
+layout_mode = 2
+text = "Discord"
+uri = "https://rivet.gg/discord"
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.gd b/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.gd
new file mode 100644
index 0000000..20e94c5
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.gd
@@ -0,0 +1,32 @@
+@tool extends Button
+
+@export var loading: bool: set = _set_loading
+
+var _tween: Tween
+
+func _set_loading(value) -> void:
+ loading = value
+ disabled = value
+
+ if _tween:
+ _tween.kill()
+
+ if value:
+ _tween = get_tree().create_tween()
+
+ var icons: Array[Texture2D] = [
+ get_theme_icon("Progress1", "EditorIcons"),
+ get_theme_icon("Progress2", "EditorIcons"),
+ get_theme_icon("Progress3", "EditorIcons"),
+ get_theme_icon("Progress4", "EditorIcons"),
+ get_theme_icon("Progress5", "EditorIcons"),
+ get_theme_icon("Progress6", "EditorIcons"),
+ get_theme_icon("Progress7", "EditorIcons"),
+ get_theme_icon("Progress8", "EditorIcons"),
+ get_theme_icon("Progress9", "EditorIcons"),
+ ]
+ for idx in icons.size():
+ _tween.tween_property(self, "icon", icons[idx], 0 if idx == 0 else 1)
+ _tween.set_loops()
+ else:
+ icon = null
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.tscn b/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.tscn
new file mode 100644
index 0000000..824cb05
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/loading_button.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://cdad7w76me3eu"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/elements/loading_button.gd" id="1_4ofna"]
+
+[node name="LoadingButton" type="Button"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/h_separation = 8
+script = ExtResource("1_4ofna")
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.gd b/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.gd
new file mode 100644
index 0000000..d253415
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.gd
@@ -0,0 +1,9 @@
+@tool extends HBoxContainer
+
+@onready var logo: TextureRect = %Logo
+var logo_dark = preload("../../../images/icon-text-black.svg")
+var logo_light = preload("../../../images/icon-text-white.svg")
+
+func _ready() -> void:
+ var is_dark = get_theme_color("font_color", "Editor").get_luminance() < 0.5
+ logo.texture = logo_dark if is_dark else logo_light
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.tscn b/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.tscn
new file mode 100644
index 0000000..3147797
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/logo_container.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=3 format=3 uid="uid://dldxcm1l8nnnf"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/elements/logo_container.gd" id="1_pgpb4"]
+[ext_resource type="Texture2D" uid="uid://10vqh72x3wrr" path="res://addons/rivet/images/icon-text-black.svg" id="2_c8wnq"]
+
+[node name="LogoContainer" type="HBoxContainer"]
+size_flags_horizontal = 3
+alignment = 1
+script = ExtResource("1_pgpb4")
+
+[node name="Logo" type="TextureRect" parent="."]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(2.08165e-12, 40)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+texture = ExtResource("2_c8wnq")
+expand_mode = 1
+stretch_mode = 5
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.gd b/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.gd
new file mode 100644
index 0000000..256ac6a
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.gd
@@ -0,0 +1,31 @@
+@tool extends OptionButton
+## A control that displays a list of namespaces and allows the user to select one.
+
+@export var current_value: Dictionary
+
+var namespaces: Array:
+ get: return RivetPluginBridge.instance.game_namespaces
+
+func _ready():
+ if RivetPluginBridge.is_part_of_edited_scene(self):
+ return
+ disabled = true
+ _update_menu_button(namespaces)
+ item_selected.connect(_on_item_selected)
+ RivetPluginBridge.instance.bootstrapped.connect(_on_plugin_bootstrapped)
+
+func _update_menu_button(value: Array) -> void:
+ clear()
+ for i in value.size():
+ add_item("%s (v%s)" % [namespaces[i].display_name, namespaces[i].version.display_name], i)
+
+func _on_item_selected(idx: int):
+ _select_menu_item(idx)
+
+func _select_menu_item(idx: int) -> void:
+ current_value = namespaces[idx]
+
+func _on_plugin_bootstrapped() -> void:
+ disabled = false
+ _update_menu_button(namespaces)
+ _select_menu_item(0)
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.tscn b/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.tscn
new file mode 100644
index 0000000..a924ae0
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/elements/namespace_menu_button.tscn
@@ -0,0 +1,11 @@
+[gd_scene load_steps=2 format=3 uid="uid://bogw8dj8rr202"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/elements/namespace_menu_button.gd" id="1_c2uah"]
+
+[node name="NamespaceMenuButton" type="OptionButton"]
+unique_name_in_owner = true
+offset_right = 190.0
+offset_bottom = 31.0
+size_flags_horizontal = 3
+disabled = true
+script = ExtResource("1_c2uah")
diff --git a/godot/bomber/addons/rivet/devtools/dock/installer.gd b/godot/bomber/addons/rivet/devtools/dock/installer.gd
new file mode 100644
index 0000000..7c13b86
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/installer.gd
@@ -0,0 +1,37 @@
+@tool extends Control
+
+@onready var InstallButton: Button = %InstallButton
+@onready var InstallDialog: AcceptDialog = %InstallDialog
+@onready var InstallLabel: RichTextLabel = %InstallLabel
+
+func prepare() -> void:
+ InstallLabel.add_theme_font_override(&"mono_font", get_theme_font(&"output_source_mono", &"EditorFonts"))
+ InstallLabel.add_theme_font_override(&"bold_font", get_theme_font(&"bold", &"EditorFonts"))
+ InstallLabel.add_theme_stylebox_override(&"normal", get_theme_stylebox(&"bg", &"AssetLib"))
+
+ InstallLabel.text = InstallLabel.text.replace(&"%%version%%", RivetPluginBridge.get_plugin().cli.REQUIRED_RIVET_CLI_VERSION).replace(&"%%bin_dir%%", RivetPluginBridge.get_plugin().cli.get_bin_dir())
+ InstallButton.loading = true
+ var error = await RivetPluginBridge.get_plugin().cli.check_existence()
+ if error:
+ InstallButton.loading = false
+ return
+ owner.change_current_screen(owner.Screen.Login)
+
+func _ready() -> void:
+ InstallButton.pressed.connect(_on_install_button_pressed)
+
+func _on_install_button_pressed() -> void:
+ InstallButton.loading = true
+ var result = await RivetPluginBridge.get_plugin().cli.install()
+ if result.exit_code == 0:
+ var error = await RivetPluginBridge.get_plugin().cli.check_existence()
+ if not error:
+ InstallDialog.title = &"Success!"
+ InstallDialog.dialog_text = &"Rivet installed successfully!\nInstalled Rivet %s in %s" % [RivetPluginBridge.get_plugin().cli.REQUIRED_RIVET_CLI_VERSION, RivetPluginBridge.get_plugin().cli.get_bin_dir()]
+ InstallDialog.popup_centered()
+ owner.change_current_screen(owner.Screen.Login)
+ return
+ InstallDialog.title = &"Error!"
+ InstallDialog.dialog_text = &"Rivet installation failed! Please try again.\n\n%s" % result.output
+ InstallDialog.popup_centered()
+ InstallButton.loading = false
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/installer.tscn b/godot/bomber/addons/rivet/devtools/dock/installer.tscn
new file mode 100644
index 0000000..de1a031
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/installer.tscn
@@ -0,0 +1,53 @@
+[gd_scene load_steps=5 format=3 uid="uid://d3l0arylk0h43"]
+
+[ext_resource type="PackedScene" uid="uid://dldxcm1l8nnnf" path="res://addons/rivet/devtools/dock/elements/logo_container.tscn" id="1_nj27r"]
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/installer.gd" id="1_s8aji"]
+[ext_resource type="PackedScene" uid="uid://bk1uwgw1hhq2p" path="res://addons/rivet/devtools/dock/elements/links_container.tscn" id="2_rgtqq"]
+[ext_resource type="PackedScene" uid="uid://cdad7w76me3eu" path="res://addons/rivet/devtools/dock/elements/loading_button.tscn" id="4_ahrlb"]
+
+[node name="Installer" type="VBoxContainer"]
+script = ExtResource("1_s8aji")
+
+[node name="LogoContainer" parent="." instance=ExtResource("1_nj27r")]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="LinksContainer" parent="." instance=ExtResource("2_rgtqq")]
+layout_mode = 2
+
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
+layout_mode = 2
+theme_override_constants/separation = 16
+alignment = 1
+
+[node name="InstallLabel" type="RichTextLabel" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(300, 2.08165e-12)
+layout_mode = 2
+bbcode_enabled = true
+text = "[center]Looks like you don't have installed Rivet CLI
+
+[b]Required[/b] [code]%%version%%[/code] in [code]%%bin_dir%%[/code].
+
+In order to use this plugin you need to install it. Use button below to auto-install it.[/center]"
+fit_content = true
+scroll_active = false
+autowrap_mode = 2
+deselect_on_focus_loss_enabled = false
+drag_and_drop_selection_enabled = false
+
+[node name="InstallButton" parent="CenterContainer/VBoxContainer" instance=ExtResource("4_ahrlb")]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Install"
+
+[node name="InstallDialog" type="AcceptDialog" parent="."]
+unique_name_in_owner = true
+size = Vector2i(112, 100)
diff --git a/godot/bomber/addons/rivet/devtools/dock/loading.gd b/godot/bomber/addons/rivet/devtools/dock/loading.gd
new file mode 100644
index 0000000..ab2136e
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/loading.gd
@@ -0,0 +1,9 @@
+extends VBoxContainer
+
+
+func _ready() -> void:
+ %CancelButton.pressed.connect(_on_cancel_button_pressed)
+
+func _on_cancel_button_pressed() -> void:
+ # TODO(forest): cancel cli command
+ owner.change_current_screen(owner.Screen.Login)
diff --git a/godot/bomber/addons/rivet/devtools/dock/loading.tscn b/godot/bomber/addons/rivet/devtools/dock/loading.tscn
new file mode 100644
index 0000000..f111cd9
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/loading.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=4 format=3 uid="uid://cpiafwq88eamc"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/loading.gd" id="1_2ygnd"]
+[ext_resource type="PackedScene" uid="uid://dldxcm1l8nnnf" path="res://addons/rivet/devtools/dock/elements/logo_container.tscn" id="1_oda17"]
+[ext_resource type="PackedScene" uid="uid://bk1uwgw1hhq2p" path="res://addons/rivet/devtools/dock/elements/links_container.tscn" id="2_c2osa"]
+
+[node name="Loading" type="VBoxContainer"]
+theme_override_constants/separation = 16
+script = ExtResource("1_2ygnd")
+
+[node name="LogoContainer" parent="." instance=ExtResource("1_oda17")]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="LinksContainer" parent="." instance=ExtResource("2_c2osa")]
+layout_mode = 2
+
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
+layout_mode = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "Loading..."
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="CancelButton" type="Button" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Cancel"
diff --git a/godot/bomber/addons/rivet/devtools/dock/login.gd b/godot/bomber/addons/rivet/devtools/dock/login.gd
new file mode 100644
index 0000000..7a29fa4
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/login.gd
@@ -0,0 +1,62 @@
+@tool extends Control
+## A button that logs the user in to the Rivet using Rivet CLI.
+
+@onready var log_in_button: Button = %LogInButton
+@onready var api_endpoint_line_edit: LineEdit = %ApiEndpointLineEdit
+@onready var advanced_options_button: Button = %AdvancedOptionsButton
+@onready var api_endpoint_field: Control = %ApiEndpointField
+
+func prepare() -> void:
+ var result = await RivetPluginBridge.get_plugin().cli.run_command([
+ "sidekick",
+ "check-login-state",
+ ])
+ if result.exit_code == result.ExitCode.SUCCESS and "Ok" in result.output:
+ owner.change_current_screen(owner.Screen.Settings)
+ return
+
+func _ready():
+ log_in_button.pressed.connect(_on_button_pressed)
+ advanced_options_button.pressed.connect(_on_advanced_options_button_pressed)
+ advanced_options_button.icon = get_theme_icon("arrow", "OptionButton")
+
+func _on_button_pressed() -> void:
+ log_in_button.disabled = true
+ var api_address = api_endpoint_line_edit.text
+ var result = await RivetPluginBridge.get_plugin().cli.run_command([
+ "--api-endpoint",
+ api_address,
+ "sidekick",
+ "get-link",
+ ])
+ if result.exit_code != result.ExitCode.SUCCESS or !("Ok" in result.output):
+ RivetPluginBridge.display_cli_error(self, result)
+ log_in_button.disabled = false
+ return
+ var data: Dictionary = result.output["Ok"]
+
+ # Now that we have the link, open it in the user's browser
+ OS.shell_open(data["device_link_url"])
+
+ owner.change_current_screen(owner.Screen.Loading)
+
+ # Long-poll the Rivet API until the user has logged in
+ result = await RivetPluginBridge.get_plugin().cli.run_command([
+ "--api-endpoint",
+ api_address,
+ "sidekick",
+ "wait-for-login",
+ "--device-link-token",
+ data["device_link_token"],
+ ])
+
+ if result.exit_code != result.ExitCode.SUCCESS or !("Ok" in result.output):
+ RivetPluginBridge.display_cli_error(self, result)
+ log_in_button.disabled = false
+ return
+
+ log_in_button.disabled = false
+ owner.change_current_screen(owner.Screen.Settings)
+
+func _on_advanced_options_button_pressed():
+ api_endpoint_field.visible = !api_endpoint_field.visible
diff --git a/godot/bomber/addons/rivet/devtools/dock/login.tscn b/godot/bomber/addons/rivet/devtools/dock/login.tscn
new file mode 100644
index 0000000..0f3eacd
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/login.tscn
@@ -0,0 +1,63 @@
+[gd_scene load_steps=6 format=3 uid="uid://mag2n5yvyus8"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/login.gd" id="1_spqru"]
+[ext_resource type="PackedScene" uid="uid://dldxcm1l8nnnf" path="res://addons/rivet/devtools/dock/elements/logo_container.tscn" id="2_qku2v"]
+[ext_resource type="PackedScene" uid="uid://bk1uwgw1hhq2p" path="res://addons/rivet/devtools/dock/elements/links_container.tscn" id="3_cecsx"]
+
+[sub_resource type="Image" id="Image_w2r7k"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 179, 179, 179, 153, 178, 178, 178, 166, 184, 184, 184, 18, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 184, 184, 184, 18, 178, 178, 178, 166, 178, 178, 178, 151, 255, 255, 255, 0, 255, 255, 255, 0, 178, 178, 178, 166, 178, 178, 178, 217, 178, 178, 178, 179, 184, 184, 184, 18, 255, 255, 255, 0, 255, 255, 255, 0, 175, 175, 175, 19, 178, 178, 178, 179, 178, 178, 178, 217, 178, 178, 178, 165, 255, 255, 255, 0, 255, 255, 255, 0, 184, 184, 184, 18, 177, 177, 177, 179, 178, 178, 178, 217, 179, 179, 179, 180, 175, 175, 175, 19, 175, 175, 175, 19, 179, 179, 179, 180, 178, 178, 178, 217, 177, 177, 177, 179, 180, 180, 180, 17, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 184, 184, 184, 18, 177, 177, 177, 179, 178, 178, 178, 217, 178, 178, 178, 181, 178, 178, 178, 181, 178, 178, 178, 217, 177, 177, 177, 179, 180, 180, 180, 17, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 184, 184, 184, 18, 177, 177, 177, 179, 178, 178, 178, 217, 178, 178, 178, 217, 177, 177, 177, 179, 180, 180, 180, 17, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 184, 184, 184, 18, 178, 178, 178, 165, 178, 178, 178, 165, 180, 180, 180, 17, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 12,
+"mipmaps": false,
+"width": 12
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_hy2yt"]
+image = SubResource("Image_w2r7k")
+
+[node name="Login" type="VBoxContainer"]
+theme_override_constants/separation = 16
+script = ExtResource("1_spqru")
+
+[node name="LogoContainer" parent="." instance=ExtResource("2_qku2v")]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="LinksContainer" parent="." instance=ExtResource("3_cecsx")]
+layout_mode = 2
+
+[node name="LogInButton" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Sign in to Rivet"
+
+[node name="AdvancedOptionsButton" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Advanced options"
+icon = SubResource("ImageTexture_hy2yt")
+flat = true
+alignment = 0
+icon_alignment = 2
+
+[node name="ApiEndpointField" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="Label" type="Label" parent="ApiEndpointField"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "API endpoint"
+
+[node name="ApiEndpointLineEdit" type="LineEdit" parent="ApiEndpointField"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "https://api.rivet.gg"
+middle_mouse_paste_enabled = false
+drag_and_drop_selection_enabled = false
diff --git a/godot/bomber/addons/rivet/devtools/dock/playtest_tab.gd b/godot/bomber/addons/rivet/devtools/dock/playtest_tab.gd
new file mode 100644
index 0000000..f00f5d4
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/playtest_tab.gd
@@ -0,0 +1,113 @@
+@tool extends MarginContainer
+
+const ButtonsBar = preload("elements/buttons_bar.gd")
+
+@onready var namespace_description: RichTextLabel = %NamespaceDescription
+@onready var buttons_bar: ButtonsBar = %ButtonsBar
+@onready var warning: RichTextLabel = %WarningLabel
+@onready var error: RichTextLabel = %ErrorLabel
+@onready var deploy_button: Button = %DeployButton
+@onready var namespace_selector = %AuthNamespaceSelector
+
+func _ready() -> void:
+ if get_tree().edited_scene_root == self:
+ return # This is the scene opened in the editor!
+ namespace_description.add_theme_font_override(&"mono_font", get_theme_font(&"output_source_mono", &"EditorFonts"))
+ namespace_description.add_theme_font_override(&"bold_font", get_theme_font(&"bold", &"EditorFonts"))
+ namespace_description.add_theme_stylebox_override(&"normal", get_theme_stylebox(&"bg", &"AssetLib"))
+ namespace_description.meta_clicked.connect(func(meta): OS.shell_open(str(meta)))
+
+ warning.add_theme_color_override(&"default_color", get_theme_color(&"warning_color", &"Editor"))
+ warning.add_theme_stylebox_override(&"normal", get_theme_stylebox(&"bg", &"AssetLib"))
+ var warning_text = warning.text
+ warning.text = ""
+ warning.add_image(get_theme_icon("StatusWarning", "EditorIcons"))
+ warning.add_text(warning_text)
+
+ error.add_theme_color_override(&"default_color", get_theme_color("error_color", "Editor"))
+ error.add_theme_stylebox_override(&"normal", get_theme_stylebox(&"bg", &"AssetLib"))
+ var error_text = error.text
+ error.text = ""
+ error.add_image(get_theme_icon("StatusError", "EditorIcons"))
+ error.add_text(error_text)
+
+ warning.visible = false
+ error.visible = false
+ deploy_button.visible = false
+
+ RivetPluginBridge.instance.bootstrapped.connect(_on_bootstrapped)
+ namespace_selector.item_selected.connect(_on_namespace_selector_item_selected)
+ deploy_button.pressed.connect(_on_deploy_button_pressed)
+ buttons_bar.selected.connect(_on_buttons_bar_selected)
+
+func _on_namespace_selector_item_selected(id: int) -> void:
+ _update_warnings()
+
+func _on_buttons_bar_selected() -> void:
+ _update_warnings()
+
+func _on_bootstrapped() -> void:
+ _update_warnings()
+
+func _update_warnings() -> void:
+ var is_local_machine = buttons_bar.current == 0
+ var is_online_server = buttons_bar.current == 1
+ var current_namespace = namespace_selector.current_value
+
+ # Local machine
+ if is_local_machine:
+ warning.visible = false
+ error.visible = false
+ deploy_button.visible = false
+ _generate_dev_auth_token(current_namespace)
+ return
+
+ # Online server
+ if is_online_server:
+ # It means that user hasn't deployed anything to this namespace yet
+ if current_namespace.version.display_name == "0.0.1":
+ warning.visible = false
+ error.visible = true
+ deploy_button.visible = true
+ else:
+ warning.visible = true
+ error.visible = false
+ deploy_button.visible = false
+ _generate_public_auth_token(current_namespace)
+ return
+
+func _all_actions_set_disabled(disabled: bool) -> void:
+ namespace_selector.disabled = disabled
+ buttons_bar.disabled = disabled
+
+func _generate_dev_auth_token(ns) -> void:
+ _actions_disabled_while(func():
+ var result = await RivetPluginBridge.get_plugin().cli.run_command(["sidekick", "get-namespace-development-token", "--namespace", ns.name_id])
+ if result.exit_code != 0 or !("Ok" in result.output):
+ RivetPluginBridge.display_cli_error(self, result)
+ return
+
+ RivetPluginBridge.get_plugin().namespace_token = result.output["Ok"]["token"]
+ RivetPluginBridge.instance.save_configuration()
+ )
+
+func _generate_public_auth_token(ns) -> void:
+ _actions_disabled_while(func():
+ var result = await RivetPluginBridge.get_plugin().cli.run_command(["sidekick", "get-namespace-public-token", "--namespace", ns.name_id])
+ if result.exit_code != 0 or !("Ok" in result.output):
+ RivetPluginBridge.display_cli_error(self, result)
+ return
+
+ RivetPluginBridge.get_plugin().namespace_token = result.output["Ok"]["token"]
+ RivetPluginBridge.instance.save_configuration()
+ )
+
+func _actions_disabled_while(fn: Callable) -> void:
+ _all_actions_set_disabled(true)
+ await fn.call()
+ _all_actions_set_disabled(false)
+
+func _on_deploy_button_pressed() -> void:
+ owner.change_tab(1)
+ owner.deploy_tab.namespace_selector.current_value = namespace_selector.current_value
+ owner.deploy_tab.namespace_selector.selected = namespace_selector.selected
diff --git a/godot/bomber/addons/rivet/devtools/dock/playtest_tab.tscn b/godot/bomber/addons/rivet/devtools/dock/playtest_tab.tscn
new file mode 100644
index 0000000..e8881e9
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/playtest_tab.tscn
@@ -0,0 +1,72 @@
+[gd_scene load_steps=4 format=3 uid="uid://b17eqs0bmrncs"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/playtest_tab.gd" id="1_4p5p5"]
+[ext_resource type="PackedScene" uid="uid://bogw8dj8rr202" path="res://addons/rivet/devtools/dock/elements/namespace_menu_button.tscn" id="1_brqcl"]
+[ext_resource type="PackedScene" uid="uid://bdpu38hakasqq" path="res://addons/rivet/devtools/dock/elements/buttons_bar.tscn" id="2_hd7vo"]
+
+[node name="Playtest" type="MarginContainer"]
+script = ExtResource("1_4p5p5")
+
+[node name="PlaytestContent" type="VBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="ButtonsBar" parent="PlaytestContent" instance=ExtResource("2_hd7vo")]
+unique_name_in_owner = true
+layout_mode = 2
+alignment = 1
+
+[node name="ThisMachineButton" type="Button" parent="PlaytestContent/ButtonsBar"]
+layout_mode = 2
+tooltip_text = "Test against your local machine to iterate quickly in the editor."
+mouse_default_cursor_shape = 2
+toggle_mode = true
+button_pressed = true
+text = "This machine"
+
+[node name="RivetServersButton" type="Button" parent="PlaytestContent/ButtonsBar"]
+layout_mode = 2
+tooltip_text = "Deploy your game to Rivet's servers to playtest with others."
+mouse_default_cursor_shape = 2
+toggle_mode = true
+text = "Rivet servers"
+
+[node name="VBoxContainer" type="VBoxContainer" parent="PlaytestContent"]
+layout_mode = 2
+
+[node name="Namespace" type="Label" parent="PlaytestContent/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Namespace"
+
+[node name="AuthNamespaceSelector" parent="PlaytestContent/VBoxContainer" instance=ExtResource("1_brqcl")]
+layout_mode = 2
+
+[node name="NamespaceDescription" type="RichTextLabel" parent="PlaytestContent/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+bbcode_enabled = true
+text = "Configure which configuration should be emulated. [url=https://rivet.gg/docs/general/concepts/dev-tokens]Learn more...[/url]"
+fit_content = true
+scroll_active = false
+autowrap_mode = 2
+
+[node name="WarningLabel" type="RichTextLabel" parent="PlaytestContent/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = " Please make sure that you’re using the same version that’s deployed.
+Using different versions can cause some unforeseen issues."
+fit_content = true
+scroll_active = false
+
+[node name="ErrorLabel" type="RichTextLabel" parent="PlaytestContent/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = " Before you can test online, you need to deploy your current game version to Rivet.
+In order to deploy current game version to the cloud, click the button below."
+fit_content = true
+scroll_active = false
+
+[node name="DeployButton" type="Button" parent="PlaytestContent/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Deploy"
diff --git a/godot/bomber/addons/rivet/devtools/dock/settings.gd b/godot/bomber/addons/rivet/devtools/dock/settings.gd
new file mode 100644
index 0000000..3ff0aa1
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/settings.gd
@@ -0,0 +1,15 @@
+@tool extends Control
+## Settings screens allow you to configure and deploy your game.
+
+@onready var errorDialog: AcceptDialog = %ErrorDialog
+@onready var buttons_bar: HBoxContainer = %ButtonsBar
+@onready var deploy_tab = %Deploy
+
+func prepare():
+ var error = await RivetPluginBridge.instance.bootstrap()
+ if error:
+ errorDialog.popup_centered()
+ return
+
+func change_tab(tab: int):
+ buttons_bar.set_current_button(tab)
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/settings.tscn b/godot/bomber/addons/rivet/devtools/dock/settings.tscn
new file mode 100644
index 0000000..e409822
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/settings.tscn
@@ -0,0 +1,73 @@
+[gd_scene load_steps=8 format=3 uid="uid://ceovepvn1782o"]
+
+[ext_resource type="PackedScene" uid="uid://dldxcm1l8nnnf" path="res://addons/rivet/devtools/dock/elements/logo_container.tscn" id="1_22gtj"]
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/settings.gd" id="1_qd6no"]
+[ext_resource type="PackedScene" uid="uid://bk1uwgw1hhq2p" path="res://addons/rivet/devtools/dock/elements/links_container.tscn" id="2_mervt"]
+[ext_resource type="PackedScene" uid="uid://b17eqs0bmrncs" path="res://addons/rivet/devtools/dock/playtest_tab.tscn" id="4_e3aw6"]
+[ext_resource type="PackedScene" uid="uid://bdpu38hakasqq" path="res://addons/rivet/devtools/dock/elements/buttons_bar.tscn" id="4_urbnf"]
+[ext_resource type="PackedScene" uid="uid://dxoly5h64g3ul" path="res://addons/rivet/devtools/dock/settings_tab.tscn" id="6_fu2a3"]
+[ext_resource type="PackedScene" uid="uid://soum1c8oyrso" path="res://addons/rivet/devtools/dock/deploy_tab.tscn" id="6_y2fod"]
+
+[node name="Settings" type="VBoxContainer"]
+theme_override_constants/separation = 16
+script = ExtResource("1_qd6no")
+
+[node name="LogoContainer" parent="." instance=ExtResource("1_22gtj")]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="LinksContainer" parent="." instance=ExtResource("2_mervt")]
+layout_mode = 2
+
+[node name="ErrorDialog" type="AcceptDialog" parent="."]
+unique_name_in_owner = true
+size = Vector2i(112, 100)
+dialog_text = "Configuration for this project couldn't be fetched. Have you run rivet init?"
+
+[node name="ButtonsBar" parent="." node_paths=PackedStringArray("tab_container") instance=ExtResource("4_urbnf")]
+unique_name_in_owner = true
+layout_mode = 2
+tab_container = NodePath("../TabContainer")
+
+[node name="PlaytestButton" type="Button" parent="ButtonsBar"]
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_default_cursor_shape = 2
+toggle_mode = true
+button_pressed = true
+text = "Playtest"
+
+[node name="DeployButton" type="Button" parent="ButtonsBar"]
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_default_cursor_shape = 2
+toggle_mode = true
+text = "Deploy"
+
+[node name="SettingsButton" type="Button" parent="ButtonsBar"]
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_default_cursor_shape = 2
+toggle_mode = true
+text = "Settings"
+
+[node name="TabContainer" type="TabContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+tabs_visible = false
+
+[node name="Playtest" parent="TabContainer" instance=ExtResource("4_e3aw6")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Deploy" parent="TabContainer" instance=ExtResource("6_y2fod")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="Settings" parent="TabContainer" instance=ExtResource("6_fu2a3")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
diff --git a/godot/bomber/addons/rivet/devtools/dock/settings_tab.gd b/godot/bomber/addons/rivet/devtools/dock/settings_tab.gd
new file mode 100644
index 0000000..cd0660d
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/settings_tab.gd
@@ -0,0 +1,24 @@
+@tool extends Control
+
+@onready var unlink_game_button: Button = %UnlinkGameButton
+
+
+func _ready() -> void:
+ unlink_game_button.pressed.connect(_on_unlink_game_button_pressed)
+
+
+func _on_unlink_game_button_pressed() -> void:
+ unlink_game_button.disabled = true
+
+ var result = await RivetPluginBridge.get_plugin().cli.run_command([
+ "unlink"
+ ])
+
+ if result.exit_code != result.ExitCode.SUCCESS:
+ RivetPluginBridge.display_cli_error(self, result)
+ unlink_game_button.disabled = false
+ return
+
+ unlink_game_button.disabled = false
+ owner.owner.reload()
+ owner.owner.change_current_screen(owner.owner.Screen.Login)
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/dock/settings_tab.tscn b/godot/bomber/addons/rivet/devtools/dock/settings_tab.tscn
new file mode 100644
index 0000000..05f4bc0
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/dock/settings_tab.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://dxoly5h64g3ul"]
+
+[ext_resource type="Script" path="res://addons/rivet/devtools/dock/settings_tab.gd" id="1_qo1d8"]
+
+[node name="Settings" type="MarginContainer"]
+script = ExtResource("1_qo1d8")
+
+[node name="UnlinkGameButton" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Unlink game"
diff --git a/godot/bomber/addons/rivet/devtools/rivet_cli.gd b/godot/bomber/addons/rivet/devtools/rivet_cli.gd
new file mode 100644
index 0000000..3be050c
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_cli.gd
@@ -0,0 +1,76 @@
+extends RefCounted
+## Wrapper aroudn the Rivet CLI, allowing you to run it from GDScript in non-blocking way, and get the output.
+##
+## @experimental
+
+const REQUIRED_RIVET_CLI_VERSION = "v1.0.0"
+
+const _RivetEditorSettings = preload("rivet_editor_settings.gd")
+const _RivetThread = preload("rivet_thread.gd")
+const _RivetCliOutput = preload("rivet_cli_output.gd")
+
+func check_existence() -> Error:
+ var editor_rivet_path = _RivetEditorSettings.get_setting(_RivetEditorSettings.RIVET_CLI_PATH_SETTING.name)
+ if not editor_rivet_path or editor_rivet_path.is_empty():
+ return FAILED
+ var result: _RivetCliOutput = await run_command(["sidekick", "get-cli-version"])
+ if result.exit_code != 0 or !("Ok" in result.output):
+ return FAILED
+ var cli_version = result.output["Ok"].version
+ if cli_version != REQUIRED_RIVET_CLI_VERSION:
+ return FAILED
+ return OK
+
+func run_command(args: PackedStringArray) -> _RivetCliOutput:
+ var thread: _RivetThread = _RivetThread.new(_run.bind(args))
+ return await thread.wait_to_finish()
+
+func get_bin_dir() -> String:
+ var home_path: String = OS.get_environment("HOME")
+ return home_path.path_join(".rivet").path_join(REQUIRED_RIVET_CLI_VERSION).path_join("bin")
+
+func get_cli_path() -> String:
+ var cli_path = _RivetEditorSettings.get_setting(_RivetEditorSettings.RIVET_CLI_PATH_SETTING.name)
+ if cli_path and !cli_path.is_empty():
+ return cli_path
+ return get_bin_dir().path_join("rivet.exe" if OS.get_name() == "Windows" else "rivet")
+
+func install() -> _RivetCliOutput:
+ var thread: _RivetThread = _RivetThread.new(_install)
+ var result = await thread.wait_to_finish()
+ if result.exit_code == 0:
+ _RivetEditorSettings.set_setting_value(_RivetEditorSettings.RIVET_CLI_PATH_SETTING.name, get_bin_dir())
+ return result
+
+
+## region Internal functions
+
+## Runs Rivet CLI with given arguments.
+func _run(args: PackedStringArray) -> _RivetCliOutput:
+ var output = []
+ RivetPluginBridge.log(["Running Rivet CLI: ", "%s %s" % [get_cli_path(), " ".join(args)]])
+ var code: int = OS.execute(get_cli_path(), args, output, true)
+
+ return _RivetCliOutput.new(code, output)
+
+func _install() -> _RivetCliOutput:
+ var output = []
+ var code: int
+ var bin_dir: String = get_bin_dir()
+
+ OS.set_environment("RIVET_CLI_VERSION", REQUIRED_RIVET_CLI_VERSION)
+ OS.set_environment("BIN_DIR", bin_dir)
+
+ # Double quotes issue: https://github.com/godotengine/godot/issues/37291#issuecomment-603821838
+ if OS.get_name() == "Windows":
+ var args = ["-Commandi", "\"'iwr https://raw.githubusercontent.com/rivet-gg/cli/$env:RIVET_CLI_VERSION/install/windows.ps1 -useb | iex'\""]
+ code = OS.execute("powershell.exe", args, output, true, true)
+ else:
+ #var args = ["-c", "\"'curl -fsSL https://raw.githubusercontent.com/rivet-gg/cli/${RIVET_CLI_VERSION}/install/unix.sh | sh''\""]
+ var args = ["-c", "\"'curl -fsSL https://raw.githubusercontent.com/rivet-gg/cli/ac57796861d195230fa043e12c5f9fe1921f467f/install/unix.sh | sh'\""]
+ code = OS.execute("/bin/sh", args, output, true, true)
+ return _RivetCliOutput.new(code, output)
+
+
+
+## endregion
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/rivet_cli_output.gd b/godot/bomber/addons/rivet/devtools/rivet_cli_output.gd
new file mode 100644
index 0000000..889499e
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_cli_output.gd
@@ -0,0 +1,38 @@
+extends RefCounted
+## It's a wrapper for the output of the command line tools
+
+var exit_code: ExitCode
+var output: Dictionary
+
+## The exit code of the command line tool
+enum ExitCode {
+ SUCCESS = 0
+ # TODO: fill with the rest of the exit codes
+}
+
+func _init(exit_code: int, internal_output: Array) -> void:
+ self.exit_code = exit_code
+
+ if internal_output and not internal_output.is_empty():
+ _parse_output(internal_output)
+
+func _parse_output(internal_output: Array) -> void:
+ var lines_with_json = internal_output.filter(
+ func (line: String):
+ return line.find("{") != -1
+ )
+
+ if lines_with_json.is_empty():
+ print("No JSON output found")
+ return
+
+ var line_with_json: String = lines_with_json.front()
+ # Parse the output as JSON
+ var json: JSON = JSON.new()
+ var error: Error = json.parse(line_with_json)
+
+ if error == OK:
+ self.output = json.data
+ else:
+ # If the output is not JSON, throw an error
+ RivetPluginBridge.error("Invalid response from the command line tool: " + str(error))
diff --git a/godot/bomber/addons/rivet/devtools/rivet_editor_settings.gd b/godot/bomber/addons/rivet/devtools/rivet_editor_settings.gd
new file mode 100644
index 0000000..89196c1
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_editor_settings.gd
@@ -0,0 +1,29 @@
+const RIVET_CLI_PATH_SETTING = {
+ "name": "rivet/cli_executable_path",
+ "type": TYPE_STRING,
+ "hint": PROPERTY_HINT_TYPE_STRING,
+}
+const RIVET_DEBUG_SETTING ={
+ "name": "rivet/debug",
+ "type": TYPE_BOOL,
+}
+
+## Returns the path to the Rivet CLI executable stored in the editor settings.
+static func set_defaults(settings: EditorSettings = EditorInterface.get_editor_settings()) -> void:
+ set_default_setting_value(RIVET_CLI_PATH_SETTING["name"], "", settings)
+ settings.add_property_info(RIVET_CLI_PATH_SETTING)
+ set_default_setting_value(RIVET_DEBUG_SETTING["name"], false, settings)
+ settings.add_property_info(RIVET_DEBUG_SETTING)
+
+## Sets the path to the Rivet CLI executable in the editor settings, if it is not already set.
+static func set_default_setting_value(name: String, default_value: Variant, settings: EditorSettings = EditorInterface.get_editor_settings()) -> void:
+ var existing_value = settings.get_setting(name)
+ settings.set_initial_value(name, default_value, false)
+ settings.set_setting(name, existing_value if existing_value else default_value)
+
+static func set_setting_value(name: String, value: Variant, settings: EditorSettings = EditorInterface.get_editor_settings()) -> void:
+ settings.set_setting(name, value)
+
+## Returns the path to the Rivet CLI executable stored in the editor settings.
+static func get_setting(name: String, settings: EditorSettings = EditorInterface.get_editor_settings()) -> Variant:
+ return settings.get_setting(name)
diff --git a/godot/bomber/addons/rivet/devtools/rivet_export_plugin.gd b/godot/bomber/addons/rivet/devtools/rivet_export_plugin.gd
new file mode 100644
index 0000000..bbb7419
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_export_plugin.gd
@@ -0,0 +1,29 @@
+@tool
+extends EditorExportPlugin
+
+var has_added_file: bool = false
+var _plugin_name = "RivetEditorPlugin"
+
+func _supports_platform(platform):
+ return true
+
+func _get_name():
+ return _plugin_name
+
+func _export_begin(features: PackedStringArray, is_debug: bool, path: String, flags: int) -> void:
+ if not FileAccess.file_exists(RivetPluginBridge.RIVET_CONFIGURATION_FILE_PATH):
+ push_warning("Rivet plugin not configured. Please configure it using plugin interface.")
+ return
+
+ var configuration_file = FileAccess.open(RivetPluginBridge.RIVET_CONFIGURATION_FILE_PATH, FileAccess.READ)
+ var source = configuration_file.get_as_text()
+ var script = GDScript.new()
+ script.source_code = source
+ var error: Error = ResourceSaver.save(script, RivetPluginBridge.RIVET_DEPLOYED_CONFIGURATION_FILE_PATH)
+ if not error:
+ has_added_file = true
+
+
+func _export_end() -> void:
+ if has_added_file:
+ DirAccess.remove_absolute(RivetPluginBridge.RIVET_DEPLOYED_CONFIGURATION_FILE_PATH)
\ No newline at end of file
diff --git a/godot/bomber/addons/rivet/devtools/rivet_plugin_bridge.gd b/godot/bomber/addons/rivet/devtools/rivet_plugin_bridge.gd
new file mode 100644
index 0000000..018e3b3
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_plugin_bridge.gd
@@ -0,0 +1,128 @@
+@tool class_name RivetPluginBridge
+## Scaffolding for the plugin to be used in the editor, this is not meant to be
+## used in the game. It's a way to get the plugin instance from the engine's
+## perspective.
+##
+## @experimental
+
+signal bootstrapped
+
+const RIVET_CONFIGURATION_PATH: String = "res://.rivet"
+const RIVET_CONFIGURATION_FILE_PATH: String = "res://.rivet/config.gd"
+const RIVET_DEPLOYED_CONFIGURATION_FILE_PATH: String = "res://.rivet_config.gd"
+const SCRIPT_TEMPLATE: String = """
+extends RefCounted
+const api_endpoint: String = \"{api_endpoint}\"
+const namespace_token: String = \"{namespace_token}\"
+const cloud_token: String = \"{cloud_token}\"
+const game_id: String = \"{game_id}\"
+"""
+const _global := preload("../rivet_global.gd")
+const _RivetEditorSettings = preload("./rivet_editor_settings.gd")
+
+static var game_namespaces: Array
+
+static var instance = RivetPluginBridge.new()
+
+static func _find_plugin():
+ var tree: SceneTree = Engine.get_main_loop()
+ return tree.get_root().get_child(0).get_node_or_null("RivetPlugin")
+
+static func display_cli_error(node: Node, cli_output) -> AcceptDialog:
+ var error = cli_output.output["Err"].c_unescape() if "Err" in cli_output.output else "\n".join(cli_output.formatted_output)
+ var alert = AcceptDialog.new()
+ alert.title = "Error!"
+ alert.dialog_text = error
+ alert.dialog_autowrap = true
+ alert.close_requested.connect(func(): alert.queue_free() )
+ node.add_child(alert)
+ alert.popup_centered_ratio(0.4)
+ return alert
+
+# https://github.com/godotengine/godot-proposals/issues/900#issuecomment-1812881718
+static func is_part_of_edited_scene(node: Node):
+ return Engine.is_editor_hint() && node.is_inside_tree() && node.get_tree().get_edited_scene_root() && (node.get_tree().get_edited_scene_root() == node || node.get_tree().get_edited_scene_root().is_ancestor_of(node))
+
+## Autoload is not available for editor interfaces, we add a scoffolding to get
+## the instance of the plugin from the engine's perspective
+## @experimental
+static func get_plugin() -> _global:
+ var plugin = _find_plugin()
+ if plugin:
+ return plugin.global
+ return null
+
+static func log(args):
+ if _RivetEditorSettings.get_setting(_RivetEditorSettings.RIVET_DEBUG_SETTING.name):
+ print("[Rivet] ", args)
+
+static func warning(args):
+ push_warning("[Rivet] ", args)
+
+static func error(args):
+ push_error("[Rivet] ", args)
+
+func save_configuration():
+ DirAccess.make_dir_recursive_absolute(RIVET_CONFIGURATION_PATH)
+
+ var gd_ignore_path = RIVET_CONFIGURATION_PATH.path_join(".gdignore")
+ if not FileAccess.file_exists(gd_ignore_path):
+ var gd_ignore = FileAccess.open(gd_ignore_path, FileAccess.WRITE)
+ gd_ignore.store_string("")
+
+ var git_ignore_path = RIVET_CONFIGURATION_PATH.path_join(".gitignore")
+ if not FileAccess.file_exists(git_ignore_path):
+ var git_ignore = FileAccess.open(git_ignore_path, FileAccess.WRITE)
+ git_ignore.store_string("*")
+
+ var plg = get_plugin()
+ var script: GDScript = GDScript.new()
+ script.source_code = SCRIPT_TEMPLATE.format({"api_endpoint": plg.api_endpoint, "namespace_token": plg.namespace_token, "cloud_token": plg.cloud_token, "game_id": plg.game_id})
+ var err: Error = ResourceSaver.save(script, RIVET_CONFIGURATION_FILE_PATH)
+ if err:
+ push_warning("Error saving Rivet data: %s" % err)
+
+func bootstrap() -> Error:
+ var plugin = get_plugin()
+ if not plugin:
+ return FAILED
+
+ var result = await get_plugin().cli.run_command([
+ "sidekick",
+ "get-bootstrap-data",
+ ])
+
+ if result.exit_code != 0 or !("Ok" in result.output):
+ return FAILED
+
+ get_plugin().api_endpoint = result.output["Ok"].api_endpoint
+ get_plugin().cloud_token = result.output["Ok"].token
+ get_plugin().game_id = result.output["Ok"].game_id
+
+ save_configuration()
+
+ var fetch_result = await _fetch_plugin_data()
+ if fetch_result == OK:
+ emit_signal("bootstrapped")
+ return fetch_result
+
+func _fetch_plugin_data() -> Error:
+ var response = await get_plugin().GET("/cloud/games/%s" % get_plugin().game_id).wait_completed()
+ # response.body:
+ # game.namespaces = {namespace_id, version_id, display_name}[]
+ # game.versions = {version_id, display_name}[]
+ if response.response_code != HTTPClient.ResponseCode.RESPONSE_OK:
+ return FAILED
+
+ var namespaces = response.body.game.namespaces
+ for space in namespaces:
+ var versions: Array = response.body.game.versions.filter(
+ func (version): return version.version_id == space.version_id
+ )
+ if versions.is_empty():
+ space["version"] = null
+ else:
+ space["version"] = versions[0]
+
+ game_namespaces = namespaces
+ return OK
diff --git a/godot/bomber/addons/rivet/devtools/rivet_thread.gd b/godot/bomber/addons/rivet/devtools/rivet_thread.gd
new file mode 100644
index 0000000..2f181f0
--- /dev/null
+++ b/godot/bomber/addons/rivet/devtools/rivet_thread.gd
@@ -0,0 +1,29 @@
+extends RefCounted
+## A wrapper around Thread that allows you to wait for the thread to finish and get the result.
+##
+## @experimental
+
+signal finished(output: Variant)
+
+var _mutex: Mutex
+var _thread: Thread
+
+## Result of the thread.
+var output: Variant = null
+
+## Returns the output of the thread.
+func wait_to_finish():
+ await finished
+ return output
+
+func _init(fn: Callable) -> void:
+ _thread = Thread.new()
+ _mutex = Mutex.new()
+ _thread.start(func():
+ var result = fn.call()
+ _mutex.lock()
+ output = result
+ call_deferred("emit_signal", "finished", result)
+ _mutex.unlock()
+ return result
+ )
diff --git a/godot/bomber/addons/rivet/images/icon-circle.png.import b/godot/bomber/addons/rivet/images/icon-circle.png.import
new file mode 100644
index 0000000..5d95e42
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/icon-circle.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cle7km6xt44im"
+path="res://.godot/imported/icon-circle.png-ad959504104d5f2d6c4a4ce568b03d7e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rivet/images/icon-circle.png"
+dest_files=["res://.godot/imported/icon-circle.png-ad959504104d5f2d6c4a4ce568b03d7e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/addons/rivet/images/icon-text-black.svg b/godot/bomber/addons/rivet/images/icon-text-black.svg
new file mode 100644
index 0000000..14f0fe0
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/icon-text-black.svg
@@ -0,0 +1,9 @@
+
diff --git a/godot/bomber/addons/rivet/images/icon-text-black.svg.import b/godot/bomber/addons/rivet/images/icon-text-black.svg.import
new file mode 100644
index 0000000..cdda90d
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/icon-text-black.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://10vqh72x3wrr"
+path="res://.godot/imported/icon-text-black.svg-10e6532df3787d7fde392c7ee5fc0deb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rivet/images/icon-text-black.svg"
+dest_files=["res://.godot/imported/icon-text-black.svg-10e6532df3787d7fde392c7ee5fc0deb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/godot/bomber/addons/rivet/images/icon-text-white.svg b/godot/bomber/addons/rivet/images/icon-text-white.svg
new file mode 100644
index 0000000..1807618
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/icon-text-white.svg
@@ -0,0 +1,9 @@
+
diff --git a/godot/bomber/addons/rivet/images/icon-text-white.svg.import b/godot/bomber/addons/rivet/images/icon-text-white.svg.import
new file mode 100644
index 0000000..cd17de1
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/icon-text-white.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://duc87c0hyoyqk"
+path="res://.godot/imported/icon-text-white.svg-c0a5d8cecfa0530a176b0b7cc6c0fed1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rivet/images/icon-text-white.svg"
+dest_files=["res://.godot/imported/icon-text-white.svg-c0a5d8cecfa0530a176b0b7cc6c0fed1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/godot/bomber/addons/rivet/images/white-logo.svg.import b/godot/bomber/addons/rivet/images/white-logo.svg.import
new file mode 100644
index 0000000..d343b6c
--- /dev/null
+++ b/godot/bomber/addons/rivet/images/white-logo.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cebc1tvicnoux"
+path="res://.godot/imported/white-logo.svg-573e799fd39e8cb89bc8e36e6ad9780d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rivet/images/white-logo.svg"
+dest_files=["res://.godot/imported/white-logo.svg-573e799fd39e8cb89bc8e36e6ad9780d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/godot/bomber/addons/rivet_api/plugin.cfg b/godot/bomber/addons/rivet/plugin.cfg
similarity index 64%
rename from godot/bomber/addons/rivet_api/plugin.cfg
rename to godot/bomber/addons/rivet/plugin.cfg
index fe1097e..79be32c 100644
--- a/godot/bomber/addons/rivet_api/plugin.cfg
+++ b/godot/bomber/addons/rivet/plugin.cfg
@@ -3,6 +3,6 @@
name="Rivet API"
description=""
author="Rivet Gaming, Inc."
-version="0.0.1"
-script="rivet_api.gd"
+version="1.0.0-rc.1"
+script="rivet.gd"
diff --git a/godot/bomber/addons/rivet/rivet.gd b/godot/bomber/addons/rivet/rivet.gd
new file mode 100644
index 0000000..db0d56c
--- /dev/null
+++ b/godot/bomber/addons/rivet/rivet.gd
@@ -0,0 +1,58 @@
+@tool extends EditorPlugin
+## Mainpoint for the Rivet editor plugin.
+
+# MARK: Plugin
+const AUTO_LOAD_RIVET_CLIENT = "RivetClient"
+const AUTO_LOAD_RIVET_HELPER = "RivetHelper"
+const AUTO_LOAD_RIVET_GLOBAL = "Rivet"
+
+const _RivetEditorSettings := preload("devtools/rivet_editor_settings.gd")
+const _RivetGlobal := preload("rivet_global.gd")
+const _RivetCLI = preload("devtools/rivet_cli.gd")
+
+var _dock: Control
+var _export_plugin: EditorExportPlugin
+var cli: _RivetCLI = _RivetCLI.new()
+
+## The global singleton for the Rivet plugin, only available in the editor.
+var global: _RivetGlobal
+
+func _init() -> void:
+ name = "RivetPlugin"
+
+func _enter_tree():
+ # Add singleton
+ add_autoload_singleton(AUTO_LOAD_RIVET_CLIENT, "rivet_client.gd")
+ add_autoload_singleton(AUTO_LOAD_RIVET_HELPER, "rivet_helper.gd")
+
+ add_autoload_singleton(AUTO_LOAD_RIVET_GLOBAL, "rivet_global.gd")
+
+ global = _RivetGlobal.new()
+ global.cli = cli
+
+ _dock = preload("devtools/dock/dock.tscn").instantiate()
+ _dock.add_child(global)
+
+ # Add export plugin
+ _export_plugin = preload("devtools/rivet_export_plugin.gd").new()
+ add_export_plugin(_export_plugin)
+
+ # Add dock
+ add_control_to_dock(DOCK_SLOT_LEFT_BR, _dock)
+ _RivetEditorSettings.set_defaults()
+
+
+func _exit_tree():
+ # Remove singleton
+ remove_autoload_singleton(AUTO_LOAD_RIVET_CLIENT)
+ remove_autoload_singleton(AUTO_LOAD_RIVET_HELPER)
+ remove_autoload_singleton(AUTO_LOAD_RIVET_GLOBAL)
+
+ # Remove export plugin
+ remove_export_plugin(_export_plugin)
+ _export_plugin = null
+
+ # Remove dock
+ remove_control_from_docks(_dock)
+ _dock.free()
+
diff --git a/godot/bomber/addons/rivet_api/rivet.toml.tpl b/godot/bomber/addons/rivet/rivet.version.toml.tpl
similarity index 99%
rename from godot/bomber/addons/rivet_api/rivet.toml.tpl
rename to godot/bomber/addons/rivet/rivet.version.toml.tpl
index ca1b0e3..0916519 100644
--- a/godot/bomber/addons/rivet_api/rivet.toml.tpl
+++ b/godot/bomber/addons/rivet/rivet.version.toml.tpl
@@ -1,5 +1,5 @@
# === Rivet Version Configuration ===
-#
+#
# - More info: https://docs.rivet.gg/general/concepts/rivet-version-config
# - Reference: https://docs.rivet.gg/cloud/api/post-games-versions#body
# - Publish a new version with `rivet publish`
diff --git a/godot/bomber/addons/rivet_api/rivet_client.gd b/godot/bomber/addons/rivet/rivet_client.gd
similarity index 93%
rename from godot/bomber/addons/rivet_api/rivet_client.gd
rename to godot/bomber/addons/rivet/rivet_client.gd
index 6bb2ad7..207e094 100644
--- a/godot/bomber/addons/rivet_api/rivet_client.gd
+++ b/godot/bomber/addons/rivet/rivet_client.gd
@@ -1,21 +1,27 @@
+## @deprecated
extends Node
var base_url = "https://api.rivet.gg/v1"
+## @deprecated
func get_token():
var token_env = OS.get_environment("RIVET_TOKEN")
assert(!token_env.is_empty(), "missing RIVET_TOKEN environment")
return token_env
-
+
+## @deprecated
func lobby_ready(body: Variant, on_success: Callable, on_fail: Callable):
_rivet_request_with_body("POST", "matchmaker", "/lobbies/ready", body, on_success, on_fail)
+## @deprecated
func find_lobby(body: Variant, on_success: Callable, on_fail: Callable):
_rivet_request_with_body("POST", "matchmaker", "/lobbies/find", body, on_success, on_fail)
+## @deprecated
func player_connected(body: Variant, on_success: Callable, on_fail: Callable):
_rivet_request_with_body("POST", "matchmaker", "/players/connected", body, on_success, on_fail)
+## @deprecated
func player_disconnected(body: Variant, on_success: Callable, on_fail: Callable):
_rivet_request_with_body("POST", "matchmaker", "/players/disconnected", body, on_success, on_fail)
@@ -27,6 +33,7 @@ func _build_headers() -> PackedStringArray:
"Authorization: Bearer " + get_token(),
]
+## @deprecated
func _rivet_request(method: String, service: String, path: String, on_success: Callable, on_fail: Callable):
var url = _build_url(service, path)
RivetHelper.rivet_print("%s %s" % [method, url])
@@ -41,6 +48,7 @@ func _rivet_request(method: String, service: String, path: String, on_success: C
if on_fail != null:
on_fail.call("Request failed to send: %s" % error)
+## @deprecated
func _rivet_request_with_body(method: String, service: String, path: String, body: Variant, on_success: Callable, on_fail: Callable):
var url = _build_url(service, path)
RivetHelper.rivet_print("%s %s: %s" % [method, url, body])
@@ -56,12 +64,16 @@ func _rivet_request_with_body(method: String, service: String, path: String, bod
if on_fail != null:
on_fail.call("Request failed to send: %s" % error)
+## @deprecated
func _http_request_completed(result, response_code, _headers, body, on_success: Callable, on_fail: Callable):
if result != HTTPRequest.RESULT_SUCCESS:
push_error("Request error ", result)
if on_fail != null:
on_fail.call("Request error: %s" % result)
return
+
+ RivetHelper.rivet_print("%s: %s" % [response_code, body.get_string_from_utf8()])
+
if response_code != 200:
push_error("Request failed ", response_code, " ", body.get_string_from_utf8())
if on_fail != null:
@@ -72,7 +84,6 @@ func _http_request_completed(result, response_code, _headers, body, on_success:
json.parse(body.get_string_from_utf8())
var response = json.get_data()
- RivetHelper.rivet_print("Success")
if on_success != null:
on_success.call(response)
diff --git a/godot/bomber/addons/rivet/rivet_global.gd b/godot/bomber/addons/rivet/rivet_global.gd
new file mode 100644
index 0000000..cfa63c2
--- /dev/null
+++ b/godot/bomber/addons/rivet/rivet_global.gd
@@ -0,0 +1,36 @@
+
+extends Node
+## Rivet [/br]
+## Mainpoint of the Rivet plugin.
+## It includes an easy access to APIs, helpers and tools. [/br]
+## @tutorial: https://rivet.gg/learn/godot
+## @experimental
+
+const _api = preload("api/rivet_api.gd")
+
+const ApiResponse = preload("api/rivet_response.gd")
+const ApiRequest = preload("api/rivet_request.gd")
+
+const _Packages = preload("api/rivet_packages.gd")
+
+var cloud_token: String
+var namespace_token: String
+var game_id: String
+var api_endpoint: String
+
+var matchmaker: _Packages.Matchmaker = _Packages.Matchmaker.new()
+
+# This variable is only accessible from editor's scripts, please do not use it in your game.
+var cli
+
+## @experimental
+func POST(path: String, body: Dictionary) -> _api.RivetRequest:
+ return _api.POST(self, path, body)
+
+## @experimental
+func GET(path: String, body: Dictionary = {}) -> _api.RivetRequest:
+ return _api.GET(self, path, body)
+
+## @experimental
+func PUT(path: String, body: Dictionary = {}) -> _api.RivetRequest:
+ return _api.PUT(self, path, body)
diff --git a/godot/bomber/addons/rivet_api/rivet_helper.gd b/godot/bomber/addons/rivet/rivet_helper.gd
similarity index 64%
rename from godot/bomber/addons/rivet_api/rivet_helper.gd
rename to godot/bomber/addons/rivet/rivet_helper.gd
index 1a66645..c0b88b3 100644
--- a/godot/bomber/addons/rivet_api/rivet_helper.gd
+++ b/godot/bomber/addons/rivet/rivet_helper.gd
@@ -1,7 +1,11 @@
extends Node
+## Triggered if running a dedicated server.
signal start_server()
+## Triggered if running a client.
+signal start_client()
+
var multiplayer_setup = false
## All player tokens for players that have authenticated.
@@ -16,11 +20,6 @@ var player_tokens = {}
var player_token = null
-func _ready():
- var dotenv = DotEnv.new()
- dotenv.config()
-
-
## Determines if running as a dedicated server.
func is_dedicated_server() -> bool:
return OS.get_cmdline_user_args().has("--server")
@@ -28,6 +27,7 @@ func is_dedicated_server() -> bool:
## Sets up the authentication hooks on SceneMultiplayer.
func setup_multiplayer():
+ RivetHelper._assert(!multiplayer_setup, "RivetHelper.setup_multiplayer already called")
multiplayer_setup = true
var scene_multiplayer = multiplayer as SceneMultiplayer
@@ -43,12 +43,15 @@ func setup_multiplayer():
if is_dedicated_server():
rivet_print("Starting server")
start_server.emit()
+ else:
+ rivet_print("Starting client")
+ start_client.emit()
## Sets the player token for the next authentication challenge.
func set_player_token(_player_token: String):
- assert(multiplayer_setup, "RivetHelper.setup_multiplayer has not been called")
- assert(!is_dedicated_server(), "cannot called RivetHelper.set_player_token on server")
+ RivetHelper._assert(multiplayer_setup, "RivetHelper.setup_multiplayer has not been called")
+ RivetHelper._assert(!is_dedicated_server(), "cannot called RivetHelper.set_player_token on server")
player_token = _player_token
@@ -63,9 +66,17 @@ func _auth_callback(id: int, buf: PackedByteArray):
rivet_print("Player authenticating %s: %s" % [id, data])
player_tokens[id] = data.player_token
- RivetClient.player_connected({
+
+ var response = await Rivet.matchmaker.players.connected({
"player_token": data.player_token
- }, _rivet_player_connected.bind(id), _rivet_player_connect_failed.bind(id))
+ })
+
+ if response.result == OK:
+ rivet_print("Player authenticated for %s" % id)
+ (multiplayer as SceneMultiplayer).complete_auth(id)
+ else:
+ rivet_print("Player authentiation failed for %s: %s" % [id, response.body])
+ (multiplayer as SceneMultiplayer).disconnect_peer(id)
else:
# Auto-approve if not a server
(multiplayer as SceneMultiplayer).complete_auth(id)
@@ -78,27 +89,27 @@ func _player_authenticating(id):
func _player_authentication_failed(id):
rivet_print("Authentication failed for %s" % id)
- multiplayer.set_network_peer(null)
-# connection_failed.emit()
+ multiplayer.set_multiplayer_peer(null)
func _player_disconnected(id):
if multiplayer.is_server():
var player_token = player_tokens.get(id)
player_tokens.erase(id)
rivet_print("Removing player %s" % player_token)
-
- RivetClient.player_disconnected({
- "player_token": player_token
- }, func(_x): pass, func(_x): pass)
-
-func _rivet_player_connected(_body, id: int):
- rivet_print("Player authenticated for %s" % id)
- (multiplayer as SceneMultiplayer).complete_auth(id)
+ var response = await Rivet.matchmaker.players.disconnected({
+ "player_token": player_token
+ })
-func _rivet_player_connect_failed(error, id: int):
- rivet_print("Player authentiation failed for %s: %s" % [id, error])
- (multiplayer as SceneMultiplayer).disconnect_peer(id)
func rivet_print(message: String):
print("[Rivet] %s" % message)
+
+func _assert(condition: bool, message: String = "Assertion failed"):
+ if not condition:
+ # For now, we won't crash the game if an assertion fails. There are a
+ # few reasons for this. See discussion:
+ # https://app.graphite.dev/github/pr/rivet-gg/plugin-godot/33/Assert-fix#
+ # OS.crash(message)
+
+ rivet_print(message)
diff --git a/godot/bomber/addons/rivet_api/dock/dock.gd b/godot/bomber/addons/rivet_api/dock/dock.gd
deleted file mode 100644
index 3f51af3..0000000
--- a/godot/bomber/addons/rivet_api/dock/dock.gd
+++ /dev/null
@@ -1,84 +0,0 @@
-@tool
-extends VBoxContainer
-
-var poll_timer: Timer
-var rng := RandomNumberGenerator.new()
-
-var server_pid = null
-
-func _ready():
- poll_timer = Timer.new()
- poll_timer.autostart = true
- poll_timer.timeout.connect(_poll_server_status)
- add_child(poll_timer)
-
-
-func exec_sh(script: String) -> int:
- return OS.create_process("sh", ["-c", script])
-# OS.create_process("CMD.exe", ["/C", script], output)
-
-
-func run_rivet_command(args: PackedStringArray):
- var execute_id = rng.randi()
- return OS.create_process("rivet", args)
-
-
-## Checks if the server process is still running.
-func _poll_server_status():
- # Check if server still running
- if server_pid != null and !OS.is_process_running(server_pid):
- print("Server stopped")
- server_pid = null
-
- # Update stop button
- $StopServer.disabled = server_pid == null
- if server_pid != null:
- $StartServer.text = "Restart Server"
- $ServerPID.text = "Process ID: %s" % server_pid
- $ServerPID.visible = true
- else:
- $StartServer.text = "Start Server"
- $StopServer.text = "Stop Server"
- $ServerPID.visible = false
-
-func start_server():
- if server_pid != null:
- stop_server()
-
- server_pid = OS.create_process(OS.get_executable_path(), ["--headless", "--", "--server"])
-
- _poll_server_status()
-
-
-## Kills the server if running.
-func stop_server():
- if server_pid != null:
- print("Stopped serer")
- OS.kill(server_pid)
- server_pid = null
- _poll_server_status()
- else:
- print("Server not running")
-
-
-# MARK: UI
-func _on_dashboard_pressed():
- run_rivet_command(["game", "dashboard"])
-
-
-func _on_start_server_pressed():
- start_server()
-
-
-func _on_stop_server_pressed():
- stop_server()
-
-
-#func _on_edit_config_pressed():
-# print(ProjectSettings.globalize_path("res://rivet.toml"))
-# OS.shell_open("file://%s" % ProjectSettings.globalize_path("res://rivet.toml"))
-
-
-#func _on_deploy_pressed():
-# deploy_pid = run_rivet_command(["deploy", "--namespace", "prod"])
-# _poll_server_status()
diff --git a/godot/bomber/addons/rivet_api/dock/dock.tscn b/godot/bomber/addons/rivet_api/dock/dock.tscn
deleted file mode 100644
index d148e5f..0000000
--- a/godot/bomber/addons/rivet_api/dock/dock.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5ed1dfcb391a43f8969dd47c5959316c408fa07beab4ad0235e0a544633329fc
-size 2107
diff --git a/godot/bomber/addons/rivet_api/dotenv.gd b/godot/bomber/addons/rivet_api/dotenv.gd
deleted file mode 100644
index 5d5cf90..0000000
--- a/godot/bomber/addons/rivet_api/dotenv.gd
+++ /dev/null
@@ -1,33 +0,0 @@
-extends Node
-class_name DotEnv
-
-func config():
- var path = "res://.env"
-
- # Check if .env exists
- if !FileAccess.file_exists(path):
- RivetHelper.rivet_print(".env does not exist")
- return
-
- # Read .env file
- var file = FileAccess.open(path, FileAccess.READ)
- while !file.eof_reached():
- var line = file.get_line()
-
- # Ignore comments
- if line.begins_with("#"):
- continue
-
- # Split line
- var split = line.split("=", true, 2)
- if split.size() != 2:
- continue
- var key = split[0]
- var value = split[1]
-
- # Trim quotes from value
- if value.begins_with("\"") and value.ends_with("\""):
- value = value.substr(1, value.length() - 2)
-
- # Set env
- OS.set_environment(split[0], split[1])
diff --git a/godot/bomber/addons/rivet_api/images/icon-circle.png b/godot/bomber/addons/rivet_api/images/icon-circle.png
deleted file mode 100644
index 67da309..0000000
--- a/godot/bomber/addons/rivet_api/images/icon-circle.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f581b038ff8e7a1e81b5e20693f8ad0b9dbfde02cebe97092bb4ba6f202cf526
-size 11263
diff --git a/godot/bomber/addons/rivet_api/images/icon-circle.png.import b/godot/bomber/addons/rivet_api/images/icon-circle.png.import
deleted file mode 100644
index 2ebd2b6..0000000
--- a/godot/bomber/addons/rivet_api/images/icon-circle.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9c7ccf8d99e564fe5476756802753428d79249c7829272b1e917385203f0ca2f
-size 791
diff --git a/godot/bomber/addons/rivet_api/rivet_api.gd b/godot/bomber/addons/rivet_api/rivet_api.gd
deleted file mode 100644
index 3a254fe..0000000
--- a/godot/bomber/addons/rivet_api/rivet_api.gd
+++ /dev/null
@@ -1,27 +0,0 @@
-@tool
-extends EditorPlugin
-
-# MARK: Plugin
-const AUTO_LOAD_RIVET_CLIENT = "RivetClient"
-const AUTO_LOAD_RIVET_HELPER = "RivetHelper"
-
-var dock: Control
-
-func _enter_tree():
- # Add singleton
- add_autoload_singleton(AUTO_LOAD_RIVET_CLIENT, "res://addons/rivet_api/rivet_client.gd")
- add_autoload_singleton(AUTO_LOAD_RIVET_HELPER, "res://addons/rivet_api/rivet_helper.gd")
-
- # Add dock
- dock = preload("res://addons/rivet_api/dock/dock.tscn").instantiate()
- add_control_to_dock(DOCK_SLOT_LEFT_BR, dock)
-
-func _exit_tree():
- # Remove singleton
- remove_autoload_singleton(AUTO_LOAD_RIVET_CLIENT)
- remove_autoload_singleton(AUTO_LOAD_RIVET_HELPER)
-
- # Remove dock
- remove_control_from_docks(dock)
- dock.free()
-
diff --git a/godot/bomber/brickfloor.png b/godot/bomber/assets/brickfloor.png
similarity index 100%
rename from godot/bomber/brickfloor.png
rename to godot/bomber/assets/brickfloor.png
diff --git a/godot/bomber/assets/brickfloor.png.import b/godot/bomber/assets/brickfloor.png.import
new file mode 100644
index 0000000..713e6fe
--- /dev/null
+++ b/godot/bomber/assets/brickfloor.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdomqql6y50po"
+path="res://.godot/imported/brickfloor.png-1ce413e4c011fbd447287b441234b9f9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/brickfloor.png"
+dest_files=["res://.godot/imported/brickfloor.png-1ce413e4c011fbd447287b441234b9f9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/charwalk.png b/godot/bomber/assets/charwalk.png
similarity index 100%
rename from godot/bomber/charwalk.png
rename to godot/bomber/assets/charwalk.png
diff --git a/godot/bomber/assets/charwalk.png.import b/godot/bomber/assets/charwalk.png.import
new file mode 100644
index 0000000..fc5eba5
--- /dev/null
+++ b/godot/bomber/assets/charwalk.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bsqovikudjr0q"
+path="res://.godot/imported/charwalk.png-faa1106936dc3ced598a0e10a43eb5e9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/charwalk.png"
+dest_files=["res://.godot/imported/charwalk.png-faa1106936dc3ced598a0e10a43eb5e9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/explosion.png b/godot/bomber/assets/explosion.png
similarity index 100%
rename from godot/bomber/explosion.png
rename to godot/bomber/assets/explosion.png
diff --git a/godot/bomber/assets/explosion.png.import b/godot/bomber/assets/explosion.png.import
new file mode 100644
index 0000000..19e58bd
--- /dev/null
+++ b/godot/bomber/assets/explosion.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://drfbkdqmj0gu2"
+path="res://.godot/imported/explosion.png-3d70b655f829d2ec362997bd3be8e3ae.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/explosion.png"
+dest_files=["res://.godot/imported/explosion.png-3d70b655f829d2ec362997bd3be8e3ae.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/assets/export_presets.cfg b/godot/bomber/assets/export_presets.cfg
new file mode 100644
index 0000000..21ab7b3
--- /dev/null
+++ b/godot/bomber/assets/export_presets.cfg
@@ -0,0 +1,43 @@
+[preset.0]
+
+name="Linux/X11"
+platform="Linux/X11"
+runnable=true
+dedicated_server=true
+custom_features=""
+export_filter="customized"
+customized_files={
+"res://": "strip"
+}
+include_filter=""
+exclude_filter=""
+export_path=""
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+script_encryption_key=""
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_script=1
+binary_format/embed_pck=false
+texture_format/bptc=true
+texture_format/s3tc=true
+texture_format/etc=false
+texture_format/etc2=false
+binary_format/architecture="x86_64"
+ssh_remote_deploy/enabled=false
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="#!/usr/bin/env bash
+export DISPLAY=:0
+unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
+\"{temp_dir}/{exe_name}\" {cmd_args}"
+ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
+kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
+rm -rf \"{temp_dir}\""
diff --git a/godot/bomber/icon.png b/godot/bomber/assets/icon.png
similarity index 100%
rename from godot/bomber/icon.png
rename to godot/bomber/assets/icon.png
diff --git a/godot/bomber/assets/icon.png.import b/godot/bomber/assets/icon.png.import
new file mode 100644
index 0000000..a54a8bb
--- /dev/null
+++ b/godot/bomber/assets/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cbby50724oj4c"
+path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/icon.png"
+dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/montserrat.otf b/godot/bomber/assets/montserrat.otf
similarity index 100%
rename from godot/bomber/montserrat.otf
rename to godot/bomber/assets/montserrat.otf
diff --git a/godot/bomber/assets/montserrat.otf.import b/godot/bomber/assets/montserrat.otf.import
new file mode 100644
index 0000000..c9acc10
--- /dev/null
+++ b/godot/bomber/assets/montserrat.otf.import
@@ -0,0 +1,33 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://knb8u535cfkw"
+path="res://.godot/imported/montserrat.otf-344d7c85facec00a87358649b6f3ceaf.fontdata"
+
+[deps]
+
+source_file="res://assets/montserrat.otf"
+dest_files=["res://.godot/imported/montserrat.otf-344d7c85facec00a87358649b6f3ceaf.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/godot/bomber/rock_bit.png b/godot/bomber/assets/rock_bit.png
similarity index 100%
rename from godot/bomber/rock_bit.png
rename to godot/bomber/assets/rock_bit.png
diff --git a/godot/bomber/assets/rock_bit.png.import b/godot/bomber/assets/rock_bit.png.import
new file mode 100644
index 0000000..7932b2b
--- /dev/null
+++ b/godot/bomber/assets/rock_bit.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bh4gbjcayios1"
+path="res://.godot/imported/rock_bit.png-7452bb52b797fd06f3864ee774cafdee.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/rock_bit.png"
+dest_files=["res://.godot/imported/rock_bit.png-7452bb52b797fd06f3864ee774cafdee.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/godot/bomber/assets/tileset.tres b/godot/bomber/assets/tileset.tres
new file mode 100644
index 0000000..283f588
--- /dev/null
+++ b/godot/bomber/assets/tileset.tres
@@ -0,0 +1,19 @@
+[gd_resource type="TileSet" load_steps=3 format=3 uid="uid://do2l6lpuotti8"]
+
+[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://assets/brickfloor.png" id="1"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_qhkfp"]
+texture = ExtResource("1")
+texture_region_size = Vector2i(48, 48)
+0:0/0 = 0
+0:0/0/physics_layer_0/linear_velocity = Vector2(0, 0)
+0:0/0/physics_layer_0/angular_velocity = 0.0
+0:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-24, -24, 24, -24, 24, 24, -24, 24)
+1:0/0 = 0
+1:0/0/physics_layer_0/linear_velocity = Vector2(0, 0)
+1:0/0/physics_layer_0/angular_velocity = 0.0
+
+[resource]
+tile_size = Vector2i(48, 48)
+physics_layer_0/collision_layer = 1
+sources/0 = SubResource("TileSetAtlasSource_qhkfp")
diff --git a/godot/bomber/bomb.tscn b/godot/bomber/bomb.tscn
deleted file mode 100644
index 6af7f28..0000000
--- a/godot/bomber/bomb.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e5a7def879bd54e8f4dd7166d7ccb039680f27bc4a55bec78e1f8ee05c0d212f
-size 4262
diff --git a/godot/bomber/brickfloor.png.import b/godot/bomber/brickfloor.png.import
deleted file mode 100644
index 6320549..0000000
--- a/godot/bomber/brickfloor.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e8f3cb1339d3c62eaa2af5f52058f9432a3518d20e17042b1f93a705f3b2482d
-size 764
diff --git a/godot/bomber/charwalk.png.import b/godot/bomber/charwalk.png.import
deleted file mode 100644
index 8fdd94b..0000000
--- a/godot/bomber/charwalk.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:beccb80ec0da0c3bdf2688922914ea1c1e084d9e976adb5fece9fd948736dd52
-size 758
diff --git a/godot/bomber/example.toml b/godot/bomber/example.toml
deleted file mode 100644
index 1a68f7c..0000000
--- a/godot/bomber/example.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[display]
-title = "Bomber"
-tutorial_url = "https://rivet.gg/learn/godot/tutorials/crash-course"
-overview_weight = 100
-
-[meta]
-engine = "Godot"
-engine_version = "4.0.0"
-languages = ["GDScript"]
-features = ["Matchmaker", "DynamicServers"]
-platforms = ["Desktop"]
-networking = "GodotHLMultiplayer"
-
-
diff --git a/godot/bomber/explosion.png.import b/godot/bomber/explosion.png.import
deleted file mode 100644
index ffeb98b..0000000
--- a/godot/bomber/explosion.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7d954947c661ffbf1975e1c76dab459b07abece85dac34501e1ab71a5a021faa
-size 761
diff --git a/godot/bomber/export_presets.cfg b/godot/bomber/export_presets.cfg
new file mode 100644
index 0000000..529ee6c
--- /dev/null
+++ b/godot/bomber/export_presets.cfg
@@ -0,0 +1,39 @@
+[preset.0]
+
+name="Linux/X11"
+platform="Linux/X11"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter=""
+export_path=""
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/bptc=true
+texture_format/s3tc=true
+texture_format/etc=false
+texture_format/etc2=false
+binary_format/architecture="x86_64"
+ssh_remote_deploy/enabled=false
+ssh_remote_deploy/host="user@host_ip"
+ssh_remote_deploy/port="22"
+ssh_remote_deploy/extra_args_ssh=""
+ssh_remote_deploy/extra_args_scp=""
+ssh_remote_deploy/run_script="#!/usr/bin/env bash
+export DISPLAY=:0
+unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
+\"{temp_dir}/{exe_name}\" {cmd_args}"
+ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
+kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
+rm -rf \"{temp_dir}\""
diff --git a/godot/bomber/icon.png.import b/godot/bomber/icon.png.import
deleted file mode 100644
index 3d3246a..0000000
--- a/godot/bomber/icon.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:dfbfc2858181ccf39cf78242cd9982b0dd6824a496e07b05ad48b7b2691febc2
-size 746
diff --git a/godot/bomber/lobby.tscn b/godot/bomber/lobby.tscn
deleted file mode 100644
index 4be2f1e..0000000
--- a/godot/bomber/lobby.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cff440ef2545139ff2152d6f5bb45706c37a6c485a0961c3843f3a87a769958f
-size 2905
diff --git a/godot/bomber/montserrat.otf.import b/godot/bomber/montserrat.otf.import
deleted file mode 100644
index 71e4283..0000000
--- a/godot/bomber/montserrat.otf.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cee51a58286bbed7068867a4928b4e707d336255ce587fcb4544da48b37bd2d1
-size 666
diff --git a/godot/bomber/player.tscn b/godot/bomber/player.tscn
deleted file mode 100644
index 974190e..0000000
--- a/godot/bomber/player.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ae24881be6a8fda271a0e47875090afde79aa178d5cc2e2a0503d64b4f9b5b91
-size 5450
diff --git a/godot/bomber/project.godot b/godot/bomber/project.godot
index 134fd3f..a29235b 100644
--- a/godot/bomber/project.godot
+++ b/godot/bomber/project.godot
@@ -14,15 +14,16 @@ config/name="Multiplayer Bomber"
config/description="A multiplayer implementation of the classical bomberman game.
One of the players should press 'host', while the other
should type in his address and press 'play'."
-run/main_scene="res://lobby.tscn"
-config/features=PackedStringArray("4.0")
-config/icon="res://icon.png"
+run/main_scene="res://scenes/lobby.tscn"
+config/features=PackedStringArray("4.2")
+config/icon="res://assets/icon.png"
[autoload]
-gamestate="*res://gamestate.gd"
-RivetClient="*res://addons/rivet_api/rivet_client.gd"
-RivetHelper="*res://addons/rivet_api/rivet_helper.gd"
+gamestate="*res://scripts/gamestate.gd"
+RivetHelper="*res://addons/rivet/rivet_helper.gd"
+Rivet="*res://addons/rivet/rivet_global.gd"
+RivetGlobal="*res://addons/rivet/rivet_global.gd"
[display]
@@ -35,7 +36,7 @@ project/assembly_name="Multiplayer Bomber"
[editor_plugins]
-enabled=PackedStringArray("res://addons/rivet_api/plugin.cfg")
+enabled=PackedStringArray("res://addons/rivet/plugin.cfg")
[input]
@@ -76,7 +77,7 @@ set_bomb={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
-, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"pressed":false,"double_click":false,"script":null)
+, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
diff --git a/godot/bomber/rivet.toml b/godot/bomber/rivet.toml
deleted file mode 100644
index 9d8efdd..0000000
--- a/godot/bomber/rivet.toml
+++ /dev/null
@@ -1,52 +0,0 @@
-# === Rivet Version Configuration ===
-#
-# - More info: https://docs.rivet.gg/general/concepts/rivet-version-config
-# - Reference: https://docs.rivet.gg/cloud/api/post-games-versions#body
-# - Publish a new version with `rivet publish`
-#
-
-# How the game lobbies run and how players connect to the game.
-#
-# https://docs.rivet.gg/matchmaker/introduction
-[matchmaker]
- # How many players can join a specific lobby.
- #
- # Read more about matchmaking: https://docs.rivet.gg/matchmaker/concepts/finding-lobby
- max_players = 12
-
- # The hardware to provide for lobbies.
- #
- # Available tiers: https://docs.rivet.gg/serverless-lobbies/concepts/available-tiers
- tier = "basic-1d1"
-
-# Which regions the game should be available in.
-#
-# Available regions: https://docs.rivet.gg/serverless-lobbies/concepts/available-regions
-[matchmaker.regions]
- lnd-sfo = {}
- lnd-fra = {}
-
-# Runtime configuration for the lobby's Docker container.
-[matchmaker.docker]
- # If you're unfamiliar with Docker, here's how to write your own
- # Dockerfile:
- # https://docker-curriculum.com/#dockerfile
- dockerfile = "Dockerfile"
-
- # Which ports to allow players to connect to. Multiple ports can be defined
- # with different protocols.
- #
- # How ports work: https://docs.rivet.gg/serverless-lobbies/concepts/ports
- ports.default = { port = 10567, protocol = "udp" }
-
-# What game modes are avaiable.
-#
-# Properties like `max_players`, `tier`, `dockerfile`, `regions`, and more can
-# be overriden for specific game modes.
-[matchmaker.game_modes]
- default = {}
-
-[kv]
-
-[identity]
-
diff --git a/godot/bomber/rock.tscn b/godot/bomber/rock.tscn
deleted file mode 100644
index 5b15ea0..0000000
--- a/godot/bomber/rock.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4165c9e29444ba7c820be42b6df02431b068cdef64a827497e3ad0b5a00c7739
-size 1453
diff --git a/godot/bomber/rock_bit.png.import b/godot/bomber/rock_bit.png.import
deleted file mode 100644
index e33e85e..0000000
--- a/godot/bomber/rock_bit.png.import
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:779b33ee0a2930659c5cffc18bef2816e84d7de34d583067267f4ea65c725337
-size 758
diff --git a/godot/bomber/scenes/bomb.tscn b/godot/bomber/scenes/bomb.tscn
new file mode 100644
index 0000000..8fa12c8
--- /dev/null
+++ b/godot/bomber/scenes/bomb.tscn
@@ -0,0 +1,135 @@
+[gd_scene load_steps=9 format=3 uid="uid://enwoaqi0rnei"]
+
+[ext_resource type="Script" path="res://scripts/bomb.gd" id="1"]
+[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://assets/brickfloor.png" id="2"]
+[ext_resource type="Texture2D" uid="uid://drfbkdqmj0gu2" path="res://assets/explosion.png" id="3"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_1ih13"]
+size = Vector2(16, 192)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_whso6"]
+size = Vector2(192, 16)
+
+[sub_resource type="Curve" id="Curve_4yges"]
+max_value = 2.0
+_data = [Vector2(0.00150494, 0.398437), 0.0, 0.0, 0, 0, Vector2(0.0152287, 1.42969), 0.0, 0.0, 0, 0, Vector2(0.478607, 1.30078), 0.0, 0.0, 0, 0, Vector2(1, 0.291016), 0.0, 0.0, 0, 0]
+point_count = 4
+
+[sub_resource type="Animation" id="Animation_21j5c"]
+length = 4.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Sprite:self_modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.4, 0.6, 0.8, 1.1, 1.3, 1.5, 1.8, 1.9, 2, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 3),
+"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)]
+}
+tracks/1/type = "method"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath(".")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(2.8, 3.4),
+"transitions": PackedFloat32Array(1, 1),
+"values": [{
+"args": [],
+"method": &"explode"
+}, {
+"args": [],
+"method": &"done"
+}]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Explosion1:emitting")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 2.8),
+"transitions": PackedFloat32Array(1, 1),
+"update": 1,
+"values": [false, true]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Explosion2:emitting")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 2.8),
+"transitions": PackedFloat32Array(1, 1),
+"update": 1,
+"values": [false, true]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_h2w7m"]
+_data = {
+"anim": SubResource("Animation_21j5c")
+}
+
+[node name="Bomb" type="Area2D"]
+monitorable = false
+script = ExtResource("1")
+
+[node name="Sprite" type="Sprite2D" parent="."]
+self_modulate = Color(1, 1, 1, 0)
+position = Vector2(-2.92606, -2.92606)
+texture = ExtResource("2")
+region_enabled = true
+region_rect = Rect2(144, 0, 48, 48)
+
+[node name="Shape1" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_1ih13")
+
+[node name="Shape2" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_whso6")
+
+[node name="Explosion1" type="CPUParticles2D" parent="."]
+emitting = false
+lifetime = 0.5
+one_shot = true
+explosiveness = 0.95
+texture = ExtResource("3")
+emission_shape = 3
+emission_rect_extents = Vector2(80, 1)
+gravity = Vector2(0, 0)
+initial_velocity_min = 1.0
+initial_velocity_max = 1.0
+angular_velocity_min = 187.35
+angular_velocity_max = 188.35
+scale_amount_curve = SubResource("Curve_4yges")
+
+[node name="Explosion2" type="CPUParticles2D" parent="."]
+rotation = 1.57162
+scale = Vector2(1, 1)
+emitting = false
+lifetime = 0.5
+one_shot = true
+explosiveness = 0.95
+texture = ExtResource("3")
+emission_shape = 3
+emission_rect_extents = Vector2(80, 1)
+gravity = Vector2(0, 0)
+initial_velocity_min = 1.0
+initial_velocity_max = 1.0
+angular_velocity_min = 187.35
+angular_velocity_max = 188.35
+scale_amount_curve = SubResource("Curve_4yges")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+autoplay = "anim"
+libraries = {
+"": SubResource("AnimationLibrary_h2w7m")
+}
+
+[connection signal="body_entered" from="." to="." method="_on_bomb_body_enter"]
+[connection signal="body_exited" from="." to="." method="_on_bomb_body_exit"]
diff --git a/godot/bomber/scenes/lobby.tscn b/godot/bomber/scenes/lobby.tscn
new file mode 100644
index 0000000..72d2198
--- /dev/null
+++ b/godot/bomber/scenes/lobby.tscn
@@ -0,0 +1,121 @@
+[gd_scene load_steps=2 format=3 uid="uid://jhdlqsokif5o"]
+
+[ext_resource type="Script" path="res://scripts/lobby.gd" id="1_q5pu8"]
+
+[node name="Lobby" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 2
+size_flags_vertical = 2
+script = ExtResource("1_q5pu8")
+
+[node name="Players" type="Panel" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -126.0
+offset_top = -177.5
+offset_right = 126.0
+offset_bottom = 177.5
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 2
+size_flags_vertical = 2
+
+[node name="Label" type="Label" parent="Players"]
+layout_mode = 0
+offset_left = 26.0
+offset_top = 18.0
+offset_right = 142.0
+offset_bottom = 32.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "Awaiting Players..."
+
+[node name="Start" type="Button" parent="Players"]
+layout_mode = 0
+offset_left = 68.0
+offset_top = 307.0
+offset_right = 193.0
+offset_bottom = 336.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "START!"
+
+[node name="List" type="ItemList" parent="Players"]
+layout_mode = 0
+offset_left = 25.0
+offset_top = 37.0
+offset_right = 229.0
+offset_bottom = 296.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+
+[node name="Connect" type="Panel" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -138.0
+offset_top = -71.0
+offset_right = 139.0
+offset_bottom = 71.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 2
+size_flags_vertical = 2
+
+[node name="NameLabel" type="Label" parent="Connect"]
+layout_mode = 0
+offset_left = 54.5
+offset_top = 8.5
+offset_right = 105.5
+offset_bottom = 34.5
+size_flags_horizontal = 2
+size_flags_vertical = 0
+text = "Name:"
+
+[node name="Name" type="LineEdit" parent="Connect"]
+layout_mode = 0
+offset_left = 54.5
+offset_top = 37.5
+offset_right = 210.5
+offset_bottom = 68.5
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "The Warrior"
+
+[node name="ErrorLabel" type="Label" parent="Connect"]
+layout_mode = 0
+offset_left = 15.0
+offset_top = 125.0
+offset_right = 257.0
+offset_bottom = 139.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+theme_override_colors/font_color = Color(0.820312, 0.291595, 0.291595, 1)
+
+[node name="Join" type="Button" parent="Connect"]
+layout_mode = 0
+offset_left = 77.5
+offset_top = 84.5
+offset_right = 170.5
+offset_bottom = 115.5
+size_flags_horizontal = 2
+size_flags_vertical = 2
+text = "Find Lobby"
+
+[node name="ErrorDialog" type="AcceptDialog" parent="."]
+
+[connection signal="pressed" from="Players/Start" to="." method="_on_start_pressed"]
+[connection signal="pressed" from="Connect/Join" to="." method="_on_join_pressed"]
diff --git a/godot/bomber/scenes/player.tscn b/godot/bomber/scenes/player.tscn
new file mode 100644
index 0000000..ac1dd88
--- /dev/null
+++ b/godot/bomber/scenes/player.tscn
@@ -0,0 +1,206 @@
+[gd_scene load_steps=16 format=3 uid="uid://dviwgv2ty8v6u"]
+
+[ext_resource type="Script" path="res://scripts/player.gd" id="1"]
+[ext_resource type="Texture2D" uid="uid://bsqovikudjr0q" path="res://assets/charwalk.png" id="2"]
+[ext_resource type="FontFile" uid="uid://knb8u535cfkw" path="res://assets/montserrat.otf" id="3"]
+[ext_resource type="Script" path="res://scripts/player_controls.gd" id="4_k1vfr"]
+
+[sub_resource type="CircleShape2D" id="1"]
+radius = 20.0
+
+[sub_resource type="Animation" id="2"]
+resource_name = "standing"
+length = 0.8
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"update": 1,
+"values": [0, 4, 8, 12]
+}
+
+[sub_resource type="Animation" id="3"]
+resource_name = "stunned"
+length = 1.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [0]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath(".:stunned")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(1),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("sprite:rotation")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [-6.28319, 0.0]
+}
+
+[sub_resource type="Animation" id="4"]
+length = 0.8
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"update": 1,
+"values": [0, 4, 8, 12]
+}
+
+[sub_resource type="Animation" id="5"]
+length = 0.8
+loop_mode = 1
+step = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"update": 1,
+"values": [1, 5, 9, 13]
+}
+
+[sub_resource type="Animation" id="6"]
+length = 0.8
+loop_mode = 1
+step = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"update": 1,
+"values": [3, 7, 11, 15]
+}
+
+[sub_resource type="Animation" id="7"]
+length = 0.8
+loop_mode = 1
+step = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("sprite:frame")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.4, 0.6),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"update": 1,
+"values": [2, 6, 10, 14]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_yb83i"]
+_data = {
+"standing": SubResource("2"),
+"stunned": SubResource("3"),
+"walk_down": SubResource("4"),
+"walk_left": SubResource("5"),
+"walk_right": SubResource("6"),
+"walk_up": SubResource("7")
+}
+
+[sub_resource type="LabelSettings" id="LabelSettings_5huhx"]
+outline_size = 8
+outline_color = Color(0, 0, 0, 1)
+
+[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_sh64w"]
+properties/0/path = NodePath(".:synced_position")
+properties/0/spawn = true
+properties/0/sync = true
+properties/1/path = NodePath("label:text")
+properties/1/spawn = true
+properties/1/sync = false
+
+[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_w53uu"]
+properties/0/path = NodePath(".:motion")
+properties/0/spawn = true
+properties/0/sync = true
+properties/1/path = NodePath(".:bombing")
+properties/1/spawn = true
+properties/1/sync = true
+
+[node name="player" type="CharacterBody2D"]
+z_index = 10
+motion_mode = 1
+script = ExtResource("1")
+
+[node name="sprite" type="Sprite2D" parent="."]
+position = Vector2(0.0750351, 6.23615)
+rotation = -6.28319
+texture = ExtResource("2")
+offset = Vector2(-0.0750351, -6.23615)
+hframes = 4
+vframes = 4
+
+[node name="shape" type="CollisionShape2D" parent="."]
+shape = SubResource("1")
+
+[node name="anim" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_yb83i")
+}
+
+[node name="label" type="Label" parent="."]
+offset_left = -82.0
+offset_top = -35.0
+offset_right = 85.0
+offset_bottom = -14.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+theme_override_fonts/font = ExtResource("3")
+theme_override_font_sizes/font_size = 16
+text = "Player 1"
+label_settings = SubResource("LabelSettings_5huhx")
+horizontal_alignment = 1
+
+[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
+replication_config = SubResource("SceneReplicationConfig_sh64w")
+
+[node name="Inputs" type="Node" parent="."]
+script = ExtResource("4_k1vfr")
+
+[node name="InputsSync" type="MultiplayerSynchronizer" parent="Inputs"]
+replication_config = SubResource("SceneReplicationConfig_w53uu")
diff --git a/godot/bomber/scenes/rock.tscn b/godot/bomber/scenes/rock.tscn
new file mode 100644
index 0000000..3afd012
--- /dev/null
+++ b/godot/bomber/scenes/rock.tscn
@@ -0,0 +1,58 @@
+[gd_scene load_steps=6 format=3 uid="uid://bao3yernlglws"]
+
+[ext_resource type="Script" path="res://scripts/rock.gd" id="1"]
+[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://assets/brickfloor.png" id="2"]
+
+[sub_resource type="RectangleShape2D" id="1"]
+size = Vector2(48, 48)
+
+[sub_resource type="Animation" id="2"]
+resource_name = "explode"
+tracks/0/type = "method"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(1),
+"transitions": PackedFloat32Array(1),
+"values": [{
+"args": [],
+"method": &"queue_free"
+}]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Sprite:visible")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_6pqaw"]
+_data = {
+"explode": SubResource("2")
+}
+
+[node name="Rock" type="CharacterBody2D"]
+motion_mode = 1
+script = ExtResource("1")
+
+[node name="Sprite" type="Sprite2D" parent="."]
+texture = ExtResource("2")
+region_enabled = true
+region_rect = Rect2(96, 0, 48, 48)
+
+[node name="Shape" type="CollisionShape2D" parent="."]
+shape = SubResource("1")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_6pqaw")
+}
diff --git a/godot/bomber/scenes/tile_scene.tscn b/godot/bomber/scenes/tile_scene.tscn
new file mode 100644
index 0000000..abcee0e
--- /dev/null
+++ b/godot/bomber/scenes/tile_scene.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=3 format=3 uid="uid://c5m3rogpaglk1"]
+
+[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://assets/brickfloor.png" id="1"]
+
+[sub_resource type="RectangleShape2D" id="1"]
+size = Vector2(48, 48)
+
+[node name="TileScene" type="Node2D"]
+
+[node name="Wall" type="Sprite2D" parent="."]
+position = Vector2(24, 24)
+texture = ExtResource("1")
+region_rect = Rect2(0, 0, 48, 48)
+
+[node name="StaticBody2D" type="StaticBody2D" parent="Wall"]
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="Wall/StaticBody2D"]
+shape = SubResource("1")
+
+[node name="Floor" type="Sprite2D" parent="."]
+position = Vector2(72, 24)
+texture = ExtResource("1")
+region_rect = Rect2(48, 0, 48, 48)
diff --git a/godot/bomber/scenes/world.tscn b/godot/bomber/scenes/world.tscn
new file mode 100644
index 0000000..80aea31
--- /dev/null
+++ b/godot/bomber/scenes/world.tscn
@@ -0,0 +1,310 @@
+[gd_scene load_steps=6 format=3 uid="uid://by3f5o7dyoqx4"]
+
+[ext_resource type="TileSet" uid="uid://do2l6lpuotti8" path="res://assets/tileset.tres" id="1"]
+[ext_resource type="PackedScene" uid="uid://bao3yernlglws" path="res://scenes/rock.tscn" id="2"]
+[ext_resource type="Script" path="res://scripts/score.gd" id="3"]
+[ext_resource type="FontFile" uid="uid://knb8u535cfkw" path="res://assets/montserrat.otf" id="4"]
+[ext_resource type="Script" path="res://scripts/bomb_spawner.gd" id="6_ac5ja"]
+
+[node name="World" type="Node2D"]
+
+[node name="TileMap" type="TileMap" parent="."]
+tile_set = ExtResource("1")
+rendering_quadrant_size = 48
+format = 2
+layer_0/tile_data = PackedInt32Array(0, 0, 0, 65536, 0, 0, 131072, 0, 0, 196608, 0, 0, 262144, 0, 0, 327680, 0, 0, 393216, 0, 0, 458752, 0, 0, 524288, 0, 0, 589824, 0, 0, 655360, 0, 0, 720896, 0, 0, 786432, 0, 0, 1, 0, 0, 65537, 65536, 0, 131073, 65536, 0, 196609, 65536, 0, 262145, 65536, 0, 327681, 65536, 0, 393217, 65536, 0, 458753, 65536, 0, 524289, 65536, 0, 589825, 65536, 0, 655361, 65536, 0, 720897, 65536, 0, 786433, 0, 0, 2, 0, 0, 65538, 65536, 0, 131074, 0, 0, 196610, 65536, 0, 262146, 0, 0, 327682, 65536, 0, 393218, 0, 0, 458754, 65536, 0, 524290, 0, 0, 589826, 65536, 0, 655362, 0, 0, 720898, 65536, 0, 786434, 0, 0, 3, 0, 0, 65539, 65536, 0, 131075, 65536, 0, 196611, 65536, 0, 262147, 65536, 0, 327683, 65536, 0, 393219, 65536, 0, 458755, 65536, 0, 524291, 0, 0, 589827, 65536, 0, 655363, 65536, 0, 720899, 65536, 0, 786435, 0, 0, 4, 0, 0, 65540, 65536, 0, 131076, 0, 0, 196612, 0, 0, 262148, 0, 0, 327684, 65536, 0, 393220, 0, 0, 458756, 65536, 0, 524292, 0, 0, 589828, 65536, 0, 655364, 0, 0, 720900, 65536, 0, 786436, 0, 0, 5, 0, 0, 65541, 65536, 0, 131077, 65536, 0, 196613, 65536, 0, 262149, 65536, 0, 327685, 65536, 0, 393221, 65536, 0, 458757, 65536, 0, 524293, 65536, 0, 589829, 65536, 0, 655365, 65536, 0, 720901, 65536, 0, 786437, 0, 0, 6, 0, 0, 65542, 65536, 0, 131078, 0, 0, 196614, 65536, 0, 262150, 0, 0, 327686, 0, 0, 393222, 0, 0, 458758, 65536, 0, 524294, 0, 0, 589830, 65536, 0, 655366, 0, 0, 720902, 65536, 0, 786438, 0, 0, 7, 0, 0, 65543, 65536, 0, 131079, 65536, 0, 196615, 65536, 0, 262151, 65536, 0, 327687, 65536, 0, 393223, 65536, 0, 458759, 65536, 0, 524295, 65536, 0, 589831, 65536, 0, 655367, 65536, 0, 720903, 65536, 0, 786439, 0, 0, 8, 0, 0, 65544, 65536, 0, 131080, 0, 0, 196616, 65536, 0, 262152, 0, 0, 327688, 65536, 0, 393224, 0, 0, 458760, 65536, 0, 524296, 0, 0, 589832, 65536, 0, 655368, 0, 0, 720904, 65536, 0, 786440, 0, 0, 9, 0, 0, 65545, 65536, 0, 131081, 65536, 0, 196617, 65536, 0, 262153, 65536, 0, 327689, 65536, 0, 393225, 65536, 0, 458761, 65536, 0, 524297, 65536, 0, 589833, 65536, 0, 655369, 65536, 0, 720905, 65536, 0, 786441, 0, 0, 10, 0, 0, 65546, 65536, 0, 131082, 0, 0, 196618, 0, 0, 262154, 0, 0, 327690, 65536, 0, 393226, 0, 0, 458762, 65536, 0, 524298, 0, 0, 589834, 65536, 0, 655370, 0, 0, 720906, 65536, 0, 786442, 0, 0, 11, 0, 0, 65547, 65536, 0, 131083, 0, 0, 196619, 65536, 0, 262155, 65536, 0, 327691, 65536, 0, 393227, 65536, 0, 458763, 65536, 0, 524299, 65536, 0, 589835, 65536, 0, 655371, 65536, 0, 720907, 65536, 0, 786443, 0, 0, 12, 0, 0, 65548, 65536, 0, 131084, 0, 0, 196620, 65536, 0, 262156, 0, 0, 327692, 65536, 0, 393228, 0, 0, 458764, 65536, 0, 524300, 0, 0, 589836, 65536, 0, 655372, 0, 0, 720908, 65536, 0, 786444, 0, 0, 13, 0, 0, 65549, 65536, 0, 131085, 0, 0, 196621, 65536, 0, 262157, 65536, 0, 327693, 65536, 0, 393229, 0, 0, 458765, 65536, 0, 524301, 0, 0, 589837, 65536, 0, 655373, 65536, 0, 720909, 65536, 0, 786445, 0, 0, 14, 0, 0, 65550, 65536, 0, 131086, 0, 0, 196622, 65536, 0, 262158, 0, 0, 327694, 65536, 0, 393230, 0, 0, 458766, 65536, 0, 524302, 0, 0, 589838, 65536, 0, 655374, 0, 0, 720910, 65536, 0, 786446, 0, 0, 15, 0, 0, 65551, 65536, 0, 131087, 65536, 0, 196623, 65536, 0, 262159, 65536, 0, 327695, 65536, 0, 393231, 0, 0, 458767, 65536, 0, 524303, 65536, 0, 589839, 65536, 0, 655375, 65536, 0, 720911, 65536, 0, 786447, 0, 0, 16, 0, 0, 65552, 65536, 0, 131088, 0, 0, 196624, 65536, 0, 262160, 0, 0, 327696, 65536, 0, 393232, 0, 0, 458768, 65536, 0, 524304, 0, 0, 589840, 65536, 0, 655376, 0, 0, 720912, 65536, 0, 786448, 0, 0, 17, 0, 0, 65553, 65536, 0, 131089, 65536, 0, 196625, 65536, 0, 262161, 65536, 0, 327697, 65536, 0, 393233, 65536, 0, 458769, 65536, 0, 524305, 65536, 0, 589841, 65536, 0, 655377, 65536, 0, 720913, 65536, 0, 786449, 0, 0, 18, 0, 0, 65554, 65536, 0, 131090, 0, 0, 196626, 65536, 0, 262162, 0, 0, 327698, 0, 0, 393234, 0, 0, 458770, 65536, 0, 524306, 0, 0, 589842, 65536, 0, 655378, 0, 0, 720914, 65536, 0, 786450, 0, 0, 19, 0, 0, 65555, 65536, 0, 131091, 65536, 0, 196627, 65536, 0, 262163, 65536, 0, 327699, 65536, 0, 393235, 65536, 0, 458771, 65536, 0, 524307, 65536, 0, 589843, 65536, 0, 655379, 65536, 0, 720915, 65536, 0, 786451, 0, 0, 20, 0, 0, 65556, 0, 0, 131092, 0, 0, 196628, 0, 0, 262164, 0, 0, 327700, 0, 0, 393236, 0, 0, 458772, 0, 0, 524308, 0, 0, 589844, 0, 0, 655380, 0, 0, 720916, 0, 0, 786452, 0, 0, 21, 0, 0, 65557, 0, 0, 131093, 0, 0, 196629, 0, 0, 262165, 0, 0, 327701, 0, 0, 393237, 0, 0, 458773, 0, 0, 524309, 0, 0, 589845, 0, 0, 655381, 0, 0, 720917, 0, 0, 786453, 0, 0)
+
+[node name="SpawnPoints" type="Node2D" parent="."]
+
+[node name="0" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(72, 72)
+
+[node name="1" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(264, 216)
+
+[node name="2" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(72, 456)
+
+[node name="3" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(360, 552)
+
+[node name="4" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(840, 360)
+
+[node name="5" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(456, 264)
+
+[node name="6" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(696, 264)
+
+[node name="7" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(744, 456)
+
+[node name="8" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(312, 456)
+
+[node name="9" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(696, 72)
+
+[node name="10" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(504, 72)
+
+[node name="11" type="Marker2D" parent="SpawnPoints"]
+position = Vector2(936, 72)
+
+[node name="Rocks" type="Node2D" parent="."]
+
+[node name="Rock0" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(120, 72)
+
+[node name="Rock1" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 168)
+
+[node name="Rock2" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 120)
+
+[node name="Rock3" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(216, 72)
+
+[node name="Rock4" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 72)
+
+[node name="Rock5" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(312, 72)
+
+[node name="Rock6" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(552, 168)
+
+[node name="Rock7" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(600, 168)
+
+[node name="Rock8" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(552, 216)
+
+[node name="Rock9" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 312)
+
+[node name="Rock10" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(120, 360)
+
+[node name="Rock11" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(168, 360)
+
+[node name="Rock12" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(216, 360)
+
+[node name="Rock13" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(120, 264)
+
+[node name="Rock14" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(168, 216)
+
+[node name="Rock15" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(72, 360)
+
+[node name="Rock16" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(72, 312)
+
+[node name="Rock17" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(72, 264)
+
+[node name="Rock18" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(360, 360)
+
+[node name="Rock19" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(408, 360)
+
+[node name="Rock20" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(504, 360)
+
+[node name="Rock21" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(600, 360)
+
+[node name="Rock22" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(648, 360)
+
+[node name="Rock23" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(504, 456)
+
+[node name="Rock24" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(552, 456)
+
+[node name="Rock25" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(552, 408)
+
+[node name="Rock26" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(360, 456)
+
+[node name="Rock27" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(360, 504)
+
+[node name="Rock28" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 504)
+
+[node name="Rock29" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(264, 552)
+
+[node name="Rock30" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(168, 456)
+
+[node name="Rock31" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(168, 504)
+
+[node name="Rock32" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(72, 552)
+
+[node name="Rock33" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(120, 552)
+
+[node name="Rock34" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(504, 552)
+
+[node name="Rock35" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(600, 552)
+
+[node name="Rock36" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(648, 552)
+
+[node name="Rock37" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(648, 504)
+
+[node name="Rock38" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(456, 216)
+
+[node name="Rock39" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(360, 216)
+
+[node name="Rock40" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(360, 168)
+
+[node name="Rock41" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(456, 120)
+
+[node name="Rock42" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(456, 408)
+
+[node name="Rock43" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(456, 456)
+
+[node name="Rock44" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(456, 504)
+
+[node name="Rock45" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(600, 264)
+
+[node name="Rock46" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(600, 72)
+
+[node name="Rock47" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(408, 72)
+
+[node name="Rock48" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(792, 168)
+
+[node name="Rock49" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(744, 168)
+
+[node name="Rock50" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(744, 264)
+
+[node name="Rock51" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(792, 264)
+
+[node name="Rock52" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(744, 360)
+
+[node name="Rock53" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(744, 408)
+
+[node name="Rock54" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(792, 552)
+
+[node name="Rock55" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 552)
+
+[node name="Rock56" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 504)
+
+[node name="Rock57" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 312)
+
+[node name="Rock58" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 264)
+
+[node name="Rock59" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 216)
+
+[node name="Rock60" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 120)
+
+[node name="Rock61" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(792, 72)
+
+[node name="Rock62" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 72)
+
+[node name="Rock63" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(936, 216)
+
+[node name="Rock64" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(936, 264)
+
+[node name="Rock65" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(936, 408)
+
+[node name="Rock66" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(888, 456)
+
+[node name="Rock67" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(936, 456)
+
+[node name="Rock68" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(792, 456)
+
+[node name="Rock69" parent="Rocks" instance=ExtResource("2")]
+position = Vector2(840, 456)
+
+[node name="Players" type="Node2D" parent="."]
+
+[node name="Score" type="HBoxContainer" parent="."]
+offset_right = 1024.0
+offset_bottom = 40.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+script = ExtResource("3")
+
+[node name="Winner" type="Label" parent="."]
+visible = false
+offset_right = 1031.0
+offset_bottom = 617.0
+size_flags_horizontal = 2
+size_flags_vertical = 0
+theme_override_constants/shadow_offset_x = 2
+theme_override_constants/shadow_offset_y = 2
+theme_override_fonts/font = ExtResource("4")
+text = "THE WINNER IS:
+YOU"
+
+[node name="ExitGame" type="Button" parent="Winner"]
+layout_mode = 0
+offset_left = 384.0
+offset_top = 408.0
+offset_right = 649.0
+offset_bottom = 469.0
+size_flags_horizontal = 2
+size_flags_vertical = 2
+theme_override_fonts/font = ExtResource("4")
+text = "EXIT GAME"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+offset = Vector2(512, 300)
+
+[node name="PlayerSpawner" type="MultiplayerSpawner" parent="."]
+_spawnable_scenes = PackedStringArray("res://scenes/player.tscn")
+spawn_path = NodePath("../Players")
+
+[node name="BombSpawner" type="MultiplayerSpawner" parent="."]
+spawn_path = NodePath("..")
+script = ExtResource("6_ac5ja")
+
+[connection signal="pressed" from="Winner/ExitGame" to="Score" method="_on_exit_game_pressed"]
diff --git a/godot/bomber/screenshots/.gdignore b/godot/bomber/screenshots/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/godot/bomber/screenshots/bomber.png b/godot/bomber/screenshots/bomber.png
new file mode 100644
index 0000000..44dd08b
--- /dev/null
+++ b/godot/bomber/screenshots/bomber.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:23b59808b1faed44ffdea7169f128141799aae0db2e5bee7b9cff32a3b7c2dee
+size 55531
diff --git a/godot/bomber/bomb.gd b/godot/bomber/scripts/bomb.gd
similarity index 100%
rename from godot/bomber/bomb.gd
rename to godot/bomber/scripts/bomb.gd
diff --git a/godot/bomber/bomb_spawner.gd b/godot/bomber/scripts/bomb_spawner.gd
similarity index 81%
rename from godot/bomber/bomb_spawner.gd
rename to godot/bomber/scripts/bomb_spawner.gd
index 6200640..453884f 100644
--- a/godot/bomber/bomb_spawner.gd
+++ b/godot/bomber/scripts/bomb_spawner.gd
@@ -7,7 +7,7 @@ func _init():
func _spawn_bomb(data):
if data.size() != 2 or typeof(data[0]) != TYPE_VECTOR2 or typeof(data[1]) != TYPE_INT:
return null
- var bomb = preload("res://bomb.tscn").instantiate()
+ var bomb = preload("res://scenes/bomb.tscn").instantiate()
bomb.position = data[0]
bomb.from_player = data[1]
return bomb
diff --git a/godot/bomber/gamestate.gd b/godot/bomber/scripts/gamestate.gd
similarity index 85%
rename from godot/bomber/gamestate.gd
rename to godot/bomber/scripts/gamestate.gd
index 294f614..d78ee90 100644
--- a/godot/bomber/gamestate.gd
+++ b/godot/bomber/scripts/gamestate.gd
@@ -26,9 +26,6 @@ signal game_error(what)
func _ready():
- RivetHelper.start_server.connect(start_server)
- RivetHelper.setup_multiplayer()
-
multiplayer.peer_connected.connect(self._player_connected)
multiplayer.peer_disconnected.connect(self._player_disconnected)
multiplayer.connected_to_server.connect(self._connected_ok)
@@ -38,13 +35,11 @@ func _ready():
func start_server():
print("Starting server on %s" % DEFAULT_PORT)
-
peer = ENetMultiplayerPeer.new()
peer.create_server(DEFAULT_PORT, MAX_PEERS)
+
multiplayer.set_multiplayer_peer(peer)
-
- RivetClient.lobby_ready({}, func(_x): pass, func(_x): pass)
# Callback from SceneTree.
@@ -84,7 +79,7 @@ func _server_disconnected():
# Callback from SceneTree, only for clients (not server).
func _connected_fail():
- multiplayer.set_network_peer(null) # Remove peer
+ multiplayer.set_multiplayer_peer(null) # Remove peer
connection_failed.emit()
@@ -104,7 +99,7 @@ func unregister_player(id):
@rpc("call_local", "reliable")
func load_world():
# Change scene.
- var world = load("res://world.tscn").instantiate()
+ var world = load("res://scenes/world.tscn").instantiate()
get_tree().get_root().add_child(world)
get_tree().get_root().get_node("Lobby").hide()
@@ -116,28 +111,14 @@ func load_world():
func join_game(new_player_name):
+ print("Joining game as %s" % new_player_name)
player_name = new_player_name
-
- RivetClient.find_lobby({
- "game_modes": ["default"]
- }, _lobby_found, _lobby_find_failed)
+ peer.create_client("127.0.0.1", DEFAULT_PORT)
-func _lobby_found(response):
- RivetHelper.set_player_token(response.player.token)
-
- var port = response.ports.default
- print("Connecting to ", port.host)
-
- peer = ENetMultiplayerPeer.new()
- peer.create_client(port.hostname, port.port)
multiplayer.set_multiplayer_peer(peer)
-func _lobby_find_failed(error):
- game_error.emit(error)
-
-
func get_player_list():
return players.values()
@@ -151,11 +132,11 @@ func get_player_name():
func begin_game():
if !multiplayer.is_server():
return
-
+
load_world.rpc()
var world = get_tree().get_root().get_node("World")
- var player_scene = load("res://player.tscn")
+ var player_scene = load("res://scenes/player.tscn")
# Create a dictionary with peer id and respective spawn points, could be improved by randomizing.
var spawn_points = {}
diff --git a/godot/bomber/lobby.gd b/godot/bomber/scripts/lobby.gd
similarity index 100%
rename from godot/bomber/lobby.gd
rename to godot/bomber/scripts/lobby.gd
diff --git a/godot/bomber/player.gd b/godot/bomber/scripts/player.gd
similarity index 100%
rename from godot/bomber/player.gd
rename to godot/bomber/scripts/player.gd
diff --git a/godot/bomber/player_controls.gd b/godot/bomber/scripts/player_controls.gd
similarity index 100%
rename from godot/bomber/player_controls.gd
rename to godot/bomber/scripts/player_controls.gd
diff --git a/godot/bomber/rock.gd b/godot/bomber/scripts/rock.gd
similarity index 100%
rename from godot/bomber/rock.gd
rename to godot/bomber/scripts/rock.gd
diff --git a/godot/bomber/scripts/run.sh b/godot/bomber/scripts/run.sh
deleted file mode 100755
index 5e82388..0000000
--- a/godot/bomber/scripts/run.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-/Applications/Godot.app/Contents/MacOS/Godot --headless -- --server
-
diff --git a/godot/bomber/score.gd b/godot/bomber/scripts/score.gd
similarity index 92%
rename from godot/bomber/score.gd
rename to godot/bomber/scripts/score.gd
index 1358427..b7ee8e8 100644
--- a/godot/bomber/score.gd
+++ b/godot/bomber/scripts/score.gd
@@ -17,7 +17,6 @@ func _process(_delta):
func increase_score(for_who):
- assert(for_who in player_labels)
var pl = player_labels[for_who]
pl.score += 1
pl.label.set_text(pl.name + "\n" + str(pl.score))
@@ -28,7 +27,7 @@ func add_player(id, new_player_name):
l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
l.set_text(new_player_name + "\n" + "0")
l.set_h_size_flags(SIZE_EXPAND_FILL)
- var font = preload("res://montserrat.otf")
+ var font = preload("res://assets/montserrat.otf")
l.set("custom_fonts/font", font)
l.set("custom_font_size/font_size", 18)
add_child(l)
diff --git a/godot/bomber/tile_scene.tscn b/godot/bomber/tile_scene.tscn
deleted file mode 100644
index 2ec2a1f..0000000
--- a/godot/bomber/tile_scene.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:14edd4a2a0a0664ad6ec2bec8ab6b88036ba76d88524f163de84b037b564282a
-size 704
diff --git a/godot/bomber/tileset.tres b/godot/bomber/tileset.tres
deleted file mode 100644
index 5cf3fb2..0000000
--- a/godot/bomber/tileset.tres
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bd5efed4ab4359ab2dd1a04ab853bdb84f97b9260722f31dcacae857aa893259
-size 752
diff --git a/godot/bomber/world.tscn b/godot/bomber/world.tscn
deleted file mode 100644
index 57f3b53..0000000
--- a/godot/bomber/world.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cc2b7620bc2f00cae8db645cd1bb51eab1add7313dc497b9d082415a8d3c9dbc
-size 13915