// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using System.Drawing; using ClassicalSharp.GraphicsAPI; using OpenTK.Input; #if USE16_BIT using BlockID = System.UInt16; #else using BlockID = System.Byte; #endif namespace ClassicalSharp.Gui.Screens { public partial class InventoryScreen : Screen { public InventoryScreen(Game game) : base(game) { font = new Font(game.FontName, 16); HandlesAllInput = true; } BlockID[] blocksTable; Texture blockInfoTexture; const int maxRows = 8; int blocksPerRow { get { return game.ClassicMode && !game.ClassicHacks ? 9 : 10; } } int selIndex, rows; int startX, startY, blockSize; float selBlockExpand; readonly Font font; StringBuffer buffer = new StringBuffer(128); IsometricBlockDrawer drawer = new IsometricBlockDrawer(); int TableX { get { return startX - 5 - 10; } } int TableY { get { return startY - 5 - 30; } } int TableWidth { get { return blocksPerRow * blockSize + 10 + 20; } } int TableHeight { get { return Math.Min(rows, maxRows) * blockSize + 10 + 40; } } // These were sourced by taking a screenshot of vanilla // Then using paint to extract the colour components // Then using wolfram alpha to solve the glblendfunc equation static FastColour topCol = new FastColour(34, 34, 34, 168); static FastColour bottomCol = new FastColour(57, 57, 104, 202); static FastColour topSelCol = new FastColour(255, 255, 255, 142); static FastColour bottomSelCol = new FastColour(255, 255, 255, 192); static VertexP3fT2fC4b[] vertices = new VertexP3fT2fC4b[8 * 10 * (4 * 4)]; int vb; public override void Render(double delta) { gfx.Draw2DQuad(TableX, TableY, TableWidth, TableHeight, topCol, bottomCol); if (rows > maxRows) DrawScrollbar(); if (selIndex != -1 && game.ClassicMode) { int x, y; GetCoords(selIndex, out x, out y); float off = blockSize * 0.1f; gfx.Draw2DQuad(x - off, y - off, blockSize + off * 2, blockSize + off * 2, topSelCol, bottomSelCol); } gfx.Texturing = true; gfx.SetBatchFormat(VertexFormat.P3fT2fC4b); drawer.BeginBatch(game, vertices, vb); for (int i = 0; i < blocksTable.Length; i++) { int x, y; if (!GetCoords(i, out x, out y)) continue; // We want to always draw the selected block on top of others if (i == selIndex) continue; drawer.DrawBatch(blocksTable[i], blockSize * 0.7f / 2f, x + blockSize / 2, y + blockSize / 2); } if (selIndex != -1) { int x, y; GetCoords(selIndex, out x, out y); drawer.DrawBatch(blocksTable[selIndex], (blockSize + selBlockExpand) * 0.7f / 2, x + blockSize / 2, y + blockSize / 2); } drawer.EndBatch(); if (blockInfoTexture.IsValid) blockInfoTexture.Render(gfx); gfx.Texturing = false; } bool GetCoords(int i, out int x, out int y) { int col = i % blocksPerRow; int row = i / blocksPerRow; x = startX + blockSize * col; y = startY + blockSize * row + 3; y -= scrollY * blockSize; row -= scrollY; return row >= 0 && row < maxRows; } Point GetMouseCoords(int i) { int x, y; GetCoords(i, out x, out y); x += blockSize / 2; y += blockSize / 2; Point topLeft = game.PointToScreen(Point.Empty); x += topLeft.X; y += topLeft.Y; return new Point(x, y); } public override void Dispose() { font.Dispose(); game.Events.BlockPermissionsChanged -= BlockPermissionsChanged; game.Keyboard.KeyRepeat = false; ContextLost(); game.Graphics.ContextLost -= ContextLost; game.Graphics.ContextRecreated -= ContextRecreated; } public override void OnResize(int width, int height) { blockSize = (int)(50 * Math.Sqrt(game.GuiInventoryScale)); selBlockExpand = (float)(25 * Math.Sqrt(game.GuiInventoryScale)); int rowsUsed = Math.Min(maxRows, rows); startX = game.Width / 2 - (blockSize * blocksPerRow) / 2; startY = game.Height / 2 - (rowsUsed * blockSize) / 2; blockInfoTexture.X1 = startX + (blockSize * blocksPerRow) / 2 - blockInfoTexture.Width / 2; blockInfoTexture.Y1 = startY - blockInfoTexture.Height - 5; } public override void Init() { blockSize = (int)(50 * Math.Sqrt(game.GuiInventoryScale)); selBlockExpand = (float)(25 * Math.Sqrt(game.GuiInventoryScale)); game.Events.BlockPermissionsChanged += BlockPermissionsChanged; ContextRecreated(); game.Graphics.ContextLost += ContextLost; game.Graphics.ContextRecreated += ContextRecreated; RecreateBlockTable(); SetBlockTo(game.Inventory.Selected); game.Keyboard.KeyRepeat = true; } public void SetBlockTo(BlockID block) { selIndex = Array.IndexOf(blocksTable, block); scrollY = (selIndex / blocksPerRow) - (maxRows - 1); ClampScrollY(); MoveCursorToSelected(); RecreateBlockInfoTexture(); } void MoveCursorToSelected() { if (selIndex == -1) return; game.DesktopCursorPos = GetMouseCoords(selIndex); } void BlockPermissionsChanged(object sender, EventArgs e) { RecreateBlockTable(); if (selIndex >= blocksTable.Length) selIndex = blocksTable.Length - 1; scrollY = selIndex / blocksPerRow; ClampScrollY(); RecreateBlockInfoTexture(); } void UpdateBlockInfoString(BlockID block) { int index = 0; buffer.Clear(); buffer.Append(ref index, "&f"); string value = game.BlockInfo.Name[block]; buffer.Append(ref index, value); if (game.ClassicMode) return; buffer.Append(ref index, " (ID "); buffer.AppendNum(ref index, block); buffer.Append(ref index, "&f, place "); buffer.Append(ref index, game.Inventory.CanPlace[block] ? "&aYes" : "&cNo"); buffer.Append(ref index, "&f, delete "); buffer.Append(ref index, game.Inventory.CanDelete[block] ? "&aYes" : "&cNo"); buffer.Append(ref index, "&f)"); } int lastCreatedIndex = -1000; void RecreateBlockInfoTexture() { if (selIndex == lastCreatedIndex || blocksTable == null) return; lastCreatedIndex = selIndex; gfx.DeleteTexture(ref blockInfoTexture); if (selIndex == -1) return; BlockID block = blocksTable[selIndex]; UpdateBlockInfoString(block); string value = buffer.ToString(); DrawTextArgs args = new DrawTextArgs(value, font, true); Size size = game.Drawer2D.MeasureSize(ref args); int x = startX + (blockSize * blocksPerRow) / 2 - size.Width / 2; int y = startY - size.Height - 5; args.SkipPartsCheck = true; blockInfoTexture = game.Drawer2D.MakeTextTexture(ref args, x, y); } void RecreateBlockTable() { int blocksCount = 0; int count = game.UseCPE ? Block.Count : Block.OriginalCount; for (int i = 1; i < count; i++) { BlockID block = game.Inventory.Map[i]; if (Show(block)) blocksCount++; } rows = Utils.CeilDiv(blocksCount, blocksPerRow); int rowsUsed = Math.Min(maxRows, rows); startX = game.Width / 2 - (blockSize * blocksPerRow) / 2; startY = game.Height / 2 - (rowsUsed * blockSize) / 2; blocksTable = new BlockID[blocksCount]; int index = 0; for (int i = 1; i < count; i++) { BlockID block = game.Inventory.Map[i]; if (Show(block)) blocksTable[index++] = block; } } bool Show(BlockID block) { if (game.PureClassic && IsHackBlock(block)) return false; if (block < Block.CpeCount) { int count = game.UseCPEBlocks ? Block.CpeCount : Block.OriginalCount; return block < count && game.BlockInfo.Name[block] != "Invalid"; } return game.BlockInfo.Name[block] != "Invalid"; } bool IsHackBlock(BlockID block) { return block == Block.DoubleSlab || block == Block.Bedrock || block == Block.Grass || game.BlockInfo.IsLiquid(block); } protected override void ContextLost() { gfx.DeleteVb(ref vb); gfx.DeleteTexture(ref blockInfoTexture); lastCreatedIndex = -1000; } protected override void ContextRecreated() { vb = gfx.CreateDynamicVb(VertexFormat.P3fT2fC4b, vertices.Length); RecreateBlockInfoTexture(); } } }