Browse Source

Fix issues uncovered by the tests

- Fixed the game enter API
- Fixed ToggleTimeMode() placing the player underground by using the full switch animation
- Fixed the menu enter/exit events only firing once due to the game keeping the menu loaded
- Moved everything from AppEngine to GameMenuEngine for consistency
- Reimplemented the Block.Static property, it should actually work again too
- Made the block property test only use blocks placed during the previous test, improved error handling and temporarily disabled testing the Material and the Flipped properties as they cause the game to crash
tags/v2.1.0
NorbiPeti 2 years ago
parent
commit
f53d0b63e7
8 changed files with 115 additions and 52 deletions
  1. +0
    -18
      TechbloxModdingAPI/App/AppEngine.cs
  2. +9
    -14
      TechbloxModdingAPI/App/Client.cs
  3. +1
    -1
      TechbloxModdingAPI/App/Game.cs
  4. +2
    -1
      TechbloxModdingAPI/App/GameGameEngine.cs
  5. +72
    -9
      TechbloxModdingAPI/App/GameMenuEngine.cs
  6. +2
    -2
      TechbloxModdingAPI/Block.cs
  7. +27
    -5
      TechbloxModdingAPI/Blocks/BlockTests.cs
  8. +2
    -2
      TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs

+ 0
- 18
TechbloxModdingAPI/App/AppEngine.cs View File

@@ -40,23 +40,5 @@ namespace TechbloxModdingAPI.App
get;
private set;
} = false;

public Game[] GetMyGames()
{
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var mgsevsB = mgsevs.ToBuffer().buffer;
Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevsB[i].ID);
}
return games;
}
}

public struct MenuEventArgs
{

}
}

+ 9
- 14
TechbloxModdingAPI/App/Client.cs View File

@@ -14,22 +14,19 @@ namespace TechbloxModdingAPI.App
/// </summary>
public class Client
{
// extensible engine
protected static AppEngine appEngine = new AppEngine();

protected static Func<object> ErrorHandlerInstanceGetter;
protected static Func<object> ErrorHandlerInstanceGetter;

protected static Action<object, Error> EnqueueError;

protected static Action<object> HandleErrorClosed;

/// <summary>
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => appEngine.EnterMenu += value;
remove => appEngine.EnterMenu -= value;
add => Game.menuEngine.EnterMenu += value;
remove => Game.menuEngine.EnterMenu -= value;
}

/// <summary>
@@ -37,8 +34,8 @@ namespace TechbloxModdingAPI.App
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu
{
add => appEngine.ExitMenu += value;
remove => appEngine.ExitMenu -= value;
add => Game.menuEngine.ExitMenu += value;
remove => Game.menuEngine.ExitMenu -= value;
}
/// <summary>
@@ -69,8 +66,8 @@ namespace TechbloxModdingAPI.App
{
get
{
if (!appEngine.IsInMenu) return new Game[0];
return appEngine.GetMyGames();
if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
return Game.menuEngine.GetMyGames();
}
}

@@ -80,7 +77,7 @@ namespace TechbloxModdingAPI.App
/// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
public bool InMenu
{
get => appEngine.IsInMenu;
get => Game.menuEngine.IsInMenu;
}

/// <summary>
@@ -119,8 +116,6 @@ namespace TechbloxModdingAPI.App
/*HandleErrorClosed = (Action<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);*/
// register engines
MenuEngineManager.AddMenuEngine(appEngine);
}

// Creating delegates once is faster than reflection every time


+ 1
- 1
TechbloxModdingAPI/App/Game.cs View File

@@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.App
{
// extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine();
protected static GameMenuEngine menuEngine = new GameMenuEngine();
protected internal static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();



+ 2
- 1
TechbloxModdingAPI/App/GameGameEngine.cs View File

@@ -8,6 +8,7 @@ using Svelto.Tasks;
using Svelto.Tasks.Lean;
using RobocraftX.Blocks;
using RobocraftX.ScreenshotTaker;
using Techblox.Environment.Transition;
using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines;
@@ -100,7 +101,7 @@ namespace TechbloxModdingAPI.App
{
if (!entitiesDB.FoundInGroups<BlockTagEntityStruct>())
throw new AppStateException("At least one block must exist in the world to enter simulation");
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
SwitchAnimationUtil.Start(entitiesDB);
}

public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)


+ 72
- 9
TechbloxModdingAPI/App/GameMenuEngine.cs View File

@@ -1,8 +1,10 @@
using System;
using System.Reflection;
using HarmonyLib;

