Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Commit

Permalink
Add godot ball example
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelOnFira committed Jan 31, 2024
1 parent 4cc223d commit 9c451bb
Show file tree
Hide file tree
Showing 61 changed files with 1,932 additions and 0 deletions.
1 change: 1 addition & 0 deletions godot/bomber/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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"
RivetClient="*res://addons/rivet/rivet_client.gd"

[display]

Expand Down
2 changes: 2 additions & 0 deletions godot/circles/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
2 changes: 2 additions & 0 deletions godot/circles/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Godot 4+ specific ignores
.godot/
19 changes: 19 additions & 0 deletions godot/circles/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ghcr.io/rivet-gg/godot-docker/godot:4.2.1 AS builder
WORKDIR /app
COPY . .
RUN mkdir -p build/linux \
&& godot -v --export-release "Linux/X11" ./build/linux/game.x86_64 --headless

FROM ubuntu:22.04
RUN apt update -y \
&& apt install -y expect-dev \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -ms /bin/bash rivet

COPY --from=builder /app/build/linux/ /app

# Change to user rivet
USER rivet

# Unbuffer output so the logs get flushed
CMD ["sh", "-c", "unbuffer /app/game.x86_64 --verbose --headless -- --server | cat"]
132 changes: 132 additions & 0 deletions godot/circles/addons/rivet/api/rivet_api.gd
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
})
97 changes: 97 additions & 0 deletions godot/circles/addons/rivet/api/rivet_packages.gd
Original file line number Diff line number Diff line change
@@ -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()
57 changes: 57 additions & 0 deletions godot/circles/addons/rivet/api/rivet_request.gd
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions godot/circles/addons/rivet/api/rivet_response.gd
Original file line number Diff line number Diff line change
@@ -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()

33 changes: 33 additions & 0 deletions godot/circles/addons/rivet/devtools/dock/deploy_tab.gd
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions godot/circles/addons/rivet/devtools/dock/deploy_tab.tscn
Git LFS file not shown
Loading

0 comments on commit 9c451bb

Please sign in to comment.