Initial commit of Access Control mod

This commit is contained in:
melchior 2019-09-09 16:56:52 -04:00
commit 8edebea402
20 changed files with 1507 additions and 0 deletions

40
.gitignore vendored Normal file
View file

@ -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

View file

@ -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<long, ChunkACNodes> Server_ACN;//Track changes - and commit every ## minutes, in addition to server shutdown data-storage, chunk unloads
private Dictionary<BlockPos, LockStatus> 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<long, Dictionary<int, BlockReinforcement>> reinforcementsByChunk = new Dictionary<long, Dictionary<int, BlockReinforcement>>();
Dictionary<ChunkPos, Dictionary<BlockPos, BlockReinforcement>>
clientChannel = api.Network
.RegisterChannel("blockreinforcement")
.RegisterMessageType(typeof(ChunkReinforcementData))
.SetMessageHandler<ChunkReinforcementData>(onData)
data = chunk.GetModdata("reinforcements");
Dictionary<int, BlockReinforcement> reinforcmentsOfChunk = null;
reinforcmentsOfChunk = SerializerUtil.Deserialize<Dictionary<int, BlockReinforcement>>(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;
}
/// <summary>
/// Backwardsy compatible method - for lockable behaviors
/// </summary>
/// <returns>when locked.</returns>
/// <param name="position">Position.</param>
/// <param name="player">Player.</param>
/// <param name="code">Code.</param>
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( );
}
/// <summary>
/// Retrieves the ACN data
/// </summary>
/// <returns> Access Control node for a BlocKPos.</returns>
/// <param name="byBlockPos">By block position.</param>
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<ChunkACNodes>(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<ACLPersisted>(rawBytes);
}
else
{
ACLPersisted newPersistedState = new ACLPersisted( );
var aclPersistBytes = SerializerUtil.Serialize<ACLPersisted>(newPersistedState);
ServerAPI.WorldManager.SaveGame.StoreData(_persistedStateKey, aclPersistBytes);
this.PersistedState = newPersistedState;
}
}
}
}

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{180853A2-7E1D-4876-9D1E-AA8608D701C3}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>FirstMachineAge</RootNamespace>
<AssemblyName>AccessControls</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="VintagestoryAPI">
<HintPath>..\FirstMachineAge_Common\vs_libs\VintagestoryAPI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VSEssentials">
<HintPath>..\FirstMachineAge_Common\vs_libs\VSEssentials.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VSCreativeMod">
<HintPath>..\FirstMachineAge_Common\vs_libs\VSCreativeMod.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VSSurvivalMod">
<HintPath>..\FirstMachineAge_Common\vs_libs\VSSurvivalMod.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="protobuf-net">
<HintPath>..\FirstMachineAge_Common\vs_libs\protobuf-net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VintagestoryLib">
<HintPath>..\FirstMachineAge_Common\vs_libs\VintagestoryLib.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AccessControlMod.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="behaviors\BlockBehaviorComplexLockable.cs" />
<Compile Include="GUIs\GuiDialog_ComboLock.cs" />
<Compile Include="items\ItemCombolock.cs" />
<Compile Include="LocksmithCmd.cs" />
<Compile Include="GroupLocksCmd.cs" />
<Compile Include="items\GenericLock.cs" />
<Compile Include="items\GenericKey.cs" />
<Compile Include="data\AccessControlNode.cs" />
<Compile Include="data\LockStatus.cs" />
<Compile Include="data\LockKinds.cs" />
<Compile Include="data\ACLPersisted.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="assets\" />
<Folder Include="assets\fma\" />
<Folder Include="assets\fma\itemtypes\" />
<Folder Include="assets\fma\itemtypes\locks\" />
<Folder Include="items\" />
<Folder Include="behaviors\" />
<Folder Include="GUIs\" />
<Folder Include="assets\fma\shapes\" />
<Folder Include="assets\fma\shapes\access_controls\" />
<Folder Include="data\" />
</ItemGroup>
<ItemGroup>
<None Include="assets\fma\itemtypes\locks\combolock.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FirstMachineAge_Common\Common.csproj">
<Project>{DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}</Project>
<Name>Common</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

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

View file

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

View file

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

View file

@ -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("")]

View file

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

View file

