Initial commit

This commit is contained in:
2026-01-21 23:51:53 +01:00
commit 60b208fee0
1703 changed files with 100223 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
extends Timer
class_name ButtonLogicImpulse
@export var interactBox: InteractBox
signal onPressed
func _ready() -> void:
interactBox.interactedWith.connect(onInteraction)
one_shot = true
func onInteraction(_playerReference: PlayerCharacter) -> void:
if is_stopped():
onPressed.emit()
start()

View File

@@ -0,0 +1 @@
uid://cjiiw7cybj24b

View File

@@ -0,0 +1,12 @@
extends Node
class_name ButtonLogicToggle
@export var interactBox: InteractBox
@export var state: bool = false #Is it on or off? :) hope this helps it is currently 1:42 am
func _ready() -> void:
interactBox.interactedWith.connect(onInteraction)
func onInteraction(_playerReference: PlayerCharacter) -> void:
state = !state

View File

@@ -0,0 +1 @@
uid://b0k1i0242ren6

41
script/MapLogic.gd Normal file
View File

@@ -0,0 +1,41 @@
extends Node3D
class_name MapLogic
signal onCollision
signal missionLost
@export var debugObj: PackedScene
@export var pathPivot: Node3D
@export var drawDebugCubesPathfinding:= false
@export var pathfindingGridLoader: PathfindingGridLoader
var astar : AStar2D
@export var playerStartPos: Vector3
func _ready() -> void:
if pathfindingGridLoader: pathfindingGridLoader.loadGrid()
func looseMission() -> void:
missionLost.emit()
func finishAstarSetup() -> void: ##Run this in process or physics process of the child map class
if pathfindingGridLoader:
if pathfindingGridLoader.loading_done:
astar = pathfindingGridLoader.astar
print(astar.get_point_count())
pathfindingGridLoader.queue_free()
if drawDebugCubesPathfinding: #Only for debugging
for point in astar.get_point_ids():
var point_position: Vector2 = astar.get_point_position(point)
addObject(debugObj,pathPivot,Vector3(point_position.x,0.2,point_position.y))
func addObject(AddedObject:PackedScene, Parent: Node, Position: Vector3, Rotation: Vector3= Vector3(0,0,0)):
var obj = AddedObject.instantiate()
Parent.add_child(obj)
obj.position = Position
obj.rotation = Rotation
return obj

1
script/MapLogic.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cbjn7f7r081q8

41
script/ShipLogic.gd Normal file
View File

@@ -0,0 +1,41 @@
extends MapLogic
class_name ShipLogic
var roomList: Array[BasicRoom]
var power: bool = true
var hullBreached: bool = false
const HULL_BREACHED_CONDITION_DROP_SPEED: float = 0.8
var shipCondition: float = 100.0
var shipFuel: float = 100.0
@export var controllRoom: ControlRoom
@export var breakableRoom: CorridorDeadEndBreakable
func _process(delta: float) -> void:
finishAstarSetup()
if Multiplayer.alivePlayerDict.size() == 0:
looseMission()
if hullBreached:
shipCondition -= HULL_BREACHED_CONDITION_DROP_SPEED * delta
updateShipConditionScreen()
func addRoomToRoomList(room: BasicRoom):
roomList.push_back(room)
func updateLights():
for room in roomList:
room.showLights(power)
func collisionWithAstroid():
onCollision.emit()
hullBreached = true
shipCondition -= 10
if breakableRoom:
breakableRoom.updateWallVisibility(false)
func updateShipConditionScreen():
if controllRoom:
controllRoom.shipConditionDisplayNumber = clamp(shipCondition + 1,0,100)

1
script/ShipLogic.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://c7ea7hd1t6ucj

180
script/audioManager.gd Normal file
View File

