diff --git a/src/Prospect.Server.Game/Client.cs b/src/Prospect.Server.Game/Client.cs index f2cc550..f230195 100644 --- a/src/Prospect.Server.Game/Client.cs +++ b/src/Prospect.Server.Game/Client.cs @@ -15,7 +15,7 @@ internal class Client public async Task Connect(string ipAddr, int port, FUrl worldUrl) { var connection = new UIpNetDriver(System.Net.IPAddress.Parse(ipAddr), port, false); - await using (var world = new ProspectWorld(worldUrl)) + await using (var world = new ProspectWorld()) connection.InitConnect(world, new FUrl { Host = System.Net.IPAddress.Parse(ipAddr), Port = port }); UnitConn = connection.ServerConnection; connection.ServerConnection.Handler?.BeginHandshaking(SendInitialJoin); diff --git a/src/Prospect.Unreal/Core/Math/FQuat.cs b/src/Prospect.Unreal/Core/Math/FQuat.cs new file mode 100644 index 0000000..5851474 --- /dev/null +++ b/src/Prospect.Unreal/Core/Math/FQuat.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Core.Math; + +public class FQuat +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Math/FRotator.cs b/src/Prospect.Unreal/Core/Math/FRotator.cs new file mode 100644 index 0000000..07f5a2b --- /dev/null +++ b/src/Prospect.Unreal/Core/Math/FRotator.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Core.Math; + +public class FRotator +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Math/FTransform.cs b/src/Prospect.Unreal/Core/Math/FTransform.cs new file mode 100644 index 0000000..8312b37 --- /dev/null +++ b/src/Prospect.Unreal/Core/Math/FTransform.cs @@ -0,0 +1,7 @@ +namespace Prospect.Unreal.Core.Math; + +public ref struct FTransform +{ + public FVector Location { get; set; } + public FRotator Rotation { get; set; } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Math/FVector.cs b/src/Prospect.Unreal/Core/Math/FVector.cs new file mode 100644 index 0000000..8a95271 --- /dev/null +++ b/src/Prospect.Unreal/Core/Math/FVector.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Core.Math; + +public class FVector +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/EObjectFlags.cs b/src/Prospect.Unreal/Core/Objects/EObjectFlags.cs similarity index 98% rename from src/Prospect.Unreal/Core/EObjectFlags.cs rename to src/Prospect.Unreal/Core/Objects/EObjectFlags.cs index 17258aa..71cec86 100644 --- a/src/Prospect.Unreal/Core/EObjectFlags.cs +++ b/src/Prospect.Unreal/Core/Objects/EObjectFlags.cs @@ -1,4 +1,4 @@ -namespace Prospect.Unreal.Core; +namespace Prospect.Unreal.Core.Objects; [Flags] public enum EObjectFlags diff --git a/src/Prospect.Unreal/Core/Objects/GUClassArray.cs b/src/Prospect.Unreal/Core/Objects/GUClassArray.cs new file mode 100644 index 0000000..a8c7765 --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/GUClassArray.cs @@ -0,0 +1,18 @@ +namespace Prospect.Unreal.Core.Objects; + +/// +/// Custom class to help with T::StaticClass() code. +/// +public class GUClassArray +{ + public static UClass StaticClass() + { + return StaticClass(typeof(T)); + } + + public static UClass StaticClass(Type type) + { + // TODO: Implement + return new UClass(); + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Objects/GUObjectArray.cs b/src/Prospect.Unreal/Core/Objects/GUObjectArray.cs new file mode 100644 index 0000000..d8c1bdd --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/GUObjectArray.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Core.Objects; + +public static class GUObjectArray +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Objects/UClass.cs b/src/Prospect.Unreal/Core/Objects/UClass.cs new file mode 100644 index 0000000..a122205 --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/UClass.cs @@ -0,0 +1,8 @@ +using Prospect.Unreal.Core.Properties; + +namespace Prospect.Unreal.Core.Objects; + +public class UClass : UStruct +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Objects/UObject.cs b/src/Prospect.Unreal/Core/Objects/UObject.cs new file mode 100644 index 0000000..0b26624 --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/UObject.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Core.Objects; + +public class UObject : UObjectBaseUtility +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Objects/UObjectBase.cs b/src/Prospect.Unreal/Core/Objects/UObjectBase.cs new file mode 100644 index 0000000..6e51d5c --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/UObjectBase.cs @@ -0,0 +1,85 @@ +using System.Runtime.CompilerServices; +using Prospect.Unreal.Core.Names; + +namespace Prospect.Unreal.Core.Objects; + +public class UObjectBase +{ + /// + /// Flags used to track and report various object states. + /// + private EObjectFlags _objectFlags; + + /// + /// Index into GObjectArray...very private. + /// + private int _internalIndex; + + /// + /// Class the object belongs to. + /// + private UClass _classPrivate; + + /// + /// Name of this object. + /// + private FName _namePrivate; + + /// + /// Object this object resides in. + /// + private UObject? _outerPrivate; + + public UObjectBase() + { + } + + /// + /// Returns the unique ID of the object...these are reused so it is only unique while the object is alive. + /// Useful as a tag. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetUniqueID() + { + return (uint)_internalIndex; + } + + /// + /// Returns the UClass that defines the fields of this object. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UClass GetClass() + { + return _classPrivate; + } + + /// + /// Returns the UObject this object resides in. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UObject? GetOuter() + { + return _outerPrivate; + } + + /// + /// Returns the logical name of this object. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FName GetFName() + { + return _namePrivate; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void SetFlagsTo(EObjectFlags newFlags) + { + _objectFlags = newFlags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EObjectFlags GetFlags() + { + return _objectFlags; + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Objects/UObjectBaseUtility.cs b/src/Prospect.Unreal/Core/Objects/UObjectBaseUtility.cs new file mode 100644 index 0000000..237dfd1 --- /dev/null +++ b/src/Prospect.Unreal/Core/Objects/UObjectBaseUtility.cs @@ -0,0 +1,79 @@ +using System.Runtime.CompilerServices; + +namespace Prospect.Unreal.Core.Objects; + +public class UObjectBaseUtility : UObjectBase +{ + /* + * Flags + */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFlags(EObjectFlags newFlags) + { + SetFlagsTo(GetFlags() | newFlags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ClearFlags(EObjectFlags newFlags) + { + SetFlagsTo(GetFlags() | ~newFlags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasAnyFlags(EObjectFlags flagsToCheck) + { + return (GetFlags() & flagsToCheck) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasAllFlags(EObjectFlags flagsToCheck) + { + return (GetFlags() & flagsToCheck) == flagsToCheck; + } + + /* + * Marks + */ + public bool IsUnreachable() + { + // TODO: GUObjectArray + return false; + } + + /* + * Outer & Package + */ + public UObject? GetTypedOuter(UClass target) + { + UObject? result = null; + for (var nextOuter = GetOuter(); result == null && nextOuter != null; nextOuter = nextOuter.GetOuter()) + { + if (nextOuter.IsA(target)) + { + result = nextOuter; + } + } + return result; + } + + public T? GetTypedOuter() where T : UObject + { + return (T?)GetTypedOuter(GUClassArray.StaticClass()); + } + + /* + * Class + */ + public bool IsChildOfWorkaround(UClass objClass, UClass testClass) + { + return objClass.IsChildOf(testClass); + } + + public bool IsA(UClass someBase) + { + var someBaseClass = someBase; + var thisClass = GetClass(); + + return IsChildOfWorkaround(thisClass, someBaseClass); + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Properties/UField.cs b/src/Prospect.Unreal/Core/Properties/UField.cs new file mode 100644 index 0000000..d967b17 --- /dev/null +++ b/src/Prospect.Unreal/Core/Properties/UField.cs @@ -0,0 +1,8 @@ +using Prospect.Unreal.Core.Objects; + +namespace Prospect.Unreal.Core.Properties; + +public class UField : UObject +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Properties/UStruct.cs b/src/Prospect.Unreal/Core/Properties/UStruct.cs new file mode 100644 index 0000000..723d5f9 --- /dev/null +++ b/src/Prospect.Unreal/Core/Properties/UStruct.cs @@ -0,0 +1,37 @@ +namespace Prospect.Unreal.Core.Properties; + +public class UStruct : UField +{ + private UStruct? _superStruct; + + public bool IsChildOf(UStruct? someBase) + { + if (someBase == null) + { + return false; + } + + var bOldResult = false; + + for (var tempStruct = this; tempStruct != null; tempStruct = tempStruct.GetSuperStruct()) + { + if (tempStruct == someBase) + { + bOldResult = true; + break; + } + } + + return bOldResult; + } + + public UStruct? GetSuperStruct() + { + return _superStruct; + } + + public virtual void SetSuperStruct(UStruct newSuperStruct) + { + _superStruct = newSuperStruct; + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/ULevel.cs b/src/Prospect.Unreal/Core/ULevel.cs index 1eccac6..5eacdc7 100644 --- a/src/Prospect.Unreal/Core/ULevel.cs +++ b/src/Prospect.Unreal/Core/ULevel.cs @@ -1,8 +1,10 @@ -using Prospect.Unreal.Net.Actors; +using Prospect.Unreal.Core.Objects; +using Prospect.Unreal.Net.Actors; +using Prospect.Unreal.Runtime; namespace Prospect.Unreal.Core; -public class ULevel +public class ULevel : UObject { public ULevel() { @@ -19,6 +21,13 @@ public class ULevel /// public List Actors { get; private set; } + /// + /// The World that has this level in its Levels array. + /// This is not the same as GetOuter(), because GetOuter() for a streaming level is a vestigial world that is not used. + /// It should not be accessed during BeginDestroy(), just like any other UObject references, since GC may occur in any order. + /// + public UWorld OwningWorld { get; private set; } + public void InitializeNetworkActors() { throw new NotImplementedException(); diff --git a/src/Prospect.Unreal/Core/UObject.cs b/src/Prospect.Unreal/Core/UObject.cs deleted file mode 100644 index 2e71345..0000000 --- a/src/Prospect.Unreal/Core/UObject.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Prospect.Unreal.Core; - -public class UObject -{ - -} \ No newline at end of file diff --git a/src/Prospect.Unreal/Net/Actors/AActor.cs b/src/Prospect.Unreal/Net/Actors/AActor.cs index aa05fb0..efdb805 100644 --- a/src/Prospect.Unreal/Net/Actors/AActor.cs +++ b/src/Prospect.Unreal/Net/Actors/AActor.cs @@ -1,11 +1,18 @@ using Prospect.Unreal.Core; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Runtime; namespace Prospect.Unreal.Net.Actors; -public class AActor +public class AActor : UObject { private bool bActorInitialized; + + // TODO: UPROPERTY(BlueprintReadWrite, ReplicatedUsing=OnRep_Instigator, meta=(ExposeOnSpawn=true, AllowPrivateAccess=true), Category=Actor) + /// + /// Pawn responsible for damage and other gameplay events caused by this actor. + /// + private APawn? _instigator; /// /// Sets the value of Role without causing other side effects to this instance. @@ -36,9 +43,38 @@ public class AActor throw new NotImplementedException(); } - public UWorld GetWorld() + public UWorld? GetWorld() { - // if () + if (!HasAnyFlags(EObjectFlags.RF_ClassDefaultObject)) + { + var outer = GetOuter(); + if (outer == null) + { + return null; + } + + if (!outer.HasAnyFlags(EObjectFlags.RF_BeginDestroyed) && + !outer.IsUnreachable()) + { + var level = GetLevel(); + if (level != null) + { + return level.OwningWorld; + } + } + } + + return null; + } + + public ULevel? GetLevel() + { + return GetTypedOuter(); + } + + public APawn? GetInstigator() + { + return _instigator; } public bool IsActorInitialized() diff --git a/src/Prospect.Unreal/Net/Actors/AGameModeBase.cs b/src/Prospect.Unreal/Net/Actors/AGameModeBase.cs index 0e0b61b..efd5437 100644 --- a/src/Prospect.Unreal/Net/Actors/AGameModeBase.cs +++ b/src/Prospect.Unreal/Net/Actors/AGameModeBase.cs @@ -1,4 +1,5 @@ using Prospect.Unreal.Core; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Runtime; namespace Prospect.Unreal.Net.Actors; @@ -19,6 +20,12 @@ public class AGameModeBase : AInfo public virtual void InitGame(string mapName, string options, out string errorMessage) { + // Default error. + errorMessage = string.Empty; + + // Find world. + var world = GetWorld(); + // Save Options for future use OptionsString = options; @@ -28,7 +35,7 @@ public class AGameModeBase : AInfo ObjectFlags = EObjectFlags.RF_Transient }; - GameSession = World.SpawnActor(); + // GameSession = world.SpawnActor(); } public virtual void InitGameState() diff --git a/src/Prospect.Unreal/Net/Actors/APawn.cs b/src/Prospect.Unreal/Net/Actors/APawn.cs new file mode 100644 index 0000000..69957f7 --- /dev/null +++ b/src/Prospect.Unreal/Net/Actors/APawn.cs @@ -0,0 +1,6 @@ +namespace Prospect.Unreal.Net.Actors; + +public class APawn : AActor +{ + +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Net/FActorSpawnParameters.cs b/src/Prospect.Unreal/Net/FActorSpawnParameters.cs index da3b9ae..1c564d7 100644 --- a/src/Prospect.Unreal/Net/FActorSpawnParameters.cs +++ b/src/Prospect.Unreal/Net/FActorSpawnParameters.cs @@ -1,5 +1,6 @@ using Prospect.Unreal.Core; using Prospect.Unreal.Core.Names; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Net.Actors; namespace Prospect.Unreal.Net; @@ -10,7 +11,7 @@ public ref struct FActorSpawnParameters /// 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]. /// - public FName? Name { get; set; } + public FName Name { get; set; } = EName.None; /// /// An Actor to use as a template when spawning the new Actor. diff --git a/src/Prospect.Unreal/Runtime/UGameInstance.cs b/src/Prospect.Unreal/Runtime/UGameInstance.cs index 18008be..e58ab81 100644 --- a/src/Prospect.Unreal/Runtime/UGameInstance.cs +++ b/src/Prospect.Unreal/Runtime/UGameInstance.cs @@ -1,4 +1,6 @@ using Prospect.Unreal.Core; +using Prospect.Unreal.Core.Objects; +using Prospect.Unreal.Net; using Prospect.Unreal.Net.Actors; namespace Prospect.Unreal.Runtime; @@ -8,6 +10,13 @@ public class UGameInstance public AGameModeBase? CreateGameModeForURL(FUrl inUrl, UWorld inWorld) { // TODO: World.SpawnActor - return new AGameModeBase(); + + var spawnInfo = new FActorSpawnParameters + { + SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod.AlwaysSpawn, + ObjectFlags = EObjectFlags.RF_Transient + }; + + return inWorld.SpawnActor(GUClassArray.StaticClass(), spawnInfo); } } \ No newline at end of file diff --git a/src/Prospect.Unreal/Runtime/UWorld.LevelActor.cs b/src/Prospect.Unreal/Runtime/UWorld.LevelActor.cs index acf1774..51788c6 100644 --- a/src/Prospect.Unreal/Runtime/UWorld.LevelActor.cs +++ b/src/Prospect.Unreal/Runtime/UWorld.LevelActor.cs @@ -1,5 +1,7 @@ using System.Text; using Prospect.Unreal.Core; +using Prospect.Unreal.Core.Math; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Net; using Prospect.Unreal.Net.Actors; @@ -42,4 +44,54 @@ public partial class UWorld Logger.Warning("Login failed: No game mode set"); return null; } + + public AActor SpawnActor(UClass? clazz, FVector? location, FRotator? rotation, FActorSpawnParameters spawnParameters) + { + var transform = new FTransform(); + + if (location != null) + { + transform.Location = location; + } + + if (rotation != null) + { + // TODO: FQuat + // transform.Rotation = + } + + return SpawnActor(clazz, transform, spawnParameters); + } + + public AActor SpawnActor(UClass? clazz, FTransform userTransform, FActorSpawnParameters spawnParameters) + { + if (clazz == null) + { + Logger.Warning("SpawnActor failed because no class was specified"); + return null; + } + + // TODO: Bunch of if checks + var levelToSpawnIn = spawnParameters.OverrideLevel; + if (levelToSpawnIn == null) + { + // Spawn in the same level as the owner if we have one. + levelToSpawnIn = (spawnParameters.Owner != null) ? spawnParameters.Owner.GetLevel() : _currentLevel; + } + + var newActorName = spawnParameters.Name; + var template = spawnParameters.Template; + + if (template == null) + { + // template = clazz.GetDefaultObject(); + } + + return null; + } + + public T SpawnActor(UClass? clazz, FActorSpawnParameters spawnParameters) where T : AActor + { + return (T)SpawnActor(clazz, null, null, spawnParameters); + } } \ No newline at end of file diff --git a/src/Prospect.Unreal/Runtime/UWorld.cs b/src/Prospect.Unreal/Runtime/UWorld.cs index f64d913..cb95501 100644 --- a/src/Prospect.Unreal/Runtime/UWorld.cs +++ b/src/Prospect.Unreal/Runtime/UWorld.cs @@ -1,5 +1,6 @@ using Prospect.Unreal.Core; using Prospect.Unreal.Core.Names; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Exceptions; using Prospect.Unreal.Net; using Prospect.Unreal.Net.Actors; @@ -16,7 +17,17 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable private UGameInstance? _owningGameInstance; private AGameModeBase? _authorityGameMode; + + /// + /// Array of levels currently in this world. Not serialized to disk to avoid hard references. + /// private List _levels; + + /// + /// Pointer to the current level being edited. + /// Level has to be in the Levels array and == PersistentLevel in the game. + /// + private ULevel? _currentLevel; public UWorld() { @@ -40,6 +51,11 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable /// public bool bActorsInitialized { get; private set; } + /// + /// Is the world in its actor initialization phase. + /// + public bool bStartup { get; private set; } + /// /// Whether BeginPlay has been called on actors /// @@ -149,9 +165,9 @@ public abstract partial class UWorld : FNetworkNotify, IAsyncDisposable if (!AreActorsInitialized()) { // Initialize network actors and start execution. - for (int i = 0; i < _levels.Count; i++) + foreach (var level in _levels) { - _levels[i].InitializeNetworkActors(); + level.InitializeNetworkActors(); } // Enable actor script calls. diff --git a/src/Prospect.Unreal/Serialization/FNetBitWriter.cs b/src/Prospect.Unreal/Serialization/FNetBitWriter.cs index 91d5ade..7164ffb 100644 --- a/src/Prospect.Unreal/Serialization/FNetBitWriter.cs +++ b/src/Prospect.Unreal/Serialization/FNetBitWriter.cs @@ -1,5 +1,6 @@ using Prospect.Unreal.Core; using Prospect.Unreal.Core.Names; +using Prospect.Unreal.Core.Objects; using Prospect.Unreal.Net; namespace Prospect.Unreal.Serialization;