@ -0,0 +1,73 @@
using System;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Config;
using Vintagestory.GameContent;
namespace FirstMachineAge
{
/// <summary>
/// Multi-use Lockable behavior for combo/key/other locks
/// </summary>
/// <remarks>Replaces the old behavior...</remarks>
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( );
}
}
}

View file

@ -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?
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using ProtoBuf;
using Vintagestory.API.MathTools;
namespace FirstMachineAge
{
/// <summary>
/// Holds individual Access control entries for that Chunk
/// </summary>
/// <remarks>
/// (by block Position)
/// </remarks>
[ProtoContract]
public class ChunkACNodes
{
public ChunkACNodes( )
{
Entries = new SortedDictionary<BlockPos, AccessControlNode>( );//SET Comparer?
}
[ProtoMember(0)]
public Vec3i OriginChunk;
[ProtoMember(1)]
public SortedDictionary<BlockPos, AccessControlNode> 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<AccessEntry> 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;
}
/// <summary>
/// A Chunk's, Lock status list.
/// </summary>
/// <remarks>
/// Used client-side for fast lookup
/// </remarks>
[ProtoContract]
public class LockStatusList
{
[ProtoMember(0)]
public Dictionary<BlockPos,LockStatus> LockStatesByBlockPos;
[ProtoMember(1)]
public Vec3i ChunkOrigin;
//Last RX time for Cache-TTL
}
}

View file

@ -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?
}
}

View file

@ -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.
}
}

View file

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

View file

@ -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<AccessControlsMod>( );
}
if (api.Side.IsClient( )) {
this.ClientAPI = ( ICoreClientAPI )api;
this.Logger = this.ClientAPI.World.Logger;
AccessControlsMod = ClientAPI.World.Api.ModLoader.GetModSystem<AccessControlsMod>( );
}
Logger.VerboseDebug("{0} ~ OnLoaded", base.Code.ToString());
if (this.Attributes.Exists && this.Attributes.KeyExists(_lockStyleKey)) {
this.LockStyle = this.Attributes[_lockStyleKey].AsObject<LockKinds>(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);
}
}
/// <summary>
/// Stores AccessControlNode in Tree-Attributes.
/// </summary>
/// <remarks>AccessControlNode -> (which are strangely part of 'ItemStack'...)</remarks>
/// <param name="acn">Control node settings.</param>
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);
}
}
}

View file

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

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DE0A4E7D-E5FA-441D-A11A-8279E6AC5BBC}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>FirstMachineAge</RootNamespace>
<AssemblyName>FirstMachineAge_Common</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="VintagestoryAPI">
<HintPath>vs_libs\VintagestoryAPI.dll</HintPath>
</Reference>
<Reference Include="VSCreativeMod">
<HintPath>vs_libs\VSCreativeMod.dll</HintPath>
</Reference>
<Reference Include="VSEssentials">
<HintPath>vs_libs\VSEssentials.dll</HintPath>
</Reference>
<Reference Include="VSSurvivalMod">
<HintPath>vs_libs\VSSurvivalMod.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="vs_libs\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -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<BlockPos> 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<BlockPos> 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;
}
/// <summary>
/// Find a BLOCK partial path match: BlockID
/// </summary>
/// <returns>Matching finds</returns>
/// <param name="assetName">Asset name.</param>
public static Dictionary<int, string> ArbitrarytBlockIdHunter(this ICoreAPI CoreApi, AssetLocation assetName, EnumBlockMaterial? material = null)
{
Dictionary<int, string> arbBlockIDTable = new Dictionary<int, string>( );
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;
}
/// <summary>
/// Chunk local index. Not block position!
/// </summary>
/// <remarks>Clamps to 5 bit ranges automagically</remarks>
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);
}
/// <summary>
/// Chunk index converted from block position (in world)
/// </summary>
/// <returns>The block indicie.</returns>
/// <param name="blockPos">Block position.</param>
/// <remarks>Clamps to 5 bit ranges automagically</remarks>
public static int ChunkBlockIndicie16(BlockPos blockPos)
{
//Chunk masked
return ((blockPos.Y & 31) * 32 + (blockPos.Z & 31)) * 32 + (blockPos.X & 31);
}
}
}

View file

@ -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("")]

168
First_Machine_Age.sln Normal file
View file

@ -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