From 8edebea402f62ccd33e5b25f67ff1d812f9a6c81 Mon Sep 17 00:00:00 2001 From: melchior Date: Mon, 9 Sep 2019 16:56:52 -0400 Subject: [PATCH] Initial commit of Access Control mod --- .gitignore | 40 ++ AccessControls/AccessControlMod.cs | 369 ++++++++++++++++++ AccessControls/AccessControls.csproj | 96 +++++ AccessControls/GUIs/GuiDialog_ComboLock.cs | 25 ++ AccessControls/GroupLocksCmd.cs | 25 ++ AccessControls/LocksmithCmd.cs | 32 ++ AccessControls/Properties/AssemblyInfo.cs | 27 ++ .../assets/fma/itemtypes/locks/combolock.json | 37 ++ .../behaviors/BlockBehaviorComplexLockable.cs | 73 ++++ AccessControls/data/ACLPersisted.cs | 21 + AccessControls/data/AccessControlNode.cs | 107 +++++ AccessControls/data/LockKinds.cs | 18 + AccessControls/data/LockStatus.cs | 19 + AccessControls/items/GenericKey.cs | 31 ++ AccessControls/items/GenericLock.cs | 148 +++++++ AccessControls/items/ItemCombolock.cs | 48 +++ FirstMachineAge_Common/Common.csproj | 52 +++ FirstMachineAge_Common/Helpers.cs | 144 +++++++ .../Properties/AssemblyInfo.cs | 27 ++ First_Machine_Age.sln | 168 ++++++++ 20 files changed, 1507 insertions(+) create mode 100644 .gitignore create mode 100644 AccessControls/AccessControlMod.cs create mode 100644 AccessControls/AccessControls.csproj create mode 100644 AccessControls/GUIs/GuiDialog_ComboLock.cs create mode 100644 AccessControls/GroupLocksCmd.cs create mode 100644 AccessControls/LocksmithCmd.cs create mode 100644 AccessControls/Properties/AssemblyInfo.cs create mode 100644 AccessControls/assets/fma/itemtypes/locks/combolock.json create mode 100644 AccessControls/behaviors/BlockBehaviorComplexLockable.cs create mode 100644 AccessControls/data/ACLPersisted.cs create mode 100644 AccessControls/data/AccessControlNode.cs create mode 100644 AccessControls/data/LockKinds.cs create mode 100644 AccessControls/data/LockStatus.cs create mode 100644 AccessControls/items/GenericKey.cs create mode 100644 AccessControls/items/GenericLock.cs create mode 100644 AccessControls/items/ItemCombolock.cs create mode 100644 FirstMachineAge_Common/Common.csproj create mode 100644 FirstMachineAge_Common/Helpers.cs create mode 100644 FirstMachineAge_Common/Properties/AssemblyInfo.cs create mode 100644 First_Machine_Age.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bbcfed --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +#Autosave files +*~ + +#build +[Oo]bj/ +[Bb]in/ +packages/ +TestResults/ + +# globs +Makefile.in +*.DS_Store +*.sln.cache +*.suo +*.cache +*.pidb +*.userprefs +*.usertasks +config.log +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.user +*.tar.gz +tarballs/ +test-results/ +Thumbs.db + +#Mac bundle stuff +*.dmg +*.app + +#resharper +*_Resharper.* +*.Resharper + +#dotCover +*.dotCover diff --git a/AccessControls/AccessControlMod.cs b/AccessControls/AccessControlMod.cs new file mode 100644 index 0000000..e3f6fef --- /dev/null +++ b/AccessControls/AccessControlMod.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections; +using System.Collections.Generic; + + +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; + +namespace FirstMachineAge +{ + public class AccessControlsMod: ModSystem + { + private const string _AccessControlNodesKey = @"ACCESS_CONTROL_NODES"; + public const string _KeyIDKey = @"key_id";//for JSON attribute, DB key sequence + public const string _persistedStateKey = @"ACL_PersistedState"; + + private ICoreServerAPI ServerAPI; + private ICoreAPI CoreAPI; + private ICoreClientAPI ClientAPI; + private PlayerDataManager PlayerDatamanager; + + private ModSystemBlockReinforcement brs; + + private SortedDictionary Server_ACN;//Track changes - and commit every ## minutes, in addition to server shutdown data-storage, chunk unloads + private Dictionary Client_LockLookup;//By BlockPos - for fast cached lookup. only hold 100's of entries + private ACLPersisted PersistedState; + + private Item KeyItem; + private Item CombolockItem; + private Item UndeployedKeylockItem;//Key & Lock together + private Item DeployedKeylockItem; + + #region Mod System + public override bool ShouldLoad(EnumAppSide forSide) + { + return true; + } + + public override void Start(ICoreAPI api) + { + this.CoreAPI = api; + base.Start(api); + } + + public override void StartClientSide(ICoreClientAPI api) + { + this.ClientAPI = api; + base.StartClientSide(api); + } + + + public override void StartServerSide(ICoreServerAPI api) + { + this.ServerAPI = api; + this.PlayerDatamanager = api.Permissions as PlayerDataManager; + base.StartServerSide(api); + + Initialize(); + } + + #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 + + //Pull data out of BRS first - or intercept its channel packets? + //Then superceed it 100% ? + + public LockStatus LockState(BlockPos pos, IPlayer forPlayer) + { + if (CoreAPI.Side == EnumAppSide.Client) + { + if (Client_LockLookup.ContainsKey(pos.Copy( ))) + { + return Client_LockLookup[pos.Copy( )]; + } + else + { + UpdateLocalLockCache(pos );//Needs to be invoked each time keys change in inventory?! + } + } + else + { + //Server instance + + } + + return LockStatus.None; + } + + + /// + /// Backwardsy compatible method - for lockable behaviors + /// + /// when locked. + /// Position. + /// Player. + /// Code. + public bool LockedForPlayer(BlockPos position, IPlayer forPlayer, AssetLocation code) + { + if (brs.IsLocked(position, forPlayer)) //Replace with local cache? + { + var controlNode = RetrieveACN(position.Copy( )); + + if (controlNode.LockStyle == LockKinds.Classic || controlNode.LockStyle == LockKinds.Combination) { + //Is it yours? + if (controlNode.OwnerPlayerUID == forPlayer.PlayerUID) return false; + + //In same faction? + if (controlNode.PermittedPlayers != null & controlNode.PermittedPlayers.Count > 0) { + + foreach (var perp in controlNode.PermittedPlayers) { + if (perp.PlayerUID == forPlayer.PlayerUID) { + return false;//By discreet entry - combo's add these + } + + if (perp.GroupID.HasValue) { + PlayerGroup targetGroup = PlayerDatamanager.PlayerGroupsById[perp.GroupID.Value]; + if (PlayerDatamanager.PlayerDataByUid.ContainsKey(forPlayer.PlayerUID)) { + ServerPlayerData theirGroup = PlayerDatamanager.PlayerDataByUid[forPlayer.PlayerUID]; + + if (theirGroup.PlayerGroupMemberships.ContainsKey(perp.GroupID.Value)) { + return false;//Is member of group, thus granted. + } + } + } + + } + } + + return true; //Locked BY DEFAULT: [Classic-lock] or [Combo-locks] + } else if (controlNode.LockStyle == LockKinds.Key) //************** End of: Classic / Combination LOCKS *********** + { + //Search inventory for matching KeyID item...each time! + foreach (var inventory in forPlayer.InventoryManager.Inventories) + { + IInventory actual = inventory.Value; + foreach (ItemSlot itmSlot in actual) + { + if (itmSlot.Empty == false && itmSlot.Itemstack.Class == EnumItemClass.Item) + { + if (itmSlot.Itemstack.Item.ItemId == KeyItem.ItemId) + { + //The right key? + var tempKey = itmSlot.Itemstack.Item; + if (tempKey.Attributes.KeyExists(_KeyIDKey)) + { + int tempKeyId = tempKey.Attributes[_KeyIDKey].AsInt(-1); + if (tempKeyId == controlNode.KeyID.Value) + { + return false;//Key works in lock + } + + } + } + + } + } + } + return true; + + }//************** End of: KEY LOCKS *********** + + + } + + return false;//No lock here! + } + + public bool LockedForPlayer(BlockPos position, IPlayer forPlayer) + { + var aclNode = RetrieveACN(position); + + if (aclNode != null) + { + if (aclNode.LockStyle == LockKinds.Key) + { + //Check player Inventory for Keys... do they have a key for THIS lock? + var matchingKeyID = aclNode.KeyID.HasValue ? aclNode.KeyID.Value : 0; + + + foreach (IInventory item in forPlayer.InventoryManager.Inventories.Values) + { + + } + } + + if (aclNode.LockStyle == LockKinds.Combination) + { + //Check lock if AccessControlNode.PermittedPlayers.PlayerUID is present? + + } + + if (aclNode.LockStyle == LockKinds.Classic) + { + return !(aclNode.OwnerPlayerUID == forPlayer.PlayerUID); + } + } + + return false; + } + + + public void ApplyLock(BlockSelection blockSel, IPlayer player, ItemSlot lockSource) + { + //Log it alot! + + GenericLock theLock = lockSource.Itemstack.Item as GenericLock; + + + if (theLock.LockStyle == LockKinds.Combination) { + + } + + if (theLock.LockStyle == LockKinds.Key) { + //keyCode.HasValue + + } + + + } + + public void AlterLockAt(BlockSelection blockSel, IPlayer player, LockKinds lockType, byte[] combinationCode = null, uint? keyCode = null) + { + + } + + protected void UpdateLocalLockCache(BlockPos pos) + { + throw new NotImplementedException( ); + } + + /// + /// Retrieves the ACN data + /// + /// Access Control node for a BlocKPos. + /// By block position. + public AccessControlNode RetrieveACN(BlockPos byBlockPos) + { + long chunkIndex = ServerAPI.World.BulkBlockAccessor.ToChunkIndex3D(byBlockPos); + AccessControlNode node = new AccessControlNode( ); + + if (this.Server_ACN.ContainsKey(chunkIndex)) { + + + if (Server_ACN[chunkIndex].Entries.TryGetValue(byBlockPos, out node)) + { + return node; + } + + + } else + { + //Retrieve and add to local cache + IServerChunk targetChunk; + byte[] data; + + if (ServerAPI.WorldManager.AllLoadedChunks.TryGetValue(chunkIndex, out targetChunk)) { + data = targetChunk.GetServerModdata(_AccessControlNodesKey); + + + } else + { + //An unloaded chunk huh... + targetChunk = ServerAPI.WorldManager.GetChunk(byBlockPos); + data = targetChunk.GetServerModdata(_AccessControlNodesKey); + + } + + if (data != null && data.Length > 0) { + + ChunkACNodes acNodes = SerializerUtil.Deserialize(data); + + Server_ACN.Add(chunkIndex, acNodes); + + acNodes.Entries.TryGetValue(byBlockPos, out node); + + } else + { + //Setup new AC Node list for this chunk. + ChunkACNodes newAcNodes = new ChunkACNodes( ); + + Server_ACN.Add(chunkIndex, newAcNodes); + + + } + } + + + return node; + } + + // byte[] GetServerModdata (string key); + //void SetServerModdata(string key, byte[] data); + + //public byte[] GetData(string name) + //public void StoreData(string name, byte[] value) + + //_oAzFHaLM7aeBn6i00bHS72XxcA9 ISaveGame + + internal int NextKeyID + { + get { return ++PersistedState.KeyId_Sequence;} + } + + public 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 + + private void Initialize( ) + { + var rawBytes = ServerAPI.WorldManager.SaveGame.GetData(_persistedStateKey); + if (rawBytes != null && rawBytes.Length > 0) + { + this.PersistedState = SerializerUtil.Deserialize(rawBytes); + } + else + { + ACLPersisted newPersistedState = new ACLPersisted( ); + + var aclPersistBytes = SerializerUtil.Serialize(newPersistedState); + + ServerAPI.WorldManager.SaveGame.StoreData(_persistedStateKey, aclPersistBytes); + + this.PersistedState = newPersistedState; + } + } + + +} +} + diff --git a/AccessControls/AccessControls.csproj b/AccessControls/AccessControls.csproj new file mode 100644 index 0000000..74634e9 --- /dev/null +++ b/AccessControls/AccessControls.csproj @@ -0,0 +1,96 @@ + + + + Debug + AnyCPU + {180853A2-7E1D-4876-9D1E-AA8608D701C3} + Library + FirstMachineAge + AccessControls + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + ..\FirstMachineAge_Common\vs_libs\VintagestoryAPI.dll + False + + + ..\FirstMachineAge_Common\vs_libs\VSEssentials.dll + False + + + ..\FirstMachineAge_Common\vs_libs\VSCreativeMod.dll + False + + + ..\FirstMachineAge_Common\vs_libs\VSSurvivalMod.dll + False + + + ..\FirstMachineAge_Common\vs_libs\protobuf-net.dll + False + + + ..\FirstMachineAge_Common\vs_libs\VintagestoryLib.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC} + Common + False + + + + \ No newline at end of file diff --git a/AccessControls/GUIs/GuiDialog_ComboLock.cs b/AccessControls/GUIs/GuiDialog_ComboLock.cs new file mode 100644 index 0000000..62031c1 --- /dev/null +++ b/AccessControls/GUIs/GuiDialog_ComboLock.cs @@ -0,0 +1,25 @@ +using System; + +using Vintagestory.API.Client; + +namespace FirstMachineAge +{ + public class GuiDialog_ComboLock : GuiDialog + { + private ICoreClientAPI ClientAPI; + + + public GuiDialog_ComboLock(ICoreClientAPI capi) : base(capi) + { + ClientAPI = capi; + + } + + public override string ToggleKeyCombinationCode { + get { + return String.Empty; + } + } + } +} + diff --git a/AccessControls/GroupLocksCmd.cs b/AccessControls/GroupLocksCmd.cs new file mode 100644 index 0000000..cea8276 --- /dev/null +++ b/AccessControls/GroupLocksCmd.cs @@ -0,0 +1,25 @@ +using System; + +using Vintagestory.API.Common; +using Vintagestory.API.MathTools; +using Vintagestory.API.Server; +using Vintagestory.API.Util; +using Vintagestory.GameContent; + +namespace FirstMachineAge +{ + public class GroupLocksCmd: ServerChatCommand + { + public GroupLocksCmd( ) + //What about Clan/Faction leaders - for shared combos? + { + this.Command = "grouplocks"; + this.Description = "Change lock permissions and assigend groupIDs."; + //this.handler += LocksmithParser; + this.Syntax = "grant [group/player] [player-name/group-name] / revoke [group/player] [player-name/group-name]"; + //this.RequiredPrivilege = "locksmith"; + + } + } +} + diff --git a/AccessControls/LocksmithCmd.cs b/AccessControls/LocksmithCmd.cs new file mode 100644 index 0000000..436d03b --- /dev/null +++ b/AccessControls/LocksmithCmd.cs @@ -0,0 +1,32 @@ +using System; + +using Vintagestory.API.Common; +using Vintagestory.API.MathTools; +using Vintagestory.API.Server; +using Vintagestory.API.Util; +using Vintagestory.GameContent; + +namespace FirstMachineAge +{ + public class LocksmithCmd : ServerChatCommand + { + private ICoreServerAPI ServerAPI; + + public LocksmithCmd( ) + { + this.Command = "locksmith"; + this.Description = "ALTER LOCKS: Remove or Change keys and combos."; + this.handler += LocksmithParser; + this.Syntax = "remove / change / downgrade / info "; + this.RequiredPrivilege = "locksmith"; + + + + } + + private void LocksmithParser(IServerPlayer player, int groupId, CmdArgs args) + { + throw new NotImplementedException( ); + } +} +} diff --git a/AccessControls/Properties/AssemblyInfo.cs b/AccessControls/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a674124 --- /dev/null +++ b/AccessControls/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("AccessControls")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Melchior")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/AccessControls/assets/fma/itemtypes/locks/combolock.json b/AccessControls/assets/fma/itemtypes/locks/combolock.json new file mode 100644 index 0000000..99de9f3 --- /dev/null +++ b/AccessControls/assets/fma/itemtypes/locks/combolock.json @@ -0,0 +1,37 @@ +{ + code: "combolock", + class: "ItemPadlock", + maxstacksize: 64, + variantgroups: [ + { code: "material", states: ["tinbronze", "blackbronze", "bismuthbronze", "iron", "steel" ] }, + ], + textures: { + "material": { base: "item/tool/material/{material}" } + }, + shape: { base: "item/tool/padlock" }, + creativeinventory: { "general": ["*"], "items": ["*"] }, + guiTransform: { + translation: { x: 4, y: 0, z: 0 }, + rotation: { x: 20, y: 16, z: -152 }, + origin: { x: 0.5, y: 0.12, z: 0.5 }, + scale: 6 + }, + groundTransform: { + translation: { x: 0, y: 0, z: 0 }, + rotation: { x: -90, y: 0, z: 0 }, + origin: { x: 0.5, y: 0.2, z: 0.46 }, + scale: 3.5 + }, + fpHandTransform: { + translation: { x: 0, y: 0, z: 0 }, + rotation: { x: -14, y: -78, z: 17 }, + origin: { x: 0.5, y: 0.1, z: 0.5 }, + scale: 2.41 + }, + tpHandTransform: { + translation: { x: -0.83, y: -0.36, z: -0.7 }, + rotation: { x: 1, y: 57, z: 18 }, + origin: { x: 0.5, y: 0.15, z: 0.5 }, + scale: 0.65 + } +} \ No newline at end of file diff --git a/AccessControls/behaviors/BlockBehaviorComplexLockable.cs b/AccessControls/behaviors/BlockBehaviorComplexLockable.cs new file mode 100644 index 0000000..a4e39ef --- /dev/null +++ b/AccessControls/behaviors/BlockBehaviorComplexLockable.cs @@ -0,0 +1,73 @@ +using System; + +using Vintagestory.API.Client; +using Vintagestory.API.Common; +using Vintagestory.API.Config; +using Vintagestory.GameContent; + +namespace FirstMachineAge +{ + + /// + /// Multi-use Lockable behavior for combo/key/other locks + /// + /// Replaces the old behavior... + public class BlockBehaviorComplexLockable : BlockBehaviorLockable + { + private AccessControlsMod acm; + + public BlockBehaviorComplexLockable(Block block) : base(block) + { + + } + + + public override bool OnBlockInteractStart(IWorldAccessor world, IPlayer byPlayer, BlockSelection blockSel, ref EnumHandling handling) + { + LockStatus lockState = acm.LockState(blockSel.Position, byPlayer); + + if (acm.LockedForPlayer(blockSel.Position, byPlayer)) //Checks for keys and known combos, ect... + { + if (world.Side == EnumAppSide.Client) + { + ICoreClientAPI clientAPI = (world.Api as ICoreClientAPI); + + switch (lockState) + { + case LockStatus.ComboUnknown: + //Does Not already know combo... + ShowComboLockGUI(world, byPlayer,blockSel); + + break; + + case LockStatus.KeyHave: + clientAPI.TriggerChatMessage("opened with a key..."); + handling = EnumHandling.PassThrough; + return true; + + case LockStatus.KeyNope: + //Did not have key... + clientAPI.TriggerIngameError(this, "locked", Lang.Get("ingameerror-nokey", new object[0])); + break; + + default: + //Normal or 'default' lock: + clientAPI.TriggerIngameError(this, "locked", Lang.Get("ingameerror-locked", new object[0])); + break; + } + } + + handling = EnumHandling.PreventSubsequent; + return false; + } + + + return base.OnBlockInteractStart(world, byPlayer, blockSel, ref handling); + } + + protected void ShowComboLockGUI(IWorldAccessor world, IPlayer byPlayer, BlockSelection blockSel) + { + throw new NotImplementedException( ); + } +} +} \ No newline at end of file diff --git a/AccessControls/data/ACLPersisted.cs b/AccessControls/data/ACLPersisted.cs new file mode 100644 index 0000000..c56b48b --- /dev/null +++ b/AccessControls/data/ACLPersisted.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + + +using ProtoBuf; + +using Vintagestory.API.MathTools; + +namespace FirstMachineAge +{ + [ProtoContract] + public class ACLPersisted + { + [ProtoMember(0)] + public int KeyId_Sequence; + + //Stats, other info? + + } +} + diff --git a/AccessControls/data/AccessControlNode.cs b/AccessControls/data/AccessControlNode.cs new file mode 100644 index 0000000..5b83a2b --- /dev/null +++ b/AccessControls/data/AccessControlNode.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; + + +using ProtoBuf; + +using Vintagestory.API.MathTools; + +namespace FirstMachineAge +{ + + + /// + /// Holds individual Access control entries for that Chunk + /// + /// + /// (by block Position) + /// + [ProtoContract] + public class ChunkACNodes + { + public ChunkACNodes( ) + { + Entries = new SortedDictionary( );//SET Comparer? + } + + [ProtoMember(0)] + public Vec3i OriginChunk; + + [ProtoMember(1)] + public SortedDictionary Entries;//CHECK: does it *NEED* to be sorted? + + //Last update DateTime? + } + + + [ProtoContract] + public class AccessControlNode + { + public AccessControlNode( ) + { + LockStyle = LockKinds.None; + } + + [ProtoMember(0)] + public string OwnerPlayerUID; + + [ProtoMember(1, IsRequired = true)] + public LockKinds LockStyle; + + [ProtoMember(2)] + public string SourceItemName; + + [ProtoMember(3)] + public byte[] CombinationCode;//Nullable + + [ProtoMember(4)] + public int? KeyID; + + [ProtoMember(5)] + public List PermittedPlayers;//Also nullable - key locks should NEVER have entries here! + + [ProtoMember(6)] + public bool LockDefeated;//Ya Picked it; Taffer! + + //public BlockPos Origin ; //Placement of lock in world (on block) + + } + + + [ProtoContract] + public class AccessEntry + { + [ProtoMember(0)] + public string PlayerUID; + + //Access type; Player or Group ? + + [ProtoMember(1)] + public int? GroupID; + + + } + + + /// + /// A Chunk's, Lock status list. + /// + /// + /// Used client-side for fast lookup + /// + [ProtoContract] + public class LockStatusList + { + [ProtoMember(0)] + public Dictionary LockStatesByBlockPos; + + [ProtoMember(1)] + public Vec3i ChunkOrigin; + + + + //Last RX time for Cache-TTL + } + +} + diff --git a/AccessControls/data/LockKinds.cs b/AccessControls/data/LockKinds.cs new file mode 100644 index 0000000..1716884 --- /dev/null +++ b/AccessControls/data/LockKinds.cs @@ -0,0 +1,18 @@ +using System; + +using System.ComponentModel; + +namespace FirstMachineAge +{ + [DefaultValue(LockKinds.None)] + public enum LockKinds : byte + { + None, //No lock here! + Classic,//Magic locks which open for their 'owner' + Combination,//Mechanical locks that need manual input entry of a number or sequence to operate + Key,//Mech. locks which need a specific item in inventory to open + //Group,//ACL controlled lock, for Factions? + + } +} + diff --git a/AccessControls/data/LockStatus.cs b/AccessControls/data/LockStatus.cs new file mode 100644 index 0000000..5ab7154 --- /dev/null +++ b/AccessControls/data/LockStatus.cs @@ -0,0 +1,19 @@ +using System; + +using System.ComponentModel; + +namespace FirstMachineAge +{ + [DefaultValue(LockStatus.None)] + public enum LockStatus : byte + { + None,//Do nothing = 'no lock here'. + Locked,//Old behavior + ComboUnknown,//GUI + ComboKnown,//GUI prefilled? + KeyHave,//Message? + KeyNope,//Message! + Unknown, //for cache non-update state? - e.g. LAG while updating. + } +} + diff --git a/AccessControls/items/GenericKey.cs b/AccessControls/items/GenericKey.cs new file mode 100644 index 0000000..0f95873 --- /dev/null +++ b/AccessControls/items/GenericKey.cs @@ -0,0 +1,31 @@ +using System; + +using Vintagestory.API.Common; + +namespace FirstMachineAge +{ + public abstract class GenericKey : Item + { + private const string _keyIdKey = @"key_id"; + + public uint KeyID { + get { + if (this.Attributes.Exists && this.Attributes.KeyExists(_keyIdKey)) { + uint keyId = ( uint )this.Attributes[_keyIdKey].AsInt(0); + + return keyId; + } + + return 0; + } + } + + //Attributes to -> AccessControlNode + //Copy keyID, owner? + + //itemstack.Collectible.Attributes[_keyIdKey].AsInt(null); + + + } +} + diff --git a/AccessControls/items/GenericLock.cs b/AccessControls/items/GenericLock.cs new file mode 100644 index 0000000..873dc86 --- /dev/null +++ b/AccessControls/items/GenericLock.cs @@ -0,0 +1,148 @@ +using System; + +using Vintagestory.API.Client; +using Vintagestory.API.Common; +using Vintagestory.API.Datastructures; +using Vintagestory.API.Server; + +namespace FirstMachineAge +{ + public abstract class GenericLock : Item + { + private const string _lockStyleKey = @"lock-style"; + private const string _comboKey = @"combo"; + private const string _lockTier = @"lock-tier"; + private const uint MinimumComboDigits = 2; + + protected ICoreServerAPI ServerAPI { get; set; } + protected ILogger Logger { get; set; } + protected ICoreClientAPI ClientAPI { get; set; } + protected AccessControlsMod AccessControlsMod { get; set; } + + public LockKinds LockStyle { get; protected set;} + + public uint LockTier { get; protected set; } + + public byte[] CombinationCode { get; protected set; } + + public uint? KeyID { get; protected set; } + + + public override void OnLoaded(ICoreAPI api) + { + base.OnLoaded(api); + + if (api.Side.IsServer( )) { + this.ServerAPI = ( ICoreServerAPI )api; + this.Logger = this.ServerAPI.World.Logger; + AccessControlsMod = ServerAPI.World.Api.ModLoader.GetModSystem( ); + } + + if (api.Side.IsClient( )) { + this.ClientAPI = ( ICoreClientAPI )api; + this.Logger = this.ClientAPI.World.Logger; + AccessControlsMod = ClientAPI.World.Api.ModLoader.GetModSystem( ); + } + + Logger.VerboseDebug("{0} ~ OnLoaded", base.Code.ToString()); + + if (this.Attributes.Exists && this.Attributes.KeyExists(_lockStyleKey)) { + this.LockStyle = this.Attributes[_lockStyleKey].AsObject(LockKinds.None); + } + + if (LockStyle != LockKinds.None && this.Attributes.KeyExists(_lockTier)) + { + this.LockTier = ( uint )this.Attributes[_lockTier].AsInt(0); + } + } + + //or? OnCreatedByCrafting -- generate keyID and/or combo? + public override void OnModifiedInInventorySlot(IWorldAccessor world, ItemSlot slot, ItemStack extractedStack = null) + { + //Set keyid,combo if unset... + if (this.LockStyle == LockKinds.Combination) { + + if (this.CombinationCode == null) + { + GenerateCombination(slot, this); + } + } else if (this.LockStyle == LockKinds.Key) { + if (this.KeyID.HasValue == false) { + GenerateKeyId(slot, this); + } + } + + } + + public override void GetHeldItemInfo(ItemSlot inSlot, System.Text.StringBuilder dsc, IWorldAccessor world, bool withDebugInfo) + { + base.GetHeldItemInfo(inSlot, dsc, world, withDebugInfo); + + if (LockStyle == LockKinds.Combination) { + dsc.AppendFormat("\nCombination#:"); + + if (this.CombinationCode != null) { + foreach (var digit in this.CombinationCode) { + dsc.AppendFormat(" {0:D}\t", digit); + } + } else { + dsc.AppendFormat("\nCombination ????"); + } + } + + if (LockStyle == LockKinds.Key) { + + if (this.KeyID.HasValue) dsc.AppendFormat("\nKeyID#: {0}", this.KeyID); + } + + if (LockTier > 0) { + dsc.AppendFormat("\nTier#: {0}", this.LockTier); + } + } + + + /// + /// Stores AccessControlNode in Tree-Attributes. + /// + /// AccessControlNode -> (which are strangely part of 'ItemStack'...) + /// Control node settings. + protected TreeAttribute TreeFromAttributes(AccessControlNode acn) + { + //Copy Combo number, keyID, type, ect... + switch (acn.LockStyle) + { + case LockKinds.Classic: + //OwnerId? + + break; + + + + } + + //itemstack.Collectible.Attributes["clothescategory"].AsString(null); + return null; + } + + + protected void GenerateCombination(ItemSlot slot, GenericLock genericLock) + { + Random randNum = new Random( ); + byte[] comboArray = new byte[this.LockTier + MinimumComboDigits]; + + for (int index = 0; index < this.LockTier + MinimumComboDigits; index++) + { + comboArray[index] = ( byte )randNum.Next(0, 9); //Extra high tiers - non-base10 ? + } + + slot.Itemstack.Attributes.SetBytes(_comboKey, comboArray); + } + + protected void GenerateKeyId(ItemSlot slot, GenericLock genericLock) + { + slot.Itemstack.Attributes.SetInt(AccessControlsMod._KeyIDKey, this.AccessControlsMod.NextKeyID); + } + + } +} + diff --git a/AccessControls/items/ItemCombolock.cs b/AccessControls/items/ItemCombolock.cs new file mode 100644 index 0000000..8612cf2 --- /dev/null +++ b/AccessControls/items/ItemCombolock.cs @@ -0,0 +1,48 @@ +using System; +using Vintagestory.API.Client; +using Vintagestory.API.Common; +using Vintagestory.API.Config; +using Vintagestory.GameContent; + +namespace FirstMachineAge +{ + public class ItemCombolock : GenericLock + { + + + + + public override void OnHeldInteractStart(ItemSlot slot, EntityAgent byEntity, BlockSelection blockSel, EntitySelection entitySel, bool firstEvent, ref EnumHandHandling handling) + { + if (byEntity.World.Side == EnumAppSide.Client) + { + ClientAPI = (byEntity.World.Api as ICoreClientAPI); + } + + if (blockSel != null && byEntity.World.BlockAccessor.GetBlock(blockSel.Position).HasBehavior( )) + { + IPlayer player = (byEntity as EntityPlayer).Player; + + + + + if (AccessControlsMod.LockedForPlayer(blockSel.Position, player, this.Code) == false)//already has a lock... + { + ClientAPI?.TriggerIngameError(this, "cannotlock", Lang.Get("ingameerror-cannotlock")); + } else { + ClientAPI?.ShowChatMessage(Lang.Get("lockapplied")); + slot.TakeOut(1); + slot.MarkDirty( ); + + AccessControlsMod.ApplyLock(blockSel, player, slot); + } + + handling = EnumHandHandling.PreventDefault; + return; + } + + base.OnHeldInteractStart(slot, byEntity, blockSel, entitySel, firstEvent, ref handling); + } + } +} + diff --git a/FirstMachineAge_Common/Common.csproj b/FirstMachineAge_Common/Common.csproj new file mode 100644 index 0000000..36d9888 --- /dev/null +++ b/FirstMachineAge_Common/Common.csproj @@ -0,0 +1,52 @@ + + + + Debug + AnyCPU + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC} + Library + FirstMachineAge + FirstMachineAge_Common + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + vs_libs\VintagestoryAPI.dll + + + vs_libs\VSCreativeMod.dll + + + vs_libs\VSEssentials.dll + + + vs_libs\VSSurvivalMod.dll + + + + + + + + + + + \ No newline at end of file diff --git a/FirstMachineAge_Common/Helpers.cs b/FirstMachineAge_Common/Helpers.cs new file mode 100644 index 0000000..c221900 --- /dev/null +++ b/FirstMachineAge_Common/Helpers.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Vintagestory.API.Client; +using Vintagestory.API.Common; +using Vintagestory.API.MathTools; + +namespace FirstMachineAge +{ + public static class Helpers + { + + + + public static string PrettyCoords(this BlockPos location, ICoreClientAPI ClientApi) + { + var start = ClientApi.World.DefaultSpawnPosition.AsBlockPos; + + return string.Format("X{0}, Y{1}, Z{2}", location.X - start.X, location.Y, location.Z - start.Z); + } + + public static BlockPos AverageHighestPos(List positions) + { + int x = 0, y = 0, z = 0, length = positions.Count; + foreach (BlockPos pos in positions) { + x += pos.X; + y = Math.Max(y, pos.Y);//Mutant Y-axis, take "HIGHEST" + z += pos.Z; + } + return new BlockPos(x / length, y, z / length); + } + + public static BlockPos PickRepresentativePosition(List positions) + { + var averagePos = AverageHighestPos(positions); + if (positions.Any(pos => pos.X == averagePos.X && pos.Y == averagePos.Y && pos.Z == averagePos.Z)) { + return averagePos;//lucky ~ center was it! + } + + //Otherwise...pick one + var whichever = positions.Last(poz => poz.Y == averagePos.Y); + + return whichever; + } + + + + /// + /// Find a BLOCK partial path match: BlockID + /// + /// Matching finds + /// Asset name. + public static Dictionary ArbitrarytBlockIdHunter(this ICoreAPI CoreApi, AssetLocation assetName, EnumBlockMaterial? material = null) + { + Dictionary arbBlockIDTable = new Dictionary( ); + uint emptyCount = 0; + + if (CoreApi.World.Blocks != null) { + +#if DEBUG + CoreApi.World.Logger.VerboseDebug(" World Blocks [Count: {0}]", CoreApi.World.Blocks.Count); +#endif + //If Brute force won't work; use GROOT FORCE! + //var theBlock = ClientApi.World.BlockAccessor.GetBlock(0); + + if (!material.HasValue) { + foreach (Block blk in CoreApi.World.Blocks) { + if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) { + emptyCount++; + } else if (blk.Code != null && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) { +#if DEBUG + //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); +#endif + + arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); + } + } + } else { + foreach (Block blk in CoreApi.World.Blocks) { + if (blk.IsMissing || blk.Id == 0 || blk.BlockId == 0) { + emptyCount++; + } else if (blk.Code != null && material.Value == blk.BlockMaterial && blk.Code.BeginsWith(assetName.Domain, assetName.Path)) { +#if DEBUG + //CoreApi.World.Logger.VerboseDebug("Block: [{0} ({1})] = #{2}", blk.Code.Path, blk.BlockMaterial, blk.BlockId); +#endif + + arbBlockIDTable.Add(blk.BlockId, blk.Code.Path); + } + } + } + +#if DEBUG + CoreApi.World.Logger.VerboseDebug("Block gaps: {0}", emptyCount); +#endif + } + + return arbBlockIDTable; + } + + public static long ToChunkIndex3D(this IBlockAccessor blocks, BlockPos blockPos) + { + return ToChunkIndex3D(blocks, blockPos.X / blocks.ChunkSize, blockPos.Y / blocks.ChunkSize, blockPos.Z / blocks.ChunkSize); + } + + + + public static long ToChunkIndex3D(this IBlockAccessor blocks, int chunkX, int chunkY, int chunkZ) + { + int ChunkMapSizeX = blocks.MapSizeX / blocks.ChunkSize; + int ChunkMapSizeZ = blocks.MapSizeZ / blocks.ChunkSize; + + return (( long )chunkY * ChunkMapSizeZ + chunkZ) * ChunkMapSizeX + chunkX; + } + + + + + + + + /// + /// Chunk local index. Not block position! + /// + /// Clamps to 5 bit ranges automagically + public static int ChunkBlockIndicie16(int X_index, int Y_index, int Z_index) + { + return ((Y_index & 31) * 32 + (Z_index & 31)) * 32 + (X_index & 31); + } + + /// + /// Chunk index converted from block position (in world) + /// + /// The block indicie. + /// Block position. + /// Clamps to 5 bit ranges automagically + public static int ChunkBlockIndicie16(BlockPos blockPos) + { + //Chunk masked + return ((blockPos.Y & 31) * 32 + (blockPos.Z & 31)) * 32 + (blockPos.X & 31); + } + } +} + diff --git a/FirstMachineAge_Common/Properties/AssemblyInfo.cs b/FirstMachineAge_Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c66ed33 --- /dev/null +++ b/FirstMachineAge_Common/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("FirstMachineAge_Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Melchior")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/First_Machine_Age.sln b/First_Machine_Age.sln new file mode 100644 index 0000000..4fcd7fa --- /dev/null +++ b/First_Machine_Age.sln @@ -0,0 +1,168 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "FirstMachineAge_Common\Common.csproj", "{DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessControls", "AccessControls\AccessControls.csproj", "{180853A2-7E1D-4876-9D1E-AA8608D701C3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}.Release|Any CPU.Build.0 = Release|Any CPU + {180853A2-7E1D-4876-9D1E-AA8608D701C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {180853A2-7E1D-4876-9D1E-AA8608D701C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {180853A2-7E1D-4876-9D1E-AA8608D701C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {180853A2-7E1D-4876-9D1E-AA8608D701C3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.DotNetNamingPolicy = $1 + $1.DirectoryNamespaceAssociation = None + $1.ResourceNamePolicy = FileFormatDefault + $0.TextStylePolicy = $2 + $2.FileWidth = 120 + $2.TabsToSpaces = False + $2.inheritsSet = VisualStudio + $2.inheritsScope = text/plain + $2.scope = text/plain + $0.StandardHeader = $3 + $3.Text = + $3.IncludeInNewFiles = True + $0.NameConventionPolicy = $4 + $4.Rules = $5 + $5.NamingRule = $6 + $6.Name = Type Parameters + $6.AffectedEntity = TypeParameter + $6.VisibilityMask = VisibilityMask + $6.NamingStyle = PascalCase + $6.IncludeInstanceMembers = True + $6.IncludeStaticEntities = True + $6.RequiredPrefixes = $28 + $28.String = T + $6.RequiredSuffixes = $29 + $29.String = Exception + $5.NamingRule = $7 + $7.Name = Types + $7.AffectedEntity = Class, Struct, Enum, Delegate + $7.VisibilityMask = Public + $7.NamingStyle = PascalCase + $7.IncludeInstanceMembers = True + $7.IncludeStaticEntities = True + $5.NamingRule = $8 + $8.Name = Interfaces + $8.RequiredPrefixes = $9 + $9.String = I + $8.AffectedEntity = Interface + $8.VisibilityMask = Public + $8.NamingStyle = PascalCase + $8.IncludeInstanceMembers = True + $8.IncludeStaticEntities = True + $5.NamingRule = $10 + $10.Name = Attributes + $10.RequiredSuffixes = $11 + $11.String = Attribute + $10.AffectedEntity = CustomAttributes + $10.VisibilityMask = Public + $10.NamingStyle = PascalCase + $10.IncludeInstanceMembers = True + $10.IncludeStaticEntities = True + $5.NamingRule = $12 + $12.Name = Event Arguments + $12.RequiredSuffixes = $13 + $13.String = EventArgs + $12.AffectedEntity = CustomEventArgs + $12.VisibilityMask = Public + $12.NamingStyle = PascalCase + $12.IncludeInstanceMembers = True + $12.IncludeStaticEntities = True + $5.NamingRule = $14 + $14.Name = Exceptions + $14.RequiredSuffixes = $15 + $15.String = Exception + $14.AffectedEntity = CustomExceptions + $14.VisibilityMask = VisibilityMask + $14.NamingStyle = PascalCase + $14.IncludeInstanceMembers = True + $14.IncludeStaticEntities = True + $5.NamingRule = $16 + $16.Name = Methods + $16.AffectedEntity = Methods + $16.VisibilityMask = Protected, Public + $16.NamingStyle = PascalCase + $16.IncludeInstanceMembers = True + $16.IncludeStaticEntities = True + $5.NamingRule = $17 + $17.Name = Static Readonly Fields + $17.AffectedEntity = ReadonlyField + $17.VisibilityMask = Protected, Public + $17.NamingStyle = PascalCase + $17.IncludeInstanceMembers = False + $17.IncludeStaticEntities = True + $5.NamingRule = $18 + $18.Name = Fields + $18.AffectedEntity = Field + $18.VisibilityMask = Protected, Public + $18.NamingStyle = PascalCase + $18.IncludeInstanceMembers = True + $18.IncludeStaticEntities = True + $5.NamingRule = $19 + $19.Name = ReadOnly Fields + $19.AffectedEntity = ReadonlyField + $19.VisibilityMask = Protected, Public + $19.NamingStyle = PascalCase + $19.IncludeInstanceMembers = True + $19.IncludeStaticEntities = False + $5.NamingRule = $20 + $20.Name = Constant Fields + $20.AffectedEntity = ConstantField + $20.VisibilityMask = Protected, Public + $20.NamingStyle = PascalCase + $20.IncludeInstanceMembers = True + $20.IncludeStaticEntities = True + $5.NamingRule = $21 + $21.Name = Properties + $21.AffectedEntity = Property + $21.VisibilityMask = Protected, Public + $21.NamingStyle = PascalCase + $21.IncludeInstanceMembers = True + $21.IncludeStaticEntities = True + $5.NamingRule = $22 + $22.Name = Events + $22.AffectedEntity = Event + $22.VisibilityMask = Protected, Public + $22.NamingStyle = PascalCase + $22.IncludeInstanceMembers = True + $22.IncludeStaticEntities = True + $5.NamingRule = $23 + $23.Name = Enum Members + $23.AffectedEntity = EnumMember + $23.VisibilityMask = VisibilityMask + $23.NamingStyle = PascalCase + $23.IncludeInstanceMembers = True + $23.IncludeStaticEntities = True + $5.NamingRule = $24 + $24.Name = Parameters + $24.AffectedEntity = Parameter + $24.VisibilityMask = VisibilityMask + $24.NamingStyle = CamelCase + $24.IncludeInstanceMembers = True + $24.IncludeStaticEntities = True + $5.NamingRule = $25 + $25.Name = Type Parameters + $25.RequiredPrefixes = $26 + $26.String = T + $25.AffectedEntity = TypeParameter + $25.VisibilityMask = VisibilityMask + $25.NamingStyle = PascalCase + $25.IncludeInstanceMembers = True + $25.IncludeStaticEntities = True + $0.VersionControlPolicy = $27 + $27.inheritsSet = Mono + EndGlobalSection +EndGlobal