@@ -0,0 +1,180 @@
extends Node
var current_sample_rate: int = 48000
var has_loopback: bool = false
var max_buffer_size: int = 1000
var voicechat_player_path : NodePath = ^"VoiceChat"
var sample_rate_min : int = 11025
var sample_rate_max : int = 48000
var local_playback: AudioStreamGeneratorPlayback = null
var local_voice_buffer: PackedByteArray = PackedByteArray()
var network_playback: Dictionary[int, AudioStreamGeneratorPlayback] = {}
var network_voice_buffer: Dictionary[int, PackedByteArray] = {}
@onready var loopback_player : AudioStreamPlayer= $/root/Main/VoiceLoopback
func _ready() -> void:
if not Steamworks.is_initiallized:
push_warning("[VoiceChat] VoiceChat disabled due to SteamAPI.")
return
if has_loopback:
print("[VoiceChat] Loopback on")
record_voice(true)
loopback_player.stream.mix_rate = current_sample_rate
loopback_player.play()
local_playback = loopback_player.get_stream_playback()
multiplayer.peer_disconnected.connect(remove_disconnected_peer)
Multiplayer.player_ready.connect(add_joined_peer)
func _process(_delta: float) -> void:
if not Steamworks.is_initiallized:
return
check_for_voice_local()
func add_joined_peer(id): # Fix calling this on yourself
var player : Node3D = Multiplayer.playerDict.get(id)
var remote_audio_stream_playback = player.get_node_or_null(voicechat_player_path).get_stream_playback()
network_playback.set(id, remote_audio_stream_playback)
network_voice_buffer.set(id,PackedByteArray())
current_sample_rate = Steam.getVoiceOptimalSampleRate()
send_sample_rate.rpc_id(id, current_sample_rate)
func remove_disconnected_peer(id):
network_playback.erase(id)
network_voice_buffer.erase(id)
func check_for_voice_local() -> void:
var available_voice: Dictionary = Steam.getAvailableVoice()
if !available_voice:
return
# Seems there is voice data
if available_voice['result'] == Steam.VOICE_RESULT_OK and available_voice['buffer'] > 0:
# Valve's getVoice uses 1024 but GodotSteam's is set at 8192?
# Our sizes might be way off; internal GodotSteam notes that Valve suggests 8kb
# However, this is not mentioned in the header nor the SpaceWar example but -is- in Valve's docs which are usually wrong
var voice_data: Dictionary = Steam.getVoice()
if voice_data['result'] == Steam.VOICE_RESULT_OK and voice_data['written']:
#print("[VoiceChat] Voice message has data: %s / %s" % [voice_data['result'], voice_data['written']])
# Here we can pass this voice data off on the network
#TODO: Fix this shit: Only execute if multiplayer connected -> Fix resetting multiplayer.peer
get_sample_rate()
send_voice.rpc(voice_data['buffer'])
# If loopback is enable, play it back at this point
if has_loopback:
# Our sample rate function above without toggling
process_voice_data(voice_data['buffer'], 1)
@rpc("any_peer","call_remote","unreliable")
func send_voice(voice_buffer : PackedByteArray):
var sender_id : int = multiplayer.get_remote_sender_id()
if not network_playback.has(sender_id):
push_error("[VoiceChat] Received audio data, but there is no AudioStreamPlayer available for that user. Emitter: " + str(sender_id))
return
if not network_voice_buffer.has(sender_id):
push_error("[VoiceChat] Received audio data without allocated voice buffer for that user. Emitter: " + str(sender_id))
return
var local_peer_buffer : PackedByteArray = network_voice_buffer.get(sender_id)
if local_peer_buffer.size() >= max_buffer_size:
push_error("[VoiceChat] AudioBuffer exeeding size limit. Emitter: " + str(sender_id))
process_voice_data(voice_buffer, sender_id)
@rpc("any_peer","call_remote","reliable")
func send_sample_rate(sample_rate : int):
var sender_id : int = multiplayer.get_remote_sender_id()
if sample_rate < sample_rate_min || sample_rate > sample_rate_max:
push_warning("[VoiceChat] Received invalid sample rate for peer. Emitter: " + str(sender_id))
return
if not network_playback.has(sender_id):
push_warning("[VoiceChat] Received sample rate for invalid peer. Emitter: " + str(sender_id)) # Race condition?
return
if not Multiplayer.playerDict.has(sender_id):
push_error("[VoiceChat] Player exists in VoiceChat variables, but not playerDict??? Emitter: " + str(sender_id))
return
if not Multiplayer.playerDict.has(sender_id): return
var player : Node3D = Multiplayer.playerDict.get(sender_id)
if not player.has_node(voicechat_player_path): return
player.get_node(voicechat_player_path).stream.mix_rate = sample_rate # Fix this (ref: process_voice_data TODO:_1
network_playback.get(sender_id).clear_buffer()
network_voice_buffer.get(sender_id).clear()
func get_sample_rate() -> void:
current_sample_rate = Steam.getVoiceOptimalSampleRate()
if loopback_player.stream.mix_rate != current_sample_rate:
send_sample_rate.rpc(current_sample_rate)
loopback_player.stream.mix_rate = current_sample_rate
print("[VoiceChat] Changed own sample rate to: " + str(current_sample_rate))
func process_voice_data(voice_data: PackedByteArray, voice_source: int) -> void:
#TODO:_1 Fix this with a mix rate dict
var decompressed_voice : Dictionary
if voice_source == 0:
decompressed_voice = Steam.decompressVoice(voice_data, current_sample_rate)
elif Multiplayer.playerDict.has(voice_source):
var player : Node3D = Multiplayer.playerDict.get(voice_source)
if not player.has_node(voicechat_player_path):
push_error("[VoiceChat] Player %s doesn't have a VoiceChat node", voice_source)
return
var mix_rate = player.get_node_or_null(voicechat_player_path).stream.mix_rate
assert(mix_rate >= sample_rate_min and mix_rate <= sample_rate_max)
decompressed_voice = Steam.decompressVoice(voice_data, mix_rate)
else:
push_error("[VoiceChat] Received voice data for non existing player")
return
if decompressed_voice['result'] == Steam.VOICE_RESULT_OK and decompressed_voice['size'] > 0:
if voice_source == 0:
local_voice_buffer = decompressed_voice['uncompressed']
local_voice_buffer.resize(decompressed_voice['size'])
# We now iterate through the local_voice_buffer and push the samples to the audio generator
for i: int in local_playback.get_frames_available():
if local_voice_buffer.size() == 0: break
# Steam's audio data is represented as 16-bit single channel PCM audio, so we need to convert it to amplitudes
# Combine the low and high bits to get full 16-bit value
var raw_value: int = local_voice_buffer[0] | (local_voice_buffer[1] << 8)
# Make it a 16-bit signed integer
raw_value = (raw_value + 32768) & 0xffff
# Convert the 16-bit integer to a float on from -1 to 1
var amplitude: float = float(raw_value - 32768) / 32768.0
# push_frame() takes a Vector2. The x represents the left channel and the y represents the right channel
local_playback.push_frame(Vector2(amplitude, amplitude))
# Delete the used samples
local_voice_buffer.remove_at(0)
local_voice_buffer.remove_at(0)
else:
#TODO: Better naming
#TODO: Better explanation for 16-bit conversions
var peer_voice_buffer : PackedByteArray = network_voice_buffer.get(voice_source)
peer_voice_buffer = decompressed_voice['uncompressed']
peer_voice_buffer.resize(decompressed_voice['size'])
var peer_playback : AudioStreamGeneratorPlayback = network_playback.get(voice_source)
for i: int in peer_playback.get_frames_available():
if peer_voice_buffer.size() == 0: break
var raw_value: int = peer_voice_buffer[0] | (peer_voice_buffer[1] << 8)
# Make it a 16-bit signed integer
raw_value = (raw_value + 32768) & 0xffff
# Convert the 16-bit integer to a float on from -1 to 1
var amplitude: float = float(raw_value - 32768) / 32768.0
# push_frame() takes a Vector2. The x represents the left channel and the y represents the right channel
peer_playback.push_frame(Vector2(amplitude, amplitude))
# Delete the used samples
peer_voice_buffer.remove_at(0)
peer_voice_buffer.remove_at(0)
func record_voice(is_recording: bool) -> void:
# If talking, suppress all other audio or voice comms from the Steam UI
Steam.setInGameVoiceSpeaking(Steamworks.steam_id, is_recording)
if is_recording:
Steam.startVoiceRecording()
else:
Steam.stopVoiceRecording()

