using System; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Linq; using System.Collections.ObjectModel; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.Datastructures; using Vintagestory.API.MathTools; using Vintagestory.API.Server; using Vintagestory.API.Util; using Vintagestory.GameContent; using Vintagestory.Server; using Vintagestory.Common; namespace FirstMachineAge { /// /// Access controls mod. /// public partial class AccessControlsMod : ModSystem { public static string _LockLocationKey = @"lock_pos"; public static string _variantDoorPartKey = @"part"; public const string _keyCodeName = @"key";//The first part of code-name public const string _lockMaterial = @"material";//second part of code-name (lock or key) //Example: fma:key-iron public const string _itemDescription = @"description"; #region Mod System public override bool ShouldLoad(EnumAppSide forSide) { return true; } public override void Start(ICoreAPI api) { this.CoreAPI = api; RegisterStuff(api); } public override void StartClientSide(ICoreClientAPI api) { this.ClientAPI = api; //Called too early? InitializeClientSide( ); } public override void StartServerSide(ICoreServerAPI api) { this.ServerAPI = api; this.PlayerDatamanager = api.Permissions as PlayerDataManager; if (ServerAPI.World is ServerMain) { this.ServerMAIN = ServerAPI.World as ServerMain; } else { Mod.Logger.Error("Cannot access 'ServerMain' class: API (implimentation) has changed, Contact Developer!"); return; } ServerAPI.Event.ServerRunPhase(EnumServerRunPhase.LoadGame, InitializeServerSide); } public override double ExecuteOrder( ) { return 0.19; } #endregion /* // Client side data Dictionary> reinforcementsByChunk = new Dictionary>(); Dictionary> clientChannel = api.Network .RegisterChannel("blockreinforcement") .RegisterMessageType(typeof(ChunkReinforcementData)) .SetMessageHandler(onData) data = chunk.GetModdata("reinforcements"); Dictionary reinforcmentsOfChunk = null; reinforcmentsOfChunk = SerializerUtil.Deserialize>(data); */ #region Access Control Interface public void AdjustBlockPostionForMultiBlockStructure(ref BlockPos blockPos) { //SO far - this means ONLY for class: BlockDoor .... the 'upper' part. var thatBlock = CoreAPI.World.BlockAccessor.GetBlock(blockPos); /* class: "BlockDoor", >> BlockBaseDoor { code: "part", states: ["down", "up"] }, { code: "state", states: ["closed", "opened"] }, */ if (thatBlock != null && thatBlock.Id > 0 && thatBlock is BlockBaseDoor) { BlockBaseDoor doorBase = thatBlock as BlockBaseDoor; if (doorBase.Variant.ContainsKey(_variantDoorPartKey)) { string doorPart = doorBase.Variant[_variantDoorPartKey]; if (doorPart.Equals("down", StringComparison.OrdinalIgnoreCase)) { Mod.Logger.VerboseDebug("Adjusting blockPos ({0}) becomes upper door half...", blockPos); blockPos = blockPos.UpCopy( ); } } } } //BRS - should be transfer'd on-fly to ACL format...from pre-loader public LockStatus LockState(BlockPos pos, IPlayer forPlayer) { pos = pos.Copy( ); if (CoreAPI.Side.IsClient( )) { if (Client_LockLookup.ContainsKey(pos)) { return Client_LockLookup[pos].LockState; } else { return LockStatus.Unknown;//Any lock state lookup is from a Behavior Lockable - thus should have _SOME_ entry } } else { //Server instance Vec3i locksChunk = ServerAPI.World.BlockAccessor.ToChunkPos(pos); if (Server_ACN.ContainsKey(locksChunk) && Server_ACN[locksChunk].Entries.ContainsKey(pos)) { var controlNode = Server_ACN[locksChunk].Entries[pos]; var locked = EvaulateACN_Rule(forPlayer, controlNode); switch (controlNode.LockStyle) { case LockKinds.Classic: return locked ? LockStatus.Locked : LockStatus.Unlocked; case LockKinds.Combination: return locked ? LockStatus.ComboUnknown : LockStatus.ComboKnown; case LockKinds.Key: return locked ? LockStatus.KeyNope : LockStatus.KeyHave; } } } return LockStatus.None;//No entry made here (new?) } public uint LockTier(BlockPos pos, IPlayer forPlayer) { if (CoreAPI.Side.IsClient( )) { if (Client_LockLookup.ContainsKey(pos.Copy( ))) { return Client_LockLookup[pos.Copy( )].Tier; } else { //TODO: Force fetch from Server ? } } else { //Server instance Vec3i locksChunk = ServerAPI.World.BlockAccessor.ToChunkPos(pos); if (Server_ACN.ContainsKey(locksChunk) && Server_ACN[locksChunk].Entries.ContainsKey(pos)) { var controlNode = Server_ACN[locksChunk].Entries[pos]; return controlNode.Tier; } } return 0; } public string LockOwnerName(BlockPos pos, IPlayer forPlayer) { if (CoreAPI.Side.IsClient( )) { if (Client_LockLookup.ContainsKey(pos.Copy( ))) { return Client_LockLookup[pos.Copy( )].OwnerName; } else { //TODO: Force fetch from Server ? } } else { //Server instance Vec3i locksChunk = ServerAPI.World.BlockAccessor.ToChunkPos(pos); if (Server_ACN.ContainsKey(locksChunk) && Server_ACN[locksChunk].Entries.ContainsKey(pos)) { var controlNode = Server_ACN[locksChunk].Entries[pos]; return ServerAPI.World.PlayerByUid(controlNode.OwnerPlayerUID).PlayerName; } } return String.Empty; } /// /// Backwardsy compatible method - for lockable behaviors /// /// when locked. /// Position. /// Player. /// AssetCode. public bool LockedForPlayer(BlockPos position, IPlayer forPlayer, AssetLocation code = null) { if (CoreAPI.Side.IsClient( )) { return this.CheckClientsideIsLocked(position, forPlayer); } var controlNode = RetrieveACN(position.Copy( )); if ( controlNode != null && controlNode.LockStyle != LockKinds.None) { return EvaulateACN_Rule(forPlayer, controlNode); } return false; } public AccessControlNode GrantIndividualPlayerAccess(IServerPlayer fromPlayer, BlockPos position, string reason) { throw new NotImplementedException( ); } public AccessControlNode RevokeIndividualPlayerAccess(IServerPlayer fromPlayer, BlockPos position, string reason) { throw new NotImplementedException( ); } public AccessControlNode GrantGroupAccess(string groupUID, BlockPos position, string reason) { throw new NotImplementedException( ); } public AccessControlNode RevokeGroupAccess(string groupUID, BlockPos position, string reason) { throw new NotImplementedException( ); } /// /// Checks the clientside is locked. /// /// If locked is TRUE. /// Position. /// For player. public bool CheckClientsideIsLocked(BlockPos position, IPlayer forPlayer) { if (Client_LockLookup.ContainsKey(position)) { if (Client_LockLookup[position].LockState == LockStatus.ComboUnknown || Client_LockLookup[position].LockState == LockStatus.KeyNope || Client_LockLookup[position].LockState == LockStatus.Locked || Client_LockLookup[position].LockState == LockStatus.Unknown) { return true; } } return false;//Probably not... } /// /// Adds a 'None' lock entry client-side only. /// /// Position. /// Owner. public void AddPlaceHolder_SelfCache(BlockPos pos) { pos = pos.Copy( ); if (this.Client_LockLookup.ContainsKey(pos)) { Mod.Logger.Error("Can't overwrite cached lock entry located: {0}", pos); } else { var placheHolderState = new LockCacheNode( ); placheHolderState.Tier = 0; placheHolderState.OwnerName = ClientAPI.World.Player.PlayerName; placheHolderState.LockState = LockStatus.None;//Default Unlocked this.Client_LockLookup.Add(pos, placheHolderState); Mod.Logger.Debug("Added cach entry located: {0}", pos); } } public void AddPlaceHolder_Server(BlockPos pos) { if (CoreAPI.Side.IsServer( )) { BlockPos blockPos = pos.Copy( ); AdjustBlockPostionForMultiBlockStructure(ref blockPos); Mod.Logger.VerboseDebug("Creating placehodler; @{0} ", blockPos); AccessControlNode placeHolder = new AccessControlNode(); if (ACN_IsNew(blockPos)) { AddACN_ToServerACNs(blockPos, placeHolder); //Send message to player that object was locked with X type lock (and combo / key#) //Send out ACN update selective broadcast msg... UpdateBroadcast(null, blockPos, placeHolder); } else { Mod.Logger.Warning("Prevented duplicate placeholder @{0} ...", blockPos); } } } public void RemovelaceHolder_SelfCache(BlockPos pos) { pos = pos.Copy( ); if (this.Client_LockLookup.ContainsKey(pos) == false) { Mod.Logger.Error("Non-existant remove cached entry located: {0}", pos); } else { this.Client_LockLookup.Remove(pos); Mod.Logger.Debug("Removed cach entry located: {0}", pos); } } public void ApplyLock(BlockSelection blockSel, IPlayer player, ItemSlot itemSlot, string desc = null) { bool commitACN = true; GenericLock theLock = itemSlot.Itemstack.Item as GenericLock; string material = theLock.Variant[_lockMaterial]; BlockPos blockPos = blockSel.Position.Copy( ); //TODO: Adjust position(s) with N block high doors, but player selected 'lower' part... AdjustBlockPostionForMultiBlockStructure(ref blockPos); //Client path only updates local cache? if (CoreAPI.Side.IsClient( )) { AddLock_ClientCache(blockPos, theLock, player); return; } //Server continues IServerPlayer serverPlayer = player as IServerPlayer; Vec3i chunkPos = ServerAPI.World.BlockAccessor.ToChunkPos(blockPos); Mod.Logger.VerboseDebug("Applying lock; {0} T{1} @ {2} by {3}", theLock.LockStyle, theLock.LockTier, blockPos, player.PlayerName); AccessControlNode newLockACN = new AccessControlNode(player.PlayerUID, theLock.LockStyle ); if (theLock.LockStyle == LockKinds.Combination) { newLockACN.LockStyle = LockKinds.Combination; newLockACN.CombinationCode = theLock.CombinationCode(itemSlot); newLockACN.PermittedPlayers = new List( ); newLockACN.Tier = theLock.LockTier; if (newLockACN.CombinationCode == null) { Mod.Logger.Warning("Undefined Combination # for existant lock - can't apply!"); commitACN = false; } } if (theLock.LockStyle == LockKinds.Key) { newLockACN.KeyID = theLock.KeyID(itemSlot).GetValueOrDefault(-1); //Perform inventory item swap to true Key from lock (create key first...) GenericKey matchingKey = ServerAPI.World.GetItem(new AssetLocation(_domain,_keyCodeName+"-"+material)) as GenericKey; ItemStack itemStackForKey = new ItemStack(matchingKey, 1); GenericKey.WriteACL_ItemStack(ref itemStackForKey, newLockACN, blockPos); serverPlayer.InventoryManager.TryGiveItemstack(itemStackForKey, true); //Mark slot dirty? Mod.Logger.VerboseDebug("Created matching Key #{0} for lock@{1}", newLockACN.KeyID, blockPos); } if (commitACN) { AddACN_ToServerACNs(blockPos, newLockACN); } //Send message to player that object was locked with X type lock (and combo / key#) //Send out ACN update selective broadcast msg... if (commitACN) UpdateBroadcast(serverPlayer, blockPos, newLockACN ); } /// /// Destroy A.C.N. node at this Position. [Permanent!] /// /// The lock. /// Block position. public void DestroyLock(BlockPos blockPos) { //By a creative player or world edit - erase any lock entry here. if (CoreAPI.Side.IsServer( )) { blockPos = blockPos.Copy( ); //Server continues Vec3i chunkPos = ServerAPI.World.BlockAccessor.ToChunkPos(blockPos); Mod.Logger.VerboseDebug("Removing ACL entry @{0} ", blockPos); AccessControlNode remLockACN = Server_ACN[chunkPos].Entries[blockPos]; remLockACN.LockStyle = LockKinds.None;//Remove from other players ACN caches' remLockACN.Tier = 0; //Send message to other players that A.C.N. no longer exists here. UpdateBroadcast(null, blockPos, remLockACN); Server_ACN[chunkPos].Entries.Remove(blockPos); } } /// /// Removes the lock. (set A.C.N. back to LockState.None) /// /// The lock. /// Block sel. /// Player. public void RemoveLock(BlockPos blockPos, IPlayer player) { blockPos = blockPos.Copy( ); //Client path only updates local cache? if (CoreAPI.Side.IsClient( )) { RemoveLock_ClientCache(blockPos); return; } //Server continues IServerPlayer serverPlayer = player as IServerPlayer; Vec3i chunkPos = ServerAPI.World.BlockAccessor.ToChunkPos(blockPos); Mod.Logger.VerboseDebug("De-lockify ACL entry @{0} by {1}", blockPos, player.PlayerName); if (Server_ACN[chunkPos].Entries.ContainsKey(blockPos)) { var remLockACN = Server_ACN[chunkPos].Entries[blockPos]; if (remLockACN.LockStyle == LockKinds.Key && ACNs_byKeyID.ContainsKey(remLockACN.KeyID.Value)) { ACNs_byKeyID.Remove(remLockACN.KeyID.Value); } Server_ACN[chunkPos].Entries.Remove(blockPos); remLockACN = new AccessControlNode();//Remove from other players ACN caches' Server_ACN[chunkPos].Entries.Add(blockPos, remLockACN); Server_ACN[chunkPos].Altered = true; //Send message to players that object was de-locked by a player UpdateBroadcast(serverPlayer, blockPos, remLockACN); } else { Mod.Logger.Warning("Removing non-existant A.C.N.: @{0} by {1}", blockPos, player.PlayerName); } } /// /// Retrieves the ACN data /// /// Access Control node for a BlocKPos. /// By block position. public AccessControlNode RetrieveACN(BlockPos byBlockPos) { var chunkPos = ServerAPI.World.BulkBlockAccessor.ToChunkPos(byBlockPos); AccessControlNode node = null; if (this.Server_ACN.ContainsKey(chunkPos)) { if (Server_ACN[chunkPos].Entries.TryGetValue(byBlockPos, out node)) { return node; } } else { //Un cached chunk; LoadACN_fromChunk(chunkPos); if (Server_ACN[chunkPos].Entries.TryGetValue(byBlockPos, out node)) { return node; } } return null; } // byte[] GetServerModdata (string key); //void SetServerModdata(string key, byte[] data); //public byte[] GetData(string name) //public void StoreData(string name, byte[] value) //_oAzFHaLM7aeBn6i00bHS72XxcA9 ISaveGame public IDictionary RetrieveACNs_ByChunk(Vec3i byChunkPos) { if (Server_ACN.ContainsKey(byChunkPos)) { return Server_ACN[byChunkPos].Entries; } else { } return null; } public ReadOnlyDictionary> RetrieveKnownKeys( ) { return new ReadOnlyDictionary>(ACNs_byKeyID); } protected bool AttemptAccess(IPlayer byPlayer, BlockPos atPosition, byte[] guess = null) { var acn = RetrieveACN(atPosition); if (acn.LockStyle == LockKinds.Combination) { } else { Mod.Logger.Warning("Attempt to access with mis-matching lock types! BY: {0}", byPlayer.PlayerName); } return false;//Not it. } #endregion } }