|
- using System;
- using System.Collections;
- using System.Collections.Generic;
-
- using Gamecraft.Blocks.BlockGroups;
- using Unity.Mathematics;
- using UnityEngine;
-
- using GamecraftModdingAPI.Blocks;
- using GamecraftModdingAPI.Utility;
-
- namespace GamecraftModdingAPI
- {
- /// <summary>
- /// A group of blocks that can be selected together. The placed version of blueprints. Dispose after usage.
- /// </summary>
- public class BlockGroup : ICollection<Block>, IDisposable
- {
- internal static BlueprintEngine _engine = new BlueprintEngine();
- public int Id { get; }
- private readonly Block sourceBlock;
- private readonly List<Block> blocks;
- private float3 position, rotation;
- internal bool PosAndRotCalculated;
-
- internal BlockGroup(int id, Block block)
- {
- if (id == BlockGroupUtility.GROUP_UNASSIGNED)
- throw new BlockException("Cannot create a block group for blocks without a group!");
- Id = id;
- sourceBlock = block;
- blocks = new List<Block>(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;
- }
-
- /// <summary>
- /// The position of the block group (center). Can only be used after initialization is complete.
- /// </summary>
- 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;
- }
- }
-
- /// <summary>
- /// The rotation of the block group. Can only be used after initialization is complete.
- /// </summary>
- 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;
- }
- }
-
- /*/// <summary>
- /// Removes all of the blocks in this group from the world.
- /// </summary>
- public void RemoveBlocks()
- {
- _engine.RemoveBlockGroup(Id); - TODO: Causes a hard crash
- }*/
-
- /// <summary>
- /// 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.<br />
- /// Note that only newly placed blocks can be added to groups.
- /// </summary>
- /// <param name="block">The block to add</param>
- /// <returns>A new block group containing the given block</returns>
- public static BlockGroup Create(Block block)
- {
- var bg = new BlockGroup(_engine.CreateBlockGroup(block.Position, Quaternion.Euler(block.Rotation)), block);
- block.BlockGroup = bg;
- return bg;
- }
-
- /// <summary>
- /// Collects each block that is a part of this group. Also sets the position and rotation.
- /// </summary>
- /// <returns>An array of blocks</returns>
- 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<Block> GetEnumerator() => blocks.GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator();
-
- /// <summary>
- /// Adds a block to the group. You can only add newly placed blocks
- /// so that the game initializes the group membership properly.
- /// </summary>
- /// <param name="item"></param>
- /// <exception cref="NullReferenceException"></exception>
- 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, Id);
- }
-
- /// <summary>
- /// Removes all blocks from this group.
- /// You cannot remove blocks that have been initialized, only those that you placed recently.
- /// </summary>
- 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);
-
- /// <summary>
- /// Removes a block from this group.
- /// You cannot remove blocks that have been initialized, only those that you placed recently.
- /// </summary>
- /// <param name="item"></param>
- /// <returns></returns>
- /// <exception cref="NullReferenceException"></exception>
- 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}";
- }
- }
- }
|