View File

@@ -0,0 +1 @@
uid://hd16egrl0bjr

73
script/featuremanager.gd Normal file
View File

@@ -0,0 +1,73 @@
extends Node
@onready var syncedFeatures: ConfigFile = ConfigFile.new()
@onready var settingsmenu = $/root/Main/Settingsmenu/VBoxContainer/MarginContainer2/HBoxContainer/VBoxContainer
@onready var config : ConfigFile = ConfigFile.new()
@export var debug : bool = false
func _ready() -> void:
var ms = MultiplayerSynchronizer.new()
var spg = SceneReplicationConfig.new()
var ppath = name + ":syncedFeatures"
spg.add_property(ppath)
ms.name = "ConfigSynchronizer"
ms.replication_config = spg
$/root.add_child.call_deferred(ms)
loadConfig()
update_menu()
func update_menu():
for feature in config.get_sections():
var container = HBoxContainer.new()
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
container.name = feature
var label = Label.new()
label.text = feature
container.add_child(label)
var control : Control
match config.get_value(feature,"mode","_"):
"check":
control = CheckButton.new()
"color:":
control = ColorPickerButton.new()
"text":
control = LineEdit.new()
"menu":
control = OptionButton.new()
"button":
control = Button.new()
_:
control = Button.new()
container.add_child(control)
settingsmenu.add_child(container)
func loadConfig():
var err : Error = config.load("user://config.cfg") if not debug else config.load("res://defaultconfig.cfg")
match err:
OK:
print("Config loaded.")
return
ERR_FILE_NOT_FOUND:
push_error("Config not found. Loading default config.")
_:
push_error("Failed to load config: error " + str(err))
var err2 : Error = config.load("res://defaultconfig.cfg")
match err2:
OK:
push_warning("Default config loaded successfully.")
ERR_PARSE_ERROR:
push_error("Invalid default config")
return
_:
push_error("Yay a bug " + str(err2))
return
if ResourceLoader.exists("user://config.cfg"):
return
var err3 : Error = config.save("user://config.cfg")
match err3:
OK:
print("Saved new config file")
ERR_FILE_NO_PERMISSION:
push_error("No permission to save new config file.")
_:
push_error("Could not save new config file: " + str(err3))
return

View File

@@ -0,0 +1 @@
uid://4r044bvpv02o

17
script/interactBox.gd Normal file
View File

@@ -0,0 +1,17 @@
extends Area3D
class_name InteractBox
signal interactedWith(playerRef: PlayerCharacter)
@export var object: Node #Logic for how the interaction works, look for the ButtonLogicToggle, or ButtonLogicImpulse nodes
@export var type: String = "default"
var playerRef: PlayerCharacter
func _ready() -> void:
set_collision_layer_value(6,true) #Enables Interaction colision layer
set_collision_layer_value(1,false) #Disables Default collision layer
set_collision_mask_value(1,false) #Disables Default collision mask
monitoring = false
func interact() -> void:
interactedWith.emit(playerRef)
playerRef = null

View File

@@ -0,0 +1 @@
uid://ymaax1x5fos3

170
script/main.gd Normal file
View File

