using System; using System.Collections; using System.Collections.Generic; using Gamecraft.Blocks.BlockGroups; using Svelto.ECS; using Unity.Mathematics; using UnityEngine; using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI { /// /// A group of blocks that can be selected together. The placed version of blueprints. Dispose after usage. /// public class BlockGroup : EcsObjectBase, ICollection, IDisposable { internal static BlueprintEngine _engine = new BlueprintEngine(); private readonly Block sourceBlock; private readonly List blocks; private float3 position, rotation; internal bool PosAndRotCalculated; internal BlockGroup(int id, Block block) : base(new EGID((uint)id, BlockGroupExclusiveGroups.BlockGroupEntityGroup)) { if (id == BlockGroupUtility.GROUP_UNASSIGNED) throw new BlockException("Cannot create a block group for blocks without a group!"); sourceBlock = block; blocks = new List(GetBlocks()); Block.Removed += OnBlockRemoved; } private void OnBlockRemoved(object sender, BlockPlacedRemovedEventArgs e) { //blocks.RemoveAll(block => block.Id == e.ID); - Allocation heavy int index = -1; for (int i = 0; i < blocks.Count; i++) { if (blocks[i].Id == e.ID) { index = i; break; } } if (index != -1) blocks.RemoveAt(index); } public void Dispose() { Block.Removed -= OnBlockRemoved; } /// /// The position of the block group (center). Can only be used after initialization is complete. /// public float3 Position { get { if (!PosAndRotCalculated) Refresh(); return position; } set { var diff = value - position; foreach (var block in blocks) block.Position += diff; if (!PosAndRotCalculated) //The condition can only be true if a block has been added/removed manually Refresh(); //So the blocks array is up to date else position += diff; } } /// /// The rotation of the block group. Can only be used after initialization is complete. /// public float3 Rotation { get { if (!PosAndRotCalculated) Refresh(); return rotation; } set { var diff = value - rotation; var qdiff = Quaternion.Euler(diff); foreach (var block in blocks) { block.Rotation += diff; block.Position = qdiff * block.Position; } if (!PosAndRotCalculated) Refresh(); else rotation += diff; } } /*/// /// Removes all of the blocks in this group from the world. /// public void RemoveBlocks() { _engine.RemoveBlockGroup(Id); - TODO: Causes a hard crash }*/ /// /// Creates a new block group consisting of a single block. /// You can add more blocks using the Add() method or by setting the BlockGroup property of the blocks.
/// Note that only newly placed blocks can be added to groups. ///
/// The block to add /// A new block group containing the given block public static BlockGroup Create(Block block) { var bg = new BlockGroup(_engine.CreateBlockGroup(block.Position, Quaternion.Euler(block.Rotation)), block); block.BlockGroup = bg; return bg; } /// /// Collects each block that is a part of this group. Also sets the position and rotation. /// /// An array of blocks private Block[] GetBlocks() { if (!sourceBlock.Exists) return new[] {sourceBlock}; //The block must exist to get the others var ret = _engine.GetBlocksFromGroup(sourceBlock.Id, out var pos, out var rot); position = pos; rotation = ((Quaternion) rot).eulerAngles; PosAndRotCalculated = true; return ret; } private void Refresh() { blocks.Clear(); blocks.AddRange(GetBlocks()); } internal static void Init() { GameEngineManager.AddGameEngine(_engine); } public IEnumerator GetEnumerator() => blocks.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator(); /// /// Adds a block to the group. You can only add newly placed blocks /// so that the game initializes the group membership properly. /// /// /// public void Add(Block item) { if (item == null) throw new NullReferenceException("Cannot add null to a block group"); item.BlockGroup = this; //Calls AddInternal } internal void AddInternal(Block item) { blocks.Add(item); _engine.AddBlockToGroup(item.Id, (int) Id.entityID); } /// /// Removes all blocks from this group. /// You cannot remove blocks that have been initialized, only those that you placed recently. /// public void Clear() { while (blocks.Count > 0) Remove(blocks[blocks.Count - 1]); } public bool Contains(Block item) => blocks.Contains(item); public void CopyTo(Block[] array, int arrayIndex) => blocks.CopyTo(array, arrayIndex); /// /// Removes a block from this group. /// You cannot remove blocks that have been initialized, only those that you placed recently. /// /// /// /// public bool Remove(Block item) { if (item == null) throw new NullReferenceException("Cannot remove null from a block group"); bool ret = item.BlockGroup == this; if (ret) item.BlockGroup = null; //Calls RemoveInternal return ret; } internal void RemoveInternal(Block item) => blocks.Remove(item); public int Count => blocks.Count; public bool IsReadOnly { get; } = false; public Block this[int index] => blocks[index]; //Setting is not supported, since the order doesn't matter public override string ToString() { return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}, {nameof(Count)}: {Count}"; } } }