Fix even more issues uncovered by tests
- Fixed the time mode toggle not working during testing (changed the runner) - Made the testing thing wait until the time toggle finishes - Fixed the Game.Enter/Exit event being triggered on time mode change - Added a way to spawn and despawn the player's machine (which doesn't work yet) - Fixed the Player.Id property always being 0 - Attempted to fix the fake action inputs not working in simulation
This commit is contained in:
parent
5602ef9268
commit
966fdd4c3a
11 changed files with 178 additions and 46 deletions
|
@ -41,6 +41,8 @@ namespace TechbloxModdingAPI.App
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (GameReloadedPatch.IsReload)
|
||||
return; // Toggling time mode
|
||||
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID });
|
||||
IsInGame = false;
|
||||
loadingFinished = false;
|
||||
|
@ -50,6 +52,8 @@ namespace TechbloxModdingAPI.App
|
|||
|
||||
public void Ready()
|
||||
{
|
||||
if (GameReloadedPatch.IsReload)
|
||||
return; // Toggling time mode
|
||||
enteredGame = true;
|
||||
Player.Joined += OnPlayerJoined;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ namespace TechbloxModdingAPI
|
|||
var id = initializer(this);
|
||||
if (!dict.ContainsKey(id)) // Multiple instances may be created
|
||||
dict.Add(id, this);
|
||||
Id = id;
|
||||
}
|
||||
|
||||
#region ECS initializer stuff
|
||||
|
|
|
@ -14,12 +14,10 @@ namespace TechbloxModdingAPI.Engines
|
|||
[HarmonyPatch]
|
||||
static class GameLoadedTimeStoppedEnginePatch
|
||||
{
|
||||
public static EntitiesSubmissionScheduler Scheduler { get; private set; }
|
||||
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
|
||||
{
|
||||
// register all game engines, including deterministic
|
||||
GameEngineManager.RegisterEngines(stateSyncReg);
|
||||
Scheduler = stateSyncReg.enginesRoot.scheduler;
|
||||
// register command engines
|
||||
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
|
||||
stateSyncReg); - uREPL C# compilation not supported anymore */
|
||||
|
@ -35,10 +33,10 @@ namespace TechbloxModdingAPI.Engines
|
|||
[HarmonyPatch]
|
||||
static class GameLoadedTimeRunningEnginePatch
|
||||
{
|
||||
public static EntitiesSubmissionScheduler Scheduler { get; private set; }
|
||||
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
|
||||
{
|
||||
GameLoadedTimeStoppedEnginePatch.Postfix(stateSyncReg);
|
||||
GameEngineManager.RegisterEngines(stateSyncReg);
|
||||
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
|
||||
}
|
||||
|
||||
public static MethodBase TargetMethod()
|
||||
|
@ -47,6 +45,28 @@ namespace TechbloxModdingAPI.Engines
|
|||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
static class GameReloadedPatch
|
||||
{
|
||||
internal static bool IsReload;
|
||||
public static void Prefix() => IsReload = true;
|
||||
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame");
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
static class GameSwitchedToPatch
|
||||
{
|
||||
public static void Prefix() => GameReloadedPatch.IsReload = false;
|
||||
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
static class MenuSwitchedToPatch
|
||||
{
|
||||
public static void Prefix() => GameReloadedPatch.IsReload = false;
|
||||
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu");
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
class MenuLoadedEnginePatch
|
||||
{
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
using System;
|
||||
using RobocraftX.Common.Input;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.Common.Input;
|
||||
using Svelto.ECS;
|
||||
using TechbloxModdingAPI.App;
|
||||
using TechbloxModdingAPI.Utility;
|
||||
|
||||
namespace TechbloxModdingAPI.Input
|
||||
{
|
||||
public static class FakeInput
|
||||
{
|
||||
private static readonly FakeInputEngine inputEngine = new FakeInputEngine();
|
||||
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine();
|
||||
|
||||
/// <summary>
|
||||
/// Customize the local input.
|
||||
|
@ -103,39 +101,39 @@ namespace TechbloxModdingAPI.Input
|
|||
}
|
||||
|
||||
public static void ActionInput(uint playerID = uint.MaxValue, bool toggleMode = false, bool forward = false, bool backward = false, bool up = false, bool down = false, bool left = false, bool right = false, bool sprint = false, bool toggleFly = false, bool alt = false, bool primary = false, bool secondary = false, bool tertiary = false, bool primaryHeld = false, bool secondaryHeld = false, bool toggleUnitGrid = false, bool ctrl = false, bool toggleColourMode = false, bool scaleBlockUp = false, bool scaleBlockDown = false, bool rotateBlockClockwise = false, bool rotateBlockCounterclockwise = false, bool cutSelection = false, bool copySelection = false, bool deleteSelection = false)
|
||||
{
|
||||
if (playerID == uint.MaxValue)
|
||||
{
|
||||
playerID = inputEngine.GetLocalPlayerID();
|
||||
}
|
||||
ref LocalPlayerInputEntityStruct currentInput = ref inputEngine.GetPlayerInputRef(playerID);
|
||||
{ // TODO: We can only alter our own inputs clientside
|
||||
ref var currentInput = ref inputEngine._localInputCache;
|
||||
|
||||
//Utility.Logging.CommandLog($"Current sim frame {currentInput.frame}");
|
||||
// set inputs
|
||||
if (toggleMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play
|
||||
if (forward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Forward;
|
||||
if (backward) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Backward;
|
||||
if (up) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Up;
|
||||
if (down) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Down;
|
||||
if (left) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Left;
|
||||
if (right) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Right;
|
||||
if (sprint) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.Sprint;
|
||||
//if (toggleFly) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
|
||||
//if (alt) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.AltAction;
|
||||
if (primary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryAction;
|
||||
if (secondary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryAction;
|
||||
if (tertiary) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
|
||||
if (primaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
|
||||
if (secondaryHeld) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
|
||||
//if (toggleUnitGrid) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
|
||||
if (ctrl) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CtrlAction;
|
||||
if (toggleColourMode) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
|
||||
if (scaleBlockUp) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
|
||||
if (scaleBlockDown) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
|
||||
if (rotateBlockClockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
|
||||
if (rotateBlockCounterclockwise) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
|
||||
if (cutSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CutSelection;
|
||||
if (copySelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.CopySelection;
|
||||
if (deleteSelection) currentInput.actionMask |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
|
||||
if (toggleMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleTimeRunningModePlay; //TODO: Test, play
|
||||
if (forward) currentInput |= RobocraftX.Common.Input.ActionInput.Forward;
|
||||
if (backward) currentInput |= RobocraftX.Common.Input.ActionInput.Backward;
|
||||
if (up) currentInput |= RobocraftX.Common.Input.ActionInput.Up;
|
||||
if (down) currentInput |= RobocraftX.Common.Input.ActionInput.Down;
|
||||
if (left) currentInput |= RobocraftX.Common.Input.ActionInput.Left;
|
||||
if (right) currentInput |= RobocraftX.Common.Input.ActionInput.Right;
|
||||
if (sprint) currentInput |= RobocraftX.Common.Input.ActionInput.Sprint;
|
||||
//if (toggleFly) currentInput |= RobocraftX.Common.Input.ActionInput.SwitchFlyMode;
|
||||
//if (alt) currentInput |= RobocraftX.Common.Input.ActionInput.AltAction;
|
||||
if (primary) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionClick;
|
||||
if (secondary) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionClick;
|
||||
if (tertiary) currentInput |= RobocraftX.Common.Input.ActionInput.TertiaryAction;
|
||||
if (primaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.PrimaryActionHeld;
|
||||
if (secondaryHeld) currentInput |= RobocraftX.Common.Input.ActionInput.SecondaryActionHeld;
|
||||
//if (toggleUnitGrid) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleUnitGrid;
|
||||
if (ctrl) currentInput |= RobocraftX.Common.Input.ActionInput.CtrlAction;
|
||||
if (toggleColourMode) currentInput |= RobocraftX.Common.Input.ActionInput.ToggleColourMode;
|
||||
if (scaleBlockUp) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockUp;
|
||||
if (scaleBlockDown) currentInput |= RobocraftX.Common.Input.ActionInput.ScaleBlockDown;
|
||||
if (rotateBlockClockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockClockwise;
|
||||
if (rotateBlockCounterclockwise) currentInput |= RobocraftX.Common.Input.ActionInput.RotateBlockAnticlockwise;
|
||||
if (cutSelection) currentInput |= RobocraftX.Common.Input.ActionInput.CutSelection;
|
||||
if (copySelection) currentInput |= RobocraftX.Common.Input.ActionInput.CopySelection;
|
||||
if (deleteSelection) currentInput |= RobocraftX.Common.Input.ActionInput.DeleteSelection;
|
||||
|
||||
if(Game.CurrentGame().IsTimeStopped)
|
||||
inputEngine.HandleCustomInput(); // Only gets called when online, so calling it here as well
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace TechbloxModdingAPI.Input
|
|||
|
||||
public bool IsReady = false;
|
||||
|
||||
internal ActionInput _localInputCache;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsReady = false;
|
||||
|
@ -86,6 +88,14 @@ namespace TechbloxModdingAPI.Input
|
|||
return ref entitiesDB.QueryEntity<LocalPlayerInputEntityStruct>(egid);
|
||||
}
|
||||
|
||||
internal void HandleCustomInput()
|
||||
{
|
||||
if (!LocalPlayerIDUtility.DoesLocalPlayerExist(entitiesDB))
|
||||
return;
|
||||
GetPlayerInputRef(GetLocalPlayerID()).actionMask |= _localInputCache;
|
||||
_localInputCache = default;
|
||||
}
|
||||
|
||||
public uint GetLocalPlayerID()
|
||||
{
|
||||
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB);
|
||||
|
|
19
TechbloxModdingAPI/Input/FakeInputPatch.cs
Normal file
19
TechbloxModdingAPI/Input/FakeInputPatch.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace TechbloxModdingAPI.Input
|
||||
{
|
||||
[HarmonyPatch]
|
||||
public static class FakeInputPatch
|
||||
{
|
||||
public static void Prefix()
|
||||
{
|
||||
FakeInput.inputEngine.HandleCustomInput(); // This gets called right before the input is sent to the server
|
||||
}
|
||||
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return AccessTools.Method("RobocraftX.Multiplayer.Input.NetworkInputRecorderEngine:RecordDeterministicInput");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,6 +125,7 @@ namespace TechbloxModdingAPI
|
|||
})
|
||||
{
|
||||
this.Type = player;
|
||||
Id = base.Id.entityID;
|
||||
}
|
||||
|
||||
// object fields & properties
|
||||
|
@ -434,16 +435,39 @@ namespace TechbloxModdingAPI
|
|||
playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter the given seat.
|
||||
/// </summary>
|
||||
/// <param name="seat">The seat to enter.</param>
|
||||
public void EnterSeat(Seat seat)
|
||||
{
|
||||
playerEngine.EnterSeat(Id, seat.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit the seat the player is currently in.
|
||||
/// </summary>
|
||||
public void ExitSeat()
|
||||
{
|
||||
playerEngine.ExitSeat(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawn the machine the player is holding in time running mode.
|
||||
/// </summary>
|
||||
public bool SpawnMachine()
|
||||
{
|
||||
return playerEngine.SpawnMachine(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawn the player's machine in time running mode and place it in their hand.
|
||||
/// </summary>
|
||||
public bool DespawnMachine()
|
||||
{
|
||||
return playerEngine.DespawnMachine(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the block the player is currently looking at in build mode.
|
||||
/// </summary>
|
||||
|
|
|
@ -12,6 +12,7 @@ using Gamecraft.GUI.HUDFeedbackBlocks;
|
|||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Multiplayer;
|
||||
using RobocraftX.PilotSeat;
|
||||
using RobocraftX.SimulationModeState;
|
||||
using Svelto.ECS;
|
||||
using Techblox.Camera;
|
||||
using Unity.Mathematics;
|
||||
|
@ -20,6 +21,7 @@ using Svelto.ECS.EntityStructs;
|
|||
using Techblox.BuildingDrone;
|
||||
|
||||
using TechbloxModdingAPI.Engines;
|
||||
using TechbloxModdingAPI.Input;
|
||||
using TechbloxModdingAPI.Utility;
|
||||
|
||||
namespace TechbloxModdingAPI.Players
|
||||
|
@ -205,6 +207,8 @@ namespace TechbloxModdingAPI.Players
|
|||
|
||||
public void EnterSeat(uint playerId, EGID seatId)
|
||||
{
|
||||
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
|
||||
return;
|
||||
PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId);
|
||||
var opt = GetCharacterStruct<CharacterPilotSeatEntityStruct>(playerId, out var group);
|
||||
if (!opt) return;
|
||||
|
@ -222,6 +226,8 @@ namespace TechbloxModdingAPI.Players
|
|||
|
||||
public void ExitSeat(uint playerId)
|
||||
{
|
||||
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
|
||||
return;
|
||||
EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup);
|
||||
var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid);
|
||||
if (!opt) return;
|
||||
|
@ -229,6 +235,32 @@ namespace TechbloxModdingAPI.Players
|
|||
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);
|
||||
}
|
||||
|
||||
public bool SpawnMachine(uint playerId)
|
||||
{
|
||||
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
|
||||
return false;
|
||||
EGID egid = new EGID(playerId, CharacterExclusiveGroups.MachineSpawningGroup);
|
||||
if (!entitiesDB.Exists<CharacterTagEntityStruct>(egid))
|
||||
return false;
|
||||
//Functions.SwapEntityGroup<CharacterEntityDescriptor>(egid, CharacterExclusiveGroups.OnFootGroup);
|
||||
FakeInput.ActionInput(playerId, primary: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DespawnMachine(uint playerId)
|
||||
{
|
||||
if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB))
|
||||
return false;
|
||||
GetCharacterStruct<CharacterTagEntityStruct>(playerId, out var group);
|
||||
if (group.isInvalid)
|
||||
return false;
|
||||
EGID egid = new EGID(playerId, group);
|
||||
if (!entitiesDB.Exists<CharacterTagEntityStruct>(egid))
|
||||
return false;
|
||||
Functions.SwapEntityGroup<CharacterEntityDescriptor>(egid, CharacterExclusiveGroups.MachineSpawningGroup);
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetPing()
|
||||
{
|
||||
return entitiesDB
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Svelto.Tasks;
|
||||
using Svelto.Tasks.Enumerators;
|
||||
|
@ -7,6 +8,7 @@ using Unity.Mathematics;
|
|||
using TechbloxModdingAPI.App;
|
||||
using TechbloxModdingAPI.Blocks;
|
||||
using TechbloxModdingAPI.Tests;
|
||||
using TechbloxModdingAPI.Utility;
|
||||
|
||||
namespace TechbloxModdingAPI.Players
|
||||
{
|
||||
|
@ -45,7 +47,20 @@ namespace TechbloxModdingAPI.Players
|
|||
[APITestCase(TestType.SimulationMode)]
|
||||
public static IEnumerator<TaskContract> SeatEventTestSim()
|
||||
{
|
||||
yield return new WaitForSecondsEnumerator(1).Continue();
|
||||
Assert.Equal(Player.LocalPlayer.SpawnMachine(), true, "Failed to spawn the player's machine.", "Successfully spawned the player's machine.");
|
||||
yield return new WaitForSecondsEnumerator(1).Continue();
|
||||
var seats = Game.CurrentGame().GetBlocksInGame(BlockIDs.DriverSeat);
|
||||
int c = 0;
|
||||
while (seats.Length == 0 && c < 10)
|
||||
{
|
||||
Logging.MetaLog("Waiting for a seat to be spawned...");
|
||||
yield return new WaitForSecondsEnumerator(1).Continue();
|
||||
Console.WriteLine("Spawn machine: " + Player.LocalPlayer.SpawnMachine());
|
||||
seats = Game.CurrentGame().GetBlocksInGame(BlockIDs.DriverSeat);
|
||||
c++;
|
||||
}
|
||||
|
||||
if (seats.Length == 0)
|
||||
{
|
||||
Assert.Fail("No driver seat found!");
|
||||
|
|
|
@ -7,7 +7,9 @@ using System.Linq; // welcome to the dark side
|
|||
using Svelto.Tasks;
|
||||
using Svelto.Tasks.Lean;
|
||||
using Svelto.Tasks.Enumerators;
|
||||
using Svelto.Tasks.Lean.Unity;
|
||||
using UnityEngine;
|
||||
|
||||
using TechbloxModdingAPI.App;
|
||||
using TechbloxModdingAPI.Tasks;
|
||||
using TechbloxModdingAPI.Utility;
|
||||
|
@ -64,7 +66,7 @@ namespace TechbloxModdingAPI.Tests
|
|||
_testsCountPassed = 0;
|
||||
_testsCountFailed = 0;
|
||||
// flow control
|
||||
Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.ClientLean.EveryFrameStepRunner_TimeRunningAndStopped); };
|
||||
Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); };
|
||||
Game.Exit += (s, a) => state = "ReturningFromGame";
|
||||
Client.EnterMenu += (sender, args) =>
|
||||
{
|
||||
|
@ -165,6 +167,7 @@ namespace TechbloxModdingAPI.Tests
|
|||
};
|
||||
for (var index = 0; index < testTypesToRun.Length; index++)
|
||||
{
|
||||
Logging.MetaLog($"Running test type {testTypesToRun[index]}");
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
|
@ -206,7 +209,16 @@ namespace TechbloxModdingAPI.Tests
|
|||
}
|
||||
|
||||
if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test
|
||||
{
|
||||
bool running = currentGame.IsTimeRunning;
|
||||
currentGame.ToggleTimeMode();
|
||||
while (running ? !currentGame.IsTimeStopped : !currentGame.IsTimeRunning)
|
||||
{
|
||||
Logging.MetaLog($"Waiting for time to {(running?"stop":"start")}...");
|
||||
yield return new WaitForSecondsEnumerator(1).Continue();
|
||||
}
|
||||
}
|
||||
|
||||
yield return new WaitForSecondsEnumerator(5).Continue();
|
||||
}
|
||||
// exit game
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using RobocraftX.StateSync;
|
||||
using Svelto.ECS;
|
||||
|
|
Loading…
Reference in a new issue