using RobocraftX;
using RobocraftX.Common;
using RobocraftX.FrontEnd;
using RobocraftX.GUI;
using RobocraftX.GUI.MyGamesScreen;
using Svelto.ECS;
@@ -16,6 +18,9 @@ namespace TechbloxModdingAPI.App
{
public class GameMenuEngine : IFactoryEngine
{
public WrappedHandler<MenuEventArgs> EnterMenu;

public WrappedHandler<MenuEventArgs> ExitMenu;
public IEntityFactory Factory { set; private get; }

public string Name => "TechbloxModdingAPIGameInfoGameEngine";
@@ -24,23 +29,43 @@ namespace TechbloxModdingAPI.App

public EntitiesDB entitiesDB { set; private get; }

public GameMenuEngine()
{
MenuEnteredEnginePatch.EnteredExitedMenu = () =>
{
if (IsInMenu)
EnterMenu.Invoke(this, new MenuEventArgs { });
else
ExitMenu.Invoke(this, new MenuEventArgs { });
};
}

public void Dispose()
{
IsInMenu = false;
}

public void Ready()
{
IsInMenu = true;
MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched
MenuEnteredEnginePatch.EnteredExitedMenu();
}

// game functionality

public bool IsInMenu
public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu;

public Game[] GetMyGames()
{
get;
private set;
} = false;
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var mgsevsB = mgsevs.ToBuffer().buffer;
Game[] games = new Game[mgsevs.count];
for (int i = 0; i < mgsevs.count; i++)
{
Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
games[i] = new Game(mgsevsB[i].ID);
}
return games;
}

public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L)
{
@@ -77,10 +102,10 @@ namespace TechbloxModdingAPI.App
{
if (!ExistsGameInfo(id)) return false;
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id);
return EnterGame(mgdes.GameName, mgdes.SavedGamePath);
return EnterGame(mgdes.GameName, mgdes.FileId);
}

public bool EnterGame(string gameName, string path, bool autoEnterSim = false)
public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false)
{
GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
var data = new GameSelectionData
@@ -89,7 +114,8 @@ namespace TechbloxModdingAPI.App
gameType = GameType.MachineEditor,
saveName = gameName,
saveType = SaveType.ExistingSave,
gameID = path
gameID = "GAMEID_Road_Track", //TODO: Expose to the API
userContentID = fileId
};
// the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[]{data});
@@ -138,4 +164,41 @@ namespace TechbloxModdingAPI.App
}

internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { }
[HarmonyPatch]
static class MenuEnteredEnginePatch
{
internal static bool IsInMenu;
internal static Action EnteredExitedMenu;
public static void Postfix()
{
IsInMenu = true;
EnteredExitedMenu();
}

public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(FullGameCompositionRoot), "GoToMenu");
}
}
[HarmonyPatch]
static class MenuExitedEnginePatch
{
public static void Prefix()
{
MenuEnteredEnginePatch.IsInMenu = false;
MenuEnteredEnginePatch.EnteredExitedMenu();
}

public static MethodBase TargetMethod()
{
return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
}
}

public struct MenuEventArgs
{

}
}

+ 2
- 2
TechbloxModdingAPI/Block.cs View File

@@ -386,8 +386,8 @@ namespace TechbloxModdingAPI
/// </summary>
public bool Static
{
get => false;
set { }
get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic;
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value;
}

/// <summary>


+ 27
- 5
TechbloxModdingAPI/Blocks/BlockTests.cs View File

@@ -46,16 +46,20 @@ namespace TechbloxModdingAPI.Blocks
"Block ID enum matches the known block types.");
}

private static Block[] blocks; // Store placed blocks as some blocks are already present as the workshop and the game save
[APITestCase(TestType.EditMode)]
public static void TestBlockIDs()
{
float3 pos = new float3();
foreach (BlockIDs id in Enum.GetValues(typeof(BlockIDs)))
var values = Enum.GetValues(typeof(BlockIDs));
blocks = new Block[values.Length - 1]; // Minus the invalid ID
int i = 0;
foreach (BlockIDs id in values)
{
if (id == BlockIDs.Invalid) continue;
try
{
Block.PlaceNew(id, pos);
blocks[i++] = Block.PlaceNew(id, pos);
pos += 0.2f;
}
catch (Exception e)
@@ -71,8 +75,9 @@ namespace TechbloxModdingAPI.Blocks
[APITestCase(TestType.EditMode)]
public static IEnumerator<TaskContract> TestBlockProperties()
{ //Uses the result of the previous test case
var blocks = Game.CurrentGame().GetBlocksInGame();
yield return Yield.It;
if (blocks is null)
yield break;
for (var index = 0; index < blocks.Length; index++)
{
if (index % 50 == 0) yield return Yield.It; //The material or flipped status can only be changed 130 times per submission
@@ -80,6 +85,7 @@ namespace TechbloxModdingAPI.Blocks
if (!block.Exists) continue;
foreach (var property in block.GetType().GetProperties())
{
if (property.Name == "Material" || property.Name == "Flipped") continue; // TODO: Crashes in game
//Includes specialised block properties
if (property.SetMethod == null) continue;
var testValues = new (Type, object, Predicate<object>)[]
@@ -121,8 +127,24 @@ namespace TechbloxModdingAPI.Blocks
yield break;
}

property.SetValue(block, valueToUse);
object got = property.GetValue(block);
try
{
property.SetValue(block, valueToUse);
}
catch (Exception e)
{
Assert.Fail($"Failed to set property {block.GetType().Name}.{property.Name} to {valueToUse}\n{e}");
}
object got;
try
{
got = property.GetValue(block);
}
catch (Exception e)
{
Assert.Fail($"Failed to get property {block.GetType().Name}.{property.Name}\n{e}");
continue;
}
var attr = property.GetCustomAttribute<TestValueAttribute>();
if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got)))
{


+ 2
- 2
TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs View File

@@ -215,10 +215,10 @@ namespace TechbloxModdingAPI.Tests
.Action(() => Player.LocalPlayer.GetBlockLookedAt().Static = true).Build();

Game.AddPersistentDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) =>
/*Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Block);
Block.Removed += (sender, args) =>
Logging.MetaDebugLog("Removed block " + args.Block);
Logging.MetaDebugLog("Removed block " + args.Block);*/
}

// dependency test


Loading…
Cancel
Save