From c0bdd4ff1dad41988ce2d51cfdb9e38fc68aca59 Mon Sep 17 00:00:00 2001
From: SquidDev <bonzoweb@hotmail.co.uk>
Date: Mon, 18 Jun 2018 22:09:24 +0100
Subject: [PATCH] Add basic support for fancy rendering of printouts

 - When held in first-person, single pages are displayed like a map.
 - When placed in an item frame, the page is drawn instead of the actual
   item.
---
 .../computercraft/client/gui/GuiPrintout.java |  47 ++--
 .../proxy/ComputerCraftProxyClient.java       |   2 +
 .../client/render/ItemPrintoutRenderer.java   | 219 ++++++++++++++++++
 .../resources/META-INF/computercraft_at.cfg   |   4 +
 4 files changed, 248 insertions(+), 24 deletions(-)
 create mode 100644 src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java

diff --git a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java
index 06d994150e..f37a57a822 100644
--- a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java
+++ b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java
@@ -20,10 +20,9 @@
 
 public class GuiPrintout extends GuiContainer
 {
-    private static final ResourceLocation background = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
-    
-    private static final int xSize = 172;
-    private static final int ySize = 209;
+    public static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
+    public static final int X_SIZE = 172;
+    public static final int Y_SIZE = 209;
     
     private final boolean m_book;
     private final int m_pages;
@@ -142,57 +141,57 @@ public void drawScreen(int mouseX, int mouseY, float f)
         
         // Draw the printout
         GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
-        this.mc.getTextureManager().bindTexture( background );
+        this.mc.getTextureManager().bindTexture( BACKGROUND );
         
-        int startY = (height - ySize) / 2;
-        //int startX = (width - xSize) / 2 - (m_page * 8);
-        int startX = (width - (xSize + (m_pages - 1)*8)) / 2;
+        int startY = (height - Y_SIZE) / 2;
+        //int startX = (width - X_SIZE) / 2 - (m_page * 8);
+        int startX = (width - (X_SIZE + (m_pages - 1)*8)) / 2;
         
         if( m_book )
         {
             // Border
-            drawTexturedModalRect( startX - 8, startY - 8, xSize + 48, 0, 12, ySize + 24);
-            drawTexturedModalRect( startX + xSize + (m_pages - 1)*8 - 4, startY - 8, xSize + 48 + 12, 0, 12, ySize + 24);
+            drawTexturedModalRect( startX - 8, startY - 8, X_SIZE + 48, 0, 12, Y_SIZE + 24);
+            drawTexturedModalRect( startX + X_SIZE + (m_pages - 1)*8 - 4, startY - 8, X_SIZE + 48 + 12, 0, 12, Y_SIZE + 24);
             
-            drawTexturedModalRect( startX, startY - 8, 0, ySize, xSize, 12);
-            drawTexturedModalRect( startX, startY + ySize - 4, 0, ySize + 12, xSize, 12);
+            drawTexturedModalRect( startX, startY - 8, 0, Y_SIZE, X_SIZE, 12);
+            drawTexturedModalRect( startX, startY + Y_SIZE - 4, 0, Y_SIZE + 12, X_SIZE, 12);
             for( int n=1; n<m_pages; ++n )
             {
-                drawTexturedModalRect( startX + xSize + (n-1)*8, startY - 8, 0, ySize, 8, 12);
-                drawTexturedModalRect( startX + xSize + (n-1)*8, startY + ySize - 4, 0, ySize + 12, 8, 12);
+                drawTexturedModalRect( startX + X_SIZE + (n-1)*8, startY - 8, 0, Y_SIZE, 8, 12);
+                drawTexturedModalRect( startX + X_SIZE + (n-1)*8, startY + Y_SIZE - 4, 0, Y_SIZE + 12, 8, 12);
             }
         }
             
         // Left half
         if( m_page == 0 )
         {            
-            drawTexturedModalRect( startX, startY, 24, 0, xSize / 2, ySize);
-            drawTexturedModalRect( startX, startY, 0, 0, 12, ySize);
+            drawTexturedModalRect( startX, startY, 24, 0, X_SIZE / 2, Y_SIZE );
+            drawTexturedModalRect( startX, startY, 0, 0, 12, Y_SIZE );
         }
         else
         {
-            drawTexturedModalRect( startX, startY, 0, 0, 12, ySize);
+            drawTexturedModalRect( startX, startY, 0, 0, 12, Y_SIZE );
             for( int n=1; n<m_page; ++n )
             {
-                drawTexturedModalRect( startX + n*8, startY, 12, 0, 12, ySize);                
+                drawTexturedModalRect( startX + n*8, startY, 12, 0, 12, Y_SIZE );                
             }
-            drawTexturedModalRect( startX + m_page*8, startY, 24, 0, xSize / 2, ySize);
+            drawTexturedModalRect( startX + m_page*8, startY, 24, 0, X_SIZE / 2, Y_SIZE );
         }
         
         // Right half
         if( m_page == (m_pages - 1) )
         {
-            drawTexturedModalRect( startX + m_page*8 + xSize/2, startY, 24 + xSize / 2, 0, xSize / 2, ySize);
-            drawTexturedModalRect( startX + m_page*8 + (xSize - 12), startY, 24 + xSize + 12, 0, 12, ySize);
+            drawTexturedModalRect( startX + m_page*8 + X_SIZE /2, startY, 24 + X_SIZE / 2, 0, X_SIZE / 2, Y_SIZE );
+            drawTexturedModalRect( startX + m_page*8 + (X_SIZE - 12), startY, 24 + X_SIZE + 12, 0, 12, Y_SIZE );
         }
         else 
         {
-            drawTexturedModalRect( startX + (m_pages - 1)*8 + (xSize - 12), startY, 24 + xSize + 12, 0, 12, ySize);
+            drawTexturedModalRect( startX + (m_pages - 1)*8 + (X_SIZE - 12), startY, 24 + X_SIZE + 12, 0, 12, Y_SIZE );
             for( int n=m_pages-2; n>=m_page; --n )
             {
-                drawTexturedModalRect( startX + n*8 + (xSize - 12), startY, 24 + xSize, 0, 12, ySize);
+                drawTexturedModalRect( startX + n*8 + (X_SIZE - 12), startY, 24 + X_SIZE, 0, 12, Y_SIZE );
             }
-            drawTexturedModalRect( startX + m_page*8 + xSize/2, startY, 24 + xSize / 2, 0, xSize / 2, ySize);
+            drawTexturedModalRect( startX + m_page*8 + X_SIZE /2, startY, 24 + X_SIZE / 2, 0, X_SIZE / 2, Y_SIZE );
         }
 
         // Draw the text
diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
index db2da93303..0c554f3670 100644
--- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
+++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
@@ -8,6 +8,7 @@
 
 import dan200.computercraft.ComputerCraft;
 import dan200.computercraft.client.gui.*;
+import dan200.computercraft.client.render.ItemPrintoutRenderer;
 import dan200.computercraft.client.render.TileEntityMonitorRenderer;
 import dan200.computercraft.shared.computer.blocks.ComputerState;
 import dan200.computercraft.shared.computer.blocks.TileComputer;
@@ -442,6 +443,7 @@ private void registerForgeHandlers()
     {
         ForgeHandlers handlers = new ForgeHandlers();
         MinecraftForge.EVENT_BUS.register( handlers );
+        MinecraftForge.EVENT_BUS.register( new ItemPrintoutRenderer() );
     }
 
     public class ForgeHandlers
diff --git a/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java
new file mode 100644
index 0000000000..c4703f63ff
--- /dev/null
+++ b/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java
@@ -0,0 +1,219 @@
+package dan200.computercraft.client.render;
+
+import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.client.gui.FixedWidthFontRenderer;
+import dan200.computercraft.client.gui.GuiPrintout;
+import dan200.computercraft.core.terminal.TextBuffer;
+import dan200.computercraft.shared.media.items.ItemPrintout;
+import dan200.computercraft.shared.util.Palette;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.ItemRenderer;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.EnumHandSide;
+import net.minecraft.util.math.MathHelper;
+import net.minecraftforge.client.event.RenderItemInFrameEvent;
+import net.minecraftforge.client.event.RenderSpecificHandEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.lwjgl.opengl.GL11;
+
+import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
+import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
+import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
+import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH;
+
+public class ItemPrintoutRenderer
+{
+    @SubscribeEvent
+    public void onRenderInHand( RenderSpecificHandEvent event )
+    {
+        ItemStack stack = event.getItemStack();
+        if( stack.getItem() != ComputerCraft.Items.printout ) return;
+
+        // We only allow single pages to be viewed in-hand for now
+        if( ItemPrintout.getType( stack ) != ItemPrintout.Type.Single ) return;
+
+        event.setCanceled( true );
+
+        EntityPlayer player = Minecraft.getMinecraft().player;
+
+        GlStateManager.pushMatrix();
+        if( event.getHand() == EnumHand.MAIN_HAND && player.getHeldItemOffhand().isEmpty() )
+        {
+            renderPrintoutFirstPersonCentre(
+                event.getInterpolatedPitch(),
+                event.getEquipProgress(),
+                event.getSwingProgress(),
+                stack
+            );
+        }
+        else
+        {
+            renderPrintoutFirstPersonSide(
+                event.getHand() == EnumHand.MAIN_HAND ? player.getPrimaryHand() : player.getPrimaryHand().opposite(),
+                event.getEquipProgress(),
+                event.getSwingProgress(),
+                stack
+            );
+        }
+        GlStateManager.popMatrix();
+    }
+
+    /**
+     * Renders a pocket computer to one side of the player.
+     *
+     * @param side          The side to render on
+     * @param equipProgress The equip progress of this item
+     * @param swingProgress The swing progress of this item
+     * @param stack         The stack to render
+     * @see ItemRenderer#renderMapFirstPersonSide(float, EnumHandSide, float, ItemStack)
+     */
+    private void renderPrintoutFirstPersonSide( EnumHandSide side, float equipProgress, float swingProgress, ItemStack stack )
+    {
+        Minecraft minecraft = Minecraft.getMinecraft();
+        float offset = side == EnumHandSide.RIGHT ? 1f : -1f;
+        GlStateManager.translate( offset * 0.125f, -0.125f, 0f );
+
+        // If the player is not invisible then render a single arm
+        if( !minecraft.player.isInvisible() )
+        {
+            GlStateManager.pushMatrix();
+            GlStateManager.rotate( offset * 10f, 0f, 0f, 1f );
+            minecraft.getItemRenderer().renderArmFirstPerson( equipProgress, swingProgress, side );
+            GlStateManager.popMatrix();
+        }
+
+        // Setup the appropriate transformations. This is just copied from the
+        // corresponding method in ItemRenderer. 
+        GlStateManager.pushMatrix();
+        GlStateManager.translate( offset * 0.51f, -0.08f + equipProgress * -1.2f, -0.75f );
+        float f1 = MathHelper.sqrt( swingProgress );
+        float f2 = MathHelper.sin( f1 * (float) Math.PI );
+        float f3 = -0.5f * f2;
+        float f4 = 0.4f * MathHelper.sin( f1 * ((float) Math.PI * 2f) );
+        float f5 = -0.3f * MathHelper.sin( swingProgress * (float) Math.PI );
+        GlStateManager.translate( offset * f3, f4 - 0.3f * f2, f5 );
+        GlStateManager.rotate( f2 * -45f, 1f, 0f, 0f );
+        GlStateManager.rotate( offset * f2 * -30f, 0f, 1f, 0f );
+
+        renderPrintoutFirstPerson( stack );
+
+        GlStateManager.popMatrix();
+    }
+
+    /**
+     * Render an item in the middle of the screen
+     *
+     * @param pitch         The pitch of the player
+     * @param equipProgress The equip progress of this item
+     * @param swingProgress The swing progress of this item
+     * @param stack         The stack to render
+     * @see ItemRenderer#renderMapFirstPerson(float, float, float)
+     */
+    private void renderPrintoutFirstPersonCentre( float pitch, float equipProgress, float swingProgress, ItemStack stack )
+    {
+        ItemRenderer itemRenderer = Minecraft.getMinecraft().getItemRenderer();
+
+        // Setup the appropriate transformations. This is just copied from the
+        // corresponding method in ItemRenderer.
+        float swingRt = MathHelper.sqrt( swingProgress );
+        float tX = -0.2f * MathHelper.sin( swingProgress * (float) Math.PI );
+        float tZ = -0.4f * MathHelper.sin( swingRt * (float) Math.PI );
+        GlStateManager.translate( 0f, -tX / 2f, tZ );
+        float pitchAngle = itemRenderer.getMapAngleFromPitch( pitch );
+        GlStateManager.translate( 0f, 0.04f + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
+        GlStateManager.rotate( pitchAngle * -85f, 1f, 0f, 0f );
+        itemRenderer.renderArms();
+        float rX = MathHelper.sin( swingRt * (float) Math.PI );
+        GlStateManager.rotate( rX * 20f, 1f, 0f, 0f );
+        GlStateManager.scale( 2f, 2f, 2f );
+
+        renderPrintoutFirstPerson( stack );
+    }
+
+
+    private static void renderPrintoutFirstPerson( ItemStack stack )
+    {
+        // Setup various transformations. Note that these are partially adapated from the corresponding method
+        // in ItemRenderer.renderMapFirstPerson
+        GlStateManager.disableLighting();
+
+        GlStateManager.rotate( 180f, 0f, 1f, 0f );
+        GlStateManager.rotate( 180f, 0f, 0f, 1f );
+        GlStateManager.scale( 0.38f, 0.38f, 0.38f );
+        GlStateManager.translate( -0.5f, -0.5f, 0.0f );
+
+        drawPrintout( stack );
+
+        GlStateManager.enableLighting();
+    }
+
+    @SubscribeEvent
+    public void onRenderInFrame( RenderItemInFrameEvent event )
+    {
+        ItemStack stack = event.getItem();
+        if( stack.getItem() != ComputerCraft.Items.printout ) return;
+
+        // We only allow single pages to be viewed in-hand for now
+        if( ItemPrintout.getType( stack ) != ItemPrintout.Type.Single ) return;
+
+        event.setCanceled( true );
+
+        GlStateManager.disableLighting();
+
+        // Move a little bit forward to ensure we're not clipping with the frame
+        GlStateManager.translate( 0.0f, 0.0f, -0.001f );
+        GlStateManager.rotate( 180f, 0f, 0f, 1f );
+        GlStateManager.translate( -0.5f, -0.5f, 0.0f );
+
+        drawPrintout( stack );
+
+        GlStateManager.enableLighting();
+    }
+
+    private static void drawPrintout( ItemStack stack )
+    {
+        int xMargin = 13;
+        int yMargin = 11;
+
+        int width = LINE_MAX_LENGTH * FONT_WIDTH + xMargin * 2;
+        int height = LINES_PER_PAGE * FONT_HEIGHT + yMargin * 2;
+        int max = Math.max( height, width );
+
+        // Scale the printout to fit correctly.
+        double scale = 1.0 / max;
+        GlStateManager.scale( scale, scale, scale );
+        GlStateManager.translate( (max - width) / 2.0f, (max - height) / 2.0f, 0.0f );
+
+        drawBackground( 0, 0, 0.01 );
+
+        FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
+
+        String[] text = ItemPrintout.getText( stack );
+        String[] colours = ItemPrintout.getColours( stack );
+        for( int line = 0; line < LINES_PER_PAGE && line < text.length; ++line )
+        {
+            fontRenderer.drawString( new TextBuffer( text[ line ] ), xMargin, yMargin + line * FONT_HEIGHT, new TextBuffer( colours[ line ] ), null, 0, 0, false, Palette.DEFAULT );
+        }
+    }
+
+    private static void drawBackground( double x, double y, double z )
+    {
+        Minecraft mc = Minecraft.getMinecraft();
+        mc.getTextureManager().bindTexture( GuiPrintout.BACKGROUND );
+
+        Tessellator tessellator = Tessellator.getInstance();
+        BufferBuilder buffer = tessellator.getBuffer();
+        buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
+        buffer.pos( x, y + GuiPrintout.Y_SIZE, z ).tex( 24 / 256.0, GuiPrintout.Y_SIZE / 256.0 ).endVertex();
+        buffer.pos( x + GuiPrintout.X_SIZE, y + GuiPrintout.Y_SIZE, z ).tex( (24 + GuiPrintout.X_SIZE) / 256.0, GuiPrintout.Y_SIZE / 256.0 ).endVertex();
+        buffer.pos( x + GuiPrintout.X_SIZE, y, z ).tex( (24 + GuiPrintout.X_SIZE) / 256.0, 0 ).endVertex();
+        buffer.pos( x, y, z ).tex( 24 / 256.0, 0 ).endVertex();
+        tessellator.draw();
+    }
+}
diff --git a/src/main/resources/META-INF/computercraft_at.cfg b/src/main/resources/META-INF/computercraft_at.cfg
index 48f245c203..e25cd87c64 100644
--- a/src/main/resources/META-INF/computercraft_at.cfg
+++ b/src/main/resources/META-INF/computercraft_at.cfg
@@ -1,3 +1,7 @@
 # RecordMedia (and related methods)
 public net.minecraft.item.ItemRecord field_185076_b # sound
 public net.minecraft.item.ItemRecord field_185077_c # displayName
+# ItemPocketRenderer
+public net.minecraft.client.renderer.ItemRenderer func_187466_c()V # renderArms
+public net.minecraft.client.renderer.ItemRenderer func_178100_c(F)F # getMapAngleFromPitch
+public net.minecraft.client.renderer.ItemRenderer func_187456_a(FFLnet/minecraft/util/EnumHandSide;)V # renderArmFirstPerson