From 919576638c196fb93c6186fe8c7943d06e743a8b Mon Sep 17 00:00:00 2001 From: John Breaux Date: Thu, 18 Nov 2021 09:00:15 -0600 Subject: [PATCH] Multiplayer sucks --- godot_ship/project.godot | 1 + godot_ship/script/game/Gameplay/Fire.gd | 4 +- godot_ship/script/game/Gameplay/Game.gd | 94 ++++++++++++++++------- godot_ship/script/game/Gameplay/Player.gd | 90 ++++++++++++---------- godot_ship/script/game/Setup.gd | 4 - godot_ship/script/network/Network.gd | 85 ++++++++++++++++++++ godot_ship/script/options/Network.gd | 5 ++ 7 files changed, 209 insertions(+), 74 deletions(-) create mode 100644 godot_ship/script/network/Network.gd create mode 100644 godot_ship/script/options/Network.gd diff --git a/godot_ship/project.godot b/godot_ship/project.godot index fbccc41..658c27a 100644 --- a/godot_ship/project.godot +++ b/godot_ship/project.godot @@ -20,6 +20,7 @@ MessageBus="*res://script/Message Bus.gd" AudioBus="*res://script/audio controller/Audio Bus.gd" OptionsController="*res://script/options/OptionsController.gd" AudioController="*res://scenes/AudioController.tscn" +Network="*res://script/network/Network.gd" [display] diff --git a/godot_ship/script/game/Gameplay/Fire.gd b/godot_ship/script/game/Gameplay/Fire.gd index 8ad90f4..6095e69 100644 --- a/godot_ship/script/game/Gameplay/Fire.gd +++ b/godot_ship/script/game/Gameplay/Fire.gd @@ -7,11 +7,9 @@ func _ready(): # Signal to pass the fire location back to yet-unknown nodes signal fire_at - func _on_Fire_pressed(): var crosshair = get_node("Crosshair") - # hides crosshair - crosshair.visible = false + # Check if the crosshair is in a valid position if crosshair.validate_position(crosshair.position) == true: var crosshair_pos = crosshair.world_to_board_space(crosshair.position) # fires at position diff --git a/godot_ship/script/game/Gameplay/Game.gd b/godot_ship/script/game/Gameplay/Game.gd index 4a4fd84..ada459a 100644 --- a/godot_ship/script/game/Gameplay/Game.gd +++ b/godot_ship/script/game/Gameplay/Game.gd @@ -1,4 +1,4 @@ -extends Node +extends Control var light_theme = load("res://light_theme.tres") var dark_theme = load("res://dark_theme.tres") @@ -11,15 +11,16 @@ onready var Victory = preload("res://scenes/Game/Victory.tscn") # Array of instances of the Player class; stores the Players -var players = [] # = player1, player2, ... +var players = {} # = player1, player2, ... +var players_ready = [] # turn counter var turn = 0 -# Variable transporting hit state between players -var hit = false -# Variable tracking whether a game is multiplayer (so that the correct Player type can be spawned) -# TODO: Multiplayer -var is_multiplayer = false +# winner +var winner = 0 +# Every game is a multiplayer game, even the ones that aren't. +# We're taking the Minecraft approach, baby +var network_id # Called when the node enters the scene tree for the first time. func _ready(): @@ -29,27 +30,69 @@ func _ready(): get_node("ConfirmationDialog").get_ok().rect_min_size.x = 100 get_node("ConfirmationDialog").get_cancel().rect_min_size.x = 100 - var _errno = 0; - _errno += OptionsController.connect("change_theme", self, "_on_change_theme") - _on_change_theme(OptionsController.get_theme()) - game_start() + if multiplayer: + # TODO: Spawn a lobby where people can either connect to a peer or create a server + pass + + game_setup() -func game_setup(): - print_debug("Congrats! Setup complete.") +# Function used to keep track of which players are ready +remote func player_ready(pid): + print (get_tree().is_network_server()) + var who = pid + # Here are some checks you can do, for example + assert(get_tree().is_network_server()) + assert(who in Network.peer_info) # Exists + assert(not who in players_ready) # Was not added yet + + players_ready.append(who) + + if players_ready.size() == Network.peer_info.size(): + rpc("game_start") # Member functions: # game_start: starts the game -func game_start(): - # Create a player 1 - var player = Player.instance() - # TODO: Create valid callback for player_ready - # It shouldn't connect to game_setup - player.connect("player_ready", self, "game_setup") - # Add player to scene tree - add_child(player) - # Add player to players - players.append(player) +func game_setup(): + # If there's no server connected, create one + if not Network.connected: + # TODO: Create a fake peer who we can automate, for single-player mode + Network.start_server() + network_id = Network.get_network_id() + # Create players for every player in Network.peer_info + for k in Network.peer_info.keys(): + # Create a new player + var player = Player.instance() + # Set the player's opponent, for now + # Give the player a recognizable name, like "1", instead of "@@97" + player.name = str(k) + # The player controls themselves + player.set_network_master(k) + # Add the player to the list of players + players[k] = player + # Add the player to the scene tree + add_child(player) pass + + # Connect to your own player_ready signal + players[network_id].connect("player_ready", self, "_on_player_ready") + # Have your player set up the board: + players[network_id].set_up_begin() + +func game_start(): + # Make sure we're the server + assert(get_tree().is_network_server()) + while not winner: + for id in players.keys(): + var hit = players[id].rpc_id(id, "turn_start") + var result = players[hit["id"]].rpc_id(hit["id"], "hit", hit["target"]) + players[id].rpc_id(id, "mark", hit["target"], result) + pass + +func _on_player_ready(pid): + print ("_on_player_ready") + match pid: + 1: player_ready(pid) + _: rpc("player_ready", pid) # victory_screen: display the victory screen func victory_screen(): @@ -77,8 +120,3 @@ func _on_Button_button_down(): func _on_ConfirmationDialog_confirmed(): end() -func _on_change_theme(theme): - if theme == "light": - get_node("Buttons").set_theme(light_theme) - elif theme == "dark": - get_node("Buttons").set_theme(dark_theme) diff --git a/godot_ship/script/game/Gameplay/Player.gd b/godot_ship/script/game/Gameplay/Player.gd index 0855916..d8c681f 100644 --- a/godot_ship/script/game/Gameplay/Player.gd +++ b/godot_ship/script/game/Gameplay/Player.gd @@ -1,38 +1,46 @@ extends Node -# Path to Board class, for instantiating new Boards in code -var Board = preload("res://scenes/Game/Board.tscn") - -# Preloaded assets, to be used later -# TODO: Move Setup into the Player. It's just here, for now, so that it can be tested and the game doesn't appear broken -var Setup = preload("res://scenes/Game/Setup.tscn") -# TODO: Move Fire into the Player. See above. -var Fire = preload("res://scenes/Game/Fire.tscn") - +# Emitted when the player is ready signal player_ready -# Player ID of this player -var pid -# board (an instance of the Board class) -onready var board = Board.instance() +# Preloaded assets, to be used later +# Path to Board class, for instantiating new Boards in code +var Board = preload("res://scenes/Game/Board.tscn") +# Path to Setup menu, so the player may set up their Board +var Setup = preload("res://scenes/Game/Setup.tscn") +# Path to Fire menu, so the player may fire on the opponent +var Fire = preload("res://scenes/Game/Fire.tscn") + +var pid # Player ID +var board # Board + +var fire_at_position # Position to fire at +var opponent_pid # PID of opponent # Called when the node enters the scene tree for the first time. func _ready(): + # Set the player ID according to which network peer ID we are + pid = int(name) + board = Board.instance() + +remote func set_up_begin(): var setup = Setup.instance() setup.connect("board_ready", self, "set_up") add_child(setup) # Member functions: # hit: Called when opponent fires on us. -# Update internal state, and return bool hit/miss, hit = true, miss = false -func hit(pos): +# Update internal state, and return hit/miss/sunk +remote func hit(pos): var res = board.hit(pos) - if res == -1: - return true - else: - return false - pass + return res + +# mark: Called when the opponent returns hit/miss/sunk +# Update internal state, return ack/nak +remote func mark(pos, value): + # Mark the position on the top board + board.fire(pos, value) # place_ship: called when ships are placed. # forwards Ship locations to the Board, so that it may construct a ship @@ -40,27 +48,32 @@ func hit(pos): func place_ship(pos, size, orientation, variant): board.place_ship(pos, size, orientation, variant) -# setUp: set up the board given the placed ship locations -# translates the ship positions in the Setup UI to board-space, then places each ship +# setup: set up the board given the placed ship locations +# Places each ship onto the board # ships: a list of lists of ship properties [[position, orientation, size, variant], ...] func set_up(ships): # Place all the ships for i in ships: place_ship(i[0], i[1], i[2], i[3]) - emit_signal("player_ready") # Add the board to the tree add_child(board) + emit_signal("player_ready", pid) -# turnStart: start player's turn +# turn_start: start player's turn # Initiates the player's turn, and blocks until the player selects a location to fire upon # returns: fire = [player id, target coordinates] -func turnStart(): - # TODO: Yielf until Fire return - add_child(Fire.instance()) - var player_id = 0 - var target = Vector2(0,0) - return [player_id, target] - pass +remote func turn_start(): + print("turn_start") + var fire = Fire.instance() + + add_child(fire) + yield(fire, "fire_at") + while not fire_at_position: + pass + var player_id = opponent_pid + var target = fire_at_position + fire_at_position = null + return {"id": player_id, "target": target} # getBoard: returns the player's board # returns: board @@ -69,18 +82,17 @@ func getBoard(): # forfeit: ends game for player # Sinks all ships -# hits every single board tile +# Ensures there are no ships left behind func forfeit(): for i in 10: for j in 10: - var pos - pos.x = i - pos.y = j - hit(pos) + # Hit the board + hit(Vector2(i, j)) # getShipCount: get the number of ships the player has left alive func getShipCount(): - var count = board.get_ship_count() - return count - pass + return board.get_ship_count() + +func _on_fire_at(pos): + fire_at_position = pos diff --git a/godot_ship/script/game/Setup.gd b/godot_ship/script/game/Setup.gd index 1c5cc7b..94da823 100644 --- a/godot_ship/script/game/Setup.gd +++ b/godot_ship/script/game/Setup.gd @@ -33,7 +33,6 @@ func _on_Confirm_Placement_pressed(): # if this is more than zero, the ship is invalid if get_node(ship).validate_placement(): valid = false - print ("Placement: ", valid) if valid == false: get_node("PlaceShipDialog").popup() else: @@ -43,9 +42,6 @@ func _on_Confirm_Placement_pressed(): ship = get_node(ship) var data = ship.get_shipdata() ship_data.append(data) - #print out the array for testing - for x in ship_data: - print_debug("Ship Position: ", x[0], ", Ship Length: ", x[1], ", Ship Orientation: ", x[2], ", Variant: ", x[3]) # Return the shipLocation array to those listening on game_ready emit_signal("board_ready", ship_data) queue_free() diff --git a/godot_ship/script/network/Network.gd b/godot_ship/script/network/Network.gd new file mode 100644 index 0000000..770e49a --- /dev/null +++ b/godot_ship/script/network/Network.gd @@ -0,0 +1,85 @@ +extends Node + +const DEFAULT_PORT = 35879 +const LOCALHOST = "127.0.0.1" + +# Store peer info in a dictionary, by player ID +var peer_info = {} +# Store this player's hostname +var local_info = {"hostname": ""} + +var connected = false + +# Network -- handles server and client setup, and facilitates communication between the two +# start_server: Host the game +# port: TCP port +# max_players: Largest number of players allowed to connect at a time +func start_server(port = DEFAULT_PORT, max_players = 2): + get_hostname() + peer_info[1] = local_info + var peer = NetworkedMultiplayerENet.new() + peer.create_server(port, max_players) + get_tree().network_peer = peer + connected = true + return + +func connect_server(ip = LOCALHOST, port = DEFAULT_PORT): + get_hostname() + var peer = NetworkedMultiplayerENet.new() + peer.create_client(ip, port) + get_tree().network_peer = peer + return + +func disconnect_server(): + get_tree().network_peer = null + connected = false + +func get_hostname(): + if local_info["hostname"] == "": + var hostname = [] + var _ret = OS.execute("hostname", [], true, hostname) + local_info["hostname"] = hostname[0].split("\n")[0] + return local_info["hostname"] + +func get_network_id(): + return get_tree().get_network_unique_id() + +func get_ip(): + print(IP.resolve_hostname(get_hostname(), IP.TYPE_IPV4)) + pass + +func _ready(): + var _trash + _trash = get_tree().connect("network_peer_connected", self, "_peer_connected") + _trash = get_tree().connect("network_peer_disconnected", self, "_peer_disconnected") + _trash = get_tree().connect("connected_to_server", self, "_server_connected") + _trash = get_tree().connect("connection_failed", self, "_connection_fail") + _trash = get_tree().connect("server_disconnected", self, "_server_disconnected") + +func _peer_connected(id): + rpc_id(id, "register_peer", local_info) + pass + +func _peer_disconnected(id): + peer_info.erase(id) + pass + +func _server_connected(): + # On connection to the server, you get a global network id + # Save your info at this id + peer_info[get_network_id()] = local_info + connected = true + pass + +func _server_disconnected(): + connected = false + pass + +func _connection_fail(): + connected = false + pass + +remote func register_peer(info): + # Save player information under the sender id's peer info + peer_info[get_tree().get_rpc_sender_id()] = info + pass diff --git a/godot_ship/script/options/Network.gd b/godot_ship/script/options/Network.gd new file mode 100644 index 0000000..0362366 --- /dev/null +++ b/godot_ship/script/options/Network.gd @@ -0,0 +1,5 @@ +extends Node + + +func _ready(): + pass