├── .github └── workflows │ └── godot-export.yml ├── .gitignore ├── .gitlab-ci.yml ├── CREDITS-CC0.txt ├── Camera.gd ├── Game.gd ├── Game.tscn ├── LICENSE.txt ├── Main.gd ├── Main.tscn ├── README.md ├── actors ├── ExplodeEffect.gd ├── ExplodeEffect.tscn ├── ExplodeEffectGradient.tres ├── Player.gd ├── Player.tscn └── player-states │ ├── Dead.gd │ ├── Duck.gd │ ├── Fall.gd │ ├── Hurt.gd │ ├── Idle.gd │ ├── Jump.gd │ ├── Move.gd │ └── Slide.gd ├── addons ├── com.heroiclabs.nakama │ ├── LICENSE.txt │ ├── Nakama.gd │ ├── api │ │ ├── NakamaAPI.gd │ │ ├── NakamaRTAPI.gd │ │ ├── NakamaRTMessage.gd │ │ ├── NakamaSession.gd │ │ ├── NakamaStorageObjectId.gd │ │ └── NakamaWriteStorageObject.gd │ ├── client │ │ ├── NakamaClient.gd │ │ └── NakamaHTTPAdapter.gd │ ├── socket │ │ ├── NakamaSocket.gd │ │ └── NakamaSocketAdapter.gd │ └── utils │ │ ├── NakamaAsyncResult.gd │ │ ├── NakamaException.gd │ │ ├── NakamaLogger.gd │ │ └── NakamaSerializer.gd └── snopek-state-machine │ ├── LICENSE.txt │ ├── State.aseprite │ ├── State.gd │ ├── State.png │ ├── State.png.import │ ├── StateMachine.aseprite │ ├── StateMachine.gd │ ├── StateMachine.png │ ├── StateMachine.png.import │ ├── init.gd │ └── plugin.cfg ├── assets ├── LICENSE.txt ├── backgrounds │ ├── 01.png │ ├── 01.png.import │ ├── 02.png │ ├── 02.png.import │ ├── 03.png │ ├── 03.png.import │ ├── 04.png │ └── 04.png.import ├── fonts │ ├── monogram.tres │ ├── monogram.ttf │ └── monogram_extended.ttf ├── kenney-platform-deluxe │ ├── license.txt │ ├── request │ │ ├── rockSmall.png │ │ └── rockSmall.png.import │ └── tiles │ │ ├── castle.png │ │ └── castle.png.import ├── music │ ├── BUILD UP.ogg │ ├── BUILD UP.ogg.import │ ├── DOMEFIGHTERS.ogg │ ├── DOMEFIGHTERS.ogg.import │ ├── FISHSTICKS.ogg │ ├── FISHSTICKS.ogg.import │ ├── THEME #1.ogg │ └── THEME #1.ogg.import ├── screenshots │ ├── .gdignore │ └── gameplay.gif ├── sounds │ ├── blops │ │ ├── blop1.wav │ │ ├── blop1.wav.import │ │ ├── blop2.wav │ │ ├── blop2.wav.import │ │ ├── blop3.wav │ │ └── blop3.wav.import │ ├── empty.wav │ ├── empty.wav.import │ ├── explosions │ │ ├── explosion1.wav │ │ ├── explosion1.wav.import │ │ ├── explosion2.wav │ │ ├── explosion2.wav.import │ │ ├── explosion3.wav │ │ └── explosion3.wav.import │ ├── hurt.wav │ ├── hurt.wav.import │ ├── jump.wav │ ├── jump.wav.import │ ├── pickup.wav │ ├── pickup.wav.import │ ├── shoot.ogg │ ├── shoot.ogg.import │ ├── sword.wav │ ├── sword.wav.import │ ├── throw.wav │ └── throw.wav.import ├── sprites │ ├── decorations1.png │ ├── decorations1.png.import │ ├── generator_flat.png │ ├── generator_flat.png.import │ ├── gun.png │ ├── gun.png.import │ ├── sword.png │ ├── sword.png.import │ ├── whale_blue.png │ ├── whale_blue.png.import │ ├── whale_green.png │ ├── whale_green.png.import │ ├── whale_orange.png │ ├── whale_orange.png.import │ ├── whale_purple.png │ └── whale_purple.png.import ├── theme.tres ├── tilesets │ ├── template.png │ ├── template.png.import │ ├── template.tres │ ├── tileset.tres │ ├── tileset1.png │ └── tileset1.png.import └── ui │ ├── close.png │ ├── close.png.import │ ├── mute.png │ └── mute.png.import ├── autoload ├── Build.gd ├── GameState.gd ├── Online.gd ├── OnlineMatch.gd └── Util.gd ├── build └── .gdignore ├── components ├── Hitbox.gd ├── Hitbox.tscn ├── InputBuffer.gd └── Sounds.gd ├── default_bus_layout.tres ├── default_env.tres ├── docker-compose.yml ├── export_presets.cfg ├── icon.png ├── icon.png.import ├── main ├── Music.gd ├── Screen.gd ├── UILayer.gd └── screens │ ├── ConnectionScreen.gd │ ├── ConnectionScreen.tscn │ ├── CreditsScreen.tscn │ ├── LeaderboardRecord.gd │ ├── LeaderboardRecord.tscn │ ├── LeaderboardScreen.gd │ ├── LeaderboardScreen.tscn │ ├── MatchScreen.gd │ ├── MatchScreen.tscn │ ├── PeerStatus.gd │ ├── PeerStatus.tscn │ ├── ReadyScreen.gd │ ├── ReadyScreen.tscn │ ├── TitleScreen.gd │ ├── TitleScreen.tscn │ └── TransparentButton.gd ├── maps ├── Map.gd ├── Map1.tscn ├── Map2.tscn ├── ParallaxBackground.tscn └── StaticBackground.tscn ├── nakama └── data │ └── modules │ └── fish_game.lua ├── objects ├── Barnacles.tscn ├── Seaweed.tscn ├── TimedGenerator.gd ├── TimedGenerator.tscn └── TimedGeneratorFlat.tscn ├── pickups ├── DisintegrateEffect.gd ├── DisintegrateEffect.tscn ├── DisintegrateEffectGradient.tres ├── Gun.gd ├── Gun.tscn ├── Pickup.gd ├── Pickup.tscn ├── Projectile.gd ├── Projectile.tscn ├── SparksEffect.gd ├── SparksEffect.tscn ├── SparksEffectGradient.tres ├── Sword.gd └── Sword.tscn ├── project.godot └── scripts └── generate-build-variables.sh /.github/workflows/godot-export.yml: -------------------------------------------------------------------------------- 1 | name: "godot-export" 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | tags: 7 | - 'v*' 8 | 9 | env: 10 | GODOT_VERSION: 3.2.3 11 | EXPORT_NAME: fishgame-godot 12 | 13 | jobs: 14 | export-windows: 15 | name: Windows Export 16 | runs-on: ubuntu-latest 17 | container: 18 | image: barichello/godot-ci:3.2.3 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | with: 23 | lfs: true 24 | - name: Setup 25 | run: | 26 | mkdir -v -p ~/.local/share/godot/templates 27 | mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable 28 | - name: Generate build variables 29 | run: ./scripts/generate-build-variables.sh 30 | env: 31 | NAKAMA_HOST: ${{ secrets.NAKAMA_HOST }} 32 | NAKAMA_PORT: ${{ secrets.NAKAMA_PORT }} 33 | NAKAMA_SERVER_KEY: ${{ secrets.NAKAMA_SERVER_KEY }} 34 | - name: Windows Build 35 | run: | 36 | mkdir -v -p build/windows 37 | godot -v --export "Windows Desktop" ./build/windows/$EXPORT_NAME.exe 38 | - name: Tar files 39 | run: tar -cvf ./build/$EXPORT_NAME-windows.tar ./build/windows/ 40 | - name: Upload Artifact 41 | uses: actions/upload-artifact@v1 42 | with: 43 | name: ${{ env.EXPORT_NAME }}-windows 44 | path: build/${{ env.EXPORT_NAME }}-windows.tar 45 | 46 | export-linux: 47 | name: Linux Export 48 | runs-on: ubuntu-latest 49 | container: 50 | image: barichello/godot-ci:3.2.3 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v2 54 | with: 55 | lfs: true 56 | - name: Setup 57 | run: | 58 | mkdir -v -p ~/.local/share/godot/templates 59 | mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable 60 | - name: Generate build variables 61 | run: ./scripts/generate-build-variables.sh 62 | env: 63 | NAKAMA_HOST: ${{ secrets.NAKAMA_HOST }} 64 | NAKAMA_PORT: ${{ secrets.NAKAMA_PORT }} 65 | NAKAMA_SERVER_KEY: ${{ secrets.NAKAMA_SERVER_KEY }} 66 | - name: Linux Build 67 | run: | 68 | mkdir -v -p build/linux 69 | godot -v --export "Linux/X11" ./build/linux/$EXPORT_NAME.x86_64 70 | - name: Tar files 71 | run: tar -cvf ./build/$EXPORT_NAME-linux.tar ./build/linux/ 72 | - name: Upload Artifact 73 | uses: actions/upload-artifact@v1 74 | with: 75 | name: ${{ env.EXPORT_NAME }}-linux 76 | path: build/${{ env.EXPORT_NAME }}-linux.tar 77 | 78 | export-macosx: 79 | name: MacOS Export 80 | runs-on: ubuntu-latest 81 | container: 82 | image: barichello/godot-ci:3.2.3 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v2 86 | with: 87 | lfs: true 88 | - name: Setup 89 | run: | 90 | mkdir -v -p ~/.local/share/godot/templates 91 | mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable 92 | - name: Generate build variables 93 | run: ./scripts/generate-build-variables.sh 94 | env: 95 | NAKAMA_HOST: ${{ secrets.NAKAMA_HOST }} 96 | NAKAMA_PORT: ${{ secrets.NAKAMA_PORT }} 97 | NAKAMA_SERVER_KEY: ${{ secrets.NAKAMA_SERVER_KEY }} 98 | - name: Mac Build 99 | run: | 100 | mkdir -v -p build/macosx 101 | godot -v --export "Mac OSX" ./build/macosx/$EXPORT_NAME.zip 102 | - name: Upload Artifact 103 | uses: actions/upload-artifact@v1 104 | with: 105 | name: ${{ env.EXPORT_NAME }}-macosx 106 | path: build/macosx 107 | 108 | release: 109 | name: Release 110 | runs-on: ubuntu-latest 111 | if: startsWith(github.ref, 'refs/tags/v') 112 | needs: 113 | - export-windows 114 | - export-linux 115 | - export-macosx 116 | steps: 117 | - name: Create release 118 | id: create_release 119 | uses: actions/create-release@v1 120 | env: 121 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 122 | with: 123 | tag_name: ${{ github.ref }} 124 | release_name: Fish Game (Godot) ${{ github.ref }} 125 | draft: true 126 | prerelease: false 127 | - name: Download artifacts 128 | uses: actions/download-artifact@v2 129 | with: 130 | path: ./artifacts 131 | - name: Prepare files 132 | run: | 133 | for x in ./artifacts/*/*.tar; do tar -xvf $x; done 134 | (cd ./build && for x in *; do mv $x $EXPORT_NAME-$x; done) 135 | (cd ./build && for x in *; do zip $x.zip $(find $x); done) 136 | - name: Upload Windows build to release 137 | uses: actions/upload-release-asset@v1 138 | env: 139 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 140 | with: 141 | upload_url: ${{ steps.create_release.outputs.upload_url }} 142 | asset_path: ./build/fishgame-godot-windows.zip 143 | asset_name: ${{ env.EXPORT_NAME }}-windows.zip 144 | asset_content_type: application/zip 145 | - name: Upload Linux build to release 146 | uses: actions/upload-release-asset@v1 147 | env: 148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 149 | with: 150 | upload_url: ${{ steps.create_release.outputs.upload_url }} 151 | asset_path: ./build/fishgame-godot-linux.zip 152 | asset_name: ${{ env.EXPORT_NAME }}-linux.zip 153 | asset_content_type: application/zip 154 | - name: Upload MacOS build to release 155 | uses: actions/upload-release-asset@v1 156 | env: 157 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 158 | with: 159 | upload_url: ${{ steps.create_release.outputs.upload_url }} 160 | asset_path: ./artifacts/fishgame-godot-macosx/fishgame-godot.zip 161 | asset_name: ${{ env.EXPORT_NAME }}-macosx.zip 162 | asset_content_type: application/zip 163 | 164 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.import/ 2 | /build/ 3 | *.swp 4 | /webrtc_debug/ 5 | /webrtc/ 6 | /nakama/data/ -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: barichello/godot-ci:3.2.3 2 | 3 | variables: 4 | EXPORT_NAME: fish-game 5 | 6 | stages: 7 | - export 8 | - deploy 9 | 10 | .export_before_script: &export_before_script 11 | before_script: 12 | - apt-get update 13 | - apt-get install -y --no-install-recommends ca-certificates unzip wget 14 | 15 | # Put in the production Nakama info 16 | - ./scripts/generate-build-variables.sh 17 | 18 | windows: 19 | <<: *export_before_script 20 | stage: export 21 | script: 22 | - mkdir -v -p build/windows 23 | - godot -v --export "Windows Desktop" ./build/windows/$EXPORT_NAME.exe 24 | artifacts: 25 | name: $EXPORT_NAME-$CI_JOB_NAME 26 | paths: 27 | - build/windows 28 | 29 | linux: 30 | <<: *export_before_script 31 | stage: export 32 | script: 33 | - mkdir -v -p build/linux 34 | - godot -v --export "Linux/X11" ./build/linux/$EXPORT_NAME.x86_64 35 | artifacts: 36 | name: $EXPORT_NAME-$CI_JOB_NAME 37 | paths: 38 | - build/linux 39 | 40 | macosx: 41 | <<: *export_before_script 42 | stage: export 43 | script: 44 | - mkdir -v -p build/macosx 45 | - godot -v --export "Mac OSX" ./build/macosx/$EXPORT_NAME.zip 46 | 47 | # Extract the zip file since GitLab CI will zip it up again. 48 | - (cd ./build/macosx && unzip -a $EXPORT_NAME.zip && rm $EXPORT_NAME.zip) 49 | artifacts: 50 | name: $EXPORT_NAME-$CI_JOB_NAME 51 | paths: 52 | - build/macosx 53 | 54 | web: 55 | <<: *export_before_script 56 | stage: export 57 | script: 58 | - mkdir -v -p build/web 59 | - godot -v --export "HTML5" ./build/web/index.html 60 | artifacts: 61 | name: $EXPORT_NAME-$CI_JOB_NAME 62 | paths: 63 | - build/web 64 | 65 | pages: 66 | stage: deploy 67 | dependencies: 68 | - web 69 | script: 70 | - rm -rf public 71 | - cp -r build/web public 72 | artifacts: 73 | paths: 74 | - public 75 | only: 76 | - develop 77 | 78 | -------------------------------------------------------------------------------- /CREDITS-CC0.txt: -------------------------------------------------------------------------------- 1 | CREDITS 2 | ======= 3 | 4 | Kenney.nl's Platformer Art Deluxe: 5 | 6 | https://www.kenney.nl/assets/platformer-art-deluxe 7 | 8 | Monogram font from: 9 | 10 | https://datagoblin.itch.io/monogram 11 | 12 | -------------------------------------------------------------------------------- /Camera.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | export (NodePath) var player_container_path 4 | export (float, 0.1, 0.5) var zoom_offset := 0.2 5 | 6 | export (float) var custom_smoothing := 2.0 7 | 8 | var player_container: Node2D 9 | 10 | func _physics_process(delta: float) -> void: 11 | update_position_and_zoom() 12 | 13 | func _center_position(pos: Vector2) -> Vector2: 14 | return pos - Vector2(0, 35) 15 | 16 | func update_position_and_zoom(custom_smoothing_enabled: bool = true) -> void: 17 | if not player_container_path: 18 | return 19 | 20 | if not player_container: 21 | player_container = get_node(player_container_path) 22 | if not player_container: 23 | return 24 | 25 | var count := player_container.get_child_count() 26 | if count == 0: 27 | return 28 | 29 | var camera_rect := Rect2(_center_position(player_container.get_child(0).global_position), Vector2()) 30 | for index in range(0, count): 31 | if index == 0: 32 | continue 33 | camera_rect = camera_rect.expand(_center_position(player_container.get_child(index).global_position)) 34 | 35 | var viewport_rect = get_viewport_rect().size 36 | 37 | # If the camera_rect is shorter than the viewpoirt, ensure that it's 38 | # positioned so that the bottom edge is just below the lowest character. 39 | var min_height = viewport_rect.y * (1.0 - zoom_offset) 40 | if camera_rect.size.y < min_height: 41 | var delta_height = min_height - camera_rect.size.y 42 | camera_rect.position.y -= delta_height 43 | camera_rect.size.y += delta_height 44 | 45 | var desired_global_position = calculate_center(camera_rect) 46 | var desired_zoom = calculate_zoom(camera_rect, get_viewport_rect().size) 47 | 48 | if custom_smoothing_enabled: 49 | var delta = get_physics_process_delta_time() 50 | global_position += (desired_global_position - global_position) * custom_smoothing * delta 51 | zoom += (desired_zoom - zoom) * custom_smoothing * delta 52 | else: 53 | global_position = desired_global_position 54 | zoom = desired_zoom 55 | 56 | func calculate_center(camera_rect: Rect2) -> Vector2: 57 | return Vector2( 58 | camera_rect.position.x + (camera_rect.size.x / 2), 59 | camera_rect.position.y + (camera_rect.size.y / 2)) 60 | 61 | func calculate_zoom(camera_rect: Rect2, viewport_size: Vector2) -> Vector2: 62 | var zoom = max( 63 | max(1.0, (camera_rect.size.x / viewport_size.x) + zoom_offset), 64 | max(1.0, (camera_rect.size.y / viewport_size.y) + zoom_offset)) 65 | return Vector2(zoom, zoom) 66 | 67 | -------------------------------------------------------------------------------- /Game.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var Player = preload("res://actors/Player.tscn") 4 | 5 | export (PackedScene) var map_scene = preload("res://maps/Map1.tscn") 6 | 7 | onready var map: Node2D = $Map 8 | onready var players_node := $Players 9 | onready var camera := $Camera2D 10 | onready var original_camera_position: Vector2 = camera.global_position 11 | 12 | var game_started := false 13 | var game_over := false 14 | var players_alive := {} 15 | var players_setup := {} 16 | 17 | signal game_started () 18 | signal player_dead (player_id) 19 | signal game_over (player_id) 20 | 21 | func _get_custom_rpc_methods() -> Array: 22 | return [ 23 | '_do_game_setup', 24 | '_finished_game_setup', 25 | '_do_game_start', 26 | ] 27 | 28 | func game_start(players: Dictionary) -> void: 29 | if GameState.online_play: 30 | OnlineMatch.custom_rpc_sync(self, '_do_game_setup', [players]) 31 | else: 32 | _do_game_setup(players) 33 | 34 | # Initializes the game so that it is ready to really start. 35 | func _do_game_setup(players: Dictionary) -> void: 36 | get_tree().set_pause(true) 37 | 38 | if game_started: 39 | game_stop() 40 | 41 | game_started = true 42 | game_over = false 43 | players_alive = players 44 | 45 | reload_map() 46 | 47 | for player_id in players: 48 | var other_player = Player.instance() 49 | other_player.name = str(player_id) 50 | players_node.add_child(other_player) 51 | 52 | other_player.set_network_master(player_id) 53 | other_player.set_player_skin(player_id - 1) 54 | other_player.set_player_name(players[player_id]) 55 | other_player.position = map.get_node("PlayerStartPositions/Player" + str(player_id)).position 56 | other_player.rotation = map.get_node("PlayerStartPositions/Player" + str(player_id)).rotation 57 | other_player.connect("player_dead", self, "_on_player_dead", [player_id]) 58 | 59 | if not GameState.online_play: 60 | other_player.player_controlled = true 61 | other_player.input_prefix = "player" + str(player_id) + "_" 62 | 63 | camera.update_position_and_zoom(false) 64 | 65 | if GameState.online_play: 66 | var my_id := OnlineMatch.get_network_unique_id() 67 | var my_player := players_node.get_node(str(my_id)) 68 | my_player.player_controlled = true 69 | 70 | # Tell the host that we've finished setup. 71 | OnlineMatch.custom_rpc_id_sync(self, 1, "_finished_game_setup", [my_id]) 72 | else: 73 | _do_game_start() 74 | 75 | # Records when each player has finished setup so we know when all players are ready. 76 | func _finished_game_setup(player_id: int) -> void: 77 | players_setup[player_id] = players_alive[player_id] 78 | if players_setup.size() == players_alive.size(): 79 | # Once all clients have finished setup, tell them to start the game. 80 | OnlineMatch.custom_rpc_sync(self, "_do_game_start") 81 | 82 | # Actually start the game on this client. 83 | func _do_game_start() -> void: 84 | if map.has_method('map_start'): 85 | map.map_start() 86 | emit_signal("game_started") 87 | get_tree().set_pause(false) 88 | 89 | func game_stop() -> void: 90 | if map.has_method('map_stop'): 91 | map.map_stop() 92 | 93 | game_started = false 94 | players_setup.clear() 95 | players_alive.clear() 96 | 97 | for child in players_node.get_children(): 98 | players_node.remove_child(child) 99 | child.queue_free() 100 | 101 | func reload_map() -> void: 102 | var map_index = map.get_index() 103 | remove_child(map) 104 | map.queue_free() 105 | 106 | map = map_scene.instance() 107 | map.name = 'Map' 108 | add_child(map) 109 | move_child(map, map_index) 110 | 111 | var map_rect = map.get_map_rect() 112 | camera.global_position = original_camera_position 113 | camera.limit_left = map_rect.position.x 114 | camera.limit_top = map_rect.position.y 115 | camera.limit_right = map_rect.position.x + map_rect.size.x 116 | 117 | func kill_player(player_id) -> void: 118 | var player_node = players_node.get_node(str(player_id)) 119 | if player_node: 120 | if player_node.has_method("die"): 121 | player_node.die() 122 | else: 123 | # If there is no die method, we do the most important things it 124 | # would have done. 125 | player_node.queue_free() 126 | _on_player_dead(player_id) 127 | 128 | func _on_player_dead(player_id) -> void: 129 | emit_signal("player_dead", player_id) 130 | 131 | players_alive.erase(player_id) 132 | if not game_over and players_alive.size() == 1: 133 | game_over = true 134 | var player_keys = players_alive.keys() 135 | emit_signal("game_over", player_keys[0]) 136 | -------------------------------------------------------------------------------- /Game.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://Camera.gd" type="Script" id=1] 4 | [ext_resource path="res://Game.gd" type="Script" id=2] 5 | 6 | [node name="Game" type="Node2D"] 7 | script = ExtResource( 2 ) 8 | 9 | [node name="Map" type="Node2D" parent="."] 10 | 11 | [node name="Players" type="Node2D" parent="."] 12 | 13 | [node name="Camera2D" type="Camera2D" parent="."] 14 | position = Vector2( 420, -350 ) 15 | current = true 16 | limit_bottom = 0 17 | script = ExtResource( 1 ) 18 | player_container_path = NodePath("../Players") 19 | zoom_offset = 0.5 20 | -------------------------------------------------------------------------------- /Main.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | onready var game = $Game 4 | onready var ui_layer: UILayer = $UILayer 5 | onready var ready_screen = $UILayer/Screens/ReadyScreen 6 | onready var music := $Music 7 | 8 | var players := {} 9 | 10 | var players_ready := {} 11 | var players_score := {} 12 | 13 | var match_started := false 14 | 15 | func _ready() -> void: 16 | OnlineMatch.connect("error", self, "_on_OnlineMatch_error") 17 | OnlineMatch.connect("disconnected", self, "_on_OnlineMatch_disconnected") 18 | OnlineMatch.connect("player_status_changed", self, "_on_OnlineMatch_player_status_changed") 19 | OnlineMatch.connect("player_left", self, "_on_OnlineMatch_player_left") 20 | 21 | randomize() 22 | music.play_random() 23 | 24 | func _get_custom_rpc_methods() -> Array: 25 | return [ 26 | 'player_ready', 27 | 'show_winner' 28 | ] 29 | 30 | ##### 31 | # UI callbacks 32 | ##### 33 | 34 | func _on_TitleScreen_play_local() -> void: 35 | GameState.online_play = false 36 | 37 | ui_layer.hide_screen() 38 | ui_layer.show_back_button() 39 | 40 | start_game() 41 | 42 | func _on_TitleScreen_play_online() -> void: 43 | GameState.online_play = true 44 | 45 | # Show the game map in the background because we have nothing better. 46 | game.reload_map() 47 | 48 | ui_layer.show_screen("ConnectionScreen") 49 | 50 | func _on_UILayer_change_screen(name: String, _screen) -> void: 51 | if name == 'TitleScreen': 52 | ui_layer.hide_back_button() 53 | else: 54 | ui_layer.show_back_button() 55 | 56 | if name != 'ReadyScreen': 57 | if match_started: 58 | match_started = false 59 | music.play_random() 60 | 61 | func _on_UILayer_back_button() -> void: 62 | ui_layer.hide_message() 63 | 64 | stop_game() 65 | 66 | if GameState.online_play: 67 | OnlineMatch.leave() 68 | 69 | if ui_layer.current_screen_name in ['ConnectionScreen', 'MatchScreen', 'CreditsScreen']: 70 | ui_layer.show_screen("TitleScreen") 71 | elif not GameState.online_play: 72 | ui_layer.show_screen("TitleScreen") 73 | else: 74 | ui_layer.show_screen("MatchScreen") 75 | 76 | func _on_ReadyScreen_ready_pressed() -> void: 77 | OnlineMatch.custom_rpc_sync(self, "player_ready", [OnlineMatch.get_my_session_id()]) 78 | 79 | ##### 80 | # OnlineMatch callbacks 81 | ##### 82 | 83 | func _on_OnlineMatch_error(message: String): 84 | if message != '': 85 | ui_layer.show_message(message) 86 | ui_layer.show_screen("MatchScreen") 87 | 88 | func _on_OnlineMatch_disconnected(): 89 | #_on_OnlineMatch_error("Disconnected from host") 90 | _on_OnlineMatch_error('') 91 | 92 | func _on_OnlineMatch_player_left(player) -> void: 93 | ui_layer.show_message(player.username + " has left") 94 | 95 | game.kill_player(player.peer_id) 96 | 97 | players.erase(player.peer_id) 98 | players_ready.erase(player.peer_id) 99 | 100 | func _on_OnlineMatch_player_status_changed(player, status) -> void: 101 | if status == OnlineMatch.PlayerStatus.CONNECTED: 102 | if OnlineMatch.is_network_server(): 103 | # Tell this new player about all the other players that are already ready. 104 | for session_id in players_ready: 105 | OnlineMatch.custom_rpc_id(self, player.peer_id, "player_ready", [session_id]) 106 | 107 | ##### 108 | # Gameplay methods and callbacks 109 | ##### 110 | 111 | func player_ready(session_id: String) -> void: 112 | ready_screen.set_status(session_id, "READY!") 113 | 114 | if OnlineMatch.is_network_server() and not players_ready.has(session_id): 115 | players_ready[session_id] = true 116 | if players_ready.size() == OnlineMatch.players.size(): 117 | if OnlineMatch.match_state != OnlineMatch.MatchState.PLAYING: 118 | OnlineMatch.start_playing() 119 | start_game() 120 | 121 | func start_game() -> void: 122 | if GameState.online_play: 123 | players = OnlineMatch.get_player_names_by_peer_id() 124 | else: 125 | players = { 126 | 1: "Player1", 127 | 2: "Player2", 128 | } 129 | 130 | game.game_start(players) 131 | 132 | func stop_game() -> void: 133 | OnlineMatch.leave() 134 | 135 | players.clear() 136 | players_ready.clear() 137 | players_score.clear() 138 | 139 | game.game_stop() 140 | 141 | func restart_game() -> void: 142 | stop_game() 143 | start_game() 144 | 145 | func _on_Game_game_started() -> void: 146 | ui_layer.hide_screen() 147 | ui_layer.hide_all() 148 | ui_layer.show_back_button() 149 | 150 | if not match_started: 151 | match_started = true 152 | music.play_random() 153 | 154 | func _on_Game_player_dead(player_id: int) -> void: 155 | if GameState.online_play: 156 | var my_id = OnlineMatch.get_network_unique_id() 157 | if player_id == my_id: 158 | ui_layer.show_message("You lose!") 159 | 160 | func _on_Game_game_over(player_id: int) -> void: 161 | players_ready.clear() 162 | 163 | if not GameState.online_play: 164 | show_winner(players[player_id]) 165 | elif OnlineMatch.is_network_server(): 166 | if not players_score.has(player_id): 167 | players_score[player_id] = 1 168 | else: 169 | players_score[player_id] += 1 170 | 171 | var player_session_id = OnlineMatch.get_session_id(player_id) 172 | var is_match: bool = players_score[player_id] >= 5 173 | OnlineMatch.custom_rpc_sync(self, "show_winner", [players[player_id], player_session_id, players_score[player_id], is_match]) 174 | 175 | func update_wins_leaderboard() -> void: 176 | if not Online.nakama_session or Online.nakama_session.is_expired(): 177 | # If our session has expired, then wait until a new session is setup. 178 | yield(Online, "session_connected") 179 | 180 | Online.nakama_client.write_leaderboard_record_async(Online.nakama_session, 'fish_game_wins', 1) 181 | 182 | func show_winner(name: String, session_id: String = '', score: int = 0, is_match: bool = false) -> void: 183 | if is_match: 184 | ui_layer.show_message(name + " WINS THE WHOLE MATCH!") 185 | else: 186 | ui_layer.show_message(name + " wins this round!") 187 | 188 | yield(get_tree().create_timer(2.0), "timeout") 189 | if not game.game_started: 190 | return 191 | 192 | if GameState.online_play: 193 | if is_match: 194 | stop_game() 195 | if session_id == OnlineMatch.my_session_id: 196 | update_wins_leaderboard() 197 | ui_layer.show_screen("MatchScreen") 198 | else: 199 | ready_screen.hide_match_id() 200 | ready_screen.reset_status("Waiting...") 201 | ready_screen.set_score(session_id, score) 202 | ui_layer.show_screen("ReadyScreen") 203 | else: 204 | restart_game() 205 | 206 | func _on_Music_song_finished(song) -> void: 207 | if not music.current_song.playing: 208 | music.play_random() 209 | -------------------------------------------------------------------------------- /Main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=19 format=2] 2 | 3 | [ext_resource path="res://Main.gd" type="Script" id=1] 4 | [ext_resource path="res://main/screens/ConnectionScreen.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://Game.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://main/screens/MatchScreen.tscn" type="PackedScene" id=4] 7 | [ext_resource path="res://main/UILayer.gd" type="Script" id=5] 8 | [ext_resource path="res://main/screens/ReadyScreen.tscn" type="PackedScene" id=6] 9 | [ext_resource path="res://main/screens/TitleScreen.tscn" type="PackedScene" id=7] 10 | [ext_resource path="res://assets/fonts/monogram.tres" type="DynamicFont" id=8] 11 | [ext_resource path="res://main/Music.gd" type="Script" id=9] 12 | [ext_resource path="res://assets/music/BUILD UP.ogg" type="AudioStream" id=10] 13 | [ext_resource path="res://assets/music/THEME #1.ogg" type="AudioStream" id=11] 14 | [ext_resource path="res://assets/music/DOMEFIGHTERS.ogg" type="AudioStream" id=12] 15 | [ext_resource path="res://assets/music/FISHSTICKS.ogg" type="AudioStream" id=13] 16 | [ext_resource path="res://main/screens/LeaderboardScreen.tscn" type="PackedScene" id=14] 17 | [ext_resource path="res://main/screens/CreditsScreen.tscn" type="PackedScene" id=15] 18 | [ext_resource path="res://main/screens/TransparentButton.gd" type="Script" id=17] 19 | [ext_resource path="res://assets/ui/mute.png" type="Texture" id=18] 20 | [ext_resource path="res://assets/ui/close.png" type="Texture" id=19] 21 | 22 | [node name="Main" type="Node2D"] 23 | script = ExtResource( 1 ) 24 | 25 | [node name="Game" parent="." instance=ExtResource( 3 )] 26 | 27 | [node name="UILayer" type="CanvasLayer" parent="."] 28 | pause_mode = 2 29 | script = ExtResource( 5 ) 30 | 31 | [node name="Screens" type="Control" parent="UILayer"] 32 | anchor_right = 1.0 33 | anchor_bottom = 1.0 34 | __meta__ = { 35 | "_edit_use_anchors_": false 36 | } 37 | 38 | [node name="TitleScreen" parent="UILayer/Screens" instance=ExtResource( 7 )] 39 | visible = false 40 | 41 | [node name="ConnectionScreen" parent="UILayer/Screens" instance=ExtResource( 2 )] 42 | visible = false 43 | 44 | [node name="MatchScreen" parent="UILayer/Screens" instance=ExtResource( 4 )] 45 | visible = false 46 | 47 | [node name="ReadyScreen" parent="UILayer/Screens" instance=ExtResource( 6 )] 48 | visible = false 49 | 50 | [node name="LeaderboardScreen" parent="UILayer/Screens" instance=ExtResource( 14 )] 51 | visible = false 52 | 53 | [node name="CreditsScreen" parent="UILayer/Screens" instance=ExtResource( 15 )] 54 | visible = false 55 | 56 | [node name="Overlay" type="Control" parent="UILayer"] 57 | anchor_right = 1.0 58 | anchor_bottom = 1.0 59 | mouse_filter = 2 60 | __meta__ = { 61 | "_edit_use_anchors_": false 62 | } 63 | 64 | [node name="Message" type="Label" parent="UILayer/Overlay"] 65 | visible = false 66 | anchor_right = 1.0 67 | margin_top = 20.0 68 | margin_bottom = 45.0 69 | custom_fonts/font = ExtResource( 8 ) 70 | text = "Waiting for players..." 71 | align = 1 72 | 73 | [node name="BackButton" type="Button" parent="UILayer/Overlay"] 74 | visible = false 75 | anchor_left = 1.0 76 | anchor_right = 1.0 77 | margin_left = -30.0 78 | margin_top = 2.0 79 | margin_right = -2.0 80 | margin_bottom = 30.0 81 | focus_mode = 0 82 | enabled_focus_mode = 0 83 | icon = ExtResource( 19 ) 84 | clip_text = true 85 | script = ExtResource( 17 ) 86 | __meta__ = { 87 | "_edit_use_anchors_": false 88 | } 89 | 90 | [node name="MuteButton" type="Button" parent="UILayer/Overlay"] 91 | margin_left = 2.0 92 | margin_top = 2.0 93 | margin_right = 30.0 94 | margin_bottom = 30.0 95 | focus_mode = 0 96 | toggle_mode = true 97 | enabled_focus_mode = 0 98 | icon = ExtResource( 18 ) 99 | clip_text = true 100 | script = ExtResource( 17 ) 101 | __meta__ = { 102 | "_edit_use_anchors_": false 103 | } 104 | 105 | [node name="Music" type="Node" parent="."] 106 | script = ExtResource( 9 ) 107 | 108 | [node name="BuildUp" type="AudioStreamPlayer" parent="Music"] 109 | stream = ExtResource( 10 ) 110 | bus = "Music" 111 | 112 | [node name="DomeFighters" type="AudioStreamPlayer" parent="Music"] 113 | stream = ExtResource( 12 ) 114 | bus = "Music" 115 | 116 | [node name="Fishsticks" type="AudioStreamPlayer" parent="Music"] 117 | stream = ExtResource( 13 ) 118 | bus = "Music" 119 | 120 | [node name="Theme1" type="AudioStreamPlayer" parent="Music"] 121 | stream = ExtResource( 11 ) 122 | bus = "Music" 123 | 124 | [node name="Tween" type="Tween" parent="Music"] 125 | 126 | [connection signal="game_over" from="Game" to="." method="_on_Game_game_over"] 127 | [connection signal="game_started" from="Game" to="." method="_on_Game_game_started"] 128 | [connection signal="player_dead" from="Game" to="." method="_on_Game_player_dead"] 129 | [connection signal="back_button" from="UILayer" to="." method="_on_UILayer_back_button"] 130 | [connection signal="change_screen" from="UILayer" to="." method="_on_UILayer_change_screen"] 131 | [connection signal="play_local" from="UILayer/Screens/TitleScreen" to="." method="_on_TitleScreen_play_local"] 132 | [connection signal="play_online" from="UILayer/Screens/TitleScreen" to="." method="_on_TitleScreen_play_online"] 133 | [connection signal="ready_pressed" from="UILayer/Screens/ReadyScreen" to="." method="_on_ReadyScreen_ready_pressed"] 134 | [connection signal="pressed" from="UILayer/Overlay/BackButton" to="UILayer" method="_on_BackButton_pressed"] 135 | [connection signal="toggled" from="UILayer/Overlay/MuteButton" to="UILayer" method="_on_MuteButton_toggled"] 136 | [connection signal="song_finished" from="Music" to="." method="_on_Music_song_finished"] 137 | [connection signal="tween_completed" from="Music/Tween" to="Music" method="_on_Tween_tween_completed"] 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | "Fish Game" for Godot 2 | ===================== 3 | 4 | **"Fish Game" for Godot** is a 2-4 player online game built in the 5 | [Godot](https://godotengine.org/) game engine, created as a demonstration of 6 | [Nakama](https://heroiclabs.com/), an open-source scalable game server. 7 | 8 | ![Animated GIF showing gameplay](assets/screenshots/gameplay.gif) 9 | 10 | You can download playable builds for Windows, Linux and MacOS from the 11 | [Releases page](https://github.com/heroiclabs/fishgame-godot/releases). 12 | 13 | **"Fish Game"** demonstrates the following Nakama features: 14 | 15 | - [User authentication](https://heroiclabs.com/docs/authentication/) 16 | - [Matchmaking](https://heroiclabs.com/docs/gameplay-matchmaker/) 17 | - [Leaderboards](https://heroiclabs.com/docs/gameplay-leaderboards/) 18 | - [Realtime Multiplayer](https://heroiclabs.com/docs/gameplay-multiplayer-realtime/) 19 | 20 | The game design is heavily inspired by [Duck Game](https://store.steampowered.com/app/312530/Duck_Game/). 21 | 22 | [Gamecom's Public Server!](https://gamecom.io/fishgame) 23 | -------- 24 | 25 | [Gamecom's](https://gamecom.io/) public server may be found [here](https://gamecom.io/fishgame). 26 | 27 | Controls 28 | -------- 29 | 30 | ### Playing Online ### 31 | 32 | #### Gamepad: #### 33 | 34 | - **D-PAD** or **LEFT ANALOG STICK** = move your fish 35 | - **A (XBox)** or **Cross (PS)** = jump 36 | - **Y (XBox)** or **Triangle (PS)** = pickup/throw weapon 37 | - **X (XBox)** or **Square (PS)** = use weapon 38 | - **B (Xbox)** or **Circle (PS)** = blub 39 | 40 | #### Keyboard: #### 41 | 42 | - **W**, **A**, **S**, **D** = move your fish 43 | - **E** = pickup/throw weapon 44 | - **Q** = use weapon 45 | - **C** = blub 46 | 47 | ### Playing Locally ### 48 | 49 | #### Gamepad: #### 50 | 51 | *Same as the "Playing Online" controls above.* 52 | 53 | #### Keyboard: #### 54 | 55 | | Action | Player 1 | Player 2 | 56 | | -------------------- | -------------------------- | ---------- | 57 | | move your fish | **W**, **A**, **S**, **D** | Arrow keys | 58 | | pickup/throw weapon | **C** | **L** | 59 | | use weapon | **V** | **;** | 60 | | blub | **E** | **P** | 61 | 62 | Playing the game from source 63 | ---------------------------- 64 | 65 | ### Dependencies ### 66 | 67 | You'll need: 68 | 69 | * [Godot](https://godotengine.org/download) 3.2.3 or later. 70 | * A Nakama server (version 2.15.0 or later) to connect to. 71 | 72 | The easiest way to setup a Nakama server locally for testing/learning purposes is [via Docker](https://heroiclabs.com/docs/install-docker-quickstart/), and in fact, there is a `docker-compose.yml` included in the source code of "Fish Game". 73 | 74 | So, if you have [Docker Compose](https://docs.docker.com/compose/install/) installed on your system, all you need to do is navigate to the directory where you put the "Fish Game" source code and run this command: 75 | 76 | ``` 77 | docker-compose up -d 78 | ``` 79 | 80 | ### Running the game from source ### 81 | 82 | 1. Download the source code to your computer. 83 | 2. Open Godot and "Import" the project. 84 | 3. (Optional) Edit the 85 | [autoload/Online.gd](https://github.com/heroiclabs/fishgame-godot/blob/main/autoload/Online.gd) 86 | file and replace the variables at the top with the right values for your Nakama server. 87 | If you're running a Nakama server locally with the default settings, then you 88 | shouldn't need to change anything. 89 | 4. Press F5 or click the play button in the upper-right corner to start the game. 90 | 91 | ### Setting up the leaderboard ### 92 | 93 | If you didn't use the `docker-compose.yml` included with "Fish Game", then the "Leaderboard" won't work until you first create it on your server. 94 | 95 | To do that, copy the `nakama/data/modules/fish_game.lua` file to the `modules/` directory where your Nakama server keeps its data, and then restart your Nakama server. 96 | 97 | _Note: The game will play fine without the leaderboard._ 98 | 99 | License 100 | ------- 101 | 102 | This project is licensed under the Apache 2.0 License, with the following exceptions: 103 | 104 | * The font (in [assets/fonts/](https://github.com/heroiclabs/fishgame-godot/blob/main/assets/fonts)) and a handful of art assets (in [assets/kenney-platform-deluxe/](https://github.com/heroiclabs/fishgame-godot/blob/main/assets/kenney-platform-deluxe)) originate from CC0 sources (see [CREDITS-CC0.txt](https://github.com/heroiclabs/fishgame-godot/blob/main/CREDITS-CC0.txt)). 105 | * The remaining art, music and sound assets are licensed under the [CC BY-NC License](https://github.com/heroiclabs/fishgame-godot/blob/main/assets/LICENSE.txt). 106 | * The [Snopek State Machine](https://gitlab.com/snopek-games/godot-state-machine) included in [addons/snopek-state-machine/](https://github.com/heroiclabs/fishgame-godot/tree/main/addons/snopek-state-machine) is licensed under the MIT License. 107 | * Most of the UI code (included in [main/](https://github.com/heroiclabs/fishgame-godot/tree/main/main)) and some other auxilary code files originate from the [WebRTC and Nakama addon for Godot](https://gitlab.com/snopek-games/godot-nakama-webrtc), which is licensed under the MIT License. 108 | 109 | -------------------------------------------------------------------------------- /actors/ExplodeEffect.gd: -------------------------------------------------------------------------------- 1 | extends CPUParticles2D 2 | 3 | onready var sounds = $Sounds 4 | 5 | func _ready(): 6 | emitting = true 7 | sounds.play("Explode") 8 | 9 | func _on_Timer_timeout() -> void: 10 | queue_free() 11 | -------------------------------------------------------------------------------- /actors/ExplodeEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://assets/kenney-platform-deluxe/request/rockSmall.png" type="Texture" id=1] 4 | [ext_resource path="res://actors/ExplodeEffect.gd" type="Script" id=2] 5 | [ext_resource path="res://actors/ExplodeEffectGradient.tres" type="Gradient" id=3] 6 | [ext_resource path="res://assets/sounds/explosions/explosion3.wav" type="AudioStream" id=4] 7 | [ext_resource path="res://components/Sounds.gd" type="Script" id=5] 8 | [ext_resource path="res://assets/sounds/explosions/explosion2.wav" type="AudioStream" id=6] 9 | [ext_resource path="res://assets/sounds/explosions/explosion1.wav" type="AudioStream" id=7] 10 | 11 | [sub_resource type="Curve" id=1] 12 | _data = [ Vector2( 0, 1 ), 0.0, 0.0, 0, 0, Vector2( 0.994253, 0.00568175 ), 0.0, 0.0, 0, 0 ] 13 | 14 | [node name="ExplodeEffect" type="CPUParticles2D"] 15 | emitting = false 16 | one_shot = true 17 | explosiveness = 1.0 18 | texture = ExtResource( 1 ) 19 | emission_shape = 2 20 | emission_rect_extents = Vector2( 10, 1 ) 21 | direction = Vector2( 0, -1 ) 22 | spread = 90.0 23 | gravity = Vector2( 0, 1500 ) 24 | initial_velocity = 600.0 25 | initial_velocity_random = 0.5 26 | angular_velocity = 90.0 27 | angular_velocity_random = 0.5 28 | linear_accel = 50.0 29 | linear_accel_random = 0.5 30 | scale_amount = 0.75 31 | scale_amount_random = 0.5 32 | scale_amount_curve = SubResource( 1 ) 33 | color_ramp = ExtResource( 3 ) 34 | hue_variation = 0.05 35 | hue_variation_random = 0.5 36 | script = ExtResource( 2 ) 37 | 38 | [node name="Timer" type="Timer" parent="."] 39 | wait_time = 1.5 40 | one_shot = true 41 | autostart = true 42 | 43 | [node name="Sounds" type="Node" parent="."] 44 | script = ExtResource( 5 ) 45 | 46 | [node name="Explode" type="Node" parent="Sounds"] 47 | 48 | [node name="Explode1" type="AudioStreamPlayer" parent="Sounds/Explode"] 49 | stream = ExtResource( 7 ) 50 | bus = "Sound Effects" 51 | 52 | [node name="Explode2" type="AudioStreamPlayer" parent="Sounds/Explode"] 53 | stream = ExtResource( 6 ) 54 | bus = "Sound Effects" 55 | 56 | [node name="Explode3" type="AudioStreamPlayer" parent="Sounds/Explode"] 57 | stream = ExtResource( 4 ) 58 | bus = "Sound Effects" 59 | [connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] 60 | -------------------------------------------------------------------------------- /actors/ExplodeEffectGradient.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Gradient" format=2] 2 | 3 | [resource] 4 | offsets = PoolRealArray( 1.4013e-45, 0.582781, 1 ) 5 | colors = PoolColorArray( 1, 0, 0, 1, 1, 0.25098, 0, 1, 0.25098, 0.25098, 0.25098, 0 ) 6 | -------------------------------------------------------------------------------- /actors/player-states/Dead.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/snopek-state-machine/State.gd" 2 | 3 | var ExplodeEffect: PackedScene = preload("res://actors/ExplodeEffect.tscn") 4 | 5 | onready var host = $"../.." 6 | 7 | func _state_enter(info: Dictionary) -> void: 8 | var explosion = ExplodeEffect.instance() 9 | host.get_parent().add_child(explosion) 10 | explosion.global_position = host.global_position 11 | 12 | host.die() 13 | -------------------------------------------------------------------------------- /actors/player-states/Duck.gd: -------------------------------------------------------------------------------- 1 | extends "res://actors/player-states/Move.gd" 2 | 3 | func _state_enter(info: Dictionary) -> void: 4 | host.play_animation("Duck") 5 | host.ducking_collision_shape.set_deferred('disabled', false) 6 | host.standing_collision_shape.set_deferred('disabled', true) 7 | 8 | func _state_exit() -> void: 9 | host.ducking_collision_shape.set_deferred('disabled', true) 10 | host.standing_collision_shape.set_deferred('disabled', false) 11 | 12 | func _state_physics_process(delta: float) -> void: 13 | _check_pickup_or_throw_or_use() 14 | 15 | var input_vector = _get_player_input_vector() 16 | do_flip_sprite(input_vector) 17 | 18 | _decelerate_to_zero(delta) 19 | 20 | if not host.input_buffer.is_action_pressed("down") or not host.is_on_floor(): 21 | get_parent().change_state("Idle") 22 | elif host.input_buffer.is_action_just_pressed("jump"): 23 | host.pass_through_one_way_platforms = true 24 | else: 25 | # If we haven't started moving by the next frame then we must not have 26 | # actually been on a one way platform, so we reset this value. 27 | host.pass_through_one_way_platforms = false 28 | -------------------------------------------------------------------------------- /actors/player-states/Fall.gd: -------------------------------------------------------------------------------- 1 | extends "res://actors/player-states/Move.gd" 2 | 3 | func _state_enter(info: Dictionary) -> void: 4 | host.play_animation("Fall") 5 | 6 | func _state_exit() -> void: 7 | host.show_gliding = false 8 | 9 | func _state_physics_process(delta: float) -> void: 10 | _check_pickup_or_throw_or_use() 11 | 12 | var input_vector = _get_player_input_vector() 13 | 14 | if host.is_on_floor(): 15 | if abs(input_vector.x) > 0: 16 | get_parent().change_state("Move", { input_vector = input_vector }) 17 | else: 18 | get_parent().change_state("Idle", { landing = true }) 19 | return 20 | 21 | do_move(input_vector) 22 | 23 | # If the player presses the jump key before landing, then glide. 24 | if host.input_buffer.is_action_pressed("jump"): 25 | host.vector.y = -host.glide_speed 26 | host.show_gliding = true 27 | host.play_animation("Glide") 28 | else: 29 | host.play_animation("Fall") 30 | 31 | -------------------------------------------------------------------------------- /actors/player-states/Hurt.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/snopek-state-machine/State.gd" 2 | 3 | onready var host = $"../.." 4 | onready var timer = $Timer 5 | 6 | func _state_enter(info: Dictionary) -> void: 7 | host.play_animation("Hurt") 8 | host.sounds.play("Hurt") 9 | var push_back_vector = info['push_back_vector'] if info.has("push_back_vector") else Vector2.UP 10 | host.vector = push_back_vector * host.push_back_speed 11 | timer.start() 12 | 13 | func _state_exit() -> void: 14 | timer.stop() 15 | 16 | func _on_Timer_timeout() -> void: 17 | if host.state_machine.current_state == self: 18 | host.state_machine.change_state("Dead") 19 | -------------------------------------------------------------------------------- /actors/player-states/Idle.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/snopek-state-machine/State.gd" 2 | 3 | onready var host = $"../.." 4 | 5 | func _state_enter(info: Dictionary) -> void: 6 | if info.get('landing', false): 7 | host.play_animation("Land") 8 | else: 9 | host.play_animation("Idle" if host.vector.x == 0.0 else "Walk") 10 | 11 | func _get_player_input_vector() -> Vector2: 12 | return Vector2(host.input_buffer.get_action_strength("right") - host.input_buffer.get_action_strength("left"), 0) 13 | 14 | func _check_pickup_or_throw_or_use(): 15 | # Only do this on the client controlling this player, because we have 16 | # seperate system for sync'ing pickups and throws that this will conflict 17 | # with if it runs on a remote player. 18 | if GameState.online_play and not host.player_controlled: 19 | return 20 | 21 | # We use call_deferred() here so that those operation happen after we've 22 | # moved the player, so we aren't, for example, shooting from our old position 23 | # rather than our new position. 24 | if host.input_buffer.is_action_just_pressed("grab"): 25 | host.call_deferred("pickup_or_throw") 26 | elif host.input_buffer.is_action_just_pressed("use"): 27 | host.call_deferred("try_use") 28 | 29 | func _decelerate_to_zero(delta: float) -> void: 30 | if host.vector.x < 0: 31 | host.vector.x = min(0.0, host.vector.x + (host.friction * delta)) 32 | elif host.vector.x > 0: 33 | host.vector.x = max(0.0, host.vector.x - (host.friction * delta)) 34 | 35 | func _state_physics_process(delta: float) -> void: 36 | _check_pickup_or_throw_or_use() 37 | 38 | if host.vector.y > 0: 39 | get_parent().change_state("Fall") 40 | return 41 | 42 | var input_vector = _get_player_input_vector() 43 | 44 | if host.input_buffer.is_action_just_pressed("jump"): 45 | if host.is_on_floor(): 46 | get_parent().change_state("Jump", { 47 | "input_vector": input_vector, 48 | }) 49 | return 50 | elif host.input_buffer.is_action_pressed("down") and host.is_on_floor(): 51 | get_parent().change_state("Duck") 52 | return 53 | elif input_vector != Vector2.ZERO: 54 | get_parent().change_state("Move", { 55 | "input_vector": input_vector, 56 | }) 57 | return 58 | 59 | _decelerate_to_zero(delta) 60 | 61 | if host.input_buffer.is_action_just_pressed("blop"): 62 | host.play_animation("Blop") 63 | 64 | # If we just decelerated to 0, then switch to the idle animation. 65 | if not host.get_current_animation() in ["Idle", "Blop", "Land"] and host.vector.x == 0: 66 | host.play_animation("Idle") 67 | 68 | func _on_SpriteAnimationPlayer_animation_finished(anim_name: String) -> void: 69 | if host.state_machine.current_state == self and anim_name == "Land": 70 | host.play_animation("Idle") 71 | -------------------------------------------------------------------------------- /actors/player-states/Jump.gd: -------------------------------------------------------------------------------- 1 | extends "res://actors/player-states/Move.gd" 2 | 3 | func _state_enter(info: Dictionary) -> void: 4 | host.play_animation("Jump") 5 | host.sounds.play("Jump") 6 | host.vector.y = -host.jump_speed 7 | 8 | if info.has('input_vector'): 9 | do_move(info['input_vector']) 10 | 11 | func _state_physics_process(delta: float) -> void: 12 | _check_pickup_or_throw_or_use() 13 | 14 | if host.is_on_floor(): 15 | get_parent().change_state("Idle", { landing = true }) 16 | return 17 | 18 | var input_vector = _get_player_input_vector() 19 | do_move(input_vector) 20 | 21 | # If the player releases the jump key, then interrupt the jump. 22 | if host.input_buffer.is_action_just_released("jump"): 23 | if host.vector.y < 0.0: 24 | host.vector.y = 0.0 25 | 26 | # Change state to falling once we start to head down. 27 | if host.vector.y >= 0.0: 28 | get_parent().change_state("Fall") 29 | 30 | -------------------------------------------------------------------------------- /actors/player-states/Move.gd: -------------------------------------------------------------------------------- 1 | extends "res://actors/player-states/Idle.gd" 2 | 3 | func _state_enter(info: Dictionary) -> void: 4 | host.play_animation("Walk") 5 | if info.has('input_vector'): 6 | do_move(info['input_vector']) 7 | 8 | 9 | func _state_physics_process(delta: float) -> void: 10 | _check_pickup_or_throw_or_use() 11 | 12 | var input_vector = _get_player_input_vector() 13 | if host.input_buffer.is_action_just_pressed("jump"): 14 | if host.is_on_floor(): 15 | get_parent().change_state("Jump", { 16 | "input_vector": input_vector, 17 | }) 18 | return 19 | elif host.input_buffer.is_action_pressed("down") and host.is_on_floor(): 20 | if abs(host.vector.x) > 10.0: 21 | get_parent().change_state("Slide") 22 | else: 23 | get_parent().change_state("Duck") 24 | return 25 | elif input_vector == Vector2.ZERO: 26 | get_parent().change_state("Idle") 27 | return 28 | 29 | do_move(input_vector) 30 | 31 | if host.vector.y > 0: 32 | get_parent().change_state("Fall") 33 | return 34 | 35 | func do_flip_sprite(input_vector: Vector2) -> void: 36 | if input_vector.x < 0: 37 | host.flip_h = true 38 | elif input_vector.x > 0: 39 | host.flip_h = false 40 | 41 | func do_move(input_vector: Vector2) -> void: 42 | do_flip_sprite(input_vector) 43 | 44 | # Accelerate to top speed. 45 | var delta = get_physics_process_delta_time() 46 | if input_vector.x > 0: 47 | host.vector.x = min(input_vector.x * host.speed, host.vector.x + (host.acceleration * delta)) 48 | elif input_vector.x < 0: 49 | host.vector.x = max(input_vector.x * host.speed, host.vector.x - (host.acceleration * delta)) 50 | else: 51 | _decelerate_to_zero(delta) 52 | 53 | 54 | -------------------------------------------------------------------------------- /actors/player-states/Slide.gd: -------------------------------------------------------------------------------- 1 | extends "res://actors/player-states/Move.gd" 2 | 3 | func _state_enter(info: Dictionary) -> void: 4 | host.play_animation("Slide") 5 | host.show_sliding = true 6 | host.sliding_collision_shape.set_deferred('disabled', false) 7 | host.standing_collision_shape.set_deferred('disabled', true) 8 | 9 | func _state_exit() -> void: 10 | host.show_sliding = false 11 | host.sliding_collision_shape.set_deferred('disabled', true) 12 | host.standing_collision_shape.set_deferred('disabled', false) 13 | 14 | func _state_physics_process(delta: float) -> void: 15 | _check_pickup_or_throw_or_use() 16 | 17 | # Decelerate to 0, but with a "slide". 18 | if host.vector.x < 0: 19 | host.vector.x = min(0.0, host.vector.x + (host.sliding_friction * delta)) 20 | elif host.vector.x > 0: 21 | host.vector.x = max(0.0, host.vector.x - (host.sliding_friction * delta)) 22 | 23 | if host.vector.x == 0.0: 24 | host.play_animation("SlideFinished") 25 | 26 | if not host.input_buffer.is_action_pressed("down") or not host.is_on_floor(): 27 | get_parent().change_state("Idle") 28 | 29 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/Nakama.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | # The default host address of the server. 5 | const DEFAULT_HOST : String = "127.0.0.1" 6 | 7 | # The default port number of the server. 8 | const DEFAULT_PORT : int = 7350 9 | 10 | # The default timeout for the connections. 11 | const DEFAULT_TIMEOUT = 3 12 | 13 | # The default protocol scheme for the client connection. 14 | const DEFAULT_CLIENT_SCHEME : String = "http" 15 | 16 | # The default protocol scheme for the socket connection. 17 | const DEFAULT_SOCKET_SCHEME : String = "ws" 18 | 19 | # The default log level for the Nakama logger. 20 | const DEFAULT_LOG_LEVEL = NakamaLogger.LOG_LEVEL.DEBUG 21 | 22 | var _http_adapter = null 23 | var logger = NakamaLogger.new() 24 | 25 | func get_client_adapter() -> NakamaHTTPAdapter: 26 | if _http_adapter == null: 27 | _http_adapter = NakamaHTTPAdapter.new() 28 | _http_adapter.logger = logger 29 | _http_adapter.name = "NakamaHTTPAdapter" 30 | add_child(_http_adapter) 31 | return _http_adapter 32 | 33 | func create_socket_adapter() -> NakamaSocketAdapter: 34 | var adapter = NakamaSocketAdapter.new() 35 | adapter.name = "NakamaWebSocketAdapter" 36 | adapter.logger = logger 37 | add_child(adapter) 38 | return adapter 39 | 40 | func create_client(p_server_key : String, 41 | p_host : String = DEFAULT_HOST, 42 | p_port : int = DEFAULT_PORT, 43 | p_scheme : String = DEFAULT_CLIENT_SCHEME, 44 | p_timeout : int = DEFAULT_TIMEOUT, 45 | p_log_level : int = DEFAULT_LOG_LEVEL) -> NakamaClient: 46 | logger._level = p_log_level 47 | return NakamaClient.new(get_client_adapter(), p_server_key, p_scheme, p_host, p_port, p_timeout) 48 | 49 | func create_socket(p_host : String = DEFAULT_HOST, 50 | p_port : int = DEFAULT_PORT, 51 | p_scheme : String = DEFAULT_SOCKET_SCHEME) -> NakamaSocket: 52 | return NakamaSocket.new(create_socket_adapter(), p_host, p_port, p_scheme, true) 53 | 54 | func create_socket_from(p_client : NakamaClient) -> NakamaSocket: 55 | var scheme = "ws" 56 | if p_client.scheme == "https": 57 | scheme = "wss" 58 | return NakamaSocket.new(create_socket_adapter(), p_client.host, p_client.port, scheme, true) 59 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/api/NakamaSession.gd: -------------------------------------------------------------------------------- 1 | extends NakamaAsyncResult 2 | class_name NakamaSession 3 | 4 | 5 | var created : bool = false setget _no_set 6 | var token : String = "" setget _no_set 7 | var create_time : int = 0 setget _no_set 8 | var expire_time : int = 0 setget _no_set 9 | var expired : bool = true setget _no_set, is_expired 10 | var vars : Dictionary = {} setget _no_set 11 | var username : String = "" setget _no_set 12 | var user_id : String = "" setget _no_set 13 | var valid : bool = false setget _no_set, is_valid 14 | 15 | func _no_set(v): 16 | return 17 | 18 | func is_expired() -> bool: 19 | return expire_time < OS.get_unix_time() 20 | 21 | func is_valid(): 22 | return valid 23 | 24 | func _init(p_token = null, p_created : bool = false, p_exception = null).(p_exception): 25 | if p_token: 26 | var unpacked = _jwt_unpack(p_token) 27 | var decoded = {} 28 | if not validate_json(unpacked): 29 | decoded = parse_json(unpacked) 30 | valid = true 31 | if typeof(decoded) != TYPE_DICTIONARY: 32 | decoded = {} 33 | if decoded.empty(): 34 | valid = false 35 | if p_exception == null: 36 | _ex = NakamaException.new("Unable to unpack token") 37 | 38 | token = p_token 39 | created = p_created 40 | create_time = OS.get_unix_time() 41 | expire_time = int(decoded.get("exp", 0)) 42 | username = str(decoded.get("usn", "")) 43 | user_id = str(decoded.get("uid", "")) 44 | if decoded.has("vrs") and typeof(decoded["vrs"]) == TYPE_DICTIONARY: 45 | for k in decoded["vrs"]: 46 | vars[k] = decoded["vrs"][k] 47 | 48 | func _to_string(): 49 | if is_exception(): 50 | return get_exception()._to_string() 51 | return "Session" % [ 52 | created, token, create_time, username, user_id, str(vars)] 53 | 54 | func _jwt_unpack(p_token : String) -> String: 55 | # Hack decode JSON payload from JWT. 56 | if p_token.find(".") == -1: 57 | return "" 58 | var payload = p_token.split('.')[1]; 59 | var pad_length = ceil(payload.length() / 4.0) * 4; 60 | # Pad base64 61 | for i in range(0, pad_length - payload.length()): 62 | payload += "=" 63 | payload = payload.replace("-", "+").replace("_", "/") 64 | return Marshalls.base64_to_utf8(payload) 65 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name NakamaStorageObjectId 3 | 4 | # The collection which stores the object. 5 | var collection : String 6 | 7 | # The key of the object within the collection. 8 | var key : String 9 | 10 | # The user owner of the object. 11 | var user_id : String 12 | 13 | # The version hash of the object. 14 | var version : String 15 | 16 | func _init(p_collection, p_key, p_user_id = "", p_version = ""): 17 | collection = p_collection 18 | key = p_key 19 | user_id = p_user_id 20 | version = p_version 21 | 22 | func as_delete(): 23 | return NakamaAPI.ApiDeleteStorageObjectId.create(NakamaAPI, { 24 | "collection": collection, 25 | "key": key, 26 | "version": version 27 | }) 28 | 29 | func as_read(): 30 | return NakamaAPI.ApiReadStorageObjectId.create(NakamaAPI, { 31 | "collection": collection, 32 | "key": key, 33 | "user_id": user_id 34 | }) 35 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name NakamaWriteStorageObject 3 | 4 | var collection : String 5 | var key : String 6 | var permission_read : int = 0 7 | var permission_write : int = 0 8 | var value : String 9 | var version : String 10 | 11 | func _init(p_collection : String, p_key : String, p_permission_read : int, 12 | p_permission_write : int, p_value : String, p_version : String): 13 | collection = p_collection 14 | key = p_key 15 | permission_read = p_permission_read 16 | permission_write = p_permission_write 17 | value = p_value 18 | version = p_version 19 | 20 | func as_write(): 21 | return NakamaAPI.ApiWriteStorageObject.create(NakamaAPI, { 22 | "collection": collection, 23 | "key": key, 24 | "permission_read": permission_read, 25 | "permission_write": permission_write, 26 | "value": value, 27 | "version": version 28 | }) 29 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | # An adapter which implements the HTTP protocol. 5 | class_name NakamaHTTPAdapter 6 | 7 | # The logger to use with the adapter. 8 | var logger : Reference = NakamaLogger.new() 9 | 10 | var _pending = {} 11 | var id : int = 0 12 | 13 | # Send a HTTP request. 14 | # @param method - HTTP method to use for this request. 15 | # @param uri - The fully qualified URI to use. 16 | # @param headers - Request headers to set. 17 | # @param body - Request content body to set. 18 | # @param timeoutSec - Request timeout. 19 | # Returns a task which resolves to the contents of the response. 20 | func send_async(p_method : String, p_uri : String, p_headers : Dictionary, p_body : PoolByteArray, p_timeout : int = 3): 21 | var req = HTTPRequest.new() 22 | if OS.get_name() != 'HTML5': 23 | req.use_threads = true # Threads not available nor needed on the web. 24 | 25 | # Parse method 26 | var method = HTTPClient.METHOD_GET 27 | if p_method == "POST": 28 | method = HTTPClient.METHOD_POST 29 | elif p_method == "PUT": 30 | method = HTTPClient.METHOD_PUT 31 | elif p_method == "DELETE": 32 | method = HTTPClient.METHOD_DELETE 33 | elif p_method == "HEAD": 34 | method = HTTPClient.METHOD_HEAD 35 | var headers = PoolStringArray() 36 | 37 | # Parse headers 38 | headers.append("Accept: application/json") 39 | for k in p_headers: 40 | headers.append("%s: %s" % [k, p_headers[k]]) 41 | 42 | # Handle timeout for 3.1 compatibility 43 | id += 1 44 | _pending[id] = [req, OS.get_ticks_msec() + (p_timeout * 1000)] 45 | 46 | logger.debug("Sending request [ID: %d, Method: %s, Uri: %s, Headers: %s, Body: %s, Timeout: %d]" % [ 47 | id, p_method, p_uri, p_headers, p_body.get_string_from_utf8(), p_timeout 48 | ]) 49 | 50 | add_child(req) 51 | return _send_async(req, p_uri, headers, method, p_body, id, _pending, logger) 52 | 53 | func _process(delta): 54 | # Handle timeout for 3.1 compatibility 55 | var ids = _pending.keys() 56 | for id in ids: 57 | var p = _pending[id] 58 | if p[0].is_queued_for_deletion(): 59 | _pending.erase(id) 60 | continue 61 | if p[1] < OS.get_ticks_msec(): 62 | logger.debug("Request %d timed out" % id) 63 | p[0].cancel_request() 64 | _pending.erase(id) 65 | p[0].emit_signal("request_completed", HTTPRequest.RESULT_REQUEST_FAILED, 0, PoolStringArray(), PoolByteArray()) 66 | 67 | static func _send_async(request : HTTPRequest, p_uri : String, p_headers : PoolStringArray, 68 | p_method : int, p_body : PoolByteArray, p_id : int, p_pending : Dictionary, logger : NakamaLogger): 69 | 70 | var err = request.request(p_uri, p_headers, true, p_method, p_body.get_string_from_utf8()) 71 | if err != OK: 72 | yield(request.get_tree(), "idle_frame") 73 | logger.debug("Request %d failed to start, error: %d" % [p_id, err]) 74 | request.queue_free() 75 | return NakamaException.new("Request failed") 76 | 77 | var args = yield(request, "request_completed") 78 | var result = args[0] 79 | var response_code = args[1] 80 | var _headers = args[2] 81 | var body = args[3] 82 | 83 | # Will be deleted next frame 84 | if not request.is_queued_for_deletion(): 85 | request.queue_free() 86 | p_pending.erase(p_id) 87 | 88 | if result != HTTPRequest.RESULT_SUCCESS: 89 | logger.debug("Request %d failed with result: %d, response code: %d" % [ 90 | p_id, result, response_code 91 | ]) 92 | return NakamaException.new("HTTPRequest failed!", result) 93 | 94 | var json : JSONParseResult = JSON.parse(body.get_string_from_utf8()) 95 | if json.error != OK: 96 | logger.debug("Unable to parse request %d response. JSON error: %d, response code: %d" % [ 97 | p_id, json.error, response_code 98 | ]) 99 | return NakamaException.new("Failed to decode JSON response", response_code) 100 | 101 | if response_code != HTTPClient.RESPONSE_OK: 102 | var error = "" 103 | var code = -1 104 | if typeof(json.result) == TYPE_DICTIONARY: 105 | error = json.result["error"] if "error" in json.result else str(json.result) 106 | code = json.result["code"] if "code" in json.result else -1 107 | else: 108 | error = str(json.result) 109 | if typeof(error) == TYPE_DICTIONARY: 110 | error = JSON.print(error) 111 | logger.debug("Request %d returned response code: %d, RPC code: %d, error: %s" % [ 112 | p_id, response_code, code, error 113 | ]) 114 | return NakamaException.new(error, response_code, code) 115 | 116 | return json.result 117 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | # An adapter which implements a socket with a protocol supported by Nakama. 5 | class_name NakamaSocketAdapter 6 | 7 | var _ws := WebSocketClient.new() 8 | var _timeout : int = 30 9 | var _start : int = 0 10 | var logger = NakamaLogger.new() 11 | 12 | # A signal emitted when the socket is connected. 13 | signal connected() 14 | 15 | # A signal emitted when the socket is disconnected. 16 | signal closed() 17 | 18 | # A signal emitted when the socket has an error when connected. 19 | signal received_error(p_exception) 20 | 21 | # A signal emitted when the socket receives a message. 22 | signal received(p_bytes) # PoolByteArray 23 | 24 | # If the socket is connected. 25 | func is_connected_to_host(): 26 | return _ws.get_connection_status() == WebSocketClient.CONNECTION_CONNECTED 27 | 28 | # If the socket is connecting. 29 | func is_connecting_to_host(): 30 | return _ws.get_connection_status() == WebSocketClient.CONNECTION_CONNECTING 31 | 32 | # Close the socket with an asynchronous operation. 33 | func close(): 34 | _ws.disconnect_from_host() 35 | 36 | # Connect to the server with an asynchronous operation. 37 | # @param p_uri - The URI of the server. 38 | # @param p_timeout - The timeout for the connect attempt on the socket. 39 | func connect_to_host(p_uri : String, p_timeout : int): 40 | _ws.disconnect_from_host() 41 | _timeout = p_timeout 42 | _start = OS.get_unix_time() 43 | var err = _ws.connect_to_url(p_uri) 44 | if err != OK: 45 | logger.debug("Error connecting to host %s" % p_uri) 46 | call_deferred("emit_signal", "received_error", err) 47 | 48 | # Send data to the server with an asynchronous operation. 49 | # @param p_buffer - The buffer with the message to send. 50 | # @param p_reliable - If the message should be sent reliably (will be ignored by some protocols). 51 | func send(p_buffer : PoolByteArray, p_reliable : bool = true) -> int: 52 | return _ws.get_peer(1).put_packet(p_buffer) 53 | 54 | func _process(delta): 55 | if _ws.get_connection_status() == WebSocketClient.CONNECTION_CONNECTING: 56 | if _start + _timeout < OS.get_unix_time(): 57 | logger.debug("Timeout when connecting to socket") 58 | emit_signal("received_error", ERR_TIMEOUT) 59 | _ws.disconnect_from_host() 60 | else: 61 | _ws.poll() 62 | if _ws.get_connection_status() != WebSocketClient.CONNECTION_DISCONNECTED: 63 | _ws.poll() 64 | 65 | func _init(): 66 | _ws.connect("data_received", self, "_received") 67 | _ws.connect("connection_established", self, "_connected") 68 | _ws.connect("connection_error", self, "_error") 69 | _ws.connect("connection_closed", self, "_closed") 70 | 71 | func _received(): 72 | emit_signal("received", _ws.get_peer(1).get_packet()) 73 | 74 | func _connected(p_protocol : String): 75 | _ws.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT) 76 | emit_signal("connected") 77 | 78 | func _error(): 79 | emit_signal("received_error", FAILED) 80 | 81 | func _closed(p_clean : bool): 82 | emit_signal("closed") 83 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name NakamaAsyncResult 3 | 4 | var exception : NakamaException setget _no_set, get_exception 5 | var _ex = null 6 | 7 | func _no_set(v): 8 | return 9 | 10 | func _init(p_ex = null): 11 | _ex = p_ex 12 | 13 | func is_exception(): 14 | return get_exception() != null 15 | 16 | func get_exception() -> NakamaException: 17 | return _ex as NakamaException 18 | 19 | func _to_string(): 20 | if is_exception(): 21 | return get_exception()._to_string() 22 | return "NakamaAsyncResult<>" 23 | 24 | static func _safe_ret(p_obj, p_type : GDScript): 25 | if p_obj is p_type: 26 | return p_obj # Correct type 27 | elif p_obj is NakamaException: 28 | return p_type.new(p_obj) # It's an exception. Incapsulate it 29 | return p_type.new(NakamaException.new()) # It's something else. generate an exception 30 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/utils/NakamaException.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | # An exception generated during a request. 4 | # Usually contains at least an error message. 5 | class_name NakamaException 6 | 7 | var status_code : int = -1 setget _no_set 8 | var grpc_status_code : int = -1 setget _no_set 9 | var message : String = "" setget _no_set 10 | 11 | func _no_set(_p): 12 | pass 13 | 14 | func _init(p_message : String = "", p_status_code : int = -1, p_grpc_status_code : int = -1): 15 | status_code = p_status_code 16 | grpc_status_code = p_grpc_status_code 17 | message = p_message 18 | 19 | func _to_string() -> String: 20 | return "NakamaException(StatusCode={%s}, Message='{%s}', GrpcStatusCode={%s})" % [status_code, message, grpc_status_code] 21 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/utils/NakamaLogger.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name NakamaLogger 3 | 4 | enum LOG_LEVEL {NONE, ERROR, WARNING, INFO, VERBOSE, DEBUG} 5 | 6 | var _level = LOG_LEVEL.ERROR 7 | var _module = "Nakama" 8 | 9 | func _init(p_module : String = "Nakama", p_level : int = LOG_LEVEL.ERROR): 10 | _level = p_level 11 | _module = p_module 12 | 13 | func _log(level : int, msg): 14 | if level <= _level: 15 | if level == LOG_LEVEL.ERROR: 16 | printerr("=== %s : ERROR === %s" % [_module, str(msg)]) 17 | else: 18 | var what = "=== UNKNOWN === " 19 | for k in LOG_LEVEL: 20 | if level == LOG_LEVEL[k]: 21 | what = "=== %s : %s === " % [_module, k] 22 | break 23 | print(what + str(msg)) 24 | 25 | func error(msg): 26 | _log(LOG_LEVEL.ERROR, msg) 27 | 28 | func warning(msg): 29 | _log(LOG_LEVEL.WARNING, msg) 30 | 31 | func info(msg): 32 | _log(LOG_LEVEL.INFO, msg) 33 | 34 | func verbose(msg): 35 | _log(LOG_LEVEL.VERBOSE, msg) 36 | 37 | func debug(msg): 38 | _log(LOG_LEVEL.DEBUG, msg) 39 | -------------------------------------------------------------------------------- /addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | class_name NakamaSerializer 3 | 4 | static func serialize(p_obj : Object) -> Dictionary: 5 | var out = {} 6 | var schema = p_obj.get("_SCHEMA") 7 | if schema == null: 8 | return {} # No schema defined 9 | for k in schema: 10 | var prop = schema[k] 11 | var val = p_obj.get(prop["name"]) 12 | if val == null: 13 | continue 14 | var type = prop["type"] 15 | var content = prop.get("content", TYPE_NIL) 16 | if typeof(content) == TYPE_STRING: 17 | content = TYPE_OBJECT 18 | var val_type = typeof(val) 19 | match val_type: 20 | TYPE_OBJECT: # Simple objects 21 | out[k] = serialize(val) 22 | TYPE_ARRAY: # Array of objects 23 | var arr = [] 24 | for e in val: 25 | if typeof(e) != TYPE_OBJECT: 26 | continue 27 | arr.append(serialize(e)) 28 | out[k] = arr 29 | TYPE_INT_ARRAY, TYPE_STRING_ARRAY: # Array of ints, bools, or strings 30 | var arr = [] 31 | for e in val: 32 | if content == TYPE_BOOL: 33 | e = bool(e) 34 | if typeof(e) != content: 35 | continue 36 | arr.append(e) 37 | out[k] = arr 38 | TYPE_DICTIONARY: # Maps 39 | var dict = {} 40 | if content == TYPE_OBJECT: # Map of objects 41 | for l in val: 42 | if typeof(val[l]) != TYPE_OBJECT: 43 | continue 44 | dict[l] = serialize(val[l]) 45 | else: # Map of simple types 46 | for l in val: 47 | if typeof(val[l]) != content: 48 | continue 49 | dict[l] = val[l] 50 | out[k] = dict 51 | _: 52 | out[k] = val 53 | return out 54 | 55 | static func deserialize(p_ns : GDScript, p_cls_name : String, p_dict : Dictionary) -> Object: 56 | var cls : GDScript = p_ns.get(p_cls_name) 57 | var schema = cls.get("_SCHEMA") 58 | if schema == null: 59 | return NakamaException.new() # No schema defined 60 | var obj = cls.new() 61 | for k in schema: 62 | var prop = schema[k] 63 | var pname = prop["name"] 64 | var type = prop["type"] 65 | var required = prop["required"] 66 | var content = prop.get("content", TYPE_NIL) 67 | var type_cmp = type 68 | if typeof(type) == TYPE_STRING: # A class 69 | type_cmp = TYPE_DICTIONARY 70 | if type_cmp == TYPE_STRING_ARRAY or type_cmp == TYPE_INT_ARRAY: # A specialized array 71 | type_cmp = TYPE_ARRAY 72 | 73 | var content_cmp = content 74 | if typeof(content) == TYPE_STRING: # A dictionary or array of classes 75 | content_cmp = TYPE_DICTIONARY 76 | 77 | var val = p_dict.get(k, null) 78 | 79 | # Ints might and up being recognized as floats. Change that if needed 80 | if typeof(val) == TYPE_REAL and type_cmp == TYPE_INT: 81 | val = int(val) 82 | 83 | if typeof(val) == type_cmp: 84 | if typeof(type) == TYPE_STRING: 85 | obj.set(pname, deserialize(p_ns, type, val)) 86 | elif type_cmp == TYPE_DICTIONARY: 87 | var v = {} 88 | for l in val: 89 | if typeof(content) == TYPE_STRING: 90 | v[l] = deserialize(p_ns, content, val[l]) 91 | elif content == TYPE_INT: 92 | v[l] = int(val[l]) 93 | elif content == TYPE_BOOL: 94 | v[l] = bool(val[l]) 95 | else: 96 | v[l] = str(val[l]) 97 | obj.set(pname, v) 98 | elif type_cmp == TYPE_ARRAY: 99 | var v 100 | match content: 101 | TYPE_INT, TYPE_BOOL: v = PoolIntArray() 102 | TYPE_STRING: v = PoolStringArray() 103 | _: v = Array() 104 | for e in val: 105 | if typeof(content) == TYPE_STRING: 106 | v.append(deserialize(p_ns, content, e)) 107 | elif content == TYPE_INT: 108 | v.append(int(e)) 109 | elif content == TYPE_BOOL: 110 | v.append(bool(e)) 111 | else: 112 | v.append(str(e)) 113 | obj.set(pname, v) 114 | else: 115 | obj.set(pname, val) 116 | elif required: 117 | obj._ex = NakamaException.new("ERROR [%s]: Missing or invalid required prop %s = %s:\n\t%s" % [p_cls_name, prop, p_dict.get(k), p_dict]) 118 | return obj 119 | return obj 120 | 121 | 122 | ### 123 | # Compatibility with Godot 3.1 which does not expose String.http_escape 124 | ### 125 | const HEX = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] 126 | 127 | static func escape_http(p_str : String) -> String: 128 | var out : String = "" 129 | for o in p_str: 130 | if (o == '.' or o == '-' or o == '_' or o == '~' or 131 | (o >= 'a' and o <= 'z') or 132 | (o >= 'A' and o <= 'Z') or 133 | (o >= '0' and o <= '9')): 134 | out += o 135 | else: 136 | for b in o.to_utf8(): 137 | out += "%%%s" % to_hex(b) 138 | return out 139 | 140 | static func to_hex(p_val : int) -> String: 141 | var v := p_val 142 | var o := "" 143 | while v != 0: 144 | o = HEX[v % 16] + o 145 | v /= 16 146 | return o 147 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2020 David Snopek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/State.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/addons/snopek-state-machine/State.aseprite -------------------------------------------------------------------------------- /addons/snopek-state-machine/State.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | const StateMachine = preload("res://addons/snopek-state-machine/StateMachine.gd") 5 | 6 | func _get_configuration_warning() -> String: 7 | if not get_parent() is StateMachine: 8 | return "Parent node must be a StateMachine node" 9 | return "" 10 | 11 | func _state_enter(info : Dictionary): 12 | pass 13 | 14 | func _state_exit(): 15 | pass 16 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/State.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/addons/snopek-state-machine/State.png -------------------------------------------------------------------------------- /addons/snopek-state-machine/State.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/State.png-1927c91e3049950f8a52016a40045232.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/snopek-state-machine/State.png" 13 | dest_files=[ "res://.import/State.png-1927c91e3049950f8a52016a40045232.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/StateMachine.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/addons/snopek-state-machine/StateMachine.aseprite -------------------------------------------------------------------------------- /addons/snopek-state-machine/StateMachine.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | export (String, MULTILINE) var allowed_transitions setget set_allowed_transitions 5 | 6 | var current_state 7 | var allowed_transitions_parsed := {} 8 | 9 | signal state_changed (state, info) 10 | 11 | # @todo Causes some error in game! 12 | #func _get_configuration_warning() -> String: 13 | # var bad_children = PoolStringArray() 14 | # for child in get_children(): 15 | # if not child is preload("res://addons/snopek-state-machine/State.gd"): 16 | # bad_children.append(child.name) 17 | # if bad_children.size() > 0: 18 | # return "All direct children of StateMachine must be State: " + bad_children.join(", ") 19 | # return "" 20 | 21 | func set_allowed_transitions(_allowed_transitions) -> void: 22 | if allowed_transitions != _allowed_transitions: 23 | allowed_transitions = _allowed_transitions 24 | 25 | allowed_transitions_parsed = {} 26 | for line in allowed_transitions.split("\n", false): 27 | var parts = line.split("->", false) 28 | if parts.size() == 2: 29 | var start = parts[0].strip_edges() 30 | var end = parts[1].strip_edges() 31 | 32 | if !allowed_transitions_parsed.has(start): 33 | allowed_transitions_parsed[start] = [] 34 | allowed_transitions_parsed[start].append(end) 35 | 36 | func check_allowed_transition(start, end) -> bool: 37 | if allowed_transitions_parsed.size() == 0: 38 | return true 39 | if allowed_transitions_parsed.has('*') and allowed_transitions_parsed['*'].find(end) != -1: 40 | return true 41 | if !allowed_transitions_parsed.has(start): 42 | return false 43 | return allowed_transitions_parsed[start].find(end) != -1 or allowed_transitions_parsed[start].find('*') != -1 44 | 45 | func change_state(name : String, info : Dictionary = {}): 46 | var next_state = get_node(name) 47 | if next_state == null: 48 | return 49 | 50 | if current_state == next_state: 51 | return 52 | 53 | if current_state: 54 | if !check_allowed_transition(current_state.name, next_state.name): 55 | return 56 | 57 | if current_state: 58 | if current_state.has_method('_state_exit'): 59 | current_state._state_exit() 60 | 61 | var previous_state = current_state 62 | current_state = next_state 63 | 64 | # Re-enable processing for the current state 65 | if current_state.has_method('_input'): 66 | current_state.set_process_input(true) 67 | if current_state.has_method('_unhandled_input'): 68 | current_state.set_process_unhandled_input(true) 69 | if current_state.has_method('_unhandled_key_input'): 70 | current_state.set_process_unhandled_key_input(true) 71 | if current_state.has_method('_process'): 72 | current_state.set_process(true) 73 | if current_state.has_method('_physics_process'): 74 | current_state.set_physics_process(true) 75 | 76 | if current_state != previous_state: 77 | if current_state.has_method('_state_enter'): 78 | current_state._state_enter(info) 79 | 80 | emit_signal("state_changed", current_state, info) 81 | 82 | func _input(event: InputEvent) -> void: 83 | if current_state and current_state.has_method('_state_input'): 84 | current_state._state_input(event) 85 | 86 | func _unhandled_input(event: InputEvent) -> void: 87 | if current_state and current_state.has_method('_state_unhandled_input'): 88 | current_state._state_unhandled_input(event) 89 | 90 | func _unhandled_key_input(event: InputEventKey) -> void: 91 | if current_state and current_state.has_method('_state_unhandled_key_input'): 92 | current_state._state_unhandled_key_input(event) 93 | 94 | func _process(delta: float) -> void: 95 | if current_state and current_state.has_method('_state_process'): 96 | current_state._state_process(delta) 97 | 98 | func _physics_process(delta: float) -> void: 99 | if current_state and current_state.has_method('_state_physics_process'): 100 | current_state._state_physics_process(delta) 101 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/StateMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/addons/snopek-state-machine/StateMachine.png -------------------------------------------------------------------------------- /addons/snopek-state-machine/StateMachine.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/StateMachine.png-c0bbe632abcf962551d4ccbc9ca6a6e9.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/snopek-state-machine/StateMachine.png" 13 | dest_files=[ "res://.import/StateMachine.png-c0bbe632abcf962551d4ccbc9ca6a6e9.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/init.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | func _enter_tree(): 5 | add_custom_type("StateMachine", "Node", preload("StateMachine.gd"), preload("StateMachine.png")) 6 | add_custom_type("State", "Node", preload("State.gd"), preload("State.png")) 7 | 8 | func _exit_tree(): 9 | remove_custom_type("StateMachine") 10 | remove_custom_type("State") 11 | 12 | 13 | -------------------------------------------------------------------------------- /addons/snopek-state-machine/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Snopek State Machine" 4 | description="A state machine implemented as custom node types" 5 | author="David Snopek" 6 | version="1.0.0" 7 | script="init.gd" 8 | 9 | -------------------------------------------------------------------------------- /assets/LICENSE.txt: -------------------------------------------------------------------------------- 1 | [Fish Game media assets](https://github.com/heroiclabs/fishgame-godot/tree/main/assets) © 2021 by [Heroic Labs](https://heroiclabs.com/), unless specified otherwise in their respective folder, are licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) 2 | -------------------------------------------------------------------------------- /assets/backgrounds/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/backgrounds/01.png -------------------------------------------------------------------------------- /assets/backgrounds/01.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/01.png-50e27982143d2f0111d7d58223e99d28.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/backgrounds/01.png" 13 | dest_files=[ "res://.import/01.png-50e27982143d2f0111d7d58223e99d28.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/backgrounds/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/backgrounds/02.png -------------------------------------------------------------------------------- /assets/backgrounds/02.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/02.png-2e5449902cae9b411b347bfc32d0d936.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/backgrounds/02.png" 13 | dest_files=[ "res://.import/02.png-2e5449902cae9b411b347bfc32d0d936.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/backgrounds/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/backgrounds/03.png -------------------------------------------------------------------------------- /assets/backgrounds/03.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/03.png-bb3b7aa7358f923169edc3ddb3924bb8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/backgrounds/03.png" 13 | dest_files=[ "res://.import/03.png-bb3b7aa7358f923169edc3ddb3924bb8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/backgrounds/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/backgrounds/04.png -------------------------------------------------------------------------------- /assets/backgrounds/04.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/04.png-25024d3cf19bb197a379f72af7a7917b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/backgrounds/04.png" 13 | dest_files=[ "res://.import/04.png-25024d3cf19bb197a379f72af7a7917b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/fonts/monogram.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://assets/fonts/monogram.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 30 7 | font_data = ExtResource( 1 ) 8 | -------------------------------------------------------------------------------- /assets/fonts/monogram.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/fonts/monogram.ttf -------------------------------------------------------------------------------- /assets/fonts/monogram_extended.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/fonts/monogram_extended.ttf -------------------------------------------------------------------------------- /assets/kenney-platform-deluxe/license.txt: -------------------------------------------------------------------------------- 1 | 2 | ############################################################################### 3 | 4 | Platformer Art Complete Pack by Kenney Vleugels (www.kenney.nl) 5 | 6 | ------------------------------ 7 | 8 | License (CC0) 9 | http://creativecommons.org/publicdomain/zero/1.0/ 10 | 11 | You may use these graphics in personal and commercial projects. 12 | Credit (Kenney or www.kenney.nl) would be nice but is not mandatory. 13 | 14 | ############################################################################### -------------------------------------------------------------------------------- /assets/kenney-platform-deluxe/request/rockSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/kenney-platform-deluxe/request/rockSmall.png -------------------------------------------------------------------------------- /assets/kenney-platform-deluxe/request/rockSmall.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rockSmall.png-3b3450bab86a401644dcc23c5184cc4c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/kenney-platform-deluxe/request/rockSmall.png" 13 | dest_files=[ "res://.import/rockSmall.png-3b3450bab86a401644dcc23c5184cc4c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/kenney-platform-deluxe/tiles/castle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/kenney-platform-deluxe/tiles/castle.png -------------------------------------------------------------------------------- /assets/kenney-platform-deluxe/tiles/castle.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/castle.png-f90058a20e39b71a7286a7c8d0cde359.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/kenney-platform-deluxe/tiles/castle.png" 13 | dest_files=[ "res://.import/castle.png-f90058a20e39b71a7286a7c8d0cde359.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/music/BUILD UP.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/music/BUILD UP.ogg -------------------------------------------------------------------------------- /assets/music/BUILD UP.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/BUILD UP.ogg-04051cabc5d0e6cf4e7f28b1e9c6fbdf.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/music/BUILD UP.ogg" 10 | dest_files=[ "res://.import/BUILD UP.ogg-04051cabc5d0e6cf4e7f28b1e9c6fbdf.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=false 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /assets/music/DOMEFIGHTERS.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/music/DOMEFIGHTERS.ogg -------------------------------------------------------------------------------- /assets/music/DOMEFIGHTERS.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/DOMEFIGHTERS.ogg-817eb43688b35cf7e8b1fe7664ced35c.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/music/DOMEFIGHTERS.ogg" 10 | dest_files=[ "res://.import/DOMEFIGHTERS.ogg-817eb43688b35cf7e8b1fe7664ced35c.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=false 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /assets/music/FISHSTICKS.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/music/FISHSTICKS.ogg -------------------------------------------------------------------------------- /assets/music/FISHSTICKS.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/FISHSTICKS.ogg-c2e1fad11bc2d9fd8d7454aee15cd469.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/music/FISHSTICKS.ogg" 10 | dest_files=[ "res://.import/FISHSTICKS.ogg-c2e1fad11bc2d9fd8d7454aee15cd469.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=false 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /assets/music/THEME #1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/music/THEME #1.ogg -------------------------------------------------------------------------------- /assets/music/THEME #1.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/THEME #1.ogg-b80a0ede0dcb7636e27ca389f9742baa.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/music/THEME #1.ogg" 10 | dest_files=[ "res://.import/THEME #1.ogg-b80a0ede0dcb7636e27ca389f9742baa.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=false 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /assets/screenshots/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/screenshots/.gdignore -------------------------------------------------------------------------------- /assets/screenshots/gameplay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/screenshots/gameplay.gif -------------------------------------------------------------------------------- /assets/sounds/blops/blop1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/blops/blop1.wav -------------------------------------------------------------------------------- /assets/sounds/blops/blop1.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/blop1.wav-7625e077c5c477be71d35de79ea10b36.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/blops/blop1.wav" 10 | dest_files=[ "res://.import/blop1.wav-7625e077c5c477be71d35de79ea10b36.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/blops/blop2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/blops/blop2.wav -------------------------------------------------------------------------------- /assets/sounds/blops/blop2.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/blop2.wav-d1941dfaaadfb48066e0c478f897c0c6.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/blops/blop2.wav" 10 | dest_files=[ "res://.import/blop2.wav-d1941dfaaadfb48066e0c478f897c0c6.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/blops/blop3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/blops/blop3.wav -------------------------------------------------------------------------------- /assets/sounds/blops/blop3.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/blop3.wav-f05135c9cfe057d3f1b02143e1de5ff9.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/blops/blop3.wav" 10 | dest_files=[ "res://.import/blop3.wav-f05135c9cfe057d3f1b02143e1de5ff9.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/empty.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/empty.wav -------------------------------------------------------------------------------- /assets/sounds/empty.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/empty.wav-6b0840cb1fbbfea69e27d284863b437d.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/empty.wav" 10 | dest_files=[ "res://.import/empty.wav-6b0840cb1fbbfea69e27d284863b437d.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/explosions/explosion1.wav -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion1.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/explosion1.wav-76696ccffa7536334aaccb0f88254a47.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/explosions/explosion1.wav" 10 | dest_files=[ "res://.import/explosion1.wav-76696ccffa7536334aaccb0f88254a47.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/explosions/explosion2.wav -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion2.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/explosion2.wav-dc9f812075c900d766fc4d8a422d2fc0.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/explosions/explosion2.wav" 10 | dest_files=[ "res://.import/explosion2.wav-dc9f812075c900d766fc4d8a422d2fc0.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/explosions/explosion3.wav -------------------------------------------------------------------------------- /assets/sounds/explosions/explosion3.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/explosion3.wav-d4afab963afbe904f19ea588c4f0b168.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/explosions/explosion3.wav" 10 | dest_files=[ "res://.import/explosion3.wav-d4afab963afbe904f19ea588c4f0b168.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/hurt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/hurt.wav -------------------------------------------------------------------------------- /assets/sounds/hurt.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/hurt.wav-792baeb99505afd6a1496d4e4330b023.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/hurt.wav" 10 | dest_files=[ "res://.import/hurt.wav-792baeb99505afd6a1496d4e4330b023.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/jump.wav -------------------------------------------------------------------------------- /assets/sounds/jump.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/jump.wav-395b727cde98999423d5c020c9c3492f.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/jump.wav" 10 | dest_files=[ "res://.import/jump.wav-395b727cde98999423d5c020c9c3492f.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/pickup.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/pickup.wav -------------------------------------------------------------------------------- /assets/sounds/pickup.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/pickup.wav-3b75f72f152acde0eb387e9a39656c4e.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/pickup.wav" 10 | dest_files=[ "res://.import/pickup.wav-3b75f72f152acde0eb387e9a39656c4e.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/shoot.ogg -------------------------------------------------------------------------------- /assets/sounds/shoot.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/shoot.ogg-0cfea5cf82907ca1a24632f88dfdc8bf.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/shoot.ogg" 10 | dest_files=[ "res://.import/shoot.ogg-0cfea5cf82907ca1a24632f88dfdc8bf.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=false 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /assets/sounds/sword.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/sword.wav -------------------------------------------------------------------------------- /assets/sounds/sword.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/sword.wav-fb5c9c2bd55c59c007aafc8b0e334c9c.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/sword.wav" 10 | dest_files=[ "res://.import/sword.wav-fb5c9c2bd55c59c007aafc8b0e334c9c.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sounds/throw.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sounds/throw.wav -------------------------------------------------------------------------------- /assets/sounds/throw.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/throw.wav-094e269cf2214f666490b29dcc2ed82a.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://assets/sounds/throw.wav" 10 | dest_files=[ "res://.import/throw.wav-094e269cf2214f666490b29dcc2ed82a.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop_mode=0 21 | edit/loop_begin=0 22 | edit/loop_end=-1 23 | compress/mode=0 24 | -------------------------------------------------------------------------------- /assets/sprites/decorations1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/decorations1.png -------------------------------------------------------------------------------- /assets/sprites/decorations1.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/decorations1.png-d506e0e9135ed9c88ffb4e89f209a52d.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/decorations1.png" 13 | dest_files=[ "res://.import/decorations1.png-d506e0e9135ed9c88ffb4e89f209a52d.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/generator_flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/generator_flat.png -------------------------------------------------------------------------------- /assets/sprites/generator_flat.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/generator_flat.png-6259a30260920bb39ec5a42d30cd25a4.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/generator_flat.png" 13 | dest_files=[ "res://.import/generator_flat.png-6259a30260920bb39ec5a42d30cd25a4.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/gun.png -------------------------------------------------------------------------------- /assets/sprites/gun.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/gun.png-a2c399b8b30a04443c2f961b3b922c4a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/gun.png" 13 | dest_files=[ "res://.import/gun.png-a2c399b8b30a04443c2f961b3b922c4a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/sword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/sword.png -------------------------------------------------------------------------------- /assets/sprites/sword.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/sword.png-0795e672da260389e06ed26faa9351bc.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/sword.png" 13 | dest_files=[ "res://.import/sword.png-0795e672da260389e06ed26faa9351bc.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/whale_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/whale_blue.png -------------------------------------------------------------------------------- /assets/sprites/whale_blue.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/whale_blue.png-7cfa8b6ba0d3b927caf0b7cc3447aca8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/whale_blue.png" 13 | dest_files=[ "res://.import/whale_blue.png-7cfa8b6ba0d3b927caf0b7cc3447aca8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/whale_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/whale_green.png -------------------------------------------------------------------------------- /assets/sprites/whale_green.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/whale_green.png-1ab6489f6ea7fa6c0d807ffaa636f158.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/whale_green.png" 13 | dest_files=[ "res://.import/whale_green.png-1ab6489f6ea7fa6c0d807ffaa636f158.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/whale_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/whale_orange.png -------------------------------------------------------------------------------- /assets/sprites/whale_orange.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/whale_orange.png-7710559247461bbb7f146e22b88110ce.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/whale_orange.png" 13 | dest_files=[ "res://.import/whale_orange.png-7710559247461bbb7f146e22b88110ce.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/sprites/whale_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/sprites/whale_purple.png -------------------------------------------------------------------------------- /assets/sprites/whale_purple.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/whale_purple.png-b6540875fda61eecb9324c0cf8f8a9fb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/sprites/whale_purple.png" 13 | dest_files=[ "res://.import/whale_purple.png-b6540875fda61eecb9324c0cf8f8a9fb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://assets/fonts/monogram.tres" type="DynamicFont" id=1] 4 | 5 | [resource] 6 | default_font = ExtResource( 1 ) 7 | CheckBox/colors/font_color = Color( 0.88, 0.88, 0.88, 1 ) 8 | CheckBox/colors/font_color_disabled = Color( 0.9, 0.9, 0.9, 0.2 ) 9 | CheckBox/colors/font_color_hover = Color( 0.94, 0.94, 0.94, 1 ) 10 | CheckBox/colors/font_color_hover_pressed = Color( 1, 1, 1, 1 ) 11 | CheckBox/colors/font_color_pressed = Color( 1, 1, 1, 1 ) 12 | CheckBox/constants/ = 0 13 | CheckBox/constants/check_vadjust = 3 14 | CheckBox/constants/hseparation = 15 15 | CheckBox/fonts/font = null 16 | CheckBox/icons/checked = null 17 | CheckBox/icons/radio_checked = null 18 | CheckBox/icons/radio_unchecked = null 19 | CheckBox/icons/unchecked = null 20 | CheckBox/styles/disabled = null 21 | CheckBox/styles/focus = null 22 | CheckBox/styles/hover = null 23 | CheckBox/styles/hover_pressed = null 24 | CheckBox/styles/normal = null 25 | CheckBox/styles/pressed = null 26 | -------------------------------------------------------------------------------- /assets/tilesets/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/tilesets/template.png -------------------------------------------------------------------------------- /assets/tilesets/template.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/template.png-2f254982dea86c4da79658a2e6fa6202.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/tilesets/template.png" 13 | dest_files=[ "res://.import/template.png-2f254982dea86c4da79658a2e6fa6202.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/tilesets/template.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="TileSet" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://assets/tilesets/template.png" type="Texture" id=1] 4 | 5 | 6 | [resource] 7 | 0/name = "template.png 0" 8 | 0/texture = ExtResource( 1 ) 9 | 0/tex_offset = Vector2( 0, 0 ) 10 | 0/modulate = Color( 1, 1, 1, 1 ) 11 | 0/region = Rect2( 0, 0, 222, 222 ) 12 | 0/tile_mode = 1 13 | 0/autotile/bitmask_mode = 1 14 | 0/autotile/bitmask_flags = [ Vector2( 0, 0 ), 432, Vector2( 0, 1 ), 438, Vector2( 0, 2 ), 54, Vector2( 0, 3 ), 48, Vector2( 0, 4 ), 182, Vector2( 0, 5 ), 434, Vector2( 1, 0 ), 504, Vector2( 1, 1 ), 511, Vector2( 1, 2 ), 63, Vector2( 1, 3 ), 56, Vector2( 1, 4 ), 155, Vector2( 1, 5 ), 218, Vector2( 2, 0 ), 216, Vector2( 2, 1 ), 219, Vector2( 2, 2 ), 27, Vector2( 2, 3 ), 24, Vector2( 2, 4 ), 248, Vector2( 2, 5 ), 59, Vector2( 2, 6 ), 443, Vector2( 3, 0 ), 144, Vector2( 3, 1 ), 146, Vector2( 3, 2 ), 18, Vector2( 3, 3 ), 16, Vector2( 3, 4 ), 440, Vector2( 3, 5 ), 62, Vector2( 3, 6 ), 254, Vector2( 4, 0 ), 255, Vector2( 4, 1 ), 507, Vector2( 4, 2 ), 250, Vector2( 4, 3 ), 187, Vector2( 4, 4 ), 176, Vector2( 4, 5 ), 178, Vector2( 4, 6 ), 50, Vector2( 5, 0 ), 447, Vector2( 5, 1 ), 510, Vector2( 5, 2 ), 190, Vector2( 5, 3 ), 442, Vector2( 5, 4 ), 184, Vector2( 5, 5 ), 186, Vector2( 5, 6 ), 58, Vector2( 6, 0 ), 506, Vector2( 6, 1 ), 446, Vector2( 6, 2 ), 251, Vector2( 6, 3 ), 191, Vector2( 6, 4 ), 152, Vector2( 6, 5 ), 154, Vector2( 6, 6 ), 26 ] 15 | 0/autotile/icon_coordinate = Vector2( 0, 0 ) 16 | 0/autotile/tile_size = Vector2( 30, 30 ) 17 | 0/autotile/spacing = 2 18 | 0/autotile/occluder_map = [ ] 19 | 0/autotile/navpoly_map = [ ] 20 | 0/autotile/priority_map = [ ] 21 | 0/autotile/z_index_map = [ ] 22 | 0/occluder_offset = Vector2( 0, 0 ) 23 | 0/navigation_offset = Vector2( 0, 0 ) 24 | 0/shape_offset = Vector2( 0, 0 ) 25 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 26 | 0/shape_one_way = false 27 | 0/shape_one_way_margin = 0.0 28 | 0/shapes = [ ] 29 | 0/z_index = 0 30 | -------------------------------------------------------------------------------- /assets/tilesets/tileset1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/tilesets/tileset1.png -------------------------------------------------------------------------------- /assets/tilesets/tileset1.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/tileset1.png-a6bc1116f3758d976341faaa82531ca5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/tilesets/tileset1.png" 13 | dest_files=[ "res://.import/tileset1.png-a6bc1116f3758d976341faaa82531ca5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/ui/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/ui/close.png -------------------------------------------------------------------------------- /assets/ui/close.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/close.png-76ad351b77a5cd8dfb1d712106bc5926.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/ui/close.png" 13 | dest_files=[ "res://.import/close.png-76ad351b77a5cd8dfb1d712106bc5926.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /assets/ui/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/assets/ui/mute.png -------------------------------------------------------------------------------- /assets/ui/mute.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/mute.png-affa619231c60846d41cdbde984393d2.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://assets/ui/mute.png" 13 | dest_files=[ "res://.import/mute.png-affa619231c60846d41cdbde984393d2.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /autoload/Build.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ##### 4 | # NOTE: This file is replaced by the build system. DO NOT EDIT! 5 | ##### 6 | -------------------------------------------------------------------------------- /autoload/GameState.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var online_play := false 4 | 5 | -------------------------------------------------------------------------------- /autoload/Online.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | # For developers to set from the outside, for example: 4 | # Online.nakama_host = 'nakama.example.com' 5 | # Online.nakama_scheme = 'https' 6 | var nakama_server_key: String = 'defaultkey' 7 | var nakama_host: String = 'localhost' 8 | var nakama_port: int = 7350 9 | var nakama_scheme: String = 'http' 10 | 11 | # For other scripts to access: 12 | var nakama_client: NakamaClient setget _set_readonly_variable, get_nakama_client 13 | var nakama_session: NakamaSession setget set_nakama_session 14 | var nakama_socket: NakamaSocket setget _set_readonly_variable 15 | 16 | # Internal variable for initializing the socket. 17 | var _nakama_socket_connecting := false 18 | 19 | signal session_changed (nakama_session) 20 | signal session_connected (nakama_session) 21 | signal socket_connected (nakama_socket) 22 | 23 | func _set_readonly_variable(_value) -> void: 24 | pass 25 | 26 | func _ready() -> void: 27 | # Don't stop processing messages from Nakama when the game is paused. 28 | Nakama.pause_mode = Node.PAUSE_MODE_PROCESS 29 | 30 | func get_nakama_client() -> NakamaClient: 31 | if nakama_client == null: 32 | nakama_client = Nakama.create_client( 33 | nakama_server_key, 34 | nakama_host, 35 | nakama_port, 36 | nakama_scheme, 37 | Nakama.DEFAULT_TIMEOUT, 38 | NakamaLogger.LOG_LEVEL.ERROR) 39 | 40 | return nakama_client 41 | 42 | func set_nakama_session(_nakama_session: NakamaSession) -> void: 43 | # Close out the old socket. 44 | if nakama_socket: 45 | nakama_socket.close() 46 | nakama_socket = null 47 | 48 | nakama_session = _nakama_session 49 | 50 | emit_signal("session_changed", nakama_session) 51 | 52 | if nakama_session and not nakama_session.is_exception() and not nakama_session.is_expired(): 53 | emit_signal("session_connected", nakama_session) 54 | 55 | func connect_nakama_socket() -> void: 56 | if nakama_socket != null: 57 | return 58 | if _nakama_socket_connecting: 59 | return 60 | _nakama_socket_connecting = true 61 | 62 | var new_socket = Nakama.create_socket_from(nakama_client) 63 | yield(new_socket.connect_async(nakama_session), "completed") 64 | nakama_socket = new_socket 65 | _nakama_socket_connecting = false 66 | 67 | emit_signal("socket_connected", nakama_socket) 68 | 69 | func is_nakama_socket_connected() -> bool: 70 | return nakama_socket != null && nakama_socket.is_connected_to_host() 71 | -------------------------------------------------------------------------------- /autoload/Util.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func find_unique_name(parent: Node, prefix: String = '') -> String: 4 | var name: String 5 | while true: 6 | name = random_name(prefix) 7 | if not parent.has_node(name): 8 | break 9 | return name 10 | 11 | func random_name(prefix: String) -> String: 12 | return prefix + str(randi()) 13 | -------------------------------------------------------------------------------- /build/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/build/.gdignore -------------------------------------------------------------------------------- /components/Hitbox.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | export (bool) var disabled := false setget set_disabled 4 | 5 | func _ready() -> void: 6 | _update_disabled() 7 | 8 | func set_disabled(_disabled: bool) -> void: 9 | if disabled != _disabled: 10 | disabled = _disabled 11 | _update_disabled() 12 | 13 | func _update_disabled() -> void: 14 | for child in get_children(): 15 | if child is CollisionShape2D or child is CollisionPolygon2D: 16 | child.set_deferred("disabled", disabled) 17 | 18 | func _on_body_entered(body: Node) -> void: 19 | if not disabled and body.has_method('hurt'): 20 | if not GameState.online_play or OnlineMatch.is_network_master_for_node(body): 21 | body.hurt(self) 22 | -------------------------------------------------------------------------------- /components/Hitbox.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://components/Hitbox.gd" type="Script" id=1] 4 | 5 | [node name="Hitbox" type="Area2D"] 6 | monitorable = false 7 | collision_layer = 0 8 | collision_mask = 0 9 | script = ExtResource( 1 ) 10 | [connection signal="body_entered" from="." to="." method="_on_body_entered"] 11 | -------------------------------------------------------------------------------- /components/InputBuffer.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | var action_prefix := '' 4 | var actions := [] 5 | var buffer := {} 6 | 7 | enum ActionType { 8 | PRESSED, 9 | JUST_PRESSED, 10 | JUST_RELEASED, 11 | STRENGTH, 12 | } 13 | 14 | func _init(_actions: Array, _action_prefix: String = '') -> void: 15 | action_prefix = _action_prefix 16 | actions = _actions 17 | 18 | func update_local() -> bool: 19 | var new_buffer := {} 20 | for action in actions: 21 | new_buffer[action] = { 22 | ActionType.PRESSED: Input.is_action_pressed(action_prefix + action), 23 | ActionType.JUST_PRESSED: Input.is_action_just_pressed(action_prefix + action), 24 | ActionType.JUST_RELEASED: Input.is_action_just_released(action_prefix + action), 25 | ActionType.STRENGTH: Input.get_action_strength(action_prefix + action), 26 | } 27 | var changed: bool = new_buffer.hash() != buffer.hash() 28 | buffer = new_buffer 29 | return changed 30 | 31 | func predict_next_frame() -> void: 32 | for action in buffer: 33 | buffer[action][ActionType.JUST_PRESSED] = false 34 | buffer[action][ActionType.JUST_RELEASED] = false 35 | 36 | func is_action_pressed(action) -> bool: 37 | if not buffer.has(action): 38 | return false 39 | return buffer[action][ActionType.PRESSED] 40 | 41 | func is_action_just_pressed(action) -> bool: 42 | if not buffer.has(action): 43 | return false 44 | return buffer[action][ActionType.JUST_PRESSED] 45 | 46 | func is_action_just_released(action) -> bool: 47 | if not buffer.has(action): 48 | return false 49 | return buffer[action][ActionType.JUST_RELEASED] 50 | 51 | func get_action_strength(action) -> float: 52 | if not buffer.has(action): 53 | return 0.0 54 | return buffer[action][ActionType.STRENGTH] 55 | -------------------------------------------------------------------------------- /components/Sounds.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func play(name: String): 4 | var node = get_node(name) 5 | assert(node != null, "No sound with name " + name) 6 | 7 | if node is AudioStreamPlayer: 8 | node.play() 9 | return node 10 | elif node is Node: 11 | var players = [] 12 | for child in node.get_children(): 13 | if child is AudioStreamPlayer: 14 | players.append(child) 15 | players.shuffle() 16 | players[0].play() 17 | return players[0] 18 | -------------------------------------------------------------------------------- /default_bus_layout.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AudioBusLayout" format=2] 2 | 3 | [resource] 4 | bus/1/name = "Music" 5 | bus/1/solo = false 6 | bus/1/mute = false 7 | bus/1/bypass_fx = false 8 | bus/1/volume_db = 0.0 9 | bus/1/send = "Master" 10 | bus/2/name = "Sound Effects" 11 | bus/2/solo = false 12 | bus/2/mute = false 13 | bus/2/bypass_fx = false 14 | bus/2/volume_db = 0.0 15 | bus/2/send = "Master" 16 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cockroachdb: 4 | image: cockroachdb/cockroach:v19.2.5 5 | command: start --insecure --store=attrs=ssd,path=/var/lib/cockroach/ 6 | restart: always 7 | volumes: 8 | - data:/var/lib/cockroach 9 | expose: 10 | - "8080" 11 | - "26257" 12 | ports: 13 | - "26257:26257" 14 | - "8080:8080" 15 | nakama: 16 | image: heroiclabs/nakama:2.15.0 17 | entrypoint: 18 | - "/bin/sh" 19 | - "-ecx" 20 | - > 21 | /nakama/nakama migrate up --database.address root@cockroachdb:26257 && 22 | exec /nakama/nakama --name nakama1 --database.address root@cockroachdb:26257 23 | restart: always 24 | links: 25 | - "cockroachdb:db" 26 | depends_on: 27 | - cockroachdb 28 | volumes: 29 | - ./nakama/data:/nakama/data 30 | expose: 31 | - "7349" 32 | - "7350" 33 | - "7351" 34 | ports: 35 | - "7349:7349" 36 | - "7350:7350" 37 | - "7351:7351" 38 | healthcheck: 39 | test: ["CMD", "curl", "-f", "http://localhost:7350/"] 40 | interval: 10s 41 | timeout: 5s 42 | retries: 5 43 | volumes: 44 | data: {} 45 | 46 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Windows Desktop" 4 | platform="Windows Desktop" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | export_path="build/windows/fish-game.exe" 11 | patch_list=PoolStringArray( ) 12 | script_export_mode=1 13 | script_encryption_key="" 14 | 15 | [preset.0.options] 16 | 17 | texture_format/bptc=false 18 | texture_format/s3tc=true 19 | texture_format/etc=false 20 | texture_format/etc2=false 21 | texture_format/no_bptc_fallbacks=true 22 | binary_format/64_bits=true 23 | binary_format/embed_pck=false 24 | custom_template/release="" 25 | custom_template/debug="" 26 | codesign/enable=false 27 | codesign/identity="" 28 | codesign/password="" 29 | codesign/timestamp=true 30 | codesign/timestamp_server_url="" 31 | codesign/digest_algorithm=1 32 | codesign/description="" 33 | codesign/custom_options=PoolStringArray( ) 34 | application/icon="" 35 | application/file_version="" 36 | application/product_version="" 37 | application/company_name="" 38 | application/product_name="" 39 | application/file_description="" 40 | application/copyright="" 41 | application/trademarks="" 42 | 43 | [preset.1] 44 | 45 | name="Linux/X11" 46 | platform="Linux/X11" 47 | runnable=true 48 | custom_features="" 49 | export_filter="all_resources" 50 | include_filter="" 51 | exclude_filter="" 52 | export_path="build/linux/fish-game.x86_64" 53 | patch_list=PoolStringArray( ) 54 | script_export_mode=1 55 | script_encryption_key="" 56 | 57 | [preset.1.options] 58 | 59 | texture_format/bptc=false 60 | texture_format/s3tc=true 61 | texture_format/etc=false 62 | texture_format/etc2=false 63 | texture_format/no_bptc_fallbacks=true 64 | binary_format/64_bits=true 65 | binary_format/embed_pck=false 66 | custom_template/release="" 67 | custom_template/debug="" 68 | 69 | [preset.2] 70 | 71 | name="Mac OSX" 72 | platform="Mac OSX" 73 | runnable=true 74 | custom_features="" 75 | export_filter="all_resources" 76 | include_filter="" 77 | exclude_filter="" 78 | export_path="build/macosx/fish-game.zip" 79 | patch_list=PoolStringArray( ) 80 | script_export_mode=1 81 | script_encryption_key="" 82 | 83 | [preset.2.options] 84 | 85 | custom_template/debug="" 86 | custom_template/release="" 87 | application/name="" 88 | application/info="Made with Godot Engine" 89 | application/icon="" 90 | application/identifier="" 91 | application/signature="" 92 | application/short_version="1.0" 93 | application/version="1.0" 94 | application/copyright="" 95 | display/high_res=false 96 | privacy/camera_usage_description="" 97 | privacy/microphone_usage_description="" 98 | texture_format/s3tc=true 99 | texture_format/etc=false 100 | texture_format/etc2=false 101 | 102 | [preset.3] 103 | 104 | name="HTML5" 105 | platform="HTML5" 106 | runnable=true 107 | custom_features="" 108 | export_filter="all_resources" 109 | include_filter="" 110 | exclude_filter="" 111 | export_path="build/web/index.html" 112 | patch_list=PoolStringArray( ) 113 | script_export_mode=1 114 | script_encryption_key="" 115 | 116 | [preset.3.options] 117 | 118 | vram_texture_compression/for_desktop=true 119 | vram_texture_compression/for_mobile=false 120 | html/custom_html_shell="" 121 | html/head_include="" 122 | custom_template/release="" 123 | custom_template/debug="" 124 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/gamecom-fishgame-godot/00c324ede97af7c7eaea5176b6a0d155392a5f65/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /main/Music.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | export (float) var cross_fade_duration = 2.0 4 | 5 | signal song_finished (song) 6 | 7 | onready var tween = $Tween 8 | 9 | var current_song 10 | var initial_volume_dbs := {} 11 | 12 | func _ready() -> void: 13 | for child in get_children(): 14 | if child is AudioStreamPlayer: 15 | initial_volume_dbs[child.name] = child.volume_db 16 | child.connect("finished", self, "_on_song_finished", [child]) 17 | 18 | func play(song_name: String) -> void: 19 | var next_song = get_node(song_name) 20 | if !next_song or next_song.playing: 21 | return 22 | 23 | if current_song: 24 | tween.interpolate_property(current_song, "volume_db", current_song.volume_db, -40.0, (cross_fade_duration / 2.0), Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) 25 | 26 | next_song.play() 27 | tween.interpolate_property(next_song, "volume_db", -40.0, initial_volume_dbs.get(next_song.name, 0.0), (cross_fade_duration / 2.0), Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) 28 | 29 | current_song = next_song 30 | tween.start() 31 | 32 | func play_random() -> void: 33 | if get_child_count() == 1: 34 | return 35 | 36 | var next_song: Node 37 | while next_song == null or current_song == next_song: 38 | next_song = _pick_random() 39 | 40 | play(next_song.name) 41 | 42 | func _pick_random() -> Node: 43 | return get_child(randi() % (get_child_count() - 1)) 44 | 45 | func _on_song_finished(song) -> void: 46 | emit_signal("song_finished", song) 47 | 48 | func _on_Tween_tween_completed(object: Object, key: NodePath) -> void: 49 | if object != current_song: 50 | object.stop() 51 | 52 | -------------------------------------------------------------------------------- /main/Screen.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | var ui_layer: UILayer 4 | 5 | func _setup_screen(_ui_layer: UILayer) -> void: 6 | ui_layer = _ui_layer 7 | 8 | func _show_screen(_info: Dictionary = {}) -> void: 9 | pass 10 | 11 | func _hide_screen() -> void: 12 | pass 13 | -------------------------------------------------------------------------------- /main/UILayer.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | class_name UILayer 3 | 4 | onready var screens = $Screens 5 | onready var message_label = $Overlay/Message 6 | onready var back_button = $Overlay/BackButton 7 | 8 | signal change_screen (name, screen) 9 | signal back_button () 10 | 11 | var current_screen: Control = null setget _set_readonly_variable 12 | var current_screen_name: String = '' setget _set_readonly_variable, get_current_screen_name 13 | 14 | var _is_ready := false 15 | 16 | func _set_readonly_variable(_value) -> void: 17 | pass 18 | 19 | func _ready() -> void: 20 | for screen in screens.get_children(): 21 | if screen.has_method('_setup_screen'): 22 | screen._setup_screen(self) 23 | 24 | show_screen("TitleScreen") 25 | _is_ready = true 26 | 27 | func get_current_screen_name() -> String: 28 | if current_screen: 29 | return current_screen.name 30 | return '' 31 | 32 | func show_screen(name: String, info: Dictionary = {}) -> void: 33 | var screen = screens.get_node(name) 34 | if not screen: 35 | return 36 | 37 | hide_screen() 38 | screen.visible = true 39 | if screen.has_method("_show_screen"): 40 | screen.callv("_show_screen", [info]) 41 | current_screen = screen 42 | 43 | if _is_ready: 44 | emit_signal("change_screen", name, screen) 45 | 46 | func hide_screen() -> void: 47 | if current_screen and current_screen.has_method('_hide_screen'): 48 | current_screen._hide_screen() 49 | 50 | for screen in screens.get_children(): 51 | screen.visible = false 52 | current_screen = null 53 | 54 | func show_message(text: String) -> void: 55 | message_label.text = text 56 | message_label.visible = true 57 | 58 | func hide_message() -> void: 59 | message_label.visible = false 60 | 61 | func show_back_button() -> void: 62 | back_button.visible = true 63 | 64 | func hide_back_button() -> void: 65 | back_button.visible = false 66 | 67 | func hide_all() -> void: 68 | hide_screen() 69 | hide_message() 70 | hide_back_button() 71 | 72 | func _on_BackButton_pressed() -> void: 73 | emit_signal("back_button") 74 | 75 | func _on_MuteButton_toggled(button_pressed: bool) -> void: 76 | AudioServer.set_bus_mute(0, button_pressed) 77 | -------------------------------------------------------------------------------- /main/screens/ConnectionScreen.gd: -------------------------------------------------------------------------------- 1 | extends "res://main/Screen.gd" 2 | 3 | onready var tab_container := $TabContainer 4 | onready var login_email_field := $TabContainer/Login/GridContainer/Email 5 | onready var login_password_field := $TabContainer/Login/GridContainer/Password 6 | 7 | const CREDENTIALS_FILENAME = 'user://credentials.json' 8 | 9 | var email: String = '' 10 | var password: String = '' 11 | 12 | var _reconnect: bool = false 13 | var _next_screen 14 | 15 | func _ready() -> void: 16 | var file = File.new() 17 | if file.file_exists(CREDENTIALS_FILENAME): 18 | file.open(CREDENTIALS_FILENAME, File.READ) 19 | var result := JSON.parse(file.get_as_text()) 20 | if result.result is Dictionary: 21 | email = result.result['email'] 22 | password = result.result['password'] 23 | login_email_field.text = email 24 | login_password_field.text = password 25 | file.close() 26 | 27 | func _save_credentials() -> void: 28 | var file = File.new() 29 | file.open(CREDENTIALS_FILENAME, File.WRITE) 30 | var credentials = { 31 | email = email, 32 | password = password, 33 | } 34 | file.store_line(JSON.print(credentials)) 35 | file.close() 36 | 37 | func _show_screen(info: Dictionary = {}) -> void: 38 | _reconnect = info.get('reconnect', false) 39 | _next_screen = info.get('next_screen', 'MatchScreen') 40 | 41 | tab_container.current_tab = 0 42 | 43 | # If we have a stored email and password, attempt to login straight away. 44 | if email != '' and password != '': 45 | do_login() 46 | 47 | func do_login(save_credentials: bool = false) -> void: 48 | visible = false 49 | 50 | if _reconnect: 51 | ui_layer.show_message("Session expired! Reconnecting...") 52 | else: 53 | ui_layer.show_message("Logging in...") 54 | 55 | var nakama_session = yield(Online.nakama_client.authenticate_email_async(email, password, null, false), "completed") 56 | 57 | if nakama_session.is_exception(): 58 | visible = true 59 | ui_layer.show_message("Login failed!") 60 | 61 | # Clear stored email and password, but leave the fields alone so the 62 | # user can attempt to correct them. 63 | email = '' 64 | password = '' 65 | 66 | # We always set Online.nakama_session in case something is yielding 67 | # on the "session_changed" signal. 68 | Online.nakama_session = null 69 | else: 70 | if save_credentials: 71 | _save_credentials() 72 | Online.nakama_session = nakama_session 73 | ui_layer.hide_message() 74 | 75 | if _next_screen: 76 | ui_layer.show_screen(_next_screen) 77 | 78 | func _on_LoginButton_pressed() -> void: 79 | email = login_email_field.text.strip_edges() 80 | password = login_password_field.text.strip_edges() 81 | do_login($TabContainer/Login/GridContainer/SaveCheckBox.pressed) 82 | 83 | func _on_CreateAccountButton_pressed() -> void: 84 | email = $"TabContainer/Create Account/GridContainer/Email".text.strip_edges() 85 | password = $"TabContainer/Create Account/GridContainer/Password".text.strip_edges() 86 | 87 | var username = $"TabContainer/Create Account/GridContainer/Username".text.strip_edges() 88 | var save_credentials = $"TabContainer/Create Account/GridContainer/SaveCheckBox".pressed 89 | 90 | if email == '': 91 | ui_layer.show_message("Must provide email") 92 | return 93 | if password == '': 94 | ui_layer.show_message("Must provide password") 95 | return 96 | if username == '': 97 | ui_layer.show_message("Must provide username") 98 | return 99 | 100 | visible = false 101 | ui_layer.show_message("Creating account...") 102 | 103 | var nakama_session = yield(Online.nakama_client.authenticate_email_async(email, password, username, true), "completed") 104 | 105 | if nakama_session.is_exception(): 106 | visible = true 107 | 108 | var msg = nakama_session.get_exception().message 109 | # Nakama treats registration as logging in, so this is what we get if the 110 | # the email is already is use but the password is wrong. 111 | if msg == 'Invalid credentials.': 112 | msg = 'E-mail already in use.' 113 | elif msg == '': 114 | msg = "Unable to create account" 115 | ui_layer.show_message(msg) 116 | 117 | # We always set Online.nakama_session in case something is yielding 118 | # on the "session_changed" signal. 119 | Online.nakama_session = null 120 | else: 121 | if save_credentials: 122 | _save_credentials() 123 | Online.nakama_session = nakama_session 124 | ui_layer.hide_message() 125 | ui_layer.show_screen("MatchScreen") 126 | -------------------------------------------------------------------------------- /main/screens/ConnectionScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://assets/theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://assets/fonts/monogram.ttf" type="DynamicFontData" id=2] 5 | [ext_resource path="res://assets/fonts/monogram.tres" type="DynamicFont" id=3] 6 | [ext_resource path="res://main/screens/ConnectionScreen.gd" type="Script" id=4] 7 | 8 | [sub_resource type="DynamicFont" id=1] 9 | size = 36 10 | font_data = ExtResource( 2 ) 11 | 12 | [node name="ConnectionScreen" type="Control"] 13 | anchor_right = 1.0 14 | anchor_bottom = 1.0 15 | theme = ExtResource( 1 ) 16 | script = ExtResource( 4 ) 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | 21 | [node name="TabContainer" type="TabContainer" parent="."] 22 | anchor_right = 1.0 23 | anchor_bottom = 1.0 24 | margin_left = 50.0 25 | margin_top = 50.0 26 | margin_right = -50.0 27 | margin_bottom = -50.0 28 | __meta__ = { 29 | "_edit_use_anchors_": false 30 | } 31 | 32 | [node name="Login" type="Control" parent="TabContainer"] 33 | anchor_right = 1.0 34 | anchor_bottom = 1.0 35 | margin_left = 4.0 36 | margin_top = 42.0 37 | margin_right = -4.0 38 | margin_bottom = -4.0 39 | 40 | [node name="GridContainer" type="GridContainer" parent="TabContainer/Login"] 41 | anchor_left = 0.5 42 | anchor_top = 0.5 43 | anchor_right = 0.5 44 | anchor_bottom = 0.5 45 | margin_left = -266.0 46 | margin_top = -75.0 47 | margin_right = 266.0 48 | margin_bottom = 9.0 49 | columns = 2 50 | __meta__ = { 51 | "_edit_use_anchors_": false 52 | } 53 | 54 | [node name="EmailLabel" type="Label" parent="TabContainer/Login/GridContainer"] 55 | margin_top = 5.0 56 | margin_right = 99.0 57 | margin_bottom = 29.0 58 | custom_fonts/font = ExtResource( 3 ) 59 | text = "E-mail:" 60 | 61 | [node name="Email" type="LineEdit" parent="TabContainer/Login/GridContainer"] 62 | margin_left = 103.0 63 | margin_right = 532.0 64 | margin_bottom = 34.0 65 | size_flags_horizontal = 3 66 | custom_fonts/font = ExtResource( 3 ) 67 | caret_blink = true 68 | 69 | [node name="PasswordLabel" type="Label" parent="TabContainer/Login/GridContainer"] 70 | margin_top = 43.0 71 | margin_right = 99.0 72 | margin_bottom = 67.0 73 | custom_fonts/font = ExtResource( 3 ) 74 | text = "Password:" 75 | 76 | [node name="Password" type="LineEdit" parent="TabContainer/Login/GridContainer"] 77 | margin_left = 103.0 78 | margin_top = 38.0 79 | margin_right = 532.0 80 | margin_bottom = 72.0 81 | size_flags_horizontal = 3 82 | custom_fonts/font = ExtResource( 3 ) 83 | secret = true 84 | caret_blink = true 85 | 86 | [node name="Empty" type="Control" parent="TabContainer/Login/GridContainer"] 87 | margin_top = 76.0 88 | margin_right = 99.0 89 | margin_bottom = 113.0 90 | __meta__ = { 91 | "_edit_use_anchors_": false 92 | } 93 | 94 | [node name="SaveCheckBox" type="CheckBox" parent="TabContainer/Login/GridContainer"] 95 | margin_left = 103.0 96 | margin_top = 76.0 97 | margin_right = 532.0 98 | margin_bottom = 113.0 99 | custom_fonts/font = SubResource( 1 ) 100 | pressed = true 101 | text = "Save email and password" 102 | 103 | [node name="LoginButton" type="Button" parent="TabContainer/Login"] 104 | anchor_left = 0.5 105 | anchor_top = 1.0 106 | anchor_right = 0.5 107 | anchor_bottom = 1.0 108 | margin_left = -33.5 109 | margin_top = -50.0 110 | margin_right = 33.5 111 | text = "Login" 112 | __meta__ = { 113 | "_edit_use_anchors_": false 114 | } 115 | 116 | [node name="Create Account" type="Control" parent="TabContainer"] 117 | visible = false 118 | anchor_right = 1.0 119 | anchor_bottom = 1.0 120 | margin_left = 4.0 121 | margin_top = 42.0 122 | margin_right = -4.0 123 | margin_bottom = -4.0 124 | 125 | [node name="GridContainer" type="GridContainer" parent="TabContainer/Create Account"] 126 | anchor_right = 1.0 127 | anchor_bottom = 1.0 128 | columns = 2 129 | __meta__ = { 130 | "_edit_use_anchors_": false 131 | } 132 | 133 | [node name="UsernameLabel" type="Label" parent="TabContainer/Create Account/GridContainer"] 134 | margin_top = 5.0 135 | margin_right = 99.0 136 | margin_bottom = 29.0 137 | custom_fonts/font = ExtResource( 3 ) 138 | text = "Username:" 139 | 140 | [node name="Username" type="LineEdit" parent="TabContainer/Create Account/GridContainer"] 141 | margin_left = 103.0 142 | margin_right = 532.0 143 | margin_bottom = 34.0 144 | size_flags_horizontal = 3 145 | custom_fonts/font = ExtResource( 3 ) 146 | caret_blink = true 147 | 148 | [node name="EmailLabel" type="Label" parent="TabContainer/Create Account/GridContainer"] 149 | margin_top = 43.0 150 | margin_right = 99.0 151 | margin_bottom = 67.0 152 | custom_fonts/font = ExtResource( 3 ) 153 | text = "E-mail:" 154 | 155 | [node name="Email" type="LineEdit" parent="TabContainer/Create Account/GridContainer"] 156 | margin_left = 103.0 157 | margin_top = 38.0 158 | margin_right = 532.0 159 | margin_bottom = 72.0 160 | size_flags_horizontal = 3 161 | custom_fonts/font = ExtResource( 3 ) 162 | caret_blink = true 163 | 164 | [node name="PasswordLabel" type="Label" parent="TabContainer/Create Account/GridContainer"] 165 | margin_top = 81.0 166 | margin_right = 99.0 167 | margin_bottom = 105.0 168 | custom_fonts/font = ExtResource( 3 ) 169 | text = "Password:" 170 | 171 | [node name="Password" type="LineEdit" parent="TabContainer/Create Account/GridContainer"] 172 | margin_left = 103.0 173 | margin_top = 76.0 174 | margin_right = 532.0 175 | margin_bottom = 110.0 176 | size_flags_horizontal = 3 177 | custom_fonts/font = ExtResource( 3 ) 178 | secret = true 179 | caret_blink = true 180 | 181 | [node name="Empty" type="Control" parent="TabContainer/Create Account/GridContainer"] 182 | margin_top = 114.0 183 | margin_right = 99.0 184 | margin_bottom = 151.0 185 | __meta__ = { 186 | "_edit_use_anchors_": false 187 | } 188 | 189 | [node name="SaveCheckBox" type="CheckBox" parent="TabContainer/Create Account/GridContainer"] 190 | margin_left = 103.0 191 | margin_top = 114.0 192 | margin_right = 532.0 193 | margin_bottom = 151.0 194 | custom_fonts/font = SubResource( 1 ) 195 | pressed = true 196 | text = "Save email and password" 197 | 198 | [node name="CreateAccountButton" type="Button" parent="TabContainer/Create Account"] 199 | anchor_left = 0.5 200 | anchor_top = 1.0 201 | anchor_right = 0.5 202 | anchor_bottom = 1.0 203 | margin_left = -83.0 204 | margin_top = -54.0 205 | margin_right = 83.0 206 | text = "Create Account" 207 | [connection signal="pressed" from="TabContainer/Login/LoginButton" to="." method="_on_LoginButton_pressed"] 208 | [connection signal="pressed" from="TabContainer/Create Account/CreateAccountButton" to="." method="_on_CreateAccountButton_pressed"] 209 | -------------------------------------------------------------------------------- /main/screens/CreditsScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://assets/fonts/monogram.ttf" type="DynamicFontData" id=1] 4 | [ext_resource path="res://assets/theme.tres" type="Theme" id=2] 5 | 6 | [sub_resource type="DynamicFont" id=1] 7 | size = 28 8 | extra_spacing_bottom = 3 9 | font_data = ExtResource( 1 ) 10 | 11 | [sub_resource type="DynamicFont" id=2] 12 | size = 24 13 | font_data = ExtResource( 1 ) 14 | 15 | [node name="CreditsScreen" type="Control"] 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | theme = ExtResource( 2 ) 19 | __meta__ = { 20 | "_edit_use_anchors_": false 21 | } 22 | 23 | [node name="ColorRect" type="ColorRect" parent="."] 24 | anchor_right = 1.0 25 | anchor_bottom = 1.0 26 | color = Color( 0, 0, 0, 1 ) 27 | __meta__ = { 28 | "_edit_use_anchors_": false 29 | } 30 | 31 | [node name="Label" type="Label" parent="."] 32 | anchor_right = 1.0 33 | margin_top = 25.0 34 | margin_bottom = 48.0 35 | text = "Credits" 36 | align = 1 37 | 38 | [node name="RichTextLabel" type="RichTextLabel" parent="."] 39 | anchor_right = 1.0 40 | anchor_bottom = 1.0 41 | margin_left = 15.0 42 | margin_top = 89.0 43 | margin_right = -15.0 44 | margin_bottom = -15.0 45 | custom_fonts/bold_font = SubResource( 1 ) 46 | custom_fonts/normal_font = SubResource( 2 ) 47 | bbcode_enabled = true 48 | bbcode_text = "[b]Programming:[/b] 49 | [color=aqua]David Snopek[/color] from [color=yellow][url=https://www.snopekgames.com]Snopek Games[/url][/color] 50 | 51 | [b]Art:[/b] 52 | [color=aqua]Orlando Herrera[/color] a.k.a. [color=yellow][url=https://pixelfrog-store.itch.io/]Pixel Frog[/url][/color] 53 | 54 | [b]Music/Sound:[/b] 55 | [color=aqua]Jakob T. Rypdal[/color] 56 | 57 | [b]Font:[/b] 58 | [color=yellow][url=https://datagoblin.itch.io/monogram]Monogram[/url][/color] by [color=aqua]Vinícius Menézio[/color]" 59 | text = "Programming: 60 | David Snopek from Snopek Games 61 | 62 | Art: 63 | Orlando Herrera a.k.a. Pixel Frog 64 | 65 | Music/Sound: 66 | Jakob T. Rypdal 67 | 68 | Font: 69 | Monogram by Vinícius Menézio" 70 | scroll_active = false 71 | __meta__ = { 72 | "_edit_use_anchors_": false 73 | } 74 | -------------------------------------------------------------------------------- /main/screens/LeaderboardRecord.gd: -------------------------------------------------------------------------------- 1 | extends HBoxContainer 2 | 3 | func setup(username, score) -> void: 4 | $UsernameLabel.text = username 5 | $WinsLabel.text = str(score) 6 | -------------------------------------------------------------------------------- /main/screens/LeaderboardRecord.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://main/screens/LeaderboardRecord.gd" type="Script" id=2] 4 | 5 | 6 | [node name="HBoxContainer" type="HBoxContainer"] 7 | anchor_right = 1.0 8 | margin_bottom = 14.0 9 | size_flags_horizontal = 3 10 | script = ExtResource( 2 ) 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="Spacer1" type="Control" parent="."] 16 | margin_right = 10.0 17 | margin_bottom = 14.0 18 | rect_min_size = Vector2( 10, 0 ) 19 | 20 | [node name="UsernameLabel" type="Label" parent="."] 21 | margin_left = 14.0 22 | margin_right = 522.0 23 | margin_bottom = 14.0 24 | size_flags_horizontal = 3 25 | text = "Username" 26 | 27 | [node name="WinsLabel" type="Label" parent="."] 28 | margin_left = 526.0 29 | margin_right = 626.0 30 | margin_bottom = 14.0 31 | rect_min_size = Vector2( 100, 0 ) 32 | text = "10" 33 | 34 | [node name="Spacer2" type="Control" parent="."] 35 | margin_left = 630.0 36 | margin_right = 640.0 37 | margin_bottom = 14.0 38 | rect_min_size = Vector2( 10, 0 ) 39 | -------------------------------------------------------------------------------- /main/screens/LeaderboardScreen.gd: -------------------------------------------------------------------------------- 1 | extends "res://main/Screen.gd" 2 | 3 | var LeaderboardRecord = preload("res://main/screens/LeaderboardRecord.tscn") 4 | 5 | onready var record_container = $PanelContainer/VBoxContainer/Panel/ScrollContainer/VBoxContainer 6 | 7 | func _ready() -> void: 8 | clear_records() 9 | 10 | func clear_records() -> void: 11 | for child in record_container.get_children(): 12 | record_container.remove_child(child) 13 | child.queue_free() 14 | 15 | func _show_screen(info: Dictionary = {}) -> void: 16 | ui_layer.hide_message() 17 | 18 | # If our session has expired, show the ConnectionScreen again. 19 | if Online.nakama_session == null or Online.nakama_session.is_expired(): 20 | ui_layer.show_screen("ConnectionScreen", { reconnect = true, next_screen = "LeaderboardScreen" }) 21 | return 22 | 23 | var result: NakamaAPI.ApiLeaderboardRecordList = yield(Online.nakama_client.list_leaderboard_records_async(Online.nakama_session, 'fish_game_wins'), "completed") 24 | if result.is_exception(): 25 | ui_layer.show_message("Unable to retrieve leaderboard") 26 | ui_layer.show_screen("MatchScreen") 27 | 28 | clear_records() 29 | for record in result.records: 30 | var record_node = LeaderboardRecord.instance() 31 | record_container.add_child(record_node) 32 | record_node.setup(record.username, record.score) 33 | -------------------------------------------------------------------------------- /main/screens/LeaderboardScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://assets/theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://main/screens/LeaderboardRecord.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://main/screens/LeaderboardScreen.gd" type="Script" id=3] 6 | 7 | [node name="LeaderboardScreen" type="Control"] 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | margin_left = 3.0 11 | margin_top = -1.0 12 | margin_right = 3.0 13 | margin_bottom = -1.0 14 | theme = ExtResource( 1 ) 15 | script = ExtResource( 3 ) 16 | __meta__ = { 17 | "_edit_use_anchors_": false 18 | } 19 | 20 | [node name="PanelContainer" type="PanelContainer" parent="."] 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | margin_left = 50.0 24 | margin_top = 50.0 25 | margin_right = -50.0 26 | margin_bottom = -50.0 27 | 28 | [node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] 29 | margin_left = 7.0 30 | margin_top = 7.0 31 | margin_right = 533.0 32 | margin_bottom = 253.0 33 | size_flags_horizontal = 3 34 | size_flags_vertical = 3 35 | custom_constants/separation = 10 36 | 37 | [node name="Label" type="Label" parent="PanelContainer/VBoxContainer"] 38 | margin_right = 526.0 39 | margin_bottom = 24.0 40 | text = "Leaderboard" 41 | align = 1 42 | 43 | [node name="Panel" type="Panel" parent="PanelContainer/VBoxContainer"] 44 | margin_top = 34.0 45 | margin_right = 526.0 46 | margin_bottom = 246.0 47 | size_flags_horizontal = 3 48 | size_flags_vertical = 3 49 | 50 | [node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/VBoxContainer/Panel"] 51 | anchor_right = 1.0 52 | anchor_bottom = 1.0 53 | size_flags_horizontal = 3 54 | size_flags_vertical = 3 55 | __meta__ = { 56 | "_edit_use_anchors_": false 57 | } 58 | 59 | [node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer/Panel/ScrollContainer"] 60 | margin_right = 526.0 61 | margin_bottom = 212.0 62 | size_flags_horizontal = 3 63 | size_flags_vertical = 3 64 | 65 | [node name="LeaderboardRecord" parent="PanelContainer/VBoxContainer/Panel/ScrollContainer/VBoxContainer" instance=ExtResource( 2 )] 66 | anchor_right = 0.0 67 | margin_right = 526.0 68 | margin_bottom = 24.0 69 | -------------------------------------------------------------------------------- /main/screens/MatchScreen.gd: -------------------------------------------------------------------------------- 1 | extends "res://main/Screen.gd" 2 | 3 | onready var matchmaker_player_count_control := $PanelContainer/VBoxContainer/MatchPanel/SpinBox 4 | onready var join_match_id_control := $PanelContainer/VBoxContainer/JoinPanel/LineEdit 5 | 6 | func _ready() -> void: 7 | $PanelContainer/VBoxContainer/MatchPanel/MatchButton.connect("pressed", self, "_on_match_button_pressed", [OnlineMatch.MatchMode.MATCHMAKER]) 8 | $PanelContainer/VBoxContainer/CreatePanel/CreateButton.connect("pressed", self, "_on_match_button_pressed", [OnlineMatch.MatchMode.CREATE]) 9 | $PanelContainer/VBoxContainer/JoinPanel/JoinButton.connect("pressed", self, "_on_match_button_pressed", [OnlineMatch.MatchMode.JOIN]) 10 | 11 | OnlineMatch.connect("matchmaker_matched", self, "_on_OnlineMatch_matchmaker_matched") 12 | OnlineMatch.connect("match_created", self, "_on_OnlineMatch_created") 13 | OnlineMatch.connect("match_joined", self, "_on_OnlineMatch_joined") 14 | 15 | func _show_screen(_info: Dictionary = {}) -> void: 16 | matchmaker_player_count_control.value = 2 17 | join_match_id_control.text = '' 18 | 19 | func _on_match_button_pressed(mode) -> void: 20 | # If our session has expired, show the ConnectionScreen again. 21 | if Online.nakama_session == null or Online.nakama_session.is_expired(): 22 | ui_layer.show_screen("ConnectionScreen", { reconnect = true, next_screen = null }) 23 | 24 | # Wait to see if we get a new valid session. 25 | yield(Online, "session_changed") 26 | if Online.nakama_session == null: 27 | return 28 | 29 | # Connect socket to realtime Nakama API if not connected. 30 | if not Online.is_nakama_socket_connected(): 31 | Online.connect_nakama_socket() 32 | yield(Online, "socket_connected") 33 | 34 | ui_layer.hide_message() 35 | 36 | # Call internal method to do actual work. 37 | match mode: 38 | OnlineMatch.MatchMode.MATCHMAKER: 39 | _start_matchmaking() 40 | OnlineMatch.MatchMode.CREATE: 41 | _create_match() 42 | OnlineMatch.MatchMode.JOIN: 43 | _join_match() 44 | 45 | func _start_matchmaking() -> void: 46 | var min_players = matchmaker_player_count_control.value 47 | 48 | ui_layer.hide_screen() 49 | ui_layer.show_message("Looking for match...") 50 | 51 | var data = { 52 | min_count = min_players, 53 | string_properties = { 54 | game = "fish_game", 55 | engine = "godot", 56 | }, 57 | query = "+properties.game:fish_game +properties.engine:godot", 58 | } 59 | 60 | OnlineMatch.start_matchmaking(Online.nakama_socket, data) 61 | 62 | func _on_OnlineMatch_matchmaker_matched(_players: Dictionary): 63 | ui_layer.hide_message() 64 | ui_layer.show_screen("ReadyScreen", { players = _players }) 65 | 66 | func _create_match() -> void: 67 | OnlineMatch.create_match(Online.nakama_socket) 68 | 69 | func _on_OnlineMatch_created(match_id: String): 70 | ui_layer.show_screen("ReadyScreen", { match_id = match_id, clear = true }) 71 | 72 | func _join_match() -> void: 73 | var match_id = join_match_id_control.text.strip_edges() 74 | if match_id == '': 75 | ui_layer.show_message("Need to paste Match ID to join") 76 | return 77 | if not match_id.ends_with('.'): 78 | match_id += '.' 79 | 80 | OnlineMatch.join_match(Online.nakama_socket, match_id) 81 | 82 | func _on_OnlineMatch_joined(match_id: String): 83 | ui_layer.show_screen("ReadyScreen", { match_id = match_id, clear = true }) 84 | 85 | func _on_PasteButton_pressed() -> void: 86 | join_match_id_control.text = OS.clipboard 87 | 88 | func _on_LeaderboardButton_pressed() -> void: 89 | ui_layer.show_screen("LeaderboardScreen") 90 | -------------------------------------------------------------------------------- /main/screens/MatchScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://assets/theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://main/screens/MatchScreen.gd" type="Script" id=2] 5 | 6 | [node name="MatchScreen" type="Control"] 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | margin_left = 3.0 10 | margin_top = -1.0 11 | margin_right = 3.0 12 | margin_bottom = -1.0 13 | theme = ExtResource( 1 ) 14 | script = ExtResource( 2 ) 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="PanelContainer" type="PanelContainer" parent="."] 20 | anchor_right = 1.0 21 | anchor_bottom = 1.0 22 | margin_left = 50.0 23 | margin_top = 50.0 24 | margin_right = -50.0 25 | margin_bottom = -100.0 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"] 31 | margin_left = 7.0 32 | margin_top = 7.0 33 | margin_right = 533.0 34 | margin_bottom = 203.0 35 | size_flags_horizontal = 3 36 | size_flags_vertical = 3 37 | custom_constants/separation = 10 38 | 39 | [node name="MatchPanel" type="Panel" parent="PanelContainer/VBoxContainer"] 40 | margin_right = 526.0 41 | margin_bottom = 58.0 42 | size_flags_horizontal = 3 43 | size_flags_vertical = 3 44 | 45 | [node name="Label" type="Label" parent="PanelContainer/VBoxContainer/MatchPanel"] 46 | anchor_top = 0.5 47 | anchor_bottom = 0.5 48 | margin_left = 7.0 49 | margin_top = -12.0 50 | margin_right = 156.0 51 | margin_bottom = 12.0 52 | text = "Find at least" 53 | __meta__ = { 54 | "_edit_use_anchors_": false 55 | } 56 | 57 | [node name="SpinBox" type="SpinBox" parent="PanelContainer/VBoxContainer/MatchPanel"] 58 | margin_left = 157.0 59 | margin_top = 13.0 60 | margin_right = 315.0 61 | margin_bottom = 47.0 62 | min_value = 2.0 63 | max_value = 4.0 64 | value = 2.0 65 | align = 1 66 | suffix = "players" 67 | __meta__ = { 68 | "_edit_use_anchors_": false 69 | } 70 | 71 | [node name="MatchButton" type="Button" parent="PanelContainer/VBoxContainer/MatchPanel"] 72 | anchor_left = 1.0 73 | anchor_top = 0.5 74 | anchor_right = 1.0 75 | anchor_bottom = 0.5 76 | margin_left = -105.0 77 | margin_top = -25.0 78 | margin_right = -5.0 79 | margin_bottom = 25.0 80 | text = "Match" 81 | __meta__ = { 82 | "_edit_use_anchors_": false 83 | } 84 | 85 | [node name="CreatePanel" type="Panel" parent="PanelContainer/VBoxContainer"] 86 | margin_top = 68.0 87 | margin_right = 526.0 88 | margin_bottom = 127.0 89 | size_flags_horizontal = 3 90 | size_flags_vertical = 3 91 | 92 | [node name="Label" type="Label" parent="PanelContainer/VBoxContainer/CreatePanel"] 93 | anchor_top = 0.5 94 | anchor_bottom = 0.5 95 | margin_left = 7.0 96 | margin_top = -12.0 97 | margin_right = 249.0 98 | margin_bottom = 12.0 99 | text = "Create a private match" 100 | __meta__ = { 101 | "_edit_use_anchors_": false 102 | } 103 | 104 | [node name="CreateButton" type="Button" parent="PanelContainer/VBoxContainer/CreatePanel"] 105 | anchor_left = 1.0 106 | anchor_top = 0.5 107 | anchor_right = 1.0 108 | anchor_bottom = 0.5 109 | margin_left = -105.0 110 | margin_top = -25.0 111 | margin_right = -5.0 112 | margin_bottom = 25.0 113 | text = "Create" 114 | __meta__ = { 115 | "_edit_use_anchors_": false 116 | } 117 | 118 | [node name="JoinPanel" type="Panel" parent="PanelContainer/VBoxContainer"] 119 | margin_top = 137.0 120 | margin_right = 526.0 121 | margin_bottom = 196.0 122 | size_flags_horizontal = 3 123 | size_flags_vertical = 3 124 | 125 | [node name="Label" type="Label" parent="PanelContainer/VBoxContainer/JoinPanel"] 126 | anchor_top = 0.5 127 | anchor_bottom = 0.5 128 | margin_left = 7.0 129 | margin_top = -12.0 130 | margin_right = 128.0 131 | margin_bottom = 12.0 132 | text = "Join match:" 133 | __meta__ = { 134 | "_edit_use_anchors_": false 135 | } 136 | 137 | [node name="LineEdit" type="LineEdit" parent="PanelContainer/VBoxContainer/JoinPanel"] 138 | margin_left = 134.0 139 | margin_top = 14.0 140 | margin_right = 325.0 141 | margin_bottom = 48.0 142 | __meta__ = { 143 | "_edit_use_anchors_": false 144 | } 145 | 146 | [node name="PasteButton" type="Button" parent="PanelContainer/VBoxContainer/JoinPanel"] 147 | margin_left = 326.0 148 | margin_top = 16.0 149 | margin_right = 393.0 150 | margin_bottom = 46.0 151 | text = "Paste" 152 | __meta__ = { 153 | "_edit_use_anchors_": false 154 | } 155 | 156 | [node name="JoinButton" type="Button" parent="PanelContainer/VBoxContainer/JoinPanel"] 157 | anchor_left = 1.0 158 | anchor_top = 0.5 159 | anchor_right = 1.0 160 | anchor_bottom = 0.5 161 | margin_left = -105.0 162 | margin_top = -25.0 163 | margin_right = -5.0 164 | margin_bottom = 25.0 165 | text = "Join" 166 | __meta__ = { 167 | "_edit_use_anchors_": false 168 | } 169 | 170 | [node name="LeaderboardButton" type="Button" parent="."] 171 | anchor_left = 1.0 172 | anchor_top = 1.0 173 | anchor_right = 1.0 174 | anchor_bottom = 1.0 175 | margin_left = -250.0 176 | margin_top = -54.0 177 | text = "Leaderboard" 178 | __meta__ = { 179 | "_edit_use_anchors_": false 180 | } 181 | [connection signal="pressed" from="PanelContainer/VBoxContainer/JoinPanel/PasteButton" to="." method="_on_PasteButton_pressed"] 182 | [connection signal="pressed" from="LeaderboardButton" to="." method="_on_LeaderboardButton_pressed"] 183 | -------------------------------------------------------------------------------- /main/screens/PeerStatus.gd: -------------------------------------------------------------------------------- 1 | extends HBoxContainer 2 | 3 | onready var name_label := $NameLabel 4 | onready var status_label := $StatusLabel 5 | onready var score_label := $ScoreLabel 6 | 7 | var status := "" setget set_status 8 | var score := 0 setget set_score 9 | 10 | func initialize(_name: String, _status: String = "Connecting...", _score: int = 0) -> void: 11 | name_label.text = _name 12 | self.status = _status 13 | self.score = _score 14 | 15 | func set_status(_status: String) -> void: 16 | status = _status 17 | status_label.text = status 18 | 19 | func set_score(_score: int): 20 | score = _score 21 | if score == 0: 22 | score_label.text = "" 23 | else: 24 | score_label.text = str(score) 25 | -------------------------------------------------------------------------------- /main/screens/PeerStatus.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://main/screens/PeerStatus.gd" type="Script" id=1] 4 | [ext_resource path="res://assets/theme.tres" type="Theme" id=2] 5 | 6 | [node name="PeerStatus" type="HBoxContainer"] 7 | margin_right = 600.0 8 | margin_bottom = 48.0 9 | size_flags_vertical = 0 10 | theme = ExtResource( 2 ) 11 | script = ExtResource( 1 ) 12 | __meta__ = { 13 | "_edit_use_anchors_": false 14 | } 15 | 16 | [node name="ScoreLabel" type="Label" parent="."] 17 | margin_right = 50.0 18 | margin_bottom = 48.0 19 | rect_min_size = Vector2( 50, 0 ) 20 | text = "10" 21 | 22 | [node name="NameLabel" type="Label" parent="."] 23 | margin_left = 54.0 24 | margin_right = 297.0 25 | margin_bottom = 48.0 26 | size_flags_horizontal = 3 27 | text = "User" 28 | 29 | [node name="StatusLabel" type="Label" parent="."] 30 | margin_left = 301.0 31 | margin_right = 600.0 32 | margin_bottom = 48.0 33 | size_flags_horizontal = 3 34 | text = "Connecting..." 35 | align = 2 36 | -------------------------------------------------------------------------------- /main/screens/ReadyScreen.gd: -------------------------------------------------------------------------------- 1 | extends "res://main/Screen.gd" 2 | 3 | var PeerStatus = preload("res://main/screens/PeerStatus.tscn"); 4 | 5 | onready var ready_button := $Panel/ReadyButton 6 | onready var match_id_container := $Panel/MatchIDContainer 7 | onready var match_id_label := $Panel/MatchIDContainer/MatchID 8 | onready var status_container := $Panel/StatusContainer 9 | 10 | signal ready_pressed () 11 | 12 | func _ready() -> void: 13 | clear_players() 14 | 15 | OnlineMatch.connect("player_joined", self, "_on_OnlineMatch_player_joined") 16 | OnlineMatch.connect("player_left", self, "_on_OnlineMatch_player_left") 17 | OnlineMatch.connect("player_status_changed", self, "_on_OnlineMatch_player_status_changed") 18 | OnlineMatch.connect("match_ready", self, "_on_OnlineMatch_match_ready") 19 | OnlineMatch.connect("match_not_ready", self, "_on_OnlineMatch_match_not_ready") 20 | 21 | func _show_screen(info: Dictionary = {}) -> void: 22 | var players: Dictionary = info.get("players", {}) 23 | var match_id: String = info.get("match_id", '') 24 | var clear: bool = info.get("clear", false) 25 | 26 | if players.size() > 0 or clear: 27 | clear_players() 28 | 29 | for session_id in players: 30 | add_player(session_id, players[session_id]['username']) 31 | 32 | if match_id: 33 | match_id_container.visible = true 34 | match_id_label.text = match_id 35 | else: 36 | match_id_container.visible = false 37 | 38 | ready_button.grab_focus() 39 | 40 | func clear_players() -> void: 41 | for child in status_container.get_children(): 42 | status_container.remove_child(child) 43 | child.queue_free() 44 | ready_button.disabled = true 45 | 46 | func hide_match_id() -> void: 47 | match_id_container.visible = false 48 | 49 | func add_player(session_id: String, username: String) -> void: 50 | if not status_container.has_node(session_id): 51 | var status = PeerStatus.instance() 52 | status_container.add_child(status) 53 | status.initialize(username) 54 | status.name = session_id 55 | 56 | func remove_player(session_id: String) -> void: 57 | var status = status_container.get_node(session_id) 58 | if status: 59 | status.queue_free() 60 | 61 | func set_status(session_id: String, status: String) -> void: 62 | var status_node = status_container.get_node(session_id) 63 | if status_node: 64 | status_node.set_status(status) 65 | 66 | func get_status(session_id: String) -> String: 67 | var status_node = status_container.get_node(session_id) 68 | if status_node: 69 | return status_node.status 70 | return '' 71 | 72 | func reset_status(status: String) -> void: 73 | for child in status_container.get_children(): 74 | child.set_status(status) 75 | 76 | func set_score(session_id: String, score: int) -> void: 77 | var status_node = status_container.get_node(session_id) 78 | if status_node: 79 | status_node.set_score(score) 80 | 81 | func set_ready_button_enabled(enabled: bool = true) -> void: 82 | ready_button.disabled = !enabled 83 | if enabled: 84 | ready_button.grab_focus() 85 | 86 | func _on_ReadyButton_pressed() -> void: 87 | emit_signal("ready_pressed") 88 | 89 | func _on_MatchCopyButton_pressed() -> void: 90 | OS.clipboard = match_id_label.text 91 | 92 | ##### 93 | # OnlineMatch callbacks: 94 | ##### 95 | 96 | func _on_OnlineMatch_player_joined(player) -> void: 97 | add_player(player.session_id, player.username) 98 | 99 | func _on_OnlineMatch_player_left(player) -> void: 100 | remove_player(player.session_id) 101 | 102 | func _on_OnlineMatch_player_status_changed(player, status) -> void: 103 | if status == OnlineMatch.PlayerStatus.CONNECTED: 104 | # Don't go backwards from 'READY!' 105 | if get_status(player.session_id) != 'READY!': 106 | set_status(player.session_id, 'Connected.') 107 | elif status == OnlineMatch.PlayerStatus.CONNECTING: 108 | set_status(player.session_id, 'Connecting...') 109 | 110 | func _on_OnlineMatch_match_ready(_players: Dictionary) -> void: 111 | set_ready_button_enabled(true) 112 | 113 | func _on_OnlineMatch_match_not_ready() -> void: 114 | set_ready_button_enabled(false) 115 | -------------------------------------------------------------------------------- /main/screens/ReadyScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://main/screens/ReadyScreen.gd" type="Script" id=1] 4 | [ext_resource path="res://assets/theme.tres" type="Theme" id=2] 5 | [ext_resource path="res://main/screens/PeerStatus.tscn" type="PackedScene" id=3] 6 | 7 | [node name="ReadyScreen" type="Control"] 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | theme = ExtResource( 2 ) 11 | script = ExtResource( 1 ) 12 | __meta__ = { 13 | "_edit_use_anchors_": false 14 | } 15 | 16 | [node name="Panel" type="Panel" parent="."] 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | margin_left = 50.0 20 | margin_top = 50.0 21 | margin_right = -50.0 22 | margin_bottom = -50.0 23 | __meta__ = { 24 | "_edit_use_anchors_": false 25 | } 26 | 27 | [node name="MatchIDContainer" type="HBoxContainer" parent="Panel"] 28 | margin_left = 10.0 29 | margin_top = 5.0 30 | margin_right = 530.0 31 | margin_bottom = 39.0 32 | custom_constants/separation = 0 33 | __meta__ = { 34 | "_edit_use_anchors_": false 35 | } 36 | 37 | [node name="Label" type="Label" parent="Panel/MatchIDContainer"] 38 | margin_top = 5.0 39 | margin_right = 99.0 40 | margin_bottom = 29.0 41 | text = "Match ID:" 42 | 43 | [node name="MatchID" type="LineEdit" parent="Panel/MatchIDContainer"] 44 | margin_left = 99.0 45 | margin_right = 464.0 46 | margin_bottom = 34.0 47 | rect_min_size = Vector2( 250, 0 ) 48 | size_flags_horizontal = 3 49 | text = "XXXX-XXXX-XXXX-XXXX" 50 | editable = false 51 | 52 | [node name="MatchCopyButton" type="Button" parent="Panel/MatchIDContainer"] 53 | margin_left = 464.0 54 | margin_right = 520.0 55 | margin_bottom = 34.0 56 | text = "Copy" 57 | 58 | [node name="StatusContainer" type="VBoxContainer" parent="Panel"] 59 | margin_left = 30.0 60 | margin_top = 59.0 61 | margin_right = 510.0 62 | margin_bottom = 205.0 63 | size_flags_vertical = 3 64 | __meta__ = { 65 | "_edit_use_anchors_": false 66 | } 67 | 68 | [node name="PeerStatus" parent="Panel/StatusContainer" instance=ExtResource( 3 )] 69 | margin_right = 480.0 70 | margin_bottom = 24.0 71 | 72 | [node name="ReadyButton" type="Button" parent="Panel"] 73 | margin_left = 209.0 74 | margin_top = 225.0 75 | margin_right = 331.0 76 | margin_bottom = 255.0 77 | size_flags_horizontal = 4 78 | disabled = true 79 | text = "I'm Ready!" 80 | __meta__ = { 81 | "_edit_use_anchors_": false 82 | } 83 | [connection signal="pressed" from="Panel/MatchIDContainer/MatchCopyButton" to="." method="_on_MatchCopyButton_pressed"] 84 | [connection signal="pressed" from="Panel/ReadyButton" to="." method="_on_ReadyButton_pressed"] 85 | -------------------------------------------------------------------------------- /main/screens/TitleScreen.gd: -------------------------------------------------------------------------------- 1 | extends "res://main/Screen.gd" 2 | 3 | signal play_local 4 | signal play_online 5 | 6 | func _on_LocalButton_pressed() -> void: 7 | emit_signal("play_local") 8 | 9 | func _on_OnlineButton_pressed() -> void: 10 | emit_signal("play_online") 11 | 12 | func _on_CreditsButton_pressed() -> void: 13 | ui_layer.show_screen("CreditsScreen") 14 | -------------------------------------------------------------------------------- /main/screens/TitleScreen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://assets/fonts/monogram_extended.ttf" type="DynamicFontData" id=1] 4 | [ext_resource path="res://assets/theme.tres" type="Theme" id=2] 5 | [ext_resource path="res://main/screens/TitleScreen.gd" type="Script" id=3] 6 | 7 | [sub_resource type="DynamicFont" id=1] 8 | size = 160 9 | font_data = ExtResource( 1 ) 10 | 11 | [node name="TitleScreen" type="Control"] 12 | anchor_right = 1.0 13 | anchor_bottom = 1.0 14 | theme = ExtResource( 2 ) 15 | script = ExtResource( 3 ) 16 | __meta__ = { 17 | "_edit_use_anchors_": false 18 | } 19 | 20 | [node name="ColorRect" type="ColorRect" parent="."] 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | color = Color( 0, 0, 0, 1 ) 24 | __meta__ = { 25 | "_edit_use_anchors_": false 26 | } 27 | 28 | [node name="TitleLabel" type="Label" parent="."] 29 | anchor_right = 1.0 30 | margin_top = 25.0 31 | margin_bottom = 130.0 32 | custom_fonts/font = SubResource( 1 ) 33 | text = "Fish Game" 34 | align = 1 35 | __meta__ = { 36 | "_edit_use_anchors_": false 37 | } 38 | 39 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 40 | anchor_top = 1.0 41 | anchor_right = 1.0 42 | anchor_bottom = 1.0 43 | margin_top = -175.0 44 | margin_bottom = -50.0 45 | rect_min_size = Vector2( 0, 125 ) 46 | custom_constants/separation = 35 47 | alignment = 1 48 | __meta__ = { 49 | "_edit_use_anchors_": false 50 | } 51 | 52 | [node name="LocalButton" type="Button" parent="HBoxContainer"] 53 | margin_left = 97.0 54 | margin_right = 222.0 55 | margin_bottom = 125.0 56 | rect_min_size = Vector2( 125, 0 ) 57 | text = "Local" 58 | 59 | [node name="OnlineButton" type="Button" parent="HBoxContainer"] 60 | margin_left = 257.0 61 | margin_right = 382.0 62 | margin_bottom = 125.0 63 | rect_min_size = Vector2( 125, 0 ) 64 | text = "Online" 65 | 66 | [node name="CreditsButton" type="Button" parent="HBoxContainer"] 67 | margin_left = 417.0 68 | margin_right = 542.0 69 | margin_bottom = 125.0 70 | rect_min_size = Vector2( 125, 0 ) 71 | text = "Credits" 72 | [connection signal="pressed" from="HBoxContainer/LocalButton" to="." method="_on_LocalButton_pressed"] 73 | [connection signal="pressed" from="HBoxContainer/OnlineButton" to="." method="_on_OnlineButton_pressed"] 74 | [connection signal="pressed" from="HBoxContainer/CreditsButton" to="." method="_on_CreditsButton_pressed"] 75 | -------------------------------------------------------------------------------- /main/screens/TransparentButton.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | onready var original_modulate = modulate 4 | 5 | export (float) var transparency := 0.75 6 | 7 | func _ready() -> void: 8 | self.connect("mouse_entered", self, "_on_mouse_entered") 9 | self.connect("mouse_exited", self, "_on_mouse_exited") 10 | _on_mouse_exited() 11 | 12 | func _on_mouse_entered() -> void: 13 | modulate = original_modulate 14 | 15 | func _on_mouse_exited() -> void: 16 | modulate.a = transparency 17 | -------------------------------------------------------------------------------- /maps/Map.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const TILE_SIZE = Vector2(70, 70) 4 | 5 | func map_start() -> void: 6 | get_tree().call_group("map_object", "map_object_start") 7 | 8 | func map_stop() -> void: 9 | get_tree().call_group("map_object", "map_object_stop") 10 | 11 | func get_map_rect() -> Rect2: 12 | var rect: Rect2 13 | for child in get_children(): 14 | if child is TileMap: 15 | if rect == null: 16 | rect = child.get_used_rect() 17 | else: 18 | rect = rect.merge(child.get_used_rect()) 19 | return Rect2(rect.position * TILE_SIZE, rect.size * TILE_SIZE) 20 | -------------------------------------------------------------------------------- /maps/ParallaxBackground.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://assets/backgrounds/03.png" type="Texture" id=1] 4 | [ext_resource path="res://assets/backgrounds/04.png" type="Texture" id=2] 5 | [ext_resource path="res://assets/backgrounds/01.png" type="Texture" id=3] 6 | [ext_resource path="res://assets/backgrounds/02.png" type="Texture" id=4] 7 | 8 | [node name="ParallaxBackground" type="ParallaxBackground"] 9 | 10 | [node name="ParallaxLayer1" type="ParallaxLayer" parent="."] 11 | motion_mirroring = Vector2( 896, 480 ) 12 | 13 | [node name="Sprite" type="Sprite" parent="ParallaxLayer1"] 14 | texture = ExtResource( 2 ) 15 | centered = false 16 | 17 | [node name="ParallaxLayer2" type="ParallaxLayer" parent="."] 18 | position = Vector2( 0, -800 ) 19 | motion_scale = Vector2( 0.1, 1 ) 20 | motion_mirroring = Vector2( 896, 0 ) 21 | 22 | [node name="Sprite" type="Sprite" parent="ParallaxLayer2"] 23 | texture = ExtResource( 1 ) 24 | centered = false 25 | 26 | [node name="ParallaxLayer3" type="ParallaxLayer" parent="."] 27 | position = Vector2( 0, -700 ) 28 | motion_scale = Vector2( 0.2, 1 ) 29 | motion_mirroring = Vector2( 896, 0 ) 30 | 31 | [node name="Sprite" type="Sprite" parent="ParallaxLayer3"] 32 | texture = ExtResource( 4 ) 33 | centered = false 34 | 35 | [node name="ParallaxLayer4" type="ParallaxLayer" parent="."] 36 | position = Vector2( 0, -600 ) 37 | motion_scale = Vector2( 0.3, 1 ) 38 | motion_mirroring = Vector2( 896, 0 ) 39 | 40 | [node name="Sprite" type="Sprite" parent="ParallaxLayer4"] 41 | texture = ExtResource( 3 ) 42 | centered = false 43 | -------------------------------------------------------------------------------- /maps/StaticBackground.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://assets/backgrounds/03.png" type="Texture" id=1] 4 | [ext_resource path="res://assets/backgrounds/04.png" type="Texture" id=2] 5 | [ext_resource path="res://assets/backgrounds/01.png" type="Texture" id=3] 6 | [ext_resource path="res://assets/backgrounds/02.png" type="Texture" id=4] 7 | 8 | [node name="CanvasLayer" type="CanvasLayer"] 9 | layer = -100 10 | 11 | [node name="Sprite1" type="Sprite" parent="."] 12 | scale = Vector2( 0.75, 0.75 ) 13 | texture = ExtResource( 2 ) 14 | centered = false 15 | 16 | [node name="Sprite2" type="Sprite" parent="."] 17 | scale = Vector2( 0.75, 0.75 ) 18 | texture = ExtResource( 1 ) 19 | centered = false 20 | 21 | [node name="Sprite3" type="Sprite" parent="."] 22 | scale = Vector2( 0.75, 0.75 ) 23 | texture = ExtResource( 4 ) 24 | centered = false 25 | 26 | [node name="Sprite4" type="Sprite" parent="."] 27 | scale = Vector2( 0.75, 0.75 ) 28 | texture = ExtResource( 3 ) 29 | centered = false 30 | -------------------------------------------------------------------------------- /nakama/data/modules/fish_game.lua: -------------------------------------------------------------------------------- 1 | local nk = require("nakama") 2 | 3 | nk.run_once(function(context) 4 | nk.leaderboard_create("fish_game_wins", false, "desc", "incr") 5 | end) 6 | 7 | -------------------------------------------------------------------------------- /objects/Barnacles.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://assets/sprites/decorations1.png" type="Texture" id=1] 4 | 5 | [sub_resource type="Animation" id=1] 6 | resource_name = "Idle" 7 | length = 0.5 8 | loop = true 9 | tracks/0/type = "value" 10 | tracks/0/path = NodePath("Sprite:frame") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/imported = false 14 | tracks/0/enabled = true 15 | tracks/0/keys = { 16 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 17 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 18 | "update": 1, 19 | "values": [ 5, 6, 7, 8, 9 ] 20 | } 21 | 22 | [node name="Barnacles" type="Node2D"] 23 | 24 | [node name="Sprite" type="Sprite" parent="."] 25 | position = Vector2( -24, -51 ) 26 | texture = ExtResource( 1 ) 27 | centered = false 28 | vframes = 2 29 | hframes = 5 30 | frame = 5 31 | 32 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 33 | autoplay = "Idle" 34 | anims/Idle = SubResource( 1 ) 35 | -------------------------------------------------------------------------------- /objects/Seaweed.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://assets/sprites/decorations1.png" type="Texture" id=1] 4 | 5 | [sub_resource type="Animation" id=1] 6 | resource_name = "Idle" 7 | length = 0.5 8 | loop = true 9 | tracks/0/type = "value" 10 | tracks/0/path = NodePath("Sprite:frame") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/imported = false 14 | tracks/0/enabled = true 15 | tracks/0/keys = { 16 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 17 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 18 | "update": 1, 19 | "values": [ 0, 1, 2, 3, 4 ] 20 | } 21 | 22 | [node name="Seaweed" type="Node2D"] 23 | 24 | [node name="Sprite" type="Sprite" parent="."] 25 | position = Vector2( -24, -51 ) 26 | texture = ExtResource( 1 ) 27 | centered = false 28 | vframes = 2 29 | hframes = 5 30 | 31 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 32 | autoplay = "Idle" 33 | anims/Idle = SubResource( 1 ) 34 | -------------------------------------------------------------------------------- /objects/TimedGenerator.gd: -------------------------------------------------------------------------------- 1 | extends StaticBody2D 2 | 3 | export (PackedScene) var pickup_scene: PackedScene 4 | export (NodePath) var pickup_parent_path: NodePath = @"../" 5 | export (float) var regenerate_delay := 10.0 6 | 7 | onready var timer = $Timer 8 | onready var animation_player = $AnimationPlayer 9 | onready var pickup_position = $PickupPosition 10 | 11 | var current_pickup: Node2D 12 | 13 | func _ready() -> void: 14 | animation_player.play("Glow") 15 | timer.connect("timeout", self, "_do_generate") 16 | 17 | func _get_custom_rpc_methods() -> Array: 18 | return [ 19 | 'generate', 20 | ] 21 | 22 | func _do_generate() -> void: 23 | var pickup_parent = get_node(pickup_parent_path) 24 | if not pickup_parent: 25 | return 26 | 27 | var pickup_name = Util.find_unique_name(pickup_parent, 'Pickup-') 28 | if GameState.online_play: 29 | OnlineMatch.custom_rpc_sync(self, "generate", [pickup_name]) 30 | else: 31 | generate(pickup_name) 32 | 33 | func generate(pickup_name: String) -> void: 34 | if not pickup_scene: 35 | return 36 | 37 | var pickup_parent = get_node(pickup_parent_path) 38 | if not pickup_parent: 39 | return 40 | 41 | current_pickup = pickup_scene.instance() 42 | current_pickup.name = pickup_name 43 | pickup_parent.add_child(current_pickup) 44 | current_pickup.global_position = pickup_position.global_position 45 | 46 | current_pickup.connect("picked_up", self, "_on_current_pickup_picked_up") 47 | 48 | func _on_current_pickup_picked_up() -> void: 49 | current_pickup.disconnect("picked_up", self, "_on_current_pickup_picked_up") 50 | current_pickup = null 51 | 52 | if not GameState.online_play or OnlineMatch.is_network_master_for_node(self): 53 | timer.start() 54 | 55 | func map_object_start() -> void: 56 | if current_pickup == null and (not GameState.online_play or OnlineMatch.is_network_master_for_node(self)): 57 | _do_generate() 58 | timer.wait_time = regenerate_delay 59 | 60 | func map_object_stop() -> void: 61 | timer.stop() 62 | -------------------------------------------------------------------------------- /objects/TimedGenerator.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://assets/kenney-platform-deluxe/tiles/castle.png" type="Texture" id=1] 4 | [ext_resource path="res://objects/TimedGenerator.gd" type="Script" id=2] 5 | 6 | [sub_resource type="RectangleShape2D" id=1] 7 | extents = Vector2( 15, 15 ) 8 | 9 | [sub_resource type="Animation" id=2] 10 | resource_name = "Glow" 11 | loop = true 12 | tracks/0/type = "value" 13 | tracks/0/path = NodePath("Sprite:modulate") 14 | tracks/0/interp = 1 15 | tracks/0/loop_wrap = true 16 | tracks/0/imported = false 17 | tracks/0/enabled = true 18 | tracks/0/keys = { 19 | "times": PoolRealArray( 0, 0.7 ), 20 | "transitions": PoolRealArray( 1, 6 ), 21 | "update": 0, 22 | "values": [ Color( 1, 0.768627, 0.768627, 1 ), Color( 0.898039, 0, 1, 1 ) ] 23 | } 24 | 25 | [node name="TimedGenerator" type="StaticBody2D" groups=[ 26 | "map_object", 27 | ]] 28 | script = ExtResource( 2 ) 29 | 30 | [node name="Sprite" type="Sprite" parent="."] 31 | modulate = Color( 1, 0.768627, 0.768627, 1 ) 32 | position = Vector2( 15, -15 ) 33 | scale = Vector2( 0.428571, 0.428571 ) 34 | texture = ExtResource( 1 ) 35 | 36 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 37 | position = Vector2( 15, -15 ) 38 | shape = SubResource( 1 ) 39 | 40 | [node name="PickupPosition" type="Position2D" parent="."] 41 | position = Vector2( 15, -40 ) 42 | 43 | [node name="Timer" type="Timer" parent="."] 44 | one_shot = true 45 | 46 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 47 | autoplay = "Glow" 48 | anims/Glow = SubResource( 2 ) 49 | -------------------------------------------------------------------------------- /objects/TimedGeneratorFlat.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://assets/sprites/generator_flat.png" type="Texture" id=1] 4 | [ext_resource path="res://objects/TimedGenerator.gd" type="Script" id=2] 5 | [ext_resource path="res://assets/kenney-platform-deluxe/request/rockSmall.png" type="Texture" id=3] 6 | 7 | [sub_resource type="Curve" id=3] 8 | min_value = 0.01 9 | max_value = 0.1 10 | _data = [ Vector2( 0, 0.101023 ), 0.0, 0.0, 0, 0, Vector2( 0.745843, 0.0560227 ), -0.191364, -0.191364, 0, 0, Vector2( 0.995249, 0.0110227 ), 0.0, 0.0, 0, 0 ] 11 | 12 | [sub_resource type="Gradient" id=4] 13 | offsets = PoolRealArray( 0, 0.851759, 1 ) 14 | colors = PoolColorArray( 0.898039, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ) 15 | 16 | [sub_resource type="RectangleShape2D" id=1] 17 | extents = Vector2( 15, 1 ) 18 | 19 | [sub_resource type="Animation" id=2] 20 | resource_name = "Glow" 21 | loop = true 22 | tracks/0/type = "value" 23 | tracks/0/path = NodePath("Sprite:modulate") 24 | tracks/0/interp = 1 25 | tracks/0/loop_wrap = true 26 | tracks/0/imported = false 27 | tracks/0/enabled = true 28 | tracks/0/keys = { 29 | "times": PoolRealArray( 0, 0.7 ), 30 | "transitions": PoolRealArray( 1, 6 ), 31 | "update": 0, 32 | "values": [ Color( 1, 0.768627, 0.768627, 1 ), Color( 0.898039, 0, 1, 1 ) ] 33 | } 34 | 35 | [node name="TimedGenerator" type="StaticBody2D" groups=[ 36 | "map_object", 37 | ]] 38 | collision_layer = 0 39 | collision_mask = 9 40 | script = ExtResource( 2 ) 41 | 42 | [node name="CPUParticles2D" type="CPUParticles2D" parent="."] 43 | position = Vector2( 15, -2 ) 44 | amount = 24 45 | lifetime = 1.1 46 | lifetime_randomness = 0.2 47 | texture = ExtResource( 3 ) 48 | emission_shape = 2 49 | emission_rect_extents = Vector2( 11, 1 ) 50 | gravity = Vector2( 0, -98 ) 51 | scale_amount_curve = SubResource( 3 ) 52 | color_ramp = SubResource( 4 ) 53 | 54 | [node name="Sprite" type="Sprite" parent="."] 55 | modulate = Color( 1, 0.768627, 0.768627, 1 ) 56 | position = Vector2( 15, -15 ) 57 | texture = ExtResource( 1 ) 58 | 59 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 60 | position = Vector2( 15, -1 ) 61 | shape = SubResource( 1 ) 62 | 63 | [node name="PickupPosition" type="Position2D" parent="."] 64 | position = Vector2( 15, -10 ) 65 | 66 | [node name="Timer" type="Timer" parent="."] 67 | one_shot = true 68 | 69 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 70 | autoplay = "Glow" 71 | anims/Glow = SubResource( 2 ) 72 | -------------------------------------------------------------------------------- /pickups/DisintegrateEffect.gd: -------------------------------------------------------------------------------- 1 | extends CPUParticles2D 2 | 3 | func _ready(): 4 | emitting = true 5 | 6 | func _on_Timer_timeout() -> void: 7 | queue_free() 8 | -------------------------------------------------------------------------------- /pickups/DisintegrateEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://assets/kenney-platform-deluxe/request/rockSmall.png" type="Texture" id=1] 4 | [ext_resource path="res://pickups/DisintegrateEffect.gd" type="Script" id=2] 5 | [ext_resource path="res://pickups/DisintegrateEffectGradient.tres" type="Gradient" id=3] 6 | 7 | [node name="DisintegrateEffect" type="CPUParticles2D"] 8 | emitting = false 9 | amount = 4 10 | one_shot = true 11 | explosiveness = 1.0 12 | texture = ExtResource( 1 ) 13 | emission_shape = 2 14 | emission_rect_extents = Vector2( 10, 1 ) 15 | direction = Vector2( 0, -1 ) 16 | spread = 25.0 17 | gravity = Vector2( 0, 0 ) 18 | initial_velocity = 50.0 19 | initial_velocity_random = 0.5 20 | linear_accel = 50.0 21 | linear_accel_random = 0.5 22 | scale_amount = 0.25 23 | scale_amount_random = 0.5 24 | color_ramp = ExtResource( 3 ) 25 | script = ExtResource( 2 ) 26 | 27 | [node name="Timer" type="Timer" parent="."] 28 | wait_time = 1.5 29 | one_shot = true 30 | autostart = true 31 | [connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] 32 | -------------------------------------------------------------------------------- /pickups/DisintegrateEffectGradient.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Gradient" format=2] 2 | 3 | [resource] 4 | offsets = PoolRealArray( 1.4013e-45, 0.582781, 1 ) 5 | colors = PoolColorArray( 0.501961, 0.501961, 0.501961, 1, 0.501961, 0.501961, 0.501961, 1, 0.25098, 0.25098, 0.25098, 0 ) 6 | -------------------------------------------------------------------------------- /pickups/Gun.gd: -------------------------------------------------------------------------------- 1 | extends Pickup 2 | 3 | var DisintegrateEffect: PackedScene = preload("res://pickups/DisintegrateEffect.tscn") 4 | var SparksEffect: PackedScene = preload("res://pickups/SparksEffect.tscn") 5 | 6 | export (PackedScene) var projectile_scene: PackedScene = preload("res://pickups/Projectile.tscn") 7 | export (float) var projectile_velocity := 1200.0 8 | export (float) var projectile_range := 400.0 9 | export (float) var cooldown_time := 0.3 10 | export (int) var max_ammo := 3 11 | 12 | onready var projectile_position := $ProjectilePosition 13 | onready var sparks_position := $SparksPosition 14 | onready var dud_detector := $DudDetector 15 | onready var animation_player := $AnimationPlayer 16 | onready var cooldown_timer := $CooldownTimer 17 | onready var sounds := $Sounds 18 | 19 | var allow_shoot := true 20 | onready var ammo := max_ammo 21 | 22 | var use_by_player: Node = null 23 | 24 | func _ready() -> void: 25 | cooldown_timer.wait_time = cooldown_time 26 | 27 | func _get_custom_rpc_methods() -> Array: 28 | return ._get_custom_rpc_methods() + [ 29 | '_start_use', 30 | '_do_fire_projectile', 31 | '_disintegrate', 32 | ] 33 | 34 | func use() -> void: 35 | if not allow_shoot: 36 | return 37 | 38 | allow_shoot = false 39 | cooldown_timer.start() 40 | 41 | if ammo > 0: 42 | if not GameState.online_play: 43 | _start_use() 44 | else: 45 | OnlineMatch.custom_rpc_sync(self, "_start_use") 46 | else: 47 | _fire_projectile() 48 | 49 | func _start_use() -> void: 50 | # Account for a player throwing the gun before it actually fires. 51 | use_by_player = player 52 | 53 | animation_player.play("Shoot") 54 | 55 | func _fire_projectile() -> void: 56 | if GameState.online_play and not OnlineMatch.is_network_master_for_node(use_by_player): 57 | return 58 | 59 | var projectile_name = Util.find_unique_name(original_parent, 'Projectile-') 60 | var projectile_vector: Vector2 = (Vector2.RIGHT * projectile_velocity).rotated(global_rotation) 61 | var projectile_dud: bool = dud_detector.get_overlapping_bodies().size() > 0 62 | 63 | if not GameState.online_play: 64 | _do_fire_projectile(projectile_name, projectile_position.global_position, projectile_vector, projectile_range, projectile_dud) 65 | else: 66 | OnlineMatch.custom_rpc_sync(self, "_do_fire_projectile", [projectile_name, projectile_position.global_position, projectile_vector, projectile_range, projectile_dud]) 67 | 68 | func _do_fire_projectile(_projectile_name: String, _projectile_position: Vector2, _projectile_vector: Vector2, _projectile_range: float, _projectile_dud: bool) -> void: 69 | var projectile_parent = original_parent 70 | 71 | if ammo <= 0: 72 | var sparks = SparksEffect.instance() 73 | sparks_position.add_child(sparks) 74 | sounds.play("Empty") 75 | else: 76 | ammo -= 1 77 | 78 | var projectile = projectile_scene.instance() 79 | projectile.name = _projectile_name 80 | projectile_parent.add_child(projectile) 81 | 82 | projectile.shoot(_projectile_position, _projectile_vector, _projectile_range, _projectile_dud) 83 | sounds.play("Shoot") 84 | 85 | func _on_throw_finished() -> void: 86 | if ammo <= 0: 87 | if not GameState.online_play: 88 | _disintegrate() 89 | else: 90 | OnlineMatch.custom_rpc_sync(self, '_disintegrate') 91 | 92 | func _disintegrate() -> void: 93 | var parent = get_parent(); 94 | if parent: 95 | var effect = DisintegrateEffect.instance() 96 | parent.add_child(effect) 97 | effect.global_position = global_position + Vector2(0, 10) 98 | 99 | queue_free() 100 | 101 | func _on_CooldownTimer_timeout() -> void: 102 | allow_shoot = true 103 | 104 | func _on_AnimationPlayer_animation_finished(anim_name: String) -> void: 105 | if anim_name == 'Shoot': 106 | animation_player.play("Idle") 107 | -------------------------------------------------------------------------------- /pickups/Gun.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=2] 2 | 3 | [ext_resource path="res://assets/sprites/gun.png" type="Texture" id=1] 4 | [ext_resource path="res://pickups/Pickup.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://pickups/Gun.gd" type="Script" id=3] 6 | [ext_resource path="res://assets/sounds/shoot.ogg" type="AudioStream" id=4] 7 | [ext_resource path="res://components/Sounds.gd" type="Script" id=5] 8 | [ext_resource path="res://assets/sounds/empty.wav" type="AudioStream" id=6] 9 | 10 | [sub_resource type="CapsuleShape2D" id=1] 11 | radius = 11.9999 12 | height = 38.0001 13 | 14 | [sub_resource type="RectangleShape2D" id=2] 15 | 16 | [sub_resource type="Animation" id=3] 17 | resource_name = "Idle" 18 | length = 0.1 19 | tracks/0/type = "value" 20 | tracks/0/path = NodePath("GunSprite:frame") 21 | tracks/0/interp = 1 22 | tracks/0/loop_wrap = true 23 | tracks/0/imported = false 24 | tracks/0/enabled = true 25 | tracks/0/keys = { 26 | "times": PoolRealArray( 0 ), 27 | "transitions": PoolRealArray( 1 ), 28 | "update": 1, 29 | "values": [ 0 ] 30 | } 31 | tracks/1/type = "value" 32 | tracks/1/path = NodePath("MuzzleFlashSprite:frame") 33 | tracks/1/interp = 1 34 | tracks/1/loop_wrap = true 35 | tracks/1/imported = false 36 | tracks/1/enabled = true 37 | tracks/1/keys = { 38 | "times": PoolRealArray( 0 ), 39 | "transitions": PoolRealArray( 1 ), 40 | "update": 1, 41 | "values": [ 8 ] 42 | } 43 | 44 | [sub_resource type="Animation" id=4] 45 | resource_name = "Shoot" 46 | length = 0.5 47 | tracks/0/type = "value" 48 | tracks/0/path = NodePath("GunSprite:frame") 49 | tracks/0/interp = 1 50 | tracks/0/loop_wrap = true 51 | tracks/0/imported = false 52 | tracks/0/enabled = true 53 | tracks/0/keys = { 54 | "times": PoolRealArray( 0, 0.1, 0.2, 0.4 ), 55 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 56 | "update": 1, 57 | "values": [ 4, 5, 6, 0 ] 58 | } 59 | tracks/1/type = "value" 60 | tracks/1/path = NodePath("MuzzleFlashSprite:frame") 61 | tracks/1/interp = 1 62 | tracks/1/loop_wrap = true 63 | tracks/1/imported = false 64 | tracks/1/enabled = true 65 | tracks/1/keys = { 66 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 67 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 68 | "update": 1, 69 | "values": [ 8, 9, 10, 11, 8 ] 70 | } 71 | tracks/2/type = "method" 72 | tracks/2/path = NodePath(".") 73 | tracks/2/interp = 1 74 | tracks/2/loop_wrap = true 75 | tracks/2/imported = false 76 | tracks/2/enabled = true 77 | tracks/2/keys = { 78 | "times": PoolRealArray( 0.1 ), 79 | "transitions": PoolRealArray( 1 ), 80 | "values": [ { 81 | "args": [ ], 82 | "method": "_fire_projectile" 83 | } ] 84 | } 85 | 86 | [node name="Gun" instance=ExtResource( 2 )] 87 | script = ExtResource( 3 ) 88 | 89 | [node name="GunSprite" type="Sprite" parent="." index="0"] 90 | position = Vector2( 14, -4 ) 91 | texture = ExtResource( 1 ) 92 | vframes = 5 93 | hframes = 4 94 | 95 | [node name="MuzzleFlashSprite" type="Sprite" parent="." index="1"] 96 | position = Vector2( 16, -4 ) 97 | texture = ExtResource( 1 ) 98 | vframes = 5 99 | hframes = 4 100 | frame = 8 101 | 102 | [node name="CollisionShape2D" type="CollisionShape2D" parent="." index="2"] 103 | rotation = 1.5708 104 | shape = SubResource( 1 ) 105 | 106 | [node name="ProjectilePosition" type="Position2D" parent="." index="3"] 107 | position = Vector2( 42, -4 ) 108 | 109 | [node name="SparksPosition" type="Position2D" parent="." index="4"] 110 | position = Vector2( 31, -4 ) 111 | 112 | [node name="DudDetector" type="Area2D" parent="." index="5"] 113 | collision_layer = 0 114 | collision_mask = 17 115 | 116 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DudDetector" index="0"] 117 | position = Vector2( 31, -4 ) 118 | shape = SubResource( 2 ) 119 | 120 | [node name="CooldownTimer" type="Timer" parent="." index="6"] 121 | process_mode = 0 122 | one_shot = true 123 | 124 | [node name="AnimationPlayer" type="AnimationPlayer" parent="." index="7"] 125 | autoplay = "Idle" 126 | anims/Idle = SubResource( 3 ) 127 | anims/Shoot = SubResource( 4 ) 128 | 129 | [node name="Sounds" type="Node" parent="." index="8"] 130 | script = ExtResource( 5 ) 131 | 132 | [node name="Shoot" type="AudioStreamPlayer" parent="Sounds" index="0"] 133 | stream = ExtResource( 4 ) 134 | volume_db = 5.0 135 | bus = "Sound Effects" 136 | 137 | [node name="Empty" type="AudioStreamPlayer" parent="Sounds" index="1"] 138 | stream = ExtResource( 6 ) 139 | bus = "Sound Effects" 140 | 141 | [node name="HeldPosition" parent="." index="9"] 142 | position = Vector2( -17, 0 ) 143 | [connection signal="timeout" from="CooldownTimer" to="." method="_on_CooldownTimer_timeout"] 144 | [connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_AnimationPlayer_animation_finished"] 145 | -------------------------------------------------------------------------------- /pickups/Pickup.gd: -------------------------------------------------------------------------------- 1 | extends KinematicBody2D 2 | class_name Pickup 3 | 4 | onready var held_position: Position2D = $HeldPosition 5 | onready var original_parent: Node2D = get_parent() 6 | onready var gravity: float = float(ProjectSettings.get_setting("physics/2d/default_gravity")) 7 | onready var linear_damp: float = float(ProjectSettings.get_setting("physics/2d/default_linear_damp")) 8 | onready var angular_damp: float = float(ProjectSettings.get_setting("physics/2d/default_angular_damp")) 9 | 10 | enum PickupPosition { 11 | FRONT, 12 | BACK, 13 | } 14 | 15 | export (PickupPosition) var pickup_position = PickupPosition.FRONT 16 | 17 | enum PickupState { 18 | FREE = 0, 19 | PICKED_UP, 20 | WORN, 21 | THROWING, 22 | THROWN, 23 | } 24 | 25 | # Minimum thresholds in order to sleep physics on this body. 26 | const MIN_LINEAR_VELOCITY := 10.0 27 | const MIN_ANGULAR_VELOCITY := 10.0 28 | 29 | var player: Node2D 30 | var pickup_state: int = PickupState.FREE 31 | var throw_position := Vector2.ZERO 32 | 33 | var sleeping := false 34 | var linear_velocity := Vector2.ZERO 35 | var angular_velocity := 0.0 36 | var bounce := 0.1 37 | 38 | signal picked_up() 39 | 40 | func _ready(): 41 | pass 42 | 43 | func _get_custom_rpc_methods() -> Array: 44 | return [ 45 | '_do_physics_finished', 46 | ] 47 | 48 | func can_pickup() -> bool: 49 | return pickup_state == PickupState.FREE or pickup_state == PickupState.THROWN 50 | 51 | func pickup(_player: Node2D) -> void: 52 | pickup_state = PickupState.PICKED_UP 53 | player = _player 54 | rotation = 0.0 55 | sleeping = true 56 | emit_signal("picked_up") 57 | 58 | func _on_throw() -> void: 59 | # This allows the pickup to do something special just before it's thrown. 60 | pass 61 | 62 | func _on_throw_finished() -> void: 63 | # This allows the pickup to do something special once its stopped moving. 64 | pass 65 | 66 | func throw(_throw_position: Vector2, _throw_vector: Vector2, _throw_torque: float) -> void: 67 | _on_throw() 68 | 69 | pickup_state = PickupState.THROWING 70 | player = null 71 | 72 | throw_position = _throw_position 73 | linear_velocity = _throw_vector 74 | angular_velocity = _throw_torque 75 | 76 | sleeping = false 77 | 78 | func use() -> void: 79 | # Implement this in child classes. 80 | pass 81 | 82 | func _physics_process(delta: float) -> void: 83 | if sleeping: 84 | return 85 | if pickup_state == PickupState.PICKED_UP or pickup_state == PickupState.WORN: 86 | return 87 | 88 | if pickup_state == PickupState.THROWING: 89 | global_transform = Transform2D(0.0, throw_position) 90 | pickup_state = PickupState.THROWN 91 | 92 | # Apply gravity. 93 | linear_velocity += (Vector2.DOWN * gravity * delta) 94 | 95 | # Apply linear damp. 96 | var ld := 1.0 - (linear_damp * delta) 97 | if ld < 0: 98 | ld = 0.0 99 | linear_velocity *= ld 100 | 101 | # Apply angular damp. 102 | var ad := 1.0 - (angular_damp * delta) 103 | if ad < 0: 104 | ad = 0.0 105 | angular_velocity *= ad 106 | 107 | # Rotate/move object and detect collisions. 108 | global_rotation += (angular_velocity * delta) 109 | var collision: KinematicCollision2D = move_and_collide(linear_velocity * delta) 110 | 111 | # Bounce the object if it collides. 112 | if collision: 113 | #linear_velocity = collision.normal * collision.remainder.length() 114 | linear_velocity = collision.normal * (linear_velocity.length() * bounce) 115 | move_and_collide(collision.normal * collision.remainder.length()) 116 | 117 | # Sleep the object if it gets below certain linear/angular velocity thresholds. 118 | if not GameState.online_play or OnlineMatch.is_network_master_for_node(self): 119 | if linear_velocity.length() < MIN_LINEAR_VELOCITY and angular_velocity < MIN_ANGULAR_VELOCITY: 120 | if GameState.online_play: 121 | OnlineMatch.custom_rpc_sync(self, '_do_physics_finished', [global_transform]) 122 | else: 123 | _do_physics_finished(global_transform) 124 | 125 | func _do_physics_finished(_remote_transform = null) -> void: 126 | if _remote_transform: 127 | global_transform = _remote_transform 128 | 129 | sleeping = true 130 | if pickup_state == PickupState.THROWN: 131 | _on_throw_finished() 132 | pickup_state = PickupState.FREE 133 | -------------------------------------------------------------------------------- /pickups/Pickup.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://pickups/Pickup.gd" type="Script" id=1] 4 | 5 | [node name="Pickup" type="KinematicBody2D"] 6 | collision_layer = 8 7 | collision_mask = 17 8 | script = ExtResource( 1 ) 9 | 10 | [node name="HeldPosition" type="Position2D" parent="."] 11 | -------------------------------------------------------------------------------- /pickups/Projectile.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | onready var ray_cast := $RayCast2D 4 | onready var trail := $Trail 5 | onready var hitbox := $Hitbox 6 | onready var animation_player := $AnimationPlayer 7 | 8 | var vector := Vector2.ZERO 9 | var start_position := Vector2.ZERO 10 | var max_distance := 0.0 11 | var dud := false 12 | 13 | func _ready(): 14 | trail.set_as_toplevel(true) 15 | trail.global_position = Vector2(0, 0) 16 | 17 | func shoot(_start_position: Vector2, _vector: Vector2, _max_distance: float, _dud: bool) -> void: 18 | start_position = _start_position 19 | vector = _vector 20 | max_distance = _max_distance 21 | dud = _dud 22 | 23 | global_position = _start_position 24 | 25 | if dud: 26 | hitbox.disabled = true 27 | hit() 28 | 29 | func _physics_process(delta: float) -> void: 30 | if vector == Vector2.ZERO or dud: 31 | return 32 | 33 | # Add to trail before moving project, so we are using the last position 34 | # from the last frame. 35 | trail.add_point(global_position) 36 | while trail.get_point_count() > 5: 37 | trail.remove_point(0) 38 | 39 | var increment = vector * delta 40 | ray_cast.cast_to = increment 41 | ray_cast.force_raycast_update() 42 | if ray_cast.is_colliding(): 43 | global_position = ray_cast.get_collision_point() 44 | vector = Vector2.ZERO 45 | else: 46 | global_position += increment 47 | 48 | if start_position.distance_to(global_position) >= max_distance: 49 | hit() 50 | 51 | func _on_Projectile_body_entered(body: Node) -> void: 52 | if not dud: 53 | hit() 54 | 55 | func hit() -> void: 56 | vector = Vector2.ZERO 57 | animation_player.play("Hit") 58 | 59 | func _on_AnimationPlayer_animation_finished(anim_name: String) -> void: 60 | if anim_name == 'Hit': 61 | queue_free() 62 | -------------------------------------------------------------------------------- /pickups/Projectile.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://assets/sprites/gun.png" type="Texture" id=1] 4 | [ext_resource path="res://pickups/Projectile.gd" type="Script" id=2] 5 | [ext_resource path="res://components/Hitbox.tscn" type="PackedScene" id=3] 6 | 7 | [sub_resource type="Gradient" id=1] 8 | colors = PoolColorArray( 1, 1, 1, 0, 1, 1, 1, 0.12549 ) 9 | 10 | [sub_resource type="CircleShape2D" id=2] 11 | radius = 7.0 12 | 13 | [sub_resource type="Animation" id=3] 14 | resource_name = "Hit" 15 | length = 0.2 16 | step = 0.05 17 | tracks/0/type = "value" 18 | tracks/0/path = NodePath("Sprite:frame") 19 | tracks/0/interp = 1 20 | tracks/0/loop_wrap = true 21 | tracks/0/imported = false 22 | tracks/0/enabled = true 23 | tracks/0/keys = { 24 | "times": PoolRealArray( 0, 0.05, 0.1, 0.15 ), 25 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 26 | "update": 1, 27 | "values": [ 16, 17, 18, 19 ] 28 | } 29 | 30 | [sub_resource type="Animation" id=4] 31 | length = 0.1 32 | tracks/0/type = "value" 33 | tracks/0/path = NodePath("Sprite:frame") 34 | tracks/0/interp = 1 35 | tracks/0/loop_wrap = true 36 | tracks/0/imported = false 37 | tracks/0/enabled = true 38 | tracks/0/keys = { 39 | "times": PoolRealArray( 0 ), 40 | "transitions": PoolRealArray( 1 ), 41 | "update": 1, 42 | "values": [ 12 ] 43 | } 44 | 45 | [node name="Projectile" type="Area2D"] 46 | collision_layer = 0 47 | collision_mask = 3 48 | script = ExtResource( 2 ) 49 | 50 | [node name="Trail" type="Line2D" parent="."] 51 | width = 4.0 52 | default_color = Color( 1, 1, 1, 0.12549 ) 53 | gradient = SubResource( 1 ) 54 | begin_cap_mode = 2 55 | end_cap_mode = 2 56 | 57 | [node name="Sprite" type="Sprite" parent="."] 58 | position = Vector2( -5, 1 ) 59 | texture = ExtResource( 1 ) 60 | vframes = 5 61 | hframes = 4 62 | frame = 12 63 | 64 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 65 | shape = SubResource( 2 ) 66 | 67 | [node name="RayCast2D" type="RayCast2D" parent="."] 68 | cast_to = Vector2( 25, 0 ) 69 | collision_mask = 3 70 | 71 | [node name="Hitbox" parent="." instance=ExtResource( 3 )] 72 | collision_mask = 2 73 | 74 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Hitbox"] 75 | shape = SubResource( 2 ) 76 | 77 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 78 | autoplay = "Idle" 79 | anims/Hit = SubResource( 3 ) 80 | anims/Idle = SubResource( 4 ) 81 | [connection signal="body_entered" from="." to="." method="_on_Projectile_body_entered"] 82 | [connection signal="animation_finished" from="AnimationPlayer" to="." method="_on_AnimationPlayer_animation_finished"] 83 | -------------------------------------------------------------------------------- /pickups/SparksEffect.gd: -------------------------------------------------------------------------------- 1 | extends CPUParticles2D 2 | 3 | func _ready(): 4 | emitting = true 5 | 6 | func _on_Timer_timeout() -> void: 7 | queue_free() 8 | -------------------------------------------------------------------------------- /pickups/SparksEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://pickups/SparksEffect.gd" type="Script" id=1] 4 | [ext_resource path="res://pickups/SparksEffectGradient.tres" type="Gradient" id=2] 5 | 6 | [node name="SparksEffect" type="CPUParticles2D"] 7 | emitting = false 8 | amount = 16 9 | lifetime = 0.3 10 | one_shot = true 11 | explosiveness = 0.5 12 | lifetime_randomness = 0.5 13 | local_coords = false 14 | direction = Vector2( 1, -1 ) 15 | gravity = Vector2( 0, 0 ) 16 | initial_velocity = 40.0 17 | scale_amount = 3.0 18 | color_ramp = ExtResource( 2 ) 19 | script = ExtResource( 1 ) 20 | 21 | [node name="Timer" type="Timer" parent="."] 22 | one_shot = true 23 | [connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] 24 | -------------------------------------------------------------------------------- /pickups/SparksEffectGradient.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Gradient" format=2] 2 | 3 | [resource] 4 | offsets = PoolRealArray( 0, 0.5, 1 ) 5 | colors = PoolColorArray( 0.2, 0.2, 0.2, 1, 0, 0, 0, 0.784314, 0, 0, 0, 0 ) 6 | -------------------------------------------------------------------------------- /pickups/Sword.gd: -------------------------------------------------------------------------------- 1 | extends Pickup 2 | 3 | onready var animation_player = $AnimationPlayer 4 | onready var sounds = $Sounds 5 | 6 | func _get_custom_rpc_methods() -> Array: 7 | return ._get_custom_rpc_methods() + [ 8 | '_do_use', 9 | ] 10 | 11 | func use() -> void: 12 | if animation_player.is_playing(): 13 | return 14 | 15 | if not GameState.online_play: 16 | _do_use() 17 | else: 18 | OnlineMatch.custom_rpc_sync(self, '_do_use') 19 | 20 | func _on_throw() -> void: 21 | if animation_player.is_playing() and animation_player.current_animation != "Reset": 22 | animation_player.play("Reset") 23 | 24 | func _do_use() -> void: 25 | animation_player.play("Swing") 26 | sounds.play("Swing") 27 | -------------------------------------------------------------------------------- /pickups/Sword.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=2] 2 | 3 | [ext_resource path="res://pickups/Pickup.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://assets/sprites/sword.png" type="Texture" id=2] 5 | [ext_resource path="res://components/Hitbox.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://pickups/Sword.gd" type="Script" id=4] 7 | [ext_resource path="res://assets/sounds/sword.wav" type="AudioStream" id=5] 8 | [ext_resource path="res://components/Sounds.gd" type="Script" id=6] 9 | 10 | [sub_resource type="RectangleShape2D" id=1] 11 | extents = Vector2( 12.9448, 32.6482 ) 12 | 13 | [sub_resource type="Animation" id=2] 14 | resource_name = "Idle" 15 | length = 0.001 16 | step = 0.05 17 | tracks/0/type = "value" 18 | tracks/0/path = NodePath("Hitbox:disabled") 19 | tracks/0/interp = 1 20 | tracks/0/loop_wrap = true 21 | tracks/0/imported = false 22 | tracks/0/enabled = true 23 | tracks/0/keys = { 24 | "times": PoolRealArray( 0 ), 25 | "transitions": PoolRealArray( 1 ), 26 | "update": 1, 27 | "values": [ true ] 28 | } 29 | tracks/1/type = "value" 30 | tracks/1/path = NodePath("Sprite:frame") 31 | tracks/1/interp = 1 32 | tracks/1/loop_wrap = true 33 | tracks/1/imported = false 34 | tracks/1/enabled = true 35 | tracks/1/keys = { 36 | "times": PoolRealArray( 0 ), 37 | "transitions": PoolRealArray( 1 ), 38 | "update": 1, 39 | "values": [ 0 ] 40 | } 41 | 42 | [sub_resource type="Animation" id=3] 43 | length = 0.3 44 | step = 0.05 45 | tracks/0/type = "value" 46 | tracks/0/path = NodePath("Hitbox:disabled") 47 | tracks/0/interp = 1 48 | tracks/0/loop_wrap = true 49 | tracks/0/imported = false 50 | tracks/0/enabled = true 51 | tracks/0/keys = { 52 | "times": PoolRealArray( 0, 0.1, 0.2 ), 53 | "transitions": PoolRealArray( 1, 1, 1 ), 54 | "update": 1, 55 | "values": [ true, false, true ] 56 | } 57 | tracks/1/type = "value" 58 | tracks/1/path = NodePath("Sprite:frame") 59 | tracks/1/interp = 1 60 | tracks/1/loop_wrap = true 61 | tracks/1/imported = false 62 | tracks/1/enabled = true 63 | tracks/1/keys = { 64 | "times": PoolRealArray( 0, 0.1, 0.2, 0.25 ), 65 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 66 | "update": 1, 67 | "values": [ 4, 5, 6, 7 ] 68 | } 69 | 70 | [node name="Sword" instance=ExtResource( 1 )] 71 | script = ExtResource( 4 ) 72 | pickup_position = 1 73 | 74 | [node name="Sprite" type="Sprite" parent="." index="0"] 75 | position = Vector2( 24, -39 ) 76 | texture = ExtResource( 2 ) 77 | vframes = 2 78 | hframes = 4 79 | 80 | [node name="CollisionShape2D" type="CollisionShape2D" parent="." index="1"] 81 | position = Vector2( 14, -29.5 ) 82 | rotation = 0.486947 83 | shape = SubResource( 1 ) 84 | 85 | [node name="AnimationPlayer" type="AnimationPlayer" parent="." index="2"] 86 | autoplay = "Idle" 87 | anims/Idle = SubResource( 2 ) 88 | anims/Swing = SubResource( 3 ) 89 | 90 | [node name="Hitbox" parent="." index="3" instance=ExtResource( 3 )] 91 | position = Vector2( 29, -23.5 ) 92 | collision_mask = 2 93 | disabled = true 94 | 95 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Hitbox" index="0"] 96 | polygon = PoolVector2Array( -23, -5.5, -16, -18.5, -8, -48.5, 9, -34.5, 25, -4.5, 27, 16.5, 22, 25.5, 8, 30.5, -3, 30.5, -23, 20.5 ) 97 | 98 | [node name="Sounds" type="Node" parent="." index="4"] 99 | script = ExtResource( 6 ) 100 | 101 | [node name="Swing" type="AudioStreamPlayer" parent="Sounds" index="0"] 102 | stream = ExtResource( 5 ) 103 | bus = "Sound Effects" 104 | 105 | [node name="HeldPosition" parent="." index="5"] 106 | position = Vector2( 0, -8.5 ) 107 | -------------------------------------------------------------------------------- /scripts/generate-build-variables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$NAKAMA_SERVER_KEY" -o -z "$NAKAMA_HOST" -o -z "$NAKAMA_PORT" ]; then 4 | exit 0 5 | fi 6 | 7 | NAKAMA_SERVER_KEY=$(base64 -d <<< "$NAKAMA_SERVER_KEY") 8 | 9 | # Use the Git hash if no CLIENT_VERSION is given. 10 | if [ -z "$CLIENT_VERSION" ]; then 11 | # GitLab CI: 12 | if [ -n "$CI_COMMIT_SHA" ]; then 13 | CLIENT_VERSION="$CI_COMMIT_SHA" 14 | 15 | # GitHub Actions: 16 | elif [ -n "$GITHUB_SHA" ]; then 17 | CLIENT_VERSION="$GITHUB_SHA" 18 | fi 19 | fi 20 | 21 | cat << EOF > autoload/Build.gd 22 | extends Node 23 | 24 | func _ready() -> void: 25 | Online.nakama_host = '$NAKAMA_HOST' 26 | Online.nakama_port = $NAKAMA_PORT 27 | Online.nakama_server_key = '$NAKAMA_SERVER_KEY' 28 | Online.nakama_scheme = 'https' 29 | 30 | OnlineMatch.client_version = '$CLIENT_VERSION' 31 | 32 | EOF 33 | 34 | --------------------------------------------------------------------------------