Seat events, and everything needed to get there

- 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 dictionary
This commit is contained in:
Norbi Peti 2021-10-11 01:26:35 +02:00
parent 4bd636b8ed
commit 6204b226d1
18 changed files with 284 additions and 128 deletions

View file

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Schedulers; using RobocraftX.Schedulers;
@ -11,6 +10,7 @@ using RobocraftX.Blocks;
using RobocraftX.ScreenshotTaker; using RobocraftX.ScreenshotTaker;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App namespace TechbloxModdingAPI.App
@ -35,6 +35,12 @@ namespace TechbloxModdingAPI.App
public void Ready() 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 }); EnterGame.Invoke(this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
IsInGame = true; IsInGame = true;
} }
@ -96,40 +102,29 @@ namespace TechbloxModdingAPI.App
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB); TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
} }
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid) public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
{ {
var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>(); var allBlocks = entitiesDB.QueryEntities<BlockTagEntityStruct>();
List<EGID> blockEGIDs = new List<EGID>(); List<EGID> blockEGIDs = new List<EGID>();
if (filter == BlockIDs.Invalid) foreach (var (blocks, _) in allBlocks)
{ {
foreach (var (blocks, _) in allBlocks) var (buffer, count) = blocks.ToBuffer();
{ for (int i = 0; i < count; i++)
var buffer = blocks.ToBuffer().buffer; {
for (int i = 0; i < buffer.capacity; i++) uint dbid;
blockEGIDs.Add(buffer[i].ID); 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(); 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 void EnableScreenshotTaker()
{ {
ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker); ref var local = ref entitiesDB.QueryEntity<ScreenshotModeEntityStruct>(ScreenshotTakerEgids.ScreenshotTaker);
if (local.enabled) if (local.enabled)

View file

@ -99,12 +99,16 @@ namespace TechbloxModdingAPI
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}, {CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))},
{CommonExclusiveGroups.LOGIC_BLOCK_GROUP, (id => new LogicGate(id), typeof(LogicGate))}, {CommonExclusiveGroups.LOGIC_BLOCK_GROUP, (id => new LogicGate(id), typeof(LogicGate))},
{CommonExclusiveGroups.PISTON_BLOCK_GROUP, (id => new Piston(id), typeof(Piston))}, {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.SERVO_BLOCK_GROUP, (id => new Servo(id), typeof(Servo))},
{CommonExclusiveGroups.WHEELRIG_BLOCK_BUILD_GROUP, (id => new WheelRig(id), typeof(WheelRig))} {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> /// <summary>
/// Returns a correctly typed instance of this block. The instances are shared for a specific block. /// 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. /// If an instance is no longer referenced a new instance is returned.
@ -126,9 +130,8 @@ namespace TechbloxModdingAPI
: GetInstance(egid, e => new Block(e)); : GetInstance(egid, e => new Block(e));
} }
public Block(EGID id) public Block(EGID id) : base(id)
{ {
Id = id;
Type expectedType; Type expectedType;
if (GroupToConstructor.ContainsKey(id.groupID) && if (GroupToConstructor.ContainsKey(id.groupID) &&
!GetType().IsAssignableFrom(expectedType = GroupToConstructor[id.groupID].Type)) !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="player">The player who placed the block</param>
/// <param name="force">Place even if not in build mode</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) 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; private EGID copiedFrom;
/// <summary> /// <summary>

View file

@ -19,17 +19,16 @@ namespace TechbloxModdingAPI
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{ {
internal static BlueprintEngine _engine = new BlueprintEngine(); internal static BlueprintEngine _engine = new BlueprintEngine();
public override EGID Id { get; }
private readonly Block sourceBlock; private readonly Block sourceBlock;
private readonly List<Block> blocks; private readonly List<Block> blocks;
private float3 position, rotation; private float3 position, rotation;
internal bool PosAndRotCalculated; 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) if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!"); throw new BlockException("Cannot create a block group for blocks without a group!");
Id = new EGID((uint) id, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
sourceBlock = block; sourceBlock = block;
blocks = new List<Block>(GetBlocks()); blocks = new List<Block>(GetBlocks());
Block.Removed += OnBlockRemoved; Block.Removed += OnBlockRemoved;

View file

@ -76,10 +76,11 @@ namespace TechbloxModdingAPI.Blocks
/// <param name="startPort">Starting port number, or guess if omitted.</param> /// <param name="startPort">Starting port number, or guess if omitted.</param>
/// <param name="endPort">Ending 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> /// <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; var th = (Wire)ecs;
endBlockEGID = end.Id; th.startBlockEGID = start.Id;
th.endBlockEGID = end.Id;
bool flipped = false; bool flipped = false;
// find block ports // find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort); EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort);
@ -94,12 +95,16 @@ namespace TechbloxModdingAPI.Blocks
if (wire != default) if (wire != default)
{ {
Construct(start.Id, end.Id, startPort, endPort, wire, flipped); th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
} }
else else
{ {
throw new WireInvalidException("Wire not found"); throw new WireInvalidException("Wire not found");
} }
return th.wireEGID;
})
{
} }
/// <summary> /// <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); Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput);
} }
@ -139,7 +144,7 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
/// <param name="wireEgid">The wire ID.</param> /// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid) public Wire(EGID wireEgid) : base(wireEgid)
{ {
WireEntityStruct wire = signalEngine.GetWire(wireEGID); WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, 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> /// <summary>
/// The wire's signal value, as a float. /// The wire's signal value, as a float.
/// </summary> /// </summary>

View file

@ -10,11 +10,8 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class Cluster : EcsObjectBase public class Cluster : EcsObjectBase
{ {
public override EGID Id { get; } public Cluster(EGID id) : base(id)
public Cluster(EGID id)
{ {
Id = id;
} }
public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP)) public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP))

View file

@ -10,8 +10,8 @@ namespace TechbloxModdingAPI
{ {
public abstract class EcsObjectBase 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 = private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>(); new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
@ -39,7 +39,19 @@ namespace TechbloxModdingAPI
return (T)instance; 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)) if (!_instances.TryGetValue(GetType(), out var dict))
{ {
@ -47,11 +59,9 @@ namespace TechbloxModdingAPI
_instances.Add(GetType(), dict); _instances.Add(GetType(), dict);
} }
// ReSharper disable VirtualMemberCallInConstructor var id = initializer(this);
// The ID should not depend on the constructor if (!dict.ContainsKey(id)) // Multiple instances may be created
if (!dict.ContainsKey(Id)) // Multiple instances may be created dict.Add(id, this);
dict.Add(Id, this);
// ReSharper enable VirtualMemberCallInConstructor
} }
#region ECS initializer stuff #region ECS initializer stuff

View file

@ -5,6 +5,7 @@ using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd; using RobocraftX.FrontEnd;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines namespace TechbloxModdingAPI.Engines
@ -12,10 +13,12 @@ namespace TechbloxModdingAPI.Engines
[HarmonyPatch] [HarmonyPatch]
class GameLoadedEnginePatch class GameLoadedEnginePatch
{ {
public static EntitiesSubmissionScheduler Scheduler { get; private set; }
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{ {
// register all game engines, including deterministic // register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg); GameEngineManager.RegisterEngines(stateSyncReg);
Scheduler = stateSyncReg.enginesRoot.scheduler;
} }
public static MethodBase TargetMethod() public static MethodBase TargetMethod()

View file

@ -0,0 +1,9 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Engines
{
public interface IFunEngine : IApiEngine
{
public IEntityFunctions Functions { set; }
}
}

View file

@ -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);
}
}

