forked from unfa/liblast
496 lines
15 KiB
GDScript
496 lines
15 KiB
GDScript
extends Node
|
|
|
|
enum GameFocus {MENU, GAME, CHAT, AWAY}
|
|
enum MultiplayerRole {NONE, CLIENT, SERVER, DEDICATED_SERVER, RELAY_SERVER}
|
|
|
|
const NET_PORT = 12597
|
|
|
|
#const NET_SERVER = "liblast.unfa.xyz"
|
|
const NET_SERVER = "localhost"
|
|
|
|
var peer = ENetMultiplayerPeer.new()
|
|
|
|
var role = MultiplayerRole.NONE:
|
|
set(new_role):
|
|
role = new_role
|
|
# print("Multiplayer Role changed to ", MultiplayerRole.keys()[new_role])
|
|
|
|
var player_scene = preload("res://Assets/Characters/Player.tscn")
|
|
|
|
@onready var gui = $GUI
|
|
@onready var hud = $HUD
|
|
@onready var chat = hud.get_node("Chat")
|
|
var local_player: Node = null
|
|
|
|
class PlayerInfo:
|
|
var name: String
|
|
var team: int
|
|
var color: Color
|
|
var focus: GameFocus
|
|
var health: int
|
|
var score: int
|
|
|
|
func _init():#name: String, team: int, color: Color):
|
|
# var player_name = ""
|
|
# for i in range(0, 4):
|
|
# player_name += ['a','b','c', 'd', 'e', 'f'][randi() % 5]
|
|
self.name = ""#player_name
|
|
self.color = Color.WHITE #Color(randf(),randf(),randf())
|
|
self.team = 0
|
|
self.focus = 999
|
|
self.health = 100
|
|
self.score = 0
|
|
|
|
func serialize():
|
|
return {
|
|
'name': self.name,
|
|
'team': str(self.team),
|
|
'color': self.color.to_html(),
|
|
'focus': self.focus,
|
|
'health': self.health,
|
|
'score': self.score,
|
|
}
|
|
func set(name: String, team: int, color: Color, focus: int, health: int, score: int):
|
|
self.name = name
|
|
self.team = team
|
|
self.color = color
|
|
self.focus = focus
|
|
self.health = health
|
|
self.score = score
|
|
|
|
func deserialize(info):
|
|
self.name = info['name']
|
|
self.team = info['team'].to_int()
|
|
self.color = Color.html(info['color'])
|
|
self.focus = info['focus']
|
|
self.health = info['health']
|
|
self.score = info['score']
|
|
#func generate():
|
|
|
|
var uptime = 0 # seconds
|
|
const respawn_delay = 3 # seconds
|
|
const reset_delay = 10 # seconds
|
|
var spawn_queue = {}
|
|
var reset_at = -1
|
|
|
|
var game_score_limit = 15 #15
|
|
|
|
const destroy_free_player_crash_workaround = true
|
|
|
|
func _process(delta):
|
|
uptime += delta
|
|
|
|
#print($GUI.settings)
|
|
|
|
$Label.text = "player_list: \n"
|
|
for i in player_list.players.keys():
|
|
if player_list.players[i]:
|
|
$Label.text += str(i) + " = " + str(player_list.get(i).serialize()) + "\n"
|
|
else:
|
|
$Label.text += str(i) + " = ???"
|
|
|
|
# poll respawn queue
|
|
|
|
$Label.text += "\n\nspawn_queue: \n"
|
|
$Label.text += str(spawn_queue)
|
|
|
|
if local_player:
|
|
$Label.text += "\n\nLOCAL PLAYER DEAD: " + str(local_player.dead)
|
|
$Label.text += "\n\nWEAPON SPREAD: " + str(local_player.weapon.spread) #TODO use this to alter crosshair
|
|
|
|
$Label.text += "\n\nGAME RESET AT: " + str(reset_at)
|
|
|
|
|
|
# respawn queue
|
|
for i in spawn_queue.keys():
|
|
if spawn_queue[i] <= uptime:
|
|
var is_local = true if i == get_tree().multiplayer.get_unique_id() else false
|
|
create_player(i, is_local, true)
|
|
spawn_queue.erase(i)
|
|
|
|
if local_player:
|
|
# 3 seconds left to new round
|
|
if 0 < reset_at and (uptime + 3.5) >= reset_at and (uptime + 3) < reset_at and not $Announcer.is_playing():
|
|
$Announcer.speak($Announcer.getready)
|
|
|
|
# starting new round
|
|
if 0 < reset_at and uptime >= reset_at:
|
|
hud.scoretab(false)
|
|
local_player.dead = false
|
|
reset_at = -1
|
|
$Announcer.speak($Announcer.go)
|
|
for i in player_list.players.keys():
|
|
player_list.players[i].score = 0
|
|
|
|
class PlayerList:
|
|
var players = {}
|
|
|
|
func erase(pid):
|
|
players.erase(pid)
|
|
|
|
func set(pid: int, info: PlayerInfo):
|
|
# if info is PlayerInfo:
|
|
players[pid] = info
|
|
|
|
func get(pid: int) -> PlayerInfo:
|
|
return players[pid]
|
|
|
|
@onready var player_list = PlayerList.new()
|
|
|
|
var focus:GameFocus:
|
|
set(new_focus):
|
|
# print("Focus set to ", new_focus)
|
|
if local_player != null:
|
|
assert(local_player != null, "local_player is not null but it is null, WTF?!")
|
|
match new_focus:
|
|
0: # MENU
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
gui.show()
|
|
hud.hide()
|
|
if local_player:
|
|
local_player.input_active = false
|
|
local_player.rpc(&"focus_banner", true, 0)
|
|
1: # GAME
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
gui.hide()
|
|
hud.show()
|
|
if local_player:
|
|
local_player.input_active = true
|
|
local_player.rpc(&"focus_banner", false)
|
|
2: # CHAT
|
|
if local_player:
|
|
local_player.input_active = false
|
|
local_player.rpc(&"focus_banner", true, 1)
|
|
3: #AWAY
|
|
if local_player:
|
|
local_player.input_active = true
|
|
local_player.rpc(&"focus_banner", true, 2)
|
|
|
|
focus = new_focus
|
|
|
|
func _input(_event) -> void:
|
|
if Input.is_action_just_pressed("ui_cancel"):
|
|
if focus == GameFocus.GAME:
|
|
focus = GameFocus.MENU
|
|
#get_tree().root.set_input_as_handled()
|
|
elif focus == GameFocus.MENU:
|
|
focus = GameFocus.GAME
|
|
#get_tree().root.set_input_as_handled()
|
|
|
|
if Input.is_action_just_pressed("show_scoretab"):
|
|
hud.scoretab(true)
|
|
elif Input.is_action_just_released("show_scoretab"):
|
|
hud.scoretab(false)
|
|
|
|
@rpc(any_peer, call_local, reliable) func game_over(winner):
|
|
if local_player:
|
|
local_player.rpc(&'set_dead', true)
|
|
hud.game_over(winner)
|
|
|
|
spawn_queue.clear()
|
|
for j in player_list.players.keys():
|
|
spawn_queue[j] = uptime + reset_delay
|
|
reset_at = uptime + reset_delay
|
|
|
|
if local_player:
|
|
if winner == local_player.get_multiplayer_authority():
|
|
$Announcer.speak($Announcer.victory)
|
|
elif player_list.get(local_player.get_multiplayer_authority()).score == 0:
|
|
$Announcer.speak($Announcer.defeat2) # embarrasing defeat
|
|
else:
|
|
$Announcer.speak($Announcer.defeat)
|
|
|
|
func check_game_win_condition():
|
|
for i in player_list.players.keys():
|
|
if player_list.players[i].score >= game_score_limit:
|
|
chat.rpc(&'chat_notification', "Player [color=" + player_list.players[i].color.to_html() + "]" + player_list.players[i].name + "[/color] has won this round!")
|
|
rpc(&'game_over', i)
|
|
break
|
|
|
|
func update_hud():
|
|
|
|
### Health
|
|
hud.get_node("Stats").get_node("HealthBar").value = player_list.get( get_tree().multiplayer.get_unique_id() ).health
|
|
|
|
if player_list.players.size() <= 1:
|
|
return # if we're the sole player in the server, stop here
|
|
|
|
### SCORE, RANK, GAP/LEAD
|
|
hud.get_node("ScoreRank").text = "SCORE: " + str(player_list.get( get_tree().multiplayer.get_unique_id() ).score)
|
|
|
|
var score = player_list.get( get_tree().multiplayer.get_unique_id() ).score
|
|
var scores = []
|
|
|
|
for i in player_list.players.values():
|
|
scores.append(i.score)
|
|
|
|
scores.sort()
|
|
scores.reverse()
|
|
var rank = scores.find(score) + 1
|
|
|
|
scores.remove(scores.find(score))
|
|
scores.sort()
|
|
scores.reverse()
|
|
|
|
var lead = score - scores[0]
|
|
|
|
hud.get_node("ScoreRank").text = "SCORE: " + str(score) + "\nRANK: "
|
|
|
|
if lead > 0:
|
|
hud.get_node("ScoreRank").text += str(rank) + "\nLEAD: " + str(lead)
|
|
else:
|
|
hud.get_node("ScoreRank").text += str(rank) + "\nGAP: " + str(-lead)
|
|
|
|
@rpc(any_peer, reliable) func player_list_update(info, pid = get_tree().get_rpc_sender_id(), erase:=false):
|
|
if erase:
|
|
player_list.erase(pid)
|
|
|
|
var new_info = PlayerInfo.new()
|
|
new_info.deserialize(info)
|
|
|
|
if player_list.players.has(pid):
|
|
var old_name = player_list.get(pid).name
|
|
if old_name != new_info.name:
|
|
chat.chat_notification("Player [b]" + old_name + "[/b] changed name to [b]" + new_info.name + "[/b]")
|
|
else:
|
|
create_player(pid, false)
|
|
|
|
if $Players.has_node(str(pid)):
|
|
var player = $Players.get_node(str(pid))
|
|
player.update_player(new_info)
|
|
|
|
player_list.set(pid, new_info) # server relays other PID's data
|
|
|
|
# update local HUD
|
|
if local_player:
|
|
update_hud()
|
|
#check_game_win_condition()
|
|
|
|
func push_local_player_info(): #
|
|
var pid = get_tree().multiplayer.get_unique_id()
|
|
rpc(&'player_list_update', player_list.get(pid).serialize(), pid)
|
|
|
|
@rpc(call_local, any_peer, reliable) func destroy_player(pid: int):
|
|
print("DESTROY_PLAYER called on ", get_tree().multiplayer.get_unique_id(), " by ", get_tree().multiplayer.get_remote_sender_id(), " regarding ", pid)
|
|
|
|
#assert($Players.has_node(str(pid)), "Attempting to destroy a player that does not exist")
|
|
|
|
var player_node
|
|
|
|
if $Players.has_node(str(pid)):
|
|
player_node = $Players.get_node(str(pid))
|
|
else:
|
|
print("Destroying a player node that's not there")
|
|
return
|
|
|
|
#assert(player_node != null, "Attempting to delete a player node that does not exist")
|
|
|
|
# alternative finale to this function that is a workaround to avoid Godot crashing due to an engine bug
|
|
# https://github.com/godotengine/godot/issues/52853
|
|
if destroy_free_player_crash_workaround:
|
|
print("Setting player ", pid, " as DEAD")
|
|
player_node.set_dead(true)
|
|
else: # regular method follows
|
|
player_node.name = str(player_node.name) + "_dead" # avoids name collision when instancing another player scene
|
|
|
|
print("before free")
|
|
player_node.queue_free()
|
|
print("after free")
|
|
# respawn queue applies ot both implementations of death
|
|
spawn_queue[pid] = uptime + respawn_delay
|
|
|
|
if pid == get_tree().multiplayer.get_unique_id():
|
|
update_hud()
|
|
|
|
func create_player(pid: int, is_local:= false, respawn:= false) -> void:
|
|
if destroy_free_player_crash_workaround:
|
|
var player_node
|
|
if $Players.has_node(str(pid)): # if this is a respawn
|
|
player_node = $Players.get_node(str(pid))
|
|
#print("Setting player ", pid, " as ALIVE")
|
|
player_node.set_dead(false)
|
|
|
|
var spawnpoint = $Map/SpawnPoints.get_children()[randi() % len($Map/SpawnPoints.get_children())]
|
|
player_node.global_transform = spawnpoint.global_transform
|
|
player_list.players[pid].health = 100
|
|
|
|
if local_player:
|
|
push_local_player_info()
|
|
|
|
#focus = GameFocus.GAME # make sure the player has no menu displayed when they respawn
|
|
return # avoid running the rest of this function
|
|
else:
|
|
pass # if this is not a respawn, let it carry on
|
|
var new_player
|
|
new_player = player_scene.instantiate()
|
|
|
|
var spawnpoint = $Map/SpawnPoints.get_children()[randi() % len($Map/SpawnPoints.get_children())]
|
|
new_player.name = str(pid)
|
|
new_player.global_transform = spawnpoint.global_transform
|
|
new_player.set_multiplayer_authority(pid)
|
|
$Players.add_child(new_player)
|
|
|
|
if not respawn and is_local:
|
|
var new_info = PlayerInfo.new()
|
|
new_info.name = $GUI.settings["username"]
|
|
new_info.color = $GUI.settings["player color"]
|
|
player_list.set(pid, new_info)
|
|
push_local_player_info()
|
|
|
|
if is_local:
|
|
local_player = new_player
|
|
|
|
|
|
|
|
# if not respawn: # first spawn
|
|
# new_info = PlayerInfo.new()
|
|
# if is_local:
|
|
# new_info.name = $GUI.settings["username"]
|
|
# new_info.color = $GUI.settings["player color"]
|
|
# else: # respawn
|
|
# new_info = player_list.players[pid] # reuse previous name, color etc
|
|
# new_info.health = 100 # give the respawned player full health
|
|
# focus = GameFocus.GAME # make sure the player has no menu displayed when they respawn
|
|
|
|
|
|
|
|
# superfluous and harmful code that messes up player data:
|
|
# if get_tree().multiplayer.get_multiplayer_unique_id() != 1: # if we're not the server - update the server
|
|
# rpc_id(1, &'player_list_update', new_info.serialize(), pid) # send local player info to the server
|
|
# else: # if we're the server, update the clients
|
|
# rpc(&'player_list_update', new_info.serialize(), pid)
|
|
|
|
# if is_local:
|
|
# local_player = new_player
|
|
# if not get_tree().multiplayer.is_server():
|
|
# push_local_player_info()
|
|
|
|
func start_dedicated_server(): # start server without creating a local player
|
|
role = MultiplayerRole.DEDICATED_SERVER
|
|
peer.create_server(NET_PORT, 16)
|
|
get_tree().multiplayer.multiplayer_peer = peer
|
|
|
|
get_viewport().debug_draw = Viewport.DebugDraw.DEBUG_DRAW_OVERDRAW
|
|
get_viewport().msaa = Viewport.MSAA.MSAA_DISABLED
|
|
get_viewport().use_debanding = false
|
|
get_viewport().screen_space_aa = 0
|
|
get_viewport().lod_threshold = 128
|
|
|
|
get_viewport().get_camera_3d().effects = null
|
|
get_viewport().shadow_atlas_size = 64
|
|
|
|
|
|
$Map/Decals.hide()
|
|
$Map/Lights.hide()
|
|
$Map/ReflectionProbes.hide()
|
|
|
|
AudioServer.set_bus_mute(0, true) # mute sound
|
|
|
|
$Label.show()
|
|
|
|
#get_tree().root.title = "LIBLAST DEDICATED SERVER"
|
|
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
gui.hide()
|
|
hud.show()
|
|
|
|
hud.get_node("ScoreRank").hide()
|
|
hud.get_node("Stats").hide()
|
|
hud.get_node("Crosshair").hide()
|
|
hud.scoretab(true)
|
|
|
|
func host_server():
|
|
_on_Host_pressed()
|
|
|
|
func connect_to_server(ip):
|
|
$NetworkTesting/Host.disabled = true
|
|
$NetworkTesting/Connect.disabled = true
|
|
|
|
peer.create_client(ip, NET_PORT)
|
|
get_tree().multiplayer.multiplayer_peer = peer
|
|
|
|
func _on_Host_pressed(): # start server and create a local player
|
|
role = MultiplayerRole.SERVER
|
|
$NetworkTesting/Host.disabled = true
|
|
$NetworkTesting/Connect.disabled = true
|
|
|
|
peer.create_server(NET_PORT, 16)
|
|
get_tree().multiplayer.multiplayer_peer = peer
|
|
create_player(1, true)
|
|
focus = GameFocus.GAME
|
|
chat.chat_notification("Started server")
|
|
|
|
func _on_Connect_pressed():
|
|
$NetworkTesting/Host.disabled = true
|
|
$NetworkTesting/Connect.disabled = true
|
|
|
|
peer.create_client(NET_SERVER, NET_PORT)
|
|
get_tree().multiplayer.multiplayer_peer = peer
|
|
|
|
func _player_connected(pid) -> void:
|
|
print("peer connected, id: ", pid)
|
|
|
|
if get_tree().multiplayer.is_server(): # if we're the server
|
|
for i in player_list.players.keys(): # send the player_list to the new client
|
|
#pass
|
|
rpc_id(pid, &'player_list_update', player_list.get(i).serialize(), i) # send local player info to the server
|
|
|
|
# if local_player:
|
|
# local_player.rpc(&"update_info")
|
|
#
|
|
# if role in [MultiplayerRole.SERVER, MultiplayerRole.DEDICATED_SERVER]:
|
|
# rpc_id(id, "player_list_update", player_list)
|
|
|
|
func _player_disconnected(pid) -> void:
|
|
print("player disconnected, id: ", pid)
|
|
spawn_queue.erase(pid)
|
|
if get_tree().multiplayer.is_server():
|
|
chat.rpc(&'chat_notification', "Player [b]" + player_list.get(pid).name + "[/b] left")
|
|
player_list.erase(pid)
|
|
rpc(&'player_list_update', null, pid, true)
|
|
rpc(&'destroy_player', pid)
|
|
|
|
func _connected_ok() -> void:
|
|
print("connected to server")
|
|
chat.chat_notification("Connected to server")
|
|
var pid = get_tree().multiplayer.get_unique_id()
|
|
create_player(pid, true)
|
|
focus = GameFocus.GAME
|
|
role = MultiplayerRole.CLIENT
|
|
|
|
func _connected_fail() -> void:
|
|
print("connection to server failed")
|
|
chat.chat_notification("Connection failed")
|
|
|
|
func _server_disconnected() -> void:
|
|
print("server disconnected")
|
|
role = MultiplayerRole.NONE
|
|
chat.chat_notification("Server disconnected")
|
|
|
|
|
|
func _ready() -> void:
|
|
hud.hide() # start in the menu
|
|
focus = GameFocus.MENU
|
|
|
|
get_tree().multiplayer.connect("peer_connected", self._player_connected)
|
|
get_tree().multiplayer.connect("peer_disconnected", self._player_disconnected)
|
|
get_tree().multiplayer.connect("connected_to_server", self._connected_ok)
|
|
get_tree().multiplayer.connect("connection_failed", self._connected_fail)
|
|
get_tree().multiplayer.connect("server_disconnected", self._server_disconnected)
|
|
|
|
if OS.get_cmdline_args().has("--dedicated-server"):
|
|
start_dedicated_server()
|
|
|
|
func change_player_color(color):
|
|
if get_tree().multiplayer.has_multiplayer_peer():
|
|
player_list.players[get_tree().multiplayer.get_unique_id()].color = color
|
|
push_local_player_info()
|
|
|
|
func change_player_name(player_name):
|
|
if get_tree().multiplayer.has_multiplayer_peer():
|
|
player_list.players[get_tree().multiplayer.get_unique_id()].name = player_name
|
|
push_local_player_info()
|
|
|
|
|
|
func _on_Disconnect_pressed():
|
|
get_tree().multiplayer.multiplayer_peer = null
|