diff --git a/LICENSE b/LICENSE index cfa3809..8108a07 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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/_internal/templater/src/main.rs b/_internal/templater/src/main.rs index fcbb0a6..2a488df 100644 --- a/_internal/templater/src/main.rs +++ b/_internal/templater/src/main.rs @@ -45,6 +45,7 @@ enum ConfigMetaLanguage { CPlusPlus, CSharp, Rust, + GDScript, } impl ConfigMetaLanguage { @@ -55,6 +56,7 @@ impl ConfigMetaLanguage { Self::CPlusPlus => "https://isocpp.org", Self::CSharp => "https://docs.microsoft.com/en-us/dotnet/csharp/", Self::Rust => "https://www.rust-lang.org", + Self::GDScript => "https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html", } } } @@ -67,6 +69,7 @@ impl std::fmt::Display for ConfigMetaLanguage { Self::CPlusPlus => write!(f, "C++"), Self::CSharp => write!(f, "C#"), Self::Rust => write!(f, "Rust"), + Self::GDScript => write!(f, "GDScript"), } } } diff --git a/c/coredump/LICENSE b/c/coredump/LICENSE index cfa3809..8108a07 100644 --- a/c/coredump/LICENSE +++ b/c/coredump/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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/.dockerignore b/godot/bomber/.dockerignore new file mode 100644 index 0000000..68e69b9 --- /dev/null +++ b/godot/bomber/.dockerignore @@ -0,0 +1,19 @@ +# Docker +Dockerfile +.dockerignore + +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json + +# System/tool-specific ignores +.directory +.DS_Store +*~ diff --git a/godot/bomber/.gitattributes b/godot/bomber/.gitattributes new file mode 100644 index 0000000..c64e184 --- /dev/null +++ b/godot/bomber/.gitattributes @@ -0,0 +1,4 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf + +*.hdr binary diff --git a/godot/bomber/.gitignore b/godot/bomber/.gitignore new file mode 100644 index 0000000..8a4d9ff --- /dev/null +++ b/godot/bomber/.gitignore @@ -0,0 +1,19 @@ +# Godot 4+ specific ignores +.godot/ + +# Godot-specific ignores +.import/ + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json + +# System/tool-specific ignores +.directory +.DS_Store +*~ + +### Rivet ### +.rivet/ +.env diff --git a/godot/bomber/Dockerfile b/godot/bomber/Dockerfile new file mode 100644 index 0000000..1fee748 --- /dev/null +++ b/godot/bomber/Dockerfile @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000..8108a07 --- /dev/null +++ b/godot/bomber/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/godot/bomber/README.md b/godot/bomber/README.md new file mode 100644 index 0000000..ff007a6 --- /dev/null +++ b/godot/bomber/README.md @@ -0,0 +1,28 @@ +# Bomber + + + +[Visit Tutorial](https://rivet.gg/learn/godot/tutorials/crash-course) + + +| 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) + diff --git a/godot/bomber/addons/rivet_api/dock/dock.gd b/godot/bomber/addons/rivet_api/dock/dock.gd new file mode 100644 index 0000000..3f51af3 --- /dev/null +++ b/godot/bomber/addons/rivet_api/dock/dock.gd @@ -0,0 +1,84 @@ +@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 new file mode 100644 index 0000000..d148e5f --- /dev/null +++ b/godot/bomber/addons/rivet_api/dock/dock.tscn @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..5d5cf90 --- /dev/null +++ b/godot/bomber/addons/rivet_api/dotenv.gd @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000..67da309 --- /dev/null +++ b/godot/bomber/addons/rivet_api/images/icon-circle.png @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..2ebd2b6 --- /dev/null +++ b/godot/bomber/addons/rivet_api/images/icon-circle.png.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c7ccf8d99e564fe5476756802753428d79249c7829272b1e917385203f0ca2f +size 791 diff --git a/godot/bomber/addons/rivet_api/plugin.cfg b/godot/bomber/addons/rivet_api/plugin.cfg new file mode 100644 index 0000000..fe1097e --- /dev/null +++ b/godot/bomber/addons/rivet_api/plugin.cfg @@ -0,0 +1,8 @@ +[plugin] + +name="Rivet API" +description="" +author="Rivet Gaming, Inc." +version="0.0.1" +script="rivet_api.gd" + diff --git a/godot/bomber/addons/rivet_api/rivet.toml.tpl b/godot/bomber/addons/rivet_api/rivet.toml.tpl new file mode 100644 index 0000000..ca1b0e3 --- /dev/null +++ b/godot/bomber/addons/rivet_api/rivet.toml.tpl @@ -0,0 +1,52 @@ +# === 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 = 32 + + # 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/addons/rivet_api/rivet_api.gd b/godot/bomber/addons/rivet_api/rivet_api.gd new file mode 100644 index 0000000..3a254fe --- /dev/null +++ b/godot/bomber/addons/rivet_api/rivet_api.gd @@ -0,0 +1,27 @@ +@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/addons/rivet_api/rivet_client.gd b/godot/bomber/addons/rivet_api/rivet_client.gd new file mode 100644 index 0000000..6bb2ad7 --- /dev/null +++ b/godot/bomber/addons/rivet_api/rivet_client.gd @@ -0,0 +1,78 @@ +extends Node + +var base_url = "https://api.rivet.gg/v1" + +func get_token(): + var token_env = OS.get_environment("RIVET_TOKEN") + assert(!token_env.is_empty(), "missing RIVET_TOKEN environment") + return token_env + +func lobby_ready(body: Variant, on_success: Callable, on_fail: Callable): + _rivet_request_with_body("POST", "matchmaker", "/lobbies/ready", body, on_success, on_fail) + +func find_lobby(body: Variant, on_success: Callable, on_fail: Callable): + _rivet_request_with_body("POST", "matchmaker", "/lobbies/find", body, on_success, on_fail) + +func player_connected(body: Variant, on_success: Callable, on_fail: Callable): + _rivet_request_with_body("POST", "matchmaker", "/players/connected", body, on_success, on_fail) + +func player_disconnected(body: Variant, on_success: Callable, on_fail: Callable): + _rivet_request_with_body("POST", "matchmaker", "/players/disconnected", body, on_success, on_fail) + +func _build_url(service, path) -> String: + return base_url.replace("://", "://" + service + ".") + path + +func _build_headers() -> PackedStringArray: + return [ + "Authorization: Bearer " + get_token(), + ] + +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]) + + var http_request = HTTPRequest.new() + add_child(http_request) + http_request.request_completed.connect(self._http_request_completed.bind(on_success, on_fail)) + + var error = http_request.request(url, _build_headers()) + if error != OK: + push_error("An error occurred in the HTTP request.") + if on_fail != null: + on_fail.call("Request failed to send: %s" % error) + +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]) + + var http_request = HTTPRequest.new() + add_child(http_request) + http_request.request_completed.connect(self._http_request_completed.bind(on_success, on_fail)) + + var body_json = JSON.stringify(body) + var error = http_request.request(url, _build_headers(), HTTPClient.METHOD_POST, body_json) + if error != OK: + push_error("An error occurred in the HTTP request.") + if on_fail != null: + on_fail.call("Request failed to send: %s" % error) + +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 + if response_code != 200: + push_error("Request failed ", response_code, " ", body.get_string_from_utf8()) + if on_fail != null: + on_fail.call("Request failed (%s): %s" % [response_code, body.get_string_from_utf8()]) + return + + var json = JSON.new() + 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_api/rivet_helper.gd b/godot/bomber/addons/rivet_api/rivet_helper.gd new file mode 100644 index 0000000..1a66645 --- /dev/null +++ b/godot/bomber/addons/rivet_api/rivet_helper.gd @@ -0,0 +1,104 @@ +extends Node + +signal start_server() + +var multiplayer_setup = false + +## All player tokens for players that have authenticated. +## +## Server only +var player_tokens = {} + +## The player token for this client that will be sent on the next +## authentication. +## +## Client only +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") + + +## Sets up the authentication hooks on SceneMultiplayer. +func setup_multiplayer(): + multiplayer_setup = true + + var scene_multiplayer = multiplayer as SceneMultiplayer + + scene_multiplayer.auth_callback = _auth_callback + scene_multiplayer.auth_timeout = 15.0 + + scene_multiplayer.peer_authenticating.connect(self._player_authenticating) + scene_multiplayer.peer_authentication_failed.connect(self._player_authentication_failed) + + scene_multiplayer.peer_disconnected.connect(self._player_disconnected) + + if is_dedicated_server(): + rivet_print("Starting server") + start_server.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") + player_token = _player_token + + +# MARK: Authentication +func _auth_callback(id: int, buf: PackedByteArray): + if multiplayer.is_server(): + # Authenticate the client if connecting to server + + var json = JSON.new() + json.parse(buf.get_string_from_utf8()) + var data = json.get_data() + + rivet_print("Player authenticating %s: %s" % [id, data]) + player_tokens[id] = data.player_token + RivetClient.player_connected({ + "player_token": data.player_token + }, _rivet_player_connected.bind(id), _rivet_player_connect_failed.bind(id)) + else: + # Auto-approve if not a server + (multiplayer as SceneMultiplayer).complete_auth(id) + +func _player_authenticating(id): + rivet_print("Authenticating %s" % id) + var body = JSON.stringify({ "player_token": player_token }) + (multiplayer as SceneMultiplayer).send_auth(id, body.to_utf8_buffer()) + + +func _player_authentication_failed(id): + rivet_print("Authentication failed for %s" % id) + multiplayer.set_network_peer(null) +# connection_failed.emit() + +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) + + +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) diff --git a/godot/bomber/bomb.gd b/godot/bomber/bomb.gd new file mode 100644 index 0000000..b6a7180 --- /dev/null +++ b/godot/bomber/bomb.gd @@ -0,0 +1,33 @@ +extends Area2D + +var in_area: Array = [] +var from_player: int + +# Called from the animation. +func explode(): + if not is_multiplayer_authority(): + # Explode only on authority. + return + for p in in_area: + if p.has_method("exploded"): + # Checks if there is wall in between bomb and the object + var world_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state + var query := PhysicsRayQueryParameters2D.create(position, p.position) + var result: Dictionary = world_state.intersect_ray(query) + if not result.collider is TileMap: + # Exploded can only be called by the authority, but will also be called locally. + p.exploded.rpc(from_player) + + +func done(): + if is_multiplayer_authority(): + queue_free() + + +func _on_bomb_body_enter(body): + if not body in in_area: + in_area.append(body) + + +func _on_bomb_body_exit(body): + in_area.erase(body) diff --git a/godot/bomber/bomb.tscn b/godot/bomber/bomb.tscn new file mode 100644 index 0000000..6af7f28 --- /dev/null +++ b/godot/bomber/bomb.tscn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5a7def879bd54e8f4dd7166d7ccb039680f27bc4a55bec78e1f8ee05c0d212f +size 4262 diff --git a/godot/bomber/bomb_spawner.gd b/godot/bomber/bomb_spawner.gd new file mode 100644 index 0000000..6200640 --- /dev/null +++ b/godot/bomber/bomb_spawner.gd @@ -0,0 +1,13 @@ +extends MultiplayerSpawner + +func _init(): + spawn_function = _spawn_bomb + + +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() + bomb.position = data[0] + bomb.from_player = data[1] + return bomb diff --git a/godot/bomber/brickfloor.png b/godot/bomber/brickfloor.png new file mode 100644 index 0000000..9a95f2e --- /dev/null +++ b/godot/bomber/brickfloor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc0572bacf7c60dc0c93f1444ad026ff58cf81029364f42b3bf165ca9815eaf6 +size 5530 diff --git a/godot/bomber/brickfloor.png.import b/godot/bomber/brickfloor.png.import new file mode 100644 index 0000000..6320549 --- /dev/null +++ b/godot/bomber/brickfloor.png.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8f3cb1339d3c62eaa2af5f52058f9432a3518d20e17042b1f93a705f3b2482d +size 764 diff --git a/godot/bomber/charwalk.png b/godot/bomber/charwalk.png new file mode 100644 index 0000000..7c37fa1 --- /dev/null +++ b/godot/bomber/charwalk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6037d0785b561c9ea9b59a4fa5b005b9d9a594a3a3e25589129bcd3e1e5566d +size 1663 diff --git a/godot/bomber/charwalk.png.import b/godot/bomber/charwalk.png.import new file mode 100644 index 0000000..8fdd94b --- /dev/null +++ b/godot/bomber/charwalk.png.import @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..0cbebab --- /dev/null +++ b/godot/bomber/example.toml @@ -0,0 +1,12 @@ +[display] +title = "Bomber" +tutorial_url = "https://rivet.gg/learn/godot/tutorials/crash-course" + +[meta] +engine = "Godot" +engine_version = "4.0.0" +language = "GDScript" +networking = "GodotHLMultiplayer" +features = ["Matchmaker", "DynamicServers"] + + diff --git a/godot/bomber/explosion.png b/godot/bomber/explosion.png new file mode 100644 index 0000000..5787e0b --- /dev/null +++ b/godot/bomber/explosion.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e33b8f18c5443f725e279365f45ed2d11d8482c60677dd2119caf86a81c9d95 +size 2692 diff --git a/godot/bomber/explosion.png.import b/godot/bomber/explosion.png.import new file mode 100644 index 0000000..ffeb98b --- /dev/null +++ b/godot/bomber/explosion.png.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d954947c661ffbf1975e1c76dab459b07abece85dac34501e1ab71a5a021faa +size 761 diff --git a/godot/bomber/gamestate.gd b/godot/bomber/gamestate.gd new file mode 100644 index 0000000..294f614 --- /dev/null +++ b/godot/bomber/gamestate.gd @@ -0,0 +1,182 @@ +extends Node + +# Default game server port. Can be any number between 1024 and 49151. +# Not on the list of registered or common ports as of November 2020: +# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers +const DEFAULT_PORT = 10567 + +# Max number of players. +const MAX_PEERS = 12 + +var peer = null + +# Name for my player. +var player_name = "The Warrior" + +# Names for remote players in id:name format. +var players = {} +var player_tokens = {} + +# Signals to let lobby GUI know what's going on. +signal player_list_changed() +signal connection_failed() +signal connection_succeeded() +signal game_ended() +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) + multiplayer.connection_failed.connect(self._connected_fail) + multiplayer.server_disconnected.connect(self._server_disconnected) + + +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. +func _player_connected(id): + print("Player connected %s" % id) + + # Registration of a client beings here, tell the connected player that we are here. + if !multiplayer.is_server(): + register_player.rpc_id(id, player_name) + + # Update player list if no other players present + player_list_changed.emit() + + +# Callback from SceneTree. +func _player_disconnected(id): + if has_node("/root/World"): # Game is in progress. + if multiplayer.is_server(): + game_error.emit("Player " + players[id] + " disconnected") + end_game() + else: # Game is not in progress. + # Unregister this player. + unregister_player(id) + + +# Callback from SceneTree, only for clients (not server). +func _connected_ok(): + # We just connected to a server + connection_succeeded.emit() + + +# Callback from SceneTree, only for clients (not server). +func _server_disconnected(): + game_error.emit("Server disconnected") + end_game() + + +# Callback from SceneTree, only for clients (not server). +func _connected_fail(): + multiplayer.set_network_peer(null) # Remove peer + connection_failed.emit() + + +# Lobby management functions. +@rpc("any_peer", "reliable") +func register_player(new_player_name): + var id = multiplayer.get_remote_sender_id() + players[id] = new_player_name + player_list_changed.emit() + + +func unregister_player(id): + players.erase(id) + player_list_changed.emit() + + +@rpc("call_local", "reliable") +func load_world(): + # Change scene. + var world = load("res://world.tscn").instantiate() + get_tree().get_root().add_child(world) + get_tree().get_root().get_node("Lobby").hide() + + # Set up score. + world.get_node("Score").add_player(multiplayer.get_unique_id(), player_name) + for pn in players: + world.get_node("Score").add_player(pn, players[pn]) + get_tree().set_pause(false) # Unpause and unleash the game! + + +func join_game(new_player_name): + player_name = new_player_name + + RivetClient.find_lobby({ + "game_modes": ["default"] + }, _lobby_found, _lobby_find_failed) + + +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() + + +func get_player_name(): + return player_name + + +# TODO: Figure out why this doesn't work as "authority" +@rpc("any_peer") +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") + + # Create a dictionary with peer id and respective spawn points, could be improved by randomizing. + var spawn_points = {} + var spawn_point_idx = 1 + for p in players: + spawn_points[p] = spawn_point_idx + spawn_point_idx += 1 + + for p_id in spawn_points: + var spawn_pos = world.get_node("SpawnPoints/" + str(spawn_points[p_id])).position + var player = player_scene.instantiate() + player.synced_position = spawn_pos + player.name = str(p_id) + player.set_player_name(player_name if p_id == multiplayer.get_unique_id() else players[p_id]) + world.get_node("Players").add_child(player) + + +func end_game(): + if has_node("/root/World"): # Game is in progress. + # End it + get_node("/root/World").queue_free() + + game_ended.emit() + players.clear() diff --git a/godot/bomber/icon.png b/godot/bomber/icon.png new file mode 100644 index 0000000..2c6a628 --- /dev/null +++ b/godot/bomber/icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91abec03a92db5d86b4d9e90ae4269ee2c145bca62727a781e1acec27659029b +size 947 diff --git a/godot/bomber/icon.png.import b/godot/bomber/icon.png.import new file mode 100644 index 0000000..3d3246a --- /dev/null +++ b/godot/bomber/icon.png.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfbfc2858181ccf39cf78242cd9982b0dd6824a496e07b05ad48b7b2691febc2 +size 746 diff --git a/godot/bomber/lobby.gd b/godot/bomber/lobby.gd new file mode 100644 index 0000000..a0ee200 --- /dev/null +++ b/godot/bomber/lobby.gd @@ -0,0 +1,64 @@ +extends Control + +func _ready(): + # Called every time the node is added to the scene. + gamestate.connection_failed.connect(self._on_connection_failed) + gamestate.connection_succeeded.connect(self._on_connection_success) + gamestate.player_list_changed.connect(self.refresh_lobby) + gamestate.game_ended.connect(self._on_game_ended) + gamestate.game_error.connect(self._on_game_error) + + # Set the player name according to the system username. Fallback to the path. + if OS.has_environment("USERNAME"): + $Connect/Name.text = OS.get_environment("USERNAME") + else: + var desktop_path = OS.get_system_dir(0).replace("\\", "/").split("/") + $Connect/Name.text = desktop_path[desktop_path.size() - 2] + + +func _on_join_pressed(): + if $Connect/Name.text == "": + $Connect/ErrorLabel.text = "Invalid name!" + return + + $Connect/ErrorLabel.text = "" + $Connect/Join.disabled = true + + var player_name = $Connect/Name.text + gamestate.join_game(player_name) + + +func _on_connection_success(): + $Connect.hide() + $Players.show() + + +func _on_connection_failed(): + $Connect/Join.disabled = false + $Connect/ErrorLabel.set_text("Connection failed.") + + +func _on_game_ended(): + show() + $Connect.show() + $Players.hide() + $Connect/Join.disabled = false + + +func _on_game_error(errtxt): + $ErrorDialog.dialog_text = errtxt + $ErrorDialog.popup_centered() + $Connect/Join.disabled = false + + +func refresh_lobby(): + var players = gamestate.get_player_list() + players.sort() + $Players/List.clear() + $Players/List.add_item(gamestate.get_player_name() + " (You)") + for p in players: + $Players/List.add_item(p) + + +func _on_start_pressed(): + gamestate.rpc("begin_game") diff --git a/godot/bomber/lobby.tscn b/godot/bomber/lobby.tscn new file mode 100644 index 0000000..4be2f1e --- /dev/null +++ b/godot/bomber/lobby.tscn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff440ef2545139ff2152d6f5bb45706c37a6c485a0961c3843f3a87a769958f +size 2905 diff --git a/godot/bomber/montserrat.otf b/godot/bomber/montserrat.otf new file mode 100644 index 0000000..901f626 --- /dev/null +++ b/godot/bomber/montserrat.otf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17ae49f737f0b36ce87dd587a7fda35a7d95eb330d175f27e70062905894b486 +size 71380 diff --git a/godot/bomber/montserrat.otf.import b/godot/bomber/montserrat.otf.import new file mode 100644 index 0000000..71e4283 --- /dev/null +++ b/godot/bomber/montserrat.otf.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cee51a58286bbed7068867a4928b4e707d336255ce587fcb4544da48b37bd2d1 +size 666 diff --git a/godot/bomber/player.gd b/godot/bomber/player.gd new file mode 100644 index 0000000..b332fc7 --- /dev/null +++ b/godot/bomber/player.gd @@ -0,0 +1,75 @@ +extends CharacterBody2D + +const MOTION_SPEED = 90.0 +const BOMB_RATE = 0.5 + +@export +var synced_position := Vector2() + +@export +var stunned = false + +@onready +var inputs = $Inputs +var last_bomb_time = BOMB_RATE +var current_anim = "" + +func _ready(): + stunned = false + position = synced_position + if str(name).is_valid_int(): + get_node("Inputs/InputsSync").set_multiplayer_authority(str(name).to_int()) + + +func _physics_process(delta): + if multiplayer.multiplayer_peer == null or str(multiplayer.get_unique_id()) == str(name): + # The client which this player represent will update the controls state, and notify it to everyone. + inputs.update() + + if multiplayer.multiplayer_peer == null or is_multiplayer_authority(): + # The server updates the position that will be notified to the clients. + synced_position = position + # And increase the bomb cooldown spawning one if the client wants to. + last_bomb_time += delta + if not stunned and is_multiplayer_authority() and inputs.bombing and last_bomb_time >= BOMB_RATE: + last_bomb_time = 0.0 + get_node("../../BombSpawner").spawn([position, str(name).to_int()]) + else: + # The client simply updates the position to the last known one. + position = synced_position + + if not stunned: + # Everybody runs physics. I.e. clients tries to predict where they will be during the next frame. + velocity = inputs.motion * MOTION_SPEED + move_and_slide() + + # Also update the animation based on the last known player input state + var new_anim = "standing" + + if inputs.motion.y < 0: + new_anim = "walk_up" + elif inputs.motion.y > 0: + new_anim = "walk_down" + elif inputs.motion.x < 0: + new_anim = "walk_left" + elif inputs.motion.x > 0: + new_anim = "walk_right" + + if stunned: + new_anim = "stunned" + + if new_anim != current_anim: + current_anim = new_anim + get_node("anim").play(current_anim) + + +func set_player_name(value): + get_node("label").text = value + + +@rpc("call_local") +func exploded(_by_who): + if stunned: + return + stunned = true + get_node("anim").play("stunned") diff --git a/godot/bomber/player.tscn b/godot/bomber/player.tscn new file mode 100644 index 0000000..974190e --- /dev/null +++ b/godot/bomber/player.tscn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae24881be6a8fda271a0e47875090afde79aa178d5cc2e2a0503d64b4f9b5b91 +size 5450 diff --git a/godot/bomber/player_controls.gd b/godot/bomber/player_controls.gd new file mode 100644 index 0000000..85bf4d1 --- /dev/null +++ b/godot/bomber/player_controls.gd @@ -0,0 +1,24 @@ +extends Node + +@export +var motion = Vector2() : + set(value): + # This will be sent by players, make sure values are within limits. + motion = clamp(value, Vector2(-1, -1), Vector2(1, 1)) + +@export +var bombing = false + +func update(): + var m = Vector2() + if Input.is_action_pressed("move_left"): + m += Vector2(-1, 0) + if Input.is_action_pressed("move_right"): + m += Vector2(1, 0) + if Input.is_action_pressed("move_up"): + m += Vector2(0, -1) + if Input.is_action_pressed("move_down"): + m += Vector2(0, 1) + + motion = m + bombing = Input.is_action_pressed("set_bomb") diff --git a/godot/bomber/project.godot b/godot/bomber/project.godot new file mode 100644 index 0000000..134fd3f --- /dev/null +++ b/godot/bomber/project.godot @@ -0,0 +1,93 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +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" + +[autoload] + +gamestate="*res://gamestate.gd" +RivetClient="*res://addons/rivet_api/rivet_client.gd" +RivetHelper="*res://addons/rivet_api/rivet_helper.gd" + +[display] + +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[dotnet] + +project/assembly_name="Multiplayer Bomber" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/rivet_api/plugin.cfg") + +[input] + +move_down={ +"deadzone": 0.5, +"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":83,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, 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":16777234,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) +] +} +move_left={ +"deadzone": 0.5, +"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":65,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, 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":16777231,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"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":68,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, 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":16777233,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null) +] +} +move_up={ +"deadzone": 0.5, +"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":87,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, 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":16777232,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) +] +} +set_bomb={ +"deadzone": 0.5, +"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) +] +} + +[replication] + +config={ +"uid://dviwgv2ty8v6u": { +"replicated": true, +"spawn_properties": [], +"sync_interval": 16, +"sync_properties": [&":stunned", &"sprite:hframes", &"sprite:vframes", &":server_position", &"label:text"], +"synced": false +} +} diff --git a/godot/bomber/rivet.toml b/godot/bomber/rivet.toml new file mode 100644 index 0000000..9d8efdd --- /dev/null +++ b/godot/bomber/rivet.toml @@ -0,0 +1,52 @@ +# === 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.gd b/godot/bomber/rock.gd new file mode 100644 index 0000000..b2a86c0 --- /dev/null +++ b/godot/bomber/rock.gd @@ -0,0 +1,6 @@ +extends CharacterBody2D + +@rpc("call_local") +func exploded(by_who): + $"../../Score".increase_score(by_who) + $"AnimationPlayer".play("explode") diff --git a/godot/bomber/rock.tscn b/godot/bomber/rock.tscn new file mode 100644 index 0000000..5b15ea0 --- /dev/null +++ b/godot/bomber/rock.tscn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4165c9e29444ba7c820be42b6df02431b068cdef64a827497e3ad0b5a00c7739 +size 1453 diff --git a/godot/bomber/rock_bit.png b/godot/bomber/rock_bit.png new file mode 100644 index 0000000..a6aa061 --- /dev/null +++ b/godot/bomber/rock_bit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:040e0c56db8ed317048948a80a37f156fd87ee5ad46ff97b695c9dd6c0e4c074 +size 89 diff --git a/godot/bomber/rock_bit.png.import b/godot/bomber/rock_bit.png.import new file mode 100644 index 0000000..e33e85e --- /dev/null +++ b/godot/bomber/rock_bit.png.import @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:779b33ee0a2930659c5cffc18bef2816e84d7de34d583067267f4ea65c725337 +size 758 diff --git a/godot/bomber/score.gd b/godot/bomber/score.gd new file mode 100644 index 0000000..1358427 --- /dev/null +++ b/godot/bomber/score.gd @@ -0,0 +1,45 @@ +extends HBoxContainer + +var player_labels = {} + +func _process(_delta): + var rocks_left = $"../Rocks".get_child_count() + if rocks_left == 0: + var winner_name = "" + var winner_score = 0 + for p in player_labels: + if player_labels[p].score > winner_score: + winner_score = player_labels[p].score + winner_name = player_labels[p].name + + $"../Winner".set_text("THE WINNER IS:\n" + winner_name) + $"../Winner".show() + + +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)) + + +func add_player(id, new_player_name): + var l = Label.new() + 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") + l.set("custom_fonts/font", font) + l.set("custom_font_size/font_size", 18) + add_child(l) + + player_labels[id] = { name = new_player_name, label = l, score = 0 } + + +func _ready(): + $"../Winner".hide() + set_process(true) + + +func _on_exit_game_pressed(): + gamestate.end_game() 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/scripts/run.sh b/godot/bomber/scripts/run.sh new file mode 100755 index 0000000..5e82388 --- /dev/null +++ b/godot/bomber/scripts/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +/Applications/Godot.app/Contents/MacOS/Godot --headless -- --server + diff --git a/godot/bomber/tile_scene.tscn b/godot/bomber/tile_scene.tscn new file mode 100644 index 0000000..2ec2a1f --- /dev/null +++ b/godot/bomber/tile_scene.tscn @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..5cf3fb2 --- /dev/null +++ b/godot/bomber/tileset.tres @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..57f3b53 --- /dev/null +++ b/godot/bomber/world.tscn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc2b7620bc2f00cae8db645cd1bb51eab1add7313dc497b9d082415a8d3c9dbc +size 13915 diff --git a/godot/example-godot-bomber b/godot/example-godot-bomber deleted file mode 160000 index bee5116..0000000 --- a/godot/example-godot-bomber +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bee511612fbdf3a6fab6cac2d587bf1a324e2f87 diff --git a/html5/colyseus/LICENSE b/html5/colyseus/LICENSE index cfa3809..8108a07 100644 --- a/html5/colyseus/LICENSE +++ b/html5/colyseus/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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/html5/tanks-socketio-canvas/LICENSE b/html5/tanks-socketio-canvas/LICENSE index cfa3809..8108a07 100644 --- a/html5/tanks-socketio-canvas/LICENSE +++ b/html5/tanks-socketio-canvas/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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/html5/webrtc/LICENSE b/html5/webrtc/LICENSE index cfa3809..8108a07 100644 --- a/html5/webrtc/LICENSE +++ b/html5/webrtc/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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/unity/tanks-fishnet/LICENSE b/unity/tanks-fishnet/LICENSE index cfa3809..8108a07 100644 --- a/unity/tanks-fishnet/LICENSE +++ b/unity/tanks-fishnet/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Rivet +Copyright (c) 2024 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