View file

@ -20,10 +20,11 @@ namespace TechbloxModdingAPI
/// <summary> /// <summary>
/// An in-game player character. Any Leo you see is a player. /// An in-game player character. Any Leo you see is a player.
/// </summary> /// </summary>
public class Player : IEquatable<Player>, IEquatable<EGID> public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{ //TODO: Inherit EcsObjectBase and make Id an EGID, useful for caching {
// static functionality // static functionality
private static PlayerEngine playerEngine = new PlayerEngine(); private static PlayerEngine playerEngine = new PlayerEngine();
private static PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer; private static Player localPlayer;
/// <summary> /// <summary>
@ -79,7 +80,7 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="id">The player's unique identifier.</param> /// <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; this.Id = id;
if (!Exists(id)) if (!Exists(id))
@ -93,22 +94,31 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param> /// <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) uint id;
{ switch (player)
case PlayerType.Local: {
this.Id = playerEngine.GetLocalPlayer(); case PlayerType.Local:
break; id = playerEngine.GetLocalPlayer();
case PlayerType.Remote: break;
this.Id = playerEngine.GetRemotePlayer(); case PlayerType.Remote:
break; id = playerEngine.GetRemotePlayer();
} break;
if (this.Id == uint.MaxValue) default:
{ id = uint.MaxValue;
throw new PlayerNotFoundException($"No player of {player} type exists"); break;
} }
this.Type = player;
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 // object fields & properties
@ -124,7 +134,7 @@ namespace TechbloxModdingAPI
/// The player's unique identifier. /// The player's unique identifier.
/// </summary> /// </summary>
/// <value>The identifier.</value> /// <value>The identifier.</value>
public uint Id { get; } public new uint Id { get; }
/// <summary> /// <summary>
/// The player's current position. /// The player's current position.
@ -424,6 +434,16 @@ namespace TechbloxModdingAPI
} }
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); playerEngine.SetLocation(Id, location, exitSeat: exitSeat);
} }
public void EnterSeat(Seat seat)
{
playerEngine.EnterSeat(Id, seat.Id);
}
public void ExitSeat()
{
playerEngine.ExitSeat(Id);
}
/// <summary> /// <summary>
/// Returns the block the player is currently looking at in build mode. /// Returns the block the player is currently looking at in build mode.
@ -507,6 +527,7 @@ namespace TechbloxModdingAPI
internal static void Init() internal static void Init()
{ {
Utility.GameEngineManager.AddGameEngine(playerEngine); Utility.GameEngineManager.AddGameEngine(playerEngine);
Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
} }
} }
} }

