TechbloxModdingAPI/TechbloxModdingAPI/BlockGroup.cs
Norbi Peti fef66c349d Merge branch 'master' into preview
# Conflicts:
#	Automation/gen_csproj.py
#	GamecraftModdingAPI/App/AppEngine.cs
#	GamecraftModdingAPI/App/GameGameEngine.cs
#	GamecraftModdingAPI/App/GameMenuEngine.cs
#	GamecraftModdingAPI/Block.cs
#	GamecraftModdingAPI/Blocks/BlockEngine.cs
#	GamecraftModdingAPI/Blocks/BlockEngineInit.cs
#	GamecraftModdingAPI/Blocks/BlockEventsEngine.cs
#	GamecraftModdingAPI/Blocks/BlockIDs.cs
#	GamecraftModdingAPI/Blocks/ConsoleBlock.cs
#	GamecraftModdingAPI/Blocks/DampedSpring.cs
#	GamecraftModdingAPI/Blocks/LogicGate.cs
#	GamecraftModdingAPI/Blocks/Motor.cs
#	GamecraftModdingAPI/Blocks/MusicBlock.cs
#	GamecraftModdingAPI/Blocks/ObjectIdentifier.cs
#	GamecraftModdingAPI/Blocks/Piston.cs
#	GamecraftModdingAPI/Blocks/PlacementEngine.cs
#	GamecraftModdingAPI/Blocks/Servo.cs
#	GamecraftModdingAPI/Blocks/SfxBlock.cs
#	GamecraftModdingAPI/Blocks/SpawnPoint.cs
#	GamecraftModdingAPI/Blocks/TextBlock.cs
#	GamecraftModdingAPI/Blocks/Timer.cs
#	GamecraftModdingAPI/GamecraftModdingAPI.csproj
#	GamecraftModdingAPI/Inventory/HotbarEngine.cs
#	GamecraftModdingAPI/Inventory/HotbarSlotSelectionHandlerEnginePatch.cs
#	GamecraftModdingAPI/Main.cs
#	GamecraftModdingAPI/Player.cs
#	GamecraftModdingAPI/Players/PlayerEngine.cs
#	GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
#	TechbloxModdingAPI/BlockGroup.cs
#	TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs
#	TechbloxModdingAPI/Blocks/Engines/RemovalEngine.cs
#	TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs
#	TechbloxModdingAPI/Blueprint.cs
#	TechbloxModdingAPI/Input/FakeInput.cs
2021-12-14 23:26:35 +01:00

217 lines
7.4 KiB
C#

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
{
/// <summary>
/// A group of blocks that can be selected together. The placed version of blueprints. Dispose after usage.
/// </summary>
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{
internal static BlueprintEngine _engine = new BlueprintEngine();
private readonly Block sourceBlock;
private readonly List<Block> 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<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, (int) Id.entityID);
}
/// <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}";
}
}
}