Files
ButterflyPinBoard/ButterflyPins/BlockEntities/BlockEntityButterflyPinBoard.cs

221 lines
6.9 KiB
C#

using System;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.Datastructures;
using Vintagestory.API.MathTools;
using Vintagestory.GameContent;
namespace ButterflyPins.BlockEntities;
public class BlockEntityButterflyPinBoard : BlockEntityDisplay
{
private const int MaxBoardSlots = 9;
private const float BoardMargin = 1f / 16f;
private const float BoardSurface = 1f / 16f;
private const string ButterflyPinPrefix = "clothes-butterflypin-";
private readonly InventoryGeneric inventory;
public override InventoryBase Inventory => inventory;
public override string InventoryClassName => "butterflypinboard";
public override int DisplayedItems => ActiveSlotCount;
private int Rows => Math.Max(1, Block?.Attributes?["rows"].AsInt(2) ?? 2);
private int Columns => Math.Max(1, Block?.Attributes?["columns"].AsInt(2) ?? 2);
private float RenderScale => Block?.Attributes?["renderScale"].AsFloat(0.18f) ?? 0.18f;
private float DisplayOriginX => GetDisplayFloat("displayOriginX", 0.31666666f);
private float DisplayOriginY => GetDisplayFloat("displayOriginY", 0.01333332f);
private float DisplayOriginZ => GetDisplayFloat("displayOriginZ", 0.47916666f);
private float DisplayStepX => GetDisplayFloat("displayStepX", -0.25f);
private float DisplayStepY => GetDisplayFloat("displayStepY", -0.25f);
private int ActiveSlotCount => Rows * Columns;
public BlockEntityButterflyPinBoard()
{
inventory = new InventoryGeneric(MaxBoardSlots, null, null);
}
public override void FromTreeAttributes(ITreeAttribute tree, IWorldAccessor worldForResolving)
{
base.FromTreeAttributes(tree, worldForResolving);
RedrawAfterReceivingTreeAttributes(worldForResolving);
}
public bool TryInsert(IPlayer byPlayer, int slotIndex, ItemSlot activeSlot)
{
if (!IsValidSlot(slotIndex) || activeSlot.Empty || !CanInsert(slotIndex, activeSlot.Itemstack))
return false;
ItemStack sourceStack = activeSlot.Itemstack;
ItemStack placedStack = sourceStack.Clone();
placedStack.StackSize = 1;
inventory[slotIndex].Itemstack = placedStack;
activeSlot.TakeOut(1);
activeSlot.MarkDirty();
inventory[slotIndex].MarkDirty();
MarkChanged();
return true;
}
public bool TryTake(IPlayer byPlayer, int slotIndex)
{
if (!CanTake(slotIndex))
return false;
ItemSlot slot = inventory[slotIndex];
ItemStack stack = slot.Itemstack!;
slot.Itemstack = null;
slot.MarkDirty();
if (!byPlayer.InventoryManager.TryGiveItemstack(stack, true))
Api.World.SpawnItemEntity(stack, Pos.ToVec3d().Add(0.5, 0.5, 0.5));
MarkChanged();
return true;
}
public bool CanInsert(int slotIndex, ItemStack stack)
{
return IsValidSlot(slotIndex) && inventory[slotIndex].Empty && IsButterflyPin(stack);
}
public bool CanTake(int slotIndex)
{
return IsValidSlot(slotIndex) && !inventory[slotIndex].Empty;
}
public void DropContents()
{
for (int slotIndex = 0; slotIndex < ActiveSlotCount; slotIndex++)
{
ItemSlot slot = inventory[slotIndex];
ItemStack? stack = slot.Itemstack;
if (stack == null)
continue;
Api.World.SpawnItemEntity(stack, Pos.ToVec3d().Add(0.5, 0.5, 0.5));
slot.Itemstack = null;
slot.MarkDirty();
}
}
public int GetSlotIndex(Vec3d hitPosition)
{
Vec3d local = ToBaseOrientation(hitPosition);
if (local.X < BoardMargin || local.X > 1 - BoardMargin || local.Y < BoardMargin || local.Y > 1 - BoardMargin)
return -1;
double width = (1 - (BoardMargin * 2)) / Columns;
double height = (1 - (BoardMargin * 2)) / Rows;
int column = GameMath.Clamp((int)((local.X - BoardMargin) / width), 0, Columns - 1);
if (IsNorthSouthVariant())
column = (Columns - 1) - column;
int rowFromBottom = GameMath.Clamp((int)((local.Y - BoardMargin) / height), 0, Rows - 1);
int rowFromTop = Rows - 1 - rowFromBottom;
return (rowFromTop * Columns) + column;
}
private bool IsValidSlot(int slotIndex)
{
return slotIndex >= 0 && slotIndex < ActiveSlotCount;
}
private bool IsButterflyPin(ItemStack stack)
{
AssetLocation? code = stack.Collectible?.Code;
return code != null && code.Path.StartsWith(ButterflyPinPrefix, StringComparison.Ordinal);
}
private void MarkChanged()
{
RedrawAfterReceivingTreeAttributes(Api.World);
base.MarkDirty(true);
Api.World.BlockAccessor.MarkBlockDirty(Pos);
}
private Vec3d ToBaseOrientation(Vec3d hitPosition)
{
Vec3d centered = new(hitPosition.X - 0.5, hitPosition.Y, hitPosition.Z - 0.5);
float angle = -GetRotationY();
float sin = GameMath.Sin(angle);
float cos = GameMath.Cos(angle);
return new Vec3d(
(centered.X * cos) - (centered.Z * sin) + 0.5,
hitPosition.Y,
(centered.X * sin) + (centered.Z * cos) + 0.5
);
}
private float GetDisplayFloat(string key, float defaultValue)
{
return Block?.Attributes?[key].AsFloat(defaultValue) ?? defaultValue;
}
protected override float[][] genTransformationMatrices()
{
float[][] matrices = new float[DisplayedItems][];
float boardRotationY = GetRotationY();
float itemRotationY = boardRotationY - GameMath.PIHALF;
for (int slotIndex = 0; slotIndex < DisplayedItems; slotIndex++)
{
int row = slotIndex / Columns;
int column = slotIndex % Columns;
Vec3f off = new(
DisplayOriginX + (column * DisplayStepX),
DisplayOriginY + (row * DisplayStepY),
DisplayOriginZ
);
off = new Matrixf().RotateY(boardRotationY).TransformVector(off.ToVec4f(0)).XYZ;
matrices[slotIndex] = new Matrixf()
.Translate(off.X, off.Y, off.Z)
.Translate(0.5f, 0.5f, 0.5f)
.RotateY(itemRotationY)
.Scale(RenderScale, RenderScale, RenderScale)
.Translate(-0.5f, -0.5f, -0.5f)
.Values;
}
return matrices;
}
public float GetRotationY()
{
string side = Block?.Variant?["side"] ?? "south";
return side switch
{
"north" => GameMath.PI,
"east" => GameMath.PIHALF,
"west" => GameMath.PI + GameMath.PIHALF,
_ => 0f,
};
}
private bool IsNorthSouthVariant()
{
string side = Block?.Variant?["side"] ?? "south";
return side is "north" or "south";
}
public float GetRenderScale()
{
return RenderScale;
}
}