View file

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System;
using System.Runtime.CompilerServices;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Character.Movement; using RobocraftX.Character.Movement;
@ -8,10 +9,13 @@ using RobocraftX.CR.MachineEditing.BoxSelect;
using RobocraftX.Physics; using RobocraftX.Physics;
using RobocraftX.Blocks.Ghost; using RobocraftX.Blocks.Ghost;
using Gamecraft.GUI.HUDFeedbackBlocks; using Gamecraft.GUI.HUDFeedbackBlocks;
using RobocraftX.Blocks;
using RobocraftX.PilotSeat;
using Svelto.ECS; using Svelto.ECS;
using Techblox.Camera; using Techblox.Camera;
using Unity.Mathematics; using Unity.Mathematics;
using Svelto.ECS.DataStructures; using Svelto.ECS.DataStructures;
using Svelto.ECS.EntityStructs;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
@ -19,7 +23,7 @@ using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {
internal class PlayerEngine : IApiEngine, IFactoryEngine internal class PlayerEngine : IFunEngine
{ {
public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine"; public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine";
@ -27,7 +31,7 @@ namespace TechbloxModdingAPI.Players
public bool isRemovable => false; public bool isRemovable => false;
public IEntityFactory Factory { set; private get; } public IEntityFunctions Functions { get; set; }
private bool isReady = false; private bool isReady = false;
@ -101,9 +105,7 @@ namespace TechbloxModdingAPI.Players
return false; return false;
if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat) if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat)
{ {
EGID egid = new EGID(playerId, group); ExitSeat(playerId);
entitiesDB.QueryEntity<CharacterPilotSeatEntityStruct>(egid).instantExit = true;
entitiesDB.PublishEntityChange<CharacterPilotSeatEntityStruct>(egid);
} }
rbesOpt.Get().position = location; rbesOpt.Get().position = location;
return true; return true;
@ -183,12 +185,12 @@ namespace TechbloxModdingAPI.Players
{ {
if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid, if (!entitiesDB.Exists<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)) BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup))
return new Block[0]; return Array.Empty<Block>();
var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid, var state = entitiesDB.QueryEntity<BoxSelectStateEntityStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid, var blocks = entitiesDB.QueryEntity<SelectedBlocksStruct>(playerid,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup);
if (!state.active) return new Block[0]; if (!state.active) return Array.Empty<Block>();
var pointer = (EGID*) blocks.selectedBlocks.ToPointer(); var pointer = (EGID*) blocks.selectedBlocks.ToPointer();
var ret = new Block[blocks.count]; var ret = new Block[blocks.count];
for (int j = 0; j < blocks.count; j++) for (int j = 0; j < blocks.count; j++)
@ -199,5 +201,31 @@ namespace TechbloxModdingAPI.Players
return ret; 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);
}
} }
} }

