using System; 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.Common; using RobocraftX.Multiplayer; using RobocraftX.SimulationModeState; using Svelto.ECS; using Techblox.Camera; using Unity.Mathematics; using Svelto.ECS.DataStructures; using Techblox.BuildingDrone; using Techblox.Character; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Players { internal class PlayerEngine : IFunEngine { public string Name { get; } = "TechbloxModdingAPIPlayerGameEngine"; public EntitiesDB entitiesDB { set; private get; } public bool isRemovable => false; public IEntityFunctions Functions { get; set; } private bool isReady = false; public void Dispose() { isReady = false; } public void Ready() { isReady = true; } public uint GetLocalPlayer() { if (!isReady) return uint.MaxValue; var (localPlayers, count) = entitiesDB.QueryEntities(PlayersExclusiveGroups.LocalPlayers); if (count > 0) { return localPlayers[0].ID.entityID; } return uint.MaxValue; } public uint GetRemotePlayer() { if (!isReady) return uint.MaxValue; var (localPlayers, count) = entitiesDB.QueryEntities(PlayersExclusiveGroups.RemotePlayers); if (count > 0) { return localPlayers[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(eg); } return count; } public long GetLocalPlayerCount() { if (entitiesDB == null) return 0; return entitiesDB.Count(PlayersExclusiveGroups.LocalPlayers); } public long GetRemotePlayerCount() { if (entitiesDB == null) return 0; return entitiesDB.Count(PlayersExclusiveGroups.RemotePlayers); } public bool ExistsById(uint playerId) { if (entitiesDB == null) return false; return entitiesDB.Exists(playerId, PlayersExclusiveGroups.LocalPlayers) || entitiesDB.Exists(playerId, PlayersExclusiveGroups.RemotePlayers); } public bool SetLocation(uint playerId, float3 location, bool exitSeat = true) { if (entitiesDB == null) return false; var rbesOpt = GetCharacterStruct(playerId, out var group); if (!rbesOpt) return false; if (group == CharacterExclusiveGroups.InPilotSeatGroup && exitSeat) { ExitSeat(playerId); } rbesOpt.Get().position = location; return true; } public bool IsDead(uint playerId) { if (entitiesDB == null) return true; return entitiesDB.Exists(playerId, CharacterExclusiveGroups.DeadGroup); } // reusable methods [MethodImpl(MethodImplOptions.AggressiveInlining)] public OptionalRef GetCharacterStruct(uint playerId) where T : unmanaged, IEntityComponent => GetCharacterStruct(playerId, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] public OptionalRef GetCharacterStruct(uint playerId, out ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent { group = default; if (GameState.IsBuildMode()) return entitiesDB.QueryEntityOptional(new EGID(playerId, LocalBuildingDrone.BuildGroup)); var characterGroups = CharacterExclusiveGroups.AllCharacters; for (int i = 0; i < characterGroups.count; i++) { EGID egid = new EGID(playerId, characterGroups[i]); var opt = entitiesDB.QueryEntityOptional(egid); if (opt.Exists) { group = characterGroups[i]; return opt; } } return new OptionalRef(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public OptionalRef GetPlayerStruct(uint playerId, PlayerType type) where T : unmanaged, IEntityComponent { var playerGroup = type == PlayerType.Local ? PlayersExclusiveGroups.LocalPlayers : PlayersExclusiveGroups.RemotePlayers; EGID egid = new EGID(playerId, playerGroup); return entitiesDB.QueryEntityOptional(egid); } public OptionalRef GetCameraStruct(uint playerId) where T : unmanaged, IEntityComponent { return entitiesDB.QueryEntityOptional(new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup)); } public EGID GetThingLookedAt(uint playerId, float maxDistance = -1f) { var opt = GetCameraStruct(playerId); if (!opt) return default; PhysicCameraRayCastEntityStruct rayCast = opt; EGID physicCameraEgid = new EGID(playerId, CameraExclusiveGroups.PhysicCameraGroup); float distance = maxDistance < 0 ? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast, physicCameraEgid, GhostBlockUtils.GhostCastMethod.GhostCastProportionalToBlockSize) : maxDistance; if (rayCast.hit && rayCast.distance <= distance) return rayCast.hitEgid; //May be EGID.Empty (default) return default; } public unsafe Block[] GetSelectedBlocks(uint playerid) { if (!entitiesDB.Exists(playerid, BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)) return Array.Empty(); var state = entitiesDB.QueryEntity(playerid, BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); var blocks = entitiesDB.QueryEntity(playerid, BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup); if (!state.active) return Array.Empty(); 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] = Block.New(egid); } return ret; } public void EnterSeat(uint playerId, EGID seatId) { if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) return; /*PilotSeatGroupUtils.SwapTagTo(Functions, seatId); var opt = GetCharacterStruct(playerId, out var group); if (!opt) return; - TODO: This is server code and mods run in client code atm. We can only send inputs even in singleplayer as it is. ref CharacterPilotSeatEntityStruct charSeat = ref opt.Get(); var charId = new EGID(playerId, group); charSeat.pilotSeatEntity = entitiesDB.GetEntityReference(seatId); charSeat.entryPositionOffset = entitiesDB.QueryEntity(charId).position - entitiesDB.QueryEntity(seatId).position; ref var seat = ref entitiesDB.QueryEntity(seatId); seat.occupyingCharacter = entitiesDB.GetEntityReference(charId); charSeat.followCam = entitiesDB.QueryEntity(seatId).followCam; Functions.SwapEntityGroup(charId, CharacterExclusiveGroups.InPilotSeatGroup);*/ } public void ExitSeat(uint playerId) { if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) return; /*EGID egid = new EGID(playerId, CharacterExclusiveGroups.InPilotSeatGroup); var opt = entitiesDB.QueryEntityOptional(egid); if (!opt) return; opt.Get().instantExit = true; entitiesDB.PublishEntityChange(egid);*/ } public bool SpawnMachine(uint playerId) { if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) return false; EGID egid = new EGID(playerId, CharacterExclusiveGroups.MachineSpawningGroup); if (!entitiesDB.Exists(egid)) return false; if (entitiesDB.QueryEntity(egid).isMachinePlacementInvalid) { Logging.MetaDebugLog("Machine placement invalid"); return false; } //Functions.SwapEntityGroup(egid, CharacterExclusiveGroups.OnFootGroup); FakeInput.ActionInput(playerId, primary: true); return true; } public bool DespawnMachine(uint playerId) { if (!TimeRunningModeUtil.IsTimeRunningMode(entitiesDB)) return false; GetCharacterStruct(playerId, out var group); if (group.isInvalid) return false; EGID egid = new EGID(playerId, group); if (!entitiesDB.Exists(egid)) return false; Functions.SwapEntityGroup(egid, CharacterExclusiveGroups.MachineSpawningGroup); return true; } public uint GetPing() { return entitiesDB .QueryUniqueEntity(MultiplayerExclusiveGroups.MultiplayerStateGroup) .networkStats.PingMs; } public Block GetGhostBlock(uint playerId) { var egid = new EGID(playerId, GHOST_BLOCKS_ENABLED.Group); return entitiesDB.Exists(egid) ? Block.New(egid) : null; } } }