# Conflicts: # Automation/gen_csproj.py # GamecraftModdingAPI/App/AppEngine.cs # GamecraftModdingAPI/App/GameGameEngine.cs # GamecraftModdingAPI/App/GameMenuEngine.cs # GamecraftModdingAPI/Block.cs # GamecraftModdingAPI/Blocks/BlockEngine.cs # GamecraftModdingAPI/Blocks/BlockEngineInit.cs # GamecraftModdingAPI/Blocks/BlockEventsEngine.cs # GamecraftModdingAPI/Blocks/BlockIDs.cs # GamecraftModdingAPI/Blocks/ConsoleBlock.cs # GamecraftModdingAPI/Blocks/DampedSpring.cs # GamecraftModdingAPI/Blocks/LogicGate.cs # GamecraftModdingAPI/Blocks/Motor.cs # GamecraftModdingAPI/Blocks/MusicBlock.cs # GamecraftModdingAPI/Blocks/ObjectIdentifier.cs # GamecraftModdingAPI/Blocks/Piston.cs # GamecraftModdingAPI/Blocks/PlacementEngine.cs # GamecraftModdingAPI/Blocks/Servo.cs # GamecraftModdingAPI/Blocks/SfxBlock.cs # GamecraftModdingAPI/Blocks/SpawnPoint.cs # GamecraftModdingAPI/Blocks/TextBlock.cs # GamecraftModdingAPI/Blocks/Timer.cs # GamecraftModdingAPI/GamecraftModdingAPI.csproj # GamecraftModdingAPI/Inventory/HotbarEngine.cs # GamecraftModdingAPI/Inventory/HotbarSlotSelectionHandlerEnginePatch.cs # GamecraftModdingAPI/Main.cs # GamecraftModdingAPI/Player.cs # GamecraftModdingAPI/Players/PlayerEngine.cs # GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs # TechbloxModdingAPI/BlockGroup.cs # TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs # TechbloxModdingAPI/Blocks/Engines/RemovalEngine.cs # TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs # TechbloxModdingAPI/Blueprint.cs # TechbloxModdingAPI/Input/FakeInput.cstags/v2.2.0
@@ -3,15 +3,22 @@ | |||
import argparse | |||
from pathlib import Path, PurePath | |||
import re | |||
import os | |||
DLL_EXCLUSIONS_REGEX = r"(System|Microsoft|Mono|IronPython|DiscordRPC)\." | |||
def getAssemblyReferences(path): | |||
asmDir = Path(path) | |||
result = list() | |||
addedPath = "" | |||
if not asmDir.exists(): | |||
addedPath = "../" | |||
asmDir = Path(addedPath + path) | |||
for child in asmDir.iterdir(): | |||
if child.is_file() and re.search(DLL_EXCLUSIONS_REGEX, str(child), re.I) is None and str(child).lower().endswith(".dll"): | |||
result.append(str(child).replace("\\", "/")) | |||
childstr = str(child) | |||
childstr = os.path.relpath(childstr, addedPath).replace("\\", "/") | |||
result.append(childstr) | |||
return result | |||
def buildReferencesXml(path): | |||
@@ -27,16 +34,16 @@ def buildReferencesXml(path): | |||
return "<!--Start Dependencies-->\n <ItemGroup>\n" + "".join(result) + " </ItemGroup>\n<!--End Dependencies-->" | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser(description="Generate GamecraftModdingAPI.csproj") | |||
parser = argparse.ArgumentParser(description="Generate TechbloxModdingAPI.csproj") | |||
# TODO (maybe?): add params for custom csproj read and write locations | |||
args = parser.parse_args() | |||
print("Building Assembly references") | |||
asmXml = buildReferencesXml("../ref/GamecraftPreview_Data/Managed") | |||
asmXml = buildReferencesXml("../ref/TechbloxPreview_Data/Managed") | |||
# print(asmXml) | |||
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile: | |||
print("Parsing GamecraftModdingAPI.csproj") | |||
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "r") as xmlFile: | |||
print("Parsing TechbloxModdingAPI.csproj") | |||
fileStr = xmlFile.read() | |||
# print(fileStr) | |||
depsStart = re.search(r"\<!--\s*Start\s+Dependencies\s*--\>", fileStr) | |||
@@ -45,7 +52,7 @@ if __name__ == "__main__": | |||
print("Unable to find dependency XML comments, aborting!") | |||
exit(1) | |||
newFileStr = fileStr[:depsStart.start()] + "\n" + asmXml + "\n" + fileStr[depsEnd.end() + 1:] | |||
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "w") as xmlFile: | |||
with open("../TechbloxModdingAPI/TechbloxModdingAPI.csproj", "w") as xmlFile: | |||
print("Writing Assembly references") | |||
xmlFile.write(newFileStr) | |||
# print(newFileStr) | |||
@@ -0,0 +1,142 @@ | |||
using System; | |||
using System.CodeDom; | |||
using System.CodeDom.Compiler; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Reflection; | |||
using Gamecraft.Tweaks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Techblox.EngineBlock; | |||
namespace CodeGenerator | |||
{ | |||
public class BlockClassGenerator | |||
{ | |||
public void Generate(string name, string group = null, Dictionary<string, string> renames = null, params Type[] types) | |||
{ | |||
if (group is null) | |||
{ | |||
group = GetGroup(name) + "_BLOCK_GROUP"; | |||
if (typeof(CommonExclusiveGroups).GetFields().All(field => field.Name != group)) | |||
group = GetGroup(name) + "_BLOCK_BUILD_GROUP"; | |||
} | |||
if (!group.Contains(".")) | |||
group = "CommonExclusiveGroups." + group; | |||
var codeUnit = new CodeCompileUnit(); | |||
var ns = new CodeNamespace("TechbloxModdingAPI.Blocks"); | |||
ns.Imports.Add(new CodeNamespaceImport("RobocraftX.Common")); | |||
ns.Imports.Add(new CodeNamespaceImport("Svelto.ECS")); | |||
var cl = new CodeTypeDeclaration(name); | |||
//cl.BaseTypes.Add(baseClass != null ? new CodeTypeReference(baseClass) : new CodeTypeReference("Block")); | |||
cl.BaseTypes.Add(new CodeTypeReference("SignalingBlock")); | |||
cl.Members.Add(new CodeConstructor | |||
{ | |||
Parameters = {new CodeParameterDeclarationExpression("EGID", "egid")}, | |||
Comments = | |||
{ | |||
_start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end | |||
}, | |||
BaseConstructorArgs = {new CodeVariableReferenceExpression("egid")}, | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final | |||
}); | |||
cl.Members.Add(new CodeConstructor | |||
{ | |||
Parameters = | |||
{ | |||
new CodeParameterDeclarationExpression(typeof(uint), "id") | |||
}, | |||
Comments = | |||
{ | |||
_start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end | |||
}, | |||
BaseConstructorArgs = | |||
{ | |||
new CodeObjectCreateExpression("EGID", new CodeVariableReferenceExpression("id"), | |||
new CodeVariableReferenceExpression(group)) | |||
}, | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final | |||
}); | |||
foreach (var type in types) | |||
{ | |||
GenerateProperties(cl, type, name, renames); | |||
} | |||
ns.Types.Add(cl); | |||
codeUnit.Namespaces.Add(ns); | |||
var provider = CodeDomProvider.CreateProvider("CSharp"); | |||
var path = $@"..\..\..\TechbloxModdingAPI\Blocks\{name}.cs"; | |||
using (var sw = new StreamWriter(path)) | |||
{ | |||
provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"}); | |||
} | |||
File.WriteAllLines(path, | |||
File.ReadAllLines(path).SkipWhile(line => line.StartsWith("//") || line.Length == 0)); | |||
} | |||
private static string GetGroup(string name) | |||
{ | |||
var ret = ""; | |||
foreach (var ch in name) | |||
{ | |||
if (char.IsUpper(ch) && ret.Length > 0) | |||
ret += "_" + ch; | |||
else | |||
ret += char.ToUpper(ch); | |||
} | |||
return ret; | |||
} | |||
private void GenerateProperties(CodeTypeDeclaration cl, Type type, string baseClass, | |||
Dictionary<string, string> renames) | |||
{ | |||
if (!typeof(IEntityComponent).IsAssignableFrom(type)) | |||
throw new ArgumentException("Type must be an entity component"); | |||
foreach (var field in type.GetFields()) | |||
{ | |||
var attr = field.GetCustomAttribute<TweakableStatAttribute>(); | |||
if (renames == null || !renames.TryGetValue(field.Name, out var propName)) | |||
{ | |||
propName = field.Name; | |||
if (attr != null) | |||
propName = attr.propertyName; | |||
} | |||
propName = char.ToUpper(propName[0]) + propName.Substring(1); | |||
var structFieldReference = new CodeFieldReferenceExpression(new CodeMethodInvokeExpression( | |||
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), | |||
"GetBlockInfo", new CodeTypeReference(type)), | |||
new CodeThisReferenceExpression()), field.Name); | |||
cl.Members.Add(new CodeMemberProperty | |||
{ | |||
Name = propName, | |||
HasGet = true, | |||
HasSet = true, | |||
GetStatements = | |||
{ | |||
new CodeMethodReturnStatement(structFieldReference) | |||
}, | |||
SetStatements = | |||
{ | |||
new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression()) | |||
}, | |||
Type = new CodeTypeReference(field.FieldType), | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final, | |||
Comments = | |||
{ | |||
_start, new CodeCommentStatement($"Gets or sets the {baseClass}'s {propName} property." + | |||
$" {(attr != null ? "Tweakable stat." : "May not be saved.")}", true), _end | |||
} | |||
}); | |||
} | |||
} | |||
private static readonly CodeCommentStatement _start = new CodeCommentStatement("<summary>", true); | |||
private static readonly CodeCommentStatement _end = new CodeCommentStatement("</summary>", true); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
using System.Collections.Generic; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.PilotSeat; | |||
using Techblox.EngineBlock; | |||
using Techblox.WheelRigBlock; | |||
namespace CodeGenerator | |||
{ | |||
internal class Program | |||
{ | |||
public static void Main(string[] args) | |||
{ | |||
var bcg = new BlockClassGenerator(); | |||
bcg.Generate("Engine", null, new Dictionary<string, string> | |||
{ | |||
{ "engineOn", "On" } | |||
}, typeof(EngineBlockComponent), // Simulation time properties | |||
typeof(EngineBlockTweakableComponent), typeof(EngineBlockReadonlyComponent)); | |||
bcg.Generate("DampedSpring", "DAMPEDSPRING_BLOCK_GROUP", new Dictionary<string, string> | |||
{ | |||
{"maxExtent", "MaxExtension"} | |||
}, | |||
typeof(TweakableJointDampingComponent), typeof(DampedSpringReadOnlyStruct)); | |||
bcg.Generate("LogicGate", "LOGIC_BLOCK_GROUP"); | |||
bcg.Generate("Servo", types: typeof(ServoReadOnlyStruct), renames: new Dictionary<string, string> | |||
{ | |||
{"minDeviation", "MinimumAngle"}, | |||
{"maxDeviation", "MaximumAngle"}, | |||
{"servoVelocity", "MaximumForce"} | |||
}); | |||
bcg.Generate("WheelRig", "WHEELRIG_BLOCK_BUILD_GROUP", null, | |||
typeof(WheelRigTweakableStruct), typeof(WheelRigReadOnlyStruct), | |||
typeof(WheelRigSteerableTweakableStruct), typeof(WheelRigSteerableReadOnlyStruct)); | |||
bcg.Generate("Seat", "RobocraftX.PilotSeat.SeatGroups.PILOTSEAT_BLOCK_BUILD_GROUP", null, typeof(SeatFollowCamComponent), typeof(SeatReadOnlySettingsComponent)); | |||
bcg.Generate("Piston", null, new Dictionary<string, string> | |||
{ | |||
{"pistonVelocity", "MaximumForce"} | |||
}, typeof(PistonReadOnlyStruct)); | |||
bcg.Generate("Motor", null, null, typeof(MotorReadOnlyStruct)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
using System.Reflection; | |||
using System.Runtime.InteropServices; | |||
// General Information about an assembly is controlled through the following | |||
// set of attributes. Change these attribute values to modify the information | |||
// associated with an assembly. | |||
[assembly: AssemblyTitle("CodeGenerator")] | |||
[assembly: AssemblyDescription("")] | |||
[assembly: AssemblyConfiguration("")] | |||
[assembly: AssemblyCompany("")] | |||
[assembly: AssemblyProduct("CodeGenerator")] | |||
[assembly: AssemblyCopyright("Copyright © ExMods 2021")] | |||
[assembly: AssemblyTrademark("")] | |||
[assembly: AssemblyCulture("")] | |||
// Setting ComVisible to false makes the types in this assembly not visible | |||
// to COM components. If you need to access a type in this assembly from | |||
// COM, set the ComVisible attribute to true on that type. | |||
[assembly: ComVisible(false)] | |||
// The following GUID is for the ID of the typelib if this project is exposed to COM | |||
[assembly: Guid("0EBB6400-95A7-4A3D-B2ED-BF31E364CC10")] | |||
// Version information for an assembly consists of the following four values: | |||
// | |||
// Major Version | |||
// Minor Version | |||
// Build Number | |||
// Revision | |||
// | |||
// You can specify all the values or you can default the Build and Revision Numbers | |||
// by using the '*' as shown below: | |||
// [assembly: AssemblyVersion("1.0.*")] | |||
[assembly: AssemblyVersion("1.0.0.0")] | |||
[assembly: AssemblyFileVersion("1.0.0.0")] |
@@ -1,64 +0,0 @@ | |||
using System; | |||
using RobocraftX.GUI.MyGamesScreen; | |||
using RobocraftX.GUI; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.App | |||
{ | |||
public class AppEngine : IFactoryEngine | |||
{ | |||
public event EventHandler<MenuEventArgs> EnterMenu; | |||
public event EventHandler<MenuEventArgs> ExitMenu; | |||
public IEntityFactory Factory { set; private get; } | |||
public string Name => "GamecraftModdingAPIAppEngine"; | |||
public bool isRemovable => false; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Dispose() | |||
{ | |||
IsInMenu = false; | |||
ExceptionUtil.InvokeEvent(ExitMenu, this, new MenuEventArgs { }); | |||
} | |||
public void Ready() | |||
{ | |||
IsInMenu = true; | |||
ExceptionUtil.InvokeEvent(EnterMenu, this, new MenuEventArgs { }); | |||
} | |||
// app functionality | |||
public bool IsInMenu | |||
{ | |||
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 | |||
{ | |||
} | |||
} |
@@ -1,130 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using RobocraftX.Common; | |||
using RobocraftX.Schedulers; | |||
using RobocraftX.SimulationModeState; | |||
using Svelto.ECS; | |||
using Svelto.Tasks; | |||
using Svelto.Tasks.Lean; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.App | |||
{ | |||
public class GameGameEngine : IApiEngine | |||
{ | |||
public event EventHandler<GameEventArgs> EnterGame; | |||
public event EventHandler<GameEventArgs> ExitGame; | |||
public string Name => "GamecraftModdingAPIGameInfoMenuEngine"; | |||
public bool isRemovable => false; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Dispose() | |||
{ | |||
ExceptionUtil.InvokeEvent(ExitGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder }); | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
ExceptionUtil.InvokeEvent(EnterGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder }); | |||
IsInGame = true; | |||
} | |||
// game functionality | |||
public bool IsInGame | |||
{ | |||
get; | |||
private set; | |||
} = false; | |||
public void ExitCurrentGame(bool async = false) | |||
{ | |||
if (async) | |||
{ | |||
ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped); | |||
} | |||
else | |||
{ | |||
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
} | |||
public IEnumerator<TaskContract> ExitCurrentGameAsync() | |||
{ | |||
/* | |||
while (Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING.isStopping) { yield return Yield.It; } | |||
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu").Invoke(FullGameFields.Instance, new object[0]);*/ | |||
yield return Yield.It; | |||
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
public void SaveCurrentGame() | |||
{ | |||
ref GameSceneEntityStruct gses = ref entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
gses.LoadAfterSaving = false; | |||
gses.SaveNow = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
public bool IsTimeRunningMode() | |||
{ | |||
return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB); | |||
} | |||
public bool IsTimeStoppedMode() | |||
{ | |||
return TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB); | |||
} | |||
public void ToggleTimeMode() | |||
{ | |||
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB); | |||
} | |||
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) | |||
{ | |||
var allBlocks = entitiesDB.QueryEntities<DBEntityStruct>(); | |||
List<EGID> blockEGIDs = new List<EGID>(); | |||
if (filter == BlockIDs.Invalid) | |||
{ | |||
foreach (var (blocks, _) in allBlocks) | |||
{ | |||
var buffer = blocks.ToBuffer().buffer; | |||
for (int i = 0; i < buffer.capacity; i++) | |||
blockEGIDs.Add(buffer[i].ID); | |||
} | |||
return blockEGIDs.ToArray(); | |||
} | |||
else | |||
{ | |||
foreach (var (blocks, _) in allBlocks) | |||
{ | |||
var array = blocks.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var block = array[index]; | |||
if (block.DBID == (ulong) filter) | |||
blockEGIDs.Add(block.ID); | |||
} | |||
} | |||
return blockEGIDs.ToArray(); | |||
} | |||
} | |||
} | |||
} |
@@ -1,141 +0,0 @@ | |||
using System; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using RobocraftX.Common; | |||
using RobocraftX.GUI; | |||
using RobocraftX.GUI.MyGamesScreen; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Experimental; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
using Svelto.DataStructures; | |||
namespace GamecraftModdingAPI.App | |||
{ | |||
public class GameMenuEngine : IFactoryEngine | |||
{ | |||
public IEntityFactory Factory { set; private get; } | |||
public string Name => "GamecraftModdingAPIGameInfoGameEngine"; | |||
public bool isRemovable => false; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Dispose() | |||
{ | |||
IsInMenu = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInMenu = true; | |||
} | |||
// game functionality | |||
public bool IsInMenu | |||
{ | |||
get; | |||
private set; | |||
} = false; | |||
public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L) | |||
{ | |||
EntityComponentInitializer eci = Factory.BuildEntity<MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal>(id); | |||
eci.Init(new MyGameDataEntityStruct | |||
{ | |||
SavedGamePath = new ECSString(path), | |||
ThumbnailId = thumbnailId, | |||
GameName = new ECSString(gameName), | |||
CreatorName = new ECSString(creatorName), | |||
GameDescription = new ECSString(description), | |||
CreatedDate = createdDate, | |||
}); | |||
// entitiesDB.PublishEntityChange<MyGameDataEntityStruct>(id); // this will always fail | |||
return true; | |||
} | |||
public uint HighestID() | |||
{ | |||
EntityCollection<MyGameDataEntityStruct> games = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames); | |||
var gamesB = games.ToBuffer().buffer; | |||
uint max = 0; | |||
for (int i = 0; i < games.count; i++) | |||
{ | |||
if (gamesB[i].ID.entityID > max) | |||
{ | |||
max = gamesB[i].ID.entityID; | |||
} | |||
} | |||
return max; | |||
} | |||
public bool EnterGame(EGID id) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id); | |||
return EnterGame(mgdes.GameName, mgdes.SavedGamePath); | |||
} | |||
public bool EnterGame(string gameName, string path, ulong workshopId = 0uL, bool autoEnterSim = false) | |||
{ | |||
GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build; | |||
GameMode.SaveGameDetails = new SaveGameDetails(gameName, path, workshopId); | |||
// the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason | |||
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[0]); | |||
return true; | |||
} | |||
public bool SetGameName(EGID id, string name) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
GetGameInfo(id).GameName.Set(name); | |||
GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name); | |||
return true; | |||
} | |||
public bool SetGameDescription(EGID id, string name) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
GetGameInfo(id).GameDescription.Set(name); | |||
GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name); | |||
return true; | |||
} | |||
public bool ExistsGameInfo(EGID id) | |||
{ | |||
return entitiesDB.Exists<MyGameDataEntityStruct>(id); | |||
} | |||
public ref MyGameDataEntityStruct GetGameInfo(EGID id) | |||
{ | |||
return ref GetComponent<MyGameDataEntityStruct>(id); | |||
} | |||
public ref MyGamesSlotEntityViewStruct GetGameViewInfo(EGID id) | |||
{ | |||
EntityCollection<MyGamesSlotEntityViewStruct> entities = | |||
entitiesDB.QueryEntities<MyGamesSlotEntityViewStruct>(MyGamesScreenExclusiveGroups.GameSlotGuiEntities); | |||
var entitiesB = entities.ToBuffer().buffer; | |||
for (int i = 0; i < entities.count; i++) | |||
{ | |||
if (entitiesB[i].ID.entityID == id.entityID) | |||
{ | |||
return ref entitiesB[i]; | |||
} | |||
} | |||
MyGamesSlotEntityViewStruct[] defRef = new MyGamesSlotEntityViewStruct[1]; | |||
return ref defRef[0]; | |||
} | |||
public ref T GetComponent<T>(EGID id) where T: unmanaged, IEntityComponent | |||
{ | |||
return ref entitiesDB.QueryEntity<T>(id); | |||
} | |||
} | |||
internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { } | |||
} |
@@ -1,26 +0,0 @@ | |||
using System; | |||
using System.Reflection; | |||
using RobocraftX.CR.MainGame; | |||
using RobocraftX.StateSync; | |||
using HarmonyLib; | |||
namespace GamecraftModdingAPI.App | |||
{ | |||
[HarmonyPatch] | |||
class StateSyncRegPatch | |||
{ | |||
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) | |||
{ | |||
// register sim/build events engines | |||
Game.InitDeterministic(stateSyncReg); | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase Target() | |||
{ | |||
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose").MakeGenericMethod(typeof(object)); | |||
} | |||
} | |||
} |
@@ -1,501 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection.Emit; | |||
using Gamecraft.Blocks.BlockGroups; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using RobocraftX.Common; | |||
using RobocraftX.Blocks; | |||
using Unity.Mathematics; | |||
using Gamecraft.Blocks.GUI; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI | |||
{ | |||
/// <summary> | |||
/// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. | |||
/// For specific block type operations, use the specialised block classes in the GamecraftModdingAPI.Blocks namespace. | |||
/// </summary> | |||
public class Block : IEquatable<Block>, IEquatable<EGID> | |||
{ | |||
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); | |||
protected static readonly MovementEngine MovementEngine = new MovementEngine(); | |||
protected static readonly RotationEngine RotationEngine = new RotationEngine(); | |||
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine(); | |||
protected static readonly SignalEngine SignalEngine = new SignalEngine(); | |||
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine(); | |||
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine(); | |||
protected internal static readonly BlockEngine BlockEngine = new BlockEngine(); | |||
/// <summary> | |||
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. | |||
/// Place blocks next to each other to connect them. | |||
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. | |||
/// </summary> | |||
/// <param name="block">The block's type</param> | |||
/// <param name="color">The block's color</param> | |||
/// <param name="darkness">The block color's darkness (0-9) - 0 is default color</param> | |||
/// <param name="position">The block's position - default block size is 0.2</param> | |||
/// <param name="rotation">The block's rotation in degrees</param> | |||
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param> | |||
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param> | |||
/// <param name="player">The player who placed the block</param> | |||
/// <returns>The placed block or null if failed</returns> | |||
public static Block PlaceNew(BlockIDs block, float3 position, | |||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, | |||
int uscale = 1, float3 scale = default, Player player = null) | |||
{ | |||
return PlaceNew<Block>(block, position, rotation, color, darkness, uscale, scale, player); | |||
} | |||
/// <summary> | |||
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. | |||
/// Place blocks next to each other to connect them. | |||
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. | |||
/// </summary> | |||
/// <param name="block">The block's type</param> | |||
/// <param name="color">The block's color</param> | |||
/// <param name="darkness">The block color's darkness (0-9) - 0 is default color</param> | |||
/// <param name="position">The block's position - default block size is 0.2</param> | |||
/// <param name="rotation">The block's rotation in degrees</param> | |||
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param> | |||
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param> | |||
/// <param name="player">The player who placed the block</param> | |||
/// <returns>The placed block or null if failed</returns> | |||
public static T PlaceNew<T>(BlockIDs block, float3 position, | |||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, | |||
int uscale = 1, float3 scale = default, Player player = null) where T : Block | |||
{ | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
var egid = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation, out var initializer); | |||
var bl = New<T>(egid.entityID, egid.groupID); | |||
bl.InitData.Group = BlockEngine.InitGroup(initializer); | |||
Placed += bl.OnPlacedInit; | |||
return bl; | |||
} | |||
return null; | |||
} | |||
/// <summary> | |||
/// Returns the most recently placed block. | |||
/// </summary> | |||
/// <returns>The block object</returns> | |||
public static Block GetLastPlacedBlock() | |||
{ | |||
return New<Block>(BlockIdentifiers.LatestBlockID); | |||
} | |||
/// <summary> | |||
/// An event that fires each time a block is placed. | |||
/// </summary> | |||
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed | |||
{ | |||
add => BlockEventsEngine.Placed += value; | |||
remove => BlockEventsEngine.Placed -= value; | |||
} | |||
/// <summary> | |||
/// An event that fires each time a block is removed. | |||
/// </summary> | |||
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed | |||
{ | |||
add => BlockEventsEngine.Removed += value; | |||
remove => BlockEventsEngine.Removed -= value; | |||
} | |||
private static Dictionary<Type, Func<EGID, Block>> initializers = new Dictionary<Type, Func<EGID, Block>>(); | |||
private static Dictionary<Type, ExclusiveGroupStruct[]> typeToGroup = | |||
new Dictionary<Type, ExclusiveGroupStruct[]> | |||
{ | |||
{typeof(ConsoleBlock), new[] {CommonExclusiveGroups.CONSOLE_BLOCK_GROUP}}, | |||
{typeof(LogicGate), new [] {CommonExclusiveGroups.LOGIC_BLOCK_GROUP}}, | |||
{typeof(Motor), new[] {CommonExclusiveGroups.MOTOR_BLOCK_GROUP}}, | |||
{typeof(MusicBlock), new[] {CommonExclusiveGroups.MUSIC_BLOCK_GROUP}}, | |||
{typeof(ObjectIdentifier), new[]{CommonExclusiveGroups.OBJID_BLOCK_GROUP}}, | |||
{typeof(Piston), new[] {CommonExclusiveGroups.PISTON_BLOCK_GROUP}}, | |||
{typeof(Servo), new[] {CommonExclusiveGroups.SERVO_BLOCK_GROUP}}, | |||
{ | |||
typeof(SpawnPoint), | |||
new[] | |||
{ | |||
CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP, | |||
CommonExclusiveGroups.BUILDINGSPAWN_BLOCK_GROUP | |||
} | |||
}, | |||
{ | |||
typeof(SfxBlock), | |||
new[] | |||
{ | |||
CommonExclusiveGroups.SIMPLESFX_BLOCK_GROUP, | |||
CommonExclusiveGroups.LOOPEDSFX_BLOCK_GROUP | |||
} | |||
}, | |||
{typeof(DampedSpring), new [] {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP}}, | |||
{typeof(TextBlock), new[] {CommonExclusiveGroups.TEXT_BLOCK_GROUP}}, | |||
{typeof(Timer), new[] {CommonExclusiveGroups.TIMER_BLOCK_GROUP}} | |||
}; | |||
/// <summary> | |||
/// Constructs a new instance of T with the given ID and group using dynamically created delegates. | |||
/// It's equivalent to new T(EGID) with a minimal overhead thanks to caching the created delegates. | |||
/// </summary> | |||
/// <param name="id">The block ID</param> | |||
/// <param name="group">The block group</param> | |||
/// <typeparam name="T">The block's type or Block itself</typeparam> | |||
/// <returns>An instance of the provided type</returns> | |||
/// <exception cref="BlockTypeException">The block group doesn't match or cannot be found</exception> | |||
/// <exception cref="MissingMethodException">The block class doesn't have the needed constructor</exception> | |||
private static T New<T>(uint id, ExclusiveGroupStruct? group = null) where T : Block | |||
{ | |||
var type = typeof(T); | |||
EGID egid; | |||
if (!group.HasValue) | |||
{ | |||
if (typeToGroup.TryGetValue(type, out var gr) && gr.Length == 1) | |||
egid = new EGID(id, gr[0]); | |||
else | |||
egid = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find block group!"); | |||
} | |||
else | |||
{ | |||
egid = new EGID(id, group.Value); | |||
if (typeToGroup.TryGetValue(type, out var gr) | |||
&& gr.All(egs => egs != group.Value)) //If this subclass has a specific group, then use that - so Block should still work | |||
throw new BlockTypeException($"Incompatible block type! Type {type.Name} belongs to group {gr.Select(g => g.ToString()).Aggregate((a, b) => a + ", " + b)} instead of {group.Value}"); | |||
} | |||
if (initializers.TryGetValue(type, out var func)) | |||
{ | |||
var bl = (T) func(egid); | |||
return bl; | |||
} | |||
//https://stackoverflow.com/a/10593806/2703239 | |||
var ctor = type.GetConstructor(new[] {typeof(EGID)}); | |||
if (ctor == null) | |||
throw new MissingMethodException("There is no constructor with an EGID parameter for this object"); | |||
DynamicMethod dynamic = new DynamicMethod(string.Empty, | |||
type, | |||
new[] {typeof(EGID)}, | |||
type); | |||
ILGenerator il = dynamic.GetILGenerator(); | |||
//il.DeclareLocal(type); | |||
il.Emit(OpCodes.Ldarg_0); //Load EGID and pass to constructor | |||
il.Emit(OpCodes.Newobj, ctor); //Call constructor | |||
//il.Emit(OpCodes.Stloc_0); - doesn't seem like we need these | |||
//il.Emit(OpCodes.Ldloc_0); | |||
il.Emit(OpCodes.Ret); | |||
func = (Func<EGID, T>) dynamic.CreateDelegate(typeof(Func<EGID, T>)); | |||
initializers.Add(type, func); | |||
var block = (T) func(egid); | |||
return block; | |||
} | |||
public Block(EGID id) | |||
{ | |||
Id = id; | |||
var type = GetType(); | |||
if (typeToGroup.TryGetValue(type, out var groups)) | |||
{ | |||
if (groups.All(gr => gr != id.groupID)) | |||
throw new BlockTypeException("The block has the wrong group! The type is " + GetType() + | |||
" while the group is " + id.groupID); | |||
} | |||
else if (type != typeof(Block)) | |||
Logging.LogWarning($"Unknown block type! Add {type} to the dictionary."); | |||
} | |||
/// <summary> | |||
/// This overload searches for the correct group the block is in. | |||
/// It will throw an exception if the block doesn't exist. | |||
/// Use the EGID constructor where possible or subclasses of Block as those specify the group. | |||
/// </summary> | |||
public Block(uint id) | |||
{ | |||
Id = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find the appropriate group for the block. The block probably doesn't exist or hasn't been submitted."); | |||
} | |||
public EGID Id { get; } | |||
internal BlockEngine.BlockInitData InitData; | |||
/// <summary> | |||
/// The block's current position or zero if the block no longer exists. | |||
/// A block is 0.2 wide by default in terms of position. | |||
/// </summary> | |||
public float3 Position | |||
{ | |||
get => MovementEngine.GetPosition(Id, InitData); | |||
set | |||
{ | |||
MovementEngine.MoveBlock(Id, InitData, value); | |||
if (blockGroup != null) | |||
blockGroup.PosAndRotCalculated = false; | |||
} | |||
} | |||
/// <summary> | |||
/// The block's current rotation in degrees or zero if the block doesn't exist. | |||
/// </summary> | |||
public float3 Rotation | |||
{ | |||
get => RotationEngine.GetRotation(Id, InitData); | |||
set | |||
{ | |||
RotationEngine.RotateBlock(Id, InitData, value); | |||
if (blockGroup != null) | |||
blockGroup.PosAndRotCalculated = false; | |||
} | |||
} | |||
/// <summary> | |||
/// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling. | |||
/// The default scale of 1 means 0.2 in terms of position. | |||
/// </summary> | |||
public float3 Scale | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ScalingEntityStruct st) => st.scale); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ScalingEntityStruct st, float3 val) => st.scale = val, value); | |||
if (!Exists) return; //UpdateCollision needs the block to exist | |||
ScalingEngine.UpdateCollision(Id); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale. | |||
/// The default scale of 1 means 0.2 in terms of position. | |||
/// </summary> | |||
public int UniformScale | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (UniformBlockScaleEntityStruct st) => st.scaleFactor); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref UniformBlockScaleEntityStruct st, int val) => st.scaleFactor = val, | |||
value); | |||
Scale = new float3(value, value, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore. | |||
/// </summary> | |||
public BlockIDs Type | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (DBEntityStruct st) => (BlockIDs) st.DBID, BlockIDs.Invalid); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's color. Returns BlockColors.Default if the block no longer exists. | |||
/// </summary> | |||
public BlockColor Color | |||
{ | |||
get | |||
{ | |||
byte index = BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.indexInPalette, | |||
byte.MaxValue); | |||
return new BlockColor(index); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, BlockColor val) => | |||
{ | |||
color.indexInPalette = (byte) (val.Color + val.Darkness * 10); | |||
//color.overridePaletteColour = false; | |||
//color.needsUpdate = true; | |||
color.hasNetworkChange = true; | |||
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); | |||
}, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's exact color. Gets reset to the palette color (Color property) after reentering the game. | |||
/// </summary> | |||
public float4 CustomColor | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.paletteColour); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, float4 val) => | |||
{ | |||
color.paletteColour = val; | |||
//color.overridePaletteColour = true; | |||
//color.needsUpdate = true; | |||
color.hasNetworkChange = true; | |||
}, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The text displayed on the block if applicable, or null. | |||
/// Setting it is temporary to the session, it won't be saved. | |||
/// </summary> | |||
public string Label | |||
{ | |||
get => BlockEngine.GetBlockInfoViewStruct(this, (TextLabelEntityViewStruct st) => st.textLabelComponent?.text); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfoViewStruct(this, (ref TextLabelEntityViewStruct text, string val) => | |||
{ | |||
if (text.textLabelComponent != null) text.textLabelComponent.text = val; | |||
}, value); | |||
} | |||
} | |||
private BlockGroup blockGroup; | |||
/// <summary> | |||
/// Returns the block group this block is a part of. Block groups can be placed using blueprints. | |||
/// Returns null if not part of a group.<br /> | |||
/// Setting the group after the block has been initialized will not update everything properly. | |||
/// You should only set this property on blocks newly placed by your code. | |||
/// </summary> | |||
public BlockGroup BlockGroup | |||
{ | |||
get | |||
{ | |||
if (blockGroup != null) return blockGroup; | |||
return blockGroup = BlockEngine.GetBlockInfo(this, | |||
(BlockGroupEntityComponent bgec) => | |||
bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this)); | |||
} | |||
set | |||
{ | |||
blockGroup?.RemoveInternal(this); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref BlockGroupEntityComponent bgec, BlockGroup val) => bgec.currentBlockGroup = val?.Id ?? -1, | |||
value); | |||
value?.AddInternal(this); | |||
blockGroup = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Whether the block exists. The other properties will return a default value if the block doesn't exist. | |||
/// If the block was just placed, then this will also return false but the properties will work correctly. | |||
/// </summary> | |||
public bool Exists => BlockEngine.BlockExists(Id); | |||
/// <summary> | |||
/// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist. | |||
/// </summary> | |||
public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id); | |||
/// <summary> | |||
/// Removes this block. | |||
/// </summary> | |||
/// <returns>True if the block exists and could be removed.</returns> | |||
public bool Remove() => RemovalEngine.RemoveBlock(Id); | |||
/// <summary> | |||
/// Returns the rigid body of the chunk of blocks this one belongs to during simulation. | |||
/// Can be used to apply forces or move the block around while the simulation is running. | |||
/// </summary> | |||
/// <returns>The SimBody of the chunk or null if the block doesn't exist or not in simulation mode.</returns> | |||
public SimBody GetSimBody() | |||
{ | |||
return BlockEngine.GetBlockInfo(this, | |||
(GridConnectionsEntityStruct st) => st.machineRigidBodyId != uint.MaxValue | |||
? new SimBody(st.machineRigidBodyId, st.clusterId) | |||
: null); | |||
} | |||
private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e) | |||
{ //Member method instead of lambda to avoid constantly creating delegates | |||
if (e.ID != Id) return; | |||
Placed -= OnPlacedInit; //And we can reference it | |||
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again | |||
} | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Type)}: {Type}, {nameof(Color)}: {Color}, {nameof(Exists)}: {Exists}"; | |||
} | |||
public bool Equals(Block other) | |||
{ | |||
if (ReferenceEquals(null, other)) return false; | |||
if (ReferenceEquals(this, other)) return true; | |||
return Id.Equals(other.Id); | |||
} | |||
public bool Equals(EGID other) | |||
{ | |||
return Id.Equals(other); | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
if (ReferenceEquals(null, obj)) return false; | |||
if (ReferenceEquals(this, obj)) return true; | |||
if (obj.GetType() != this.GetType()) return false; | |||
return Equals((Block) obj); | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return Id.GetHashCode(); | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(PlacementEngine); | |||
GameEngineManager.AddGameEngine(MovementEngine); | |||
GameEngineManager.AddGameEngine(RotationEngine); | |||
GameEngineManager.AddGameEngine(RemovalEngine); | |||
GameEngineManager.AddGameEngine(BlockEngine); | |||
GameEngineManager.AddGameEngine(BlockEventsEngine); | |||
GameEngineManager.AddGameEngine(ScalingEngine); | |||
GameEngineManager.AddGameEngine(SignalEngine); | |||
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine | |||
} | |||
/// <summary> | |||
/// Convert the block to a specialised block class. | |||
/// </summary> | |||
/// <returns>The block.</returns> | |||
/// <typeparam name="T">The specialised block type.</typeparam> | |||
public T Specialise<T>() where T : Block | |||
{ | |||
// What have I gotten myself into? | |||
// C# can't cast to a child of Block unless the object was originally that child type | |||
// And C# doesn't let me make implicit cast operators for child types | |||
// So thanks to Microsoft, we've got this horrible implementation using reflection | |||
//Lets improve that using delegates | |||
var block = New<T>(Id.entityID, Id.groupID); | |||
if (this.InitData.Group != null) | |||
{ | |||
block.InitData = this.InitData; | |||
Placed += block.OnPlacedInit; //Reset InitData of new object | |||
} | |||
return block; | |||
} | |||
#if DEBUG | |||
public static EntitiesDB entitiesDB | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetEntitiesDB(); | |||
} | |||
} | |||
#endif | |||
} | |||
} |
@@ -1,64 +0,0 @@ | |||
using System; | |||
using Unity.Mathematics; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public struct BlockColor | |||
{ | |||
public BlockColors Color; | |||
public byte Darkness; | |||
public byte Index => Color == BlockColors.Default | |||
? byte.MaxValue | |||
: (byte) (Darkness * 10 + Color); | |||
public BlockColor(byte index) | |||
{ | |||
if (index == byte.MaxValue) | |||
{ | |||
Color = BlockColors.Default; | |||
Darkness = 0; | |||
} | |||
else | |||
{ | |||
if (index > 99) | |||
throw new ArgumentOutOfRangeException(nameof(index), "Invalid color index. Must be 0-90 or 255."); | |||
Color = (BlockColors) (index % 10); | |||
Darkness = (byte) (index / 10); | |||
} | |||
} | |||
public BlockColor(BlockColors color, byte darkness) | |||
{ | |||
if (darkness > 9) | |||
throw new ArgumentOutOfRangeException(nameof(darkness), "Darkness must be 0-9 where 0 is default."); | |||
Color = color; | |||
Darkness = darkness; | |||
} | |||
public float4 RGBA => Block.BlockEngine.ConvertBlockColor(Index); | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Color)}: {Color}, {nameof(Darkness)}: {Darkness}"; | |||
} | |||
} | |||
/// <summary> | |||
/// Preset block colours | |||
/// </summary> | |||
public enum BlockColors | |||
{ | |||
Default = byte.MaxValue, | |||
White = 0, | |||
Pink, | |||
Purple, | |||
Blue, | |||
Aqua, | |||
Green, | |||
Lime, | |||
Yellow, | |||
Orange, | |||
Red | |||
} | |||
} |
@@ -1,294 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using Gamecraft.ColourPalette; | |||
using Gamecraft.TimeRunning; | |||
using Gamecraft.Wires; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using RobocraftX.Physics; | |||
using RobocraftX.Scene.Simulation; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Hybrid; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Engine for executing general block actions | |||
/// </summary> | |||
public partial class BlockEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public void Dispose() | |||
{ | |||
} | |||
public void Ready() | |||
{ | |||
} | |||
public Block[] GetConnectedBlocks(EGID blockID) | |||
{ | |||
if (!BlockExists(blockID)) return new Block[0]; | |||
Stack<EGID> cubeStack = new Stack<EGID>(); | |||
FasterList<EGID> cubes = new FasterList<EGID>(10); | |||
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
foreach (var (ecoll, _) in coll) | |||
{ | |||
var ecollB = ecoll.ToBuffer(); | |||
for(int i = 0; i < ecoll.count; i++) | |||
{ | |||
ref var conn = ref ecollB.buffer[i]; | |||
conn.isProcessed = false; | |||
} | |||
} | |||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes, | |||
(in GridConnectionsEntityStruct g) => { return false; }); | |||
var ret = new Block[cubes.count]; | |||
for (int i = 0; i < cubes.count; i++) | |||
ret[i] = new Block(cubes[i]); | |||
return ret; | |||
} | |||
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue | |||
? new float4(-1f, -1f, -1f, -1f) | |||
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index, | |||
CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; | |||
public ref T GetBlockInfo<T>(EGID blockID) where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDB.Exists<T>(blockID)) | |||
return ref entitiesDB.QueryEntity<T>(blockID); | |||
T[] structHolder = new T[1]; //Create something that can be referenced | |||
return ref structHolder[0]; //Gets a default value automatically | |||
} | |||
public ref T GetBlockInfoViewStruct<T>(EGID blockID) where T : struct, INeedEGID, IEntityViewComponent | |||
{ | |||
if (entitiesDB.Exists<T>(blockID)) | |||
{ | |||
// TODO: optimize by using EntitiesDB internal calls instead of iterating over everything | |||
BT<MB<T>> entities = entitiesDB.QueryEntities<T>(blockID.groupID).ToBuffer(); | |||
for (int i = 0; i < entities.count; i++) | |||
{ | |||
if (entities.buffer[i].ID == blockID) | |||
{ | |||
return ref entities.buffer[i]; | |||
} | |||
} | |||
} | |||
T[] structHolder = new T[1]; //Create something that can be referenced | |||
return ref structHolder[0]; //Gets a default value automatically | |||
} | |||
public U GetBlockInfo<T, U>(Block block, Func<T, U> getter, | |||
U def = default) where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
return getter(entitiesDB.QueryEntity<T>(block.Id)); | |||
return GetBlockInitInfo(block, getter, def); | |||
} | |||
public U GetBlockInfoViewStruct<T, U>(Block block, Func<T, U> getter, | |||
U def = default) where T : struct, IEntityViewComponent | |||
{ | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
return getter(entitiesDB.QueryEntity<T>(block.Id)); | |||
return GetBlockInitInfo(block, getter, def); | |||
} | |||
private U GetBlockInitInfo<T, U>(Block block, Func<T, U> getter, U def) where T : struct, IEntityComponent | |||
{ | |||
if (block.InitData.Group == null) return def; | |||
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
if (initializer.Has<T>()) | |||
return getter(initializer.Get<T>()); | |||
return def; | |||
} | |||
public delegate void Setter<T, U>(ref T component, U value) where T : struct, IEntityComponent; | |||
public void SetBlockInfoViewStruct<T, U>(Block block, Setter<T, U> setter, U value) where T : struct, IEntityViewComponent | |||
{ | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
setter(ref entitiesDB.QueryEntity<T>(block.Id), value); | |||
else | |||
SetBlockInitInfo(block, setter, value); | |||
} | |||
public void SetBlockInfo<T, U>(Block block, Setter<T, U> setter, U value) where T : unmanaged, IEntityComponent | |||
{ | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
setter(ref entitiesDB.QueryEntity<T>(block.Id), value); | |||
else | |||
SetBlockInitInfo(block, setter, value); | |||
} | |||
private void SetBlockInitInfo<T, U>(Block block, Setter<T, U> setter, U value) | |||
where T : struct, IEntityComponent | |||
{ | |||
if (block.InitData.Group != null) | |||
{ | |||
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
T component = initializer.Has<T>() ? initializer.Get<T>() : default; | |||
ref T structRef = ref component; | |||
setter(ref structRef, value); | |||
initializer.Init(structRef); | |||
} | |||
} | |||
public bool BlockExists(EGID blockID) | |||
{ | |||
return entitiesDB.Exists<DBEntityStruct>(blockID); | |||
} | |||
public bool GetBlockInfoExists<T>(Block block) where T : struct, IEntityComponent | |||
{ | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
return true; | |||
if (block.InitData.Group == null) | |||
return false; | |||
var init = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
return init.Has<T>(); | |||
} | |||
public SimBody[] GetSimBodiesFromID(byte id) | |||
{ | |||
var ret = new FasterList<SimBody>(4); | |||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.OBJID_BLOCK_GROUP)) | |||
return new SimBody[0]; | |||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.OBJID_BLOCK_GROUP).ToBuffer(); | |||
var connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(CommonExclusiveGroups.OBJID_BLOCK_GROUP); | |||
for (int i = 0; i < oids.count; i++) | |||
{ | |||
ref ObjectIdEntityStruct oid = ref oids.buffer[i]; | |||
if (oid.objectId != id) continue; | |||
var rid = connections.Entity(oid.ID.entityID).machineRigidBodyId; | |||
foreach (var rb in ret) | |||
{ | |||
if (rb.Id.entityID == rid) | |||
goto DUPLICATE; //Multiple Object Identifiers on one rigid body | |||
} | |||
ret.Add(new SimBody(rid)); | |||
DUPLICATE: ; | |||
} | |||
return ret.ToArray(); | |||
} | |||
public ObjectIdentifier[] GetObjectIDsFromID(byte id, bool sim) | |||
{ | |||
var ret = new FasterList<ObjectIdentifier>(4); | |||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.OBJID_BLOCK_GROUP)) | |||
return new ObjectIdentifier[0]; | |||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.OBJID_BLOCK_GROUP).ToBuffer(); | |||
for (int i = 0; i < oids.count; i++) | |||
{ | |||
ref ObjectIdEntityStruct oid = ref oids.buffer[i]; | |||
if (sim ? oid.simObjectId == id : oid.objectId == id) | |||
ret.Add(new ObjectIdentifier(oid.ID)); | |||
} | |||
return ret.ToArray(); | |||
} | |||
public SimBody[] GetConnectedSimBodies(uint id) | |||
{ | |||
var joints = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP).ToBuffer(); | |||
var list = new FasterList<SimBody>(4); | |||
for (int i = 0; i < joints.count; i++) | |||
{ | |||
ref var joint = ref joints.buffer[i]; | |||
if (joint.jointState == JointState.Broken) continue; | |||
if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); | |||
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA)); | |||
} | |||
return list.ToArray(); | |||
} | |||
public SimBody[] GetClusterBodies(uint cid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
var bodies = new HashSet<uint>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
if (conn.clusterId == cid) | |||
bodies.Add(conn.machineRigidBodyId); | |||
} | |||
} | |||
return bodies.Select(id => new SimBody(id, cid)).ToArray(); | |||
} | |||
public EGID? FindBlockEGID(uint id) | |||
{ | |||
var groups = entitiesDB.FindGroups<DBEntityStruct>(); | |||
foreach (ExclusiveGroupStruct group in groups) | |||
{ | |||
if (entitiesDB.Exists<DBEntityStruct>(id, group)) | |||
return new EGID(id, group); | |||
} | |||
return null; | |||
} | |||
public Cluster GetCluster(uint sbid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
//Static blocks don't have a cluster ID but the cluster destruction manager should have one | |||
if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue) | |||
return new Cluster(conn.clusterId); | |||
} | |||
} | |||
return null; | |||
} | |||
public Block[] GetBodyBlocks(uint sbid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
var set = new HashSet<Block>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
if (conn.machineRigidBodyId == sbid) | |||
set.Add(new Block(conn.ID)); | |||
} | |||
} | |||
return set.ToArray(); | |||
} | |||
#if DEBUG | |||
public EntitiesDB GetEntitiesDB() | |||
{ | |||
return entitiesDB; | |||
} | |||
#endif | |||
} | |||
} |
@@ -1,52 +0,0 @@ | |||
using System; | |||
using System.Linq.Expressions; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Internal; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public partial class BlockEngine | |||
{ | |||
/// <summary> | |||
/// Holds information needed to construct a component initializer | |||
/// </summary> | |||
internal struct BlockInitData | |||
{ | |||
public FasterDictionary<RefWrapperType, ITypeSafeDictionary> Group; | |||
} | |||
internal delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroup( | |||
EntityComponentInitializer initializer); | |||
/// <summary> | |||
/// Accesses the group field of the initializer | |||
/// </summary> | |||
internal GetInitGroup InitGroup = CreateAccessor<GetInitGroup>("_group"); | |||
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection | |||
internal static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate | |||
{ | |||
var invokeMethod = typeof(TDelegate).GetMethod("Invoke"); | |||
if (invokeMethod == null) | |||
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined."); | |||
var delegateParameters = invokeMethod.GetParameters(); | |||
if (delegateParameters.Length != 1) | |||
throw new InvalidOperationException("Delegate must have a single parameter."); | |||
var paramType = delegateParameters[0].ParameterType; | |||
var objParam = Expression.Parameter(paramType, "obj"); | |||
var memberExpr = Expression.PropertyOrField(objParam, memberName); | |||
Expression returnExpr = memberExpr; | |||
if (invokeMethod.ReturnType != memberExpr.Type) | |||
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType); | |||
var lambda = | |||
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam}); | |||
return lambda.Compile(); | |||
} | |||
} | |||
} |
@@ -1,356 +0,0 @@ | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Possible block types | |||
/// </summary> | |||
public enum BlockIDs : ushort | |||
{ | |||
/// <summary> | |||
/// Called "nothing" in Gamecraft. (DBID.NOTHING) | |||
/// </summary> | |||
Invalid = ushort.MaxValue, | |||
AluminiumCube = 0, | |||
AxleS, | |||
HingeS = 3, | |||
MotorS, | |||
HingeM, | |||
MotorM, | |||
TyreM, | |||
AxleM, | |||
IronCube, | |||
RubberCube, | |||
OiledCube, | |||
AluminiumConeSegment, //12 | |||
AluminiumCorner, | |||
AluminiumRoundedCorner, | |||
AluminiumSlicedCube, | |||
AluminiumRoundedSlicedCube, | |||
AluminiumCylinder, | |||
AluminiumPyramidSegment, | |||
AluminiumSlope, | |||
AluminiumRoundedSlope, | |||
AluminiumSphere, | |||
RubberConeSegment, //22 | |||
RubberCorner, | |||
RubberRoundedCorner, | |||
RubberSlicedCube, | |||
RubberRoundedSlicedCube, | |||
RubberCylinder, | |||
RubberPyramidSegment, | |||
RubberSlope, | |||
RubberRoundedSlope, | |||
RubberSphere, | |||
OiledConeSegment, //32 | |||
OiledCorner, | |||
OiledRoundedCorner, | |||
OiledSlicedCube, | |||
OiledRoundedSlicedCube, | |||
OiledCylinder, | |||
OiledPyramidSegment, | |||
OiledSlope, | |||
OiledRoundedSlope, | |||
OiledSphere, | |||
IronConeSegment, //42 | |||
IronCorner, | |||
IronRoundedCorner, | |||
IronSlicedCube, | |||
IronRoundedSlicedCube, | |||
IronCylinder, | |||
IronPyramidSegment, | |||
IronSlope, | |||
IronRoundedSlope, | |||
IronSphere, | |||
GlassCube, //52 | |||
GlassSlicedCube, | |||
GlassSlope, | |||
GlassCorner, | |||
GlassPyramidSegment, | |||
GlassRoundedSlicedCube, | |||
GlassRoundedSlope, | |||
GlassRoundedCorner, | |||
GlassConeSegment, | |||
GlassCylinder, | |||
GlassSphere, | |||
Lever, //63 - two IDs skipped | |||
PlayerSpawn = 66, //Crashes without special handling | |||
SmallSpawn, | |||
MediumSpawn, | |||
LargeSpawn, | |||
BallJoint, | |||
UniversalJoint, | |||
ServoAxle, | |||
ServoHinge, | |||
StepperAxle, | |||
StepperHinge, | |||
TelescopicJoint, | |||
DampedSpring, | |||
ServoPiston, | |||
StepperPiston, | |||
PneumaticPiston, | |||
PneumaticHinge, | |||
PneumaticAxle, //82 | |||
PilotSeat = 90, //Might crash | |||
PassengerSeat, | |||
PilotControls, | |||
GrassCube, | |||
DirtCube, | |||
GrassConeSegment, | |||
GrassCorner, | |||
GrassRoundedCorner, | |||
GrassSlicedCube, | |||
GrassRoundedSlicedCube, | |||
GrassPyramidSegment, | |||
GrassSlope, | |||
GrassRoundedSlope, | |||
DirtConeSegment, | |||
DirtCorner, | |||
DirtRoundedCorner, | |||
DirtSlicedCube, | |||
DirtRoundedSlicedCube, | |||
DirtPyramidSegment, | |||
DirtSlope, | |||
DirtRoundedSlope, | |||
RubberHemisphere, | |||
AluminiumHemisphere, | |||
GrassInnerCornerBulged, | |||
DirtInnerCornerBulged, | |||
IronHemisphere, | |||
OiledHemisphere, | |||
GlassHemisphere, | |||
TyreS, | |||
ThreeWaySwitch, | |||
Dial, //120 | |||
CharacterOnEnterTrigger, //Probably crashes | |||
CharacterOnLeaveTrigger, | |||
CharacterOnStayTrigger, | |||
ObjectOnEnterTrigger, | |||
ObjectOnLeaveTrigger, | |||
ObjectOnStayTrigger, | |||
Button, | |||
Switch, | |||
TextBlock, //Brings up a screen | |||
ConsoleBlock, //Brings up a screen | |||
Door, | |||
GlassDoor, | |||
PoweredDoor, | |||
PoweredGlassDoor, | |||
AluminiumTubeCorner, | |||
IronTubeCorner, | |||
WoodCube, | |||
WoodSlicedCube, | |||
WoodSlope, | |||
WoodCorner, | |||
WoodPyramidSegment, | |||
WoodConeSegment, | |||
WoodRoundedSlicedCube, | |||
WoodRoundedSlope, | |||
WoodRoundedCorner, | |||
WoodCylinder, | |||
WoodHemisphere, | |||
WoodSphere, | |||
BrickCube, //149 | |||
BrickSlicedCube = 151, | |||
BrickSlope, | |||
BrickCorner, | |||
ConcreteCube, | |||
ConcreteSlicedCube, | |||
ConcreteSlope, | |||
ConcreteCorner, | |||
RoadCarTyre, | |||
OffRoadCarTyre, | |||
RacingCarTyre, | |||
BicycleTyre, | |||
FrontBikeTyre, | |||
RearBikeTyre, | |||
ChopperBikeTyre, | |||
TractorTyre, | |||
MonsterTruckTyre, | |||
MotocrossBikeTyre, | |||
CartTyre, //168 | |||
ObjectIdentifier, | |||
ANDLogicBlock, | |||
NANDLogicBlock, | |||
NORLogicBlock, | |||
NOTLogicBlock, | |||
ORLogicBlock, | |||
XNORLogicBlock, | |||
XORLogicBlock, | |||
AbsoluteMathsBlock, | |||
AdderMathsBlock, | |||
DividerMathsBlock, | |||
SignMathsBlock, //180 | |||
MaxMathsBlock, | |||
MinMathsBlock, | |||
MultiplierMathsBlock, | |||
SubtractorMathsBlock, | |||
SimpleConnector, | |||
MeanMathsBlock, | |||
Bit, | |||
Counter, | |||
Timer, | |||
ObjectFilter, | |||
PlayerFilter, | |||
TeamFilter, | |||
Number2Text, //193 | |||
DestructionManager = 260, | |||
ChunkHealthModifier, | |||
ClusterHealthModifier, //262 | |||
BeachTree1 = 200, | |||
BeachTree2, | |||
BeachTree3, | |||
Rock1, | |||
Rock2, | |||
Rock3, | |||
Rock4, | |||
BirchTree1, | |||
BirchTree2, | |||
BirchTree3, | |||
PineTree1, | |||
PineTree2, | |||
PineTree3, | |||
Flower1, | |||
Flower2, | |||
Flower3, | |||
Shrub1, | |||
Shrub2, | |||
Shrub3, | |||
CliffCube, | |||
CliffSlicedCorner, | |||
CliffCornerA, | |||
CliffCornerB, | |||
CliffSlopeA, | |||
CliffSlopeB, | |||
GrassEdge, | |||
GrassEdgeInnerCorner, | |||
GrassEdgeCorner, | |||
GrassEdgeSlope, | |||
CentreHUD, | |||
ObjectiveHUD, | |||
GameStatsHUD, //231 | |||
GameOverBlock, | |||
SFXBlockGameplay = 240, | |||
SFXBlock8Bit, | |||
SFXBlockInstrument, | |||
SFXBlockSciFi, | |||
SFXBlockLoops, | |||
SFXBlockVocal, | |||
MovementConstrainer, //246 | |||
RotationConstrainer, | |||
AdvancedMovementDampener, | |||
AdvancedRotationDampener, | |||
Mover = 250, | |||
Rotator, | |||
MovementDampener, | |||
RotationDampener, | |||
AdvancedMover, | |||
AdvancedRotator, | |||
MusicBlock, //256 | |||
PlasmaCannonBlock, | |||
QuantumRiflePickup = 300, | |||
QuantumRifleAmmoPickup, | |||
AluminiumSlicedFraction, | |||
AluminiumSlicedSlope, | |||
AluminiumHalfPyramidLeft = 305, | |||
AluminiumHalfPyramidRight, | |||
AluminiumPyramidSliced, | |||
AluminiumTubeCross, | |||
AluminiumTubeT, | |||
AluminiumPlateSquare, | |||
AluminiumPlateCircle, | |||
AluminiumPlateTriangle, //312 | |||
OiledSlicedFraction = 314, | |||
OiledSlicedSlope, | |||
OiledHalfPyramidLeft, | |||
OiledHalfPyramidRight, | |||
OiledPyramidSliced, | |||
GlassSlicedFraction, | |||
GlassSlicedSlope, | |||
GlassHalfPyramidLeft, | |||
GlassHalfPyramidRight, | |||
GlassPyramidSliced, | |||
RubberSlicedFraction, | |||
RubberSlicedSlope, | |||
RubberHalfPyramidLeft, | |||
RubberHalfPyramidRight, | |||
RubberPyramidSliced, | |||
WoodSlicedFraction, | |||
WoodSlicedSlope, //330 | |||
WoodHalfPyramidLeft, | |||
WoodHalfPyramidRight, | |||
WoodPyramidSliced, | |||
HexNetSlicedFraction, | |||
HexNetSlicedSlope, | |||
HexNetHalfPyramidLeft, | |||
HexNetHalfPyramidRight, | |||
HexNetPyramidSliced, | |||
OiledTubeCross, | |||
OiledTubeT, //340 | |||
GlassTubeCross, | |||
GlassTubeT, | |||
RubberTubeCross, | |||
RubberTubeT, | |||
WoodTubeCross, | |||
WoodTubeT, | |||
HexNetTubeCross, | |||
HexNetTubeT, | |||
BouncyCube, | |||
BouncySlicedCube, //350 | |||
BouncySlope, | |||
BouncyCorner, | |||
OiledTubeCorner, | |||
GlassTubeCorner, | |||
RubberTubeCorner, | |||
WoodTubeCorner, | |||
Basketball, | |||
BowlingBall, | |||
SoccerBall, | |||
GolfBall, //360 | |||
HockeyPuck, | |||
PoolBall, | |||
BouncyBall, | |||
TennisBall, | |||
UnlitCube, | |||
IronSlicedFraction, | |||
IronSlicedSlope, | |||
IronHalfPyramidLeft, | |||
IronHalfPyramidRight, | |||
IronPyramidSliced, //370 | |||
IronTubeCross, | |||
IronTubeT, | |||
SFXBlockMob = 374, | |||
PointLight, | |||
SpotLight, | |||
SunLight, | |||
AmbientLight, | |||
UnlitGlowCube = 381, | |||
PointLightInvisible, | |||
SpotLightInvisible, | |||
UnlitSlope, | |||
UnlitGlowSlope, | |||
Fog, | |||
Sky, | |||
MagmaRockCube = 777, | |||
MagmaRockCubeSliced, | |||
MagmaRockSlope, | |||
MagmaRockCorner, | |||
MagmaRockPyramidSegment, | |||
MagmaRockConeSegment, | |||
MagmaRockSlicedRounded, | |||
MagmaRockSlopeRounded, | |||
MagmaRockCornerRounded, | |||
HexNetCube, | |||
HexNetCubeSliced, | |||
HexNetSlope, | |||
HexNetCorner, | |||
HexNetPyramidSegment, | |||
HexNetConeSegment, | |||
HexNetSlicedRounded, | |||
HexNetSlopeRounded, | |||
HexNetCornerRounded, //794 | |||
MagmaRockBulgedInner, | |||
HexNetCylinder = 797, | |||
HexNetHemisphere, | |||
HexNetSphere, | |||
HexNetTubeCorner //800 | |||
} | |||
} |
@@ -1,42 +0,0 @@ | |||
using Svelto.ECS; | |||
using RobocraftX.Common; | |||
using HarmonyLib; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// ExclusiveGroups and IDs used with blocks | |||
/// </summary> | |||
public static class BlockIdentifiers | |||
{ | |||
/// <summary> | |||
/// Blocks placed by the player | |||
/// </summary> - TODO | |||
//public static ExclusiveGroup OWNED_BLOCKS { get { return CommonExclusiveGroups.REAL_BLOCKS_GROUPS_DON_T_USE_IN_NEW_CODE; } } | |||
/// <summary> | |||
/// Extra parts used in functional blocks | |||
/// </summary> | |||
public static ExclusiveGroup FUNCTIONAL_BLOCK_PARTS { get { return CommonExclusiveGroups.FUNCTIONAL_BLOCK_PART_GROUP; } } | |||
/// <summary> | |||
/// Blocks which are disabled in Simulation mode | |||
/// </summary> | |||
public static ExclusiveGroup SIM_BLOCKS_DISABLED { get { return CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP; } } | |||
//public static ExclusiveGroup SPAWN_POINTS { get { return CommonExclusiveGroups.SPAWN_POINTS_GROUP; } } | |||
//public static ExclusiveGroup SPAWN_POINTS_DISABLED { get { return CommonExclusiveGroups.SPAWN_POINTS_DISABLED_GROUP; } } | |||
/// <summary> | |||
/// The ID of the most recently placed block | |||
/// </summary> | |||
public static uint LatestBlockID { | |||
get | |||
{ //Need the private field as the property increments itself | |||
return ((uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null)) - 1; | |||
} | |||
} | |||
} | |||
} |
@@ -1,124 +0,0 @@ | |||
using System; | |||
using Gamecraft.Wires; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Tests; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
#if TEST | |||
/// <summary> | |||
/// Block test cases. Not accessible in release versions. | |||
/// </summary> | |||
[APITestClass] | |||
public static class BlockTests | |||
{ | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestPlaceNew() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero); | |||
Assert.NotNull(newBlock.Id, "Newly placed block is missing Id. This should be populated when the block is placed.", "Newly placed block Id is not null, block successfully placed."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestInitProperty() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero + 2); | |||
if (!Assert.CloseTo(newBlock.Position, (Unity.Mathematics.float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return; | |||
//Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestTextBlock() | |||
{ | |||
TextBlock textBlock = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { textBlock = Block.PlaceNew<TextBlock>(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1); }, "Block.PlaceNew<TextBlock>() raised an exception: ", "Block.PlaceNew<TextBlock>() completed without issue."); | |||
if (!Assert.NotNull(textBlock, "Block.PlaceNew<TextBlock>() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return; | |||
if (!Assert.NotNull(textBlock.Text, "TextBlock.Text is null, possibly because it failed silently.", "TextBlock.Text is not null.")) return; | |||
if (!Assert.NotNull(textBlock.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestMotor() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.MotorS, Unity.Mathematics.float3.zero + 1); | |||
Motor b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<Motor>(); }, "Block.Specialize<Motor>() raised an exception: ", "Block.Specialize<Motor>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Motor>() returned null, possibly because it failed silently.", "Specialized Motor is not null.")) return; | |||
if (!Assert.CloseTo(b.Torque, 75f, $"Motor.Torque {b.Torque} does not equal default value, possibly because it failed silently.", "Motor.Torque close enough to default.")) return; | |||
if (!Assert.CloseTo(b.TopSpeed, 30f, $"Motor.TopSpeed {b.TopSpeed} does not equal default value, possibly because it failed silently.", "Motor.Torque is close enough to default.")) return; | |||
if (!Assert.Equal(b.Reverse, false, $"Motor.Reverse {b.Reverse} does not equal default value, possibly because it failed silently.", "Motor.Reverse is default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestPiston() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.PneumaticPiston, Unity.Mathematics.float3.zero + 1); | |||
Piston b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<Piston>(); }, "Block.Specialize<Piston>() raised an exception: ", "Block.Specialize<Piston>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Piston>() returned null, possibly because it failed silently.", "Specialized Piston is not null.")) return; | |||
if (!Assert.CloseTo(b.MaximumExtension, 1.01f, $"Piston.MaximumExtension {b.MaximumExtension} does not equal default value, possibly because it failed silently.", "Piston.MaximumExtension is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Piston.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Piston.MaximumForce is close enough to default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestServo() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.ServoAxle, Unity.Mathematics.float3.zero + 1); | |||
Servo b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<Servo>(); }, "Block.Specialize<Servo>() raised an exception: ", "Block.Specialize<Servo>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<Servo>() returned null, possibly because it failed silently.", "Specialized Servo is not null.")) return; | |||
if (!Assert.CloseTo(b.MaximumAngle, 180f, $"Servo.MaximumAngle {b.MaximumAngle} does not equal default value, possibly because it failed silently.", "Servo.MaximumAngle is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MinimumAngle, -180f, $"Servo.MinimumAngle {b.MinimumAngle} does not equal default value, possibly because it failed silently.", "Servo.MinimumAngle is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Servo.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Servo.MaximumForce is close enough to default.")) return; | |||
} | |||
[APITestCase(TestType.Game)] | |||
public static void TestMusicBlock1() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2); | |||
MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<MusicBlock>(); }, "Block.Specialize<MusicBlock>() raised an exception: ", "Block.Specialize<MusicBlock>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; | |||
if (!Assert.CloseTo(b.Volume, 100f, $"MusicBlock.Volume {b.Volume} does not equal default value, possibly because it failed silently.", "MusicBlock.Volume is close enough to default.")) return; | |||
if (!Assert.Equal(b.TrackIndex, 0, $"MusicBlock.TrackIndex {b.TrackIndex} does not equal default value, possibly because it failed silently.", "MusicBlock.TrackIndex is equal to default.")) return; | |||
_musicBlock = b; | |||
} | |||
private static MusicBlock _musicBlock; | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestMusicBlock2() | |||
{ | |||
//Block newBlock = Block.GetLastPlacedBlock(); | |||
var b = _musicBlock; | |||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; | |||
b.IsPlaying = true; // play sfx | |||
if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return; | |||
if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return; | |||
//Assert.Log(b.Track.ToString()); | |||
if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestLogicGate() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1); | |||
LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; | |||
if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return; | |||
if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return; | |||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; | |||
//if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return; | |||
LogicGate target = null; | |||
if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return; | |||
Wire newWire = null; | |||
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return; | |||
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return; | |||
} | |||
} | |||
#endif | |||
} |
@@ -1,75 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class ConsoleBlock : SignalingBlock | |||
{ | |||
public ConsoleBlock(EGID id): base(id) | |||
{ | |||
} | |||
public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.CONSOLE_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom console block properties | |||
/// <summary> | |||
/// Setting a nonexistent command will crash the game when switching to simulation | |||
/// </summary> | |||
public string Command | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.commandName); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.commandName.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg1 | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg1); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg1.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg2 | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg2); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg2.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg3 | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg3); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg3.Set(val), | |||
value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,48 +0,0 @@ | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class DampedSpring : Block | |||
{ | |||
public DampedSpring(EGID id) : base(id) | |||
{ | |||
} | |||
public DampedSpring(uint id) : base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP)) | |||
{ | |||
} | |||
/// <summary> | |||
/// The spring's maximum force. This is known as Stiffness in-game | |||
/// </summary> | |||
public float MaxForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct dsrs) => dsrs.maxForce); | |||
set => BlockEngine.SetBlockInfo(this, | |||
(ref DampedSpringReadOnlyStruct dsrs, float val) => dsrs.maxForce = val, value); | |||
} | |||
/// <summary> | |||
/// Alias of MaxForce. | |||
/// </summary> | |||
public float Stiffness | |||
{ | |||
get => MaxForce; | |||
set => MaxForce = value; | |||
} | |||
/// <summary> | |||
/// The spring's maximum damping force. | |||
/// </summary> | |||
public float Damping | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (LinearJointForcesReadOnlyStruct ljf) => ljf.dampingForceMagnitude); | |||
set => BlockEngine.SetBlockInfo(this, | |||
(ref LinearJointForcesReadOnlyStruct ljf, float val) => ljf.dampingForceMagnitude = val, value); | |||
} | |||
} | |||
} |
@@ -1,16 +0,0 @@ | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class LogicGate : SignalingBlock | |||
{ | |||
public LogicGate(EGID id) : base(id) | |||
{ | |||
} | |||
public LogicGate(uint id) : base(new EGID(id, CommonExclusiveGroups.LOGIC_BLOCK_GROUP)) | |||
{ | |||
} | |||
} | |||
} |
@@ -1,72 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Motor : SignalingBlock | |||
{ | |||
public Motor(EGID id) : base(id) | |||
{ | |||
} | |||
public Motor(uint id): base(new EGID(id, CommonExclusiveGroups.MOTOR_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom motor properties | |||
/// <summary> | |||
/// The motor's maximum rotational velocity. | |||
/// </summary> | |||
public float TopSpeed | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxVelocity); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxVelocity = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The motor's maximum rotational force. | |||
/// </summary> | |||
public float Torque | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxForce); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The motor's direction. | |||
/// </summary> | |||
public bool Reverse | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.reverse); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, bool val) => st.reverse = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,80 +0,0 @@ | |||
using RobocraftX.Common; | |||
using RobocraftX.UECS; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Transforms; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Engine which executes block movement actions | |||
/// </summary> | |||
public class MovementEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIMovementGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsInGame = false; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
// implementations for Movement static class | |||
internal float3 MoveBlock(EGID blockID, BlockEngine.BlockInitData data, float3 vector) | |||
{ | |||
if (!entitiesDB.Exists<PositionEntityStruct>(blockID)) | |||
{ | |||
if (data.Group == null) return float3.zero; | |||
var init = new EntityComponentInitializer(blockID, data.Group); | |||
init.Init(new PositionEntityStruct {position = vector}); | |||
init.Init(new GridRotationStruct {position = vector}); | |||
init.Init(new LocalTransformEntityStruct {position = vector}); | |||
return vector; | |||
} | |||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID); | |||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID); | |||
// main (persistent) position | |||
posStruct.position = vector; | |||
// placement grid position | |||
gridStruct.position = vector; | |||
// rendered position | |||
transStruct.position = vector; | |||
// collision position | |||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation | |||
{ | |||
Value = posStruct.position | |||
}); | |||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID).isProcessed = false; | |||
return posStruct.position; | |||
} | |||
internal float3 GetPosition(EGID blockID, BlockEngine.BlockInitData data) | |||
{ | |||
if (!entitiesDB.Exists<PositionEntityStruct>(blockID)) | |||
{ | |||
if (data.Group == null) return float3.zero; | |||
var init = new EntityComponentInitializer(blockID, data.Group); | |||
return init.Has<PositionEntityStruct>() ? init.Get<PositionEntityStruct>().position : float3.zero; | |||
} | |||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID); | |||
return posStruct.position; | |||
} | |||
} | |||
} |
@@ -1,146 +0,0 @@ | |||
using System; | |||
using FMOD.Studio; | |||
using FMODUnity; | |||
using Gamecraft.Wires; | |||
using RobocraftX.Common; | |||
using RobocraftX.Blocks; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Tests; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class MusicBlock : SignalingBlock | |||
{ | |||
public MusicBlock(EGID id) : base(id) | |||
{ | |||
} | |||
public MusicBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.MUSIC_BLOCK_GROUP)) | |||
{ | |||
} | |||
public byte TrackIndex | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct st) => st.trackIndx); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref MusicBlockDataEntityStruct msdes, byte val) => msdes.trackIndx = val, value); | |||
} | |||
} | |||
public Guid Track | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, | |||
(MusicBlockDataEntityStruct msdes) => msdes.fmod2DEventPaths.Get<Guid>(msdes.trackIndx)); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref MusicBlockDataEntityStruct msdes, Guid val) => | |||
{ | |||
for (byte i = 0; i < msdes.fmod2DEventPaths.Count<Guid>(); i++) | |||
{ | |||
Guid track = msdes.fmod2DEventPaths.Get<Guid>(i); | |||
if (track == val) | |||
{ | |||
msdes.trackIndx = i; | |||
break; | |||
} | |||
} | |||
}, value); | |||
} | |||
} | |||
public Guid[] Tracks | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct msdes) => | |||
{ | |||
Guid[] tracks = new Guid[msdes.fmod2DEventPaths.Count<Guid>()]; | |||
for (byte i = 0; i < tracks.Length; i++) | |||
{ | |||
tracks[i] = msdes.fmod2DEventPaths.Get<Guid>(i); | |||
} | |||
return tracks; | |||
}); | |||
} | |||
} | |||
public float Volume | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct msdes) => msdes.tweakableVolume); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref MusicBlockDataEntityStruct msdes, float val) => msdes.tweakableVolume = val, value); | |||
} | |||
} | |||
public ChannelType ChannelType | |||
{ | |||
get | |||
{ | |||
//Assert.Log("Block exists: " + Exists); | |||
return BlockEngine.GetBlockInfo(this, | |||
(MusicBlockDataEntityStruct msdes) => (ChannelType) msdes.channelType); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref MusicBlockDataEntityStruct msdes, ChannelType val) => msdes.channelType = (byte) val, value); | |||
} | |||
} | |||
public bool IsPlaying | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, | |||
(MusicBlockDataEntityStruct msdes) => msdes.isPlaying); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref MusicBlockDataEntityStruct msdes, bool val) => | |||
{ | |||
if (msdes.isPlaying == val) return; | |||
if (val) | |||
{ | |||
// start playing | |||
EventInstance inst = RuntimeManager.CreateInstance(msdes.fmod2DEventPaths.Get<Guid>(msdes.trackIndx)); | |||
inst.setVolume(msdes.tweakableVolume / 100f); | |||
inst.start(); | |||
msdes.eventHandle = inst.handle; | |||
} | |||
else | |||
{ | |||
// stop playing | |||
EventInstance inst = default(EventInstance); | |||
inst.handle = msdes.eventHandle; | |||
inst.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); | |||
inst.release(); | |||
} | |||
msdes.isPlaying = val; | |||
}, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,52 +0,0 @@ | |||
using Gamecraft.Wires; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class ObjectIdentifier : Block | |||
{ | |||
public ObjectIdentifier(EGID id) : base(id) | |||
{ | |||
} | |||
public ObjectIdentifier(uint id) : base(new EGID(id, CommonExclusiveGroups.OBJID_BLOCK_GROUP)) | |||
{ | |||
} | |||
public char Identifier | |||
{ | |||
get => (char) BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.objectId + 'A'); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ObjectIdEntityStruct st, char val) => | |||
{ | |||
st.objectId = (byte) (val - 'A'); | |||
Label = val + ""; //The label isn't updated automatically | |||
}, value); | |||
} | |||
} | |||
/// <summary> | |||
/// Simulation-time ID. Assigned by the game starting from 0. | |||
/// </summary> | |||
public byte SimID | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.simObjectId); | |||
} | |||
/// <summary> | |||
/// Finds the identifier blocks with the given ID. | |||
/// </summary> | |||
/// <param name="id">The ID to look for</param> | |||
/// <returns>An array that may be empty</returns> | |||
public static ObjectIdentifier[] GetByID(char id) => BlockEngine.GetObjectIDsFromID((byte) (id - 'A'), false); | |||
/// <summary> | |||
/// Finds the identifier blocks with the given simulation-time ID. This ID is assigned by the game starting from 0. | |||
/// </summary> | |||
/// <param name="id"></param> | |||
/// <returns></returns> | |||
public static ObjectIdentifier[] GetBySimID(byte id) => BlockEngine.GetObjectIDsFromID(id, true); | |||
} | |||
} |
@@ -1,51 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI.Utility; | |||
using RobocraftX.Common; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Piston : SignalingBlock | |||
{ | |||
public Piston(EGID id) : base(id) | |||
{ | |||
} | |||
public Piston(uint id) : base(new EGID(id, CommonExclusiveGroups.PISTON_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom piston properties | |||
/// <summary> | |||
/// The piston's max extension distance. | |||
/// </summary> | |||
public float MaximumExtension | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxDeviation); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxDeviation = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
/// The piston's max extension force. | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,131 +0,0 @@ | |||
using System; | |||
using System.Reflection; | |||
using DataLoader; | |||
using HarmonyLib; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Blocks.Scaling; | |||
using RobocraftX.Character; | |||
using RobocraftX.Common; | |||
using RobocraftX.CR.MachineEditing; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Players; | |||
using RobocraftX.Rendering.GPUI; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Engine which executes block placement actions | |||
/// </summary> | |||
public class PlacementEngine : IApiEngine | |||
{ | |||
public bool IsInGame; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine | |||
public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale, | |||
float3 scale, Player player, float3 rotation, out EntityComponentInitializer initializer) | |||
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one | |||
if (darkness > 9) | |||
throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)"); | |||
initializer = BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, | |||
(player ?? new Player(PlayerType.Local)).Id); | |||
return initializer.EGID; | |||
} | |||
private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) | |||
{ | |||
if (_blockEntityFactory == null) | |||
throw new BlockException("The factory is null."); | |||
if (uscale < 1) | |||
throw new BlockException("Scale needs to be at least 1"); | |||
if (scale.x < 4e-5) scale.x = uscale; | |||
if (scale.y < 4e-5) scale.y = uscale; | |||
if (scale.z < 4e-5) scale.z = uscale; | |||
uint dbid = block; | |||
if (!PrefabsID.HasPrefabRegistered(dbid, 0)) | |||
throw new BlockException("Block with ID " + dbid + " not found!"); | |||
//RobocraftX.CR.MachineEditing.PlaceBlockEngine | |||
ScalingEntityStruct scaling = new ScalingEntityStruct {scale = scale}; | |||
Quaternion rotQ = Quaternion.Euler(rot); | |||
RotationEntityStruct rotation = new RotationEntityStruct {rotation = rotQ}; | |||
GridRotationStruct gridRotation = new GridRotationStruct | |||
{position = position, rotation = rotQ}; | |||
DBEntityStruct dbEntity = new DBEntityStruct {DBID = dbid}; | |||
BlockPlacementScaleEntityStruct placementScale = new BlockPlacementScaleEntityStruct | |||
{ | |||
blockPlacementHeight = uscale, blockPlacementWidth = uscale, desiredScaleFactor = uscale | |||
}; | |||
EquippedColourStruct colour = new EquippedColourStruct {indexInPalette = color}; | |||
EntityComponentInitializer | |||
structInitializer = | |||
_blockEntityFactory.Build(CommonExclusiveGroups.nextBlockEntityID, dbid); //The ghost block index is only used for triggers | |||
if (colour.indexInPalette != byte.MaxValue) | |||
structInitializer.Init(new ColourParameterEntityStruct | |||
{ | |||
indexInPalette = colour.indexInPalette, | |||
hasNetworkChange = true | |||
}); | |||
uint prefabId = PrefabsID.GetPrefabId(dbid, 0); | |||
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId)); | |||
structInitializer.Init(new PhysicsPrefabEntityStruct(prefabId)); | |||
structInitializer.Init(dbEntity); | |||
structInitializer.Init(new PositionEntityStruct {position = position}); | |||
structInitializer.Init(rotation); | |||
structInitializer.Init(scaling); | |||
structInitializer.Init(gridRotation); | |||
structInitializer.Init(new UniformBlockScaleEntityStruct | |||
{ | |||
scaleFactor = placementScale.desiredScaleFactor | |||
}); | |||
structInitializer.Init(new BlockPlacementInfoStruct() | |||
{ | |||
loadedFromDisk = false, | |||
placedBy = playerId | |||
}); | |||
PrimaryRotationUtility.InitialisePrimaryDirection(rotation.rotation, ref structInitializer); | |||
EGID playerEGID = new EGID(playerId, CharacterExclusiveGroups.OnFootGroup); | |||
ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity<PickedBlockExtraDataStruct>(playerEGID); | |||
pickedBlock.placedBlockEntityID = structInitializer.EGID; | |||
pickedBlock.placedBlockWasAPickedBlock = false; | |||
return structInitializer; | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine"; | |||
public bool isRemovable => false; | |||
[HarmonyPatch] | |||
public class FactoryObtainerPatch | |||
{ | |||
static void Postfix(BlockEntityFactory blockEntityFactory) | |||
{ | |||
_blockEntityFactory = blockEntityFactory; | |||
Logging.MetaDebugLog("Block entity factory injected."); | |||
} | |||
static MethodBase TargetMethod(Harmony instance) | |||
{ | |||
return AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceBlockEngine").GetConstructors()[0]; | |||
} | |||
} | |||
} | |||
} |
@@ -1,90 +0,0 @@ | |||
using RobocraftX.Common; | |||
using RobocraftX.UECS; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Engine which executes block movement actions | |||
/// </summary> | |||
public class RotationEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIRotationGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsInGame = false; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
// implementations for Rotation static class | |||
internal float3 RotateBlock(EGID blockID, BlockEngine.BlockInitData data, Vector3 vector) | |||
{ | |||
if (!entitiesDB.Exists<RotationEntityStruct>(blockID)) | |||
{ | |||
if (data.Group == null) return float3.zero; | |||
var init = new EntityComponentInitializer(blockID, data.Group); | |||
init.Init(new RotationEntityStruct {rotation = new Quaternion {eulerAngles = vector}}); | |||
init.Init(new GridRotationStruct {rotation = new Quaternion {eulerAngles = vector}}); | |||
init.Init(new LocalTransformEntityStruct {rotation = new Quaternion {eulerAngles = vector}}); | |||
return vector; | |||
} | |||
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID); | |||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID); | |||
// main (persistent) position | |||
Quaternion newRotation = rotStruct.rotation; | |||
newRotation.eulerAngles = vector; | |||
rotStruct.rotation = newRotation; | |||
// placement grid rotation | |||
Quaternion newGridRotation = gridStruct.rotation; | |||
newGridRotation.eulerAngles = vector; | |||
gridStruct.rotation = newGridRotation; | |||
// rendered position | |||
Quaternion newTransRotation = rotStruct.rotation; | |||
newTransRotation.eulerAngles = vector; | |||
transStruct.rotation = newTransRotation; | |||
// collision position | |||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation | |||
{ | |||
Value = rotStruct.rotation | |||
}); | |||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID).isProcessed = false; | |||
return ((Quaternion)rotStruct.rotation).eulerAngles; | |||
} | |||
internal float3 GetRotation(EGID blockID, BlockEngine.BlockInitData data) | |||
{ | |||
if (!entitiesDB.Exists<RotationEntityStruct>(blockID)) | |||
{ | |||
if (data.Group == null) return float3.zero; | |||
var init = new EntityComponentInitializer(blockID, data.Group); | |||
return init.Has<RotationEntityStruct>() | |||
? (float3) ((Quaternion) init.Get<RotationEntityStruct>().rotation).eulerAngles | |||
: float3.zero; | |||
} | |||
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID); | |||
return ((Quaternion) rotStruct.rotation).eulerAngles; | |||
} | |||
} | |||
} |
@@ -1,76 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Servo : SignalingBlock | |||
{ | |||
public Servo(EGID id) : base(id) | |||
{ | |||
} | |||
public Servo(uint id) : base(new EGID(id, CommonExclusiveGroups.SERVO_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom servo properties | |||
/// <summary> | |||
/// The servo's minimum angle. | |||
/// </summary> | |||
public float MinimumAngle | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.minDeviation); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.minDeviation = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The servo's maximum angle. | |||
/// </summary> | |||
public float MaximumAngle | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxDeviation); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxDeviation = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The servo's maximum force. | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The servo's direction. | |||
/// </summary> | |||
public bool Reverse | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.reverse); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, bool val) => st.reverse = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,209 +0,0 @@ | |||
using System; | |||
using FMOD.Studio; | |||
using FMODUnity; | |||
using Gamecraft.Wires; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class SfxBlock : SignalingBlock | |||
{ | |||
public SfxBlock(EGID id) : base(id) | |||
{ | |||
} | |||
public SfxBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.SIMPLESFX_BLOCK_GROUP /* This could also be BUILD_LOOPEDSFX_BLOCK_GROUP */)) | |||
{ | |||
} | |||
public float Volume | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => obj.tweakableVolume); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, float val) => obj.tweakableVolume = val, value); | |||
} | |||
} | |||
public float Pitch | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => obj.tweakablePitch); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, float val) => obj.tweakablePitch = val, value); | |||
} | |||
} | |||
public bool Is3D | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => obj.is3D); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, bool val) => obj.is3D = val, value); | |||
} | |||
} | |||
public ChannelType ChannelType | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => (ChannelType)obj.channelType); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, ChannelType val) => obj.tweakableVolume = (byte) val, value); | |||
} | |||
} | |||
public byte TrackIndex | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => obj.soundEffectIndex); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, byte val) => obj.soundEffectIndex = val, value); | |||
} | |||
} | |||
// track | |||
public Guid Track | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, | |||
(SoundSfxBlockDataEntityStruct obj) => obj.is3D ? obj.fmod3DEventPaths.Get<Guid>(obj.soundEffectIndex) : obj.fmod2DEventPaths.Get<Guid>(obj.soundEffectIndex)); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SoundSfxBlockDataEntityStruct obj, Guid val) => | |||
{ | |||
for (byte i = 0; i < obj.fmod2DEventPaths.Count<Guid>(); i++) | |||
{ | |||
Guid track = obj.fmod2DEventPaths.Get<Guid>(i); | |||
if (track == val) | |||
{ | |||
obj.soundEffectIndex = i; | |||
obj.is3D = false; | |||
return; | |||
} | |||
} | |||
for (byte i = 0; i < obj.fmod3DEventPaths.Count<Guid>(); i++) | |||
{ | |||
Guid track = obj.fmod3DEventPaths.Get<Guid>(i); | |||
if (track == val) | |||
{ | |||
obj.soundEffectIndex = i; | |||
obj.is3D = true; | |||
return; | |||
} | |||
} | |||
}, value); | |||
} | |||
} | |||
// all tracks | |||
public Guid[] Tracks2D | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => | |||
{ | |||
Guid[] tracks = new Guid[obj.fmod2DEventPaths.Count<Guid>()]; | |||
for (byte i = 0; i < tracks.Length; i++) | |||
{ | |||
tracks[i] = obj.fmod2DEventPaths.Get<Guid>(i); | |||
} | |||
return tracks; | |||
}); | |||
} | |||
} | |||
public Guid[] Tracks3D | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => | |||
{ | |||
Guid[] tracks = new Guid[obj.fmod3DEventPaths.Count<Guid>()]; | |||
for (byte i = 0; i < tracks.Length; i++) | |||
{ | |||
tracks[i] = obj.fmod2DEventPaths.Get<Guid>(i); | |||
} | |||
return tracks; | |||
}); | |||
} | |||
} | |||
public bool IsLooped | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, (SoundSfxBlockDataEntityStruct obj) => obj.isLoopedBlock); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, | |||
(ref SoundSfxBlockDataEntityStruct obj, bool val) => obj.isLoopedBlock = val, value); | |||
} | |||
} | |||
public bool IsPlaying | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo(this, | |||
(SoundSfxBlockDataEntityStruct obj) => obj.isPlaying); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SoundSfxBlockDataEntityStruct obj, bool val) => | |||
{ | |||
if (obj.isPlaying == val) return; | |||
if (val) | |||
{ | |||
// start playing | |||
EventInstance inst = RuntimeManager.CreateInstance(obj.is3D ? obj.fmod3DEventPaths.Get<Guid>(obj.soundEffectIndex) : obj.fmod2DEventPaths.Get<Guid>(obj.soundEffectIndex)); | |||
inst.setVolume(obj.tweakableVolume / 100f); | |||
inst.start(); | |||
obj.eventHandle = inst.handle; | |||
} | |||
else | |||
{ | |||
// stop playing | |||
EventInstance inst = default(EventInstance); | |||
inst.handle = obj.eventHandle; | |||
inst.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT); | |||
inst.release(); | |||
} | |||
obj.isPlaying = val; | |||
}, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,78 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Gamecraft.CharacterVulnerability; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class SpawnPoint : Block | |||
{ | |||
public SpawnPoint(EGID id) : base(id) | |||
{ | |||
} | |||
public SpawnPoint(uint id) : base(new EGID(id, CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom spawn point properties | |||
/// <summary> | |||
/// The lives the player spawns in with. | |||
/// </summary> | |||
public uint Lives | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.lives); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, uint val) => st.lives = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// Whether the spawned player can take damage. | |||
/// </summary> | |||
public bool Damageable | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.canTakeDamage); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.canTakeDamage = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// Whether the game over screen will be displayed | |||
/// </summary> | |||
public bool GameOverEnabled | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.gameOverScreen); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.gameOverScreen = val, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The team id for players who spawn here. | |||
/// </summary> | |||
public byte Team | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointIdsEntityStruct st) => st.teamId); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointIdsEntityStruct st, byte val) => st.teamId = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,63 +0,0 @@ | |||
using System; | |||
using Gamecraft.Blocks.GUI; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class TextBlock : SignalingBlock | |||
{ | |||
public TextBlock(EGID id) : base(id) | |||
{ | |||
} | |||
public TextBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.TEXT_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom text block properties | |||
/// <summary> | |||
/// The text block's current text. | |||
/// </summary> | |||
public string Text | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textCurrent); | |||
set | |||
{ | |||
if (value == null) value = ""; | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
{ | |||
if (val == null) val = ""; | |||
tbds.textCurrent.Set(val); | |||
tbds.textStored.Set(val); | |||
}, value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value); | |||
} | |||
} | |||
/// <summary> | |||
/// The text block's current text block ID (used in ChangeTextBlockCommand). | |||
/// </summary> | |||
public string TextBlockId | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textBlockID); | |||
set | |||
{ | |||
if (value == null) value = ""; | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
tbds.textBlockID.Set(val), value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,82 +0,0 @@ | |||
using System; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Gamecraft.Blocks.TimerBlock; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Timer : SignalingBlock | |||
{ | |||
public Timer(EGID id) : base(id) | |||
{ | |||
} | |||
public Timer(uint id) : base(new EGID(id, CommonExclusiveGroups.TIMER_BLOCK_GROUP)) | |||
{ | |||
} | |||
// custom timer properties | |||
/// <summary> | |||
/// The player-specified start time. | |||
/// </summary> | |||
public float Start | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.startTime); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.startTime = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player-specified end time. | |||
/// </summary> | |||
public float End | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.endTime); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.endTime = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
/// Whether to display time with millisecond precision. | |||
/// </summary> | |||
public bool DisplayMilliseconds | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.outputFormatHasMS); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, bool val) => tbds.outputFormatHasMS = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
/// Current time (as of the last video frame), in milliseconds. | |||
/// </summary> | |||
public int CurrentTime | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockLabelCacheEntityStruct st) => st.timeLastRenderFrameMS); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockLabelCacheEntityStruct tbds, int val) => tbds.timeLastRenderFrameMS = val, | |||
value); | |||
} | |||
} | |||
} | |||
} |
@@ -1,355 +0,0 @@ | |||
using System; | |||
using Gamecraft.Wires; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Experimental; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Wire | |||
{ | |||
internal static SignalEngine signalEngine; | |||
protected EGID startPortEGID; | |||
protected EGID endPortEGID; | |||
protected EGID startBlockEGID; | |||
protected EGID endBlockEGID; | |||
protected EGID wireEGID; | |||
protected bool inputToOutput; | |||
protected byte startPort; | |||
protected byte endPort; | |||
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort) | |||
{ | |||
WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort); | |||
return new Wire(wire, start, end); | |||
} | |||
/// <summary> | |||
/// An existing wire connection ending at the specified input. | |||
/// If multiple exist, this will return the first one found. | |||
/// </summary> | |||
/// <param name="end">Destination block.</param> | |||
/// <param name="endPort">Port number.</param> | |||
/// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns> | |||
public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort) | |||
{ | |||
EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists); | |||
if (!exists) return null; | |||
WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists); | |||
if (exists) | |||
{ | |||
return new Wire(new Block(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort); | |||
} | |||
return null; | |||
} | |||
/// <summary> | |||
/// An existing wire connection starting at the specified output. | |||
/// If multiple exist, this will return the first one found. | |||
/// </summary> | |||
/// <param name="start">Source block entity ID.</param> | |||
/// <param name="startPort">Port number.</param> | |||
/// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns> | |||
public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort) | |||
{ | |||
EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists); | |||
if (!exists) return null; | |||
WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists); | |||
if (exists) | |||
{ | |||
return new Wire(start, new Block(wire.destinationBlockEGID), startPort, wire.destinationPortUsage); | |||
} | |||
return null; | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing connection. | |||
/// </summary> | |||
/// <param name="start">Starting block ID.</param> | |||
/// <param name="end">Ending block ID.</param> | |||
/// <param name="startPort">Starting port number, or guess if omitted.</param> | |||
/// <param name="endPort">Ending port number, or guess if omitted.</param> | |||
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception> | |||
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) | |||
{ | |||
startBlockEGID = start.Id; | |||
endBlockEGID = end.Id; | |||
// find block ports | |||
WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort); | |||
if (exists) | |||
{ | |||
wireEGID = wire.ID; | |||
endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
inputToOutput = false; | |||
endPort = wire.destinationPortUsage; | |||
startPort = wire.sourcePortUsage; | |||
} | |||
else | |||
{ | |||
// flip I/O around and try again | |||
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort); | |||
if (exists) | |||
{ | |||
wireEGID = wire.ID; | |||
endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
inputToOutput = true; // end is actually the source | |||
// NB: start and end are handled exactly as they're received as params. | |||
// This makes wire traversal easier, but makes logic in this class a bit more complex | |||
endPort = wire.sourcePortUsage; | |||
startPort = wire.destinationPortUsage; | |||
} | |||
else | |||
{ | |||
throw new WireInvalidException("Wire not found"); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing wire connection. | |||
/// </summary> | |||
/// <param name="start">Starting block ID.</param> | |||
/// <param name="end">Ending block ID.</param> | |||
/// <param name="startPort">Starting port number.</param> | |||
/// <param name="endPort">Ending port number.</param> | |||
/// <param name="wire">The wire ID.</param> | |||
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param> | |||
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput) | |||
{ | |||
this.startBlockEGID = start.Id; | |||
this.endBlockEGID = end.Id; | |||
this.inputToOutput = inputToOutput; | |||
this.wireEGID = wire; | |||
if (inputToOutput) | |||
{ | |||
endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
} | |||
else | |||
{ | |||
endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
} | |||
this.startPort = startPort; | |||
this.endPort = endPort; | |||
} | |||
/// <summary> | |||
/// Construct a wire object from an existing wire connection. | |||
/// </summary> | |||
/// <param name="wireEgid">The wire ID.</param> | |||
public Wire(EGID wireEgid) | |||
{ | |||
this.wireEGID = wireEgid; | |||
WireEntityStruct wire = signalEngine.GetWire(wireEGID); | |||
this.startBlockEGID = wire.sourceBlockEGID; | |||
this.endBlockEGID = wire.destinationBlockEGID; | |||
this.inputToOutput = false; | |||
endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
this.endPort = wire.destinationPortUsage; | |||
this.startPort = wire.sourcePortUsage; | |||
} | |||
internal Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest) | |||
{ | |||
this.wireEGID = wire.ID; | |||
this.startBlockEGID = wire.sourceBlockEGID; | |||
this.endBlockEGID = wire.destinationBlockEGID; | |||
inputToOutput = false; | |||
endPortEGID = signalEngine.MatchBlockInputToPort(dest, wire.destinationPortUsage, out bool exists); | |||
if (!exists) throw new WireInvalidException("Wire end port not found"); | |||
startPortEGID = signalEngine.MatchBlockOutputToPort(src, wire.sourcePortUsage, out exists); | |||
if (!exists) throw new WireInvalidException("Wire start port not found"); | |||
this.endPort = wire.destinationPortUsage; | |||
this.startPort = wire.sourcePortUsage; | |||
} | |||
/// <summary> | |||
/// The wire's in-game id. | |||
/// </summary> | |||
public EGID Id | |||
{ | |||
get => wireEGID; | |||
} | |||
/// <summary> | |||
/// The wire's signal value, as a float. | |||
/// </summary> | |||
public float Float | |||
{ | |||
get | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return 0f; | |||
return cds.valueAsFloat; | |||
} | |||
set | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return; | |||
cds.valueAsFloat = value; | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's string signal. | |||
/// </summary> | |||
public string String | |||
{ | |||
get | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return ""; | |||
return cds.valueAsEcsString; | |||
} | |||
set | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return; | |||
cds.valueAsEcsString.Set(value); | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's raw string signal. | |||
/// </summary> | |||
public ECSString ECSString | |||
{ | |||
get | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return default; | |||
return cds.valueAsEcsString; | |||
} | |||
set | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return; | |||
cds.valueAsEcsString = value; | |||
} | |||
} | |||
/// <summary> | |||
/// The wire's signal id. | |||
/// I'm 50% sure this is useless. | |||
/// </summary> | |||
public uint SignalId | |||
{ | |||
get | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return uint.MaxValue; | |||
return cds.valueAsID; | |||
} | |||
set | |||
{ | |||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists); | |||
if (!exists) return; | |||
cds.valueAsID = value; | |||
} | |||
} | |||
/// <summary> | |||
/// The block at the beginning of the wire. | |||
/// </summary> | |||
public SignalingBlock Start | |||
{ | |||
get => new SignalingBlock(startBlockEGID); | |||
} | |||
/// <summary> | |||
/// The port number that the beginning of the wire connects to. | |||
/// </summary> | |||
public byte StartPort | |||
{ | |||
get => startPort; | |||
} | |||
/// <summary> | |||
/// The block at the end of the wire. | |||
/// </summary> | |||
public SignalingBlock End | |||
{ | |||
get => new SignalingBlock(endBlockEGID); | |||
} | |||
/// <summary> | |||
/// The port number that the end of the wire connects to. | |||
/// </summary> | |||
public byte EndPort | |||
{ | |||
get => endPort; | |||
} | |||
/// <summary> | |||
/// Create a copy of the wire object where the direction of the wire is guaranteed to be from a block output to a block input. | |||
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input). | |||
/// </summary> | |||
/// <returns>A copy of the wire object.</returns> | |||
public Wire OutputToInputCopy() | |||
{ | |||
return new Wire(wireEGID); | |||
} | |||
/// <summary> | |||
/// Convert the wire object to the direction the signal flows. | |||
/// Signals on wires always flow from a block output port to a block input port. | |||
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input). | |||
/// </summary> | |||
public void OutputToInputInPlace() | |||
{ | |||
if (inputToOutput) | |||
{ | |||
inputToOutput = false; | |||
// swap inputs and outputs | |||
EGID temp = endBlockEGID; | |||
endBlockEGID = startBlockEGID; | |||
startBlockEGID = temp; | |||
temp = endPortEGID; | |||
endPortEGID = startPortEGID; | |||
startPortEGID = temp; | |||
byte tempPortNumber = endPort; | |||
endPort = startPort; | |||
startPort = tempPortNumber; | |||
} | |||
} | |||
public override string ToString() | |||
{ | |||
if (signalEngine.Exists<WireEntityStruct>(wireEGID)) | |||
{ | |||
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {Start.PortName(StartPort, inputToOutput)}) -> ({End.Type}::{EndPort} aka {End.PortName(EndPort, !inputToOutput)})"; | |||
} | |||
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})"; | |||
} | |||
internal static void Init() { } | |||
} | |||
} |
@@ -1,38 +0,0 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
using Svelto.Context; | |||
using Svelto.ECS; | |||
using RobocraftX; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Commands | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.GUI.CommandLine.CommandLineCompositionRoot.Compose<T>() | |||
/// </summary> | |||
// TODO: fix | |||
[HarmonyPatch] | |||
//[HarmonyPatch(typeof(RobocraftX.GUI.CommandLine.CommandLineCompositionRoot))] | |||
//[HarmonyPatch("Compose")] | |||
//[HarmonyPatch("Compose", new Type[] { typeof(UnityContext<FullGameCompositionRoot>), typeof(EnginesRoot), typeof(World), typeof(Action), typeof(MultiplayerInitParameters), typeof(StateSyncRegistrationHelper)})] | |||
static class CommandPatch | |||
{ | |||
public static void Postfix(EnginesRoot enginesRoot) | |||
{ | |||
// When a game is loaded, register the command engines | |||
CommandManager.RegisterEngines(enginesRoot); | |||
} | |||
public static MethodBase TargetMethod(Harmony instance) | |||
{ | |||
return typeof(RobocraftX.GUI.CommandLine.CommandLineCompositionRoot).GetMethod("Compose").MakeGenericMethod(typeof(object)); | |||
//return func.Method; | |||
} | |||
} | |||
} |
@@ -1,42 +0,0 @@ | |||
using System; | |||
using uREPL; | |||
namespace GamecraftModdingAPI.Commands | |||
{ | |||
public static class ExistingCommands | |||
{ | |||
public static void Call(string commandName) | |||
{ | |||
RuntimeCommands.Call(commandName); | |||
} | |||
public static void Call<Arg0>(string commandName, Arg0 arg0) | |||
{ | |||
RuntimeCommands.Call<Arg0>(commandName, arg0); | |||
} | |||
public static void Call<Arg0, Arg1>(string commandName, Arg0 arg0, Arg1 arg1) | |||
{ | |||
RuntimeCommands.Call<Arg0, Arg1>(commandName, arg0, arg1); | |||
} | |||
public static void Call<Arg0, Arg1, Arg2>(string commandName, Arg0 arg0, Arg1 arg1, Arg2 arg2) | |||
{ | |||
RuntimeCommands.Call<Arg0, Arg1, Arg2>(commandName, arg0, arg1, arg2); | |||
} | |||
public static bool Exists(string commandName) | |||
{ | |||
return RuntimeCommands.HasRegistered(commandName); | |||
} | |||
public static string[] GetCommandNames() | |||
{ | |||
var keys = RuntimeCommands.table.Keys; | |||
string[] res = new string[keys.Count]; | |||
keys.CopyTo(res, 0); | |||
return res; | |||
} | |||
} | |||
} |
@@ -1,47 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using HarmonyLib; | |||
using Svelto.ECS; | |||
using RobocraftX.Common; | |||
using RobocraftX.StateSync; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.StateSync.DeterministicStepCompositionRoot.ComposeEnginesGroups(...) | |||
/// </summary> | |||
//[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "DeterministicCompose")] | |||
[Obsolete] | |||
[HarmonyPatch] | |||
class GameHostTransitionDeterministicGroupEnginePatch | |||
{ | |||
public static readonly GameStateBuildEmitterEngine buildEngine = new GameStateBuildEmitterEngine(); | |||
public static readonly GameStateSimulationEmitterEngine simEngine = new GameStateSimulationEmitterEngine(); | |||
public static void Postfix() | |||
{ | |||
//stateSyncReg.buildModeInitializationEngines.Add(buildEngine); | |||
//stateSyncReg.simulationModeInitializationEngines.Add(simEngine); | |||
//enginesRoot.AddEngine(buildEngine); | |||
//enginesRoot.AddEngine(simEngine); | |||
buildEngine.EmitIfBuildMode(); | |||
simEngine.EmitIfSimMode(); | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase TargetMethod(Harmony harmonyInstance) | |||
{ | |||
return AccessTools.Method(AccessTools.TypeByName("RobocraftX.StateSync.GameHostTransitionDeterministicGroupEngine"), "EndTransition"); | |||
//.MakeGenericMethod(typeof(CosmeticEnginesSequenceBuildOrder), typeof(CosmeticEnginesSequenceSimOrder), typeof(DeterministicToCosmeticSyncBuildOrder), typeof(DeterministicToCosmeticSyncSimOrder)); | |||
} | |||
} | |||
} |
@@ -1,106 +0,0 @@ | |||
using System; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
[Obsolete] | |||
public class EmitterBuilder | |||
{ | |||
private string name; | |||
private int? type; | |||
/// <summary> | |||
/// Create a new event emitter builder. | |||
/// </summary> | |||
public EmitterBuilder() | |||
{ | |||
} | |||
/// <summary> | |||
/// Create a new event emitter builder. | |||
/// This is equivalent to new <code>EmitterBuilder().Name(name)</code> | |||
/// </summary> | |||
/// <param name="name">The emitter name.</param> | |||
public EmitterBuilder(string name) | |||
{ | |||
this.name = name; | |||
} | |||
/// <summary> | |||
/// Create and return an event emitter builder. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
public static EmitterBuilder Builder() | |||
{ | |||
return new EmitterBuilder(); | |||
} | |||
/// <summary> | |||
/// Create and return an event emitter builder. | |||
/// This is equivalent to <code>Builder().Name(name)</code> | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="name">The emitter name.</param> | |||
public static EmitterBuilder Builder(string name) | |||
{ | |||
return new EmitterBuilder(name); | |||
} | |||
/// <summary> | |||
/// Name the event emitter. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="name">The event emitter name.</param> | |||
public EmitterBuilder Name(string name) | |||
{ | |||
this.name = name; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Set the type of event to handle. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="eventType">The event type.</param> | |||
public EmitterBuilder Handle(EventType eventType) | |||
{ | |||
return Handle((int)eventType); | |||
} | |||
/// <summary> | |||
/// Set the type of event to handle. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="eventType">The event type.</param> | |||
public EmitterBuilder Handle(int eventType) | |||
{ | |||
this.type = eventType; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Build the event emitter. | |||
/// </summary> | |||
/// <returns>The event emitter.</returns> | |||
/// <param name="register">Automatically register the event emitter with EventManager.AddEventemitter().</param> | |||
public IEventEmitterEngine Build(bool register = true) | |||
{ | |||
if (string.IsNullOrWhiteSpace(name)) | |||
{ | |||
throw new EventParameterMissingException("Event emitter name must be defined before Build() is called"); | |||
} | |||
if (!type.HasValue) | |||
{ | |||
throw new EventParameterMissingException("Event emitter event type must be defined before Build() is called"); | |||
} | |||
SimpleEventEmitterEngine result = new SimpleEventEmitterEngine(type.Value, name); | |||
if (register) | |||
{ | |||
EventManager.AddEventEmitter(result); | |||
} | |||
return result; | |||
} | |||
} | |||
} |
@@ -1,61 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Convenient factories for mod event engines | |||
/// </summary> | |||
[Obsolete] | |||
public static class EventEngineFactory | |||
{ | |||
/// <summary> | |||
/// Factory method which automatically adds the SimpleEventHandlerEngine to the Manager | |||
/// </summary> | |||
/// <param name="name">The name of the engine</param> | |||
/// <param name="type">The type of event to handle</param> | |||
/// <param name="onActivated">The operation to do when the event is created</param> | |||
/// <param name="onDestroyed">The operation to do when the event is destroyed (if applicable)</param> | |||
/// <returns>The created object</returns> | |||
public static SimpleEventHandlerEngine CreateAddSimpleHandler(string name, int type, Action onActivated, Action onDestroyed) | |||
{ | |||
var engine = new SimpleEventHandlerEngine(onActivated, onDestroyed, type, name); | |||
EventManager.AddEventHandler(engine); | |||
return engine; | |||
} | |||
/// <summary> | |||
/// Factory method which automatically adds the SimpleEventHandlerEngine to the Manager | |||
/// </summary> | |||
/// <param name="name">The name of the engine</param> | |||
/// <param name="type">The type of event to handle</param> | |||
/// <param name="onActivated">The operation to do when the event is created</param> | |||
/// <param name="onDestroyed">The operation to do when the event is destroyed (if applicable)</param> | |||
/// <returns>The created object</returns> | |||
public static SimpleEventHandlerEngine CreateAddSimpleHandler(string name, int type, Action<EntitiesDB> onActivated, Action<EntitiesDB> onDestroyed) | |||
{ | |||
var engine = new SimpleEventHandlerEngine(onActivated, onDestroyed, type, name); | |||
EventManager.AddEventHandler(engine); | |||
return engine; | |||
} | |||
/// <summary> | |||
/// Factory method which automatically adds the SimpleEventEmitterEngine to the Manager | |||
/// </summary> | |||
/// <param name="name">The name of the engine</param> | |||
/// <param name="type">The type of event to emit</param> | |||
/// <param name="isRemovable">Will removing this engine not break your code?</param> | |||
/// <returns>The created object</returns> | |||
public static SimpleEventEmitterEngine CreateAddSimpleEmitter(string name, int type, bool isRemovable = true) | |||
{ | |||
var engine = new SimpleEventEmitterEngine(type, name, isRemovable); | |||
EventManager.AddEventEmitter(engine); | |||
return engine; | |||
} | |||
} | |||
} |
@@ -1,66 +0,0 @@ | |||
using System; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
public class EventException : GamecraftModdingAPIException | |||
{ | |||
public EventException() | |||
{ | |||
} | |||
public EventException(string message) : base(message) | |||
{ | |||
} | |||
public EventException(string message, Exception innerException) : base(message, innerException) | |||
{ | |||
} | |||
} | |||
public class EventNotFoundException : EventException | |||
{ | |||
public EventNotFoundException() | |||
{ | |||
} | |||
public EventNotFoundException(string message) : base(message) | |||
{ | |||
} | |||
} | |||
public class EventAlreadyExistsException : EventException | |||
{ | |||
public EventAlreadyExistsException() | |||
{ | |||
} | |||
public EventAlreadyExistsException(string message) : base(message) | |||
{ | |||
} | |||
} | |||
public class EventRuntimeException : EventException | |||
{ | |||
public EventRuntimeException() | |||
{ | |||
} | |||
public EventRuntimeException(string message) : base(message) | |||
{ | |||
} | |||
public EventRuntimeException(string message, Exception innerException) : base(message, innerException) | |||
{ | |||
} | |||
} | |||
public class EventParameterMissingException : EventException | |||
{ | |||
public EventParameterMissingException() | |||
{ | |||
} | |||
public EventParameterMissingException(string message) : base(message) | |||
{ | |||
} | |||
} | |||
} |
@@ -1,129 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Keeps track of event handlers and emitters. | |||
/// This is used to add, remove and get API event handlers and emitters. | |||
/// </summary> | |||
[Obsolete("This will be removed in an upcoming update. Use the new C# event architecture from GamecraftModdingAPI.App")] | |||
public static class EventManager | |||
{ | |||
private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>(); | |||
private static Dictionary<string, IEventHandlerEngine> _eventHandlers = new Dictionary<string, IEventHandlerEngine>(); | |||
private static EnginesRoot _lastEngineRoot; | |||
// event handler management | |||
public static void AddEventHandler(IEventHandlerEngine engine) | |||
{ | |||
if (ExistsEventHandler(engine)) | |||
{ | |||
throw new EventAlreadyExistsException($"IEventHandlerEngine {engine.Name} already exists"); | |||
} | |||
_eventHandlers[engine.Name] = engine; | |||
if (_lastEngineRoot != null) | |||
{ | |||
Logging.MetaDebugLog($"Registering IEventHandlerEngine {engine.Name}"); | |||
_lastEngineRoot.AddEngine(engine); | |||
} | |||
} | |||
public static bool ExistsEventHandler(string name) | |||
{ | |||
return _eventHandlers.ContainsKey(name); | |||
} | |||
public static bool ExistsEventHandler(IEventHandlerEngine engine) | |||
{ | |||
return ExistsEventHandler(engine.Name); | |||
} | |||
public static IEventHandlerEngine GetEventHandler(string name) | |||
{ | |||
return _eventHandlers[name]; | |||
} | |||
public static string[] GetEventHandlerNames() | |||
{ | |||
return _eventHandlers.Keys.ToArray(); | |||
} | |||
public static void RemoveEventHandler(string name) | |||
{ | |||
_eventHandlers.Remove(name); | |||
} | |||
// event emitter management | |||
public static void AddEventEmitter(IEventEmitterEngine engine) | |||
{ | |||
if (ExistsEventEmitter(engine)) | |||
{ | |||
throw new EventAlreadyExistsException($"IEventEmitterEngine {engine.Name} already exists"); | |||
} | |||
_eventEmitters[engine.Name] = engine; | |||
if (_lastEngineRoot != null) | |||
{ | |||
Logging.MetaDebugLog($"Registering IEventEmitterEngine {engine.Name}"); | |||
_lastEngineRoot.AddEngine(engine); | |||
} | |||
} | |||
public static bool ExistsEventEmitter(string name) | |||
{ | |||
return _eventEmitters.ContainsKey(name); | |||
} | |||
public static bool ExistsEventEmitter(IEventEmitterEngine engine) | |||
{ | |||
return ExistsEventEmitter(engine.Name); | |||
} | |||
public static IEventEmitterEngine GetEventEmitter(string name) | |||
{ | |||
return _eventEmitters[name]; | |||
} | |||
public static string[] GetEventEmitterNames() | |||
{ | |||
return _eventEmitters.Keys.ToArray(); | |||
} | |||
public static void RemoveEventEmitter(string name) | |||
{ | |||
if (_eventEmitters[name].isRemovable) | |||
{ | |||
_eventEmitters.Remove(name); | |||
} | |||
} | |||
public static void RegisterEngines(EnginesRoot enginesRoot) | |||
{ | |||
_lastEngineRoot = enginesRoot; | |||
// Register handlers before emitters so no events are missed | |||
var entityFactory = enginesRoot.GenerateEntityFactory(); | |||
foreach (var key in _eventHandlers.Keys) | |||
{ | |||
Logging.MetaDebugLog($"Registering IEventHandlerEngine {_eventHandlers[key].Name}"); | |||
enginesRoot.AddEngine(_eventHandlers[key]); | |||
} | |||
foreach (var key in _eventEmitters.Keys) | |||
{ | |||
Logging.MetaDebugLog($"Registering IEventEmitterEngine {_eventEmitters[key].Name}"); | |||
_eventEmitters[key].Factory = entityFactory; | |||
enginesRoot.AddEngine(_eventEmitters[key]); | |||
} | |||
} | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Built-in event types. | |||
/// These are configured to fire when the API is initialized. | |||
/// </summary> | |||
public enum EventType | |||
{ | |||
ApplicationInitialized, | |||
Menu, | |||
MenuSwitchedTo, | |||
Game, | |||
GameReloaded, | |||
GameSwitchedTo, | |||
SimulationSwitchedTo, | |||
BuildSwitchedTo | |||
} | |||
} |
@@ -1,59 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using HarmonyLib; | |||
using RobocraftX.CR.MainGame; | |||
using Svelto.ECS; | |||
using Unity.Entities; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame() | |||
/// </summary> | |||
[Obsolete] | |||
[HarmonyPatch] | |||
class GameActivatedComposePatch | |||
{ | |||
public static bool IsGameSwitching = false; | |||
public static bool IsGameReloading = false; | |||
public static void Postfix(ref object contextHolder, ref EnginesRoot enginesRoot, World physicsWorld) | |||
{ | |||
// register custom game engines | |||
GameEngineManager.RegisterEngines(enginesRoot); | |||
// initialize AsyncUtils | |||
AsyncUtils.Setup(enginesRoot); | |||
// A new EnginesRoot is always created when ActivateGame is called | |||
// so all event emitters and handlers must be re-registered. | |||
EventManager.RegisterEngines(enginesRoot); | |||
Logging.Log("Dispatching Game Activated event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIGameActivatedEventEmitter").Emit(); | |||
if (IsGameSwitching) | |||
{ | |||
IsGameSwitching = false; | |||
Logging.Log("Dispatching Game Switched To event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIGameSwitchedToEventEmitter").Emit(); | |||
} | |||
if (IsGameReloading) | |||
{ | |||
IsGameReloading = false; | |||
Logging.Log("Dispatching Game Reloaded event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIGameReloadedEventEmitter").Emit(); | |||
} | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return typeof(MainGameCompositionRoot).GetMethods().First(m => m.Name == "Compose") | |||
.MakeGenericMethod(typeof(object)); | |||
} | |||
} | |||
} |
@@ -1,26 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.FullGameCompositionRoot.ReloadGame() | |||
/// </summary> | |||
[Obsolete] | |||
[HarmonyPatch(typeof(FullGameCompositionRoot), "ReloadGame")] | |||
class GameReloadedPatch | |||
{ | |||
public static void Postfix() | |||
{ | |||
GameActivatedComposePatch.IsGameReloading = true; | |||
} | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
using System; | |||
using Unity.Jobs; | |||
using RobocraftX.SimulationModeState; | |||
using RobocraftX.StateSync; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Event emitter engine for switching to to build mode. | |||
/// </summary> | |||
[Obsolete] | |||
public class GameStateBuildEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeStoppedModeEntered | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIGameStateBuildEventEmitter" ; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public int type { get; } = (int)EventType.BuildSwitchedTo; | |||
public bool isRemovable { get; } = false; | |||
public IEntityFactory Factory { set; private get; } | |||
public void Dispose() { } | |||
public void Emit() | |||
{ | |||
Logging.Log("Dispatching Build Switched To event"); | |||
if (Factory == null) { return; } | |||
Factory.BuildEntity<ModEventEntityDescriptor>(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup) | |||
.Init(new ModEventEntityStruct { type = type }); | |||
} | |||
public void EmitIfBuildMode() | |||
{ | |||
//Logging.MetaDebugLog($"nextSimulationMode: {entitiesDB.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).nextSimulationMode}"); | |||
if (entitiesDB.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).nextSimulationMode == SimulationMode.TimeStopped) | |||
{ | |||
Emit(); | |||
} | |||
} | |||
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps) | |||
{ | |||
Emit(); | |||
return inputDeps; | |||
} | |||
public void Ready() { } | |||
} | |||
} |
@@ -1,54 +0,0 @@ | |||
using System; | |||
using Unity.Jobs; | |||
using RobocraftX.SimulationModeState; | |||
using RobocraftX.StateSync; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Event emitter engine for switching to simulation mode. | |||
/// </summary> | |||
[Obsolete] | |||
public class GameStateSimulationEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeRunningModeEntered | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIGameStateSimulationEventEmitter" ; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public int type { get; } = (int)EventType.SimulationSwitchedTo; | |||
public bool isRemovable { get; } = false; | |||
public IEntityFactory Factory { set; private get; } | |||
public void Dispose() { } | |||
public void Emit() | |||
{ | |||
Logging.Log("Dispatching Simulation Switched To event"); | |||
if (Factory == null) { return; } | |||
Factory.BuildEntity<ModEventEntityDescriptor>(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup) | |||
.Init(new ModEventEntityStruct { type = type }); | |||
} | |||
public void EmitIfSimMode() | |||
{ | |||
if (entitiesDB.QueryUniqueEntity<SimulationModeStateEntityStruct>(SimulationModeStateExclusiveGroups.GAME_STATE_GROUP).nextSimulationMode == SimulationMode.TimeRunning) | |||
{ | |||
Emit(); | |||
} | |||
} | |||
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps) | |||
{ | |||
Emit(); | |||
return inputDeps; | |||
} | |||
public void Ready() { } | |||
} | |||
} |
@@ -1,30 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using RobocraftX.CR.MainGame; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame() | |||
/// (scheduled for execution during RobocraftX.FullGameCompositionRoot.SwitchToGame()) | |||
/// </summary> | |||
[Obsolete] | |||
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToGame")] | |||
class GameSwitchedToPatch | |||
{ | |||
public static void Prefix() | |||
{ | |||
GameActivatedComposePatch.IsGameSwitching = true; | |||
} | |||
} | |||
} |
@@ -1,166 +0,0 @@ | |||
using System; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
[Obsolete] | |||
public class HandlerBuilder | |||
{ | |||
private string name; | |||
private int? type; | |||
private Action<EntitiesDB> activated; | |||
private Action<EntitiesDB> destroyed; | |||
/// <summary> | |||
/// Create a new event handler builder. | |||
/// </summary> | |||
public HandlerBuilder() | |||
{ | |||
} | |||
/// <summary> | |||
/// Create a new event handler builder. | |||
/// This is equivalent to new <code>HandlerBuilder().Name(name)</code> | |||
/// </summary> | |||
/// <param name="name">The handler name.</param> | |||
public HandlerBuilder(string name) | |||
{ | |||
this.name = name; | |||
} | |||
/// <summary> | |||
/// Create and return an event handler builder. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
public static HandlerBuilder Builder() | |||
{ | |||
return new HandlerBuilder(); | |||
} | |||
/// <summary> | |||
/// Create and return an event handler builder. | |||
/// This is equivalent to <code>Builder().Name(name)</code> | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="name">The handler name.</param> | |||
public static HandlerBuilder Builder(string name) | |||
{ | |||
return new HandlerBuilder(name); | |||
} | |||
/// <summary> | |||
/// Name the event handler. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="name">The event handler name.</param> | |||
public HandlerBuilder Name(string name) | |||
{ | |||
this.name = name; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Set the action to perform on when the activated event occurs. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="action">The activated event action.</param> | |||
public HandlerBuilder OnActivation(Action action) | |||
{ | |||
return OnActivation((_) => { action(); }); | |||
} | |||
/// <summary> | |||
/// Set the action to perform on when the activated event occurs. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="action">The activated event action.</param> | |||
public HandlerBuilder OnActivation(Action<EntitiesDB> action) | |||
{ | |||
this.activated = action; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Set the action to perform when the destroyed event occurs. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="action">The destroyed event action.</param> | |||
public HandlerBuilder OnDestruction(Action action) | |||
{ | |||
return OnDestruction((_) => { action(); }); | |||
} | |||
/// <summary> | |||
/// Set the action to perform when the destroyed event occurs. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="action">The destroyed event action.</param> | |||
public HandlerBuilder OnDestruction(Action<EntitiesDB> action) | |||
{ | |||
this.destroyed = action; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Set the type of event to handle. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="eventType">The event type.</param> | |||
public HandlerBuilder Handle(EventType eventType) | |||
{ | |||
return Handle((int)eventType); | |||
} | |||
/// <summary> | |||
/// Set the type of event to handle. | |||
/// </summary> | |||
/// <returns>The builder.</returns> | |||
/// <param name="eventType">The event type.</param> | |||
public HandlerBuilder Handle(int eventType) | |||
{ | |||
this.type = eventType; | |||
return this; | |||
} | |||
/// <summary> | |||
/// Build the event handler. | |||
/// </summary> | |||
/// <returns>The event handler.</returns> | |||
/// <param name="register">Automatically register the event handler with EventManager.AddEventHandler().</param> | |||
public IEventHandlerEngine Build(bool register = true) | |||
{ | |||
if (string.IsNullOrWhiteSpace(name)) | |||
{ | |||
throw new EventParameterMissingException("Event handler name must be defined before Build() is called"); | |||
} | |||
if (activated == null && destroyed == null) | |||
{ | |||
throw new EventParameterMissingException("Event handler destruction or activated event action must be defined before Build() is called"); | |||
} | |||
if (!type.HasValue) | |||
{ | |||
throw new EventParameterMissingException("Event handler event type must be defined before Build() is called"); | |||
} | |||
Action<EntitiesDB> validActivated = activated; | |||
if (validActivated == null) | |||
{ | |||
validActivated = (_) => { }; | |||
} | |||
Action<EntitiesDB> validDestroyed = destroyed; | |||
if (validDestroyed == null) | |||
{ | |||
validDestroyed = (_) => { }; | |||
} | |||
SimpleEventHandlerEngine result = new SimpleEventHandlerEngine(validActivated, validDestroyed, type.Value, name); | |||
if (register) | |||
{ | |||
EventManager.AddEventHandler(result); | |||
} | |||
return result; | |||
} | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Engine interface to create a ModEventEntityStruct in entitiesDB when a specific event occurs. | |||
/// </summary> | |||
[Obsolete] | |||
public interface IEventEmitterEngine : IFactoryEngine | |||
{ | |||
/// <summary> | |||
/// Emit the event. (Optional) | |||
/// </summary> | |||
void Emit(); | |||
} | |||
} |
@@ -1,21 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Internal; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines. | |||
/// </summary> | |||
[Obsolete] | |||
public interface IEventHandlerEngine : IReactionaryEngine<ModEventEntityStruct> | |||
{ | |||
} | |||
} |
@@ -1,43 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateMenu() | |||
/// </summary> | |||
[Obsolete] | |||
[HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")] | |||
class MenuActivatedPatch | |||
{ | |||
private static bool firstLoad = true; | |||
public static void Postfix(ref EnginesRoot ____frontEndEnginesRoot, FullGameCompositionRoot __instance) | |||
{ | |||
// register custom menu engines | |||
MenuEngineManager.RegisterEngines(____frontEndEnginesRoot); | |||
// A new EnginesRoot is always created when ActivateMenu is called | |||
// so all event emitters and handlers must be re-registered. | |||
EventManager.RegisterEngines(____frontEndEnginesRoot); | |||
if (firstLoad) | |||
{ | |||
firstLoad = false; | |||
FullGameFields.Init(__instance); | |||
//Application.Application.SetFullGameCompositionRoot(__instance); | |||
Logging.Log("Dispatching App Init event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIApplicationInitializedEventEmitter").Emit(); | |||
} | |||
Logging.Log("Dispatching Menu Activated event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIMenuActivatedEventEmitter").Emit(); | |||
} | |||
} | |||
} |
@@ -1,29 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using HarmonyLib; | |||
using RobocraftX; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// Patch of RobocraftX.FullGameCompositionRoot.SwitchToMenu() | |||
/// </summary> | |||
[Obsolete] | |||
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToMenu")] | |||
class MenuSwitchedToPatch | |||
{ | |||
public static void Postfix() | |||
{ | |||
// Event emitters and handlers should already be registered by MenuActivated event | |||
Logging.Log("Dispatching Menu Switched To event"); | |||
EventManager.GetEventEmitter("GamecraftModdingAPIMenuSwitchedToEventEmitter").Emit(); | |||
} | |||
} | |||
} |
@@ -1,17 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// EntityDescriptor for creating ModEventEntityStructs | |||
/// </summary> | |||
public class ModEventEntityDescriptor : GenericEntityDescriptor<ModEventEntityStruct> | |||
{ | |||
} | |||
} |
@@ -1,23 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// The event entity struct | |||
/// </summary> | |||
public struct ModEventEntityStruct : IEntityComponent, INeedEGID | |||
{ | |||
/// <summary> | |||
/// The type of event that has been emitted | |||
/// </summary> | |||
public int type; | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -1,66 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// A simple implementation of IEventEmitterEngine sufficient for most uses | |||
/// </summary> | |||
[Obsolete] | |||
public class SimpleEventEmitterEngine : IEventEmitterEngine | |||
{ | |||
public string Name { get; set; } | |||
public int type { get; set; } | |||
public bool isRemovable { get; } | |||
public IEntityFactory Factory { private get; set; } | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Ready() { } | |||
/// <summary> | |||
/// Emit the event | |||
/// </summary> | |||
public void Emit() | |||
{ | |||
Factory.BuildEntity<ModEventEntityDescriptor>(ApiExclusiveGroups.eventID++, ApiExclusiveGroups.eventsExclusiveGroup) | |||
.Init(new ModEventEntityStruct { type = type }); | |||
} | |||
public void Dispose() { } | |||
/// <summary> | |||
/// Construct the engine | |||
/// </summary> | |||
/// <param name="type">The EventType to use for ModEventEntityStruct.type</param> | |||
/// <param name="name">The name of this engine</param> | |||
/// <param name="isRemovable">Will removing this engine not break your code?</param> | |||
public SimpleEventEmitterEngine(EventType type, string name, bool isRemovable = true) | |||
{ | |||
this.type = (int)type; | |||
this.Name = name; | |||
this.isRemovable = isRemovable; | |||
} | |||
/// <summary> | |||
/// Construct the engine | |||
/// </summary> | |||
/// <param name="type">The object to use for ModEventEntityStruct.type</param> | |||
/// <param name="name">The name of this engine</param> | |||
/// <param name="isRemovable">Will removing this engine not break your code?</param> | |||
public SimpleEventEmitterEngine(int type, string name, bool isRemovable = true) | |||
{ | |||
this.type = type; | |||
this.Name = name; | |||
this.isRemovable = isRemovable; | |||
} | |||
} | |||
} |
@@ -1,146 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Events | |||
{ | |||
/// <summary> | |||
/// A simple implementation of IEventHandlerEngine sufficient for most uses | |||
/// </summary> | |||
[Obsolete] | |||
public class SimpleEventHandlerEngine : IEventHandlerEngine | |||
{ | |||
public int type { get; set; } | |||
public string Name { get; set; } | |||
private bool isActivated = false; | |||
private bool jankActivateFix = false; | |||
private bool jankDestroyFix = false; | |||
private readonly Action<EntitiesDB> onActivated; | |||
private readonly Action<EntitiesDB> onDestroyed; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => true; | |||
public void Add(ref ModEventEntityStruct entityView, EGID egid) | |||
{ | |||
if (entityView.type.Equals(this.type)) | |||
{ | |||
jankActivateFix = !jankActivateFix; | |||
if (jankActivateFix) return; | |||
isActivated = true; | |||
onActivatedInvokeCatchError(entitiesDB); | |||
} | |||
} | |||
/// <summary> | |||
/// Manually activate the EventHandler. | |||
/// Once activated, the next remove event will not be ignored. | |||
/// </summary> | |||
/// <param name="handle">Whether to invoke the activated action</param> | |||
public void Activate(bool handle = false) | |||
{ | |||
isActivated = true; | |||
if (handle && entitiesDB != null) | |||
{ | |||
onActivatedInvokeCatchError(entitiesDB); | |||
} | |||
} | |||
public void Deactivate() | |||
{ | |||
isActivated = false; | |||
} | |||
public void Ready() { } | |||
public void Remove(ref ModEventEntityStruct entityView, EGID egid) | |||
{ | |||
if (entityView.type.Equals(this.type) && isActivated) | |||
{ | |||
jankDestroyFix = !jankDestroyFix; | |||
if (jankDestroyFix) return; | |||
isActivated = false; | |||
onDestroyedInvokeCatchError(entitiesDB); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
if (isActivated) | |||
{ | |||
isActivated = false; | |||
onDestroyedInvokeCatchError(entitiesDB); | |||
} | |||
} | |||
/// <summary> | |||
/// Construct the engine | |||
/// </summary> | |||
/// <param name="activated">The operation to do when the event is created</param> | |||
/// <param name="removed">The operation to do when the event is destroyed (if applicable)</param> | |||
/// <param name="type">The type of event to handle</param> | |||
/// <param name="name">The name of the engine</param> | |||
/// <param name="simple">A useless parameter to use to avoid Python overload resolution errors</param> | |||
public SimpleEventHandlerEngine(Action activated, Action removed, int type, string name, bool simple = true) | |||
: this((EntitiesDB _) => { activated.Invoke(); }, (EntitiesDB _) => { removed.Invoke(); }, type, name) { } | |||
/// <summary> | |||
/// Construct the engine | |||
/// </summary> | |||
/// <param name="activated">The operation to do when the event is created</param> | |||
/// <param name="removed">The operation to do when the event is destroyed (if applicable)</param> | |||
/// <param name="type">The type of event to handler</param> | |||
/// <param name="name">The name of the engine</param> | |||
public SimpleEventHandlerEngine(Action<EntitiesDB> activated, Action<EntitiesDB> removed, int type, string name) | |||
{ | |||
this.type = type; | |||
this.Name = name; | |||
this.onActivated = activated; | |||
this.onDestroyed = removed; | |||
} | |||
private void onActivatedInvokeCatchError(EntitiesDB _entitiesDB) | |||
{ | |||
try | |||
{ | |||
onActivated.Invoke(_entitiesDB); | |||
} | |||
catch (Exception e) | |||
{ | |||
EventRuntimeException wrappedException = new EventRuntimeException($"EventHandler {Name} threw an exception when activated", e); | |||
Logging.LogWarning(wrappedException.ToString()); | |||
} | |||
} | |||
private void onDestroyedInvokeCatchError(EntitiesDB _entitiesDB) | |||
{ | |||
try | |||
{ | |||
onDestroyed.Invoke(_entitiesDB); | |||
} | |||
catch (Exception e) | |||
{ | |||
EventRuntimeException wrappedException = new EventRuntimeException($"EventHandler {Name} threw an exception when destroyed", e); | |||
Logging.LogWarning(wrappedException.ToString()); | |||
} | |||
} | |||
public SimpleEventHandlerEngine(Action activated, Action removed, EventType type, string name, bool simple = true) | |||
: this((EntitiesDB _) => { activated.Invoke(); }, (EntitiesDB _) => { removed.Invoke(); }, (int)type, name) { } | |||
public SimpleEventHandlerEngine(Action<EntitiesDB> activated, Action<EntitiesDB> removed, EventType type, string name, bool simple = true) | |||
: this(activated, removed, (int)type, name) { } | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
using System; | |||
using System.Runtime.Serialization; | |||
namespace GamecraftModdingAPI | |||
{ | |||
public class GamecraftModdingAPIException : Exception | |||
{ | |||
public GamecraftModdingAPIException() | |||
{ | |||
} | |||
public GamecraftModdingAPIException(string message) : base(message) | |||
{ | |||
} | |||
public GamecraftModdingAPIException(string message, Exception innerException) : base(message, innerException) | |||
{ | |||
} | |||
protected GamecraftModdingAPIException(SerializationInfo info, StreamingContext context) : base(info, context) | |||
{ | |||
} | |||
} | |||
} |
@@ -1,65 +0,0 @@ | |||
using System; | |||
using RobocraftX.Common.Input; | |||
using RobocraftX.Players; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Input | |||
{ | |||
public class FakeInputEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIFakeInputEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsReady = false; | |||
public void Dispose() | |||
{ | |||
IsReady = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsReady = true; | |||
} | |||
public bool SendCustomInput(LocalInputEntityStruct input, uint playerID, bool remote = false) | |||
{ | |||
EGID egid = new EGID(playerID, remote ? InputExclusiveGroups.RemotePlayers : InputExclusiveGroups.LocalPlayers); | |||
if (entitiesDB.Exists<LocalInputEntityStruct>(egid)) | |||
{ | |||
ref LocalInputEntityStruct ies = ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid); | |||
ies = input; | |||
return true; | |||
} | |||
else return false; | |||
} | |||
public LocalInputEntityStruct GetInput(uint playerID, bool remote = false) | |||
{ | |||
EGID egid = new EGID(playerID, remote ? InputExclusiveGroups.RemotePlayers : InputExclusiveGroups.LocalPlayers); | |||
if (entitiesDB.Exists<LocalInputEntityStruct>(egid)) | |||
{ | |||
return entitiesDB.QueryEntity<LocalInputEntityStruct>(egid); | |||
} | |||
else return default(LocalInputEntityStruct); | |||
} | |||
public ref LocalInputEntityStruct GetInputRef(uint playerID, bool remote = false) | |||
{ | |||
EGID egid = new EGID(playerID, remote ? InputExclusiveGroups.RemotePlayers : InputExclusiveGroups.LocalPlayers); | |||
return ref entitiesDB.QueryEntity<LocalInputEntityStruct>(egid); | |||
} | |||
public uint GetLocalPlayerID() | |||
{ | |||
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB); | |||
} | |||
} | |||
} |
@@ -1,47 +0,0 @@ | |||
using System; | |||
using RobocraftX.Common.Input; | |||
using RobocraftX.Multiplayer.Input; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using HarmonyLib; | |||
namespace GamecraftModdingAPI.Inventory | |||
{ | |||
public static class Hotbar | |||
{ | |||
private static readonly HotbarEngine hotbarEngine = new HotbarEngine(); | |||
/// <summary> | |||
/// Switch the block in the player's hand | |||
/// </summary> | |||
/// <param name="block">The block to switch to.</param> | |||
/// <param name="playerID">The player. Omit this to use the local player.</param> | |||
public static void EquipBlock(BlockIDs block, uint playerID = uint.MaxValue) | |||
{ | |||
if (playerID == uint.MaxValue) | |||
{ | |||
playerID = hotbarEngine.GetLocalPlayerID(); | |||
} | |||
hotbarEngine.SelectBlock((int) block, playerID); | |||
// cubeSelectedByPick = true will crash the game | |||
// (this would be equivalent to mouse middle click pick block action) | |||
// reason: the game expects a Dictionary entry for the tweaked stats | |||
} | |||
/// <summary> | |||
/// Gets the block in the player's hand | |||
/// </summary> | |||
/// <returns>The equipped block.</returns> | |||
public static BlockIDs GetEquippedBlock() | |||
{ | |||
return HotbarSlotSelectionHandlerEnginePatch.EquippedPartID; | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(hotbarEngine); | |||
} | |||
} | |||
} |
@@ -1,58 +0,0 @@ | |||
using System; | |||
using RobocraftX.Character; | |||
using RobocraftX.GUI.Hotbar; | |||
using RobocraftX.Players; | |||
using RobocraftX.Common; | |||
using RobocraftX.Common.Input; | |||
using RobocraftX.Common.Players; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Inventory | |||
{ | |||
public class HotbarEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIHotbarGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsInGame = false; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
public bool SelectBlock(int block, uint playerID, bool cubeSelectedByPick = false) | |||
{ | |||
var inputs = entitiesDB.QueryEntities<LocalInputEntityStruct>(InputExclusiveGroups.LocalPlayers).ToBuffer(); | |||
if (inputs.count == 0) return false; | |||
for (int i = 0; i < inputs.count; i++) | |||
{ | |||
if (inputs.buffer[i].ID.entityID == playerID) { | |||
inputs.buffer[i].cubeSelectedByPick = cubeSelectedByPick; | |||
inputs.buffer[i].selectedCube = block; | |||
return true; | |||
} | |||
} | |||
// TODO: expose the rest of the input functionality | |||
return false; | |||
} | |||
public uint GetLocalPlayerID() | |||
{ | |||
return LocalPlayerIDUtility.GetLocalPlayerID(entitiesDB); | |||
} | |||
} | |||
} |
@@ -1,446 +0,0 @@ | |||
using System; | |||
using Gamecraft.GUI.Blueprints; | |||
using Unity.Mathematics; | |||
using RobocraftX.Common; | |||
using RobocraftX.Common.Players; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Players; | |||
using GamecraftModdingAPI.Blocks; | |||
namespace GamecraftModdingAPI | |||
{ | |||
/// <summary> | |||
/// An in-game player character. Any Leo you see is a player. | |||
/// </summary> | |||
public class Player : IEquatable<Player>, IEquatable<EGID> | |||
{ | |||
// static functionality | |||
private static PlayerEngine playerEngine = new PlayerEngine(); | |||
private static Player localPlayer; | |||
/// <summary> | |||
/// Checks if the specified player exists. | |||
/// </summary> | |||
/// <returns>Whether the player exists.</returns> | |||
/// <param name="player">Player type.</param> | |||
public static bool Exists(PlayerType player) | |||
{ | |||
switch (player) | |||
{ | |||
case PlayerType.Remote: | |||
return playerEngine.GetRemotePlayer() != uint.MaxValue; | |||
case PlayerType.Local: | |||
return playerEngine.GetLocalPlayer() != uint.MaxValue; | |||
} | |||
return false; | |||
} | |||
/// <summary> | |||
/// Checks if the specified player exists. | |||
/// </summary> | |||
/// <returns>Whether the player exists.</returns> | |||
/// <param name="player">The player's unique identifier.</param> | |||
public static bool Exists(uint player) | |||
{ | |||
return playerEngine.ExistsById(player); | |||
} | |||
/// <summary> | |||
/// The amount of Players in the current game. | |||
/// </summary> | |||
/// <returns>The count.</returns> | |||
public static uint Count() | |||
{ | |||
return (uint) playerEngine.GetAllPlayerCount(); | |||
} | |||
/// <summary> | |||
/// Returns the current player belonging to this client. | |||
/// </summary> | |||
public static Player LocalPlayer | |||
{ | |||
get | |||
{ | |||
if (localPlayer == null || localPlayer.Id != playerEngine.GetLocalPlayer()) | |||
localPlayer = new Player(PlayerType.Local); | |||
return localPlayer; | |||
} | |||
} | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class. | |||
/// </summary> | |||
/// <param name="id">The player's unique identifier.</param> | |||
public Player(uint id) | |||
{ | |||
this.Id = id; | |||
if (!Exists(id)) | |||
{ | |||
throw new PlayerNotFoundException($"No player with id {id} exists"); | |||
} | |||
this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote; | |||
} | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class. | |||
/// </summary> | |||
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param> | |||
public Player(PlayerType player) | |||
{ | |||
switch (player) | |||
{ | |||
case PlayerType.Local: | |||
this.Id = playerEngine.GetLocalPlayer(); | |||
break; | |||
case PlayerType.Remote: | |||
this.Id = playerEngine.GetRemotePlayer(); | |||
break; | |||
} | |||
if (this.Id == uint.MaxValue) | |||
{ | |||
throw new PlayerNotFoundException($"No player of {player} type exists"); | |||
} | |||
this.Type = player; | |||
} | |||
// object fields & properties | |||
/// <summary> | |||
/// The player's type. | |||
/// The player type is always relative to the current client, not the game host. | |||
/// </summary> | |||
/// <value>The enumerated player type.</value> | |||
public PlayerType Type { get; } | |||
/// <summary> | |||
/// The player's unique identifier. | |||
/// </summary> | |||
/// <value>The identifier.</value> | |||
public uint Id { get; } | |||
/// <summary> | |||
/// The player's current position. | |||
/// </summary> | |||
/// <value>The position.</value> | |||
public float3 Position | |||
{ | |||
get | |||
{ | |||
return playerEngine.GetLocation(Id); | |||
} | |||
set | |||
{ | |||
playerEngine.SetLocation(Id, value, false); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's current rotation. | |||
/// </summary> | |||
/// <value>The rotation.</value> | |||
public float3 Rotation | |||
{ | |||
get | |||
{ | |||
return playerEngine.GetRotation(Id); | |||
} | |||
set | |||
{ | |||
playerEngine.SetRotation(Id, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's current velocity. | |||
/// </summary> | |||
/// <value>The velocity.</value> | |||
public float3 Velocity | |||
{ | |||
get | |||
{ | |||
return playerEngine.GetLinearVelocity(Id); | |||
} | |||
set | |||
{ | |||
playerEngine.SetLinearVelocity(Id, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's current angular velocity. | |||
/// </summary> | |||
/// <value>The angular velocity.</value> | |||
public float3 AngularVelocity | |||
{ | |||
get | |||
{ | |||
return playerEngine.GetAngularVelocity(Id); | |||
} | |||
set | |||
{ | |||
playerEngine.SetAngularVelocity(Id, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's mass. | |||
/// </summary> | |||
/// <value>The mass.</value> | |||
public float Mass | |||
{ | |||
get | |||
{ | |||
return 1f / playerEngine.GetMass(Id).InverseMass; | |||
} | |||
// FIXME: Setting mass doesn't do anything | |||
/*set | |||
{ | |||
playerEngine.SetInverseMass(Id, 1f / value); | |||
}*/ | |||
} | |||
private float _ping = -1f; | |||
/// <summary> | |||
/// The player's latest network ping time. | |||
/// </summary> | |||
/// <value>The ping (s).</value> | |||
public float Ping | |||
{ | |||
get | |||
{ | |||
float? temp = playerEngine.GetLastPingTime(Id, Type); | |||
if (temp.HasValue) | |||
{ | |||
_ping = temp.Value; | |||
} | |||
return _ping; | |||
} | |||
} | |||
/// <summary> | |||
/// The player's initial health when entering Simulation (aka Time Running) mode. | |||
/// </summary> | |||
/// <value>The initial health.</value> | |||
public float InitialHealth | |||
{ | |||
get => playerEngine.GetInitialHealth(Id); | |||
set | |||
{ | |||
playerEngine.SetInitialHealth(Id, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's current health in Simulation (aka Time Running) mode. | |||
/// </summary> | |||
/// <value>The current health.</value> | |||
public float CurrentHealth | |||
{ | |||
get => playerEngine.GetCurrentHealth(Id); | |||
set | |||
{ | |||
playerEngine.DamagePlayer(Id, CurrentHealth - value); | |||
} | |||
} | |||
/// <summary> | |||
/// Whether this <see cref="T:GamecraftModdingAPI.Player"/> is damageable. | |||
/// </summary> | |||
/// <value><c>true</c> if damageable; otherwise, <c>false</c>.</value> | |||
public bool Damageable | |||
{ | |||
get => playerEngine.GetDamageable(Id); | |||
set | |||
{ | |||
playerEngine.SetDamageable(Id, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's lives when initially entering Simulation (aka Time Running) mode. | |||
/// </summary> | |||
/// <value>The initial lives.</value> | |||
public uint InitialLives | |||
{ | |||
get => playerEngine.GetInitialLives(Id); | |||
set => playerEngine.SetInitialLives(Id, value); | |||
} | |||
/// <summary> | |||
/// The player's current lives in Simulation (aka Time Running) mode. | |||
/// </summary> | |||
/// <value>The current lives.</value> | |||
public uint CurrentLives | |||
{ | |||
get => playerEngine.GetCurrentLives(Id); | |||
set => playerEngine.SetCurrentLives(Id, value); | |||
} | |||
/// <summary> | |||
/// Whether the Game Over screen is displayed for the player. | |||
/// </summary> | |||
/// <value><c>true</c> if game over; otherwise, <c>false</c>.</value> | |||
public bool GameOver | |||
{ | |||
get => playerEngine.GetGameOverScreen(Id); | |||
} | |||
/// <summary> | |||
/// Whether the player is dead. | |||
/// If <c>true</c>, hopefully it was quick. | |||
/// </summary> | |||
/// <value><c>true</c> if dead; otherwise, <c>false</c>.</value> | |||
public bool Dead | |||
{ | |||
get => playerEngine.IsDead(Id); | |||
} | |||
/// <summary> | |||
/// The player's selected block ID in their hand. | |||
/// </summary> | |||
/// <value>The selected block.</value> | |||
public BlockIDs SelectedBlock | |||
{ | |||
get | |||
{ | |||
return (BlockIDs)playerEngine.GetSelectedBlock(Id); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's selected block color in their hand. | |||
/// </summary> | |||
/// <value>The selected block's color.</value> | |||
public BlockColor SelectedColor | |||
{ | |||
get | |||
{ | |||
return new BlockColor(playerEngine.GetSelectedColor(Id)); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's selected block colour in their hand. | |||
/// </summary> | |||
/// <value>The selected block's colour.</value> | |||
public BlockColor SelectedColour | |||
{ | |||
get | |||
{ | |||
return new BlockColor(playerEngine.GetSelectedColor(Id)); | |||
} | |||
} | |||
/// <summary> | |||
/// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage. | |||
/// </summary> | |||
public Blueprint SelectedBlueprint | |||
{ | |||
get => playerEngine.GetPlayerStruct(Id, out BlueprintInventoryItemEntityStruct biies) | |||
? new Blueprint(biies.blueprintResourceId) | |||
: null; | |||
set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue); | |||
} | |||
// object methods | |||
/// <summary> | |||
/// Teleport the player to the specified coordinates. | |||
/// </summary> | |||
/// <param name="x">The x coordinate.</param> | |||
/// <param name="y">The y coordinate.</param> | |||
/// <param name="z">The z coordinate.</param> | |||
/// <param name="relative">If set to <c>true</c> teleport relative to the player's current position.</param> | |||
/// <param name="exitSeat">If set to <c>true</c> exit any seat the player is in.</param> | |||
public void Teleport(float x, float y, float z, bool relative = true, bool exitSeat = true) | |||
{ | |||
float3 location = new float3(x, y, z); | |||
if (relative) | |||
{ | |||
location += playerEngine.GetLocation(Id); | |||
} | |||
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); | |||
} | |||
/// <summary> | |||
/// Returns the block the player is currently looking at in build mode. | |||
/// </summary> | |||
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param> | |||
/// <returns>The block or null if not found</returns> | |||
public Block GetBlockLookedAt(float maxDistance = -1f) | |||
{ | |||
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); | |||
return egid.HasValue && egid.Value.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP | |||
? new Block(egid.Value) | |||
: null; | |||
} | |||
/// <summary> | |||
/// Returns the rigid body the player is currently looking at during simulation. | |||
/// </summary> | |||
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param> | |||
/// <returns>The block or null if not found</returns> | |||
public SimBody GetSimBodyLookedAt(float maxDistance = -1f) | |||
{ | |||
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); | |||
return egid.HasValue && egid.Value.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP | |||
? new SimBody(egid.Value) | |||
: null; | |||
} | |||
/// <summary> | |||
/// Returns the blocks that are in the player's current selection. | |||
/// </summary> | |||
/// <returns>An array of blocks or an empty array</returns> | |||
public Block[] GetSelectedBlocks() | |||
{ | |||
return playerEngine.GetSelectedBlocks(Id); | |||
} | |||
public bool Equals(Player other) | |||
{ | |||
if (ReferenceEquals(null, other)) return false; | |||
if (ReferenceEquals(this, other)) return true; | |||
return Id == other.Id; | |||
} | |||
public bool Equals(EGID other) | |||
{ | |||
return Id == other.entityID && other.groupID == (Type == PlayerType.Local | |||
? PlayersExclusiveGroups.LocalPlayers | |||
: PlayersExclusiveGroups.RemotePlayers); | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
if (ReferenceEquals(null, obj)) return false; | |||
if (ReferenceEquals(this, obj)) return true; | |||
if (obj.GetType() != this.GetType()) return false; | |||
return Equals((Player) obj); | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return (int) Id; | |||
} | |||
// internal methods | |||
internal static void Init() | |||
{ | |||
Utility.GameEngineManager.AddGameEngine(playerEngine); | |||
} | |||
} | |||
} |
@@ -1,479 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using System.Runtime.CompilerServices; | |||
using RobocraftX.Character; | |||
using RobocraftX.Character.Movement; | |||
using RobocraftX.Common.Players; | |||
using RobocraftX.Common.Input; | |||
using RobocraftX.CR.MachineEditing.BoxSelect; | |||
using RobocraftX.Physics; | |||
using RobocraftX.Blocks.Ghost; | |||
using RobocraftX.Character.Camera; | |||
using RobocraftX.Character.Factories; | |||
using Gamecraft.GUI.HUDFeedbackBlocks; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using Unity.Physics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Engines; | |||
using HarmonyLib; | |||
using RobocraftX.Common; | |||
using Svelto.ECS.DataStructures; | |||
namespace GamecraftModdingAPI.Players | |||
{ | |||
internal class PlayerEngine : IApiEngine, IFactoryEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIPlayerGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public IEntityFactory Factory { set; private get; } | |||
private bool isReady = false; | |||
public void Dispose() | |||
{ | |||
isReady = false; | |||
} | |||
public void Ready() | |||
{ | |||
isReady = true; | |||
} | |||
public uint GetLocalPlayer() | |||
{ | |||
if (!isReady) return uint.MaxValue; | |||
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers).ToBuffer(); | |||
if (localPlayers.count > 0) | |||
{ | |||
return localPlayers.buffer[0].ID.entityID; | |||
} | |||
return uint.MaxValue; | |||
} | |||
public uint GetRemotePlayer() | |||
{ | |||
if (!isReady) return uint.MaxValue; | |||
var localPlayers = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers).ToBuffer(); | |||
if (localPlayers.count > 0) | |||
{ | |||
return localPlayers.buffer[0].ID.entityID; | |||
} | |||
return uint.MaxValue; | |||
} | |||
public long GetAllPlayerCount() | |||
{ | |||
if (entitiesDB == null) return 0; | |||
long count = 0; | |||
foreach (ExclusiveGroupStruct eg in PlayersExclusiveGroups.AllPlayers) | |||
{ | |||
count += entitiesDB.Count<PlayerIDStruct>(eg); | |||
} | |||
return count; | |||
} | |||
public long GetLocalPlayerCount() | |||
{ | |||
if (entitiesDB == null) return 0; | |||
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers); | |||
} | |||
public long GetRemotePlayerCount() | |||
{ | |||
if (entitiesDB == null) return 0; | |||
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers); | |||
} | |||
public bool ExistsById(uint playerId) | |||
{ | |||
if (entitiesDB == null) return false; | |||
return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers) | |||
|| entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.RemotePlayers); | |||
} | |||
public float3 GetLocation(uint playerId) | |||
{ | |||
if (entitiesDB == null) return float3.zero; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return rbes.position; | |||
} | |||
return float3.zero; | |||
} | |||
public bool SetLocation(uint playerId, float3 location, bool exitSeat = true) | |||
{ | |||
if (entitiesDB == null) return false; | |||
var characterGroups = CharacterExclusiveGroups.AllCharacters; | |||
for (int i = 0; i < characterGroups.count; i++) | |||
{ | |||
EGID egid = new EGID(playerId, characterGroups[i]); | |||
if (entitiesDB.Exists<RigidBodyEntityStruct>(egid)) | |||
{ | |||
ref RigidBodyEntityStruct rbes = ref entitiesDB.QueryEntity<RigidBodyEntityStruct>(egid); | |||
if (characterGroups[i] == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat) | |||
{ | |||
entitiesDB.QueryEntity<CharacterPilotSeatEntityStruct>(egid).instantExit = true; | |||
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid); | |||
} | |||
rbes.position = location; | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
public float3 GetRotation(uint playerId) | |||
{ | |||
if (entitiesDB == null) return float3.zero; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return ((Quaternion) rbes.rotation).eulerAngles; | |||
} | |||
return default(float3); | |||
} | |||
public bool SetRotation(uint playerId, float3 value) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
Quaternion q = rbes.rotation; | |||
q.eulerAngles = value; | |||
rbes.rotation = q; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public float3 GetLinearVelocity(uint playerId) | |||
{ | |||
if (entitiesDB == null) return float3.zero; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return rbes.velocity; | |||
} | |||
return float3.zero; | |||
} | |||
public bool SetLinearVelocity(uint playerId, float3 value) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
rbes.velocity = value; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public float3 GetAngularVelocity(uint playerId) | |||
{ | |||
if (entitiesDB == null) return float3.zero; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return rbes.angularVelocity; | |||
} | |||
return float3.zero; | |||
} | |||
public bool SetAngularVelocity(uint playerId, float3 value) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
rbes.angularVelocity = value; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public PhysicsMass GetMass(uint playerId) | |||
{ | |||
if (entitiesDB == null) return default(PhysicsMass); | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return rbes.physicsMass; | |||
} | |||
return default(PhysicsMass); | |||
} | |||
public bool SetInverseMass(uint playerId, float inverseMass) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
rbes.physicsMass.InverseInertia = inverseMass; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public float? GetLastPingTime(uint playerId, PlayerType type) | |||
{ | |||
if (entitiesDB == null) return null; | |||
EGID egid = new EGID(playerId, PlayerGroupFromEnum(type)); | |||
if (entitiesDB.Exists<PlayerNetworkStatsEntityStruct>(egid)) | |||
{ | |||
return entitiesDB.QueryEntity<PlayerNetworkStatsEntityStruct>(egid).lastPingTimeSinceLevelLoad; | |||
} | |||
return null; | |||
} | |||
public float GetInitialHealth(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.initialHealth; | |||
} | |||
return -1f; | |||
} | |||
public bool SetInitialHealth(uint playerId, float val) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
c.initialHealth = val; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public float GetCurrentHealth(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.currentHealth; | |||
} | |||
return -1f; | |||
} | |||
public bool SetCurrentHealth(uint playerId, float val) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
c.currentHealth = val; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public bool DamagePlayer(uint playerId, float amount) | |||
{ | |||
if (entitiesDB == null) return false; | |||
return SetCurrentHealth(playerId, GetCurrentHealth(playerId) - amount); | |||
} | |||
public bool GetDamageable(uint playerId) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.canTakeDamageStat; | |||
} | |||
return false; | |||
} | |||
public bool SetDamageable(uint playerId, bool val) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var ches = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
ches.canTakeDamage = val; | |||
ches.canTakeDamage = val; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public uint GetInitialLives(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.initialLives; | |||
} | |||
return uint.MaxValue; | |||
} | |||
public bool SetInitialLives(uint playerId, uint val) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
c.initialLives = val; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public uint GetCurrentLives(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.currentLives; | |||
} | |||
return uint.MaxValue; | |||
} | |||
public bool SetCurrentLives(uint playerId, uint val) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
c.currentLives = val; | |||
return true; | |||
} | |||
return false; | |||
} | |||
public bool GetGameOverScreen(uint playerId) | |||
{ | |||
if (entitiesDB == null) return false; | |||
ref HudActivatedBlocksEntityStruct habes = ref entitiesDB.QueryEntity<HudActivatedBlocksEntityStruct>(HUDFeedbackBlocksGUIExclusiveGroups.GameOverHudEgid); | |||
NativeDynamicArrayCast<EGID> nativeDynamicArrayCast = new NativeDynamicArrayCast<EGID>(habes.activatedBlocksOrdered); | |||
return nativeDynamicArrayCast.count > 0; | |||
} | |||
public bool IsDead(uint playerId) | |||
{ | |||
if (entitiesDB == null) return true; | |||
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadCharacters); | |||
} | |||
public int GetSelectedBlock(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<EquippedPartStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.SelectedDBPartID; | |||
} | |||
return ushort.MaxValue; | |||
} | |||
public byte GetSelectedColor(uint playerId) | |||
{ | |||
if (entitiesDB == null) return 0; | |||
ref var c = ref GetCharacterStruct<EquippedColourStruct>(playerId, out bool exists); | |||
if (exists) | |||
{ | |||
return c.indexInPalette; | |||
} | |||
return 255; | |||
} | |||
// reusable methods | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private ExclusiveGroup PlayerGroupFromEnum(PlayerType type) | |||
{ | |||
return type == PlayerType.Local ? PlayersExclusiveGroups.LocalPlayers : PlayersExclusiveGroups.RemotePlayers; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T GetCharacterStruct<T>(uint playerId, out bool exists) where T : unmanaged, IEntityComponent | |||
{ | |||
var characterGroups = CharacterExclusiveGroups.AllCharacters; | |||
for (int i = 0; i < characterGroups.count; i++) | |||
{ | |||
EGID egid = new EGID(playerId, characterGroups[i]); | |||
if (entitiesDB.Exists<T>(egid)) | |||
{ | |||
exists = true; | |||
return ref entitiesDB.QueryEntity<T>(egid); | |||
} | |||
} | |||
exists = false; | |||
T[] arr = new T[1]; | |||
return ref arr[0]; //Return default value | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool GetPlayerStruct<T>(uint playerId, out T s) where T : unmanaged, IEntityComponent | |||
{ | |||
var playerGroups = PlayersExclusiveGroups.AllPlayers; | |||
for (int i = 0; i < playerGroups.count; i++) | |||
{ | |||
EGID egid = new EGID(playerId, playerGroups[i]); | |||
if (entitiesDB.Exists<T>(egid)) | |||
{ | |||
s = entitiesDB.QueryEntity<T>(egid); | |||
return true; | |||
} | |||
} | |||
s = default; | |||
return false; | |||
} | |||
public EGID? GetThingLookedAt(uint playerId, float maxDistance = -1f) | |||
{ | |||
if (!entitiesDB.TryQueryMappedEntities<CharacterCameraRayCastEntityStruct>( | |||
CameraExclusiveGroups.CameraGroup, out var mapper)) | |||
return null; | |||
mapper.TryGetEntity(playerId, out CharacterCameraRayCastEntityStruct rayCast); | |||
float distance = maxDistance < 0 | |||
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, | |||
GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize) | |||
: maxDistance; | |||
if (rayCast.hit && rayCast.distance <= distance) | |||
return rayCast.hitEgid; | |||
return null; | |||
} | |||
public unsafe Block[] GetSelectedBlocks(uint playerid) | |||
{ | |||
if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)) | |||
return new Block[0]; | |||
var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); | |||
var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); | |||
if (!state.active) return new Block[0]; | |||
var pointer = (EGID*) blocks.selectedBlocks.ToPointer(); | |||
var ret = new Block[blocks.count]; | |||
for (int j = 0; j < blocks.count; j++) | |||
{ | |||
var egid = pointer[j]; | |||
ret[j] = new Block(egid); | |||
} | |||
return ret; | |||
} | |||
} | |||
} |
@@ -1,497 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Reflection.Emit; | |||
using System.Text; | |||
using HarmonyLib; | |||
using IllusionInjector; | |||
// test | |||
using GPUInstancer; | |||
using Svelto.ECS; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using RobocraftX.SimulationModeState; | |||
using RobocraftX.FrontEnd; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Commands; | |||
using GamecraftModdingAPI.Events; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Players; | |||
using EventType = GamecraftModdingAPI.Events.EventType; | |||
namespace GamecraftModdingAPI.Tests | |||
{ | |||
#if DEBUG | |||
// unused by design | |||
/// <summary> | |||
/// Modding API implemented as a standalone IPA Plugin. | |||
/// Ideally, GamecraftModdingAPI should be loaded by another mod; not itself | |||
/// </summary> | |||
public class GamecraftModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin | |||
{ | |||
private static Harmony harmony { get; set; } | |||
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; | |||
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); | |||
public string HarmonyID { get; } = "org.git.exmods.modtainers.gamecraftmoddingapi"; | |||
public override void OnApplicationQuit() | |||
{ | |||
GamecraftModdingAPI.Main.Shutdown(); | |||
} | |||
public override void OnApplicationStart() | |||
{ | |||
FileLog.Reset(); | |||
Harmony.DEBUG = true; | |||
GamecraftModdingAPI.Main.Init(); | |||
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}"); | |||
// in case Steam is not installed/running | |||
// this will crash the game slightly later during startup | |||
//SteamInitPatch.ForcePassSteamCheck = true; | |||
// in case running in a VM | |||
//MinimumSpecsCheckPatch.ForcePassMinimumSpecCheck = true; | |||
// disable some Gamecraft analytics | |||
//AnalyticsDisablerPatch.DisableAnalytics = true; | |||
// disable background music | |||
Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers())); | |||
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :( | |||
//Utility.VersionTracking.Enable();//(very) unstable | |||
// debug/test handlers | |||
#pragma warning disable 0612 | |||
HandlerBuilder.Builder() | |||
.Name("appinit API debug") | |||
.Handle(EventType.ApplicationInitialized) | |||
.OnActivation(() => { Logging.Log("App Inited event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("menuact API debug") | |||
.Handle(EventType.Menu) | |||
.OnActivation(() => { Logging.Log("Menu Activated event!"); }) | |||
.OnDestruction(() => { Logging.Log("Menu Destroyed event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("menuswitch API debug") | |||
.Handle(EventType.MenuSwitchedTo) | |||
.OnActivation(() => { Logging.Log("Menu Switched To event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("gameact API debug") | |||
.Handle(EventType.Menu) | |||
.OnActivation(() => { Logging.Log("Game Activated event!"); }) | |||
.OnDestruction(() => { Logging.Log("Game Destroyed event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("gamerel API debug") | |||
.Handle(EventType.GameReloaded) | |||
.OnActivation(() => { Logging.Log("Game Reloaded event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("gameswitch API debug") | |||
.Handle(EventType.GameSwitchedTo) | |||
.OnActivation(() => { Logging.Log("Game Switched To event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("simulationswitch API debug") | |||
.Handle(EventType.SimulationSwitchedTo) | |||
.OnActivation(() => { Logging.Log("Game Mode Simulation Switched To event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("buildswitch API debug") | |||
.Handle(EventType.BuildSwitchedTo) | |||
.OnActivation(() => { Logging.Log("Game Mode Build Switched To event!"); }) | |||
.Build(); | |||
HandlerBuilder.Builder("menu activated API error thrower test") | |||
.Handle(EventType.Menu) | |||
.OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); }) | |||
.Build(); | |||
#pragma warning restore 0612 | |||
/*HandlerBuilder.Builder("enter game from menu test") | |||
.Handle(EventType.Menu) | |||
.OnActivation(() => | |||
{ | |||
Tasks.Scheduler.Schedule(new Tasks.Repeatable(enterGame, shouldRetry, 0.2f)); | |||
}) | |||
.Build();*/ | |||
// debug/test commands | |||
if (Dependency.Hell("ExtraCommands")) | |||
{ | |||
CommandBuilder.Builder() | |||
.Name("Exit") | |||
.Description("Close Gamecraft immediately, without any prompts") | |||
.Action(() => { UnityEngine.Application.Quit(); }) | |||
.Build(); | |||
CommandBuilder.Builder() | |||
.Name("SetFOV") | |||
.Description("Set the player camera's field of view") | |||
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; }) | |||
.Build(); | |||
CommandBuilder.Builder() | |||
.Name("MoveLastBlock") | |||
.Description("Move the most-recently-placed block, and any connected blocks by the given offset") | |||
.Action((float x, float y, float z) => | |||
{ | |||
if (GameState.IsBuildMode()) | |||
foreach (var block in Block.GetLastPlacedBlock().GetConnectedCubes()) | |||
block.Position += new Unity.Mathematics.float3(x, y, z); | |||
else | |||
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!"); | |||
}).Build(); | |||
CommandBuilder.Builder() | |||
.Name("PlaceAluminium") | |||
.Description("Place a block of aluminium at the given coordinates") | |||
.Action((float x, float y, float z) => | |||
{ | |||
var block = Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x, y, z)); | |||
Logging.CommandLog("Block placed with type: " + block.Type); | |||
}) | |||
.Build(); | |||
CommandBuilder.Builder() | |||
.Name("PlaceAluminiumLots") | |||
.Description("Place a lot of blocks of aluminium at the given coordinates") | |||
.Action((float x, float y, float z) => | |||
{ | |||
Logging.CommandLog("Starting..."); | |||
var sw = Stopwatch.StartNew(); | |||
for (int i = 0; i < 100; i++) | |||
for (int j = 0; j < 100; j++) | |||
Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x + i, y, z + j)); | |||
//Block.Sync(); | |||
sw.Stop(); | |||
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms"); | |||
}) | |||
.Build(); | |||
Block b = null; | |||
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up") | |||
.Action(() => | |||
{ | |||
if (b == null) | |||
{ | |||
b = new Player(PlayerType.Local).GetBlockLookedAt(); | |||
Logging.CommandLog("Block saved: " + b); | |||
} | |||
else | |||
Logging.CommandLog("Block moved to: " + (b.GetSimBody().Position += new float3(0, 2, 0))); | |||
}).Build(); | |||
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.") | |||
.Action(() => { throw new Exception("Error Command always throws an error"); }) | |||
.Build(); | |||
CommandBuilder.Builder("ColorBlock", | |||
"Change color of the block looked at if there's any.") | |||
.Action<string>(str => | |||
{ | |||
if (!Enum.TryParse(str, out BlockColors color)) | |||
{ | |||
Logging.CommandLog("Color " + str + " not found! Interpreting as 4 color values."); | |||
var s = str.Split(' '); | |||
new Player(PlayerType.Local).GetBlockLookedAt().CustomColor = new float4(float.Parse(s[0]), | |||
float.Parse(s[1]), float.Parse(s[2]), float.Parse(s[3])); | |||
return; | |||
} | |||
new Player(PlayerType.Local).GetBlockLookedAt().Color = | |||
new BlockColor { Color = color }; | |||
Logging.CommandLog("Colored block to " + color); | |||
}).Build(); | |||
CommandBuilder.Builder("GetBlockByID", "Gets a block based on its object identifier and teleports it up.") | |||
.Action<char>(ch => | |||
{ | |||
foreach (var body in SimBody.GetFromObjectID(ch)) | |||
{ | |||
Logging.CommandLog("SimBody: " + body); | |||
body.Position += new float3(0, 10, 0); | |||
foreach (var bodyConnectedBody in body.GetConnectedBodies()) | |||
{ | |||
Logging.CommandLog("Moving " + bodyConnectedBody); | |||
bodyConnectedBody.Position += new float3(0, 10, 0); | |||
} | |||
} | |||
}).Build(); | |||
CommandBuilder.Builder() | |||
.Name("PlaceConsole") | |||
.Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist") | |||
.Action((float x, float y, float z) => | |||
{ | |||
Stopwatch sw = new Stopwatch(); | |||
sw.Start(); | |||
for (int i = 0; i < 100; i++) | |||
{ | |||
for (int j = 0; j < 100; j++) | |||
{ | |||
var block = Block.PlaceNew<ConsoleBlock>(BlockIDs.ConsoleBlock, | |||
new float3(x + i, y, z + j)); | |||
block.Command = "test_command"; | |||
} | |||
} | |||
sw.Stop(); | |||
Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms"); | |||
}) | |||
.Build(); | |||
CommandBuilder.Builder() | |||
.Name("WireTest") | |||
.Description("Place two blocks and then wire them together") | |||
.Action(() => | |||
{ | |||
LogicGate notBlock = Block.PlaceNew<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0)); | |||
LogicGate andBlock = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, new float3(2, 2, 0)); | |||
// connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position) | |||
Wire conn = notBlock.Connect(0, andBlock, 1); | |||
Logging.CommandLog(conn.ToString()); | |||
}) | |||
.Build(); | |||
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.") | |||
.Action((float val, float max) => | |||
{ | |||
var body = new Player(PlayerType.Local).GetSimBodyLookedAt(); | |||
if (body == null) return; | |||
body.CurrentHealth = val; | |||
body.InitialHealth = max; | |||
Logging.CommandLog("Health set to: " + val); | |||
}).Build(); | |||
CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group") | |||
.Action((float x, float y, float z) => | |||
{ | |||
var pos = new float3(x, y, z); | |||
var group = BlockGroup.Create(Block.PlaceNew(BlockIDs.AluminiumCube, pos, | |||
color: BlockColors.Aqua)); | |||
Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Blue) | |||
.BlockGroup = group; | |||
Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Green) | |||
.BlockGroup = group; | |||
Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Lime) | |||
.BlockGroup = group; | |||
}).Build(); | |||
GameClient.SetDebugInfo("InstalledMods", InstalledMods); | |||
Block.Placed += (sender, args) => | |||
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); | |||
Block.Removed += (sender, args) => | |||
Logging.MetaDebugLog("Removed block " + args.Type + " with ID " + args.ID); | |||
/* | |||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; }, | |||
"SetFOV", "Set the player camera's field of view")); | |||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>( | |||
(x, y, z) => { | |||
bool success = GamecraftModdingAPI.Blocks.Movement.MoveConnectedBlocks( | |||
GamecraftModdingAPI.Blocks.BlockIdentifiers.LatestBlockID, | |||
new Unity.Mathematics.float3(x, y, z)); | |||
if (!success) | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!"); | |||
} | |||
}, "MoveLastBlock", "Move the most-recently-placed block, and any connected blocks by the given offset")); | |||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>( | |||
(x, y, z) => { Blocks.Placement.PlaceBlock(Blocks.BlockIDs.AluminiumCube, new Unity.Mathematics.float3(x, y, z)); }, | |||
"PlaceAluminium", "Place a block of aluminium at the given coordinates")); | |||
System.Random random = new System.Random(); // for command below | |||
CommandManager.AddCommand(new SimpleCustomCommandEngine( | |||
() => { | |||
if (!GameState.IsSimulationMode()) | |||
{ | |||
Logging.CommandLogError("You must be in simulation mode for this to work!"); | |||
return; | |||
} | |||
Tasks.Repeatable task = new Tasks.Repeatable(() => { | |||
uint count = 0; | |||
EGID[] eBlocks = Blocks.Signals.GetElectricBlocks(); | |||
for (uint i = 0u; i < eBlocks.Length; i++) | |||
{ | |||
uint[] ids = Blocks.Signals.GetSignalIDs(eBlocks[i]); | |||
for (uint j = 0u; j < ids.Length; j++) | |||
{ | |||
Blocks.Signals.SetSignalByID(ids[j], (float)random.NextDouble()); | |||
count++; | |||
} | |||
} | |||
Logging.MetaDebugLog($"Did the thing on {count} inputs"); | |||
}, | |||
() => { return GameState.IsSimulationMode(); }); | |||
Tasks.Scheduler.Schedule(task); | |||
}, "RandomizeSignalsInputs", "Do the thing")); | |||
*/ | |||
} | |||
// dependency test | |||
if (Dependency.Hell("GamecraftScripting", new Version("0.0.1.0"))) | |||
{ | |||
Logging.LogWarning("You're in GamecraftScripting dependency hell"); | |||
} | |||
else | |||
{ | |||
Logging.Log("Compatible GamecraftScripting detected"); | |||
} | |||
#if TEST | |||
TestRoot.RunTests(); | |||
#endif | |||
} | |||
private string modsString; | |||
private string InstalledMods() | |||
{ | |||
if (modsString != null) return modsString; | |||
StringBuilder sb = new StringBuilder("Installed mods:"); | |||
foreach (var plugin in PluginManager.Plugins) | |||
sb.Append("\n" + plugin.Name + " - " + plugin.Version); | |||
return modsString = sb.ToString(); | |||
} | |||
private bool retry = true; | |||
private bool shouldRetry() | |||
{ | |||
return retry; | |||
} | |||
private void enterGame() | |||
{ | |||
App.Client app = new App.Client(); | |||
App.Game[] myGames = app.MyGames; | |||
Logging.MetaDebugLog($"MyGames count {myGames.Length}"); | |||
if (myGames.Length != 0) | |||
{ | |||
Logging.MetaDebugLog($"MyGames[0] EGID {myGames[0].EGID}"); | |||
retry = false; | |||
try | |||
{ | |||
//myGames[0].Description = "test msg pls ignore"; // make sure game exists first | |||
Logging.MetaDebugLog($"Entering game {myGames[0].Name}"); | |||
myGames[0].EnterGame(); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog($"Failed to enter game; exception: {e}"); | |||
retry = true; | |||
} | |||
} | |||
else | |||
{ | |||
Logging.MetaDebugLog("MyGames not populated yet :("); | |||
} | |||
} | |||
[HarmonyPatch] | |||
public class MinimumSpecsPatch | |||
{ | |||
public static bool Prefix() | |||
{ | |||
return false; | |||
} | |||
public static MethodInfo TargetMethod() | |||
{ | |||
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method; | |||
} | |||
} | |||
[HarmonyPatch] | |||
public class BugHuntPatch | |||
{ | |||
public static MethodInfo method = | |||
SymbolExtensions.GetMethodInfo<string>(str => Console.WriteLine(str)); | |||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) | |||
{ | |||
int i = 0; | |||
foreach (var instruction in instructions) | |||
{ | |||
i++; | |||
yield return instruction; //Return the instruction first | |||
//stloc, dup, callvirt | |||
if (instruction.opcode.Name.ToLower().StartsWith("stloc") | |||
|| instruction.opcode == OpCodes.Dup | |||
|| instruction.opcode == OpCodes.Callvirt) | |||
{ | |||
yield return new CodeInstruction(OpCodes.Ldstr, | |||
"Just ran the " + i + ". instruction ending with " + instruction.opcode.Name); | |||
yield return new CodeInstruction(OpCodes.Call, method); | |||
} | |||
} | |||
} | |||
public static MethodInfo TargetMethod() | |||
{ | |||
return AccessTools.Method("RobocraftX.CR.MachineEditing.BoxSelect.CopySelectionEngine:GenerateThumbnail"); | |||
} | |||
} | |||
[HarmonyPatch] | |||
public class BugHuntPatch2 | |||
{ | |||
public static void Prefix(int width, float fieldOfView, Vector3 cameraDirection, Vector3 lightDirection) | |||
{ | |||
Console.WriteLine("TakeThumbnail invoked with parameters: " + width + ", " + fieldOfView + ", " + | |||
cameraDirection + ", " + lightDirection); | |||
GPUInstancerManager manager = GPUInstancerAPI.GetActiveManagers().Find(m => m is GPUInstancerPrefabManager); | |||
Bounds instancesBounds = manager.ComputeInstancesBounds(2); | |||
Console.WriteLine("Bounds: " + instancesBounds); | |||
Console.WriteLine("Size: " + instancesBounds.size); | |||
Console.WriteLine("Size.x < 0: " + (instancesBounds.size.x < 0)); | |||
} | |||
public static void Postfix(Texture2D __result) | |||
{ | |||
Console.WriteLine("TakeThumbnail returned: " + (__result == null ? null : __result.name)); | |||
} | |||
private delegate Texture2D TakeThumbnailDel(int width, float fieldOfView, Vector3 cameraDirection, | |||
Vector3 lightDirection); | |||
public static MethodInfo TargetMethod() | |||
{ | |||
return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method; | |||
} | |||
} | |||
[HarmonyPatch] | |||
public class BugHuntPatch3 | |||
{ | |||
public static void Prefix(int width, int filterLayerMask, GPUInstancerManager manager, | |||
Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection, | |||
int cullingLayer) | |||
{ | |||
Console.WriteLine("Inner TakeThumbnail invoked with parameters: " + width + ", " + filterLayerMask + | |||
", " + (manager != null ? manager.name : null) + ", " + cameraPosition + ", " + | |||
cameraRotation + ", " + cameraFov + ", " + lightDirection + ", " + cullingLayer); | |||
} | |||
private delegate Texture2D TakeThumbnailDel(int width, int filterLayerMask, GPUInstancerManager manager, | |||
Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection, | |||
int cullingLayer); | |||
public static MethodInfo TargetMethod() | |||
{ | |||
return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method; | |||
} | |||
} | |||
} | |||
#endif | |||
} |
@@ -1,35 +0,0 @@ | |||
using System.Threading.Tasks; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
public static class AsyncUtils | |||
{ | |||
private static AsyncUtilsEngine gameEngine = new AsyncUtilsEngine(); | |||
/// <summary> | |||
/// Waits for entity submission asynchronously. | |||
/// Use after placing a block or otherwise creating things in the game to access their properties. | |||
/// </summary> | |||
public static async Task WaitForSubmission() | |||
{ | |||
await gameEngine.WaitForSubmission(); | |||
} | |||
public static async Task WaitForNextFrame() | |||
{ | |||
await gameEngine.WaitForNextFrame(); | |||
} | |||
public static void Setup(EnginesRoot enginesRoot) | |||
{ | |||
gameEngine.Setup(enginesRoot.GenerateEntityFunctions(), enginesRoot.GenerateEntityFactory()); | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(gameEngine); | |||
} | |||
} | |||
} |
@@ -1,63 +0,0 @@ | |||
using System.Collections; | |||
using System.Threading.Tasks; | |||
using RobocraftX.Schedulers; | |||
using Svelto.ECS; | |||
using Svelto.Tasks.ExtraLean; | |||
using GamecraftModdingAPI.Engines; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
public class AsyncUtilsEngine : IApiEngine | |||
{ | |||
private IEntityFunctions _efu; | |||
private IEntityFactory _efa; | |||
private IEnumerator WaitForSubmissionInternal(IEntityFunctions efu, IEntityFactory efa, | |||
EntitiesDB entitiesDB, TaskCompletionSource<object> task) | |||
{ | |||
var waitEnumerator = new WaitForSubmissionEnumerator(efu, efa, entitiesDB); | |||
while (waitEnumerator.MoveNext()) | |||
yield return null; | |||
task.SetResult(null); | |||
} | |||
private IEnumerator WaitForNextFrameInternal(TaskCompletionSource<object> task) | |||
{ | |||
yield return null; | |||
task.SetResult(null); | |||
} | |||
public Task WaitForSubmission() | |||
{ | |||
var task = new TaskCompletionSource<object>(); | |||
WaitForSubmissionInternal(_efu, _efa, entitiesDB, task).RunOn(ExtraLean.EveryFrameStepRunner_TimeStopped); | |||
return task.Task; | |||
} | |||
public Task WaitForNextFrame() | |||
{ | |||
var task = new TaskCompletionSource<object>(); | |||
WaitForNextFrameInternal(task).RunOn(ExtraLean.EveryFrameStepRunner_TimeStopped); | |||
return task.Task; | |||
} | |||
public void Setup(IEntityFunctions efu, IEntityFactory efa) | |||
{ | |||
_efu = efu; | |||
_efa = efa; | |||
} | |||
public void Ready() | |||
{ | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
public void Dispose() | |||
{ | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIAsyncUtilsGameEngine"; | |||
public bool isRemovable { get; } = false; | |||
} | |||
} |
@@ -1,29 +0,0 @@ | |||
using System; | |||
using GamecraftModdingAPI.Events; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
public static class ExceptionUtil | |||
{ | |||
/// <summary> | |||
/// Invokes an event in a try-catch block to avoid propagating exceptions. | |||
/// </summary> | |||
/// <param name="handler">The event to emit, can be null</param> | |||
/// <param name="sender">Event sender</param> | |||
/// <param name="args">Event arguments</param> | |||
/// <typeparam name="T">Type of the event arguments</typeparam> | |||
public static void InvokeEvent<T>(EventHandler<T> handler, object sender, T args) | |||
{ | |||
try | |||
{ | |||
handler?.Invoke(sender, args); | |||
} | |||
catch (Exception e) | |||
{ | |||
EventRuntimeException wrappedException = | |||
new EventRuntimeException($"EventHandler with arg type {typeof(T).Name} threw an exception", e); | |||
Logging.LogWarning(wrappedException.ToString()); | |||
} | |||
} | |||
} | |||
} |
@@ -1,30 +0,0 @@ | |||
using System; | |||
using GamecraftModdingAPI.Blocks; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
public static class GameClient | |||
{ | |||
private static DebugInterfaceEngine _engine = new DebugInterfaceEngine(); | |||
/// <summary> | |||
/// Saves the extra information to be displayed on the debug view. | |||
/// The provided getter function is called each time the view updates so make sure it returns quickly. | |||
/// </summary> | |||
/// <param name="id">A global ID for the custom information</param> | |||
/// <param name="contentGetter">A function that returns the current information</param> | |||
public static void SetDebugInfo(string id, Func<string> contentGetter) => _engine.SetInfo(id, contentGetter); | |||
/// <summary> | |||
/// Removes an information provided by a plugin. | |||
/// </summary> | |||
/// <param name="id">The ID of the custom information</param> | |||
/// <returns></returns> | |||
public static bool RemoveDebugInfo(string id) => _engine.RemoveInfo(id); | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(_engine); | |||
} | |||
} | |||
} |
@@ -1,116 +0,0 @@ | |||
using System; | |||
using System.Reflection; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Serialization; | |||
using GamecraftModdingAPI.Persistence; | |||
using GamecraftModdingAPI.Events; | |||
namespace GamecraftModdingAPI.Utility | |||
{ | |||
/// <summary> | |||
/// Tracks the API version the current game was built for. | |||
/// For compatibility reasons, this must be enabled before it will work. | |||
/// </summary> | |||
[Obsolete] | |||
public static class VersionTracking | |||
{ | |||
private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine(); | |||
private static bool isEnabled = false; | |||
/// <summary> | |||
/// Gets the API version saved in the current game. | |||
/// </summary> | |||
/// <returns>The version.</returns> | |||
public static uint GetVersion() | |||
{ | |||
if (!isEnabled) return 0u; | |||
return versionEngine.GetGameVersion(); | |||
} | |||
/// <summary> | |||
/// Enable API version tracking. | |||
/// </summary> | |||
public static void Enable() | |||
{ | |||
if (!SerializerManager.ExistsSerializer(typeof(ModVersionStruct).FullName)) | |||
{ | |||
SerializerManager.AddSerializer<ModVersionDescriptor>(new SimpleEntitySerializer<ModVersionDescriptor>( | |||
(_) => { return new EGID[1] { new EGID(0u, ApiExclusiveGroups.versionGroup) }; } | |||
)); | |||
} | |||
EventManager.AddEventEmitter(versionEngine); | |||
isEnabled = true; | |||
} | |||
/// <summary> | |||
/// Disable API version tracking. | |||
/// </summary> | |||
public static void Disable() | |||
{ | |||
EventManager.AddEventEmitter(versionEngine); | |||
isEnabled = false; | |||
} | |||
public static void Init() { } | |||
} | |||
[Obsolete] | |||
internal class VersionTrackingEngine : IEventEmitterEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public int type => -1; | |||
public bool isRemovable => false; | |||
public IEntityFactory Factory { set; private get; } | |||
public void Dispose() { } | |||
public void Ready() | |||
{ | |||
EGID egid = new EGID(0u, ApiExclusiveGroups.versionGroup); | |||
if (!entitiesDB.Exists<ModVersionStruct>(egid)) | |||
{ | |||
Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version; | |||
int v = (currentVersion.Major * 1000) + (currentVersion.Minor); | |||
Factory.BuildEntity<ModVersionDescriptor>(egid).Init<ModVersionStruct>(new ModVersionStruct | |||
{ | |||
version = (uint)v | |||
}); | |||
} | |||
} | |||
public uint GetGameVersion() | |||
{ | |||
return entitiesDB.QueryUniqueEntity<ModVersionStruct>(ApiExclusiveGroups.versionGroup).version; | |||
} | |||
public void Emit() { } | |||
} | |||
[Obsolete] | |||
public struct ModVersionStruct : IEntityComponent | |||
{ | |||
public uint version; | |||
} | |||
[Obsolete] | |||
public class ModVersionDescriptor: SerializableEntityDescriptor<ModVersionDescriptor._ModVersionDescriptor> | |||
{ | |||
[HashName("GamecraftModdingAPIVersionV0")] | |||
public class _ModVersionDescriptor : IEntityDescriptor | |||
{ | |||
public IComponentBuilder[] componentsToBuild { get; } = new IComponentBuilder[]{ | |||
new SerializableComponentBuilder<SerializationType, ModVersionStruct>(((int)SerializationType.Network, new DefaultSerializer<ModVersionStruct>()), ((int)SerializationType.Storage, new DefaultSerializer<ModVersionStruct>())), | |||
}; | |||
} | |||
} | |||
} |
@@ -1,12 +1,12 @@ | |||
# GamecraftModdingAPI | |||
# TechbloxModdingAPI | |||
Unofficial Gamecraft modding API for interfacing Gamecraft from mods. | |||
Unofficial Techblox modding API for interfacing Techblox from mods. | |||
The GamecraftModdingAPI aims to simplify the mods in two ways: | |||
The TechbloxModdingAPI aims to simplify the mods in two ways: | |||
- *Ease-of-Use* The API provides convenient ways to do common tasks such as moving blocks and adding commands. | |||
All of the Harmony patching is done for you, so you can focus on writing your mod instead of reading swathes of undocumented code. | |||
- *Stability* The API aims to be reliable and consistent between versions. | |||
This means your code won't break when the GamecraftModdingAPI or Gamecraft updates. | |||
This means your code won't break when the TechbloxModdingAPI or Techblox updates. | |||
For more info, please check out the [official documentation](https://mod.exmods.org). | |||
@@ -16,16 +16,16 @@ For more support, join the ExMods [Discord](https://discord.exmods.org). | |||
[Please follow the official mod installation guide](https://www.exmods.org/guides/install.html) or use GCMM. | |||
## Development | |||
To get started, create a symbolic link called `ref` in the root of the project, or one folder higher, linking to the Gamecraft install folder. | |||
This will allow your IDE to resolve references to Gamecraft files for building and IDE tools. | |||
To get started, create a symbolic link called `ref` in the root of the project, or one folder higher, linking to the Techblox install folder. | |||
This will allow your IDE to resolve references to Techblox files for building and IDE tools. | |||
GamecraftModdingAPI version numbers follow the [Semantic Versioning guidelines](https://semver.org/). | |||
TechbloxModdingAPI version numbers follow the [Semantic Versioning guidelines](https://semver.org/). | |||
## External Libraries | |||
GamecraftModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to modify the behaviour of existing Gamecraft code. | |||
TechbloxModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to modify the behaviour of existing Techblox code. | |||
# Disclaimer | |||
This API is an unofficial modification of Gamecraft software, and is not endorsed or supported by FreeJam or Gamecraft. | |||
The GamecraftModdingAPI developer(s) claim no rights on the Gamecraft code referenced within this project. | |||
All code contained in this project is licensed under the [GNU Public License v3](https://git.exmods.org/modtainers/GamecraftModdingAPI/src/branch/master/LICENSE). | |||
This API is an unofficial modification of Techblox software, and is not endorsed or supported by FreeJam or Techblox. | |||
The TechbloxModdingAPI developer(s) claim no rights on the Techblox code referenced within this project. | |||
All code contained in this project is licensed under the [GNU Public License v3](https://git.exmods.org/modtainers/TechbloxModdingAPI/src/branch/master/LICENSE). | |||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 | |||
# Visual Studio Version 16 | |||
VisualStudioVersion = 16.0.29411.108 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GamecraftModdingAPI", "GamecraftModdingAPI\GamecraftModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}" | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechbloxModdingAPI", "TechbloxModdingAPI\TechbloxModdingAPI.csproj", "{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGenerator", "CodeGenerator\CodeGenerator.csproj", "{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
@@ -18,6 +20,12 @@ Global | |||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.ActiveCfg = Test|Any CPU | |||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.Build.0 = Test|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.ActiveCfg = Debug|Any CPU | |||
{0EBB6400-95A7-4A3D-B2ED-BF31E364CC10}.Test|Any CPU.Build.0 = Debug|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE |
@@ -1,8 +1,8 @@ | |||
using System; | |||
using GamecraftModdingAPI.Tests; | |||
using TechbloxModdingAPI.Tests; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
#if TEST | |||
/// <summary> |
@@ -0,0 +1,44 @@ | |||
using System; | |||
using RobocraftX.GUI.MyGamesScreen; | |||
using Svelto.ECS; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public class AppEngine : IFactoryEngine | |||
{ | |||
public WrappedHandler<MenuEventArgs> EnterMenu; | |||
public WrappedHandler<MenuEventArgs> ExitMenu; | |||
public IEntityFactory Factory { set; private get; } | |||
public string Name => "TechbloxModdingAPIAppEngine"; | |||
public bool isRemovable => false; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public void Dispose() | |||
{ | |||
IsInMenu = false; | |||
ExitMenu.Invoke(this, new MenuEventArgs { }); | |||
} | |||
public void Ready() | |||
{ | |||
IsInMenu = true; | |||
EnterMenu.Invoke(this, new MenuEventArgs { }); | |||
} | |||
// app functionality | |||
public bool IsInMenu | |||
{ | |||
get; | |||
private set; | |||
} = false; | |||
} | |||
} |
@@ -1,9 +1,9 @@ | |||
using System; | |||
using System.Runtime.Serialization; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public class AppException : GamecraftModdingAPIException | |||
public class AppException : TechbloxModdingAPIException | |||
{ | |||
public AppException() | |||
{ |
@@ -4,33 +4,29 @@ using HarmonyLib; | |||
using RobocraftX.Services; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Utility; | |||
using RobocraftX.Common; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
/// <summary> | |||
/// The Gamecraft application that is running this code right now. | |||
/// The Techblox application that is running this code right now. | |||
/// </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> | |||
@@ -38,12 +34,12 @@ namespace GamecraftModdingAPI.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> | |||
/// Gamecraft build version string. | |||
/// Techblox build version string. | |||
/// Usually this is in the form YYYY.mm.DD.HH.MM.SS | |||
/// </summary> | |||
/// <value>The version.</value> | |||
@@ -70,23 +66,23 @@ namespace GamecraftModdingAPI.App | |||
{ | |||
get | |||
{ | |||
if (!appEngine.IsInMenu) return new Game[0]; | |||
return appEngine.GetMyGames(); | |||
if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>(); | |||
return Game.menuEngine.GetMyGames(); | |||
} | |||
} | |||
/// <summary> | |||
/// Whether Gamecraft is in the Main Menu | |||
/// Whether Techblox is in the Main Menu | |||
/// </summary> | |||
/// <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> | |||
/// Open a popup which prompts the user to click a button. | |||
/// This reuses Gamecraft's error dialog popup | |||
/// This reuses Techblox's error dialog popup | |||
/// </summary> | |||
/// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param> | |||
public void PromptUser(Error popup) | |||
@@ -111,17 +107,15 @@ namespace GamecraftModdingAPI.App | |||
// this would have been so much simpler if this didn't involve a bunch of internal fields & classes | |||
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); | |||
Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle"); | |||
ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenInstanceGetter") | |||
ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenInstanceGetter") | |||
.MakeGenericMethod(errorHandler) | |||
.Invoke(null, new object[0]); | |||
EnqueueError = (Action<object, Error>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenEnqueueError") | |||
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") | |||
.MakeGenericMethod(errorHandler, errorHandle) | |||
.Invoke(null, new object[0]); | |||
/*HandleErrorClosed = (Action<object>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenHandlePopupClosed") | |||
/*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 |
@@ -3,9 +3,9 @@ using HarmonyLib; | |||
using RobocraftX.Services; | |||
using GamecraftModdingAPI.Tests; | |||
using TechbloxModdingAPI.Tests; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
#if TEST | |||
/// <summary> |
@@ -0,0 +1,23 @@ | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public enum CurrentGameMode | |||
{ | |||
None, | |||
/// <summary> | |||
/// Building a game | |||
/// </summary> | |||
Build, | |||
/// <summary> | |||
/// Playing a game | |||
/// </summary> | |||
Play, | |||
/// <summary> | |||
/// Viewing a prefab | |||
/// </summary> | |||
View, | |||
/// <summary> | |||
/// Viewing a tutorial | |||
/// </summary> | |||
Tutorial | |||
} | |||
} |
@@ -7,12 +7,12 @@ using RobocraftX.GUI.MyGamesScreen; | |||
using RobocraftX.StateSync; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Tasks; | |||
using GamecraftModdingAPI.Utility; | |||
using TechbloxModdingAPI; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Tasks; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
/// <summary> | |||
/// An in-game save. | |||
@@ -23,7 +23,7 @@ namespace GamecraftModdingAPI.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(); | |||
@@ -33,7 +33,7 @@ namespace GamecraftModdingAPI.App | |||
private bool hasId = false; | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class. | |||
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class. | |||
/// </summary> | |||
/// <param name="id">Menu identifier.</param> | |||
public Game(uint id) : this(new EGID(id, MyGamesScreenExclusiveGroups.MyGames)) | |||
@@ -41,7 +41,7 @@ namespace GamecraftModdingAPI.App | |||
} | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class. | |||
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class. | |||
/// </summary> | |||
/// <param name="id">Menu identifier.</param> | |||
public Game(EGID id) | |||
@@ -54,7 +54,7 @@ namespace GamecraftModdingAPI.App | |||
} | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class without id. | |||
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class without id. | |||
/// This is assumed to be the current game. | |||
/// </summary> | |||
public Game() | |||
@@ -118,7 +118,7 @@ namespace GamecraftModdingAPI.App | |||
/// <summary> | |||
/// An event that fires right before a game returns to the main menu. | |||
/// At this point, Gamecraft is transitioning state so many things are invalid/unstable here. | |||
/// At this point, Techblox is transitioning state so many things are invalid/unstable here. | |||
/// </summary> | |||
public static event EventHandler<GameEventArgs> Exit | |||
{ | |||
@@ -165,7 +165,7 @@ namespace GamecraftModdingAPI.App | |||
{ | |||
if (!VerifyMode()) return null; | |||
if (menuMode) return menuEngine.GetGameInfo(EGID).GameName; | |||
return GameMode.SaveGameDetails.Name; | |||
return gameEngine.GetGameData().saveName; | |||
} | |||
set | |||
@@ -174,11 +174,7 @@ namespace GamecraftModdingAPI.App | |||
if (menuMode) | |||
{ | |||
menuEngine.SetGameName(EGID, value); | |||
} | |||
else | |||
{ | |||
GameMode.SaveGameDetails.Name = value; | |||
} | |||
} // Save details are directly saved from user input or not changed at all when in game | |||
} | |||
} | |||
@@ -201,11 +197,7 @@ namespace GamecraftModdingAPI.App | |||
if (menuMode) | |||
{ | |||
menuEngine.SetGameDescription(EGID, value); | |||
} | |||
else | |||
{ | |||
// No description exists in-game | |||
} | |||
} // No description exists in-game | |||
} | |||
} | |||
@@ -219,7 +211,7 @@ namespace GamecraftModdingAPI.App | |||
{ | |||
if (!VerifyMode()) return null; | |||
if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath; | |||
return GameMode.SaveGameDetails.Folder; | |||
return gameEngine.GetGameData().gameID; | |||
} | |||
set | |||
@@ -229,11 +221,6 @@ namespace GamecraftModdingAPI.App | |||
{ | |||
menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value); | |||
} | |||
else | |||
{ | |||
// this likely breaks things | |||
GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, value, GameMode.SaveGameDetails.WorkshopId); | |||
} | |||
} | |||
} | |||
@@ -242,28 +229,16 @@ namespace GamecraftModdingAPI.App | |||
/// In most cases this is invalid and returns 0, so this can be ignored. | |||
/// </summary> | |||
/// <value>The workshop identifier.</value> | |||
[Obsolete] | |||
public ulong WorkshopId | |||
{ | |||
get | |||
{ | |||
if (!VerifyMode()) return 0uL; | |||
if (menuMode) return 0uL; // MyGames don't have workshop IDs | |||
return GameMode.SaveGameDetails.WorkshopId; | |||
return 0uL; // Not supported anymore | |||
} | |||
set | |||
{ | |||
VerifyMode(); | |||
if (menuMode) | |||
{ | |||
// MyGames don't have workshop IDs | |||
// menuEngine.GetGameInfo(EGID).GameName.Set(value); | |||
} | |||
else | |||
{ | |||
// this likely breaks things | |||
GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, GameMode.SaveGameDetails.Folder, value); | |||
} | |||
} | |||
} | |||
@@ -335,6 +310,18 @@ namespace GamecraftModdingAPI.App | |||
gameEngine.ToggleTimeMode(); | |||
} | |||
/// <summary> | |||
/// The mode of the game. | |||
/// </summary> | |||
public CurrentGameMode Mode | |||
{ | |||
get | |||
{ | |||
if (menuMode || !VerifyMode()) return CurrentGameMode.None; | |||
return (CurrentGameMode) GameMode.CurrentMode; | |||
} | |||
} | |||
/// <summary> | |||
/// Load the game save. | |||
/// This happens asynchronously, so when this method returns the game not loaded yet. | |||
@@ -384,19 +371,20 @@ namespace GamecraftModdingAPI.App | |||
/// <summary> | |||
/// Add information to the in-game debug display. | |||
/// When this object is garbage collected, this debug info is automatically removed. | |||
/// When this object is garbage collected, this debug info is automatically removed. | |||
/// The provided getter function is called each frame so make sure it returns quickly. | |||
/// </summary> | |||
/// <param name="id">Debug info identifier.</param> | |||
/// <param name="contentGetter">Content getter.</param> | |||
public void AddDebugInfo(string id, Func<string> contentGetter) | |||
/// <param name="contentGetter">A function that returns the current information.</param> | |||
public void AddDebugInfo(string id, Func<string> contentGetter) | |||
{ | |||
if (!VerifyMode()) return; | |||
if (menuMode) | |||
{ | |||
throw new GameNotFoundException("Game object references a menu item but AddDebugInfo only works on the currently-loaded game"); | |||
} | |||
debugOverlayEngine.SetInfo(id, contentGetter); | |||
debugIds.Add(id); | |||
if (!VerifyMode()) return; | |||
if (menuMode) | |||
{ | |||
throw new GameNotFoundException("Game object references a menu item but AddDebugInfo only works on the currently-loaded game"); | |||
} | |||
debugOverlayEngine.SetInfo("game_" + id, contentGetter); | |||
debugIds.Add(id); | |||
} | |||
/// <summary> | |||
@@ -406,14 +394,36 @@ namespace GamecraftModdingAPI.App | |||
/// <param name="id">Debug info identifier.</param> | |||
public bool RemoveDebugInfo(string id) | |||
{ | |||
if (!VerifyMode()) return false; | |||
if (menuMode) | |||
{ | |||
throw new GameNotFoundException("Game object references a menu item but RemoveDebugInfo only works on the currently-loaded game"); | |||
} | |||
if (!debugIds.Contains(id)) return false; | |||
debugOverlayEngine.RemoveInfo(id); | |||
return debugIds.Remove(id); | |||
if (!VerifyMode()) return false; | |||
if (menuMode) | |||
{ | |||
throw new GameNotFoundException("Game object references a menu item but RemoveDebugInfo only works on the currently-loaded game"); | |||
} | |||
if (!debugIds.Contains(id)) return false; | |||
debugOverlayEngine.RemoveInfo("game_" + id); | |||
return debugIds.Remove(id); | |||
} | |||
/// <summary> | |||
/// Add information to the in-game debug display. | |||
/// This debug info will be present for all games until it is manually removed. | |||
/// The provided getter function is called each frame so make sure it returns quickly. | |||
/// </summary> | |||
/// <param name="id">Debug info identifier.</param> | |||
/// <param name="contentGetter">A function that returns the current information.</param> | |||
public static void AddPersistentDebugInfo(string id, Func<string> contentGetter) | |||
{ | |||
debugOverlayEngine.SetInfo("persistent_" + id, contentGetter); | |||
} | |||
/// <summary> | |||
/// Remove persistent information from the in-game debug display. | |||
/// </summary> | |||
/// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns> | |||
/// <param name="id">Debug info identifier.</param> | |||
public static bool RemovePersistentDebugInfo(string id) | |||
{ | |||
return debugOverlayEngine.RemoveInfo("persistent_" + id); | |||
} | |||
/// <summary> | |||
@@ -433,11 +443,20 @@ namespace GamecraftModdingAPI.App | |||
Block[] blocks = new Block[blockEGIDs.Length]; | |||
for (int b = 0; b < blockEGIDs.Length; b++) | |||
{ | |||
blocks[b] = new Block(blockEGIDs[b]); | |||
blocks[b] = Block.New(blockEGIDs[b]); | |||
} | |||
return blocks; | |||
} | |||
/// <summary> | |||
/// Enable the screenshot taker for updating the game's screenshot. Breaks the pause menu in a new save. | |||
/// </summary> | |||
public void EnableScreenshotTaker() | |||
{ | |||
if (!VerifyMode()) return; | |||
gameEngine.EnableScreenshotTaker(); | |||
} | |||
~Game() | |||
{ | |||
foreach (string id in debugIds) | |||
@@ -466,12 +485,8 @@ namespace GamecraftModdingAPI.App | |||
{ | |||
GameEngineManager.AddGameEngine(gameEngine); | |||
GameEngineManager.AddGameEngine(debugOverlayEngine); | |||
GameEngineManager.AddGameEngine(buildSimEventEngine); | |||
MenuEngineManager.AddMenuEngine(menuEngine); | |||
} | |||
internal static void InitDeterministic(StateSyncRegistrationHelper stateSyncReg) | |||
{ | |||
stateSyncReg.AddDeterministicEngine(buildSimEventEngine); | |||
} | |||
} | |||
} |
@@ -4,19 +4,18 @@ using RobocraftX.Common; | |||
using RobocraftX.StateSync; | |||
using Svelto.ECS; | |||
using Unity.Jobs; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public class GameBuildSimEventEngine : IApiEngine, IUnorderedInitializeOnTimeRunningModeEntered, IUnorderedInitializeOnTimeStoppedModeEntered | |||
{ | |||
public event EventHandler<GameEventArgs> SimulationMode; | |||
public WrappedHandler<GameEventArgs> SimulationMode; | |||
public event EventHandler<GameEventArgs> BuildMode; | |||
public WrappedHandler<GameEventArgs> BuildMode; | |||
public string Name => "GamecraftModdingAPIBuildSimEventGameEngine"; | |||
public string Name => "TechbloxModdingAPIBuildSimEventGameEngine"; | |||
public bool isRemovable => false; | |||
@@ -28,13 +27,13 @@ namespace GamecraftModdingAPI.App | |||
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps) | |||
{ | |||
ExceptionUtil.InvokeEvent(SimulationMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder }); | |||
SimulationMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); // TODO | |||
return inputDeps; | |||
} | |||
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps) | |||
{ | |||
ExceptionUtil.InvokeEvent(BuildMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder }); | |||
BuildMode.Invoke(this, new GameEventArgs { GameName = "", GamePath = "" }); | |||
return inputDeps; | |||
} | |||
} |
@@ -0,0 +1,150 @@ | |||
using System.Collections.Generic; | |||
using RobocraftX.Common; | |||
using RobocraftX.Schedulers; | |||
using RobocraftX.SimulationModeState; | |||
using Svelto.ECS; | |||
using Svelto.Tasks; | |||
using Svelto.Tasks.Lean; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common.Loading; | |||
using RobocraftX.ScreenshotTaker; | |||
using Techblox.Environment.Transition; | |||
using Techblox.GameSelection; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public class GameGameEngine : IApiEngine, IReactOnAddAndRemove<LoadingActionEntityStruct> | |||
{ | |||
public WrappedHandler<GameEventArgs> EnterGame; | |||
public WrappedHandler<GameEventArgs> ExitGame; | |||
public string Name => "TechbloxModdingAPIGameInfoMenuEngine"; | |||
public bool isRemovable => false; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
private bool enteredGame; | |||
public void Dispose() | |||
{ | |||
ExitGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID }); | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
enteredGame = true; | |||
} | |||
// game functionality | |||
public bool IsInGame | |||
{ | |||
get; | |||
private set; | |||
} = false; | |||
public void ExitCurrentGame(bool async = false) | |||
{ | |||
if (async) | |||
{ | |||
ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_TimeRunningAndStopped); | |||
} | |||
else | |||
{ | |||
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
} | |||
public IEnumerator<TaskContract> ExitCurrentGameAsync() | |||
{ | |||
/* | |||
while (Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING.isStopping) { yield return Yield.It; } | |||
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu").Invoke(FullGameFields.Instance, new object[0]);*/ | |||
yield return Yield.It; | |||
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
public void SaveCurrentGame() | |||
{ | |||
ref GameSceneEntityStruct gses = ref entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
gses.LoadAfterSaving = false; | |||
gses.SaveNow = true; | |||
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); | |||
} | |||
public bool IsTimeRunningMode() | |||
{ | |||
return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB); | |||
} | |||
public bool IsTimeStoppedMode() | |||
{ | |||
return TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB); | |||
} | |||
public void ToggleTimeMode() | |||
{ | |||
if (!entitiesDB.FoundInGroups<BlockTagEntityStruct>()) | |||
throw new AppStateException("At least one block must exist in the world to enter simulation"); | |||
SwitchAnimationUtil.Start(entitiesDB); | |||
} | |||
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) | |||
{ | |||
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>(); | |||
List<EGID> blockEGIDs = new List<EGID>(); | |||
foreach (var (blocks, _) in allBlocks) | |||
{ | |||
var (buffer, count) = blocks.ToBuffer(); | |||
for (int i = 0; i < count; i++) | |||
{ | |||
uint dbid; | |||
if (filter == BlockIDs.Invalid) | |||
dbid = (uint)filter; | |||
else | |||
dbid = entitiesDB.QueryEntity<DBEntityStruct>(buffer[i].ID).DBID; | |||
if (dbid == (ulong)filter) | |||
blockEGIDs.Add(buffer[i].ID); | |||
} | |||
} | |||
return blockEGIDs.ToArray(); | |||
} | |||
public void EnableScreenshotTaker() | |||
{ | |||
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker); | |||
if (local.enabled) | |||
return; | |||
local.enabled = true; | |||
entitiesDB.PublishEntityChange<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker); | |||
} | |||
public GameSelectionComponent GetGameData() | |||
{ | |||
return entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID); | |||
} | |||
public void Add(ref LoadingActionEntityStruct entityComponent, EGID egid) | |||
{ | |||
} | |||
public void Remove(ref LoadingActionEntityStruct entityComponent, EGID egid) | |||
{ // Finished loading | |||
if (!enteredGame) return; | |||
EnterGame.Invoke(this, new GameEventArgs { GameName = GetGameData().saveName, GamePath = GetGameData().gameID }); | |||
IsInGame = true; | |||
enteredGame = false; | |||
} | |||
} | |||
} |
@@ -0,0 +1,204 @@ | |||
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; | |||
using Svelto.ECS.Experimental; | |||
using Techblox.GameSelection; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using GameMode = RobocraftX.Common.GameMode; | |||
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"; | |||
public bool isRemovable => false; | |||
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() | |||
{ | |||
} | |||
public void Ready() | |||
{ | |||
MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched | |||
MenuEnteredEnginePatch.EnteredExitedMenu(); | |||
} | |||
// game functionality | |||
public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu; | |||
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 bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L) | |||
{ | |||
EntityInitializer eci = Factory.BuildEntity<MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal>(id); | |||
eci.Init(new MyGameDataEntityStruct | |||
{ | |||
SavedGamePath = new ECSString(path), | |||
ThumbnailId = thumbnailId, | |||
GameName = new ECSString(gameName), | |||
CreatorName = new ECSString(creatorName), | |||
GameDescription = new ECSString(description), | |||
CreatedDate = createdDate, | |||
}); | |||
// entitiesDB.PublishEntityChange<MyGameDataEntityStruct>(id); // this will always fail | |||
return true; | |||
} | |||
public uint HighestID() | |||
{ | |||
EntityCollection<MyGameDataEntityStruct> games = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames); | |||
var gamesB = games.ToBuffer().buffer; | |||
uint max = 0; | |||
for (int i = 0; i < games.count; i++) | |||
{ | |||
if (gamesB[i].ID.entityID > max) | |||
{ | |||
max = gamesB[i].ID.entityID; | |||
} | |||
} | |||
return max; | |||
} | |||
public bool EnterGame(EGID id) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id); | |||
return EnterGame(mgdes.GameName, mgdes.FileId); | |||
} | |||
public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false) | |||
{ | |||
GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build; | |||
var data = new GameSelectionData | |||
{ | |||
gameMode = Techblox.GameSelection.GameMode.PlayGame, | |||
gameType = GameType.MachineEditor, | |||
saveName = gameName, | |||
saveType = SaveType.ExistingSave, | |||
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}); | |||
return true; | |||
} | |||
public bool SetGameName(EGID id, string name) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
GetGameInfo(id).GameName.Set(name); | |||
GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name); | |||
return true; | |||
} | |||
public bool SetGameDescription(EGID id, string name) | |||
{ | |||
if (!ExistsGameInfo(id)) return false; | |||
GetGameInfo(id).GameDescription.Set(name); | |||
GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name); | |||
return true; | |||
} | |||
public bool ExistsGameInfo(EGID id) | |||
{ | |||
return entitiesDB.Exists<MyGameDataEntityStruct>(id); | |||
} | |||
public ref MyGameDataEntityStruct GetGameInfo(EGID id) | |||
{ | |||
return ref GetComponent<MyGameDataEntityStruct>(id); | |||
} | |||
public dynamic GetGameViewInfo(EGID id) | |||
{ | |||
dynamic structOptional = AccessTools.Method("TechbloxModdingAPI.Utility.NativeApiExtensions:QueryEntityOptional", new []{typeof(EntitiesDB), typeof(EGID)}) | |||
.MakeGenericMethod(AccessTools.TypeByName("RobocraftX.GUI.MyGamesScreen.MyGamesSlotEntityViewStruct")) | |||
.Invoke(null, new object[] {entitiesDB, new EGID(id.entityID, MyGamesScreenExclusiveGroups.GameSlotGuiEntities)}); | |||
if (structOptional == null) throw new Exception("Could not get game slot entity"); | |||
return structOptional ? structOptional : null; | |||
} | |||
public ref T GetComponent<T>(EGID id) where T: unmanaged, IEntityComponent | |||
{ | |||
return ref entitiesDB.QueryEntity<T>(id); | |||
} | |||
} | |||
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 | |||
{ | |||
} | |||
} |
@@ -3,7 +3,7 @@ using HarmonyLib; | |||
using RobocraftX.Services; | |||
namespace GamecraftModdingAPI.App | |||
namespace TechbloxModdingAPI.App | |||
{ | |||
public class DualChoicePrompt : MultiChoiceError | |||
{ |
@@ -0,0 +1,495 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using DataLoader; | |||
using Gamecraft.Blocks.BlockGroups; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using RobocraftX.Common; | |||
using RobocraftX.Blocks; | |||
using Unity.Mathematics; | |||
using Gamecraft.Blocks.GUI; | |||
using HarmonyLib; | |||
using RobocraftX.PilotSeat; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Blocks.Engines; | |||
using TechbloxModdingAPI.Tests; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI | |||
{ | |||
/// <summary> | |||
/// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. | |||
/// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace. | |||
/// </summary> | |||
public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID> | |||
{ | |||
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); | |||
protected static readonly MovementEngine MovementEngine = new MovementEngine(); | |||
protected static readonly RotationEngine RotationEngine = new RotationEngine(); | |||
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine(); | |||
protected static readonly SignalEngine SignalEngine = new SignalEngine(); | |||
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine(); | |||
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine(); | |||
protected static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine(); | |||
protected internal static readonly BlockEngine BlockEngine = new BlockEngine(); | |||
/// <summary> | |||
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. | |||
/// Place blocks next to each other to connect them. | |||
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. | |||
/// </summary> | |||
/// <param name="block">The block's type</param> | |||
/// <param name="position">The block's position - default block size is 0.2</param> | |||
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param> | |||
/// <param name="player">The player who placed the block</param> | |||
/// <param name="force"></param> | |||
/// <returns>The placed block or null if failed</returns> | |||
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null, | |||
bool force = false) | |||
{ | |||
if (PlacementEngine.IsInGame && (GameState.IsBuildMode() || force)) | |||
{ | |||
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); | |||
var egid = initializer.EGID; | |||
var bl = New(egid); | |||
bl.InitData = initializer; | |||
Placed += bl.OnPlacedInit; | |||
return bl; | |||
} | |||
return null; | |||
} | |||
/// <summary> | |||
/// Returns the most recently placed block. | |||
/// </summary> | |||
/// <returns>The block object or null if doesn't exist</returns> | |||
public static Block GetLastPlacedBlock() | |||
{ | |||
uint lastBlockID = (uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null) - 1; | |||
EGID? egid = BlockEngine.FindBlockEGID(lastBlockID); | |||
return egid.HasValue ? New(egid.Value) : null; | |||
} | |||
/*public static Block CreateGhostBlock() | |||
{ | |||
return BlockGroup._engine.BuildGhostChild(); | |||
}*/ | |||
/// <summary> | |||
/// An event that fires each time a block is placed. | |||
/// </summary> | |||
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed | |||
{ //TODO: Rename and add instance version in 3.0 | |||
add => BlockEventsEngine.Placed += value; | |||
remove => BlockEventsEngine.Placed -= value; | |||
} | |||
/// <summary> | |||
/// An event that fires each time a block is removed. | |||
/// </summary> | |||
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed | |||
{ | |||
add => BlockEventsEngine.Removed += value; | |||
remove => BlockEventsEngine.Removed -= value; | |||
} | |||
private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor = | |||
new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)> | |||
{ | |||
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))}, | |||
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}, | |||
{CommonExclusiveGroups.LOGIC_BLOCK_GROUP, (id => new LogicGate(id), typeof(LogicGate))}, | |||
{CommonExclusiveGroups.PISTON_BLOCK_GROUP, (id => new Piston(id), typeof(Piston))}, | |||
{CommonExclusiveGroups.SERVO_BLOCK_GROUP, (id => new Servo(id), typeof(Servo))}, | |||
{CommonExclusiveGroups.WHEELRIG_BLOCK_BUILD_GROUP, (id => new WheelRig(id), typeof(WheelRig))} | |||
}; | |||
static Block() | |||
{ | |||
foreach (var group in SeatGroups.SEATS_BLOCK_GROUPS) // Adds driver and passenger seats, occupied and unoccupied | |||
GroupToConstructor.Add(group, (id => new Seat(id), typeof(Seat))); | |||
} | |||
/// <summary> | |||
/// Returns a correctly typed instance of this block. The instances are shared for a specific block. | |||
/// If an instance is no longer referenced a new instance is returned. | |||
/// </summary> | |||
/// <param name="egid">The EGID of the block</param> | |||
/// <param name="signaling">Whether the block is definitely a signaling block</param> | |||
/// <returns></returns> | |||
internal static Block New(EGID egid, bool signaling = false) | |||
{ | |||
if (egid == default) return null; | |||
if (GroupToConstructor.ContainsKey(egid.groupID)) | |||
{ | |||
var (constructor, type) = GroupToConstructor[egid.groupID]; | |||
return GetInstance(egid, constructor, type); | |||
} | |||
return signaling | |||
? GetInstance(egid, e => new SignalingBlock(e)) | |||
: GetInstance(egid, e => new Block(e)); | |||
} | |||
public Block(EGID id) : base(id) | |||
{ | |||
Type expectedType; | |||
if (GroupToConstructor.ContainsKey(id.groupID) && | |||
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type)) | |||
throw new BlockSpecializationException($"Incorrect block type! Expected: {expectedType} Actual: {GetType()}"); | |||
} | |||
/// <summary> | |||
/// This overload searches for the correct group the block is in. | |||
/// It will throw an exception if the block doesn't exist. | |||
/// Use the EGID constructor where possible or subclasses of Block as those specify the group. | |||
/// </summary> | |||
public Block(uint id) : this(BlockEngine.FindBlockEGID(id) | |||
?? throw new BlockTypeException( | |||
"Could not find the appropriate group for the block." + | |||
" The block probably doesn't exist or hasn't been submitted.")) | |||
{ | |||
} | |||
/// <summary> | |||
/// Places a new block in the world. | |||
/// </summary> | |||
/// <param name="type">The block's type</param> | |||
/// <param name="position">The block's position (a block is 0.2 wide in terms of position)</param> | |||
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param> | |||
/// <param name="player">The player who placed the block</param> | |||
/// <param name="force">Place even if not in build mode</param> | |||
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null, bool force = false) | |||
: base(block => | |||
{ | |||
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode() && !force) | |||
throw new BlockException("Blocks can only be placed in build mode."); | |||
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire); | |||
block.InitData = initializer; | |||
Placed += ((Block)block).OnPlacedInit; | |||
return initializer.EGID; | |||
}) | |||
{ | |||
} | |||
private EGID copiedFrom; | |||
/// <summary> | |||
/// The block's current position or zero if the block no longer exists. | |||
/// A block is 0.2 wide by default in terms of position. | |||
/// </summary> | |||
public float3 Position | |||
{ | |||
get => MovementEngine.GetPosition(this); | |||
set | |||
{ | |||
MovementEngine.MoveBlock(this, value); | |||
if (blockGroup != null) | |||
blockGroup.PosAndRotCalculated = false; | |||
BlockEngine.UpdateDisplayedBlock(Id); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's current rotation in degrees or zero if the block doesn't exist. | |||
/// </summary> | |||
public float3 Rotation | |||
{ | |||
get => RotationEngine.GetRotation(this); | |||
set | |||
{ | |||
RotationEngine.RotateBlock(this, value); | |||
if (blockGroup != null) | |||
blockGroup.PosAndRotCalculated = false; | |||
BlockEngine.UpdateDisplayedBlock(Id); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling. | |||
/// The default scale of 1 means 0.2 in terms of position. | |||
/// </summary> | |||
public float3 Scale | |||
{ | |||
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale; | |||
set | |||
{ | |||
int uscale = UniformScale; | |||
if (value.x < 4e-5) value.x = uscale; | |||
if (value.y < 4e-5) value.y = uscale; | |||
if (value.z < 4e-5) value.z = uscale; | |||
BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale = value; | |||
//BlockEngine.GetBlockInfo<GridScaleStruct>(this).gridScale = value - (int3) value + 1; | |||
if (!Exists) return; //UpdateCollision needs the block to exist | |||
ScalingEngine.UpdateCollision(Id); | |||
BlockEngine.UpdateDisplayedBlock(Id); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale. | |||
/// The default scale of 1 means 0.2 in terms of position. | |||
/// </summary> | |||
public int UniformScale | |||
{ | |||
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor; | |||
set | |||
{ | |||
if (value < 1) value = 1; | |||
BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor = value; | |||
Scale = new float3(value, value, value); | |||
} | |||
} | |||
/** | |||
* Whether the block is flipped. | |||
*/ | |||
public bool Flipped | |||
{ | |||
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale.x < 0; | |||
set | |||
{ | |||
ref var st = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(this); | |||
st.scale.x = math.abs(st.scale.x) * (value ? -1 : 1); | |||
BlockEngine.UpdatePrefab(this, (byte) Material, value); | |||
} | |||
} | |||
/// <summary> | |||
/// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore. | |||
/// </summary> | |||
public BlockIDs Type | |||
{ | |||
get | |||
{ | |||
var opt = BlockEngine.GetBlockInfoOptional<DBEntityStruct>(this); | |||
return opt ? (BlockIDs) opt.Get().DBID : BlockIDs.Invalid; | |||
} | |||
} | |||
/// <summary> | |||
/// The block's color. Returns BlockColors.Default if the block no longer exists. | |||
/// </summary> | |||
public BlockColor Color | |||
{ | |||
get | |||
{ | |||
var opt = BlockEngine.GetBlockInfoOptional<ColourParameterEntityStruct>(this); | |||
return new BlockColor(opt ? opt.Get().indexInPalette : byte.MaxValue); | |||
} | |||
set | |||
{ | |||
if (value.Color == BlockColors.Default) | |||
value = new BlockColor(FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld) | |||
? cld.DefaultColour | |||
: throw new BlockTypeException("Unknown block type! Could not set default color.")); | |||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this); | |||
color.indexInPalette = value.Index; | |||
color.hasNetworkChange = true; | |||
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black | |||
} | |||
} | |||
/// <summary> | |||
/// The block's exact color. Gets reset to the palette color (Color property) after reentering the game. | |||
/// </summary> | |||
public float4 CustomColor | |||
{ | |||
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this).paletteColour; | |||
set | |||
{ | |||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this); | |||
color.paletteColour = value; | |||
color.hasNetworkChange = true; | |||
} | |||
} | |||
/** | |||
* The block's material. | |||
*/ | |||
public BlockMaterial Material | |||
{ | |||
get | |||
{ | |||
var opt = BlockEngine.GetBlockInfoOptional<CubeMaterialStruct>(this); | |||
return opt ? (BlockMaterial) opt.Get().materialId : BlockMaterial.Default; | |||
} | |||
set | |||
{ | |||
byte val = (byte) value; | |||
if (value == BlockMaterial.Default) | |||
val = FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld) | |||
? cld.DefaultMaterialID | |||
: throw new BlockTypeException("Unknown block type! Could not set default material."); | |||
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val)) | |||
throw new BlockException($"Block material {value} does not exist!"); | |||
ref var comp = ref BlockEngine.GetBlockInfo<CubeMaterialStruct>(this); | |||
if (comp.materialId == val) | |||
return; | |||
comp.materialId = val; | |||
BlockEngine.UpdatePrefab(this, val, Flipped); //The default causes the screen to go black | |||
} | |||
} | |||
/// <summary> | |||
/// The text displayed on the block if applicable, or null. | |||
/// Setting it is temporary to the session, it won't be saved. | |||
/// </summary> | |||
[TestValue(null)] | |||
public string Label | |||
{ | |||
get => BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent?.text; | |||
set | |||
{ | |||
var comp = BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent; | |||
if (comp != null) comp.text = value; | |||
} | |||
} | |||
private BlockGroup blockGroup; | |||
/// <summary> | |||
/// Returns the block group this block is a part of. Block groups can also be placed using blueprints. | |||
/// Returns null if not part of a group, although all blocks should have their own by default.<br /> | |||
/// Setting the group after the block has been initialized will not update everything properly, | |||
/// so you can only set this property on blocks newly placed by your code.<br /> | |||
/// To set it for existing blocks, you can use the Copy() method and set the property on the resulting block | |||
/// (and remove this block). | |||
/// </summary> | |||
public BlockGroup BlockGroup | |||
{ | |||
get | |||
{ | |||
if (blockGroup != null) return blockGroup; | |||
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this); | |||
return blockGroup = bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this); | |||
} | |||
set | |||
{ | |||
if (Exists) | |||
{ | |||
Logging.LogWarning("Attempted to set group of existing block. This is not supported." | |||
+ " Copy the block and set the group of the resulting block."); | |||
return; | |||
} | |||
blockGroup?.RemoveInternal(this); | |||
if (!InitData.Valid) | |||
return; | |||
BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1; | |||
value?.AddInternal(this); | |||
blockGroup = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Whether the block should be static in simulation. If set, it cannot be moved. The effect is temporary, it will not be saved with the block. | |||
/// </summary> | |||
public bool Static | |||
{ | |||
get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic; | |||
set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value; | |||
} | |||
/// <summary> | |||
/// Whether the block exists. The other properties will return a default value if the block doesn't exist. | |||
/// If the block was just placed, then this will also return false but the properties will work correctly. | |||
/// </summary> | |||
public bool Exists => BlockEngine.BlockExists(Id); | |||
/// <summary> | |||
/// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist. | |||
/// </summary> | |||
public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id); | |||
/// <summary> | |||
/// Removes this block. | |||
/// </summary> | |||
/// <returns>True if the block exists and could be removed.</returns> | |||
public bool Remove() => RemovalEngine.RemoveBlock(Id); | |||
/// <summary> | |||
/// Returns the rigid body of the chunk of blocks this one belongs to during simulation. | |||
/// Can be used to apply forces or move the block around while the simulation is running. | |||
/// </summary> | |||
/// <returns>The SimBody of the chunk or null if the block doesn't exist or not in simulation mode.</returns> | |||
public SimBody GetSimBody() | |||
{ | |||
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this); | |||
return st.machineRigidBodyId != uint.MaxValue | |||
? new SimBody(st.machineRigidBodyId, st.clusterId) | |||
: null; | |||
} | |||
/// <summary> | |||
/// Creates a copy of the block in the game with the same properties, stats and wires. | |||
/// </summary> | |||
/// <returns></returns> | |||
public Block Copy() | |||
{ | |||
var block = PlaceNew(Type, Position); | |||
block.Rotation = Rotation; | |||
block.Color = Color; | |||
block.Material = Material; | |||
block.UniformScale = UniformScale; | |||
block.Scale = Scale; | |||
block.copiedFrom = Id; | |||
return block; | |||
} | |||
private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e) | |||
{ //Member method instead of lambda to avoid constantly creating delegates | |||
if (e.ID != Id) return; | |||
Placed -= OnPlacedInit; //And we can reference it | |||
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again | |||
if (copiedFrom != default) | |||
BlockCloneEngine.CopyBlockStats(copiedFrom, Id); | |||
} | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Type)}: {Type}, {nameof(Color)}: {Color}, {nameof(Exists)}: {Exists}"; | |||
} | |||
public bool Equals(Block other) | |||
{ | |||
if (ReferenceEquals(null, other)) return false; | |||
if (ReferenceEquals(this, other)) return true; | |||
return Id.Equals(other.Id); | |||
} | |||
public bool Equals(EGID other) | |||
{ | |||
return Id.Equals(other); | |||
} | |||
public override bool Equals(object obj) | |||
{ | |||
if (ReferenceEquals(null, obj)) return false; | |||
if (ReferenceEquals(this, obj)) return true; | |||
if (obj.GetType() != this.GetType()) return false; | |||
return Equals((Block) obj); | |||
} | |||
public override int GetHashCode() | |||
{ | |||
return Id.GetHashCode(); | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(PlacementEngine); | |||
GameEngineManager.AddGameEngine(MovementEngine); | |||
GameEngineManager.AddGameEngine(RotationEngine); | |||
GameEngineManager.AddGameEngine(RemovalEngine); | |||
GameEngineManager.AddGameEngine(BlockEngine); | |||
GameEngineManager.AddGameEngine(BlockEventsEngine); | |||
GameEngineManager.AddGameEngine(ScalingEngine); | |||
GameEngineManager.AddGameEngine(SignalEngine); | |||
GameEngineManager.AddGameEngine(BlockCloneEngine); | |||
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine | |||
} | |||
} | |||
} |
@@ -1,39 +1,62 @@ | |||
using System; | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using Gamecraft.Blocks.BlockGroups; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Blocks.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI | |||
namespace TechbloxModdingAPI | |||
{ | |||
/// <summary> | |||
/// A group of blocks that can be selected together. The placed version of blueprints. | |||
/// A group of blocks that can be selected together. The placed version of blueprints. Dispose after usage. | |||
/// </summary> | |||
public class BlockGroup : ICollection<Block> | |||
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable | |||
{ | |||
internal static BlueprintEngine _engine = new BlueprintEngine(); | |||
public int Id { get; } | |||
private readonly Block sourceBlock; | |||
private readonly List<Block> blocks; | |||
private float3 position, rotation; | |||
internal bool PosAndRotCalculated; | |||
internal BlockGroup(int id, Block block) | |||
internal BlockGroup(int id, Block block) : base(new EGID((uint)id, | |||
BlockGroupExclusiveGroups.BlockGroupEntityGroup)) | |||
{ | |||
if (id == BlockGroupUtility.GROUP_UNASSIGNED) | |||
throw new BlockException("Cannot create a block group for blocks without a group!"); | |||
Id = id; | |||
sourceBlock = block; | |||
blocks = new List<Block>(GetBlocks()); | |||
Block.Removed += OnBlockRemoved; | |||
} | |||
private void OnBlockRemoved(object sender, BlockPlacedRemovedEventArgs e) | |||
{ | |||
//blocks.RemoveAll(block => block.Id == e.ID); - Allocation heavy | |||
int index = -1; | |||
for (int i = 0; i < blocks.Count; i++) | |||
{ | |||
if (blocks[i].Id == e.ID) | |||
{ | |||
index = i; | |||
break; | |||
} | |||
} | |||
if (index != -1) blocks.RemoveAt(index); | |||
} | |||
public void Dispose() | |||
{ | |||
Block.Removed -= OnBlockRemoved; | |||
} | |||
/// <summary> | |||
/// The position of the block group (center). Recalculated if blocks have been added/removed since the last query. | |||
/// The position of the block group (center). Can only be used after initialization is complete. | |||
/// </summary> | |||
public float3 Position | |||
{ | |||
@@ -56,7 +79,7 @@ namespace GamecraftModdingAPI | |||
} | |||
/// <summary> | |||
/// The rotation of the block group. Recalculated if blocks have been added/removed since the last query. | |||
/// The rotation of the block group. Can only be used after initialization is complete. | |||
/// </summary> | |||
public float3 Rotation | |||
{ | |||
@@ -93,13 +116,15 @@ namespace GamecraftModdingAPI | |||
/// <summary> | |||
/// Creates a new block group consisting of a single block. | |||
/// You can add more blocks using the Add() method or by setting the BlockGroup property of the blocks.<br /> | |||
/// Note that only newly placed blocks should be added to groups. | |||
/// Note that only newly placed blocks can be added to groups. | |||
/// </summary> | |||
/// <param name="block">The block to add</param> | |||
/// <returns>A new block group containing the given block</returns> | |||
public static BlockGroup Create(Block block) | |||
{ | |||
return new BlockGroup(_engine.CreateBlockGroup(default, default), block); | |||
var bg = new BlockGroup(_engine.CreateBlockGroup(block.Position, Quaternion.Euler(block.Rotation)), block); | |||
block.BlockGroup = bg; | |||
return bg; | |||
} | |||
/// <summary> | |||
@@ -131,7 +156,7 @@ namespace GamecraftModdingAPI | |||
IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator(); | |||
/// <summary> | |||
/// Adds a block to the group. You should only add newly placed blocks | |||
/// Adds a block to the group. You can only add newly placed blocks | |||
/// so that the game initializes the group membership properly. | |||
/// </summary> | |||
/// <param name="item"></param> | |||
@@ -142,11 +167,15 @@ namespace GamecraftModdingAPI | |||
item.BlockGroup = this; //Calls AddInternal | |||
} | |||
internal void AddInternal(Block item) => blocks.Add(item); | |||
internal void AddInternal(Block item) | |||
{ | |||
blocks.Add(item); | |||
_engine.AddBlockToGroup(item.Id, (int) Id.entityID); | |||
} | |||
/// <summary> | |||
/// Removes all blocks from this group. | |||
/// You should not remove blocks that have been initialized, only those that you placed recently. | |||
/// You cannot remove blocks that have been initialized, only those that you placed recently. | |||
/// </summary> | |||
public void Clear() | |||
{ | |||
@@ -159,7 +188,7 @@ namespace GamecraftModdingAPI | |||
/// <summary> | |||
/// Removes a block from this group. | |||
/// You should not remove blocks that have been initialized, only those that you placed recently. | |||
/// You cannot remove blocks that have been initialized, only those that you placed recently. | |||
/// </summary> | |||
/// <param name="item"></param> | |||
/// <returns></returns> | |||
@@ -179,5 +208,10 @@ namespace GamecraftModdingAPI | |||
public bool IsReadOnly { get; } = false; | |||
public Block this[int index] => blocks[index]; //Setting is not supported, since the order doesn't matter | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Count)}: {Count}"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
using System; | |||
using Unity.Mathematics; | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
public struct BlockColor | |||
{ | |||
public BlockColors Color => Index == byte.MaxValue | |||
? BlockColors.Default | |||
: (BlockColors) (Index % 10); | |||
public byte Darkness => (byte) (Index == byte.MaxValue | |||
? 0 | |||
: Index / 10); | |||
public byte Index { get; } | |||
public BlockColor(byte index) | |||
{ | |||
if (index > 99 && index != byte.MaxValue) | |||
throw new ArgumentOutOfRangeException(nameof(index), "Invalid color index. Must be 0-90 or 255."); | |||
Index = index; | |||
} | |||
public BlockColor(BlockColors color, byte darkness = 0) | |||
{ | |||
if (darkness > 9) | |||
throw new ArgumentOutOfRangeException(nameof(darkness), "Darkness must be 0-9 where 0 is default."); | |||
if (color > BlockColors.Red && color != BlockColors.Default) //Last valid color | |||
throw new ArgumentOutOfRangeException(nameof(color), "Invalid color!"); | |||
Index = (byte) (darkness * 10 + (byte) color); | |||
} | |||
public static implicit operator BlockColor(BlockColors color) | |||
{ | |||
return new BlockColor(color); | |||
} | |||
public float4 RGBA => Block.BlockEngine.ConvertBlockColor(Index); | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Color)}: {Color}, {nameof(Darkness)}: {Darkness}"; | |||
} | |||
} | |||
/// <summary> | |||
/// Preset block colours | |||
/// </summary> | |||
public enum BlockColors : byte | |||
{ | |||
Default = byte.MaxValue, | |||
White = 0, | |||
Pink, | |||
Purple, | |||
Blue, | |||
Aqua, | |||
Green, | |||
Lime, | |||
Yellow, | |||
Orange, | |||
Red | |||
} | |||
} |
@@ -1,10 +1,10 @@ | |||
using System; | |||
using GamecraftModdingAPI; | |||
using TechbloxModdingAPI; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
public class BlockException : GamecraftModdingAPIException | |||
public class BlockException : TechbloxModdingAPIException | |||
{ | |||
public BlockException() | |||
{ |
@@ -0,0 +1,278 @@ | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Possible block types | |||
/// </summary> | |||
public enum BlockIDs : ushort | |||
{ | |||
/// <summary> | |||
/// Called "nothing" in Techblox. (DBID.NOTHING) | |||
/// </summary> | |||
Invalid = ushort.MaxValue, | |||
Cube = 0, | |||
Wedge, | |||
QuarterPyramid, | |||
Tetrahedron, | |||
RoundedWedge, | |||
RoundedQuarterPyramid, | |||
RoundedTetrahedron, | |||
NegativeQuarterPyramid, | |||
NegativeTetrahedron, | |||
RoundedNegativeQuarterPyramid, | |||
RoundedNegativeTetrahedron, | |||
Plate, | |||
PlateWedge, | |||
PlateQuarterPyramid, | |||
PlateTetrahedron, | |||
Sphere, | |||
CarWheelArch = 47, | |||
CarArchSmallFlare, | |||
CarArchFlare, | |||
CarArchExtrudedFlare, | |||
Axle = 100, | |||
Hinge, | |||
BallJoint, | |||
UniversalJoint, | |||
TelescopicJoint, | |||
DampedHingeSpring, | |||
DampedAxleSpring, | |||
DampedSpring, | |||
WheelRigNoSteering, | |||
WheelRigWithSteering, | |||
PlateTriangle = 130, | |||
PlateCircle, | |||
PlateQuarterCircle, | |||
PlateRoundedWedge, | |||
PlateRoundedTetrahedron, | |||
Cone, | |||
ConeSegment, | |||
DoubleSliced, | |||
HalfDoubleSliced, | |||
EighthPyramid, | |||
Hemisphere, | |||
WideCylinder, | |||
WideCylinderBend, | |||
WideCylinderT, | |||
WideCylinderCross, | |||
WideCylinderCorner, | |||
NarrowCylinder, | |||
NarrowCylinderBend, | |||
NarrowCylinderT, | |||
NarrowCylinderCross, | |||
DriverSeat, | |||
PassengerSeat, | |||
Engine, | |||
NarrowCylinderCorner, | |||
PlateWideCylinder, | |||
PlateNarrowCylinder, | |||
PlateNegativeTetrahedron, | |||
PlateNegativeQuarterPyramid, | |||
PlateRoundedNegativeTetrahedron, | |||
PlateRoundedNegativeQuarterPyramid, | |||
HeadlampSquare, | |||
HeadlampCircle, | |||
HeadlampWedge, | |||
WideCylinderDiagonal, | |||
NarrowCylinderDiagonal, | |||
HeadlampTetrahedron, | |||
GoKartEngine, | |||
Screen5X2Y2Z, | |||
Screen5X2Y3Z, | |||
Screen5X2Y5Z, | |||
Screen9X2Y2Z, | |||
Screen9X3Y2Z, | |||
Screen9X2Y3Z, | |||
Screen9X3Y3Z, | |||
Screen9X2Y5Z, | |||
Screen9X3Y5Z, | |||
Screen11X3Y2Z, | |||
Screen11X3Y3Z, | |||
Screen11X3Y5Z, | |||
Window6X2Y2Z, | |||
Window6X3Y2Z, | |||
Window6X2Y2ZS1, | |||
Window6X3Y2ZS1, | |||
Window6X2Y2ZS2, | |||
Window6X3Y2ZS2, | |||
Window6X2Y2ZS4, | |||
Window6X3Y2ZS4, | |||
FrameSquare, | |||
FrameSkewedSquare, | |||
FrameTriangle, | |||
FrameSkewedTriangle, | |||
GlassFrameSquare, | |||
GlassFrameSkewedSquare, | |||
GlassFrameTriangle, | |||
GlassFrameSkewedTriangle, | |||
GlassPlate, | |||
GlassPlateTriangle, | |||
GoKartWheelRigNoSteering, | |||
GoKartWheelRigWithSteering, | |||
GoKartSeat, | |||
CarWheelWideProfile, | |||
CarWheel, | |||
GoKartWheelWideProfile, | |||
GoKartWheel, | |||
ANDLogicGate, | |||
ORLogicGate, | |||
NOTLogicGate, | |||
NANDLogicGate, | |||
NORLogicGate, | |||
XORLogicGate, | |||
XNORLogicGate, | |||
AdderMathBlock, | |||
SubtractorMathBlock, | |||
MultiplierMathBlock, | |||
DividerMathBlock, | |||
InverterMathBlock, | |||
AverageMathBlock, | |||
AbsoluteMathBlock, | |||
MinMathBlock, | |||
MaxMathBlock, | |||
SimpleConnector, | |||
Motor, | |||
AxleServo, | |||
HingeServo, | |||
Piston, | |||
Button, | |||
Switch, | |||
Dial, | |||
Lever, | |||
ThreeWaySwitch, | |||
EqualsMathBlock, | |||
LessThanMathBlock, | |||
LessThanOrEqualMathBlock, | |||
GreaterThanMathBlock, | |||
GreaterThanOrEqualMathBlock, | |||
HatchbackWheelRigNoSteering, | |||
HatchbackWheelRigWithSteering, | |||
HatchbackEngine, | |||
HatchbackWheel, | |||
HatchbackWheelArch, | |||
HatchbackArchSmallFlare, | |||
HatchbackArchFlare, | |||
CeilingStripLight, | |||
CardboardBox, | |||
BarrierRail, | |||
BarrierRailEnd, | |||
TruckWheel, | |||
HatchbackWheelWideProfile, | |||
TruckWheelRigWithSteering = 249, | |||
TruckWheelRigNoSteering, | |||
HatchbackDriverSeat, | |||
HatchbackPassengerSeat, | |||
FormulaEngine, | |||
SmallGrass, | |||
SmallGrassRoad, | |||
GrassBridge, | |||
SmallGrassTurn, | |||
MediumGrassTurn, | |||
LargeGrassTurn, | |||
ExtraLargeGrassTurn, | |||
TruckWheelDouble, | |||
TruckWheelArch, | |||
TruckArchSingleFlare, | |||
WoodenDoorWithWindow, | |||
TyreBarrierCorner, | |||
TyreBarrierEdge, | |||
TyreBarrierCenter, | |||
AppleTree, | |||
AppleForestTree, | |||
FormulaWheel, | |||
FormulaWheelRear, | |||
AppleSapling, | |||
GrassHill, | |||
GrassHillInnerCorner, | |||
GrassHillOuterCorner, | |||
GrassRoadHill, | |||
FormulaSeat, | |||
SmallDirt, | |||
SmallDirtRoad, | |||
SmallDirtTurn, | |||
MediumDirtTurn, | |||
LargeDirtTurn, | |||
ExtraLargeDirtTurn, | |||
SmallGrid, | |||
MonsterTruckWheel, | |||
SmallGrassGridStart, | |||
SmallGrassRumbleStripRoad, | |||
SmallGrassRumbleStripEndRoad, | |||
SmallGrassStartLine, | |||
MonsterTruckEngine, | |||
DirtHill, | |||
DirtHillInnerCorner, | |||
DirtHillOuterCorner, | |||
BuildingWindowEdge, | |||
BuildingWindowCorner, | |||
BuildingWindowStraight, | |||
BuildingWindowTJunction, | |||
BuildingWindowCross, | |||
BuildingWindowEdgeSill, | |||
BuildingWindowCornerSill, | |||
BuildingWindowTJunctionSill, | |||
Broadleaf, | |||
ForestBroadleaf, | |||
AzaleaBush, | |||
AzaleaFlowers1, | |||
AzaleaFlowers2, | |||
TreeStump1, | |||
TreeStump2, | |||
FieldJuniper, | |||
ForestJuniper, | |||
JuniperSapling, | |||
JuniperSeedling, | |||
FieldRedMaple, | |||
RedMapleForest1, | |||
RedMapleForest2, | |||
RedMapleSapling, | |||
FieldWhiteSpruce, | |||
ForestWhiteSpruce, | |||
WhiteSpruceSapling, | |||
GirderBase, | |||
GirderStraight, | |||
GirderDiagonal, | |||
GirderCorner, | |||
PostBase, | |||
PostStraight, | |||
PostLShape, | |||
PostTJunction, | |||
PostCross, | |||
PostCorner, | |||
PostDiagonal, | |||
DirtRock1, | |||
DirtRock2, | |||
DirtRock3, | |||
DirtRock4, | |||
DirtRoadHill, | |||
WoodenPalette, | |||
ElderberryBush, | |||
BarrelCactus, | |||
KnapweedFlower, | |||
MarigoldFlowers, | |||
TrampledBushyBluestep, | |||
RoughGrass, | |||
DogRose, | |||
WesternSwordFern, | |||
BackyardGrass, | |||
ThickGrass, | |||
FireExtinguisher, | |||
DirtLowRamp, | |||
DirtTabletopRamp, | |||
MonsterTruckWheelRigNoSteering, | |||
MonsterTruckWheelRigWithSteering, | |||
MeadowCloudyDayAtmosphere, | |||
BarrierRailDiagonal, | |||
DirtHighRamp, | |||
GrassRock1, | |||
GrassRock2, | |||
GrassRock3, | |||
GrassRock4, | |||
GreenFieldsSunnyDayAtmosphere, | |||
RedMountainsDawnAtmosphere, | |||
HighFantasySunriseAtmosphere, | |||
/// <summary> | |||
/// The grid block used by the world editor, named Small Grid like the other one | |||
/// </summary> | |||
SmallGridInWorldEditor | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
public enum BlockMaterial : byte | |||
{ | |||
Default = byte.MaxValue, | |||
SteelBodywork = 0, | |||
RigidSteel, | |||
CarbonFiber, | |||
Plastic, | |||
Wood = 6, | |||
RigidSteelPainted, | |||
RigidSteelRustedPaint, | |||
RigidSteelHeavyRust, | |||
SteelBodyworkMetallicPaint, | |||
SteelBodyworkRustedPaint, | |||
SteelBodyworkHeavyRust, | |||
WoodVarnishedDark, | |||
Chrome, | |||
FenceChainLink, | |||
ConcreteUnpainted, | |||
Grid9x9, | |||
CeramicTileFloor, | |||
PlasticBumpy, | |||
PlasticDustySmeared, | |||
AluminiumGarageDoor, | |||
SteelRigidScratched, | |||
AluminiumBrushedTinted, | |||
AluminiumSheetStained, | |||
ConcretePaintedGrooves, | |||
PlasticSpecklySatin, | |||
SteelBodyworkPaintedChipped, | |||
WoodPainted, | |||
WoodRoughGrungy, | |||
} | |||
} |
@@ -0,0 +1,251 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using DataLoader; | |||
using Svelto.Tasks; | |||
using Svelto.Tasks.Enumerators; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using TechbloxModdingAPI.App; | |||
using TechbloxModdingAPI.Tests; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
#if TEST | |||
/// <summary> | |||
/// Block test cases. Not accessible in release versions. | |||
/// </summary> | |||
[APITestClass] | |||
public static class BlockTests | |||
{ | |||
[APITestCase(TestType.Game)] //At least one block must be placed for simulation to work | |||
public static void TestPlaceNew() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero); | |||
Assert.NotNull(newBlock.Id, "Newly placed block is missing Id. This should be populated when the block is placed.", "Newly placed block Id is not null, block successfully placed."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestInitProperty() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.Cube, float3.zero + 2); | |||
if (!Assert.CloseTo(newBlock.Position, (float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return; | |||
//Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestBlockIDCoverage() | |||
{ | |||
Assert.Equal( | |||
FullGameFields._dataDb.GetValues<CubeListData>().Keys.Select(ushort.Parse).OrderBy(id => id) | |||
.SequenceEqual(Enum.GetValues(typeof(BlockIDs)).Cast<ushort>().OrderBy(id => id) | |||
.Except(new[] {(ushort) BlockIDs.Invalid})), true, | |||
"Block ID enum is different than the known block types, update needed.", | |||
"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(); | |||
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 | |||
{ | |||
blocks[i++] = Block.PlaceNew(id, pos); | |||
pos += 0.2f; | |||
} | |||
catch (Exception e) | |||
{ //Only print failed case | |||
Assert.Fail($"Failed to place block type {id}: {e}"); | |||
return; | |||
} | |||
} | |||
Assert.Pass("Placing all possible block types succeeded."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static IEnumerator<TaskContract> TestBlockProperties() | |||
{ //Uses the result of the previous test case | |||
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 | |||
var block = blocks[index]; | |||
if (!block.Exists) continue; | |||
foreach (var property in block.GetType().GetProperties()) | |||
{ | |||
if (property.Name == "Material" || property.Name == "Flipped") continue; // TODO: Crashes in game | |||
if (property.Name == "Material" || property.Name == "Flipped") | |||
{ | |||
Console.WriteLine("Block type: "+block.Type); | |||
Console.WriteLine("Will set " + property.Name); | |||
yield return new WaitForSecondsEnumerator(1).Continue(); | |||
} | |||
//Includes specialised block properties | |||
if (property.SetMethod == null) continue; | |||
var testValues = new (Type, object, Predicate<object>)[] | |||
{ | |||
//(type, default value, predicate or null for equality) | |||
(typeof(long), 3, null), | |||
(typeof(int), 4, null), | |||
(typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon), | |||
(typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon), | |||
(typeof(bool), true, obj => (bool) obj), | |||
(typeof(string), "Test", obj => (string) obj == "Test"), //String equality check | |||
(typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)), | |||
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null), | |||
(typeof(float4), (float4) 5, obj => math.all((float4) obj - 5 < (float4) float.Epsilon)) | |||
}; | |||
var propType = property.PropertyType; | |||
if (!propType.IsValueType) continue; | |||
(object valueToUse, Predicate<object> predicateToUse) = (null, null); | |||
foreach (var (type, value, predicate) in testValues) | |||
{ | |||
if (type.IsAssignableFrom(propType)) | |||
{ | |||
valueToUse = value; | |||
predicateToUse = predicate ?? (obj => Equals(obj, value)); | |||
break; | |||
} | |||
} | |||
if (propType.IsEnum) | |||
{ | |||
var values = propType.GetEnumValues(); | |||
valueToUse = values.GetValue(values.Length / 2); | |||
predicateToUse = val => Equals(val, valueToUse); | |||
} | |||
if (valueToUse == null) | |||
{ | |||
Assert.Fail($"Property {block.GetType().Name}.{property.Name} has an unknown type {propType}, test needs fixing."); | |||
yield break; | |||
} | |||
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))) | |||
{ | |||
Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}."); | |||
yield break; | |||
} | |||
} | |||
} | |||
Assert.Pass("Setting all possible properties of all registered API block types succeeded."); | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static IEnumerator<TaskContract> TestDefaultValue() | |||
{ | |||
for (int i = 0; i < 2; i++) | |||
{ //Tests shared defaults | |||
var block = Block.PlaceNew(BlockIDs.Cube, 1); | |||
while (!block.Exists) | |||
yield return Yield.It; | |||
block.Remove(); | |||
while (block.Exists) | |||
yield return Yield.It; | |||
if(!Assert.Equal(block.Position, default, | |||
$"Block position default value {block.Position} is incorrect, should be 0.", | |||
$"Block position default value {block.Position} matches default.")) | |||
yield break; | |||
block.Position = 4; | |||
} | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestDampedSpring() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.DampedSpring, Unity.Mathematics.float3.zero + 1); | |||
DampedSpring b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = (DampedSpring) newBlock; }, "Casting block to DampedSpring raised an exception: ", "Casting block to DampedSpring completed without issue."); | |||
if (!Assert.CloseTo(b.Stiffness, 1f, $"DampedSpring.Stiffness {b.Stiffness} does not equal default value, possibly because it failed silently.", "DampedSpring.Stiffness is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.Damping, 0.1f, $"DampedSpring.Damping {b.Damping} does not equal default value, possibly because it failed silently.", "DampedSpring.Damping is close enough to default.")) return; | |||
if (!Assert.CloseTo(b.MaxExtension, 0.3f, $"DampedSpring.MaxExtension {b.MaxExtension} does not equal default value, possibly because it failed silently.", "DampedSpring.MaxExtension is close enough to default.")) return; | |||
} | |||
/*[APITestCase(TestType.Game)] | |||
public static void TestMusicBlock1() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2); | |||
MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<MusicBlock>(); }, "Block.Specialize<MusicBlock>() raised an exception: ", "Block.Specialize<MusicBlock>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; | |||
if (!Assert.CloseTo(b.Volume, 100f, $"MusicBlock.Volume {b.Volume} does not equal default value, possibly because it failed silently.", "MusicBlock.Volume is close enough to default.")) return; | |||
if (!Assert.Equal(b.TrackIndex, 0, $"MusicBlock.TrackIndex {b.TrackIndex} does not equal default value, possibly because it failed silently.", "MusicBlock.TrackIndex is equal to default.")) return; | |||
_musicBlock = b; | |||
} | |||
private static MusicBlock _musicBlock; | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestMusicBlock2() | |||
{ | |||
//Block newBlock = Block.GetLastPlacedBlock(); | |||
var b = _musicBlock; | |||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return; | |||
b.IsPlaying = true; // play sfx | |||
if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return; | |||
if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return; | |||
//Assert.Log(b.Track.ToString()); | |||
if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return; | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestLogicGate() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1); | |||
LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler | |||
Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue."); | |||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; | |||
if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return; | |||
if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return; | |||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return; | |||
//if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return; | |||
LogicGate target = null; | |||
if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return; | |||
Wire newWire = null; | |||
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return; | |||
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return; | |||
}*/ | |||
/*[APITestCase(TestType.EditMode)] | |||
public static void TestSpecialiseError() | |||
{ | |||
Block newBlock = Block.PlaceNew(BlockIDs.Bench, new float3(1, 1, 1)); | |||
if (Assert.Errorful<BlockTypeException>(() => newBlock.Specialise<MusicBlock>(), "Block.Specialise<MusicBlock>() was expected to error on a bench block.", "Block.Specialise<MusicBlock>() errored as expected for a bench block.")) return; | |||
}*/ | |||
} | |||
#endif | |||
} |
@@ -0,0 +1,71 @@ | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
public class DampedSpring : SignalingBlock | |||
{ | |||
/// <summary> | |||
/// Constructs a(n) DampedSpring object representing an existing block. | |||
/// </summary> | |||
public DampedSpring(EGID egid) : | |||
base(egid) | |||
{ | |||
} | |||
/// <summary> | |||
/// Constructs a(n) DampedSpring object representing an existing block. | |||
/// </summary> | |||
public DampedSpring(uint id) : | |||
base(new EGID(id, CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP)) | |||
{ | |||
} | |||
/// <summary> | |||
/// Gets or sets the DampedSpring's Stiffness property. Tweakable stat. | |||
/// </summary> | |||
public float Stiffness | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).stiffness = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the DampedSpring's Damping property. Tweakable stat. | |||
/// </summary> | |||
public float Damping | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).damping; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<RobocraftX.Blocks.TweakableJointDampingComponent>(this).damping = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the DampedSpring's MaxExtension property. Tweakable stat. | |||
/// </summary> | |||
public float MaxExtension | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<RobocraftX.Blocks.DampedSpringReadOnlyStruct>(this).maxExtent; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<RobocraftX.Blocks.DampedSpringReadOnlyStruct>(this).maxExtent = value; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,382 @@ | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
public class Engine : SignalingBlock | |||
{ | |||
/// <summary> | |||
/// Constructs a(n) Engine object representing an existing block. | |||
/// </summary> | |||
public Engine(EGID egid) : | |||
base(egid) | |||
{ | |||
} | |||
/// <summary> | |||
/// Constructs a(n) Engine object representing an existing block. | |||
/// </summary> | |||
public Engine(uint id) : | |||
base(new EGID(id, CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP)) | |||
{ | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's On property. May not be saved. | |||
/// </summary> | |||
public bool On | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's CurrentGear property. May not be saved. | |||
/// </summary> | |||
public int CurrentGear | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's GearChangeCountdown property. May not be saved. | |||
/// </summary> | |||
public float GearChangeCountdown | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's CurrentRpmAV property. May not be saved. | |||
/// </summary> | |||
public float CurrentRpmAV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's CurrentRpmLV property. May not be saved. | |||
/// </summary> | |||
public float CurrentRpmLV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's TargetRpmAV property. May not be saved. | |||
/// </summary> | |||
public float TargetRpmAV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's TargetRpmLV property. May not be saved. | |||
/// </summary> | |||
public float TargetRpmLV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's CurrentTorque property. May not be saved. | |||
/// </summary> | |||
public float CurrentTorque | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's TotalWheelVelocityAV property. May not be saved. | |||
/// </summary> | |||
public float TotalWheelVelocityAV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's TotalWheelVelocityLV property. May not be saved. | |||
/// </summary> | |||
public float TotalWheelVelocityLV | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's TotalWheelCount property. May not be saved. | |||
/// </summary> | |||
public int TotalWheelCount | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's LastGearUpInput property. May not be saved. | |||
/// </summary> | |||
public bool LastGearUpInput | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's LastGearDownInput property. May not be saved. | |||
/// </summary> | |||
public bool LastGearDownInput | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's ManualToAutoGearCoolOffCounter property. May not be saved. | |||
/// </summary> | |||
public float ManualToAutoGearCoolOffCounter | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's Load property. May not be saved. | |||
/// </summary> | |||
public float Load | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's Power property. Tweakable stat. | |||
/// </summary> | |||
public float Power | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).power = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's AutomaticGears property. Tweakable stat. | |||
/// </summary> | |||
public bool AutomaticGears | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockTweakableComponent>(this).automaticGears = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's GearChangeTime property. May not be saved. | |||
/// </summary> | |||
public float GearChangeTime | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearChangeTime = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's MinRpm property. May not be saved. | |||
/// </summary> | |||
public float MinRpm | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).minRpm = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's MaxRpm property. May not be saved. | |||
/// </summary> | |||
public float MaxRpm | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpm = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets the Engine's GearDownRpms property. May not be saved. | |||
/// </summary> | |||
public float[] GearDownRpms | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms.ToManagedArray<float>(); | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's GearUpRpm property. May not be saved. | |||
/// </summary> | |||
public float GearUpRpm | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearUpRpm = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's MaxRpmChange property. May not be saved. | |||
/// </summary> | |||
public float MaxRpmChange | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).maxRpmChange = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets the Engine's ManualToAutoGearCoolOffTime property. May not be saved. | |||
/// </summary> | |||
public float ManualToAutoGearCoolOffTime | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,105 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Gamecraft.Wires; | |||
using HarmonyLib; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Character; | |||
using RobocraftX.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using TechbloxModdingAPI.Engines; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
public class BlockCloneEngine : IApiEngine | |||
{ | |||
private static Type copyEngineType = | |||
AccessTools.TypeByName("Gamecraft.GUI.Tweaks.Engines.CopyTweaksOnPickEngine"); | |||
private static Type copyWireEngineType = | |||
AccessTools.TypeByName("Gamecraft.Wires.WireConnectionCopyOnPickEngine"); | |||
private static Type createWireEngineType = | |||
AccessTools.TypeByName("RobocraftX.GUI.Wires.WireConnectionCreateOnPlaceEngine"); | |||
private MethodBase copyFromBlock = AccessTools.Method(copyEngineType, "CopyTweaksFromBlock"); | |||
private MethodBase copyToBlock = AccessTools.Method(copyEngineType, "ApplyTweaksToPlacedBlock"); | |||
private MethodBase copyWireFromBlock = AccessTools.Method(copyWireEngineType, "CopyWireInputsAndOutputs"); | |||
private MethodBase copyWireToBlock = AccessTools.Method(createWireEngineType, "PlaceWiresOnPlaceNewCube"); | |||
public void Ready() | |||
{ | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
public void Dispose() | |||
{ | |||
} | |||
public void CopyBlockStats(EGID sourceID, EGID targetID) | |||
{ | |||
var allCharacters = (LocalFasterReadOnlyList<ExclusiveGroupStruct>) CharacterExclusiveGroups.AllCharacters; | |||
foreach (var ((pickedBlockColl, count), _) in entitiesDB.QueryEntities<PickedBlockExtraDataStruct>(allCharacters)) | |||
{ | |||
for (int i = 0; i < count; ++i) | |||
{ | |||
ref PickedBlockExtraDataStruct pickedBlock = ref pickedBlockColl[i]; | |||
var oldStruct = pickedBlock; | |||
pickedBlock.pickedBlockEntityID = sourceID; | |||
pickedBlock.placedBlockEntityID = targetID; | |||
pickedBlock.placedBlockTweaksMustCopy = true; | |||
if (entitiesDB.Exists<DBEntityStruct>(pickedBlock.pickedBlockEntityID) | |||
&& entitiesDB.Exists<DBEntityStruct>(pickedBlock.placedBlockEntityID)) | |||
{ | |||
copyFromBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock}); | |||
uint playerID = Player.LocalPlayer.Id; | |||
var parameters = new object[] {playerID, pickedBlock}; | |||
copyWireFromBlock.Invoke(Patch.copyWireEngine, parameters); | |||
pickedBlock = (PickedBlockExtraDataStruct) parameters[1]; //ref arg | |||
copyToBlock.Invoke(Patch.copyEngine, new object[] {pickedBlock.ID, pickedBlock}); | |||
ExclusiveGroupStruct group = WiresExclusiveGroups.WIRES_COPY_GROUP + playerID; | |||
copyWireToBlock.Invoke(Patch.createWireEngine, new object[] {group, pickedBlock.ID}); | |||
pickedBlock.placedBlockTweaksMustCopy = false; | |||
} | |||
pickedBlock = oldStruct; //Make sure to not interfere with the game - Although that might not be the case with the wire copying | |||
} | |||
} | |||
} | |||
[HarmonyPatch] | |||
private static class Patch | |||
{ | |||
public static object copyEngine; | |||
public static object copyWireEngine; | |||
public static object createWireEngine; | |||
public static void Postfix(object __instance) | |||
{ | |||
if (__instance.GetType() == copyEngineType) | |||
copyEngine = __instance; | |||
else if (__instance.GetType() == copyWireEngineType) | |||
copyWireEngine = __instance; | |||
else if (__instance.GetType() == createWireEngineType) | |||
createWireEngine = __instance; | |||
} | |||
public static IEnumerable<MethodBase> TargetMethods() | |||
{ | |||
return new[] | |||
{ | |||
AccessTools.GetDeclaredConstructors(copyEngineType)[0], | |||
AccessTools.GetDeclaredConstructors(copyWireEngineType)[0], | |||
AccessTools.GetDeclaredConstructors(createWireEngineType)[0] | |||
}; | |||
} | |||
} | |||
public string Name { get; } = "TechbloxModdingAPIBlockCloneGameEngine"; | |||
public bool isRemovable { get; } = false; | |||
} | |||
} |
@@ -0,0 +1,274 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
using Gamecraft.ColourPalette; | |||
using Gamecraft.TimeRunning; | |||
using Gamecraft.Wires; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using RobocraftX.Physics; | |||
using RobocraftX.Rendering; | |||
using RobocraftX.Rendering.GPUI; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Hybrid; | |||
using Techblox.BuildingDrone; | |||
using Unity.Mathematics; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
/// <summary> | |||
/// Engine for executing general block actions | |||
/// </summary> | |||
public partial class BlockEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "TechbloxModdingAPIBlockGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public void Dispose() | |||
{ | |||
} | |||
public void Ready() | |||
{ | |||
} | |||
public Block[] GetConnectedBlocks(EGID blockID) | |||
{ | |||
if (!BlockExists(blockID)) return new Block[0]; | |||
Stack<EGID> cubeStack = new Stack<EGID>(); | |||
FasterList<EGID> cubes = new FasterList<EGID>(10); | |||
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
foreach (var (ecoll, _) in coll) | |||
{ | |||
var ecollB = ecoll.ToBuffer(); | |||
for(int i = 0; i < ecoll.count; i++) | |||
{ | |||
ref var conn = ref ecollB.buffer[i]; | |||
conn.isProcessed = false; | |||
} | |||
} | |||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes, | |||
(in GridConnectionsEntityStruct g) => { return false; }); | |||
var ret = new Block[cubes.count]; | |||
for (int i = 0; i < cubes.count; i++) | |||
ret[i] = Block.New(cubes[i]); | |||
return ret; | |||
} | |||
public float4 ConvertBlockColor(byte index) => index == byte.MaxValue | |||
? new float4(-1f, -1f, -1f, -1f) | |||
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index, | |||
CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; | |||
public OptionalRef<T> GetBlockInfoOptional<T>(Block block) where T : unmanaged, IEntityComponent | |||
{ | |||
return entitiesDB.QueryEntityOptional<T>(block); | |||
} | |||
public ref T GetBlockInfo<T>(Block block) where T : unmanaged, IEntityComponent | |||
{ | |||
return ref entitiesDB.QueryEntityOrDefault<T>(block); | |||
} | |||
internal ref T GetBlockInfo<T>(EcsObjectBase obj) where T : unmanaged, IEntityComponent | |||
{ | |||
return ref entitiesDB.QueryEntityOrDefault<T>(obj); | |||
} | |||
public ref T GetBlockInfoViewComponent<T>(Block block) where T : struct, IEntityViewComponent | |||
{ | |||
return ref entitiesDB.QueryEntityOrDefault<T>(block); | |||
} | |||
public void UpdateDisplayedBlock(EGID id) | |||
{ | |||
if (!BlockExists(id)) return; | |||
RenderingPatch.UpdateBlocks(); | |||
} | |||
internal void UpdatePrefab(Block block, byte material, bool flipped) | |||
{ | |||
var prefabAssetIDOpt = entitiesDB.QueryEntityOptional<PrefabAssetIDComponent>(block); | |||
uint prefabAssetID = prefabAssetIDOpt | |||
? prefabAssetIDOpt.Get().prefabAssetID | |||
: uint.MaxValue; | |||
if (prefabAssetID == uint.MaxValue) | |||
{ | |||
if (entitiesDB.QueryEntityOptional<DBEntityStruct>(block)) //The block exists | |||
throw new BlockException("Prefab asset ID not found for block " + block); //Set by the game | |||
return; | |||
} | |||
uint prefabId = | |||
PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, material, 1, flipped); | |||
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId; | |||
if (block.Exists) | |||
{ | |||
entitiesDB.PublishEntityChange<CubeMaterialStruct>(block.Id); | |||
entitiesDB.PublishEntityChange<GFXPrefabEntityStructGPUI>(block.Id); | |||
ref BuildingActionComponent local = | |||
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility | |||
.GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB)); | |||
local.buildAction = BuildAction.ChangeMaterial; | |||
local.targetPosition = block.Position; | |||
this.entitiesDB.PublishEntityChange<BuildingActionComponent>(local.ID); | |||
} | |||
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData | |||
} | |||
public bool BlockExists(EGID blockID) | |||
{ | |||
return entitiesDB.Exists<DBEntityStruct>(blockID); | |||
} | |||
public SimBody[] GetSimBodiesFromID(byte id) | |||
{ | |||
var ret = new FasterList<SimBody>(4); | |||
var oide = entitiesDB.QueryEntities<ObjectIdEntityStruct>(); | |||
EGIDMapper<GridConnectionsEntityStruct>? connections = null; | |||
foreach (var ((oids, count), _) in oide) | |||
{ | |||
for (int i = 0; i < count; i++) | |||
{ | |||
ref ObjectIdEntityStruct oid = ref oids[i]; | |||
if (oid.objectId != id) continue; | |||
if (!connections.HasValue) //Would need reflection to get the group from the build group otherwise | |||
connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(oid.ID.groupID); | |||
var rid = connections.Value.Entity(oid.ID.entityID).machineRigidBodyId; | |||
foreach (var rb in ret) | |||
{ | |||
if (rb.Id.entityID == rid) | |||
goto DUPLICATE; //Multiple Object Identifiers on one rigid body | |||
} | |||
ret.Add(new SimBody(rid)); | |||
DUPLICATE: ; | |||
} | |||
} | |||
return ret.ToArray(); | |||
} | |||
public SimBody[] GetConnectedSimBodies(uint id) | |||
{ | |||
var joints = entitiesDB.QueryEntities<JointEntityStruct>(MachineSimulationGroups.JOINTS_GROUP).ToBuffer(); | |||
var list = new FasterList<SimBody>(4); | |||
for (int i = 0; i < joints.count; i++) | |||
{ | |||
ref var joint = ref joints.buffer[i]; | |||
if (joint.isBroken) continue; | |||
if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); | |||
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA)); | |||
} | |||
return list.ToArray(); | |||
} | |||
public SimBody[] GetClusterBodies(uint cid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
var bodies = new HashSet<uint>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
if (conn.clusterId == cid) | |||
bodies.Add(conn.machineRigidBodyId); | |||
} | |||
} | |||
return bodies.Select(id => new SimBody(id, cid)).ToArray(); | |||
} | |||
public EGID? FindBlockEGID(uint id) | |||
{ | |||
var groups = entitiesDB.FindGroups<DBEntityStruct>(); | |||
foreach (ExclusiveGroupStruct group in groups) | |||
{ | |||
if (entitiesDB.Exists<DBEntityStruct>(id, group)) | |||
return new EGID(id, group); | |||
} | |||
return null; | |||
} | |||
public Cluster GetCluster(uint sbid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
//Static blocks don't have a cluster ID but the cluster destruction manager should have one | |||
if (conn.machineRigidBodyId == sbid && conn.clusterId != uint.MaxValue) | |||
return new Cluster(conn.clusterId); | |||
} | |||
} | |||
return null; | |||
} | |||
public Block[] GetBodyBlocks(uint sbid) | |||
{ | |||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(); | |||
var set = new HashSet<Block>(); | |||
foreach (var (coll, _) in groups) | |||
{ | |||
var array = coll.ToBuffer().buffer; | |||
for (var index = 0; index < array.capacity; index++) | |||
{ | |||
var conn = array[index]; | |||
if (conn.machineRigidBodyId == sbid) | |||
set.Add(Block.New(conn.ID)); | |||
} | |||
} | |||
return set.ToArray(); | |||
} | |||
#if DEBUG | |||
public EntitiesDB GetEntitiesDB() | |||
{ | |||
return entitiesDB; | |||
} | |||
#endif | |||
[HarmonyPatch] | |||
public static class RenderingPatch | |||
{ | |||
private static ComputeRenderingEntitiesMatricesEngine Engine; | |||
public static void Postfix(ComputeRenderingEntitiesMatricesEngine __instance) | |||
{ | |||
Engine = __instance; | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return typeof(ComputeRenderingEntitiesMatricesEngine).GetConstructors()[0]; | |||
} | |||
public static void UpdateBlocks() | |||
{ | |||
var data = new RenderingDataStruct(); | |||
Engine.Add(ref data, new EGID(0, CommonExclusiveGroups.BUTTON_BLOCK_GROUP)); | |||
} | |||
} | |||
} | |||
} |
@@ -1,18 +1,17 @@ | |||
using System; | |||
using RobocraftX.Common; | |||
using RobocraftX.Blocks; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
using RobocraftX.Blocks; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
public class BlockEventsEngine : IReactionaryEngine<BlockTagEntityStruct> | |||
{ | |||
public event EventHandler<BlockPlacedRemovedEventArgs> Placed; | |||
public event EventHandler<BlockPlacedRemovedEventArgs> Removed; | |||
public WrappedHandler<BlockPlacedRemovedEventArgs> Placed; | |||
public WrappedHandler<BlockPlacedRemovedEventArgs> Removed; | |||
public void Ready() | |||
{ | |||
@@ -24,7 +23,7 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine"; | |||
public string Name { get; } = "TechbloxModdingAPIBlockEventsEngine"; | |||
public bool isRemovable { get; } = false; | |||
private bool shouldAddRemove; | |||
@@ -32,25 +31,22 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (!(shouldAddRemove = !shouldAddRemove)) | |||
return; | |||
ExceptionUtil.InvokeEvent(Placed, this, | |||
new BlockPlacedRemovedEventArgs {ID = egid}); | |||
Placed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid}); | |||
} | |||
public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid) | |||
{ | |||
if (!(shouldAddRemove = !shouldAddRemove)) | |||
return; | |||
ExceptionUtil.InvokeEvent(Removed, this, | |||
new BlockPlacedRemovedEventArgs {ID = egid}); | |||
Removed.Invoke(this, new BlockPlacedRemovedEventArgs {ID = egid}); | |||
} | |||
} | |||
public struct BlockPlacedRemovedEventArgs | |||
{ | |||
public EGID ID; | |||
public BlockIDs Type; | |||
private Block block; | |||
public Block Block => block ?? (block = new Block(ID)); | |||
public Block Block => block ??= Block.New(ID); | |||
} | |||
} |
@@ -1,26 +1,32 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Gamecraft.Blocks.BlockGroups; | |||
using Gamecraft.GUI.Blueprints; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
using HarmonyLib; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Blocks.Ghost; | |||
using RobocraftX.Common; | |||
using RobocraftX.CR.MachineEditing.BoxSelect; | |||
using RobocraftX.CR.MachineEditing.BoxSelect.ClipboardOperations; | |||
using RobocraftX.Physics; | |||
using RobocraftX.Rendering; | |||
using RobocraftX.Rendering.GPUI; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.EntityStructs; | |||
using Svelto.ECS.Native; | |||
using Svelto.ECS.Serialization; | |||
using Techblox.Blocks; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using Unity.Collections; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using Allocator = Svelto.Common.Allocator; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
public class BlueprintEngine : IFactoryEngine | |||
{ | |||
@@ -29,13 +35,18 @@ namespace GamecraftModdingAPI.Blocks | |||
private NativeDynamicArray selectedBlocksInGroup; | |||
private NativeHashSet<ulong> removedConnections = new NativeHashSet<ulong>(); | |||
private int addingToBlockGroup = -1; | |||
private static readonly Type PlaceBlueprintUtilityType = | |||
AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceBlueprintUtility"); | |||
private static readonly FieldInfo LocalBlockMap = | |||
AccessTools.DeclaredField(PlaceBlueprintUtilityType, "_localBlockMap"); | |||
private static readonly MethodInfo BuildBlock = AccessTools.Method(PlaceBlueprintUtilityType, "BuildBlock"); | |||
private static readonly MethodInfo BuildWires = AccessTools.Method(PlaceBlueprintUtilityType, "BuildWires"); | |||
private static readonly Type SerializeGhostBlueprintType = | |||
AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BoxSelect.SerializeGhostChildrenOnAddEngine"); | |||
private static readonly MethodInfo SerializeGhostBlueprint = | |||
AccessTools.Method(SerializeGhostBlueprintType, "SerializeClipboardGhostEntities"); | |||
private static NativeEntityRemove nativeRemove; | |||
private static MachineGraphConnectionEntityFactory connectionFactory; | |||
@@ -44,13 +55,16 @@ namespace GamecraftModdingAPI.Blocks | |||
private static IEntitySerialization entitySerialization; | |||
private static IEntityFactory entityFactory; | |||
private static FasterList<EGID> globalBlockMap; | |||
private static object SerializeGhostBlueprintInstance; | |||
private static GhostChildEntityFactory BuildGhostBlueprintFactory; | |||
public void Ready() | |||
{ | |||
selectedBlocksInGroup = NativeDynamicArray.Alloc<EGID>(Allocator.Persistent); | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
public void Dispose() | |||
{ | |||
selectedBlocksInGroup.Dispose(); | |||
@@ -67,7 +81,7 @@ namespace GamecraftModdingAPI.Blocks | |||
int count = selectedBlocksInGroup.Count<EGID>(); | |||
var ret = new Block[count]; | |||
for (uint i = 0; i < count; i++) | |||
ret[i] = new Block(selectedBlocksInGroup.Get<EGID>(i)); | |||
ret[i] = Block.New(selectedBlocksInGroup.Get<EGID>(i)); | |||
selectedBlocksInGroup.FastClear(); | |||
return ret; | |||
} | |||
@@ -90,17 +104,26 @@ namespace GamecraftModdingAPI.Blocks | |||
return nextFilterId; | |||
} | |||
public void AddBlockToGroup(EGID blockID, int groupID) | |||
{ | |||
if (globalBlockMap == null) | |||
globalBlockMap = FullGameFields._deserialisedBlockMap; | |||
if (groupID != addingToBlockGroup) | |||
{ | |||
Logging.MetaDebugLog("Changing current block group from " + addingToBlockGroup + " to " + groupID); | |||
addingToBlockGroup = groupID; | |||
globalBlockMap.Clear(); | |||
} | |||
globalBlockMap.Add(blockID); | |||
} | |||
public void SelectBlueprint(uint resourceID) | |||
{ | |||
if (resourceID == uint.MaxValue) | |||
BlueprintUtil.UnselectBlueprint(entitiesDB); | |||
else | |||
{ | |||
BlueprintUtil.SelectBlueprint(entitiesDB, new BlueprintInventoryItemEntityStruct | |||
{ | |||
blueprintResourceId = resourceID, | |||
}); | |||
} | |||
BlueprintUtil.SelectBlueprint(entitiesDB, resourceID, false, -1); | |||
} | |||
public uint CreateBlueprint() | |||
@@ -108,7 +131,7 @@ namespace GamecraftModdingAPI.Blocks | |||
uint index = clipboardManager.AllocateSerializationData(); | |||
return index; | |||
} | |||
public void ReplaceBlueprint(uint playerID, uint blueprintID, ICollection<Block> selected, float3 pos, quaternion rot) | |||
{ | |||
var blockIDs = new EGID[selected.Count]; | |||
@@ -122,7 +145,7 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
var serializationData = clipboardManager.GetSerializationData(blueprintID); | |||
SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData); | |||
SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData, -1); | |||
if (selected.Count == 0) | |||
return; | |||
//ref BlockGroupTransformEntityComponent groupTransform = ref EntityNativeDBExtensions.QueryEntity<BlockGroupTransformEntityComponent>(entitiesDb, (uint) local1.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup); | |||
@@ -130,11 +153,28 @@ namespace GamecraftModdingAPI.Blocks | |||
//float3 bottomOffset = PlaceBlockUtility.GetBottomOffset(collider); | |||
//var rootPosition = math.mul(groupTransform.blockGroupGridRotation, bottomOffset) + groupTransform.blockGroupGridPosition; | |||
//var rootRotation = groupTransform.blockGroupGridRotation; | |||
clipboardManager.SetGhostSerialized(blueprintID, false); | |||
SelectionSerializationUtility.CopySelectionToClipboard(playerID, entitiesDB, | |||
serializationData.blueprintData, entitySerialization, entityFactory, blockIDs, | |||
(uint) blockIDs.Length, pos, rot); | |||
(uint) blockIDs.Length, pos, rot, -1); | |||
BuildGhostBlueprint(selected, pos, rot, playerID); | |||
SerializeGhostBlueprint.Invoke(SerializeGhostBlueprintInstance, new object[] {playerID, blueprintID}); | |||
} | |||
private void BuildGhostBlueprint(ICollection<Block> blocks, float3 pos, quaternion rot, uint playerID) | |||
{ | |||
GhostChildUtility.ClearGhostChildren(playerID, entitiesDB, entityFunctions); | |||
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerID, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)); | |||
if (!bssesopt) | |||
return; | |||
foreach (var block in blocks) | |||
{ | |||
GhostChildUtility.BuildGhostChild(in playerID, block.Id, in pos, in rot, entitiesDB, | |||
BuildGhostBlueprintFactory, false, bssesopt.Get().buildingDroneReference); | |||
} | |||
} | |||
public Block[] PlaceBlueprintBlocks(uint blueprintID, uint playerID, float3 pos, float3 rot) | |||
@@ -159,11 +199,12 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
} | |||
int nextFilterId1 = BlockGroupUtility.NextFilterId; | |||
entityFactory.BuildEntity<BlockGroupEntityDescriptor>(new EGID((uint) nextFilterId1, BlockGroupExclusiveGroups.BlockGroupEntityGroup)).Init(new BlockGroupTransformEntityComponent | |||
{ | |||
blockGroupGridPosition = selectionPosition.position, | |||
blockGroupGridRotation = selectionRotation.rotation | |||
}); | |||
entityFactory.BuildEntity<BlockGroupEntityDescriptor>(new EGID((uint) nextFilterId1, | |||
BlockGroupExclusiveGroups.BlockGroupEntityGroup)).Init(new BlockGroupTransformEntityComponent | |||
{ | |||
blockGroupGridPosition = selectionPosition.position, | |||
blockGroupGridRotation = selectionRotation.rotation | |||
}); | |||
var frot = Quaternion.Euler(rot); | |||
var grid = new GridRotationStruct {position = pos, rotation = frot}; | |||
var poss = new PositionEntityStruct {position = pos}; | |||
@@ -173,7 +214,7 @@ namespace GamecraftModdingAPI.Blocks | |||
new object[] | |||
{ | |||
playerID, grid, poss, rots, selectionPosition, selectionRotation, blueprintData, | |||
entitiesDB, entitySerialization, nextFilterId1 | |||
entitySerialization, nextFilterId1 | |||
}); | |||
/* | |||
uint playerId, in GridRotationStruct ghostParentGrid, | |||
@@ -191,10 +232,22 @@ namespace GamecraftModdingAPI.Blocks | |||
new object[] {playerID, blueprintData, entitySerialization, entitiesDB, entityFactory}); | |||
var blocks = new Block[placedBlocks.count]; | |||
for (int i = 0; i < blocks.Length; i++) | |||
blocks[i] = new Block(placedBlocks[i]); | |||
blocks[i] = Block.New(placedBlocks[i]); | |||
return blocks; | |||
} | |||
public void GetBlueprintInfo(uint blueprintID, out float3 pos, out quaternion rot, out uint selectionSize) | |||
{ | |||
var serializationData = clipboardManager.GetSerializationData(blueprintID); | |||
var blueprintData = serializationData.blueprintData; | |||
blueprintData.dataPos = 0U; | |||
BoxSelectSerializationUtilities.ReadClipboardHeader(blueprintData, out selectionSize, out var posst, | |||
out var rotst, out _); | |||
blueprintData.dataPos = 0U; //Just to be sure, it gets reset when it's read anyway | |||
pos = posst.position; | |||
rot = rotst.rotation; | |||
} | |||
public void InitBlueprint(uint blueprintID) | |||
{ | |||
clipboardManager.IncrementRefCount(blueprintID); | |||
@@ -204,8 +257,78 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
clipboardManager.DecrementRefCount(blueprintID); | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIBlueprintGameEngine"; | |||
//GhostChildUtility.BuildGhostChild | |||
public Block BuildGhostChild() | |||
{ | |||
var sourceId = new EGID(Player.LocalPlayer.Id, GHOST_BLOCKS_ENABLED.Group); | |||
var positionEntityStruct = entitiesDB.QueryEntity<PositionEntityStruct>(sourceId); | |||
var rotationEntityStruct = entitiesDB.QueryEntity<RotationEntityStruct>(sourceId); | |||
var scalingEntityStruct = entitiesDB.QueryEntity<ScalingEntityStruct>(sourceId); | |||
var dbStruct = entitiesDB.QueryEntity<DBEntityStruct>(sourceId); | |||
var colliderStruct = entitiesDB.QueryEntity<ColliderAabb>(sourceId); | |||
var colorStruct = entitiesDB.QueryEntity<ColourParameterEntityStruct>(sourceId); | |||
uint ghostChildBlockId = CommonExclusiveGroups.GetNewGhostChildBlockID(); | |||
var ghostEntityReference = GhostBlockUtils.GetGhostEntityReference(sourceId.entityID, entitiesDB); | |||
var entityInitializer = BuildGhostBlueprintFactory.Build( | |||
new EGID(ghostChildBlockId, BoxSelectExclusiveGroups.GhostChildEntitiesExclusiveGroup), /*dbStruct.DBID*/ (uint)BlockIDs.Cube); | |||
entityInitializer.Init(dbStruct); | |||
entityInitializer.Init(new GFXPrefabEntityStructGPUI( | |||
PrefabsID.GetOrCreatePrefabID((ushort)entityInitializer.Get<PrefabAssetIDComponent>().prefabAssetID, | |||
entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId).materialId, 7, | |||
FlippedBlockUtils.IsFlipped(in scalingEntityStruct.scale)), true)); | |||
entityInitializer.Init(entitiesDB.QueryEntity<CubeMaterialStruct>(sourceId)); | |||
entityInitializer.Init(new GhostParentEntityStruct | |||
{ | |||
ghostBlockParentEntityReference = ghostEntityReference, | |||
ownerMustSerializeOnAdd = false | |||
}); | |||
entityInitializer.Init(colorStruct); | |||
entityInitializer.Init(colliderStruct); | |||
entityInitializer.Init(new RigidBodyEntityStruct | |||
{ | |||
position = positionEntityStruct.position, | |||
rotation = rotationEntityStruct.rotation | |||
}); | |||
entityInitializer.Init(new ScalingEntityStruct | |||
{ | |||
scale = scalingEntityStruct.scale | |||
}); | |||
entityInitializer.Init(new LocalTransformEntityStruct | |||
{ | |||
position = positionEntityStruct.position, | |||
rotation = rotationEntityStruct.rotation | |||
}); | |||
entityInitializer.Init(new RotationEntityStruct | |||
{ | |||
rotation = rotationEntityStruct.rotation | |||
}); | |||
entityInitializer.Init(new PositionEntityStruct | |||
{ | |||
position = positionEntityStruct.position | |||
}); | |||
entityInitializer.Init(new SkewComponent | |||
{ | |||
skewMatrix = entitiesDB.QueryEntity<SkewComponent>(sourceId).skewMatrix | |||
}); | |||
entityInitializer.Init(new BlockPlacementInfoStruct | |||
{ | |||
placedByBuildingDrone = entitiesDB | |||
.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(Player.LocalPlayer.Id, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)).Get().buildingDroneReference | |||
}); | |||
entityInitializer.Init(new GridRotationStruct | |||
{ | |||
position = float3.zero, | |||
rotation = quaternion.identity | |||
}); | |||
var block = Block.New(entityInitializer.EGID); | |||
block.InitData = entityInitializer; | |||
return block; | |||
} | |||
public string Name { get; } = "TechbloxModdingAPIBlueprintGameEngine"; | |||
public bool isRemovable { get; } = false; | |||
[HarmonyPatch] | |||
@@ -243,6 +366,34 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
} | |||
[HarmonyPatch] | |||
private static class SerializeGhostBlueprintPatch | |||
{ | |||
public static void Postfix(object __instance) | |||
{ | |||
SerializeGhostBlueprintInstance = __instance; | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return AccessTools.GetDeclaredConstructors(SerializeGhostBlueprintType)[0]; | |||
} | |||
} | |||
[HarmonyPatch] | |||
private static class BuildGhostBlueprintPatch | |||
{ | |||
public static void Postfix(GhostChildEntityFactory ghostChildEntityFactory) | |||
{ | |||
BuildGhostBlueprintFactory = ghostChildEntityFactory; | |||
} | |||
public static MethodBase TargetMethod() | |||
{ | |||
return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.BuildGhostChildForMultiblockPickEngine"))[0]; | |||
} | |||
} | |||
public IEntityFactory Factory { get; set; } | |||
} | |||
} |
@@ -0,0 +1,68 @@ | |||
using RobocraftX.Common; | |||
using RobocraftX.UECS; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
using Unity.Transforms; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
/// <summary> | |||
/// Engine which executes block movement actions | |||
/// </summary> | |||
public class MovementEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "TechbloxModdingAPIMovementGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsInGame = false; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
// implementations for Movement static class | |||
internal float3 MoveBlock(Block block, float3 vector) | |||
{ | |||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block); | |||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntityOrDefault<UECSPhysicsEntityStruct>(block); | |||
// main (persistent) position | |||
posStruct.position = vector; | |||
// placement grid position | |||
gridStruct.position = vector; | |||
// rendered position | |||
transStruct.position = vector; | |||
// collision position | |||
if (phyStruct.ID != default) | |||
{ //It exists | |||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation | |||
{ | |||
Value = posStruct.position | |||
}); | |||
} | |||
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false; | |||
return posStruct.position; | |||
} | |||
internal float3 GetPosition(Block block) | |||
{ | |||
return entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block).position; | |||
} | |||
} | |||
} |
@@ -0,0 +1,130 @@ | |||
using System.Reflection; | |||
using DataLoader; | |||
using Gamecraft.Blocks.BlockGroups; | |||
using Gamecraft.Wires; | |||
using HarmonyLib; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Character; | |||
using RobocraftX.Common; | |||
using RobocraftX.CR.MachineEditing.BoxSelect; | |||
using RobocraftX.Rendering; | |||
using RobocraftX.Rendering.GPUI; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
/// <summary> | |||
/// Engine which executes block placement actions | |||
/// </summary> | |||
public class PlacementEngine : IApiEngine | |||
{ | |||
public bool IsInGame; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceSingleBlockEngine | |||
private static IEntityFactory _entityFactory; | |||
public EntityInitializer PlaceBlock(BlockIDs block, float3 position, Player player, bool autoWire) | |||
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one | |||
return BuildBlock((ushort) block, position, autoWire, (player ?? Player.LocalPlayer).Id); | |||
} | |||
private EntityInitializer BuildBlock(ushort block, float3 position, bool autoWire, uint playerId) | |||
{ | |||
if (_blockEntityFactory == null) | |||
throw new BlockException("The factory is null."); | |||
if(!FullGameFields._dataDb.ContainsKey<CubeListData>(block)) | |||
throw new BlockException("Block with ID " + block + " not found!"); | |||
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine | |||
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block}; | |||
EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.nextBlockEntityID, block); //The ghost block index is only used for triggers | |||
uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>() | |||
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID | |||
: throw new BlockException("Prefab asset ID not found!"); //Set by the game | |||
uint prefabId = PrefabsID.GetOrCreatePrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false); | |||
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId)); | |||
structInitializer.Init(dbEntity); | |||
structInitializer.Init(new PositionEntityStruct {position = position}); | |||
structInitializer.Init(new RotationEntityStruct {rotation = quaternion.identity}); | |||
structInitializer.Init(new ScalingEntityStruct {scale = new float3(1, 1, 1)}); | |||
structInitializer.Init(new GridRotationStruct | |||
{ | |||
position = position, | |||
rotation = quaternion.identity | |||
}); | |||
structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1}); | |||
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork; | |||
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)); | |||
if (!bssesopt) | |||
throw new BlockException("Invalid player ID specified for block placement"); | |||
structInitializer.Init(new BlockPlacementInfoStruct | |||
{ | |||
loadedFromDisk = false, | |||
placedByBuildingDrone = bssesopt.Get().buildingDroneReference, | |||
triggerAutoWiring = autoWire && structInitializer.Has<BlockPortsStruct>() | |||
}); | |||
int nextFilterId = BlockGroupUtility.NextFilterId; | |||
structInitializer.Init(new BlockGroupEntityComponent | |||
{ | |||
currentBlockGroup = nextFilterId | |||
}); | |||
_entityFactory.BuildEntity<BlockGroupEntityDescriptor>((uint) nextFilterId, | |||
BlockGroupExclusiveGroups.BlockGroupEntityGroup) | |||
.Init(new BlockGroupTransformEntityComponent | |||
{ | |||
blockGroupGridRotation = quaternion.identity, | |||
blockGroupGridPosition = position | |||
}); | |||
foreach (var group in CharacterExclusiveGroups.AllCharacters) | |||
{ | |||
EGID playerEGID = new EGID(playerId, group); | |||
if (!entitiesDB.TryQueryEntitiesAndIndex<PickedBlockExtraDataStruct>(playerEGID, out uint index, | |||
out var array)) continue; | |||
ref PickedBlockExtraDataStruct pickedBlock = ref array[index]; | |||
pickedBlock.placedBlockEntityID = structInitializer.EGID; | |||
pickedBlock.placedBlockWasAPickedBlock = false; | |||
} | |||
return structInitializer; | |||
} | |||
public string Name => "TechbloxModdingAPIPlacementGameEngine"; | |||
public bool isRemovable => false; | |||
[HarmonyPatch] | |||
class FactoryObtainerPatch | |||
{ | |||
static void Postfix(BlockEntityFactory blockEntityFactory, IEntityFactory entityFactory) | |||
{ | |||
_blockEntityFactory = blockEntityFactory; | |||
_entityFactory = entityFactory; | |||
Logging.MetaDebugLog("Block entity factory injected."); | |||
} | |||
static MethodBase TargetMethod(Harmony instance) | |||
{ | |||
return AccessTools.TypeByName("RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine").GetConstructors()[0]; | |||
} | |||
} | |||
} | |||
} |
@@ -3,12 +3,14 @@ using System.Reflection; | |||
using HarmonyLib; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.Common; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Native; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
public class RemovalEngine : IApiEngine | |||
{ | |||
@@ -21,8 +23,8 @@ namespace GamecraftModdingAPI.Blocks | |||
return false; | |||
var connections = entitiesDB.QueryEntity<MachineGraphConnectionsEntityStruct>(target); | |||
var groups = entitiesDB.FindGroups<MachineGraphConnectionsEntityStruct>(); | |||
var connStructMapper = | |||
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(groups); | |||
using var connStructMapper = //The allocator needs to be persistent because that's what is used in the Dispose() method | |||
entitiesDB.QueryNativeMappedEntities<MachineGraphConnectionsEntityStruct>(groups, Allocator.Persistent); | |||
for (int i = connections.connections.Count<MachineConnectionStruct>() - 1; i >= 0; i--) | |||
_connectionFactory.RemoveConnection(connections, i, connStructMapper); | |||
_entityFunctions.RemoveEntity<BlockEntityDescriptor>(target); | |||
@@ -39,12 +41,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIRemovalGameEngine"; | |||
public string Name { get; } = "TechbloxModdingAPIRemovalGameEngine"; | |||
public bool isRemovable => false; | |||
[HarmonyPatch] | |||
public class FactoryObtainerPatch | |||
class FactoryObtainerPatch | |||
{ | |||
static void Postfix(IEntityFunctions entityFunctions, | |||
MachineGraphConnectionEntityFactory machineGraphConnectionEntityFactory) |
@@ -0,0 +1,73 @@ | |||
using RobocraftX.Common; | |||
using RobocraftX.UECS; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
/// <summary> | |||
/// Engine which executes block movement actions | |||
/// </summary> | |||
public class RotationEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "TechbloxModdingAPIRotationGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public bool IsInGame = false; | |||
public void Dispose() | |||
{ | |||
IsInGame = false; | |||
} | |||
public void Ready() | |||
{ | |||
IsInGame = true; | |||
} | |||
// implementations for Rotation static class | |||
internal float3 RotateBlock(Block block, Vector3 vector) | |||
{ | |||
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block); | |||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntityOrDefault<UECSPhysicsEntityStruct>(block); | |||
// main (persistent) rotation | |||
Quaternion newRotation = rotStruct.rotation; | |||
newRotation.eulerAngles = vector; | |||
rotStruct.rotation = newRotation; | |||
// placement grid rotation | |||
gridStruct.rotation = newRotation; | |||
// rendered rotation | |||
transStruct.rotation = newRotation; | |||
// collision rotation | |||
if (phyStruct.ID != default) | |||
{ //It exists | |||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, | |||
new Unity.Transforms.Rotation | |||
{ | |||
Value = rotStruct.rotation | |||
}); | |||
} | |||
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).isProcessed = false; | |||
return ((Quaternion)rotStruct.rotation).eulerAngles; | |||
} | |||
internal float3 GetRotation(Block block) | |||
{ | |||
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block); | |||
return ((Quaternion) rotStruct.rotation).eulerAngles; | |||
} | |||
} | |||
} |
@@ -6,10 +6,10 @@ using RobocraftX.UECS; | |||
using Svelto.ECS; | |||
using Unity.Entities; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
public class ScalingEngine : IApiEngine | |||
{ | |||
@@ -24,7 +24,7 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIScalingEngine"; | |||
public string Name { get; } = "TechbloxModdingAPIScalingEngine"; | |||
public bool isRemovable { get; } = false; | |||
private EntityManager _entityManager; //Unity entity manager | |||
@@ -41,7 +41,7 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
[HarmonyPatch] | |||
public class PhysicsEnginePatch | |||
class PhysicsEnginePatch | |||
{ | |||
static void Postfix(IReactOnAddAndRemove<UECSPhysicsEntityCreationStruct> __instance) | |||
{ |
@@ -1,11 +1,13 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using Svelto.DataStructures; | |||
using Gamecraft.Wires; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
/// <summary> | |||
/// Engine which executes signal actions | |||
@@ -17,7 +19,7 @@ namespace GamecraftModdingAPI.Blocks | |||
public const float HIGH = 1.0f; | |||
public const float ZERO = 0.0f; | |||
public string Name { get; } = "GamecraftModdingAPISignalGameEngine"; | |||
public string Name { get; } = "TechbloxModdingAPISignalGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
@@ -42,7 +44,7 @@ namespace GamecraftModdingAPI.Blocks | |||
public WireEntityStruct CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort) | |||
{ | |||
EGID wireEGID = new EGID(WiresExclusiveGroups.NewWireEntityId, NamedExclusiveGroup<WiresGroup>.Group); | |||
EntityComponentInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID); | |||
EntityInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID); | |||
wireInitializer.Init(new WireEntityStruct | |||
{ | |||
sourceBlockEGID = startBlock, | |||
@@ -88,8 +90,8 @@ namespace GamecraftModdingAPI.Blocks | |||
public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input) | |||
{ | |||
BlockPortsStruct bps = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out bool exists); | |||
if (!exists) | |||
var bps = entitiesDB.QueryEntityOptional<BlockPortsStruct>(block); | |||
if (!bps) | |||
{ | |||
throw new BlockException("Block does not exist"); | |||
} | |||
@@ -206,46 +208,35 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
return outputs; | |||
} | |||
public EGID MatchBlockInputToPort(Block block, byte portUsage, out bool exists) | |||
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(Block block, byte portUsage, bool output) | |||
{ | |||
BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists); | |||
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group); | |||
return MatchBlockIOToPort(block.Id, portUsage, output); | |||
} | |||
public EGID MatchBlockInputToPort(EGID block, byte portUsage, out bool exists) | |||
public OptionalRef<PortEntityStruct> MatchBlockIOToPort(EGID block, byte portUsage, bool output) | |||
{ | |||
if (!entitiesDB.Exists<BlockPortsStruct>(block)) | |||
{ | |||
exists = false; | |||
return default; | |||
} | |||
exists = true; | |||
var group = output | |||
? NamedExclusiveGroup<OutputPortsGroup>.Group | |||
: NamedExclusiveGroup<InputPortsGroup>.Group; | |||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); | |||
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group); | |||
} | |||
public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists) | |||
{ | |||
BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists); | |||
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group); | |||
} | |||
public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists) | |||
{ | |||
if (!entitiesDB.Exists<BlockPortsStruct>(block)) | |||
{ | |||
exists = false; | |||
if (!entitiesDB.TryQueryMappedEntities<PortEntityStruct>(group, out var mapper)) | |||
return default; | |||
for (uint i = 0; i < (output ? ports.outputCount : ports.inputCount); ++i) | |||
{ | |||
uint entityID = (output ? ports.firstOutputID : ports.firstInputID) + i; | |||
if (!mapper.TryGetArrayAndEntityIndex(entityID, out var index, out var array) || | |||
array[index].usage != portUsage) continue; | |||
return new OptionalRef<PortEntityStruct>(array, index); | |||
} | |||
exists = true; | |||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block); | |||
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group); | |||
return default; | |||
} | |||
public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists) | |||
public ref WireEntityStruct MatchPortToWire(PortEntityStruct port, EGID blockID, out bool exists) | |||
{ | |||
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); | |||
var wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group); | |||
var wiresB = wires.ToBuffer().buffer; | |||
for (uint i = 0; i < wires.count; i++) | |||
@@ -262,8 +253,7 @@ namespace GamecraftModdingAPI.Blocks | |||
return ref defRef[0]; | |||
} | |||
public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue, | |||
byte endPort = byte.MaxValue) | |||
public EGID MatchBlocksToWire(EGID startBlock, EGID endBlock, byte startPort = byte.MaxValue, byte endPort = byte.MaxValue) | |||
{ | |||
EGID[] startPorts; | |||
if (startPort == byte.MaxValue) | |||
@@ -302,31 +292,23 @@ namespace GamecraftModdingAPI.Blocks | |||
if ((wiresB[w].destinationPortUsage == endPES.usage && wiresB[w].destinationBlockEGID == endBlock) | |||
&& (wiresB[w].sourcePortUsage == startPES.usage && wiresB[w].sourceBlockEGID == startBlock)) | |||
{ | |||
exists = true; | |||
return ref wiresB[w]; | |||
return wiresB[w].ID; | |||
} | |||
} | |||
} | |||
} | |||
exists = false; | |||
WireEntityStruct[] defRef = new WireEntityStruct[1]; | |||
return ref defRef[0]; | |||
return default; | |||
} | |||
public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists) | |||
{ | |||
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID); | |||
public OptionalRef<ChannelDataStruct> GetChannelDataStruct(EGID portID) | |||
{ | |||
var port = GetPort(portID); | |||
var channels = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group); | |||
var channelsB = channels.ToBuffer(); | |||
if (port.firstChannelIndexCachedInSim < channels.count) | |||
{ | |||
exists = true; | |||
return ref channelsB.buffer[port.firstChannelIndexCachedInSim]; | |||
} | |||
exists = false; | |||
ChannelDataStruct[] defRef = new ChannelDataStruct[1]; | |||
return ref defRef[0]; | |||
return port.firstChannelIndexCachedInSim < channels.count | |||
? new OptionalRef<ChannelDataStruct>(channelsB.buffer, port.firstChannelIndexCachedInSim) | |||
: default; | |||
} | |||
public EGID[] GetElectricBlocks() | |||
@@ -386,29 +368,6 @@ namespace GamecraftModdingAPI.Blocks | |||
return results.ToArray(); | |||
} | |||
private ref T GetFromDbOrInitData<T>(Block block, EGID id, out bool exists) where T : unmanaged, IEntityComponent | |||
{ | |||
T[] defRef = new T[1]; | |||
if (entitiesDB.Exists<T>(id)) | |||
{ | |||
exists = true; | |||
return ref entitiesDB.QueryEntity<T>(id); | |||
} | |||
if (block == null || block.InitData.Group == null) | |||
{ | |||
exists = false; | |||
return ref defRef[0]; | |||
} | |||
EntityComponentInitializer initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
if (initializer.Has<T>()) | |||
{ | |||
exists = true; | |||
return ref initializer.Get<T>(); | |||
} | |||
exists = false; | |||
return ref defRef[0]; | |||
} | |||
private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true) | |||
{ | |||
ExclusiveGroup group = input |
@@ -0,0 +1,26 @@ | |||
namespace TechbloxModdingAPI.Blocks | |||
{ | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
public class LogicGate : SignalingBlock | |||
{ | |||
/// <summary> | |||
/// Constructs a(n) LogicGate object representing an existing block. | |||
/// </summary> | |||
public LogicGate(EGID egid) : | |||
base(egid) | |||
{ | |||
} | |||
/// <summary> | |||
/// Constructs a(n) LogicGate object representing an existing block. | |||
/// </summary> | |||
public LogicGate(uint id) : | |||
base(new EGID(id, CommonExclusiveGroups.LOGIC_BLOCK_GROUP)) | |||
{ | |||
} | |||
} | |||
} |