Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UEHelpers Improvements for Dedicated Servers #688

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

igromanru
Copy link
Contributor

@igromanru igromanru commented Oct 17, 2024

UEHelpers rework goes in the third round with new experience, now testing it on a dedicated game server.

Description

On a server first player controller instance appears only once a player has joined the server.
Therefore, GetPlayerController returns an invalid object, as do GetWorld and GetWorldContextObject, which were dependent on the player controller.
I immediately run into the problem that I need a valid WorldContext/World while there are no players on the server.

I've changed the GetWorld to get the UWorld from the UGameInstance which always exist and returns the same UWorld player controller, but I left GetPlayerController code as fallback, in case a UGameInstance couldn't be found.
The GetWorldContextObject now returns GetWorld.
I will port some of my mods to make them compatible with the server and see if I can find more things to improve. Maybe even some new functions to help mod a server.

Testing

Slightly improved version of the lua code from before.

local playerController = UEHelpers.GetPlayerController()
if playerController:IsValid() then
	print(string.format("playerController: %s\n", playerController:GetFullName()))
else
	print("playerController invalid\n")
end
local player = UEHelpers.GetPlayer()
if player:IsValid() then
	print(string.format("player: %s\n", player:GetFullName()))
else
	print("player invalid\n")
end
local engine = UEHelpers.GetEngine()
if engine:IsValid() then
	print(string.format("engine: %s\n", engine:GetFullName()))
else
	print("engine invalid\n")
end
local gameInstance = UEHelpers.GetGameInstance()
if gameInstance:IsValid() then
	print(string.format("gameInstance: %s\n", gameInstance:GetFullName()))
else
	print("gameInstance invalid\n")
end
local gameViewportClient = UEHelpers.GetGameViewportClient()
if gameViewportClient:IsValid() then
	print(string.format("gameViewportClient: %s\n", gameViewportClient:GetFullName()))
else
	print("gameViewportClient invalid\n")
end
local world = UEHelpers.GetWorld()
if world:IsValid() then
	print(string.format("world: %s\n", world:GetFullName()))
else
	print("world invalid\n")
end
print(string.format("WorldDeltaSeconds: %f\n", UEHelpers.GetGameplayStatics():GetWorldDeltaSeconds(UEHelpers.GetWorldContextObject())))
local persistentLevel = UEHelpers.GetPersistentLevel()
if persistentLevel:IsValid() then
	print(string.format("persistentLevel: %s\n", persistentLevel:GetFullName()))
else
	print("persistentLevel invalid\n")
end
local worldSettings = UEHelpers.GetWorldSettings()
if worldSettings:IsValid() then
	print(string.format("worldSettings: %s\n", worldSettings:GetFullName()))
else
	print("worldSettings invalid\n")
end
local gameModeBase = UEHelpers.GetGameModeBase()
if gameModeBase:IsValid() then
	print(string.format("gameModeBase: %s\n", gameModeBase:GetFullName()))
else
	print("gameModeBase invalid\n")
end
local gameStateBase = UEHelpers.GetGameStateBase()
if gameStateBase:IsValid() then
	print(string.format("gameStateBase: %s\n", gameStateBase:GetFullName()))
else
	print("gameStateBase invalid\n")
