Actor progress

This commit is contained in:
AeonLucid
2022-01-16 23:05:53 +01:00
parent f3b7efca45
commit fe97b7a145
14 changed files with 431 additions and 22 deletions
+2 -2
View File
@@ -36,11 +36,11 @@ internal static class Program
Map = "/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap" Map = "/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap"
}; };
//Task.Run(() => { Thread.Sleep(100); new Client().Connect("127.0.0.1", 7777, worldUrl); }); await using (var world = new ProspectWorld())
await using (var world = new ProspectWorld(worldUrl))
{ {
world.SetGameInstance(new UGameInstance()); world.SetGameInstance(new UGameInstance());
world.SetGameMode(worldUrl); world.SetGameMode(worldUrl);
world.InitializeActorsForPlay(worldUrl, true);
world.Listen(); world.Listen();
while (await Tick.WaitForNextTickAsync()) while (await Tick.WaitForNextTickAsync())
+1 -5
View File
@@ -1,11 +1,7 @@
using Prospect.Unreal.Core; using Prospect.Unreal.Runtime;
using Prospect.Unreal.Runtime;
namespace Prospect.Server.Game; namespace Prospect.Server.Game;
public class ProspectWorld : UWorld public class ProspectWorld : UWorld
{ {
public ProspectWorld(FUrl url) : base(url)
{
}
} }
+47
View File
@@ -0,0 +1,47 @@
namespace Prospect.Unreal.Core;
[Flags]
public enum EObjectFlags
{
// Do not add new flags unless they truly belong here. There are alternatives.
// if you change any the bit of any of the RF_Load flags, then you will need legacy serialization
RF_NoFlags = 0x00000000, ///< No flags, used to avoid a cast
// This first group of flags mostly has to do with what kind of object it is. Other than transient, these are the persistent object flags.
// The garbage collector also tends to look at these.
RF_Public =0x00000001, ///< Object is visible outside its package.
RF_Standalone =0x00000002, ///< Keep object around for editing even if unreferenced.
RF_MarkAsNative =0x00000004, ///< Object (UField) will be marked as native on construction (DO NOT USE THIS FLAG in HasAnyFlags() etc)
RF_Transactional =0x00000008, ///< Object is transactional.
RF_ClassDefaultObject =0x00000010, ///< This object is its class's default object
RF_ArchetypeObject =0x00000020, ///< This object is a template for another object - treat like a class default object
RF_Transient =0x00000040, ///< Don't save object.
// This group of flags is primarily concerned with garbage collection.
RF_MarkAsRootSet =0x00000080, ///< Object will be marked as root set on construction and not be garbage collected, even if unreferenced (DO NOT USE THIS FLAG in HasAnyFlags() etc)
RF_TagGarbageTemp =0x00000100, ///< This is a temp user flag for various utilities that need to use the garbage collector. The garbage collector itself does not interpret it.
// The group of flags tracks the stages of the lifetime of a uobject
RF_NeedInitialization =0x00000200, ///< This object has not completed its initialization process. Cleared when ~FObjectInitializer completes
RF_NeedLoad =0x00000400, ///< During load, indicates object needs loading.
RF_KeepForCooker =0x00000800, ///< Keep this object during garbage collection because it's still being used by the cooker
RF_NeedPostLoad =0x00001000, ///< Object needs to be postloaded.
RF_NeedPostLoadSubobjects =0x00002000, ///< During load, indicates that the object still needs to instance subobjects and fixup serialized component references
RF_NewerVersionExists =0x00004000, ///< Object has been consigned to oblivion due to its owner package being reloaded, and a newer version currently exists
RF_BeginDestroyed =0x00008000, ///< BeginDestroy has been called on the object.
RF_FinishDestroyed =0x00010000, ///< FinishDestroy has been called on the object.
// Misc. Flags
RF_BeingRegenerated =0x00020000, ///< Flagged on UObjects that are used to create UClasses (e.g. Blueprints) while they are regenerating their UClass on load (See FLinkerLoad::CreateExport()), as well as UClass objects in the midst of being created
RF_DefaultSubObject =0x00040000, ///< Flagged on subobjects that are defaults
RF_WasLoaded =0x00080000, ///< Flagged on UObjects that were loaded
RF_TextExportTransient =0x00100000, ///< Do not export object to text form (e.g. copy/paste). Generally used for sub-objects that can be regenerated from data in their parent object.
RF_LoadCompleted =0x00200000, ///< Object has been completely serialized by linkerload at least once. DO NOT USE THIS FLAG, It should be replaced with RF_WasLoaded.
RF_InheritableComponentTemplate = 0x00400000, ///< Archetype of the object can be in its super class
RF_DuplicateTransient =0x00800000, ///< Object should not be included in any type of duplication (copy/paste, binary duplication, etc.)
RF_StrongRefOnFrame =0x01000000, ///< References to this object from persistent function frame are handled as strong ones.
RF_NonPIEDuplicateTransient =0x02000000, ///< Object should not be included for duplication unless it's being duplicated for a PIE session
RF_Dynamic =0x04000000, ///< Field Only. Dynamic field - doesn't get constructed during static initialization, can be constructed multiple times
RF_WillBeLoaded =0x08000000, ///< This object was constructed during load and will be loaded shortly
RF_HasExternalPackage =0x10000000, ///< This object has an external package assigned and should look it up when getting the outermost package
}
+33
View File
@@ -18,6 +18,39 @@ public class FUrl
public string Portal { get; set; } = string.Empty; public string Portal { get; set; } = string.Empty;
public bool Valid { get; set; } = true; public bool Valid { get; set; } = true;
public string? GetOption(string match, string? defaultValue)
{
var len = match.Length;
if (len > 0)
{
foreach (var option in Options)
{
if (option.StartsWith(match))
{
if (option[len - 1] == '=' || option[len] == '=' || option.Length == len)
{
return option.Substring(len);
}
}
}
}
return defaultValue;
}
public string OptionsToString()
{
var optionsBuilder = new StringBuilder();
foreach (var op in Options)
{
optionsBuilder.Append('?');
optionsBuilder.Append(op);
}
return optionsBuilder.ToString();
}
public string ToString(bool fullyQualified) public string ToString(bool fullyQualified)
{ {
var result = new StringBuilder(); var result = new StringBuilder();
+26
View File
@@ -0,0 +1,26 @@
using Prospect.Unreal.Net.Actors;
namespace Prospect.Unreal.Core;
public class ULevel
{
public ULevel()
{
Actors = new List<AActor>();
}
/// <summary>
/// URL associated with this level.
/// </summary>
public FUrl URL { get; private set; }
/// <summary>
/// Array of all actors in this level, used by FActorIteratorBase and derived classes
/// </summary>
public List<AActor> Actors { get; private set; }
public void InitializeNetworkActors()
{
throw new NotImplementedException();
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Prospect.Unreal.Core;
public class UPackage
{
}
+13
View File
@@ -1,9 +1,12 @@
using Prospect.Unreal.Core; using Prospect.Unreal.Core;
using Prospect.Unreal.Runtime;
namespace Prospect.Unreal.Net.Actors; namespace Prospect.Unreal.Net.Actors;
public class AActor public class AActor
{ {
private bool bActorInitialized;
/// <summary> /// <summary>
/// Sets the value of Role without causing other side effects to this instance. /// Sets the value of Role without causing other side effects to this instance.
/// </summary> /// </summary>
@@ -32,4 +35,14 @@ public class AActor
// TODO: Implement // TODO: Implement
throw new NotImplementedException(); throw new NotImplementedException();
} }
public UWorld GetWorld()
{
// if ()
}
public bool IsActorInitialized()
{
return bActorInitialized;
}
} }
@@ -5,6 +5,37 @@ namespace Prospect.Unreal.Net.Actors;
public class AGameModeBase : AInfo public class AGameModeBase : AInfo
{ {
public AGameModeBase()
{
OptionsString = string.Empty;
}
/// <summary>
/// Save options string and parse it when needed
/// </summary>
public string OptionsString { get; set; }
public AGameSession? GameSession { get; set; }
public virtual void InitGame(string mapName, string options, out string errorMessage)
{
// Save Options for future use
OptionsString = options;
var spawnInfo = new FActorSpawnParameters
{
Instigator = GetInstigator(),
ObjectFlags = EObjectFlags.RF_Transient
};
GameSession = World.SpawnActor();
}
public virtual void InitGameState()
{
throw new NotImplementedException();
}
public void PreLogin(string options, string address, FUniqueNetIdRepl uniqueId, out string? errorMessage) public void PreLogin(string options, string address, FUniqueNetIdRepl uniqueId, out string? errorMessage)
{ {
// Login unique id must match server expected unique id type OR No unique id could mean game doesn't use them // Login unique id must match server expected unique id type OR No unique id could mean game doesn't use them
@@ -14,10 +45,23 @@ public class AGameModeBase : AInfo
public APlayerController? Login(UPlayer newPlayer, ENetRole inRemoteRole, string portal, string options, FUniqueNetIdRepl uniqueId, out string errorMessage) public APlayerController? Login(UPlayer newPlayer, ENetRole inRemoteRole, string portal, string options, FUniqueNetIdRepl uniqueId, out string errorMessage)
{ {
errorMessage = string.Empty; errorMessage = string.Empty;
throw new NotImplementedException();
if (GameSession == null)
{
errorMessage = "Failed to spawn player controller, GameSession is null";
return null; return null;
} }
errorMessage = GameSession.ApproveLogin(options);
if (!string.IsNullOrEmpty(errorMessage))
{
return null;
}
throw new NotImplementedException();
}
public void PostLogin(APlayerController newPlayer) public void PostLogin(APlayerController newPlayer)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -0,0 +1,12 @@
namespace Prospect.Unreal.Net.Actors;
public class AGameSession : AInfo
{
public string ApproveLogin(string options)
{
// Check;
// - SpectatorOnly
// - SplitscreenCount
return string.Empty;
}
}
@@ -0,0 +1,10 @@
namespace Prospect.Unreal.Net;
public enum ESpawnActorCollisionHandlingMethod
{
Undefined,
AlwaysSpawn,
AdjustIfPossibleButAlwaysSpawn,
AdjustIfPossibleButDontSpawnIfColliding,
DontSpawnIfColliding
}
@@ -0,0 +1,24 @@
namespace Prospect.Unreal.Net;
public enum ESpawnActorNameMode
{
/// <summary>
/// Fatal if unavailable, application will assert
/// </summary>
Required_Fatal,
/// <summary>
/// Report an error return null if unavailable
/// </summary>
Required_ErrorAndReturnNull,
/// <summary>
/// Return null if unavailable
/// </summary>
Required_ReturnNull,
/// <summary>
/// If the supplied Name is already in use the generate an unused one using the supplied version as a base
/// </summary>
Requested
}
@@ -0,0 +1,105 @@
using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Net.Actors;
namespace Prospect.Unreal.Net;
public ref struct FActorSpawnParameters
{
/// <summary>
/// A name to assign as the Name of the Actor being spawned.
/// If no value is specified, the name of the spawned Actor will be automatically generated using the form [Class]_[Number].
/// </summary>
public FName? Name { get; set; }
/// <summary>
/// An Actor to use as a template when spawning the new Actor.
/// The spawned Actor will be initialized using the property values of the template Actor.
/// If left NULL the class default object (CDO) will be used to initialize the spawned Actor.
/// </summary>
public AActor? Template { get; set; }
/// <summary>
/// The Actor that spawned this Actor. (Can be left as NULL).
/// </summary>
public AActor? Owner { get; set; }
/// <summary>
/// The APawn that is responsible for damage done by the spawned Actor. (Can be left as NULL).
/// </summary>
public AActor? Instigator { get; set; }
/// <summary>
/// The ULevel to spawn the Actor in, i.e. the Outer of the Actor.
/// If left as NULL the Outer of the Owner is used. If the Owner is NULL the persistent level is used.
/// </summary>
public ULevel? OverrideLevel { get; set; }
/// <summary>
/// The UPackage to set the Actor in.
/// If left as NULL the Package will not be set and the actor will be saved in the same package as the persistent level.
/// </summary>
public UPackage? OverridePackage { get; set; }
// UChildActorComponent
/// <summary>
/// The Guid to set to this actor. Should only be set when reinstancing blueprint actors.
/// </summary>
public FGuid? OverrideActorGuid { get; set; }
/// <summary>
/// Method for resolving collisions at the spawn point. Undefined means no override, use the actor's setting.
/// </summary>
public ESpawnActorCollisionHandlingMethod SpawnCollisionHandlingOverride { get; set; }
/// <summary>
/// Is the actor remotely owned.
/// This should only be set true by the package map when it is creating an actor on a client that was replicated from the server.
/// </summary>
public bool bRemoteOwned { get; set; }
/// <summary>
/// Determines whether spawning will not fail if certain conditions are not met.
/// If true, spawning will not fail because the class being spawned is `bStatic=true` or because the class of the template Actor is not the same as the class of the Actor being spawned.
/// </summary>
public bool bNoFail { get; set; }
/// <summary>
/// Determines whether the construction script will be run.
/// If true, the construction script will not be run on the spawned Actor.
/// Only applicable if the Actor is being spawned from a Blueprint.
/// </summary>
public bool bDeferConstruction { get; set; }
/// <summary>
/// Determines whether or not the actor may be spawned when running a construction script.
/// If true spawning will fail if a construction script is being run.
/// </summary>
public bool bAllowDuringConstructionScript { get; set; }
/// <summary>
/// Determines whether the begin play cycle will run on the spawned actor when in the editor.
/// </summary>
public bool bTemporaryEditorActor { get; set; }
/// <summary>
/// Determines whether or not the actor should be hidden from the Scene Outliner
/// </summary>
public bool bHideFromSceneOutliner { get; set; }
/// <summary>
/// Determines whether to create a new package for the actor or not.
/// </summary>
public bool bCreateActorPackage { get; set; }
/// <summary>
/// In which way should SpawnActor should treat the supplied Name if not none.
/// </summary>
public ESpawnActorNameMode NameMode { get; set; }
/// <summary>
/// Flags used to describe the spawned actor/object instance.
/// </summary>
public EObjectFlags ObjectFlags { get; set; }
}
@@ -12,15 +12,7 @@ public partial class UWorld
error = string.Empty; error = string.Empty;
// Make the option string. // Make the option string.
var optionsBuilder = new StringBuilder(); var options = inURL.OptionsToString();
foreach (var op in inURL.Options)
{
optionsBuilder.Append('?');
optionsBuilder.Append(op);
}
var options = optionsBuilder.ToString();
var gameMode = GetAuthGameMode(); var gameMode = GetAuthGameMode();
if (gameMode != null) if (gameMode != null)
+105 -4
View File
@@ -1,4 +1,3 @@
using System.Text;
using Prospect.Unreal.Core; using Prospect.Unreal.Core;
using Prospect.Unreal.Core.Names; using Prospect.Unreal.Core.Names;
using Prospect.Unreal.Exceptions; using Prospect.Unreal.Exceptions;
@@ -17,17 +16,65 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
private UGameInstance? _owningGameInstance; private UGameInstance? _owningGameInstance;
private AGameModeBase? _authorityGameMode; private AGameModeBase? _authorityGameMode;
private List<ULevel> _levels;
public UWorld(FUrl url) public UWorld()
{ {
Url = url;
_owningGameInstance = null; _owningGameInstance = null;
_authorityGameMode = null; _authorityGameMode = null;
_levels = new List<ULevel>();
} }
public FUrl Url { get; } /// <summary>
/// Persistent level containing the world info, default brush and actors spawned during gameplay among other things
/// </summary>
public ULevel? PersistentLevel { get; private set; }
/// <summary>
/// The NAME_GameNetDriver game connection(s) for client/server communication
/// </summary>
public UNetDriver? NetDriver { get; private set; } public UNetDriver? NetDriver { get; private set; }
/// <summary>
/// Whether actors have been initialized for play
/// </summary>
public bool bActorsInitialized { get; private set; }
/// <summary>
/// Whether BeginPlay has been called on actors
/// </summary>
public bool bBegunPlay { get; private set; }
/// <summary>
/// The URL that was used when loading this World.
/// </summary>
public FUrl? Url { get; private set; }
/// <summary>
/// Time in seconds since level began play, but IS paused when the game is paused, and IS dilated/clamped.
/// </summary>
public float TimeSeconds { get; private set; }
/// <summary>
/// Time in seconds since level began play, but IS NOT paused when the game is paused, and IS dilated/clamped.
/// </summary>
public float UnpausedTimeSeconds { get; private set; }
/// <summary>
/// Time in seconds since level began play, but IS NOT paused when the game is paused, and IS NOT dilated/clamped.
/// </summary>
public float RealTimeSeconds { get; private set; }
/// <summary>
/// Time in seconds since level began play, but IS paused when the game is paused, and IS NOT dilated/clamped.
/// </summary>
public float AudioTimeSeconds { get; private set; }
/// <summary>
/// Frame delta time in seconds adjusted by e.g. time dilation.
/// </summary>
public float DeltaTimeSeconds { get; private set; }
public void Tick(float deltaTime) public void Tick(float deltaTime)
{ {
if (NetDriver != null) if (NetDriver != null)
@@ -78,6 +125,50 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
return _authorityGameMode; return _authorityGameMode;
} }
public void InitializeActorsForPlay(FUrl inUrl, bool bResetTime)
{
// Don't reset time for seamless world transitions.
if (bResetTime)
{
TimeSeconds = 0.0f;
UnpausedTimeSeconds = 0.0f;
RealTimeSeconds = 0.0f;
AudioTimeSeconds = 0.0f;
}
// Get URL Options
var options = inUrl.OptionsToString();
// Set level info.
if (!string.IsNullOrEmpty(inUrl.GetOption("load", null)))
{
Url = inUrl;
}
// Init level gameplay info.
if (!AreActorsInitialized())
{
// Initialize network actors and start execution.
for (int i = 0; i < _levels.Count; i++)
{
_levels[i].InitializeNetworkActors();
}
// Enable actor script calls.
bStartup = true;
bActorsInitialized = true;
// Spawn server actors
// TODO: GEngine SpawnServerActors.
// Init the game mode.
if (_authorityGameMode != null && !_authorityGameMode.IsActorInitialized())
{
_authorityGameMode.InitGame(inUrl.Map /* TODO: FPaths.GetBaseFilename */, options, out _);
}
}
}
public bool Listen() public bool Listen()
{ {
if (NetDriver != null) if (NetDriver != null)
@@ -349,6 +440,16 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable
return true; return true;
} }
public bool HasBegunPlay()
{
return bBegunPlay && PersistentLevel != null && PersistentLevel.Actors.Count != 0;
}
public bool AreActorsInitialized()
{
return bActorsInitialized && PersistentLevel != null && PersistentLevel.Actors.Count != 0;
}
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
if (NetDriver != null) if (NetDriver != null)