-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4f235b5
commit 8da97f2
Showing
100 changed files
with
2,890 additions
and
449 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,16 @@ | ||
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 | ||
# MARK: Builder | ||
FROM ghcr.io/angelonfira/rivet-godot-docker/godot:4.2 AS builder | ||
WORKDIR /app | ||
COPY . . | ||
RUN mkdir -p build/linux \ | ||
&& godot -v --export-release "Linux/X11" --headless ./build/linux/game.x86_64 | ||
|
||
# === | ||
|
||
# MARK: Runner | ||
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"] | ||
|
||
CMD ["sh", "-c", "echo 'test' && env && unbuffer /app/game.x86_64 --verbose --headless -- --server | cat"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,15 @@ | ||
# Bomber | ||
# Multiplayer Bomber | ||
|
||
<p align="center"> | ||
<img src="./_media/preview_512.png" /> | ||
</p> | ||
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 | ||
|
||
| Engine Version | Language | Networking | | ||
| --- | --- | --- | | ||
| 4.0.0 | [GDScript](https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html) | [High-Level Multiplayer](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html) | | ||
|
||
**Rivet Features** | ||
|
||
- [♟️ Matchmaker](https://rivet.gg/docs/matchmaker) | ||
- [🌐 Dynamic Servers](https://rivet.gg/docs/dynamic-servers) | ||
|
||
|
||
## Running locally | ||
|
||
1. [Clone the GitHub repo](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) | ||
2. Open this folder | ||
3. Run: `rivet init` | ||
4. Run `yarn start` | ||
|
||
## Deploying to Rivet | ||
|
||
[Documentation](https://rivet.gg/learn/godot/tutorials/crash-course#step-4-deploy-to-rivet) | ||
## Screenshots | ||
|
||
![Screenshot](screenshots/bomber.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
const _RivetResponse = preload("rivet_response.gd") | ||
|
||
## Lobby | ||
## @experimental | ||
class Lobby: | ||
## 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(body: Dictionary = {}) -> _RivetResponse: | ||
return await Rivet.GET("matchmaker/lobbies/state", body).wait_completed() | ||
|
||
## Player | ||
## @experimental | ||
class Player: | ||
## 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 Region: | ||
## 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 lobby: Lobby = Lobby.new() | ||
static var player: Player = Player.new() | ||
static var region: Region = Region.new() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
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 | ||
|
||
## Waits for the request to complete and returns the response in non-blocking way | ||
func wait_completed2(): | ||
return completed | ||
|
Oops, something went wrong.