end
local playerStates = UEHelpers.GetAllPlayerStates()
print(string.format("playerStates Count: %d\n", #playerStates))
for i, playerState in ipairs(playerStates) do
	if playerState:IsValid() then
		print(string.format("%d: playerState: %s\n", i, playerState:GetFullName()))
		print(string.format("%d: playerState Class: %s\n", i, playerState:GetClass():GetFullName()))
	end
end
local players = UEHelpers.GetAllPlayers()
print(string.format("players Count: %d\n", #players))
for i, player in ipairs(players) do
	if player:IsValid() then
		print(string.format("%d: player: %s\n", i, player:GetFullName()))
		print(string.format("%d: player Class: %s\n", i, player:GetClass():GetFullName()))
	end
end

Result without a player on the server

playerController invalid
player invalid
engine: GameEngine /Engine/Transient.GameEngine_2147482617
gameInstance: Abiotic_GameInstance_C /Engine/Transient.GameEngine_2147482617:Abiotic_GameInstance_C_2147482546
gameViewportClient invalid
world: World /Game/Maps/Facility.Facility
WorldDeltaSeconds: 0.032851
persistentLevel: Level /Game/Maps/Facility.Facility:PersistentLevel
worldSettings: AbioticWorldSettings /Game/Maps/Facility.Facility:PersistentLevel.AbioticWorldSettings
gameModeBase: Abiotic_Survival_GameMode_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameMode_C_2147482298
gameStateBase: Abiotic_Survival_GameState_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameState_C_2147482126
playerStates Count: 0
players Count: 0

Result with a player on the server

playerController: Abiotic_PlayerController_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerController_C_2147476421
player: Abiotic_PlayerCharacter_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerCharacter_C_2147476410
engine: GameEngine /Engine/Transient.GameEngine_2147482617
gameInstance: Abiotic_GameInstance_C /Engine/Transient.GameEngine_2147482617:Abiotic_GameInstance_C_2147482546
gameViewportClient invalid
world: World /Game/Maps/Facility.Facility
WorldDeltaSeconds: 0.034148
persistentLevel: Level /Game/Maps/Facility.Facility:PersistentLevel
worldSettings: AbioticWorldSettings /Game/Maps/Facility.Facility:PersistentLevel.AbioticWorldSettings
gameModeBase: Abiotic_Survival_GameMode_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameMode_C_2147482298
gameStateBase: Abiotic_Survival_GameState_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameState_C_2147482126
playerStates Count: 1
1: playerState: Abiotic_PlayerState_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerState_C_2147476420
1: playerState Class: BlueprintGeneratedClass /Game/Blueprints/Meta/Abiotic_PlayerState.Abiotic_PlayerState_C
players Count: 1
1: player: Abiotic_PlayerCharacter_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerCharacter_C_2147476410
1: player Class: BlueprintGeneratedClass /Game/Blueprints/Characters/Abiotic_PlayerCharacter.Abiotic_PlayerCharacter_C

Result in client main menu

playerController: Abiotic_PlayerController_C /Game/Maps/MainMenu.MainMenu:PersistentLevel.Abiotic_PlayerController_C_2147450037
player invalid
engine: GameEngine /Engine/Transient.GameEngine_2147482617
gameInstance: Abiotic_GameInstance_C /Engine/Transient.GameEngine_2147482617:Abiotic_GameInstance_C_2147482539
gameViewportClient: AbioticGameViewportClient /Engine/Transient.GameEngine_2147482617:AbioticGameViewportClient_2147482416
world: World /Game/Maps/MainMenu.MainMenu
WorldDeltaSeconds: 0.016606
persistentLevel: Level /Game/Maps/MainMenu.MainMenu:PersistentLevel
worldSettings: AbioticWorldSettings /Game/Maps/MainMenu.MainMenu:PersistentLevel.AbioticWorldSettings
gameModeBase: Abiotic_MainMenu_GameMode_C /Game/Maps/MainMenu.MainMenu:PersistentLevel.Abiotic_MainMenu_GameMode_C_2147450057
gameStateBase: GameState /Game/Maps/MainMenu.MainMenu:PersistentLevel.GameState_2147450044
playerStates Count: 1
1: playerState: Abiotic_PlayerState_C /Game/Maps/MainMenu.MainMenu:PersistentLevel.Abiotic_PlayerState_C_2147450036
1: playerState Class: BlueprintGeneratedClass /Game/Blueprints/Meta/Abiotic_PlayerState.Abiotic_PlayerState_C
players Count: 0

Result in client in game

playerController: Abiotic_PlayerController_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerController_C_2147473009
player: Abiotic_PlayerCharacter_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerCharacter_C_2147472998
engine: GameEngine /Engine/Transient.GameEngine_2147482617
gameInstance: Abiotic_GameInstance_C /Engine/Transient.GameEngine_2147482617:Abiotic_GameInstance_C_2147482539
gameViewportClient: AbioticGameViewportClient /Engine/Transient.GameEngine_2147482617:AbioticGameViewportClient_2147482416
world: World /Game/Maps/Facility.Facility
WorldDeltaSeconds: 0.016205
persistentLevel: Level /Game/Maps/Facility.Facility:PersistentLevel
worldSettings: AbioticWorldSettings /Game/Maps/Facility.Facility:PersistentLevel.AbioticWorldSettings
gameModeBase: Abiotic_Survival_GameMode_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameMode_C_2147473210
gameStateBase: Abiotic_Survival_GameState_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_Survival_GameState_C_2147473042
playerStates Count: 1
1: playerState: Abiotic_PlayerState_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerState_C_2147473008
1: playerState Class: BlueprintGeneratedClass /Game/Blueprints/Meta/Abiotic_PlayerState.Abiotic_PlayerState_C
players Count: 1
1: player: Abiotic_PlayerCharacter_C /Game/Maps/Facility.Facility:PersistentLevel.Abiotic_PlayerCharacter_C_2147472998
1: player Class: BlueprintGeneratedClass /Game/Blueprints/Characters/Abiotic_PlayerCharacter.Abiotic_PlayerCharacter_C

…ce and fallback on GetPlayerController() only if a valid GameInstance doesn't exist

feat(UEHelpers): Change GetWorldContextObject() to return GetWorld() instead of GetPlayerController()
@igromanru igromanru force-pushed the UEHelpers-Server-Improvements branch from d0626dd to ac6ba17 Compare October 17, 2024 22:07
@igromanru
Copy link
Contributor Author

Added GetAllPlayerStates and GetAllPlayers functions to UEHelpers together with proper Changelog.md changes.
Updated code and test results above.

@igromanru igromanru marked this pull request as ready for review October 20, 2024 11:43
@igromanru igromanru force-pushed the UEHelpers-Server-Improvements branch from 9adcb7d to 50a3300 Compare October 20, 2024 12:20
@narknon
Copy link
Collaborator

narknon commented Nov 14, 2024

I think the GameInstance world should likely be the fallback. GameInstance world can be persistent and generally when people are spawning actors in, they want them to act like any other actor for destruction. The PC's world will generally be the actual current level and therefore follow that for destroy/GC.

@igromanru
Copy link
Contributor Author

Does it really matter? When do they point to different UWorlds?
According to my tests, which weren't posted here, both were the same all the time. At least in my game.
I've used GetAddress() in main menu and in game to see if one of them is pointing to a different World.
GameInstance exists at the start of a Dedicated Server, but PlayerController only gets created once a player has joined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants