diff --git a/TechbloxModdingAPI/Block.cs b/TechbloxModdingAPI/Block.cs index e64fab7..44bbc92 100644 --- a/TechbloxModdingAPI/Block.cs +++ b/TechbloxModdingAPI/Block.cs @@ -45,10 +45,12 @@ namespace TechbloxModdingAPI /// The block's position - default block size is 0.2 /// Whether the block should be auto-wired (if functional) /// The player who placed the block + /// /// The placed block or null if failed - public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null) + public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null, + bool force = false) { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) + if (PlacementEngine.IsInGame && (GameState.IsBuildMode() || force)) { var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); var egid = initializer.EGID; @@ -140,9 +142,10 @@ namespace TechbloxModdingAPI /// The block's position (a block is 0.2 wide in terms of position) /// Whether the block should be auto-wired (if functional) /// The player who placed the block - public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null) + /// Place even if not in build mode + public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null, bool force = false) { - if (!PlacementEngine.IsInGame || !GameState.IsBuildMode()) + if (!PlacementEngine.IsInGame || !GameState.IsBuildMode() && !force) throw new BlockException("Blocks can only be placed in build mode."); var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire); Id = initializer.EGID; diff --git a/TechbloxModdingAPI/EcsObjectBase.cs b/TechbloxModdingAPI/EcsObjectBase.cs index c96cf25..6a4db65 100644 --- a/TechbloxModdingAPI/EcsObjectBase.cs +++ b/TechbloxModdingAPI/EcsObjectBase.cs @@ -1,18 +1,45 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using Svelto.DataStructures; using Svelto.ECS; using Svelto.ECS.Internal; -using TechbloxModdingAPI.Blocks; +using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI { public abstract class EcsObjectBase { public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor - + + private static readonly Dictionary> _instances = + new Dictionary>(); + + private static readonly WeakDictionary _noInstance = + new WeakDictionary(); + + internal static WeakDictionary GetInstances(Type type) + { + return _instances.TryGetValue(type, out var dict) ? dict : null; + } + + protected EcsObjectBase() + { + if (!_instances.TryGetValue(GetType(), out var dict)) + { + dict = new WeakDictionary(); + _instances.Add(GetType(), dict); + } + + // ReSharper disable once VirtualMemberCallInConstructor + // The ID should not depend on the constructor + dict.Add(Id, this); + } + + #region ECS initializer stuff + protected internal EcsInitData InitData; - + /// /// Holds information needed to construct a component initializer /// @@ -22,7 +49,7 @@ namespace TechbloxModdingAPI private EntityReference reference; public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData - {group = GetInitGroup(initializer), reference = initializer.reference}; + { group = GetInitGroup(initializer), reference = initializer.reference }; public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference); public bool Valid => group != null; @@ -56,8 +83,10 @@ namespace TechbloxModdingAPI returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType); var lambda = - Expression.Lambda(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam}); + Expression.Lambda(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam }); return lambda.Compile(); } + + #endregion } } \ No newline at end of file diff --git a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs index 32ef656..dd1d2b3 100644 --- a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs +++ b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs @@ -91,7 +91,7 @@ namespace TechbloxModdingAPI.Tests .Description("Place a block of aluminium at the given coordinates") .Action((float x, float y, float z) => { - var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z)); + var block = Block.PlaceNew(BlockIDs.Cube, new float3(x, y, z), force: true); Logging.CommandLog("Block placed with type: " + block.Type); }) .Build(); diff --git a/TechbloxModdingAPI/Utility/WeakDictionary.cs b/TechbloxModdingAPI/Utility/WeakDictionary.cs new file mode 100644 index 0000000..287b044 --- /dev/null +++ b/TechbloxModdingAPI/Utility/WeakDictionary.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace TechbloxModdingAPI.Utility +{ + public class WeakDictionary : IDictionary where TValue : class + { + private Dictionary> _dictionary = new Dictionary>(); + + public IEnumerator> GetEnumerator() + { + using var enumerator = _dictionary.GetEnumerator(); + while (enumerator.MoveNext()) + { + if (enumerator.Current.Value.TryGetTarget(out var value)) + yield return new KeyValuePair(enumerator.Current.Key, value); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + _dictionary.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return TryGetValue(item.Key, out var value) && item.Value == value; + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new System.NotImplementedException(); + } + + public bool Remove(KeyValuePair item) + { + return Contains(item) && Remove(item.Key); + } + + public int Count => _dictionary.Count; + public bool IsReadOnly => false; + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + _dictionary.Add(key, new WeakReference(value)); + } + + public bool Remove(TKey key) + { + return _dictionary.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + value = null; + return _dictionary.TryGetValue(key, out var reference) && reference.TryGetTarget(out value); + } + + public TValue this[TKey key] + { + get => TryGetValue(key, out var value) + ? value + : throw new KeyNotFoundException($"Key {key} not found in WeakDictionary."); + set => _dictionary[key] = new WeakReference(value); + } + + public ICollection Keys => _dictionary.Keys; + public ICollection Values => new ValueCollection(this); + + public class ValueCollection : ICollection, IReadOnlyCollection + { + private WeakDictionary _dictionary; + internal ValueCollection(WeakDictionary dictionary) + { + _dictionary = dictionary; + } + + public IEnumerator GetEnumerator() + { + using var enumerator = _dictionary.GetEnumerator(); + while (enumerator.MoveNext()) + { + yield return enumerator.Current.Value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(TValue item) + { + throw new NotSupportedException("The value collection is read only."); + } + + public void Clear() + { + throw new NotSupportedException("The value collection is read only."); + } + + public bool Contains(TValue item) + { + return _dictionary.Any(kv => kv.Value == item); + } + + public void CopyTo(TValue[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(TValue item) + { + throw new NotSupportedException("The value collection is read only."); + } + + public int Count => _dictionary.Count; + public bool IsReadOnly => true; + } + } +} \ No newline at end of file