This repository has been archived on 2022-01-09. You can view files and clone it, but cannot push or open issues/pull-requests.
liblast/Game/Main.gd

587 lines
18 KiB
GDScript

extends Node
enum GameFocus {MENU, GAME, CHAT, AWAY}
enum MultiplayerRole {NONE, CLIENT, SERVER, DEDICATED_SERVER, RELAY_SERVER}
const NET_PORT = 12597
var server_message : String
var version : String
#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
var local_player_focus_previous: GameFocus # to store focus that should be set after AWAY is gone
var mute_previous: bool
class PlayerInfo:
var name: String
var team: int
var color: Color
var focus: GameFocus
var health: int
var score: int
var ping: int
var packet_loss: 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
self.ping = -1
self.packet_loss = -1
func serialize():
return {
'name': self.name,
'team': str(self.team),
'color': self.color.to_html(),
'focus': self.focus,
'health': self.health,
'score': self.score,
'ping': self.ping,
'loss': self.packet_loss,
}
func set(name: String, team: int, color: Color, focus: int, health: int, score: int, ping: int, packet_loss: int):
self.name = name
self.team = team
self.color = color
self.focus = focus
self.health = health
self.score = score
self.ping = ping
self.packet_loss = packet_loss
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']
self.ping = info['ping']
self.packet_loss = info['loss']
#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 = 10 #15
const destroy_free_player_crash_workaround = true
func get_mute() -> bool:
return AudioServer.is_bus_mute(0)
func set_mute(mute) -> void:
if mute == null: # toggle
AudioServer.set_bus_mute(0, not get_mute())
else:
AudioServer.set_bus_mute(0, mute)
# update the HUD icon
hud.get_node("MuteIcon").visible = get_mute()
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_FOCUS_OUT:
Engine.target_fps = 5
mute_previous = get_mute()
set_mute(true)
if local_player:
local_player_focus_previous = focus
focus = GameFocus.AWAY
NOTIFICATION_APPLICATION_FOCUS_IN:
# `0` means "unlimited".
Engine.target_fps = 0
set_mute(mute_previous)
if local_player:
focus = local_player_focus_previous
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:
if players.has(pid):
return players[pid]
else:
return PlayerInfo.new()
@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)
if Input.is_action_just_pressed("screenshot"):
var res = get_viewport().get_texture().get_image().save_exr('res://Screnshot.exr')
chat.chat_notification("Screenshot taken: " + str(res))
if Input.is_action_just_pressed("mute_audio"):
set_mute(null)
@rpc(any_peer, call_local, reliable) func game_over(winner):
if local_player:
local_player.die(-1)
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_at(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)
spawn_queue.erase(pid)
get_node("Players").get_node(str(pid)).die(-1)
return
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()
assert(pid >= 1, "Another server must be running. PID is 0, that means Enet did not initialize.")
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:
pass
#print("Setting player ", pid, " as DEAD")
if not player_node.dead: # disconnected, not killed
player_node.die(-1)
return # skip the rest - don't add to respawn queue
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
if player_list.players.has(pid): # don't respawn players that are not there, lol
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.spawn(spawnpoint.global_transform)
# 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 commit_suicide():
local_player.rpc(&"die", local_player.get_multiplayer_authority())
focus = GameFocus.GAME
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
chat.rpc_id(pid, &'chat_notification', server_message)
# if local_player:
# local_player.rpc(&"update_info")
#
# if role in [MultiplayerRole.SERVER, MultiplayerRole.DEDICATED_SERVER]:
# rpc_id(id, "player_list_update", player_list)
@rpc(call_remote, any_peer, reliable) func disconnect_player(pid):
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 _player_disconnected(pid) -> void:
disconnect_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
focus = GameFocus.GAME
local_player_focus_previous = focus
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)
# update game build version, if we're running in the editor
if Engine.get_version_info():
OS.execute('bash', ["./version.sh"])
var file = File.new()
file.open("res://version", File.READ)
version = file.get_as_text()
file.close()
var out = []
OS.execute('hostname', [], out)
var ev = Engine.get_version_info()
server_message = "Welcome to Liblast server on " + out[0] + "game version: " + version + "\nengine version: " + ev['string'] + " [" + ev['hash'].left(10) + "]"
$Announcer.speak($Announcer.welcome)
if OS.get_cmdline_args().has("--dedicated-server"):
start_dedicated_server()
print(server_message)
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