View file

@ -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 });
}
}
}

View file

@ -1,8 +1,11 @@
using System; using System.Collections.Generic;
using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
namespace TechbloxModdingAPI.Players 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; 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."); 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)] [APITestCase(TestType.Menu)]
public static void InvalidStateTest() public static void InvalidStateTest()

View file

@ -14,8 +14,6 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID> public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID>
{ {
public override EGID Id { get; }
/// <summary> /// <summary>
/// The cluster this chunk belongs to, or null if no cluster destruction manager present or the chunk doesn't exist. /// 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. /// Get the SimBody from a Block if possible for good performance here.
@ -28,9 +26,8 @@ namespace TechbloxModdingAPI
private Cluster cluster; private Cluster cluster;
private readonly uint clusterId = uint.MaxValue; 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)) public SimBody(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_BODIES_GROUP))

View file

@ -58,6 +58,14 @@ namespace TechbloxModdingAPI.Tests
// debug/test handlers // debug/test handlers
Client.EnterMenu += (sender, args) => throw new Exception("Test handler always throws an exception!"); 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 // debug/test commands
if (Dependency.Hell("ExtraCommands")) if (Dependency.Hell("ExtraCommands"))

View file

@ -72,14 +72,6 @@ namespace TechbloxModdingAPI.Utility
} }
} }
public static SimpleEntitiesSubmissionScheduler _mainGameSubmissionScheduler
{
get
{
return (SimpleEntitiesSubmissionScheduler)fgcr?.Field("_sub").Field("_mainGameSubmissionScheduler").GetValue();
}
}
public static BuildPhysicsWorld _physicsWorldSystem public static BuildPhysicsWorld _physicsWorldSystem
{ {
get get

View file

@ -26,10 +26,10 @@ namespace TechbloxModdingAPI.Utility
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine); _lastEngineRoot.AddEngine(engine);
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType())) if (engine is IFactoryEngine factoryEngine)
{ factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory(); if (engine is IFunEngine funEngine)
} funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
} }
} }
@ -66,6 +66,7 @@ namespace TechbloxModdingAPI.Utility
var enginesRoot = helper.enginesRoot; var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _gameEngines.Keys) foreach (var key in _gameEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}"); Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
@ -75,6 +76,8 @@ namespace TechbloxModdingAPI.Utility
enginesRoot.AddEngine(_gameEngines[key]); enginesRoot.AddEngine(_gameEngines[key]);
if (_gameEngines[key] is IFactoryEngine factEngine) if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory; factEngine.Factory = factory;
if (_gameEngines[key] is IFunEngine funEngine)
funEngine.Functions = functions;
} }
} }
} }

View file

@ -26,10 +26,10 @@ namespace TechbloxModdingAPI.Utility
{ {
Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}"); Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine); _lastEngineRoot.AddEngine(engine);
if (typeof(IFactoryEngine).IsAssignableFrom(engine.GetType())) if (engine is IFactoryEngine factoryEngine)
{ factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
((IFactoryEngine)engine).Factory = _lastEngineRoot.GenerateEntityFactory(); if (engine is IFunEngine funEngine)
} funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
} }
} }
@ -65,14 +65,13 @@ namespace TechbloxModdingAPI.Utility
{ {
_lastEngineRoot = enginesRoot; _lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory(); IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _menuEngines.Keys) foreach (var key in _menuEngines.Keys)
{ {
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}"); Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}");
enginesRoot.AddEngine(_menuEngines[key]); enginesRoot.AddEngine(_menuEngines[key]);
if (_menuEngines[key] is IFactoryEngine factEngine) if (_menuEngines[key] is IFactoryEngine factEngine) factEngine.Factory = factory;
{ if(_menuEngines[key] is IFunEngine funEngine) funEngine.Functions = functions;
factEngine.Factory = factory;
}
} }
} }
} }