@@ -0,0 +1,170 @@
extends Control
class_name Main
@onready var start_btn : Button = $Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer4/VBoxContainer3/Start
@onready var disconnect_btn : Button = $Pausemenu/Menu/MarginContainer/VBoxCurrentLobby/Disconnect
@onready var name_edit : = $Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer4/VBoxContainer2/NameEdit
@onready var disconnectDialog = $Dialogs/DisconnectDialog
@onready var pausemenu = $Pausemenu
@onready var mainmenu = $Mainmenu
@onready var hud = $Hud
@onready var mapsParent: Node3D = $Maps
@onready var playersParent: Node3D = $Players
var peer = WebSocketMultiplayerPeer.new()
var lobby: String = "res://Maps/Lobby/Lobby.tscn"
var player: PackedScene = preload("res://actors/Player/player_character.tscn")
func _init():
peer.supported_protocols = ["ludus"]
Steamworks.initialize()
func _ready():
await Multiplayer.noray_connected
multiplayer.peer_connected.connect(peer_connected)
multiplayer.peer_disconnected.connect(peer_disconnected)
multiplayer.server_disconnected.connect(close_network)
multiplayer.connection_failed.connect(close_network)
multiplayer.connected_to_server.connect(connected)
# Set the player name according to the system username. Fallback to the path.
if Steamworks.is_initiallized:
get_tree().auto_accept_quit = false
Steamworks.create_lobby()
name_edit.text = Steam.getPersonaName()
elif false: #TODO: Set text to saved value
name_edit.text = ""
disableMultiplayer()
else:
name_edit.text = "Spacebots Player"
disableMultiplayer()
func _process(_delta: float):
if Input.is_action_just_pressed("pause") and playersParent.get_child_count() > 0 and %Settingsmenu.visible == false:
if not pausemenu.visible:
pausemenu.show()
pausemenu.grab_focus() #TODO: fix
hud.hide()
Input.mouse_mode = Input.MOUSE_MODE_CONFINED
else:
hud.show()
pausemenu.hide()
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func disableMultiplayer() -> void:
$/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer/VBoxMultiplayerDisabled.show()
$/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer/VBoxLobbylist.hide()
func start_game():
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
mainmenu.hide()
start_btn.disabled = true
disconnect_btn.disabled = false
hud.show()
@rpc("authority","call_local","reliable")
func changeMap(newMap: String) -> void:
if !(load(newMap).instantiate() is MapLogic):
push_error("newMap: String is not a map scene")
return
var map : MapLogic = load(newMap).instantiate()
if mapsParent.get_child_count() > 0:
#if Multiplayer.getCurrentMapLogic().is_connected(StringName("missionLost"),missionLost):
#Multiplayer.getCurrentMapLogic().disconnect(StringName("missionLost"),missionLost)
mapsParent.get_child(0).queue_free()
mapsParent.add_child(map)
initiatePlayerPosition(map.playerStartPos)
Multiplayer.setCurrentMapLogic(map)
#map.missionLost.connect(missionLost)
func initiatePlayerPosition(pos: Vector3) -> void:
for p in Multiplayer.playerDict.values():
if p.is_multiplayer_authority():
p.global_position = pos
func missionLost() -> void:
changeMap(lobby)
func stop_game():
disconnect_btn.disabled = true
mainmenu.show()
pausemenu.hide()
hud.hide()
Input.mouse_mode = Input.MOUSE_MODE_CONFINED
#Steamworks.stop()
var currentMaps = mapsParent.get_children()
for child in currentMaps:
child.queue_free()
var players = playersParent.get_children()
for child in players:
child.set_process(false)
child.queue_free()
await get_tree().process_frame
start_btn.disabled = false
func close_network():
peer.close()
stop_game()
disconnectDialog.popup_centered()
disconnectDialog.get_ok_button().grab_focus()
multiplayer.multiplayer_peer = null
Multiplayer.currentMapLogic = null
Multiplayer.playerDict = {}
Multiplayer.alivePlayerDict = {}
Multiplayer.is_host = false
Steamworks.lobby_id = 0
Steamworks.lobby_members = []
Steamworks.lobby_members_max = 5
Steamworks.steam_id = 0
Steamworks.steam_username = ""
func connected():
if Multiplayer.is_host == false:
add_player.rpc_id(1, multiplayer.get_unique_id())
func peer_connected(_id):
pass
#Steamworks.on_peer_add(id)
func peer_disconnected(id):
if Multiplayer.is_host:
remove_player(id)
func on_server_disconnected():
on_Disconnect_pressed()
func on_Start_pressed():
Multiplayer.host()
start_game()
changeMap(lobby)
add_player(multiplayer.get_unique_id())
func on_Disconnect_pressed():
if Steamworks.lobby_members.size() > 1:
Steam.leaveLobby(Steamworks.lobby_id)
close_network()
func on_Connect(norayid: String):
start_btn.disabled = true
disconnect_btn.disabled = false
start_game()
Multiplayer.join(norayid)
@rpc("any_peer")
func add_player(id):
var player_instance: CharacterBody3D = player.instantiate()
player_instance.name = str(id)
playersParent.add_child(player_instance)
@rpc("any_peer")
func remove_player(id):
if playersParent.get_node(str(id)):
playersParent.get_node(str(id)).queue_free()

1
script/main.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://b4avd5n3wv18p

14
script/mainmenu.gd Normal file
View File

@@ -0,0 +1,14 @@
extends Control
@onready var debugbtn : Button = $/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer/VBoxLobbylist/RefreshLobbies
@onready var main : Control = $/root/Main
func on_start_pressed() -> void:
main.on_Start_pressed()
func on_lobby_list_item_activated(index: int) -> void:
Steamworks.leave_lobby()
var lobby_id = Steamworks.lobby_index_list.get(index)
Steamworks.join_lobby(lobby_id)
func on_refresh_lobbies_pressed() -> void:
Steamworks.render_global_lobbies()

1
script/mainmenu.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://dfqkj3vcm3a84

