A Libre Multiplayer FPS Game built with Godot 4 engine and a fully open-source toolchain https://libla.st
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
Liblast/Game/LegacyMain.gd

463 lines
15 KiB

class_name Main
extends Node
# Main game loop.
const player_scene = preload("res://Assets/Characters/Player.tscn")
const destroy_free_player_crash_workaround := true
@onready var menu_root := $MenuRoot
@onready var hud := $HUD
@onready var chat := hud.get_node("Chat")
@onready var game_server = load("Server.gd").new()
@onready var player_list := PlayerList.new()
var server_message: String
var local_player_focus_previous: Globals.Focus # to store focus that should be set after AWAY is gone
var mute_previous: bool
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 MultiplayerState.local_player:
local_player_focus_previous = focus
focus = Globals.Focus.AWAY
NOTIFICATION_APPLICATION_FOCUS_IN:
# `0` means "unlimited".
Engine.target_fps = 0
set_mute(mute_previous)
if MultiplayerState.local_player:
focus = local_player_focus_previous
func _process(delta):
MultiplayerState.uptime += delta
$Label.text = "player_list: \n"
for i in player_list.players.keys():
if player_list.players[i]:
$Label.text += "%s = %s\n" % [str(i), str(player_list.get_item(i).serialize())]
else:
$Label.text += "%s = ???" % str(i)
# poll respawn queue
$Label.text += "\n\nspawn_queue: \n"
$Label.text += str(MultiplayerState.spawn_queue)
if MultiplayerState.local_player != null:
$Label.text += "\n\nLOCAL PLAYER DEAD: %s" % str(MultiplayerState.local_player.dead)
$Label.text += "\n\nWEAPON SPREAD: %s" % str(MultiplayerState.local_player.weapon.spread) #TODO: use this to alter crosshair
$Label.text += "\n\nGAME RESET AT: %s" % str(MultiplayerState.reset_at)
# respawn queue
for i in MultiplayerState.spawn_queue.keys():
if MultiplayerState.spawn_queue[i] <= MultiplayerState.uptime:
var is_local: bool = true if i == get_tree().get_multiplayer().get_unique_id() else false
create_player(i, is_local, true)
MultiplayerState.spawn_queue.erase(i)
if MultiplayerState.local_player:
# 3 seconds left to new round
if 0 < MultiplayerState.reset_at and (MultiplayerState.uptime + 3.5) >= MultiplayerState.reset_at and (MultiplayerState.uptime + 3) < MultiplayerState.reset_at and not $Announcer.is_playing():
$Announcer.speak($Announcer.getready)
# starting new round
if 0 < MultiplayerState.reset_at and MultiplayerState.uptime >= MultiplayerState.reset_at:
hud.scoretab(false)
MultiplayerState.local_player.dead = false
MultiplayerState.reset_at = -1
$Announcer.speak($Announcer.go)
for i in player_list.players.keys():
player_list.players[i].score = 0
var focus : Globals.Focus:
set(new_focus):
if new_focus == focus:
print("Attemped tp set focus to it's previous value, skipping")
return
if MultiplayerState.local_player != null:
assert(MultiplayerState.local_player != null, "MultiplayerState.local_player is not null but it is null, WTF?!")
match new_focus:
0: # MENU
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
menu_root.show()
hud.hide()
if MultiplayerState.local_player:
MultiplayerState.local_player.input_active = false
MultiplayerState.local_player.rpc(&"focus_banner", true, 0)
1: # GAME
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
menu_root.hide()
hud.show()
if MultiplayerState.local_player:
MultiplayerState.local_player.input_active = true
MultiplayerState.local_player.rpc(&"focus_banner", false)
2: # CHAT
if MultiplayerState.local_player:
MultiplayerState.local_player.input_active = false
MultiplayerState.local_player.rpc(&"focus_banner", true, 1)
3: #AWAY
if MultiplayerState.local_player:
MultiplayerState.local_player.input_active = true
MultiplayerState.local_player.rpc(&"focus_banner", true, 2)
focus = new_focus
func _unhandled_input(_event) -> void:
if Input.is_action_just_pressed("ui_cancel"):
if focus == Globals.Focus.GAME:
focus = Globals.Focus.MENU
#get_tree().root.set_input_as_handled()
elif focus == Globals.Focus.MENU:
focus = Globals.Focus.GAME
get_viewport().set_input_as_handled()
if Input.is_action_just_pressed("show_scoretab"):
hud.scoretab(true)
get_viewport().set_input_as_handled()
elif Input.is_action_just_released("show_scoretab"):
hud.scoretab(false)
get_viewport().set_input_as_handled()
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))
get_viewport().set_input_as_handled()
if Input.is_action_just_pressed("mute_audio"):
set_mute(null)
get_viewport().set_input_as_handled()
@rpc(any_peer, call_local, reliable) func game_over(winner) -> void:
if MultiplayerState.local_player:
MultiplayerState.local_player.die(-1)
hud.game_over(winner)
MultiplayerState.spawn_queue.clear()
for j in player_list.players.keys():
MultiplayerState.spawn_queue[j] = MultiplayerState.uptime + MultiplayerState.reset_delay
MultiplayerState.reset_at = MultiplayerState.uptime + MultiplayerState.reset_delay
if MultiplayerState.local_player:
if winner == MultiplayerState.local_player.get_multiplayer_authority():
$Announcer.speak($Announcer.victory)
elif player_list.get_item(MultiplayerState.local_player.get_multiplayer_authority()).score == 0:
$Announcer.speak($Announcer.defeat2) # embarrasing defeat
else:
$Announcer.speak($Announcer.defeat)
func check_game_win_condition() -> void:
for i in player_list.players.keys():
if player_list.players[i].score >= MultiplayerState.game_score_limit:
var notif := "Player [color=\"%s\"]%s[/color] has won this round!" % [
player_list.players[i].color.to_html(),
player_list.players[i].name
]
chat.rpc(&'chat_notification', notif)
rpc(&'game_over', i)
break
func update_jetpack_bar(value: float) -> void:
var jetpack_bar := hud.get_node("Stats").get_node("JetpackBar")
jetpack_bar.value = value
jetpack_bar.get_node("Label").text = str(round(value * 100)) + "%"
func update_ammo_info(update_reload_ammo_bar: bool) -> void:
var pid : int = get_multiplayer_authority()
var player := $Players.get_node(str(pid))
if player == null:
print_debug("Attempting to updade HUD taking info from a null player node")
return
var weapon = player.weapon
var ammo : int = weapon.ammo
var ammo_bar := hud.get_node("Stats").get_node("AmmoBar")
ammo_bar.max_value = weapon.max_ammo
ammo_bar.value = ammo
ammo_bar.get_node("Label").text = str(weapon.ammo) + " / " + str(weapon.max_ammo)
if update_reload_ammo_bar:
var reload_ammo_bar := hud.get_node("Stats").get_node("ReloadAmmoBar")
reload_ammo_bar.max_value = weapon.max_reload_ammo
reload_ammo_bar.value = weapon.reload_ammo
reload_ammo_bar.get_node("Label").text = str(weapon.reload_ammo) + " / " + str(weapon.max_reload_ammo)
var ammo_label := hud.get_node("AmmoLabel")
if ammo > 1:
ammo_label.text = ""
elif ammo == 1:
ammo_label.text = "Low Ammo!"
else:
ammo_label.text = "No Ammo!"
@rpc(any_peer, reliable) func player_list_update(info, pid = get_tree().get_rpc_sender_id(), erase:=false) -> void:
if erase:
player_list.erase(pid)
MultiplayerState.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_item(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_item(pid, new_info) # server relays other PID's data
# update local HUD
if MultiplayerState.local_player:
hud.update_hud()
#check_game_win_condition()
func push_local_player_info() -> void:
var pid := get_tree().get_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_item(pid).serialize(), pid)
@rpc(call_local, any_peer, reliable) func destroy_player(pid: int) -> void:
print("DESTROY_PLAYER called on ", get_tree().get_multiplayer().get_unique_id(), " by ", get_tree().get_multiplayer().get_remote_sender_id(), " regarding ", pid)
var player_node : 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
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 to both implementations of death
if player_list.players.has(pid): # don't respawn players that are not there, lol
MultiplayerState.spawn_queue[pid] = MultiplayerState.uptime + MultiplayerState.respawn_delay
if pid == get_tree().get_multiplayer().get_unique_id():
hud.update_hud()
func create_player(pid: int, is_local:= false, respawn:= false) -> void:
#var player_node: Node
var new_player := player_scene.instantiate()
var spawnpoint := $Map/SpawnPoints.get_children()[randi() % len($Map/SpawnPoints.get_children())]
#player_node.spawn(spawnpoint.global_transform)
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 = Settings.get_var('player_name')
new_info.color = Settings.get_var('player_color')
player_list.set_item(pid, new_info)
push_local_player_info()
if is_local:
MultiplayerState.local_player = new_player
func host_server():
_on_Host_pressed()
func commit_suicide():
MultiplayerState.local_player.rpc(&"die", MultiplayerState.local_player.get_multiplayer_authority())
focus = Globals.Focus.GAME
func connect_to_server(ip):
$NetworkTesting/Host.disabled = true
$NetworkTesting/Connect.disabled = true
MultiplayerState.peer.create_client(ip, Globals.NET_PORT)
get_tree().get_multiplayer().multiplayer_peer = MultiplayerState.peer
func _on_Host_pressed(): # start server and create a local player
$NetworkTesting/Host.disabled = true
$NetworkTesting/Connect.disabled = true
game_server.start_server()
get_tree().get_multiplayer().multiplayer_peer = MultiplayerState.peer
create_player(1, true)
print(player_list.players)
focus = Globals.Focus.GAME
chat.chat_notification("Started server")
func _on_Connect_pressed():
$NetworkTesting/Host.disabled = true
$NetworkTesting/Connect.disabled = true
MultiplayerState.peer.create_client(Globals.NET_SERVER, Globals.NET_PORT)
get_tree().get_multiplayer().multiplayer_peer = MultiplayerState.peer
func _player_connected(pid) -> void:
print("peer connected, id: ", pid)
if get_tree().get_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_item(i).serialize(), i) # send local player info to the server
chat.rpc_id(pid, &'chat_notification', server_message)
@rpc(call_remote, any_peer, reliable) func disconnect_player(pid):
print("player disconnected, id: ", pid)
MultiplayerState.spawn_queue.erase(pid)
if get_tree().get_multiplayer().is_server():
chat.rpc(&'chat_notification', "Player [b]" + player_list.get_item(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().get_multiplayer().get_unique_id()
create_player(pid, true)
focus = Globals.Focus.GAME
MultiplayerState.role = Globals.MultiplayerRole.CLIENT
func _connected_fail() -> void:
print("connection to server failed")
chat.chat_notification("Connection failed")
func _server_disconnected() -> void:
print("server disconnected")
MultiplayerState.role = Globals.MultiplayerRole.NONE
chat.chat_notification("Server disconnected")
func _ready() -> void:
hud.hide() # start in the menu
focus = Globals.Focus.MENU
local_player_focus_previous = focus
get_tree().get_multiplayer().connect("peer_connected", self._player_connected)
get_tree().get_multiplayer().connect("peer_disconnected", self._player_disconnected)
get_tree().get_multiplayer().connect("connected_to_server", self._connected_ok)
get_tree().get_multiplayer().connect("connection_failed", self._connected_fail)
get_tree().get_multiplayer().connect("server_disconnected", self._server_disconnected)
var out = Settings.check_version()
var ev := Engine.get_version_info()
server_message = "Welcome to Liblast server on %s game version: %s\n engine version: %s [%s]" % [
out[0],
Settings.version,
ev['string'],
ev['hash'].left(10)
]
# Check for dedicated server
if "--dedicated-server" in OS.get_cmdline_args():
#get_tree().root.title = "Liblast Dedicated Server"
set_server_hud()
game_server.start_server(Globals.MultiplayerRole.DEDICATED_SERVER)
get_tree().get_multiplayer().multiplayer_peer = MultiplayerState.peer
print(server_message)
else:
$Announcer.speak($Announcer.welcome)
chat.chat_notification(server_message)
# Set up an interface for dedicated server HUD
func set_server_hud():
get_viewport().debug_draw = Viewport.DebugDraw.DEBUG_DRAW_OVERDRAW
get_viewport().msaa = Viewport.MSAA.MSAA_DISABLED
get_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
get_viewport().use_debanding = false
get_viewport().disable_3d = true
if not get_viewport().disable_3d:
get_viewport().get_camera_3d().effects = null
get_viewport().shadow_atlas_size = 64
$Map/Decals.hide()
$Map/Lights.hide()
$Map/ReflectionProbes.hide()
$MenuRoot.hide()
$Label.show()
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
$HUD.show()
$HUD.get_node("ScoreRank").hide()
$HUD.get_node("Stats").hide()
$HUD.get_node("Crosshair").hide()
$HUD.scoretab(true)
func change_player_color(color):
if get_tree().get_multiplayer().has_multiplayer_peer():
player_list.players[get_tree().get_multiplayer().get_unique_id()].color = color
push_local_player_info()
func change_player_name(player_name):
if get_tree().get_multiplayer().has_multiplayer_peer():
player_list.players[get_tree().get_multiplayer().get_unique_id()].name = player_name
push_local_player_info()
@rpc(any_peer, reliable) func disconnect_self():
if multiplayer.is_server():
chat.rpc(&'chat_notification', "Server has ended the game")
rpc(&'disconnect_self') # make everyone disconnect
else:
var notif := "Player %s left the game" % player_list.get_item(get_multiplayer_authority()).name
chat.rpc(&'chat_notification', notif)
rpc(&'disconnect_player', get_multiplayer_authority())
get_tree().get_multiplayer().multiplayer_peer = null
MultiplayerState.role = Globals.MultiplayerRole.NONE
focus = Globals.Focus.MENU