- Added support for seat enter and exit events and a test for them - Added support for entering and exiting seat from code - Changed the Id property of ECS objects to non-abstract, requiring it in the constructor, so that the Player class can inherit EcsObjectBase - Added a weird constructor to EcsObjectBase that allows running code to determine the object Id - Added interface for engines that receive entity functions - Exposed the entity submission scheduler and removed it from FullGameFields because it moved from there - Made the Game.Enter event only fire after the first entity submission so the game is fully initialized and the local player exists - Added all seat groups to the dictionarytags/v2.1.0
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Generic; | |||
using RobocraftX.Common; | |||
using RobocraftX.Schedulers; | |||
@@ -11,6 +10,7 @@ using RobocraftX.Blocks; | |||
using RobocraftX.ScreenshotTaker; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Tasks; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.App | |||
@@ -35,6 +35,12 @@ namespace TechbloxModdingAPI.App | |||
public void Ready() | |||
{ | |||
EnteringGame().RunOn(Scheduler.leanRunner); | |||
} | |||
private IEnumerator<TaskContract> EnteringGame() | |||
{ | |||
yield return new WaitForSubmissionEnumerator(GameLoadedEnginePatch.Scheduler).Continue(); | |||
EnterGame.Invoke(this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder }); | |||
IsInGame = true; | |||
} | |||
@@ -96,40 +102,29 @@ namespace TechbloxModdingAPI.App | |||
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB); | |||
} | |||
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) | |||
{ | |||
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>(); | |||
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]; | |||
uint dbid = entitiesDB.QueryEntity<DBEntityStruct>(block.ID).DBID; | |||
if (dbid == (ulong) filter) | |||
blockEGIDs.Add(block.ID); | |||
} | |||
} | |||
return blockEGIDs.ToArray(); | |||
} | |||
} | |||
public void EnableScreenshotTaker() | |||
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) | |||
@@ -99,12 +99,16 @@ namespace TechbloxModdingAPI | |||
{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))}, | |||
{SeatGroups.PASSENGER_BLOCK_BUILD_GROUP, (id => new Seat(id), typeof(Seat))}, | |||
{SeatGroups.PILOTSEAT_BLOCK_BUILD_GROUP, (id => new Seat(id), typeof(Seat))}, | |||
{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. | |||
@@ -126,9 +130,8 @@ namespace TechbloxModdingAPI | |||
: GetInstance(egid, e => new Block(e)); | |||
} | |||
public Block(EGID id) | |||
public Block(EGID id) : base(id) | |||
{ | |||
Id = id; | |||
Type expectedType; | |||
if (GroupToConstructor.ContainsKey(id.groupID) && | |||
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type)) | |||
@@ -156,17 +159,18 @@ namespace TechbloxModdingAPI | |||
/// <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; | |||
}) | |||
{ | |||
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); | |||
Id = initializer.EGID; | |||
InitData = initializer; | |||
Placed += OnPlacedInit; | |||
} | |||
public override EGID Id { get; } | |||
private EGID copiedFrom; | |||
/// <summary> | |||
@@ -19,17 +19,16 @@ namespace TechbloxModdingAPI | |||
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable | |||
{ | |||
internal static BlueprintEngine _engine = new BlueprintEngine(); | |||
public override EGID 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 = new EGID((uint) id, BlockGroupExclusiveGroups.BlockGroupEntityGroup); | |||
sourceBlock = block; | |||
blocks = new List<Block>(GetBlocks()); | |||
Block.Removed += OnBlockRemoved; | |||
@@ -76,10 +76,11 @@ namespace TechbloxModdingAPI.Blocks | |||
/// <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) | |||
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) : base(ecs => | |||
{ | |||
startBlockEGID = start.Id; | |||
endBlockEGID = end.Id; | |||
var th = (Wire)ecs; | |||
th.startBlockEGID = start.Id; | |||
th.endBlockEGID = end.Id; | |||
bool flipped = false; | |||
// find block ports | |||
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort); | |||
@@ -94,12 +95,16 @@ namespace TechbloxModdingAPI.Blocks | |||
if (wire != default) | |||
{ | |||
Construct(start.Id, end.Id, startPort, endPort, wire, flipped); | |||
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped); | |||
} | |||
else | |||
{ | |||
throw new WireInvalidException("Wire not found"); | |||
} | |||
return th.wireEGID; | |||
}) | |||
{ | |||
} | |||
/// <summary> | |||
@@ -116,7 +121,7 @@ namespace TechbloxModdingAPI.Blocks | |||
{ | |||
} | |||
private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) | |||
private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire) | |||
{ | |||
Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput); | |||
} | |||
@@ -139,7 +144,7 @@ namespace TechbloxModdingAPI.Blocks | |||
/// Construct a wire object from an existing wire connection. | |||
/// </summary> | |||
/// <param name="wireEgid">The wire ID.</param> | |||
public Wire(EGID wireEgid) | |||
public Wire(EGID wireEgid) : base(wireEgid) | |||
{ | |||
WireEntityStruct wire = signalEngine.GetWire(wireEGID); | |||
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, | |||
@@ -151,14 +156,6 @@ namespace TechbloxModdingAPI.Blocks | |||
{ | |||
} | |||
/// <summary> | |||
/// The wire's in-game id. | |||
/// </summary> | |||
public override EGID Id | |||
{ | |||
get => wireEGID; | |||
} | |||
/// <summary> | |||
/// The wire's signal value, as a float. | |||
/// </summary> | |||
@@ -10,11 +10,8 @@ namespace TechbloxModdingAPI | |||
/// </summary> | |||
public class Cluster : EcsObjectBase | |||
{ | |||
public override EGID Id { get; } | |||
public Cluster(EGID id) | |||
public Cluster(EGID id) : base(id) | |||
{ | |||
Id = id; | |||
} | |||
public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP)) | |||
@@ -10,8 +10,8 @@ namespace TechbloxModdingAPI | |||
{ | |||
public abstract class EcsObjectBase | |||
{ | |||
public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor | |||
public EGID Id { get; } | |||
private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances = | |||
new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>(); | |||
@@ -39,7 +39,19 @@ namespace TechbloxModdingAPI | |||
return (T)instance; | |||
} | |||
protected EcsObjectBase() | |||
protected EcsObjectBase(EGID id) | |||
{ | |||
if (!_instances.TryGetValue(GetType(), out var dict)) | |||
{ | |||
dict = new WeakDictionary<EGID, EcsObjectBase>(); | |||
_instances.Add(GetType(), dict); | |||
} | |||
if (!dict.ContainsKey(id)) // Multiple instances may be created | |||
dict.Add(id, this); | |||
Id = id; | |||
} | |||
protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer) | |||
{ | |||
if (!_instances.TryGetValue(GetType(), out var dict)) | |||
{ | |||
@@ -47,11 +59,9 @@ namespace TechbloxModdingAPI | |||
_instances.Add(GetType(), dict); | |||
} | |||
// ReSharper disable VirtualMemberCallInConstructor | |||
// The ID should not depend on the constructor | |||
if (!dict.ContainsKey(Id)) // Multiple instances may be created | |||
dict.Add(Id, this); | |||
// ReSharper enable VirtualMemberCallInConstructor | |||
var id = initializer(this); | |||
if (!dict.ContainsKey(id)) // Multiple instances may be created | |||
dict.Add(id, this); | |||
} | |||
#region ECS initializer stuff | |||
@@ -5,6 +5,7 @@ using RobocraftX.CR.MainGame; | |||
using RobocraftX.FrontEnd; | |||
using RobocraftX.StateSync; | |||
using Svelto.ECS; | |||
using Svelto.ECS.Schedulers; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Engines | |||
@@ -12,10 +13,12 @@ namespace TechbloxModdingAPI.Engines | |||
[HarmonyPatch] | |||
class GameLoadedEnginePatch | |||
{ | |||
public static EntitiesSubmissionScheduler Scheduler { get; private set; } | |||
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) | |||
{ | |||
// register all game engines, including deterministic | |||
GameEngineManager.RegisterEngines(stateSyncReg); | |||
Scheduler = stateSyncReg.enginesRoot.scheduler; | |||
} | |||
public static MethodBase TargetMethod() | |||
@@ -0,0 +1,9 @@ | |||
using Svelto.ECS; | |||
namespace TechbloxModdingAPI.Engines | |||
{ | |||
public interface IFunEngine : IApiEngine | |||
{ | |||
public IEntityFunctions Functions { set; } | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
using System; | |||
using Svelto.ECS; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI | |||
{ | |||
public partial class Player | |||
{ | |||
internal WrappedHandler<PlayerSeatEventArgs> seatEntered; | |||
public event EventHandler<PlayerSeatEventArgs> SeatEntered | |||
{ | |||
add => seatEntered += value; | |||
remove => seatEntered -= value; | |||
} | |||
internal WrappedHandler<PlayerSeatEventArgs> seatExited; | |||
public event EventHandler<PlayerSeatEventArgs> SeatExited | |||
{ | |||
add => seatExited += value; | |||
remove => seatExited -= value; | |||
} | |||
} | |||
public struct PlayerSeatEventArgs | |||
{ | |||
public EGID SeatId; | |||
public Seat Seat => (Seat)Block.New(SeatId); | |||
} | |||
} |
@@ -20,10 +20,11 @@ namespace TechbloxModdingAPI | |||
/// <summary> | |||
/// An in-game player character. Any Leo you see is a player. | |||
/// </summary> | |||
public class Player : IEquatable<Player>, IEquatable<EGID> | |||
{ //TODO: Inherit EcsObjectBase and make Id an EGID, useful for caching | |||
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID> | |||
{ | |||
// static functionality | |||
private static PlayerEngine playerEngine = new PlayerEngine(); | |||
private static PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine(); | |||
private static Player localPlayer; | |||
/// <summary> | |||
@@ -79,7 +80,7 @@ namespace TechbloxModdingAPI | |||
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. | |||
/// </summary> | |||
/// <param name="id">The player's unique identifier.</param> | |||
public Player(uint id) | |||
public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup)) | |||
{ | |||
this.Id = id; | |||
if (!Exists(id)) | |||
@@ -93,22 +94,31 @@ namespace TechbloxModdingAPI | |||
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. | |||
/// </summary> | |||
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param> | |||
public Player(PlayerType player) | |||
public Player(PlayerType player) : base(ecs => | |||
{ | |||
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; | |||
uint id; | |||
switch (player) | |||
{ | |||
case PlayerType.Local: | |||
id = playerEngine.GetLocalPlayer(); | |||
break; | |||
case PlayerType.Remote: | |||
id = playerEngine.GetRemotePlayer(); | |||
break; | |||
default: | |||
id = uint.MaxValue; | |||
break; | |||
} | |||
if (id == uint.MaxValue) | |||
{ | |||
throw new PlayerNotFoundException($"No player of {player} type exists"); | |||
} | |||
return new EGID(id, CharacterExclusiveGroups.OnFootGroup); | |||
}) | |||
{ | |||
this.Type = player; | |||
} | |||
// object fields & properties | |||
@@ -124,7 +134,7 @@ namespace TechbloxModdingAPI | |||
/// The player's unique identifier. | |||
/// </summary> | |||
/// <value>The identifier.</value> | |||
public uint Id { get; } | |||
public new uint Id { get; } | |||
/// <summary> | |||
/// The player's current position. | |||
@@ -424,6 +434,16 @@ namespace TechbloxModdingAPI | |||
} | |||
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); | |||
} | |||
public void EnterSeat(Seat seat) | |||
{ | |||
playerEngine.EnterSeat(Id, seat.Id); | |||
} | |||
public void ExitSeat() | |||
{ | |||
playerEngine.ExitSeat(Id); | |||
} | |||
/// <summary> | |||
/// Returns the block the player is currently looking at in build mode. | |||
@@ -507,6 +527,7 @@ namespace TechbloxModdingAPI | |||
internal static void Init() | |||
{ | |||
Utility.GameEngineManager.AddGameEngine(playerEngine); | |||
Utility.GameEngineManager.AddGameEngine(playerEventsEngine); | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System.Runtime.CompilerServices; | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using RobocraftX.Character; | |||
using RobocraftX.Character.Movement; | |||
@@ -8,10 +9,13 @@ using RobocraftX.CR.MachineEditing.BoxSelect; | |||
using RobocraftX.Physics; | |||
using RobocraftX.Blocks.Ghost; | |||
using Gamecraft.GUI.HUDFeedbackBlocks; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.PilotSeat; | |||
using Svelto.ECS; | |||
using Techblox.Camera; | |||
using Unity.Mathematics; | |||
using Svelto.ECS.DataStructures; | |||
using Svelto.ECS.EntityStructs; | |||
using Techblox.BuildingDrone; | |||
using TechbloxModdingAPI.Engines; | |||
@@ -19,7 +23,7 @@ using TechbloxModdingAPI.Utility; | |||
namespace TechbloxModdingAPI.Players | |||
{ | |||
internal class PlayerEngine : IApiEngine, IFactoryEngine | |||
internal class PlayerEngine : IFunEngine | |||
{ | |||
public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine"; | |||
@@ -27,7 +31,7 @@ namespace TechbloxModdingAPI.Players | |||
public bool isRemovable => false; | |||
public IEntityFactory Factory { set; private get; } | |||
public IEntityFunctions Functions { get; set; } | |||
private bool isReady = false; | |||
@@ -101,9 +105,7 @@ namespace TechbloxModdingAPI.Players | |||
return false; | |||
if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat) | |||
{ | |||
EGID egid = new EGID(playerId, group); | |||
entitiesDB.QueryEntity<CharacterPilotSeatEntityStruct>(egid).instantExit = true; | |||
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid); | |||
ExitSeat(playerId); | |||
} | |||
rbesOpt.Get().position = location; | |||
return true; | |||
@@ -183,12 +185,12 @@ namespace TechbloxModdingAPI.Players | |||
{ | |||
if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)) | |||
return new Block[0]; | |||
return Array.Empty<Block>(); | |||
var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); | |||
var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid, | |||
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); | |||
if (!state.active) return new Block[0]; | |||
if (!state.active) return Array.Empty<Block>(); | |||
var pointer = (EGID*) blocks.selectedBlocks.ToPointer(); | |||
var ret = new Block[blocks.count]; | |||
for (int j = 0; j < blocks.count; j++) | |||
@@ -199,5 +201,31 @@ namespace TechbloxModdingAPI.Players | |||
return ret; | |||
} | |||
public void EnterSeat(uint playerId, EGID seatId) | |||
{ | |||
PilotSeatGroupUtils.SwapTagTo<OCCUPIED_TAG>(Functions, seatId); | |||
var opt = GetCharacterStruct<CharacterPilotSeatEntityStruct>(playerId, out var group); | |||
if (!opt) return; | |||
ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get(); | |||
var charId = new EGID(playerId, group); | |||
charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId); | |||
charSeat.entryPositionOffset = | |||
entitiesDB.QueryEntity<PositionEntityStruct>(charId).position - | |||
entitiesDB.QueryEntity<PositionEntityStruct>(seatId).position; | |||
ref var seat = ref entitiesDB.QueryEntity<PilotSeatEntityStruct>(seatId); | |||
seat.occupyingCharacter = entitiesDB.GetEntityReference(charId); | |||
charSeat.followCam = entitiesDB.QueryEntity<SeatFollowCamComponent>(seatId).followCam; | |||
Functions.SwapEntityGroup<CharacterEntityDescriptor>(charId, CharacterExclusiveGroups.InPilotSeatGroup); | |||
} | |||
public void ExitSeat(uint playerId) | |||
{ | |||
EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup); | |||
var opt = entitiesDB.QueryEntityOptional<CharacterPilotSeatEntityStruct>(egid); | |||
if (!opt) return; | |||
opt.Get().instantExit = true; | |||
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid); | |||
} | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
using RobocraftX.Character; | |||
using RobocraftX.Character.Movement; | |||
using Svelto.ECS; | |||
using TechbloxModdingAPI.Engines; | |||
namespace TechbloxModdingAPI.Players | |||
{ | |||
public class PlayerEventsEngine : IApiEngine, IReactOnSwap<CharacterPilotSeatEntityStruct> | |||
{ | |||
public void Ready() | |||
{ | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
public void Dispose() | |||
{ | |||
} | |||
public string Name => "TechbloxModdingAPIPlayerEventsEngine"; | |||
public bool isRemovable => false; | |||
public void MovedTo(ref CharacterPilotSeatEntityStruct entityComponent, ExclusiveGroupStruct previousGroup, EGID egid) | |||
{ | |||
var seatId = entityComponent.pilotSeatEntity.ToEGID(entitiesDB); | |||
var player = EcsObjectBase.GetInstance(new EGID(egid.entityID, CharacterExclusiveGroups.OnFootGroup), | |||
e => new Player(e.entityID)); | |||
if (previousGroup == CharacterExclusiveGroups.InPilotSeatGroup) | |||
player.seatExited.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId}); | |||
else if (egid.groupID == CharacterExclusiveGroups.InPilotSeatGroup) | |||
player.seatEntered.Invoke(this, new PlayerSeatEventArgs { SeatId = seatId }); | |||
} | |||
} | |||
} |
@@ -1,8 +1,11 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.Tasks; | |||
using Svelto.Tasks.Enumerators; | |||
using Unity.Mathematics; | |||
using TechbloxModdingAPI; | |||
using TechbloxModdingAPI.App; | |||
using TechbloxModdingAPI.Blocks; | |||
using TechbloxModdingAPI.Tests; | |||
namespace TechbloxModdingAPI.Players | |||
@@ -30,6 +33,34 @@ namespace TechbloxModdingAPI.Players | |||
if (!Assert.Errorless(() => { p.Position = float3.zero + 1; }, "Player.Position = origin+1 errored: ", "Player moved to origin+1.")) return; | |||
Assert.CloseTo(p.Position, float3.zero + 1, "Player is not close to origin+1 despite being teleported there.", "Player.Position is at origin+1."); | |||
} | |||
[APITestCase(TestType.Game)] | |||
public static void SeatEventTestBuild() | |||
{ | |||
Player.LocalPlayer.SeatEntered += Assert.CallsBack<PlayerSeatEventArgs>("SeatEntered"); | |||
Player.LocalPlayer.SeatExited += Assert.CallsBack<PlayerSeatEventArgs>("SeatExited"); | |||
Block.PlaceNew(BlockIDs.DriverSeat, -1f); | |||
} | |||
[APITestCase(TestType.SimulationMode)] | |||
public static IEnumerator<TaskContract> SeatEventTestSim() | |||
{ | |||
var seats = Game.CurrentGame().GetBlocksInGame(BlockIDs.DriverSeat); | |||
if (seats.Length == 0) | |||
{ | |||
Assert.Fail("No driver seat found!"); | |||
yield break; | |||
} | |||
if (seats[0] is Seat seat) | |||
Assert.Errorless(() => Player.LocalPlayer.EnterSeat(seat), "Failed to enter seat.", | |||
"Entered seat successfully."); | |||
else | |||
Assert.Fail("Found a seat that is not a seat!"); | |||
yield return new WaitForSecondsEnumerator(1).Continue(); | |||
Assert.Errorless(() => Player.LocalPlayer.ExitSeat(), "Failed to exit seat.", | |||
"Exited seat successfully."); | |||
} | |||
[APITestCase(TestType.Menu)] | |||
public static void InvalidStateTest() | |||
@@ -14,8 +14,6 @@ namespace TechbloxModdingAPI | |||
/// </summary> | |||
public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID> | |||
{ | |||
public override EGID Id { get; } | |||
/// <summary> | |||
/// The cluster this chunk belongs to, or null if no cluster destruction manager present or the chunk doesn't exist. | |||
/// Get the SimBody from a Block if possible for good performance here. | |||
@@ -28,9 +26,8 @@ namespace TechbloxModdingAPI | |||
private Cluster cluster; | |||
private readonly uint clusterId = uint.MaxValue; | |||
public SimBody(EGID id) | |||
public SimBody(EGID id) : base(id) | |||
{ | |||
Id = id; | |||
} | |||
public SimBody(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_BODIES_GROUP)) | |||
@@ -58,6 +58,14 @@ namespace TechbloxModdingAPI.Tests | |||
// debug/test handlers | |||
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!"); | |||
Client.EnterMenu += (sender, args) => Console.WriteLine("EnterMenu handler after erroring handler"); | |||
Game.Enter += (s, a) => | |||
{ | |||
Player.LocalPlayer.SeatEntered += (sender, args) => | |||
Console.WriteLine($"Player {Player.LocalPlayer} entered seat {args.Seat}"); | |||
Player.LocalPlayer.SeatExited += (sender, args) => | |||
Console.WriteLine($"Player {Player.LocalPlayer} exited seat {args.Seat}"); | |||
}; | |||
// debug/test commands | |||
if (Dependency.Hell("ExtraCommands")) | |||
@@ -72,14 +72,6 @@ namespace TechbloxModdingAPI.Utility | |||
} | |||
} | |||
public static SimpleEntitiesSubmissionScheduler _mainGameSubmissionScheduler | |||
{ | |||
get | |||
{ | |||
return (SimpleEntitiesSubmissionScheduler)fgcr?.Field("_sub").Field("_mainGameSubmissionScheduler").GetValue(); | |||
} | |||
} | |||
public static BuildPhysicsWorld _physicsWorldSystem | |||
{ | |||
get | |||
@@ -26,10 +26,10 @@ namespace TechbloxModdingAPI.Utility | |||
{ | |||
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}"); | |||
_lastEngineRoot.AddEngine(engine); | |||
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType())) | |||
{ | |||
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory(); | |||
} | |||
if (engine is IFactoryEngine factoryEngine) | |||
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory(); | |||
if (engine is IFunEngine funEngine) | |||
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions(); | |||
} | |||
} | |||
@@ -66,6 +66,7 @@ namespace TechbloxModdingAPI.Utility | |||
var enginesRoot = helper.enginesRoot; | |||
_lastEngineRoot = enginesRoot; | |||
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); | |||
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions(); | |||
foreach (var key in _gameEngines.Keys) | |||
{ | |||
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}"); | |||
@@ -75,6 +76,8 @@ namespace TechbloxModdingAPI.Utility | |||
enginesRoot.AddEngine(_gameEngines[key]); | |||
if (_gameEngines[key] is IFactoryEngine factEngine) | |||
factEngine.Factory = factory; | |||
if (_gameEngines[key] is IFunEngine funEngine) | |||
funEngine.Functions = functions; | |||
} | |||
} | |||
} | |||
@@ -26,10 +26,10 @@ namespace TechbloxModdingAPI.Utility | |||
{ | |||
Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}"); | |||
_lastEngineRoot.AddEngine(engine); | |||
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType())) | |||
{ | |||
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory(); | |||
} | |||
if (engine is IFactoryEngine factoryEngine) | |||
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory(); | |||
if (engine is IFunEngine funEngine) | |||
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions(); | |||
} | |||
} | |||
@@ -65,14 +65,13 @@ namespace TechbloxModdingAPI.Utility | |||
{ | |||
_lastEngineRoot = enginesRoot; | |||
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); | |||
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions(); | |||
foreach (var key in _menuEngines.Keys) | |||
{ | |||
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}"); | |||
enginesRoot.AddEngine(_menuEngines[key]); | |||
if (_menuEngines[key] is IFactoryEngine factEngine) | |||
{ | |||
factEngine.Factory = factory; | |||
} | |||
if (_menuEngines[key] is IFactoryEngine factEngine) factEngine.Factory = factory; | |||
if(_menuEngines[key] is IFunEngine funEngine) funEngine.Functions = functions; | |||
} | |||
} | |||
} | |||