122
script/multiplayer.gd Normal file
View File

@@ -0,0 +1,122 @@
extends Node
signal noray_connected
signal player_ready(id : int)
const NORAY_ADDRESS = "tomfol.io"
const NORAY_PORT = 8890
var is_host = false
var external_oid = ""
var debug = false
var playerDict: Dictionary[int,PlayerCharacter]
var alivePlayerDict: Dictionary[int,PlayerCharacter]
var currentMapLogic: MapLogic
@onready var errordialog = $/root/Main/Dialogs/ErrorDialog
@onready var start_btn : Button = $/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer4/VBoxContainer3/Start
@onready var main : Main = $/root/Main
func _ready():
Noray.on_connect_to_host.connect(on_noray_connected)
Noray.on_connect_nat.connect(handle_nat_connection)
#Noray.on_connect_relay.connect(handle_relay_connection)
Noray.connect_to_host(NORAY_ADDRESS, NORAY_PORT)
player_ready.connect(peer_connected)
multiplayer.peer_disconnected.connect(peer_disconnected)
func getCurrentMapLogic() -> MapLogic:
return currentMapLogic
func setCurrentMapLogic(toMap: MapLogic) -> void:
currentMapLogic = toMap
func peer_connected(id: int) -> void:
if !get_node("/root/Main/Players").get_child_count(): return
playerDict.set(id,get_node("/root/Main/Players/" + str(id)))
alivePlayerDict.set(id,get_node("/root/Main/Players/" + str(id)))
if !playerDict.get(id): playerDict.erase(id)
if !alivePlayerDict.get(id): alivePlayerDict.erase(id)
func peer_disconnected(id: int) -> void:
playerDict.erase(id)
alivePlayerDict.erase(id)
func on_noray_connected():
print("Connected to Noray server")
Noray.register_host()
await Noray.on_pid
await Noray.register_remote()
start_btn.disabled = false
noray_connected.emit()
func host():
var peer = ENetMultiplayerPeer.new()
var error = OK
if debug:
error = peer.create_server(1069)
else:
error = peer.create_server(Noray.local_port)
if error:
errordialog.title = "Error"
errordialog.dialog_text = "Failed to create Server: " + str(error_string(error) + " (" + str(error) + ")")
errordialog.popup_centered()
errordialog.get_ok_button().grab_focus()
main.close_network()
else:
multiplayer.multiplayer_peer = peer
is_host = true
func join(oid: String):
if debug:
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client("localhost", 1069)
print("Join: " + error_string(error) + " " + str(error))
multiplayer.multiplayer_peer = peer
return
Noray.connect_nat(oid)
external_oid = oid
func handle_nat_connection(address, port):
var err = await connect_to_server(address, port)
if err != OK && !is_host:
print("NAT failed, using relay")
err = Noray.connect_relay(external_oid)
return err
func handle_relay_connection(address, port):
return await connect_to_server(address, port)
func connect_to_server(address, port):
var err = OK
if !is_host:
var udp = PacketPeerUDP.new()
udp.bind(Noray.local_port)
udp.set_dest_address(address, port)
err = await PacketHandshake.over_packet_peer(udp)
udp.close()
if err != OK:
if err != ERR_BUSY:
print("Handshake failed")
return err
else:
print("Handshake success")
var peer = ENetMultiplayerPeer.new()
err = peer.create_client(address, port, 0, 0, 0, Noray.local_port)
if err != OK:
return err
multiplayer.multiplayer_peer = peer
return OK
else:
err = await PacketHandshake.over_enet(multiplayer.multiplayer_peer.host, address, port)
return err

View File

@@ -0,0 +1 @@
uid://77q1pjrfpua6

31
script/objectSpawner.gd Normal file
View File

@@ -0,0 +1,31 @@
extends Node3D
class_name ObjectSpawner
@export var Objects: Array[RandomObject]
@export var Parent: Node
var weightArray: PackedFloat32Array
var rng = RandomNumberGenerator.new()
func _ready():
if Objects.is_empty(): return
var spaceship = get_node("/root/Main/Maps/Spaceship")
for obj in Objects: #Init weight array
weightArray.push_back(obj.probability)
var objectReference = load(Objects[rng.rand_weighted(weightArray)].dir) #Get random Object depending on weights
var ObjectInstance = objectReference.instantiate()
if Parent:
Parent.call_deferred("add_child",ObjectInstance) #Adds Object instance
elif spaceship:
spaceship.call_deferred("add_child",ObjectInstance)
else:
push_error("Object Spawner failed to spawn object because of missing parent node")
ObjectInstance.rotation = self.global_rotation
ObjectInstance.position = self.global_position
self.queue_free()

View File

@@ -0,0 +1 @@
uid://dfqpquon420gl

View File

@@ -0,0 +1,89 @@
extends Node3D
class_name PathfindingGridLoader
var loading: bool = false # when this is true the grid starts loading
var loading_done: bool = false # when the grid is loaded, this becomes true
@export var height: float= 0.2 ##Choose a height that is just above the floor
@export var lengthX: int = 50 ##How far the grid generates in the x direction, generates as a rectangle
@export var lengthZ: int = 50 ##How far the grid generates in the z direction, generates as a rectangle
@export var point_density: float = 1 ##How many points per unit of grid, cannot be = 0
@export var minimum_connections_per_point: int = 3
@export var wallCollisionLayer: int = 0b100
var astar: AStar2D = AStar2D.new()
func _physics_process(_delta: float) -> void:
if loading == false: return
if loading_done == true: return
generatePoints()
connectPointsToNeighbors()
killPointsWithoutFriends(minimum_connections_per_point)
addWeightToPoints()
loading = false
loading_done = true
func loadGrid() -> void:
loading = true
func castRay(startPoint:Vector3,endPoint: Vector3,collisionMask:int = 0xFFFFFFFF,hitFromInside: bool = true) -> bool: ##Returns true if the ray collides and false if it doesn't
var space_state = get_world_3d().direct_space_state
var rayQueryParam = PhysicsRayQueryParameters3D.create(startPoint,endPoint)
rayQueryParam.collision_mask = collisionMask
rayQueryParam.hit_from_inside = hitFromInside
return space_state.intersect_ray(rayQueryParam).size()
func checkPoint(point: Vector2) -> bool: ## only ever call this from physics process as it uses physics for raycast and may not work on idle callback
var floorDetected: bool = castRay(Vector3(point.x,height,point.y),Vector3(point.x,-(height*1.5),point.y))
var wallDetected: bool = castRay(Vector3(point.x,height,point.y),Vector3(point.x,-(height*1.5),point.y),wallCollisionLayer) #Walls are on collision layer 3 (0b100)
if !wallDetected && floorDetected:
return true
else:
return false
func getPointsInRadius(origin: Vector2,radius: float, maxPoints: int = 9) -> Array[int]: #Origin point is always index 0 in the array
var pointIDs: Array[int]
for point in maxPoints:
var id = astar.get_closest_point(origin)
if (astar.get_point_position(id) - origin).length() <= radius:
pointIDs.push_back(id)
astar.set_point_disabled(id)
for id in pointIDs:
astar.set_point_disabled(id,false)
return pointIDs
func connectIfValid(id1: int, id2: int):
var point1 := astar.get_point_position(id1)
var point2 := astar.get_point_position(id2)
if !castRay(Vector3(point1.x,height,point1.y),Vector3(point2.x,height,point2.y)):
astar.connect_points(id1,id2)
func generatePoints() -> void:
var currentPoint: Vector2
var currentID: int = 0
for x in lengthX*point_density:
for z in lengthZ*point_density:
currentPoint = Vector2((x/point_density + self.global_position.x),z/point_density + self.global_position.z)
if checkPoint(currentPoint):
astar.add_point(currentID,currentPoint)
currentID += 1
func connectPointsToNeighbors() -> void:
for id in astar.get_point_ids():
var neighborPoints: Array[int]
neighborPoints = getPointsInRadius(astar.get_point_position(id),(1/point_density)*1.5)
for point in neighborPoints.size()-1:
connectIfValid(neighborPoints[0],neighborPoints[point+1])
func killPointsWithoutFriends(minConnections: int) -> void: ##Really mean and evil and fucked up function
var cullPoints: Array[int]
for id in astar.get_point_ids():
if astar.get_point_connections(id).size() < minConnections:
cullPoints.push_back(id)
for point in cullPoints:
astar.remove_point(point)
func addWeightToPoints() -> void: #Add weight depending on number of connections
for id in astar.get_point_ids():
astar.set_point_weight_scale(id,8 - astar.get_point_connections(id).size())

View File

@@ -0,0 +1 @@
uid://cjgxd88k1bntf

13
script/pausemenu.gd Normal file
View File

@@ -0,0 +1,13 @@
extends Control
@onready var main : Main = $/root/Main
func on_disconnect_pressed() -> void:
main.on_Disconnect_pressed()
func on_lobby_member_list_item_activated(index: int) -> void:
if Steamworks.is_initiallized:
print("You clicked on: " + Steam.getFriendPersonaName(Steamworks.member_index_list.get(index)))
else:
print("You clicked on: index: " + str(index) + " (Steam unavailable)")

1
script/pausemenu.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://2niwcxdyq213

5
script/randomObject.gd Normal file
View File

@@ -0,0 +1,5 @@
extends Node
class_name RandomObject
@export var dir: String
@export var probability: float #Value between 0 and 1

View File

@@ -0,0 +1 @@
uid://d3w6uvg5hmphc

13
script/settingsmenu.gd Normal file
View File

@@ -0,0 +1,13 @@
extends Control
var settingsmenu : bool = false
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("settings") and %Pausemenu.visible:
if not %Settingsmenu.visible:
%Settingsmenu.show()
%Settingsmenu.grab_focus()
else:
%Settingsmenu.hide()
%Pausemenu.grab_focus()
settingsmenu = !settingsmenu

View File

@@ -0,0 +1 @@
uid://bsteyp1ndegj

4
script/start_ik.gd Normal file
View File

@@ -0,0 +1,4 @@
extends SkeletonIK3D
func _ready() -> void:
start()

1
script/start_ik.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cp822pg0wyna7

226
script/steamworks.gd Normal file
View File

@@ -0,0 +1,226 @@
extends Control
@onready var list : ItemList = $/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer/VBoxLobbylist/ItemList
@onready var lobby_members_list : ItemList = $/root/Main/Pausemenu/Menu/MarginContainer/VBoxCurrentLobby/LobbyMemberList
@onready var profileSprite : Sprite2D = $/root/Main/Mainmenu/Menu/MarginContainer/VBoxContainer/HBoxContainer4/VBoxContainer/ProfileSprite
@onready var main : Main = $/root/Main
const PACKET_READ_LIMIT: int = 32
const app_id: int = 480
var is_initiallized: bool = false #Steam initialized successfully
var lobby_index_list: Dictionary = {} # index -> lobbyid
var member_index_list: Dictionary = {} # index -> steam_id
var lobby_data
var lobby_id: int = 0
var lobby_members: Array = []
var lobby_members_max: int = 5
var steam_id: int = 0
var steam_username: String = ""
func initialize() -> void:
var initialize_response: Dictionary = Steam.steamInitEx(false, Steamworks.app_id)
if initialize_response["status"] != OK:
match initialize_response["status"]:
1:
push_error("[Steam] Initialization failed: Error")
2:
push_error("[Steam] Initialization failed: Can't connect to steam.")
3:
push_error("[Steam] Initialization failed: Steam client out of date.")
return
print("[Steam] Initialized successfully")
steam_id = Steam.getSteamID()
steam_username = Steam.getPersonaName()
#Load user avatar
Steam.getPlayerAvatar(Steam.AVATAR_SMALL,Steam.getSteamID())
Steam.avatar_loaded.connect(steam_avatar_loaded)
Steam.join_requested.connect(on_lobby_join_requested)
Steam.lobby_chat_update.connect(on_lobby_chat_update)
Steam.lobby_created.connect(on_lobby_created)
# Steam.lobby_data_update.connect(on_lobby_data_update)
#Steam.lobby_invite.connect(_on_lobby_invite)
Steam.lobby_joined.connect(on_lobby_joined)
Steam.lobby_match_list.connect(on_lobby_match_list)
Steam.lobby_message.connect(on_lobby_message)
Steam.persona_state_change.connect(on_persona_change)
# Check for command line arguments
check_command_line()
#Load lobbylist
render_global_lobbies()
is_initiallized = true
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST:
# do save stuff here
if lobby_id != 0:
Steam.leaveLobby(lobby_id)
print("[Steam] Left lobby " + str(lobby_id))
main.on_Disconnect_pressed()
print("Exiting")
get_tree().quit()
func check_command_line() -> void:
var args: Array = OS.get_cmdline_args()
# There are arguments to process
if args.size() <= 0:
return
# A Steam connection argument exists
if args[0] != "+connect_lobby":
return
# Lobby invite exists so try to connect to it
if int(args[1]) <= 0:
return
print("[Steam] Command line lobby ID: %s" % args[1])
join_lobby(int(args[1]))
func steam_avatar_loaded(user_id: int, avatar_size: int, avatar_buffer: PackedByteArray):
var avatar_image: Image = Image.create_from_data(avatar_size, avatar_size, false, Image.FORMAT_RGBA8, avatar_buffer)
var avatar_texture: ImageTexture = ImageTexture.create_from_image(avatar_image)
if user_id == steam_id:
profileSprite.set_texture(avatar_texture)
var index = member_index_list.find_key(user_id)
if index != null:
lobby_members_list.set_item_icon(index,avatar_texture)
func render_global_lobbies():
Steam.addRequestLobbyListDistanceFilter(Steam.LOBBY_DISTANCE_FILTER_WORLDWIDE)
Steam.requestLobbyList()
func on_lobby_match_list(lobbies: Array):
list.clear()
lobby_index_list = {}
await get_tree().create_timer(.05).timeout
for lobby in lobbies:
var lobby_name = Steam.getLobbyData(lobby,"name")
var lobby_mode = Steam.getLobbyData(lobby,"mode")
#TODO: Remove when own steam page is aquired
if lobby_name == "" or lobby_mode != "Spacebots":
continue
if Steam.getNumLobbyMembers(lobby) == 0 or lobby == lobby_id:
continue
var index = list.add_item("%s [%s/%s]" % [lobby_name, Steam.getNumLobbyMembers(lobby), Steam.getLobbyMemberLimit(lobby)] , null, false)
list.set_item_tooltip(index, str(lobby))
lobby_index_list.set(index,lobby)
func create_lobby() -> void:
if lobby_id != 0: # Make sure a lobby is not already set
return
print("[Steam] Creating Lobby")
Steam.createLobby(Steam.LOBBY_TYPE_PUBLIC, lobby_members_max)
func on_lobby_created(connect_: int, this_lobby_id: int) -> void:
if connect_ != 1:
push_error("[Steam] Error creating lobby:" + str(connect_))
return
# Set the lobby ID
lobby_id = this_lobby_id
print("[Steam] Created steam lobby: %s" % lobby_id)
Steam.setLobbyJoinable(lobby_id, true)
Steam.setLobbyData(lobby_id, "name", steam_username + "'s lobby")
Steam.setLobbyData(lobby_id, "mode", "Spacebots")
print("[Steam] Lobbydata set successfully")
lobby_members_list.add_item(Steam.getPlayerNickname(steam_id), profileSprite.get_texture(), true)
#Allow P2P connections to fallback to being relayed through Steam if needed
#var set_relay: bool = Steam.allowP2PPacketRelay(true)
#print("Allowing Steam to be relay backup: %s" % set_relay)
func join_lobby(this_lobby_id: int) -> void:
print("[Steam] Attempting to join lobby %s" % this_lobby_id)
# Make the lobby join request to Steam
Steam.joinLobby(this_lobby_id)
func on_lobby_joined(this_lobby_id: int, _permissions: int, _locked: bool, response: int) -> void:
# If joining was successful
if response != Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS:
# Get the failure reason
var fail_reason: String
match response:
Steam.CHAT_ROOM_ENTER_RESPONSE_DOESNT_EXIST: fail_reason = "This lobby no longer exists."
Steam.CHAT_ROOM_ENTER_RESPONSE_NOT_ALLOWED: fail_reason = "You don't have permission to join this lobby."
Steam.CHAT_ROOM_ENTER_RESPONSE_FULL: fail_reason = "The lobby is now full."
Steam.CHAT_ROOM_ENTER_RESPONSE_ERROR: fail_reason = "Uh... something unexpected happened!"
Steam.CHAT_ROOM_ENTER_RESPONSE_BANNED: fail_reason = "You are banned from this lobby."
Steam.CHAT_ROOM_ENTER_RESPONSE_LIMITED: fail_reason = "You cannot join due to having a limited account."
Steam.CHAT_ROOM_ENTER_RESPONSE_CLAN_DISABLED: fail_reason = "This lobby is locked or disabled."
Steam.CHAT_ROOM_ENTER_RESPONSE_COMMUNITY_BAN: fail_reason = "This lobby is community locked."
Steam.CHAT_ROOM_ENTER_RESPONSE_MEMBER_BLOCKED_YOU: fail_reason = "A user in the lobby has blocked you from joining."
Steam.CHAT_ROOM_ENTER_RESPONSE_YOU_BLOCKED_MEMBER: fail_reason = "A user you have blocked is in the lobby."
print("[Steam] Failed to join lobby : %s" % fail_reason)
return
lobby_id = this_lobby_id
# Clear any previous lobby members lists, if you were in a previous lobby
get_lobby_members()
# TODO: Add failed to connect dialog
func on_lobby_join_requested(this_lobby_id: int, friend_id: int) -> void:
# Get the lobby owner's name
if this_lobby_id == lobby_id:
print("[Steam] Lobby join canceled: Already in that lobby")
return
if lobby_id != 0:
print("[Steam] Leaving lobby " + str(lobby_id))
leave_lobby()
var owner_name: String = Steam.getFriendPersonaName(friend_id)
print("[Steam] Joining %s's lobby..." % owner_name)
# Attempt to join the lobby
join_lobby(this_lobby_id)
func get_lobby_members() -> void:
# Clear your previous lobby list
lobby_members.clear()
lobby_members_list.clear()
# Get the number of members from this lobby from Steam
var num_of_members: int = Steam.getNumLobbyMembers(lobby_id)
# Get the data of these players from Steam
for this_member in range(0, num_of_members):
var member_steam_id: int = Steam.getLobbyMemberByIndex(lobby_id, this_member)
var member_steam_name: String = Steam.getFriendPersonaName(member_steam_id)
# Add them to the list
lobby_members.append({"steam_id":member_steam_id, "steam_name":member_steam_name})
Steam.getPlayerAvatar(Steam.AVATAR_SMALL,member_steam_id)
if member_steam_id == Steam.getLobbyOwner(lobby_id):
member_steam_name += " (Host)"
var index: int = lobby_members_list.add_item(member_steam_name, null, true)
if member_steam_id != Steam.getLobbyOwner(lobby_id):
lobby_members_list.set_item_tooltip_enabled(index, false)
else:
lobby_members_list.set_item_tooltip(index,str(lobby_id))
if Steam.getFriendRelationship(member_steam_id) == Steam.FRIEND_FLAG_IMMEDIATE:
list.set_item_custom_fg_color(index, Color(0.9,1,0.9,1))
member_index_list.set(index, member_steam_id)
func on_persona_change(this_steam_id: int, flag: int) -> void:
# Make sure you're in a lobby and this user is valid or Steam might spam your console log
if lobby_id > 0:
var hex : String = "0x%04X" % flag
var name_ : String = Steam.getFriendPersonaName(this_steam_id)
print("[Steam] %s (%s) had information change, updating member list: %s" % [name_, this_steam_id, hex])
# Update the player list
get_lobby_members()
func on_lobby_message(lobby_id_msg: int, user: int, buffer: String, _chat_type: Steam.ChatEntryType):
print("[Chat] " + Steam.getFriendPersonaName(user) + ": " + buffer)
if(user != Steam.getLobbyOwner(lobby_id)):
print("[Steam] Message not by Lobby owner: " + str(lobby_id_msg) + " " + str(Steam.getLobbyOwner(lobby_id)))
return
if(buffer.begins_with("NorayID:")):
buffer = buffer.trim_prefix("NorayID:")
print(buffer)
$/root/Main.on_Connect(buffer)
func on_lobby_chat_update(_lobby_id_msg_update: int, _changed_id: int, _changer_id: int, chat_state: Steam.ChatMemberStateChange):
print("lobby chat update: " + str(chat_state))
if Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED and Steam.getLobbyOwner(lobby_id) == steam_id:
var result = Steam.sendLobbyChatMsg(lobby_id, "NorayID:" + str(Noray.oid))
print("lobby chat update2: " + str(chat_state))
if result == false:
print("Chat message couldn't be send")
func leave_lobby():
Steam.leaveLobby(lobby_id)
lobby_id = 0

1
script/steamworks.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://dnygkxv8lwrgw