diff --git a/dependencies.gradle b/dependencies.gradle index 9f7b24d3..9181a98f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,9 +1,26 @@ // Add your dependencies here dependencies { - api("com.github.GTNewHorizons:GTNHLib:0.3.2:dev") - runtimeOnly("com.github.GTNewHorizons:NotEnoughItems:2.6.14-GTNH:dev") + api("com.github.GTNewHorizons:GTNHLib:0.4.0:dev") + // Now a hard dep for the NEI Handlers + api("com.github.GTNewHorizons:NotEnoughItems:2.6.22-GTNH:dev") + compileOnly("com.github.GTNewHorizons:NotEnoughEnergistics:1.6.0:dev") testImplementation(platform('org.junit:junit-bom:5.9.2')) testImplementation('org.junit.jupiter:junit-jupiter') + + + // Temporary + api("com.github.GTNewHorizons:GT5-Unofficial:5.09.48.150:dev") { + exclude group:"com.github.GTNewHorizons", module:"StructureLib" + } + runtimeOnlyNonPublishable("com.github.GTNewHorizons:StructureCompat:0.6.3:dev") { + exclude group:"com.github.GTNewHorizons", module:"StructureLib" + } + + compileOnly('org.jetbrains:annotations:24.0.1') + compileOnly("org.projectlombok:lombok:1.18.22") {transitive = false } + annotationProcessor("org.projectlombok:lombok:1.18.22") + + runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2")) } diff --git a/gradle.properties b/gradle.properties index 446d0563..f6fb8fd0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,10 +50,10 @@ enableGenericInjection = true # Generate a class with a String field for the mod version named as defined below. # If generateGradleTokenClass is empty or not missing, no such class will be generated. # If gradleTokenVersion is empty or missing, the field will not be present in the class. -generateGradleTokenClass = +generateGradleTokenClass = com.gtnewhorizon.structurelib.Tags # Name of the token containing the project's current version to generate/replace. -gradleTokenVersion = GRADLETOKEN_VERSION +gradleTokenVersion = VERSION # [DEPRECATED] Mod ID replacement token. gradleTokenModId = @@ -70,7 +70,7 @@ gradleTokenGroupName = # The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's # version in @Mod([...], version = VERSION, [...]). # Leave these properties empty to skip individual token replacements. -replaceGradleTokenInFile = StructureLib.java +replaceGradleTokenInFile = # In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can # leave this property empty. @@ -80,13 +80,13 @@ apiPackage = # Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/ # There can be multiple files in a space-separated list. # Example value: mymodid_at.cfg nei_at.cfg -accessTransformersFile = +accessTransformersFile = structurelib_at.cfg # Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled! usesMixins = true # Adds some debug arguments like verbose output and class export. -usesMixinDebug = false +usesMixinDebug = true # Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. mixinPlugin = diff --git a/repositories.gradle b/repositories.gradle index 45729e10..72fe253d 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -1,4 +1,22 @@ // Add any additional repositiroes for your dependencies here repositories { + exclusiveContent { + forRepository { + ivy { + name = 'CoreTweaks releases' + url = 'https://github.com/makamys/CoreTweaks/releases/download/' + patternLayout { + artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])' + } + metadataSources { + artifact() + } + } + } + filter { + includeGroup('CoreTweaks') + } + } + mavenLocal() } diff --git a/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java index aef996c0..a049b796 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java @@ -11,6 +11,7 @@ import net.minecraft.block.Block; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.GuiNewChat; import net.minecraft.client.particle.EntityFX; import net.minecraft.client.renderer.Tessellator; @@ -24,18 +25,27 @@ import net.minecraft.util.ChatComponentText; import net.minecraft.util.IChatComponent; import net.minecraft.util.IIcon; +import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.event.world.WorldEvent; import org.lwjgl.opengl.GL11; +import com.gtnewhorizon.structurelib.client.nei.IMCForNEI; +import com.gtnewhorizon.structurelib.client.nei.InputHandler; +import com.gtnewhorizon.structurelib.client.renderer.RenderStructureGlobal; +import com.gtnewhorizon.structurelib.client.world.StructureWorld; import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX; import com.gtnewhorizon.structurelib.net.SetChannelDataMessage; +import codechicken.nei.guihook.GuiContainerManager; import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent; @@ -43,9 +53,17 @@ public class ClientProxy extends CommonProxy { + // Structure Renderer + private static final Minecraft minecraft = Minecraft.getMinecraft(); + private static StructureWorld structureWorld = null; + private static final ThreadLocal renderStructureGlobal = ThreadLocal + .withInitial(RenderStructureGlobal::new); + + // End Structure Renderer private static final short[] RGBA_NO_TINT = { 255, 255, 255, 255 }; private static final short[] RGBA_RED_TINT = { 255, 128, 128, 0 }; private static final Map allHints = new HashMap<>(); + /** * All batches of hints. */ @@ -81,6 +99,14 @@ public class ClientProxy extends CommonProxy { */ private static final Map localThrottleMap = new HashMap<>(); + public static StructureWorld getStructureWorld() { + return structureWorld; + } + + public static void setStructureWorld(StructureWorld structureWorld) { + ClientProxy.structureWorld = structureWorld; + } + @Override public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { ensureHinting(); @@ -242,8 +268,44 @@ public void uploadChannels(ItemStack trigger) { @Override public void preInit(FMLPreInitializationEvent e) { + super.preInit(e); FMLCommonHandler.instance().bus().register(new FMLEventHandler()); MinecraftForge.EVENT_BUS.register(new ForgeEventHandler()); + GuiContainerManager.addInputHandler(new InputHandler()); + GuiContainerManager.addTooltipHandler(new InputHandler()); + } + + @Override + public void init(FMLInitializationEvent e) { + super.init(e); + IMCForNEI.IMCSender(); + } + + @Override + public void postInit(FMLPostInitializationEvent e) { + super.postInit(e); + MinecraftForge.EVENT_BUS.register(new EventHandler()); + } + + private static ForgeDirection getOrientation(EntityPlayer player) { + if (player.rotationPitch > 45) { + return ForgeDirection.DOWN; + } else if (player.rotationPitch < -45) { + return ForgeDirection.UP; + } else { + switch (MathHelper.floor_double(player.rotationYaw / 90.0 + 0.5) & 3) { + case 0: + return ForgeDirection.SOUTH; + case 1: + return ForgeDirection.WEST; + case 2: + return ForgeDirection.NORTH; + case 3: + return ForgeDirection.EAST; + } + } + + return ForgeDirection.UNKNOWN; } static void markTextureUsed(IIcon icon) { @@ -503,6 +565,29 @@ public void onWorldLoad(WorldEvent.Load e) { @SubscribeEvent public void onRenderWorldLast(RenderWorldLastEvent e) { + renderParticlePreview(e); + renderNewPreview(e); + } + + private void renderNewPreview(RenderWorldLastEvent e) { + EntityPlayerSP player = minecraft.thePlayer; + if (player == null || structureWorld == null || !structureWorld.isRendering()) return; + + final RenderStructureGlobal renderStructureGlobal = ClientProxy.renderStructureGlobal.get(); + renderStructureGlobal.setPlayerPosition( + player.lastTickPosX + (player.posX - player.lastTickPosX) * e.partialTicks, + player.lastTickPosY + (player.posY - player.lastTickPosY) * e.partialTicks, + player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * e.partialTicks); + renderStructureGlobal.setOrientation(getOrientation(player)); + renderStructureGlobal.setRotationRender(MathHelper.floor_double(player.rotationYaw / 90) & 3); + + minecraft.mcProfiler.startSection("structlibRenderPreview"); + renderStructureGlobal.render(structureWorld); + + minecraft.mcProfiler.endSection(); + } + + private void renderParticlePreview(RenderWorldLastEvent e) { if (allHintsForRender.isEmpty()) return; // seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here. @@ -571,4 +656,5 @@ public void onRenderWorldLast(RenderWorldLastEvent e) { p.endSection(); } } + } diff --git a/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java index 6f542b55..07e68ef9 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java @@ -16,6 +16,8 @@ import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage; import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; public class CommonProxy { @@ -55,6 +57,10 @@ public void endHinting(World w) {} public void preInit(FMLPreInitializationEvent e) {} + public void init(FMLInitializationEvent e) {} + + public void postInit(FMLPostInitializationEvent e) {} + public long getOverworldTime() { return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime(); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/EventHandler.java b/src/main/java/com/gtnewhorizon/structurelib/EventHandler.java new file mode 100644 index 00000000..92cd45dc --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/EventHandler.java @@ -0,0 +1,21 @@ +package com.gtnewhorizon.structurelib; + +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.client.nei.GUI_MultiblockHandler; +import com.gtnewhorizon.structurelib.structure.IStructureElement; +import com.gtnewhorizon.structurelib.util.PositionedIStructureElement; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class EventHandler { + + @SubscribeEvent + public void OnStructureEvent(StructureEvent.StructureElementVisitedEvent event) { + GUI_MultiblockHandler.structureElements.add( + new PositionedIStructureElement( + event.getX(), + event.getY(), + event.getZ(), + (IStructureElement) event.getElement())); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java index 2269a7db..746b7cb8 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java @@ -24,6 +24,8 @@ import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.SidedProxy; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; import cpw.mods.fml.common.network.NetworkRegistry; @@ -37,8 +39,8 @@ */ @Mod( modid = StructureLibAPI.MOD_ID, - name = "StructureLib", - version = "GRADLETOKEN_VERSION", + version = Tags.VERSION, + name = StructureLibAPI.MOD_NAME, acceptableRemoteVersions = "*", guiFactory = "com.gtnewhorizon.structurelib.GuiFactory") public class StructureLib { @@ -95,6 +97,9 @@ public Item getTabIconItem() { } }; + public static boolean isGTLoaded; + public static boolean isNEELoaded; + @Mod.EventHandler public void preInit(FMLPreInitializationEvent e) { ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile()); @@ -112,6 +117,20 @@ public void preInit(FMLPreInitializationEvent e) { if (Loader.isModLoaded(STRUCTURECOMPAT_MODID)) { COMPAT = Loader.instance().getIndexedModList().get(STRUCTURECOMPAT_MODID).getMod(); } + + isGTLoaded = Loader.isModLoaded("gregtech"); + isNEELoaded = Loader.isModLoaded("neenergistics"); + } + + @Mod.EventHandler + // load "Do your mod setup. Build whatever data structures you care about. Register recipes." + public void init(FMLInitializationEvent event) { + proxy.init(event); + } + + @Mod.EventHandler + public void postInit(FMLPostInitializationEvent event) { + proxy.postInit(event); } @Mod.EventHandler diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index db8339e8..915a95c3 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -27,6 +27,8 @@ public class StructureLibAPI { public static final String MOD_ID = "structurelib"; + public static final String MOD_NAME = "StructureLib"; + public static final int HINT_BLOCK_META_GENERIC_0 = 0; public static final int HINT_BLOCK_META_GENERIC_1 = 1; public static final int HINT_BLOCK_META_GENERIC_2 = 2; diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/gui/GuiText.java b/src/main/java/com/gtnewhorizon/structurelib/client/gui/GuiText.java new file mode 100644 index 00000000..d174182a --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/gui/GuiText.java @@ -0,0 +1,52 @@ +package com.gtnewhorizon.structurelib.client.gui; + +import net.minecraft.util.StatCollector; + +import com.gtnewhorizon.structurelib.StructureLib; + +public enum GuiText { + + // UI Text + Tier, + Layer, + // UI Colors + FontColor(0x333333), + BgColor(0xC6C6C6), + ButtonEnabledColor(0x202020), + ButtonDisabledColor(0xA0A0A0), + ButtonHoveredColor(0xFFFFA0); + + private final String root; + private final int color; + + GuiText() { + this.root = "gui.blockrenderer6343"; + this.color = 0x000000; + } + + GuiText(final int hex) { + this.root = "gui.blockrenderer6343"; + this.color = hex; + } + + public int getColor() { + String hex = StatCollector.translateToLocal(this.getUnlocalized()); + int color = this.color; + if (hex.length() <= 6) { + try { + color = Integer.parseUnsignedInt(hex, 16); + } catch (final NumberFormatException e) { + StructureLib.LOGGER.warn("Couldn't format color correctly for: " + this.root + " -> " + hex); + } + } + return color; + } + + public String getLocal() { + return StatCollector.translateToLocal(this.getUnlocalized()); + } + + public String getUnlocalized() { + return this.root + '.' + this.toString(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/gui/TooltipButton.java b/src/main/java/com/gtnewhorizon/structurelib/client/gui/TooltipButton.java new file mode 100644 index 00000000..bf5f4428 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/gui/TooltipButton.java @@ -0,0 +1,57 @@ +package com.gtnewhorizon.structurelib.client.gui; + +import java.util.function.BooleanSupplier; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.Tessellator; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.client.config.GuiButtonExt; + +public class TooltipButton extends GuiButtonExt { + + public final String hoverString; + private final BooleanSupplier supplier; + + public TooltipButton(int id, int xPos, int yPos, int width, int height, String displayString, String hoverString) { + this(id, xPos, yPos, width, height, displayString, hoverString, null); + } + + public TooltipButton(int id, int xPos, int yPos, int width, int height, String displayString, String hoverString, + BooleanSupplier supplier) { + super(id, xPos, yPos, width, height, displayString); + this.hoverString = hoverString; + this.supplier = supplier; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) { + if (supplier != null && !supplier.getAsBoolean()) return; + super.drawButton(mc, mouseX, mouseY); + } + + public boolean isMouseOver(int mouseX, int mouseY) { + if (supplier != null && !supplier.getAsBoolean()) return false; + return this.enabled && this.visible + && mouseX >= this.xPosition + && mouseY >= this.yPosition + && mouseX < this.xPosition + this.width + && mouseY < this.yPosition + this.height; + } + + public void drawTooltipBox(FontRenderer fontRenderer, int x, int y, int w, int h) { + GL11.glDisable(GL11.GL_TEXTURE_2D); + final Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + tessellator.setColorRGBA(33, 33, 33, 200); + tessellator.addVertex(x, y + h, 0D); + tessellator.addVertex(x + w, y + h, 0D); + tessellator.addVertex(x + w, y, 0D); + tessellator.addVertex(x, y, 0D); + tessellator.draw(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + fontRenderer.drawString(hoverString, x + 2, y + 3, 0XFFFFFF); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/GUI_MultiblockHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/GUI_MultiblockHandler.java new file mode 100644 index 00000000..4d3c4821 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/GUI_MultiblockHandler.java @@ -0,0 +1,940 @@ +package com.gtnewhorizon.structurelib.client.nei; + +import static com.gtnewhorizon.structurelib.client.world.StructureWorld.PLACE_POSITION; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IIcon; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.StatCollector; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import com.github.vfyjxf.nee.network.NEENetworkHandler; +import com.github.vfyjxf.nee.network.packet.PacketNEIPatternRecipe; +import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; +import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor; +import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility; +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.client.gui.GuiText; +import com.gtnewhorizon.structurelib.client.gui.TooltipButton; +import com.gtnewhorizon.structurelib.client.renderer.ImmediateWorldSceneRenderer; +import com.gtnewhorizon.structurelib.client.renderer.WorldSceneRenderer; +import com.gtnewhorizon.structurelib.client.world.CreativeItemSource; +import com.gtnewhorizon.structurelib.client.world.StructureFakePlayer; +import com.gtnewhorizon.structurelib.client.world.StructureWorld; +import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger; +import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment; +import com.gtnewhorizon.structurelib.structure.IStructureElement; +import com.gtnewhorizon.structurelib.util.PositionedIStructureElement; + +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.math.MathHelper; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.recipe.GuiRecipe; + +public abstract class GUI_MultiblockHandler { + + protected static ImmediateWorldSceneRenderer renderer; + + public static final int SLOT_SIZE = 18; + + protected static final int RECIPE_LAYOUT_X = 8; + protected static final int RECIPE_LAYOUT_Y = 50; + protected static final int RECIPE_WIDTH = 160; + protected static final int sceneHeight = RECIPE_WIDTH - 10; + protected static final int ICON_SIZE_X = 20; + protected static final int ICON_SIZE_Y = 12; + protected static final int MOUSE_OFFSET_X = 5; + protected static final int MOUSE_OFFSET_Y = 37; + protected static final int BUTTON_LEFT = -5; + protected static final int UNDER_PREVIEW_Y = 153; + protected static final int BUTTON_RIGHT = 145; + protected static final int BETWEEN_BUTTON_X = ICON_SIZE_X + 3; + protected static final float DEFAULT_RANGE_MULTIPLIER = 3.5f; + + protected static int guiMouseX; + protected static int guiMouseY; + protected static int lastGuiMouseX; + protected static int lastGuiMouseY; + protected static Vector3f center; + protected static float rotationYaw; + protected static float rotationPitch; + protected static float zoom; + + protected static ItemStack tooltipBlockStack; + protected static final Vector3i selectedBlock = new Vector3i(0, -1, 0); + + protected static int layerIndex = -1; + protected static int guiColorBg; + protected static int guiColorFont; + protected static int buttonColorEnabled; + protected static int buttonColorDisabled; + protected static int buttonColorHovered; + + protected static String guiTextLayer; + protected static String guiLayerButtonTitle; + protected static String guiTextTier; + protected static String guiTierButtonTitle; + protected static int initialTierButtonTitleWidth; + protected static int initialLayerButtonTitleWidth; + protected static int initialChannelTierButtonTitleWidth; + protected ClearGuiButton previousLayerButton, nextLayerButton; + protected ClearGuiButton previousTierButton, nextTierButton; + protected ClearGuiButton previousChannelButton, nextChannelButton; + protected ClearGuiButton previousChannelTier, nextChannelTier; + + protected List ingredients = new ArrayList<>(); + protected Consumer> onIngredientChanged; + protected final Map buttons = new HashMap<>(); + + protected T renderingController; + protected ItemStack stackForm; + protected T lastRenderingController; + + protected final List> candidates = new ArrayList<>(); + public static List structureElements = new ArrayList<>(); + protected Consumer>> onCandidateChanged; + protected static int tierIndex = 1; + + protected int scrolled = 0; + protected int blocksBelowController; + protected static int scaledSceneHeight = sceneHeight; + + protected int channelIndex; + protected ItemStack trigger; + public static Set channels = new HashSet<>(); + protected int[] channelTier; + protected String[] channelArray; + protected boolean useMasterChannel = true; + protected String channelTitle, channelTierTitle; + protected int lastHeight; + + public GUI_MultiblockHandler() { + previousTierButton = new ClearGuiButton(1, BUTTON_LEFT, UNDER_PREVIEW_Y, "<"); + nextTierButton = new ClearGuiButton(1, BUTTON_LEFT + ICON_SIZE_X, UNDER_PREVIEW_Y, ">"); + previousLayerButton = new ClearGuiButton(2, BUTTON_LEFT, UNDER_PREVIEW_Y + ICON_SIZE_Y, "<"); + nextLayerButton = new ClearGuiButton(2, BUTTON_LEFT + ICON_SIZE_X, UNDER_PREVIEW_Y + ICON_SIZE_Y, ">"); + previousChannelButton = new ClearGuiButton( + 4, + BUTTON_LEFT, + UNDER_PREVIEW_Y + ICON_SIZE_Y * 3, + "<", + this::hasChannels); + nextChannelButton = new ClearGuiButton( + 4, + BUTTON_LEFT + ICON_SIZE_X, + UNDER_PREVIEW_Y + ICON_SIZE_Y * 3, + ">", + this::hasChannels); + previousChannelTier = new ClearGuiButton( + 5, + BUTTON_LEFT, + UNDER_PREVIEW_Y + ICON_SIZE_Y * 4, + "<", + this::hasChannels); + nextChannelTier = new ClearGuiButton( + 5, + BUTTON_LEFT + ICON_SIZE_X, + UNDER_PREVIEW_Y + ICON_SIZE_Y * 4, + ">", + this::hasChannels); + TooltipButton projectMultiblocksButton = new TooltipButton( + 1, + BUTTON_RIGHT, + UNDER_PREVIEW_Y, + ICON_SIZE_X, + ICON_SIZE_Y, + "P", + StatCollector.translateToLocal("blockrenderer6343.multiblock.project")); + TooltipButton overlayMultiblocksButton = new TooltipButton( + 1, + BUTTON_RIGHT - BETWEEN_BUTTON_X, + UNDER_PREVIEW_Y, + ICON_SIZE_X, + ICON_SIZE_Y, + "?", + StatCollector.translateToLocal("blockrenderer6343.multiblock.overlay")); + TooltipButton copyChannelButton = new TooltipButton( + 1, + BUTTON_RIGHT - BETWEEN_BUTTON_X * 2, + UNDER_PREVIEW_Y, + ICON_SIZE_X, + ICON_SIZE_Y, + "C", + StatCollector.translateToLocal("blockrenderer6343.multiblock.copy_channels"), + this::hasChannels); + + buttons.put(previousLayerButton, this::togglePreviousLayer); + buttons.put(nextLayerButton, this::toggleNextLayer); + buttons.put(previousTierButton, this::togglePreviousTier); + buttons.put(nextTierButton, this::toggleNextTier); + buttons.put(previousChannelButton, this::togglePreviousChannel); + buttons.put(nextChannelButton, this::toggleNextChannel); + buttons.put(previousChannelTier, this::togglePreviousChannelTier); + buttons.put(nextChannelTier, this::toggleNextChannelTier); + buttons.put(projectMultiblocksButton, this::projectMultiblock); + buttons.put(overlayMultiblocksButton, this::neiOverlay); + buttons.put(copyChannelButton, this::copyToHologram); + } + + protected void setupColors() { + guiTextLayer = GuiText.Layer.getLocal(); + guiColorBg = GuiText.BgColor.getColor(); + guiColorFont = GuiText.FontColor.getColor(); + buttonColorEnabled = GuiText.ButtonEnabledColor.getColor(); + buttonColorDisabled = GuiText.ButtonDisabledColor.getColor(); + buttonColorHovered = GuiText.ButtonHoveredColor.getColor(); + } + + protected void setupButtonText() { + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + guiTextTier = GuiText.Tier.getLocal(); + refreshButtonText(); + initialLayerButtonTitleWidth = fontRenderer.getStringWidth(guiLayerButtonTitle); + initialTierButtonTitleWidth = fontRenderer.getStringWidth(guiTierButtonTitle); + if (hasChannels()) { + initialChannelTierButtonTitleWidth = fontRenderer.getStringWidth(channelTierTitle); + } + for (GuiButton button : buttons.keySet()) { + if (button instanceof ClearGuiButton clearButton) { + clearButton.setColors(buttonColorEnabled, buttonColorDisabled, buttonColorHovered); + } + } + refreshButtonPos(); + } + + protected void refreshButtonPos() { + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + nextTierButton.xPosition = BUTTON_LEFT + ICON_SIZE_X + + initialTierButtonTitleWidth + - fontRenderer.getStringWidth("<") / 2; + nextLayerButton.xPosition = BUTTON_LEFT + ICON_SIZE_X + + initialLayerButtonTitleWidth + - fontRenderer.getStringWidth("<") / 2; + nextChannelButton.xPosition = BUTTON_LEFT + ICON_SIZE_X + + fontRenderer.getStringWidth(StatCollector.translateToLocal("blockrenderer6343.nei.channel")); + nextChannelTier.xPosition = BUTTON_LEFT + ICON_SIZE_X + + initialChannelTierButtonTitleWidth + - fontRenderer.getStringWidth("<") / 2; + for (GuiButton b : buttons.keySet()) { + if (b instanceof TooltipButton tooltipButton) { + tooltipButton.yPosition = scaledSceneHeight + ICON_SIZE_Y; + continue; + } + b.yPosition = scaledSceneHeight + ICON_SIZE_Y * b.id; + } + } + + public void refreshButtonText() { + guiLayerButtonTitle = getLayerButtonTitle(); + guiTierButtonTitle = getTierButtonTitle(); + if (hasChannels()) { + channelTitle = getChannelTitle(); + channelTierTitle = getChannelTierTitle(); + } + } + + public void loadMultiblock(T multiblock, ItemStack stackForm) { + setupColors(); + renderingController = multiblock; + this.stackForm = stackForm; + if (stackForm.stackSize == 0) stackForm.stackSize = 1; + if (lastRenderingController != renderingController) { + loadNewMultiblock(); + } else { + loadPreviousMultiblockAgain(); + } + setupButtonText(); + } + + protected void loadNewMultiblock() { + trigger = getOriginalTriggerStack(); + channels.clear(); + layerIndex = -1; + tierIndex = 1; + channelIndex = 0; + initializeSceneRenderer(true); + lastRenderingController = renderingController; + channelTier = new int[channels.size()]; + Arrays.fill(channelTier, 1); + if (hasChannels()) { + channelArray = channels.toArray(new String[0]); + } + } + + protected void loadPreviousMultiblockAgain() { + initializeSceneRenderer(false); + } + + public void setOnIngredientChanged(Consumer> callback) { + onIngredientChanged = callback; + } + + private void toggleNextLayer() { + int height = (int) renderer.world.getSize().y - 1; + if (++layerIndex > height) { + // if current layer index is more than max height, reset it + // to display all layers + layerIndex = -1; + } + setNextLayer(layerIndex); + refreshButtonText(); + } + + private void togglePreviousLayer() { + int height = (int) renderer.world.getSize().y - 1; + if (layerIndex == -1) { + layerIndex = height; + } else if (--layerIndex < 0) { + layerIndex = -1; + } + setNextLayer(layerIndex); + refreshButtonText(); + } + + private void toggleNextChannel() { + if (!hasChannels()) return; + if (++channelIndex >= channels.size()) { + channelIndex = 0; + } + useMasterChannel = false; + initializeSceneRenderer(false); + refreshButtonText(); + } + + private void togglePreviousChannel() { + if (!hasChannels()) return; + if (--channelIndex < 0) { + channelIndex = channels.size() - 1; + } + useMasterChannel = false; + initializeSceneRenderer(false); + refreshButtonText(); + } + + private void toggleNextChannelTier() { + if (!hasChannels()) return; + channelTier[channelIndex] += 1; + useMasterChannel = false; + ChannelDataAccessor.setChannelData(trigger, channelArray[channelIndex], channelTier[channelIndex]); + initializeSceneRenderer(false); + refreshButtonText(); + } + + private void togglePreviousChannelTier() { + if (!hasChannels()) return; + channelTier[channelIndex] -= 1; + if (channelTier[channelIndex] == 0) { + channelTier[channelIndex] = 1; + refreshButtonText(); + return; + } + useMasterChannel = false; + ChannelDataAccessor.setChannelData(trigger, channelArray[channelIndex], channelTier[channelIndex]); + initializeSceneRenderer(false); + refreshButtonText(); + } + + protected void toggleNextTier() { + tierIndex++; + useMasterChannel = true; + initializeSceneRenderer(false); + refreshButtonText(); + } + + protected void togglePreviousTier() { + if (tierIndex > 1) { + useMasterChannel = true; + tierIndex--; + initializeSceneRenderer(false); + refreshButtonText(); + } + } + + protected boolean hasChannels() { + return !channels.isEmpty(); + } + + private void setNextLayer(int newLayer) { + layerIndex = newLayer; + if (renderer != null) { + StructureWorld world = renderer.world; + resetCenter(); + renderer.clearRenderedBlocks(); + int minY = (int) world.getMinPos().y; + if (newLayer == -1) { + renderer.addRenderedBlocks(world.placedBlocks); + renderer.setRenderAllFaces(false); + } else { + world.placedBlocks.longStream().filter(pos -> CoordinatePacker.unpackY(pos) - minY == newLayer) + .forEach(pos -> renderer.addRenderedBlock(pos)); + renderer.setRenderAllFaces(true); + } + + scanIngredients(); + } + } + + private void resetCenter() { + StructureWorld world = renderer.world; + org.joml.Vector3f size = world.getSize(); + Vector3f minPos = world.getMinPos(); + center = new Vector3f(minPos.x + size.x / 2, minPos.y + size.y / 2, minPos.z + size.z / 2); + renderer.setCameraLookAt(center, zoom, Math.toRadians(rotationPitch), Math.toRadians(rotationYaw)); + } + + public void drawMultiblock() { + guiMouseX = GuiDraw.getMousePosition().x; + guiMouseY = GuiDraw.getMousePosition().y; + int guiLeft = NEIClientUtils.getGuiContainer().guiLeft; + int guiTop = NEIClientUtils.getGuiContainer().guiTop; + + int guiHeight = NEIClientUtils.getGuiContainer().height; + if (guiHeight != lastHeight) { + scaledSceneHeight = Math.min(sceneHeight, sceneHeight * guiHeight / 500); + refreshButtonPos(); + } + renderer.render( + RECIPE_LAYOUT_X + guiLeft, + RECIPE_LAYOUT_Y + guiTop, + RECIPE_WIDTH, + scaledSceneHeight, + lastGuiMouseX, + lastGuiMouseY); + drawMultiblockName(); + + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + + tooltipBlockStack = null; + + MovingObjectPosition rayTraceResult = renderer.getLastTraceResult(); + boolean insideView = guiMouseX >= guiLeft + RECIPE_LAYOUT_X && guiMouseY >= guiTop + RECIPE_LAYOUT_Y + && guiMouseX < guiLeft + RECIPE_LAYOUT_X + RECIPE_WIDTH + && guiMouseY < guiTop + RECIPE_LAYOUT_Y + sceneHeight; + boolean leftClickHeld = Mouse.isButtonDown(0); + boolean rightClickHeld = Mouse.isButtonDown(1); + boolean middleClickHeld = Mouse.isButtonDown(2); + + if (insideView) { + if (leftClickHeld) { + rotationPitch += guiMouseX - lastGuiMouseX + 360; + rotationPitch = rotationPitch % 360; + rotationYaw = (float) MathHelper.clip(rotationYaw + (guiMouseY - lastGuiMouseY), -89.9, 89.9); + } else if (rightClickHeld) { + int mouseDeltaY = guiMouseY - lastGuiMouseY; + if (Math.abs(mouseDeltaY) > 1) { + zoom = (float) MathHelper.clip(zoom + (mouseDeltaY > 0 ? 0.15 : -0.15), 3, 999); + } + } + if (middleClickHeld) { + int mouseDeltaX = guiMouseX - lastGuiMouseX; + int mouseDeltaY = guiMouseY - lastGuiMouseY; + // generated by copilot + Vector3f lookAt = renderer.getLookAt(); + Vector3f eyePos = renderer.getEyePos(); + Vector3f worldUp = renderer.getWorldUp(); + Vector3f lookDir = lookAt.sub(eyePos, new Vector3f()); + Vector3f rightDir = lookDir.cross(worldUp, new Vector3f()); + rightDir.normalize(); + Vector3f upDir = rightDir.cross(lookDir, new Vector3f()); + upDir.normalize(); + Vector3f offset = new Vector3f( + -mouseDeltaX * rightDir.x + mouseDeltaY * upDir.x, + -mouseDeltaX * rightDir.y + mouseDeltaY * upDir.y, + -mouseDeltaX * rightDir.z + mouseDeltaY * upDir.z); + offset.mul(0.15f); + center.add(offset); + } + if (scrolled != 0) { + zoom = (float) MathHelper.clip(zoom - scrolled * 5, 3, 999); + scrolled = 0; + } + + renderer.setCameraLookAt(center, zoom, Math.toRadians(rotationPitch), Math.toRadians(rotationYaw)); + } + + // draw buttons + int actualMouseX = guiMouseX - guiLeft - MOUSE_OFFSET_X; + int actualMouseY = guiMouseY - guiTop - MOUSE_OFFSET_Y; + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + for (GuiButton button : buttons.keySet()) { + button.drawButton(Minecraft.getMinecraft(), actualMouseX, actualMouseY); + } + drawButtonsTitle(fontRenderer); + for (GuiButton button : buttons.keySet()) { + if (button instanceof TooltipButton tooltipButton + && tooltipButton.isMouseOver(actualMouseX, actualMouseY)) { + int textWidth = fontRenderer.getStringWidth(tooltipButton.hoverString); + tooltipButton.drawTooltipBox( + fontRenderer, + actualMouseX - 3, + actualMouseY - 17, + textWidth + 3, + tooltipButton.height); + } + } + if (!(leftClickHeld || rightClickHeld) && rayTraceResult != null + && !renderer.world.isAirBlock(rayTraceResult.blockX, rayTraceResult.blockY, rayTraceResult.blockZ)) { + Block block = renderer.world.getBlock(rayTraceResult.blockX, rayTraceResult.blockY, rayTraceResult.blockZ); + tooltipBlockStack = block.getPickBlock( + rayTraceResult, + renderer.world, + rayTraceResult.blockX, + rayTraceResult.blockY, + rayTraceResult.blockZ, + Minecraft.getMinecraft().thePlayer); + } + + lastHeight = guiHeight; + lastGuiMouseX = guiMouseX; + lastGuiMouseY = guiMouseY; + + // don't activate these + // GlStateManager.disableRescaleNormal(); + // GlStateManager.disableLighting(); + // RenderHelper.disableStandardItemLighting(); + } + + protected String getMultiblockName() { + return I18n.format(stackForm.getDisplayName()); + } + + private void drawMultiblockName() { + String localizedName = getMultiblockName(); + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + List lines = fontRenderer.listFormattedStringToWidth(localizedName, RECIPE_WIDTH - 10); + for (int i = 0; i < lines.size(); i++) { + fontRenderer.drawString( + lines.get(i), + (RECIPE_WIDTH - fontRenderer.getStringWidth(lines.get(i))) / 2, + fontRenderer.FONT_HEIGHT * i, + guiColorFont); + } + } + + protected void drawButtonsTitle(FontRenderer fontRenderer) { + fontRenderer.drawString( + guiTierButtonTitle, + BUTTON_LEFT + ICON_SIZE_X + + (initialTierButtonTitleWidth - fontRenderer.getStringWidth(guiTierButtonTitle)) / 2, + scaledSceneHeight + ICON_SIZE_Y + 2, + guiColorFont); + fontRenderer.drawString( + guiLayerButtonTitle, + BUTTON_LEFT + ICON_SIZE_X + + (initialLayerButtonTitleWidth - fontRenderer.getStringWidth(guiLayerButtonTitle)) / 2, + scaledSceneHeight + (ICON_SIZE_Y * 2) + 2, + guiColorFont); + if (hasChannels()) { + fontRenderer.drawString( + StatCollector.translateToLocal("blockrenderer6343.nei.current_channel") + ": " + channelTitle, + BUTTON_LEFT + 6, + scaledSceneHeight + (ICON_SIZE_Y * 3) + 2, + guiColorFont); + fontRenderer.drawString( + StatCollector.translateToLocal("blockrenderer6343.nei.channel"), + BUTTON_LEFT + ICON_SIZE_X, + scaledSceneHeight + (ICON_SIZE_Y * 4) + 2, + guiColorFont); + fontRenderer.drawString( + channelTierTitle, + BUTTON_LEFT + ICON_SIZE_X + + (initialChannelTierButtonTitleWidth - fontRenderer.getStringWidth(channelTierTitle)) / 2, + scaledSceneHeight + (ICON_SIZE_Y * 5) + 2, + guiColorFont); + } + } + + protected void initializeSceneRenderer(boolean resetCamera) { + Vector3f eyePos = new Vector3f(), lookAt = new Vector3f(), worldUp = new Vector3f(); + if (!resetCamera) { + try { + eyePos = renderer.getEyePos(); + lookAt = renderer.getLookAt(); + worldUp = renderer.getWorldUp(); + } catch (NullPointerException e) { + StructureLib.LOGGER.error("please reset camera on your first renderer call!"); + } + } + + renderer = new ImmediateWorldSceneRenderer(new StructureWorld()); + renderer.world.updateEntitiesForNEI(); + renderer.setClearColor(guiColorBg); + + renderer.world.placeMultiBlock(stackForm); + + Vector3f size = renderer.world.getSize(); + Vector3f minPos = renderer.world.getMinPos(); + center = new Vector3f(minPos.x + size.x / 2, minPos.y + size.y / 2, minPos.z + size.z / 2); + + renderer.getRenderedBlocks().clear(); + renderer.addRenderedBlocks(renderer.world.placedBlocks); + renderer.setOnLookingAt(ray -> {}); + + renderer.setOnWorldRender(this::onRendererRender); + + blocksBelowController = MathHelper.floor_double(PLACE_POSITION.y - minPos.y); + selectedBlock.set(0, -1, 0); + onBlockSelected(); + setNextLayer(layerIndex); + + if (resetCamera) { + float max = Math.max(Math.max(size.x, size.y), size.z); + float baseZoom = (float) (DEFAULT_RANGE_MULTIPLIER * Math.sqrt(max)); + float sizeFactor = (float) (1.0f + Math.log(max) / Math.log(10)); + + zoom = baseZoom * sizeFactor / 1.5f; + rotationYaw = 20.0f; + rotationPitch = 50f; + if (renderer != null) { + resetCenter(); + } + } else { + renderer.setCameraLookAt(eyePos, lookAt, worldUp); + } + } + + public void handleMouseScrollUp(int scrolled) { + this.scrolled = scrolled; + } + + protected String getTierButtonTitle() { + return guiTextTier + ": " + tierIndex; + } + + protected String getChannelTitle() { + return useMasterChannel ? "All" : firstUpper(channelArray[channelIndex]); + } + + protected String getChannelTierTitle() { + return StatCollector.translateToLocal("blockrenderer6343.nei.channel_tier") + ": " + channelTier[channelIndex]; + } + + protected String getLayerButtonTitle() { + return guiTextLayer + ": " + (layerIndex == -1 ? "A" : Integer.toString(layerIndex + 1)); + } + + private final Vector3i lastLookPos = new Vector3i(0, -1, 0); + + public void onRendererRender(WorldSceneRenderer renderer) { + final MovingObjectPosition lookingPos = renderer.getLastTraceResult(); + if (lookingPos != null) { + lastLookPos.set(lookingPos.blockX, lookingPos.blockY, lookingPos.blockZ); + } else { + lastLookPos.set(0, -1, 0); + } + + if (lastLookPos.y >= 0 && lastLookPos.equals(selectedBlock)) { + renderBlockOverLay(selectedBlock, Blocks.glass.getIcon(0, 6)); + return; + } + renderBlockOverLay(lastLookPos, Blocks.stained_glass.getIcon(0, 7)); + renderBlockOverLay(selectedBlock, Blocks.stained_glass.getIcon(0, 14)); + } + + protected void projectMultiblock() { + EntityPlayer player = Minecraft.getMinecraft().thePlayer; + World baseWorld = Minecraft.getMinecraft().theWorld; + Vec3 lookVec = player.getLookVec(); + MovingObjectPosition lookingPos = player.rayTrace(10, 1); + int blockX, blockY, blockZ; + if (lookingPos == null || lookingPos.typeOfHit == MovingObjectPosition.MovingObjectType.MISS) { + blockX = MathHelper.floor_double(player.posX + lookVec.xCoord * 2); + blockZ = MathHelper.floor_double(player.posZ + lookVec.zCoord * 2); + blockY = baseWorld.getPrecipitationHeight(blockX, blockZ) + blocksBelowController; + } else { + blockX = lookingPos.blockX; + blockY = lookingPos.blockY + blocksBelowController + 1; + blockZ = lookingPos.blockZ; + } + ItemStack copy = stackForm.copy(); + + if (!baseWorld.isAirBlock(blockX, blockY, blockZ)) { + player.addChatMessage(new ChatComponentText(StatCollector.translateToLocal("blockrenderer6343.no_space"))); + return; + } + + if (!copy.getItem().onItemUse(copy, player, baseWorld, blockX, blockY, blockZ, 0, blockX, blockY - 1, blockZ)) { + player.addChatMessage(new ChatComponentText(StatCollector.translateToLocal("blockrenderer6343.no_block"))); + return; + } + ConstructableUtility.handle(getBuildTriggerStack(), player, baseWorld, blockX, blockY, blockZ, 0); + baseWorld.setBlockToAir(blockX, blockY, blockZ); + baseWorld.removeTileEntity(blockX, blockY, blockZ); + } + + protected void neiOverlay() { + if (!StructureLib.isNEELoaded) return; + NBTTagCompound recipeInputs = new NBTTagCompound(); + GuiRecipe currentScreen = (GuiRecipe) Minecraft.getMinecraft().currentScreen; + Minecraft.getMinecraft().displayGuiScreen(currentScreen.firstGui); + for (int i = 0; i < ingredients.size(); i++) { + ItemStack itemStack = ingredients.get(i); + if (itemStack != null) { + NBTTagCompound itemStackNBT = new NBTTagCompound(); + itemStack.writeToNBT(itemStackNBT); + itemStackNBT.setInteger("Count", itemStack.stackSize); + recipeInputs.setTag("#" + i, itemStackNBT); + } + } + NEENetworkHandler.getInstance().sendToServer(new PacketNEIPatternRecipe(recipeInputs, new NBTTagCompound())); + } + + protected void copyToHologram() { + if (!hasChannels()) return; + EntityPlayer player = Minecraft.getMinecraft().thePlayer; + ItemStack stack = player.getHeldItem(); + if (stack == null || !(stack.getItem() instanceof ItemConstructableTrigger)) { + player.addChatMessage( + new ChatComponentText(StatCollector.translateToLocal("blockrenderer6343.no_projector"))); + } else { + StructureLib.instance().proxy().uploadChannels(trigger); + } + } + + private void scanIngredients() { + List ingredients = new ArrayList<>(); + Vector3i renderedBlock = new Vector3i(); + for (final long longPos : renderer.getRenderedBlocks()) { + CoordinatePacker.unpack(longPos, renderedBlock); + + Block block = renderer.world.getBlock(renderedBlock.x, renderedBlock.y, renderedBlock.z); + if (block.equals(Blocks.air)) continue; + int meta = renderer.world.getBlockMetadata(renderedBlock.x, renderedBlock.y, renderedBlock.z); + int qty = block.quantityDropped(renderer.world.rand); + ArrayList itemstacks = new ArrayList<>(); + if (qty != 1) { + itemstacks.add(new ItemStack(block)); + } else { + itemstacks = block.getDrops(renderer.world, renderedBlock.x, renderedBlock.y, renderedBlock.z, meta, 0); + } + boolean added = false; + for (ItemStack ingredient : ingredients) { + if (NEIClientUtils.areStacksSameTypeWithNBT(ingredient, itemstacks.get(0))) { + ingredient.stackSize++; + added = true; + break; + } + } + if (!added) ingredients.add(itemstacks.get(0)); + } + this.ingredients = ingredients; + + if (onIngredientChanged != null) { + onIngredientChanged.accept(ingredients); + } + } + + private void renderBlockOverLay(Vector3i pos, IIcon icon) { + if (pos.y < 0) return; + + RenderBlocks bufferBuilder = new RenderBlocks(); + bufferBuilder.blockAccess = renderer.world; + bufferBuilder.setRenderBounds(0, 0, 0, 1, 1, 1); + bufferBuilder.renderAllFaces = true; + Block block = renderer.world.getBlock(pos.x, pos.y, pos.z); + bufferBuilder.renderBlockUsingTexture(block, pos.x, pos.y, pos.z, icon); + } + + public boolean mouseClicked(int button) { + for (Map.Entry buttons : buttons.entrySet()) { + int guiLeft = NEIClientUtils.getGuiContainer().guiLeft; + int guiTop = NEIClientUtils.getGuiContainer().guiTop; + if (buttons.getKey().mousePressed( + Minecraft.getMinecraft(), + guiMouseX - guiLeft - MOUSE_OFFSET_X, + guiMouseY - guiTop - MOUSE_OFFSET_Y)) { + buttons.getValue().run(); + selectedBlock.set(0, -1, 0); + onBlockSelected(); + return true; + } + } + if (button == 1 && renderer != null) { + final MovingObjectPosition lookingPos = renderer.getLastTraceResult(); + if (lookingPos == null) { + if (selectedBlock.y >= 0) { + selectedBlock.set(0, -1, 0); + onBlockSelected(); + return true; + } + return false; + } + selectedBlock.set(lookingPos.blockX, lookingPos.blockY, lookingPos.blockZ); + onBlockSelected(); + } + return false; + } + + @NotNull + protected static ItemStack getOriginalTriggerStack() { + return new ItemStack(StructureLibAPI.getDefaultHologramItem(), tierIndex); + } + + @NotNull + protected ItemStack getBuildTriggerStack() { + return useMasterChannel ? getOriginalTriggerStack() : trigger; + } + + protected void scanCandidates() { + candidates.clear(); + if (selectedBlock.y < 0) { + if (onCandidateChanged != null) { + onCandidateChanged.accept(candidates); + } + return; + } + final StructureFakePlayer builder = renderer.world.getFakeMultiblockBuilder(); + for (PositionedIStructureElement structureElement : structureElements) { + if (structureElement.x == selectedBlock.x && structureElement.y == selectedBlock.y + && structureElement.z == selectedBlock.z) { + + IStructureElement.BlocksToPlace blocksToPlace = structureElement.element.getBlocksToPlace( + (IConstructable) renderingController, + renderer.world, + selectedBlock.x, + selectedBlock.y, + selectedBlock.z, + getOriginalTriggerStack(), + AutoPlaceEnvironment.fromLegacy(CreativeItemSource.instance, builder, iChatComponent -> {})); + if (blocksToPlace != null) { + Predicate predicate = blocksToPlace.getPredicate(); + Set rawCandidates = CreativeItemSource.instance + .takeEverythingMatches(predicate, false, 0).keySet(); + + List> stackedCandidates = new ArrayList<>(); + for (ItemStack rawCandidate : rawCandidates) { + boolean added = false; + for (List stackedCandidate : stackedCandidates) { + List firstCandidateTooltip = stackedCandidate.get(0).getTooltip(builder, false); + List rawCandidateTooltip = rawCandidate.getTooltip(builder, false); + if (firstCandidateTooltip.size() > 1 && rawCandidateTooltip.size() > 1 + && firstCandidateTooltip.get(1).equals(rawCandidateTooltip.get(1))) { + stackedCandidate.add(rawCandidate); + added = true; + break; + } + } + if (!added) { + List newStackedCandidate = new ArrayList<>(); + newStackedCandidate.add(rawCandidate); + stackedCandidates.add(newStackedCandidate); + } + } + + candidates.addAll(stackedCandidates); + + if (onCandidateChanged != null) { + onCandidateChanged.accept(candidates); + } + } + return; + } + } + if (onCandidateChanged != null) { + onCandidateChanged.accept(candidates); + } + } + + protected void onBlockSelected() { + scanCandidates(); + }; + + public void setOnCandidateChanged(Consumer>> callback) { + onCandidateChanged = callback; + } + + public List handleTooltip() { + if (tooltipBlockStack != null) { + return tooltipBlockStack.getTooltip( + Minecraft.getMinecraft().thePlayer, + Minecraft.getMinecraft().gameSettings.advancedItemTooltips); + } else { + return null; + } + } + + public String firstUpper(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + protected static class ClearGuiButton extends GuiButton { + + private int colorEnabled; + private int colorDisabled; + private int colorHovered; + private final BooleanSupplier booleanSupplier; + + public ClearGuiButton(int id, int x, int y, String displayString) { + this(id, x, y, displayString, null); + } + + public ClearGuiButton(int id, int x, int y, String displayString, BooleanSupplier booleanSupplier) { + super(id, x, y, ICON_SIZE_X, ICON_SIZE_Y, displayString); + this.booleanSupplier = booleanSupplier; + } + + public void setColors(int clrEnabled, int clrDisabled, int clrHovered) { + colorEnabled = clrEnabled; + colorDisabled = clrDisabled; + colorHovered = clrHovered; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) { + if (this.visible) { + if (booleanSupplier != null && !booleanSupplier.getAsBoolean()) return; + + FontRenderer fontrenderer = mc.fontRenderer; + this.field_146123_n = mouseX >= this.xPosition && mouseY >= this.yPosition + && mouseX < this.xPosition + this.width + && mouseY < this.yPosition + this.height; + int l = colorEnabled; + + if (packedFGColour != 0) { + l = packedFGColour; + } else if (!this.enabled) { + l = colorDisabled; + } else if (this.field_146123_n) { + l = colorHovered; + } + + this.drawCenteredString( + fontrenderer, + this.displayString, + this.xPosition + this.width / 2, + this.yPosition + (this.height - 8) / 2, + l); + } + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/IMCForNEI.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/IMCForNEI.java new file mode 100644 index 00000000..8ed30926 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/IMCForNEI.java @@ -0,0 +1,48 @@ +package com.gtnewhorizon.structurelib.client.nei; + +import net.minecraft.nbt.NBTTagCompound; + +import cpw.mods.fml.common.event.FMLInterModComms; + +public class IMCForNEI { + + public static final String GT_NEI_MB_HANDLER_NAME = "gregtech.nei.multiblockhandler"; + public static final String STRUCTURE_LIB_HANDLER = "structurelib.neiHandler"; + + public static void IMCSender() { + + sendHandler( + GT_NEI_MB_HANDLER_NAME, + "GregTech", + "gregtech", + "structurelib:item.structurelib.constructableTrigger", + 168, + 192, + 1, + 6); + sendHandler( + STRUCTURE_LIB_HANDLER, + "StructureLib", + "structurelib", + "structurelib:item.structurelib.constructableTrigger", + 168, + 192, + 1, + 6); + } + + private static void sendHandler(String aName, String modName, String modID, String aBlock, int width, int height, + int maxrecipesperpage, int yshift) { + NBTTagCompound aNBT = new NBTTagCompound(); + aNBT.setString("handler", aName); + aNBT.setString("modName", modName); + aNBT.setString("modId", modID); + aNBT.setBoolean("modRequired", true); + aNBT.setString("itemName", aBlock); + aNBT.setInteger("handlerHeight", height); + aNBT.setInteger("handlerWidth", width); + aNBT.setInteger("maxRecipesPerPage", maxrecipesperpage); + aNBT.setInteger("yShift", yshift); + FMLInterModComms.sendMessage("NotEnoughItems", "registerHandlerInfo", aNBT); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/InputHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/InputHandler.java new file mode 100644 index 00000000..ee8664e7 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/InputHandler.java @@ -0,0 +1,89 @@ +package com.gtnewhorizon.structurelib.client.nei; + +import java.util.List; + +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.item.ItemStack; + +import codechicken.nei.guihook.IContainerInputHandler; +import codechicken.nei.guihook.IContainerTooltipHandler; +import codechicken.nei.recipe.GuiCraftingRecipe; +import codechicken.nei.recipe.GuiRecipe; +import codechicken.nei.recipe.GuiUsageRecipe; + +public class InputHandler implements IContainerInputHandler, IContainerTooltipHandler { + + private MultiblockHandler activeHandler; + + public boolean canHandle(GuiContainer gui) { + if ((gui instanceof GuiUsageRecipe || gui instanceof GuiCraftingRecipe)) { + GuiRecipe recipe = (GuiRecipe) gui; + if (recipe.getHandler() instanceof MultiblockHandler mbHandler) { + activeHandler = mbHandler; + return true; + } + } + return false; + } + + @Override + public boolean mouseClicked(GuiContainer gui, int mousex, int mousey, int button) { + if (canHandle(gui)) { + return activeHandler.getGuiHandler().mouseClicked(button); + } + return false; + } + + @Override + public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) { + return false; + } + + @Override + public List handleTooltip(GuiContainer gui, int mousex, int mousey, List currenttip) { + if (canHandle(gui) && activeHandler.getGuiHandler().handleTooltip() != null) { + currenttip.addAll(activeHandler.getGuiHandler().handleTooltip()); + } + return currenttip; + } + + @Override + public List handleItemDisplayName(GuiContainer gui, ItemStack itemstack, List currenttip) { + return currenttip; + } + + @Override + public List handleItemTooltip(GuiContainer gui, ItemStack itemstack, int mousex, int mousey, + List currenttip) { + return currenttip; + } + + @Override + public boolean keyTyped(GuiContainer gui, char keyChar, int keyCode) { + return false; + } + + @Override + public void onKeyTyped(GuiContainer gui, char keyChar, int keyID) {} + + @Override + public void onMouseClicked(GuiContainer gui, int mousex, int mousey, int button) {} + + @Override + public void onMouseUp(GuiContainer gui, int mousex, int mousey, int button) {} + + @Override + public boolean mouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) { + return false; + } + + @Override + public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) { + if (canHandle(gui)) { + activeHandler.getGuiHandler().handleMouseScrollUp(scrolled); + } + } + + @Override + public void onMouseDragged(GuiContainer gui, int amousex, int amousey, int button, long heldTime) {} +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/MultiblockHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/MultiblockHandler.java new file mode 100644 index 00000000..1a6b9fbe --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/MultiblockHandler.java @@ -0,0 +1,131 @@ +package com.gtnewhorizon.structurelib.client.nei; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.StatCollector; + +import codechicken.nei.PositionedStack; +import codechicken.nei.recipe.GuiRecipeCatalyst; +import codechicken.nei.recipe.RecipeCatalysts; +import codechicken.nei.recipe.TemplateRecipeHandler; + +public abstract class MultiblockHandler extends TemplateRecipeHandler { + + public static final int CANDIDATE_SLOTS_X = 150; + public static final int CANDIDATE_SLOTS_Y = 20; + public static final int CANDIDATE_IN_COlUMN = 6; + + protected List ingredients = new ArrayList<>(); + protected final List positionedIngredients = new ArrayList<>(); + protected int lastRecipeHeight; + protected RecipeCacher recipeCacher = new RecipeCacher(); + protected GUI_MultiblockHandler guiHandler; + + public MultiblockHandler(GUI_MultiblockHandler guiHandler) { + super(); + this.guiHandler = guiHandler; + } + + @Override + public void loadCraftingRecipes(String outputId, Object... results) { + super.loadCraftingRecipes(outputId, results); + } + + @Override + public void loadCraftingRecipes(ItemStack result) { + tryLoadingMultiblock(result); + super.loadCraftingRecipes(result); + } + + @Override + public void loadUsageRecipes(ItemStack ingredient) { + tryLoadingMultiblock(ingredient); + super.loadUsageRecipes(ingredient); + } + + @Override + public void drawBackground(int recipe) { + super.drawBackground(recipe); + guiHandler.drawMultiblock(); + + if (lastRecipeHeight != RecipeCatalysts.getHeight()) { + resetPositionedIngredients(); + lastRecipeHeight = RecipeCatalysts.getHeight(); + } + } + + @Override + public String getGuiTexture() { + return "structurelib:textures/void.png"; + } + + @Override + public String getRecipeName() { + return StatCollector.translateToLocal("blockrenderer6343.multiblock.structure"); + } + + protected abstract void tryLoadingMultiblock(ItemStack candidate); + + protected GUI_MultiblockHandler getGuiHandler() { + return guiHandler; + } + + public void resetPositionedIngredients() { + positionedIngredients.clear(); + int rowCount = RecipeCatalysts.getRowCount(RecipeCatalysts.getHeight(), ingredients.size()); + + for (int index = 0; index < ingredients.size(); index++) { + ItemStack catalyst = ingredients.get(index); + int column = index / rowCount; + int row = index % rowCount; + positionedIngredients.add( + new PositionedStack( + catalyst, + -column * GuiRecipeCatalyst.ingredientSize, + row * GuiRecipeCatalyst.ingredientSize)); + } + + Map> catalystMap = RecipeCatalysts.getPositionedRecipeCatalystMap(); + catalystMap.put(getOverlayIdentifier(), positionedIngredients); + } + + public void setResults(List> results) { + arecipes.clear(); + recipeCacher.setResults(results); + arecipes.add(recipeCacher); + } + + public class RecipeCacher extends CachedRecipe { + + private final List positionedResults = new ArrayList<>(); + + public void setResults(List> results) { + positionedResults.clear(); + int columnCount = results.size() / CANDIDATE_IN_COlUMN + 1; + int realCandidateInColumn = results.size() % columnCount == 0 ? results.size() / columnCount + : results.size() / columnCount + 1; + for (int i = 0; i < results.size(); i++) { + PositionedStack result = new PositionedStack( + results.get(i), + CANDIDATE_SLOTS_X - (columnCount - 1) * GUI_MultiblockHandler.SLOT_SIZE + + (i / realCandidateInColumn) * GUI_MultiblockHandler.SLOT_SIZE, + CANDIDATE_SLOTS_Y + (i % realCandidateInColumn) * GUI_MultiblockHandler.SLOT_SIZE); + result.generatePermutations(); + positionedResults.add(result); + } + } + + @Override + public PositionedStack getResult() { + return null; + } + + @Override + public List getOtherStacks() { + return getCycledIngredients(cycleticks / 20, positionedResults); + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/NEI_Config.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/NEI_Config.java new file mode 100644 index 00000000..b4631747 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/NEI_Config.java @@ -0,0 +1,43 @@ +package com.gtnewhorizon.structurelib.client.nei; + +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; +import com.gtnewhorizon.structurelib.Tags; +import com.gtnewhorizon.structurelib.client.nei.integration.gregtech.GT_NEI_MultiblockHandler; +import com.gtnewhorizon.structurelib.client.nei.integration.structurelib.StructureCompatNEIHandler; + +import codechicken.nei.api.IConfigureNEI; +import codechicken.nei.recipe.GuiCraftingRecipe; +import codechicken.nei.recipe.GuiUsageRecipe; +import codechicken.nei.recipe.TemplateRecipeHandler; + +@SuppressWarnings("unused") +public class NEI_Config implements IConfigureNEI { + + public static boolean isAdded = true; + + @Override + public void loadConfig() { + isAdded = false; + TemplateRecipeHandler handler = new StructureCompatNEIHandler(); + GuiCraftingRecipe.craftinghandlers.add(handler); + GuiUsageRecipe.usagehandlers.add(handler); + + if (StructureLib.isGTLoaded) { + handler = new GT_NEI_MultiblockHandler(); + GuiCraftingRecipe.craftinghandlers.add(handler); + GuiUsageRecipe.usagehandlers.add(handler); + } + isAdded = true; + } + + @Override + public String getName() { + return StructureLibAPI.MOD_ID + " NEI Plugin"; + } + + @Override + public String getVersion() { + return Tags.VERSION; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_GUI_MultiblockHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_GUI_MultiblockHandler.java new file mode 100644 index 00000000..e6d998d1 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_GUI_MultiblockHandler.java @@ -0,0 +1,71 @@ +package com.gtnewhorizon.structurelib.client.nei.integration.gregtech; + +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.client.nei.GUI_MultiblockHandler; + +public class GT_GUI_MultiblockHandler extends GUI_MultiblockHandler { + + public GT_GUI_MultiblockHandler() { + super(); + } + + // spotless:off +// TODO: Move this into a non GUI multi block handler class +// @Override +// protected void placeMultiblock() { +// if (GT_Runnable_MachineBlockUpdate.isCurrentThreadEnabled()) { +// GT_Runnable_MachineBlockUpdate.setCurrentThreadEnabled(false); +// } +// +// IConstructable constructable = null; +// ItemStack copy = stackForm.copy(); +// copy.getItem().onItemUse( +// copy, +// renderer.world.getFakeMultiblockBuilder(), +// renderer.world, +// PLACE_POSITION.x, +// PLACE_POSITION.y, +// PLACE_POSITION.z, +// 0, +// PLACE_POSITION.x, +// PLACE_POSITION.y, +// PLACE_POSITION.z); +// +// TileEntity tTileEntity = renderer.world.getTileEntity(PLACE_POSITION.x, PLACE_POSITION.y, PLACE_POSITION.z); +// ((ITurnable) tTileEntity).setFrontFacing(ForgeDirection.SOUTH); +// IMetaTileEntity mte = ((IGregTechTileEntity) tTileEntity).getMetaTileEntity(); +// +// if (!StructureLibAPI.isInstrumentEnabled()) { +// StructureLibAPI.enableInstrument(StructureLibAPI.MOD_ID); +// } +// structureElements.clear(); +// +// This seems relevant because the .construct() call ignores hatches, etc. Needs an optimized CreativeItemSource +// if (mte instanceof ISurvivalConstructable survivalConstructable) { +// int result, iterations = 0; +// do { +// result = survivalConstructable.survivalConstruct( +// getBuildTriggerStack(), +// Integer.MAX_VALUE, +// ISurvivalBuildEnvironment.create(CreativeItemSource.instance, renderer.world.getFakeMultiblockBuilder())); +// iterations++; +// } while (result > 0 && iterations < MAX_PLACE_ROUNDS); +// } else if (tTileEntity instanceof IConstructableProvider iConstructableProvider) { +// constructable = iConstructableProvider.getConstructable(); +// } else if (tTileEntity instanceof IConstructable iConstructable) { +// constructable = iConstructable; +// } +// if (constructable != null) { +// constructable.construct(getBuildTriggerStack(), false); +// } +// +// if (StructureLibAPI.isInstrumentEnabled()) { +// StructureLibAPI.disableInstrument(); +// } +// +// if (!GT_Runnable_MachineBlockUpdate.isCurrentThreadEnabled()) { +// GT_Runnable_MachineBlockUpdate.setCurrentThreadEnabled(true); +// } +// } +// spotless:on +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_NEI_MultiblockHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_NEI_MultiblockHandler.java new file mode 100644 index 00000000..7e5ddef2 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/gregtech/GT_NEI_MultiblockHandler.java @@ -0,0 +1,57 @@ +package com.gtnewhorizon.structurelib.client.nei.integration.gregtech; + +import static com.gtnewhorizon.structurelib.client.nei.IMCForNEI.GT_NEI_MB_HANDLER_NAME; +import static gregtech.api.GregTech_API.METATILEENTITIES; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.ItemStack; + +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.client.nei.MultiblockHandler; + +import codechicken.nei.NEIClientUtils; +import codechicken.nei.recipe.TemplateRecipeHandler; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; + +public class GT_NEI_MultiblockHandler extends MultiblockHandler { + + public static List multiblocksList = new ArrayList<>(); + private static final GT_GUI_MultiblockHandler baseHandler = new GT_GUI_MultiblockHandler(); + + public GT_NEI_MultiblockHandler() { + super(baseHandler); + for (IMetaTileEntity mte : METATILEENTITIES) { + if (mte instanceof IConstructable) { + multiblocksList.add((IConstructable) mte); + } + } + } + + @Override + public String getOverlayIdentifier() { + return GT_NEI_MB_HANDLER_NAME; + } + + @Override + public TemplateRecipeHandler newInstance() { + return new GT_NEI_MultiblockHandler(); + } + + @Override + protected void tryLoadingMultiblock(ItemStack candidate) { + for (IConstructable multiblock : multiblocksList) { + ItemStack stackForm = ((IMetaTileEntity) multiblock).getStackForm(1); + if (NEIClientUtils.areStacksSameType(stackForm, candidate)) { + baseHandler.setOnIngredientChanged(ingredients -> { + this.ingredients = ingredients; + resetPositionedIngredients(); + }); + baseHandler.setOnCandidateChanged(this::setResults); + baseHandler.loadMultiblock(multiblock, stackForm); + return; + } + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatGuiHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatGuiHandler.java new file mode 100644 index 00000000..976240c5 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatGuiHandler.java @@ -0,0 +1,71 @@ +package com.gtnewhorizon.structurelib.client.nei.integration.structurelib; + +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; + +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.alignment.constructable.IMultiblockInfoContainer; +import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.client.nei.GUI_MultiblockHandler; + +public class StructureCompatGuiHandler extends GUI_MultiblockHandler { + + public StructureCompatGuiHandler() { + super(); + } + + // spotless:off +// TODO: Move this into a non GUI multi block handler class +// @Override +// protected void placeMultiblock() { +// Block stackBlock = ((ItemBlock) stackForm.getItem()).field_150939_a; +// renderer.world.setBlock( +// PLACE_POSITION.x, +// PLACE_POSITION.y, +// PLACE_POSITION.z, +// stackBlock, +// stackForm.getItemDamage(), +// 3); +// +// TileEntity tTileEntity = renderer.world.getTileEntity(PLACE_POSITION.x, PLACE_POSITION.y, PLACE_POSITION.z); +// IMultiblockInfoContainer t = IMultiblockInfoContainer.get(tTileEntity.getClass()); +// ISurvivalConstructable multi = t.toConstructable(tTileEntity, ExtendedFacing.DEFAULT); +// +// if (!StructureLibAPI.isInstrumentEnabled()) { +// StructureLibAPI.enableInstrument(StructureLibAPI.MOD_ID); +// } +// structureElements.clear(); +// +// int result, iterations = 0; +// boolean tryContruct = false; +// do { +// result = multi.survivalConstruct( +// getBuildTriggerStack(), +// Integer.MAX_VALUE, +// ISurvivalBuildEnvironment.create(CreativeItemSource.instance, fakeMultiblockBuilder)); +// iterations++; +// if (result == -2) { +// tryContruct = true; +// break; +// } +// } while (result > 0 && iterations < StructureWorld.MAX_PLACE_ROUNDS); +// +// if (tryContruct) { +// multi.construct(getBuildTriggerStack(), false); +// } +// +// // A single tick is needed for some non GT multiblocks to complete +// renderer.world.updateEntitiesForNEI(); +// +// if (StructureLibAPI.isInstrumentEnabled()) { +// StructureLibAPI.disableInstrument(); +// } +// +// } +// spotless:on + public void loadTileMulti(TileEntity multiblock, ItemStack stackForm) { + IMultiblockInfoContainer m = IMultiblockInfoContainer.get(multiblock.getClass()); + IConstructable constructable = m.toConstructable(multiblock, ExtendedFacing.DEFAULT); + super.loadMultiblock(constructable, stackForm); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatNEIHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatNEIHandler.java new file mode 100644 index 00000000..804126b1 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/nei/integration/structurelib/StructureCompatNEIHandler.java @@ -0,0 +1,52 @@ +package com.gtnewhorizon.structurelib.client.nei.integration.structurelib; + +import static com.gtnewhorizon.structurelib.client.nei.IMCForNEI.STRUCTURE_LIB_HANDLER; + +import net.minecraft.block.Block; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; + +import com.gtnewhorizon.structurelib.alignment.constructable.IMultiblockInfoContainer; +import com.gtnewhorizon.structurelib.client.nei.MultiblockHandler; +import com.gtnewhorizon.structurelib.client.world.StructureWorld; + +import codechicken.nei.recipe.TemplateRecipeHandler; + +public class StructureCompatNEIHandler extends MultiblockHandler { + + private static final StructureCompatGuiHandler baseHandler = new StructureCompatGuiHandler(); + private static final StructureWorld DummyWorld = new StructureWorld(); + + public StructureCompatNEIHandler() { + super(baseHandler); + } + + @Override + public TemplateRecipeHandler newInstance() { + return new StructureCompatNEIHandler(); + } + + @Override + public String getOverlayIdentifier() { + return STRUCTURE_LIB_HANDLER; + } + + @Override + protected void tryLoadingMultiblock(ItemStack candidate) { + if (candidate.getItem() instanceof ItemBlock ib) { + Block block = ib.field_150939_a; + if (block.hasTileEntity(candidate.getItemDamage())) { + TileEntity te = block.createTileEntity(DummyWorld, ib.getMetadata(candidate.getItemDamage())); + if (te != null && IMultiblockInfoContainer.contains(te.getClass())) { + baseHandler.setOnIngredientChanged(ingredients -> { + this.ingredients = ingredients; + resetPositionedIngredients(); + }); + baseHandler.setOnCandidateChanged(this::setResults); + baseHandler.loadTileMulti(te, candidate); + } + } + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ImmediateWorldSceneRenderer.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ImmediateWorldSceneRenderer.java new file mode 100644 index 00000000..b94e68d0 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ImmediateWorldSceneRenderer.java @@ -0,0 +1,50 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.ScaledResolution; + +import org.lwjgl.opengl.GL11; + +import com.gtnewhorizon.structurelib.client.world.StructureWorld; +import com.gtnewhorizon.structurelib.util.PositionedRect; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * Created with IntelliJ IDEA. + * + * @Author: KilaBash, backported by Quarri6343 + * @Date: 2021/8/24 + * @Description: Real-time rendering renderer. + */ +@SideOnly(Side.CLIENT) +public class ImmediateWorldSceneRenderer extends WorldSceneRenderer { + + public ImmediateWorldSceneRenderer(StructureWorld world) { + super(world); + } + + @Override + protected PositionedRect getPositionedRect(int x, int y, int width, int height) { + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution resolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + // compute window size from scaled width & height + int windowWidth = (int) (width / (resolution.getScaledWidth() * 1.0) * mc.displayWidth); + int windowHeight = (int) (height / (resolution.getScaledHeight() * 1.0) * mc.displayHeight); + // translate gui coordinates to window's ones (y is inverted) + int windowX = (int) (x / (resolution.getScaledWidth() * 1.0) * mc.displayWidth); + int windowY = mc.displayHeight - (int) (y / (resolution.getScaledHeight() * 1.0) * mc.displayHeight) + - windowHeight; + + return super.getPositionedRect(windowX, windowY, windowWidth, windowHeight); + } + + @Override + protected void clearView(int x, int y, int width, int height) { + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(x, y, width, height); + super.clearView(x, y, width, height); + GL11.glDisable(GL11.GL_SCISSOR_TEST); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/MutVec3.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/MutVec3.java new file mode 100644 index 00000000..89badc75 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/MutVec3.java @@ -0,0 +1,32 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import net.minecraft.util.Vec3; + +import org.joml.Vector3f; + +public class MutVec3 extends Vec3 { + + public MutVec3(double x, double y, double z) { + super(x, y, z); + } + + public void set(double x, double y, double z) { + this.xCoord = x; + this.yCoord = y; + this.zCoord = z; + } + + public MutVec3 set(Vector3f vec) { + this.xCoord = vec.x; + this.yCoord = vec.y; + this.zCoord = vec.z; + return this; + } + + public MutVec3 add(Vector3f vec) { + this.xCoord += vec.x; + this.yCoord += vec.y; + this.zCoord += vec.z; + return this; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ProjectionUtils.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ProjectionUtils.java new file mode 100644 index 00000000..d047c975 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/ProjectionUtils.java @@ -0,0 +1,47 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; + +import com.gtnewhorizon.structurelib.util.PositionedRect; + +public class ProjectionUtils { + + // TODO: Replace this with something from JOML; and/or something that allocates less + public static Vector3f unProject(PositionedRect rect, Vector3f eyePos, Vector3f lookat, int mouseX, int mouseY) { + int width = rect.getWidth(); + int height = rect.getHeight(); + + double aspectRatio = ((double) width / (double) height); + double fov = ((60 / 2d)) * (Math.PI / 180); + + double a = -((double) (mouseX - rect.x) / (double) width - 0.5) * 2; + double b = -((double) (height - (mouseY - rect.y)) / (double) height - 0.5) * 2; + double tanf = Math.tan(fov); + + Vector3f lookVec = new Vector3f(); + eyePos.sub(lookat, lookVec); + float yawn = (float) Math.atan2(lookVec.x, -lookVec.z); + float pitch = (float) Math.atan2(lookVec.y, Math.sqrt(lookVec.x * lookVec.x + lookVec.z * lookVec.z)); + + Matrix4f rot = new Matrix4f(); + rot.rotate(yawn, new Vector3f(0, -1, 0)); + rot.rotate(pitch, new Vector3f(1, 0, 0)); + Vector4f forward = new Vector4f(0, 0, 1, 0); + Vector4f up = new Vector4f(0, 1, 0, 0); + Vector4f left = new Vector4f(1, 0, 0, 0); + + rot.transform(forward, forward); + rot.transform(up, up); + rot.transform(left, left); + + Vector3f result = new Vector3f(forward.x, forward.y, forward.z); + result.add( + (float) (left.x * tanf * aspectRatio * a), + (float) (left.y * tanf * aspectRatio * a), + (float) (left.z * tanf * aspectRatio * a)); + result.add((float) (up.x * tanf * b), (float) (up.y * tanf * b), (float) (up.z * tanf * b)); + return result.normalize(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderHelper.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderHelper.java new file mode 100644 index 00000000..ce88531f --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderHelper.java @@ -0,0 +1,488 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import java.nio.FloatBuffer; + +import org.joml.Vector3f; +import org.lwjgl.BufferUtils; + +public class RenderHelper { + + public static final int QUAD_DOWN = 0x01; + public static final int QUAD_UP = 0x02; + public static final int QUAD_NORTH = 0x04; + public static final int QUAD_SOUTH = 0x08; + public static final int QUAD_WEST = 0x10; + public static final int QUAD_EAST = 0x20; + public static final int QUAD_ALL = QUAD_DOWN | QUAD_UP | QUAD_NORTH | QUAD_SOUTH | QUAD_WEST | QUAD_EAST; + + public static final int LINE_DOWN_WEST = 0x11; + public static final int LINE_UP_WEST = 0x12; + public static final int LINE_DOWN_EAST = 0x21; + public static final int LINE_UP_EAST = 0x22; + public static final int LINE_DOWN_NORTH = 0x05; + public static final int LINE_UP_NORTH = 0x06; + public static final int LINE_DOWN_SOUTH = 0x09; + public static final int LINE_UP_SOUTH = 0x0A; + public static final int LINE_NORTH_WEST = 0x14; + public static final int LINE_NORTH_EAST = 0x24; + public static final int LINE_SOUTH_WEST = 0x18; + public static final int LINE_SOUTH_EAST = 0x28; + public static final int LINE_ALL = LINE_DOWN_WEST | LINE_UP_WEST + | LINE_DOWN_EAST + | LINE_UP_EAST + | LINE_DOWN_NORTH + | LINE_UP_NORTH + | LINE_DOWN_SOUTH + | LINE_UP_SOUTH + | LINE_NORTH_WEST + | LINE_NORTH_EAST + | LINE_SOUTH_WEST + | LINE_SOUTH_EAST; + + public static final Vector3f VEC_ZERO = new Vector3f(0, 0, 0); + + private static int quadSize = 0; + private static float[] quadVertexBuffer = null; + private static float[] quadColorBuffer = null; + private static int quadVertexIndex = 0; + private static int quadColorIndex = 0; + private static int quadCount = 0; + + private static int lineSize = 0; + private static float[] lineVertexBuffer = null; + private static float[] lineColorBuffer = null; + private static int lineVertexIndex = 0; + private static int lineColorIndex = 0; + private static int lineCount = 0; + + private static final Vector3f vecZero = new Vector3f(); + private static final Vector3f vecSize = new Vector3f(); + + public static void createBuffers() { + quadSize = 240; + quadVertexBuffer = new float[quadSize * 3]; + quadColorBuffer = new float[quadSize * 4]; + + lineSize = 240; + lineVertexBuffer = new float[lineSize * 3]; + lineColorBuffer = new float[lineSize * 4]; + + initBuffers(); + } + + public static void initBuffers() { + quadVertexIndex = 0; + quadColorIndex = 0; + quadCount = 0; + + lineVertexIndex = 0; + lineColorIndex = 0; + lineCount = 0; + } + + public static void destroyBuffers() { + quadSize = 0; + quadVertexBuffer = null; + quadColorBuffer = null; + + lineSize = 0; + lineVertexBuffer = null; + lineColorBuffer = null; + } + + public static FloatBuffer getQuadVertexBuffer() { + FloatBuffer buffer = BufferUtils.createFloatBuffer(quadVertexBuffer.length).put(quadVertexBuffer); + buffer.flip(); + return buffer; + } + + public static FloatBuffer getQuadColorBuffer() { + FloatBuffer buffer = BufferUtils.createFloatBuffer(quadColorBuffer.length).put(quadColorBuffer); + buffer.flip(); + return buffer; + } + + public static int getQuadCount() { + return quadCount; + } + + public static FloatBuffer getLineVertexBuffer() { + FloatBuffer buffer = BufferUtils.createFloatBuffer(lineVertexBuffer.length).put(lineVertexBuffer); + buffer.flip(); + return buffer; + } + + public static FloatBuffer getLineColorBuffer() { + FloatBuffer buffer = BufferUtils.createFloatBuffer(lineColorBuffer.length).put(lineColorBuffer); + buffer.flip(); + return buffer; + } + + public static int getLineCount() { + return lineCount; + } + + private static float[] createAndCopyBuffer(int newSize, float[] oldBuffer) { + float[] tempBuffer = new float[newSize]; + System.arraycopy(oldBuffer, 0, tempBuffer, 0, oldBuffer.length); + return tempBuffer; + } + + final static double BLOCK_DELTA = 0.005; + + public static void drawCuboidSurface(Vector3f zero, Vector3f size, int sides, float red, float green, float blue, + float alpha) { + vecZero.set(zero.x - BLOCK_DELTA, zero.y - BLOCK_DELTA, zero.z - BLOCK_DELTA); + vecSize.set(size.x + BLOCK_DELTA, size.y + BLOCK_DELTA, size.z + BLOCK_DELTA); + + if (quadCount + 24 >= quadSize) { + quadSize *= 2; + + quadVertexBuffer = createAndCopyBuffer(quadSize * 3, quadVertexBuffer); + quadColorBuffer = createAndCopyBuffer(quadSize * 4, quadColorBuffer); + } + + int total = 0; + + if ((sides & QUAD_DOWN) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + total += 4; + } + + if ((sides & QUAD_UP) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + total += 4; + } + + if ((sides & QUAD_NORTH) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + total += 4; + } + + if ((sides & QUAD_SOUTH) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + total += 4; + } + + if ((sides & QUAD_WEST) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecZero.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + total += 4; + } + + if ((sides & QUAD_EAST) != 0) { + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecZero.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecZero.z; + quadCount++; + + quadVertexBuffer[quadVertexIndex++] = vecSize.x; + quadVertexBuffer[quadVertexIndex++] = vecSize.y; + quadVertexBuffer[quadVertexIndex++] = vecSize.z; + quadCount++; + + total += 4; + } + + for (int i = 0; i < total; i++) { + quadColorBuffer[quadColorIndex++] = red; + quadColorBuffer[quadColorIndex++] = green; + quadColorBuffer[quadColorIndex++] = blue; + quadColorBuffer[quadColorIndex++] = alpha; + } + } + + public static void drawCuboidOutline(Vector3f zero, Vector3f size, int sides, float red, float green, float blue, + float alpha) { + vecZero.set(zero.x - BLOCK_DELTA, zero.y - BLOCK_DELTA, zero.z - BLOCK_DELTA); + vecSize.set(size.x + BLOCK_DELTA, size.y + BLOCK_DELTA, size.z + BLOCK_DELTA); + + if (lineCount + 24 >= lineSize) { + lineSize *= 2; + + lineVertexBuffer = createAndCopyBuffer(lineSize * 3, lineVertexBuffer); + lineColorBuffer = createAndCopyBuffer(lineSize * 4, lineColorBuffer); + } + + int total = 0; + + if ((sides & LINE_DOWN_WEST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_UP_WEST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_DOWN_EAST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_UP_EAST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_DOWN_NORTH) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_UP_NORTH) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_DOWN_SOUTH) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_UP_SOUTH) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_NORTH_WEST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_NORTH_EAST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecZero.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_SOUTH_WEST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecZero.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + if ((sides & LINE_SOUTH_EAST) != 0) { + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecZero.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + lineVertexBuffer[lineVertexIndex++] = vecSize.x; + lineVertexBuffer[lineVertexIndex++] = vecSize.y; + lineVertexBuffer[lineVertexIndex++] = vecSize.z; + lineCount++; + + total += 2; + } + + for (int i = 0; i < total; i++) { + lineColorBuffer[lineColorIndex++] = red; + lineColorBuffer[lineColorIndex++] = green; + lineColorBuffer[lineColorIndex++] = blue; + lineColorBuffer[lineColorIndex++] = alpha; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderStructureGlobal.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderStructureGlobal.java new file mode 100644 index 00000000..38b88448 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RenderStructureGlobal.java @@ -0,0 +1,182 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.culling.Frustrum; +import net.minecraft.profiler.Profiler; +import net.minecraftforge.common.util.ForgeDirection; + +import org.joml.Vector3d; +import org.lwjgl.opengl.GL11; + +import com.gtnewhorizon.structurelib.client.world.StructureWorld; + +public class RenderStructureGlobal { + + private final Vector3d playerPosition = new Vector3d(); + private ForgeDirection orientation = ForgeDirection.UNKNOWN; + private int rotationRender = 0; + + private final Frustrum frustrum = new Frustrum(); + public RenderBlocks renderBlocks = null; + public final List sortedRendererStructureChunk = new ArrayList<>(); + private final RendererStructureChunkComparator rendererStructureChunkComparator = new RendererStructureChunkComparator(); + + public void setPlayerPosition(Vector3d playerPosistion) { + this.playerPosition.set(playerPosistion); + } + + public void setPlayerPosition(double x, double y, double z) { + this.playerPosition.set(x, y, z); + } + + public void setOrientation(ForgeDirection orientation) { + this.orientation = orientation; + } + + public void setRotationRender(int rotationRender) { + this.rotationRender = rotationRender; + } + + public void render(StructureWorld structureWorld) { + final Minecraft minecraft = Minecraft.getMinecraft(); + final Profiler profiler = minecraft.mcProfiler; + + GL11.glPushMatrix(); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glEnable(GL11.GL_BLEND); + + Vector3d extra = new Vector3d(); + if (structureWorld != null) { + extra.add( + structureWorld.renderPosition.x, + structureWorld.renderPosition.y, + structureWorld.renderPosition.z); + playerPosition.sub(extra); + } + + GL11.glTranslated(-playerPosition.x, -playerPosition.y, -playerPosition.z); + + profiler.startSection("schematic"); + if (structureWorld != null && structureWorld.isRendering()) { + profiler.startSection("updateFrustrum"); + updateFrustrum(structureWorld); + + profiler.endStartSection("sortAndUpdate"); + if (RendererStructureChunk.getCanUpdate()) { + sortAndUpdate(structureWorld); + } + + profiler.endStartSection("render"); + int pass; + for (pass = 0; pass < 3; pass++) { + for (RendererStructureChunk renderer : this.sortedRendererStructureChunk) { + renderer.render(playerPosition, pass); + } + } + profiler.endSection(); + } + + profiler.endStartSection("guide"); + + RenderHelper.createBuffers(); + + profiler.startSection("dataPrep"); + + int quadCount = RenderHelper.getQuadCount(); + int lineCount = RenderHelper.getLineCount(); + + if (quadCount > 0 || lineCount > 0) { + GL11.glDisable(GL11.GL_TEXTURE_2D); + + GL11.glLineWidth(1.5f); + + GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); + GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); + + profiler.endStartSection("quad"); + if (quadCount > 0) { + GL11.glVertexPointer(3, 0, RenderHelper.getQuadVertexBuffer()); + GL11.glColorPointer(4, 0, RenderHelper.getQuadColorBuffer()); + GL11.glDrawArrays(GL11.GL_QUADS, 0, quadCount); + } + + profiler.endStartSection("line"); + if (lineCount > 0) { + GL11.glVertexPointer(3, 0, RenderHelper.getLineVertexBuffer()); + GL11.glColorPointer(4, 0, RenderHelper.getLineColorBuffer()); + GL11.glDrawArrays(GL11.GL_LINES, 0, lineCount); + } + + profiler.endSection(); + + GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); + GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); + + GL11.glEnable(GL11.GL_TEXTURE_2D); + } + + profiler.endSection(); + + GL11.glDisable(GL11.GL_BLEND); + GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + GL11.glPopMatrix(); + } + + private void updateFrustrum(StructureWorld schematic) { + this.frustrum.setPosition( + playerPosition.x - schematic.renderPosition.x, + playerPosition.y - schematic.renderPosition.y, + playerPosition.z - schematic.renderPosition.z); + for (RendererStructureChunk RendererStructureChunk : this.sortedRendererStructureChunk) { + RendererStructureChunk.isInFrustrum = this.frustrum + .isBoundingBoxInFrustum(RendererStructureChunk.getBoundingBox()); + } + } + + private void sortAndUpdate(StructureWorld world) { + this.rendererStructureChunkComparator.setPosition(playerPosition, world.renderPosition); + this.sortedRendererStructureChunk.sort(this.rendererStructureChunkComparator); + + for (RendererStructureChunk RendererStructureChunk : this.sortedRendererStructureChunk) { + if (RendererStructureChunk.getDirty()) { + RendererStructureChunk.updateRenderer(renderBlocks); + break; + } + } + } + + public void createRendererStructureChunks(StructureWorld structureWorld) { + int width = (structureWorld.getWidth() - 1) / 16 + 1; + int height = (structureWorld.getHeight() - 1) / 16 + 1; + int length = (structureWorld.getLength() - 1) / 16 + 1; + + destroyRendererStructureChunks(); + + this.renderBlocks = new RenderBlocks(structureWorld); + for (int y = 0; y < height; y++) { + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++) { + this.sortedRendererStructureChunk.add(new RendererStructureChunk(structureWorld, x, y, z)); + } + } + } + } + + public void destroyRendererStructureChunks() { + this.renderBlocks = null; + while (!this.sortedRendererStructureChunk.isEmpty()) { + this.sortedRendererStructureChunk.remove(0).delete(); + } + } + + public void refresh() { + for (RendererStructureChunk renderer : this.sortedRendererStructureChunk) { + renderer.setDirty(); + } + } + +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunk.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunk.java new file mode 100644 index 00000000..a4ddc1ec --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunk.java @@ -0,0 +1,414 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.profiler.Profiler; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.world.IBlockAccess; + +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; + +import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram; +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.client.world.StructureWorld; + +public class RendererStructureChunk { + + private static final ShaderProgram alphaShader = initAlphaShader(); + private static final int alphaUniform = GL20.glGetUniformLocation(alphaShader.getProgram(), "alpha_multiplier"); + + private static boolean canUpdate = false; + + public boolean isInFrustrum = false; + + public final Vector3d centerPosition = new Vector3d(); + + private final Minecraft minecraft = Minecraft.getMinecraft(); + private final Profiler profiler = this.minecraft.mcProfiler; + private final StructureWorld world; + private final List tileEntities = new ArrayList<>(); + private final Vector3d distance = new Vector3d(); + + private final AxisAlignedBB boundingBox = AxisAlignedBB.getBoundingBox(0, 0, 0, 0, 0, 0); + + private boolean needsUpdate = true; + private int glList = -1; + // TODO: move this away from GL lists + private int glListHighlight = -1; + + private static ShaderProgram initAlphaShader() { + ShaderProgram shader; + try { + shader = new ShaderProgram("structurelib", null, "shaders/alpha.frag"); + shader.use(); + GL20.glUniform1i(GL20.glGetUniformLocation(shader.getProgram(), "texture"), 0); + ShaderProgram.clear(); + } catch (Exception e) { + shader = null; + } + return shader; + } + + private static final int SUBCHUNK_SIZE = 16; + + public RendererStructureChunk(StructureWorld world, int x, int y, int z) { + this.world = world; + + this.boundingBox.setBounds( + x * SUBCHUNK_SIZE, + y * SUBCHUNK_SIZE, + z * SUBCHUNK_SIZE, + (x + 1) * SUBCHUNK_SIZE, + (y + 1) * SUBCHUNK_SIZE, + (z + 1) * SUBCHUNK_SIZE); + this.centerPosition.set( + (int) ((x + 0.5) * SUBCHUNK_SIZE), + (int) ((y + 0.5) * SUBCHUNK_SIZE), + (int) ((z + 0.5) * SUBCHUNK_SIZE)); + + // TODO: Replace this with something less stupid + int tx, ty, tz; + for (TileEntity tileEntity : this.world.getTileEntities()) { + tx = tileEntity.xCoord; + ty = tileEntity.yCoord; + tz = tileEntity.zCoord; + + if (tx < this.boundingBox.minX || tx >= this.boundingBox.maxX) { + continue; + } else if (tz < this.boundingBox.minZ || tz >= this.boundingBox.maxZ) { + continue; + } else if (ty < this.boundingBox.minY || ty >= this.boundingBox.maxY) { + continue; + } + + this.tileEntities.add(tileEntity); + } + + } + + public void delete() { + // Deallocate VBOs + } + + public void updateRenderer(RenderBlocks renderBlocks) { + if (this.needsUpdate) { + this.needsUpdate = false; + setCanUpdate(false); + + RenderHelper.createBuffers(); + + for (int pass = 0; pass < 3; pass++) { + RenderHelper.initBuffers(); + + int minX, maxX, minY, maxY, minZ, maxZ; + + minX = (int) this.boundingBox.minX; + maxX = (int) this.boundingBox.maxX; + minY = (int) this.boundingBox.minY; + maxY = (int) this.boundingBox.maxY; + minZ = (int) this.boundingBox.minZ; + maxZ = (int) this.boundingBox.maxZ; + + // TODO: Rendering layer + // int renderingLayer = this.world.renderingLayer; + // if (this.world.isRenderingLayer) { + // if (renderingLayer >= minY && renderingLayer < maxY) { + // minY = renderingLayer; + // maxY = renderingLayer + 1; + // } else { + // minY = maxY = 0; + // } + // } + + GL11.glNewList(this.glList + pass, GL11.GL_COMPILE); + renderBlocks(renderBlocks, pass, minX, minY, minZ, maxX, maxY, maxZ); + GL11.glEndList(); + + GL11.glNewList(this.glListHighlight + pass, GL11.GL_COMPILE); + int quadCount = RenderHelper.getQuadCount(); + int lineCount = RenderHelper.getLineCount(); + + if (quadCount > 0 || lineCount > 0) { + GL11.glDisable(GL11.GL_TEXTURE_2D); + + GL11.glLineWidth(1.5f); + + GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); + GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); + + if (quadCount > 0) { + GL11.glVertexPointer(3, 0, RenderHelper.getQuadVertexBuffer()); + GL11.glColorPointer(4, 0, RenderHelper.getQuadColorBuffer()); + GL11.glDrawArrays(GL11.GL_QUADS, 0, quadCount); + } + + if (lineCount > 0) { + GL11.glVertexPointer(3, 0, RenderHelper.getLineVertexBuffer()); + GL11.glColorPointer(4, 0, RenderHelper.getLineColorBuffer()); + GL11.glDrawArrays(GL11.GL_LINES, 0, lineCount); + } + + GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); + GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); + + GL11.glEnable(GL11.GL_TEXTURE_2D); + } + + GL11.glEndList(); + } + + RenderHelper.destroyBuffers(); + } + } + + public void render(Vector3d playerPosition, int renderPass) { + if (!this.isInFrustrum) { + return; + } + + if (this.distance.set(playerPosition) + .sub(this.world.renderPosition.x, this.world.renderPosition.y, this.world.renderPosition.z) + .sub(this.centerPosition).lengthSquared() > 25600) { + return; + } + + // some mods enable this, beats me why - it's supposed to be disabled! + GL11.glDisable(GL11.GL_LIGHTING); + + this.profiler.startSection("blocks"); + this.minecraft.renderEngine.bindTexture(TextureMap.locationBlocksTexture); + + GL20.glUseProgram(alphaShader.getProgram()); + GL20.glUniform1f(alphaUniform, ALPHA); + + GL11.glCallList(this.glList + renderPass); + + GL20.glUseProgram(0); + + this.profiler.endStartSection("highlight"); + GL11.glCallList(this.glListHighlight + renderPass); + + this.profiler.endStartSection("tileEntities"); + renderTileEntities(renderPass); + + // re-enable blending... spawners disable it, somewhere... + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + // re-set alpha func... beacons set it to (GL_GREATER, 0.5f) + // EntityRenderer sets it to (GL_GREATER, 0.1f) before dispatching the event + GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F); + + this.profiler.endSection(); + } + + public void renderBlocks(RenderBlocks renderBlocks, int renderPass, int minX, int minY, int minZ, int maxX, + int maxY, int maxZ) { + IBlockAccess mcWorld = this.minecraft.theWorld; + + int x, y, z, wx, wy, wz; + int sides; + Block block, mcBlock; + Vector3f zero = new Vector3f(); + Vector3f size = new Vector3f(); + + int ambientOcclusion = this.minecraft.gameSettings.ambientOcclusion; + this.minecraft.gameSettings.ambientOcclusion = 0; + + Tessellator.instance.startDrawingQuads(); + + for (y = minY; y < maxY; y++) { + for (z = minZ; z < maxZ; z++) { + for (x = minX; x < maxX; x++) { + try { + block = this.world.getBlock(x, y, z); + + wx = this.world.renderPosition.x + x; + wy = this.world.renderPosition.y + y; + wz = this.world.renderPosition.z + z; + + mcBlock = mcWorld.getBlock(wx, wy, wz); + + sides = 0; + if (block != null) { + if (block.shouldSideBeRendered(this.world, x, y - 1, z, 0)) { + sides |= RenderHelper.QUAD_DOWN; + } + + if (block.shouldSideBeRendered(this.world, x, y + 1, z, 1)) { + sides |= RenderHelper.QUAD_UP; + } + + if (block.shouldSideBeRendered(this.world, x, y, z - 1, 2)) { + sides |= RenderHelper.QUAD_NORTH; + } + + if (block.shouldSideBeRendered(this.world, x, y, z + 1, 3)) { + sides |= RenderHelper.QUAD_SOUTH; + } + + if (block.shouldSideBeRendered(this.world, x - 1, y, z, 4)) { + sides |= RenderHelper.QUAD_WEST; + } + + if (block.shouldSideBeRendered(this.world, x + 1, y, z, 5)) { + sides |= RenderHelper.QUAD_EAST; + } + } + + boolean isAirBlock = this.world.isAirBlock(x, y, z); + boolean isMcAirBlock = mcWorld.isAirBlock(wx, wy, wz); + + if (!isMcAirBlock) { + // if ConfigurationHandler.highlight + if (renderPass == 2) { + // if ConfigurationHandler.highlightAir + if (isAirBlock) { + zero.set(x, y, z); + size.set(x + 1, y + 1, z + 1); + // if (ConfigurationHandler.drawQuads) + RenderHelper.drawCuboidSurface( + zero, + size, + RenderHelper.QUAD_ALL, + 0.75f, + 0.0f, + 0.75f, + 0.25f); + // if (ConfigurationHandler.drawLines) + RenderHelper.drawCuboidOutline( + zero, + size, + RenderHelper.LINE_ALL, + 0.75f, + 0.0f, + 0.75f, + 0.25f); + } else if (block != mcBlock) { + zero.set(x, y, z); + size.set(x + 1, y + 1, z + 1); + // if (ConfigurationHandler.drawQuads) + RenderHelper.drawCuboidSurface(zero, size, sides, 1.0f, 0.0f, 0.0f, 0.25f); + // if (ConfigurationHandler.drawLines) + RenderHelper.drawCuboidOutline(zero, size, sides, 1.0f, 0.0f, 0.0f, 0.25f); + } else + if (this.world.getBlockMetadata(x, y, z) != mcWorld.getBlockMetadata(wx, wy, wz)) { + zero.set(x, y, z); + size.set(x + 1, y + 1, z + 1); + // if (ConfigurationHandler.drawQuads) + RenderHelper.drawCuboidSurface(zero, size, sides, 0.75f, 0.35f, 0.0f, 0.25f); + // if (ConfigurationHandler.drawLines) + RenderHelper.drawCuboidOutline(zero, size, sides, 0.75f, 0.35f, 0.0f, 0.25f); + } + } + } else if (!isAirBlock) { + // if ConfigurationHandler.highlight + if (renderPass == 2) { + zero.set(x, y, z); + size.set(x + 1, y + 1, z + 1); + // if (ConfigurationHandler.drawQuads) + RenderHelper.drawCuboidSurface(zero, size, sides, 0.0f, 0.75f, 1.0f, 0.25f); + // if (ConfigurationHandler.drawLines) + RenderHelper.drawCuboidOutline(zero, size, sides, 0.0f, 0.75f, 1.0f, 0.25f); + } + + if (block != null && block.canRenderInPass(renderPass)) { + renderBlocks.renderBlockByRenderType(block, x, y, z); + } + } + } catch (Exception e) { + StructureLib.LOGGER.error("Failed to render block!", e); + } + } + } + } + + Tessellator.instance.draw(); + + this.minecraft.gameSettings.ambientOcclusion = ambientOcclusion; + } + + public static float ALPHA = 0.75f; + + public void renderTileEntities(int renderPass) { + if (renderPass != 0) { + return; + } + + IBlockAccess mcWorld = this.minecraft.theWorld; + + int x, y, z; + + GL11.glColor4f(1.0f, 1.0f, 1.0f, ALPHA); + + try { + for (TileEntity tileEntity : this.tileEntities) { + x = tileEntity.xCoord; + y = tileEntity.yCoord; + z = tileEntity.zCoord; + + // TODO: Rendering layer + // if (this.world.isRenderingLayer && this.world.renderingLayer != y) { + // continue; + // } + + final boolean isAirBlock = mcWorld.isAirBlock( + x + this.world.renderPosition.x, + y + this.world.renderPosition.y, + z + this.world.renderPosition.z); + + if (isAirBlock) { + TileEntitySpecialRenderer tileEntitySpecialRenderer = TileEntityRendererDispatcher.instance + .getSpecialRenderer(tileEntity); + if (tileEntitySpecialRenderer != null) { + try { + tileEntitySpecialRenderer.renderTileEntityAt(tileEntity, x, y, z, 0); + + OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit); + GL11.glDisable(GL11.GL_TEXTURE_2D); + OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit); + } catch (Exception e) { + StructureLib.LOGGER.error("Failed to render a tile entity!", e); + } + GL11.glColor4f(1.0f, 1.0f, 1.0f, ALPHA); + } + } + } + } catch (Exception ex) { + StructureLib.LOGGER.error("Failed to render tile entities!", ex); + } + } + + public static void setCanUpdate(boolean parCanUpdate) { + canUpdate = parCanUpdate; + } + + public static boolean getCanUpdate() { + return canUpdate; + } + + public void setDirty() { + this.needsUpdate = true; + } + + public boolean getDirty() { + return this.needsUpdate; + } + + public AxisAlignedBB getBoundingBox() { + return boundingBox; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunkComparator.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunkComparator.java new file mode 100644 index 00000000..71cd819b --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/RendererStructureChunkComparator.java @@ -0,0 +1,31 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import java.util.Comparator; + +import org.joml.Vector3d; +import org.joml.Vector3i; + +public class RendererStructureChunkComparator implements Comparator { + + private final Vector3d position = new Vector3d(); + private final Vector3d renderPosition = new Vector3d(); + + @Override + public int compare(RendererStructureChunk RendererStructureChunk1, RendererStructureChunk RendererStructureChunk2) { + if (RendererStructureChunk1.isInFrustrum && !RendererStructureChunk2.isInFrustrum) { + return -1; + } else if (!RendererStructureChunk1.isInFrustrum && RendererStructureChunk2.isInFrustrum) { + return 1; + } else { + final double dist1 = position.distanceSquared(RendererStructureChunk1.centerPosition); + final double dist2 = position.distanceSquared(RendererStructureChunk2.centerPosition); + return Double.compare(dist1, dist2); + } + } + + public void setPosition(Vector3d playerPosition, Vector3i position) { + this.renderPosition.set(position); + this.position.set(playerPosition).sub(this.renderPosition.set(position)); + } + +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/renderer/WorldSceneRenderer.java b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/WorldSceneRenderer.java new file mode 100644 index 00000000..5cb983b3 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/renderer/WorldSceneRenderer.java @@ -0,0 +1,323 @@ +package com.gtnewhorizon.structurelib.client.renderer; + +import java.util.function.Consumer; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; +import net.minecraft.init.Blocks; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.MovingObjectPosition; +import net.minecraftforge.client.ForgeHooksClient; + +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.lwjgl.util.glu.GLU; + +import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; +import com.gtnewhorizon.structurelib.client.world.StructureWorld; +import com.gtnewhorizon.structurelib.util.PositionedRect; + +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; + +/** + * Created with IntelliJ IDEA. + * + * @Author: KilaBash, backported by Quarri6343 + * @Date: 2021/08/23 + * @Description: Abstract class, and extend a lot of features compared with the original one. + */ +public abstract class WorldSceneRenderer { + + // you have to place blocks in the world before use + public final StructureWorld world; + // the Blocks which this renderer needs to render + protected final LongSet renderedBlocks = new LongOpenHashSet(); + private Consumer beforeRender; + private Consumer onRender; + private Consumer onLookingAt; + private int clearColor; + private MovingObjectPosition lastTraceResult; + private final Vector3f eyePos = new Vector3f(0, 0, -10f); + private final Vector3f lookAt = new Vector3f(0, 0, 0); + private final Vector3f worldUp = new Vector3f(0, 1, 0); + private boolean renderAllFaces = false; + + public WorldSceneRenderer(StructureWorld world) { + this.world = world; + } + + public WorldSceneRenderer setBeforeWorldRender(Consumer callback) { + this.beforeRender = callback; + return this; + } + + public WorldSceneRenderer setOnWorldRender(Consumer callback) { + this.onRender = callback; + return this; + } + + public WorldSceneRenderer addRenderedBlocks(LongSet blocks) { + if (blocks != null) this.renderedBlocks.addAll(blocks); + return this; + } + + public WorldSceneRenderer addRenderedBlock(long pos) { + this.renderedBlocks.add(pos); + return this; + } + + public WorldSceneRenderer setOnLookingAt(Consumer onLookingAt) { + this.onLookingAt = onLookingAt; + return this; + } + + public void setRenderAllFaces(boolean renderAllFaces) { + this.renderAllFaces = renderAllFaces; + } + + public void setClearColor(int clearColor) { + this.clearColor = clearColor; + } + + public MovingObjectPosition getLastTraceResult() { + return lastTraceResult; + } + + /** + * Renders scene on given coordinates with given width and height, and RGB background color Note that this will + * ignore any transformations applied currently to projection/view matrix, so specified coordinates are scaled MC + * gui coordinates. It will return matrices of projection and view in previous state after rendering + */ + public void render(int x, int y, int width, int height, int mouseX, int mouseY) { + + PositionedRect positionedRect = getPositionedRect(x, y, width, height); + PositionedRect mouse = getPositionedRect(mouseX, mouseY, 0, 0); + mouseX = mouse.x; + mouseY = mouse.y; + // setupCamera + setupCamera(positionedRect); + + // render TrackedDummyWorld + drawWorld(); + + // check lookingAt + this.lastTraceResult = null; + if (onLookingAt != null && mouseX > positionedRect.getLeft() + && mouseX < positionedRect.getRight() + && mouseY > positionedRect.getTop() + && mouseY < positionedRect.getBottom()) { + Vector3f lookVec = ProjectionUtils.unProject(positionedRect, eyePos, lookAt, mouseX, mouseY); + MovingObjectPosition result = rayTrace(lookVec); + if (result != null) { + this.lastTraceResult = result; + onLookingAt.accept(result); + } + } + + // resetcamera + resetCamera(); + } + + public Vector3f getEyePos() { + return eyePos; + } + + public Vector3f getLookAt() { + return lookAt; + } + + public Vector3f getWorldUp() { + return worldUp; + } + + public void setCameraLookAt(Vector3f eyePos, Vector3f lookAt, Vector3f worldUp) { + this.eyePos.set(eyePos); + this.lookAt.set(lookAt); + this.worldUp.set(worldUp); + } + + public void setCameraLookAt(Vector3f lookAt, double radius, double rotationPitch, double rotationYaw) { + this.lookAt.set(lookAt); + Vector3d vecX = new Vector3d(Math.cos(rotationPitch), 0, Math.sin(rotationPitch)); + Vector3d vecY = new Vector3d(0, Math.tan(rotationYaw) * vecX.length(), 0); + Vector3d pos = new Vector3d(vecX).add(vecY).normalize().mul(radius); + this.eyePos.set(pos.add(lookAt.x, lookAt.y, lookAt.z)); + } + + public LongSet getRenderedBlocks() { + return renderedBlocks; + } + + public void clearRenderedBlocks() { + renderedBlocks.clear(); + } + + protected PositionedRect getPositionedRect(int x, int y, int width, int height) { + return new PositionedRect(x, y, width, height); + } + + public void setupCamera(PositionedRect positionedRect) { + final int x = positionedRect.x; + final int y = positionedRect.y; + final int width = positionedRect.getWidth(); + final int height = positionedRect.getHeight(); + final float aspectRatio = width / (height * 1.0f); + + Minecraft mc = Minecraft.getMinecraft(); + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + GL11.glPushClientAttrib(GL11.GL_ALL_CLIENT_ATTRIB_BITS); + mc.entityRenderer.disableLightmap(0); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL11.GL_BLEND); + + // setup viewport and clear GL buffers + GL11.glViewport(x, y, width, height); + + clearView(x, y, width, height); + + // setup projection matrix to perspective + GL11.glMatrixMode(GL11.GL_PROJECTION); + GL11.glPushMatrix(); + GL11.glLoadIdentity(); + + GLU.gluPerspective(60.0f, aspectRatio, 0.1f, 10000.0f); + + // setup modelview matrix + GL11.glMatrixMode(GL11.GL_MODELVIEW); + GL11.glPushMatrix(); + GL11.glLoadIdentity(); + GLU.gluLookAt(eyePos.x, eyePos.y, eyePos.z, lookAt.x, lookAt.y, lookAt.z, worldUp.x, worldUp.y, worldUp.z); + } + + public static void setGlClearColorFromInt(int colorValue, int opacity) { + int i = (colorValue & 16711680) >> 16; + int j = (colorValue & 65280) >> 8; + int k = (colorValue & 255); + GL11.glClearColor(i / 255.0f, j / 255.0f, k / 255.0f, opacity / 255.0f); + } + + protected void clearView(int x, int y, int width, int height) { + setGlClearColorFromInt(clearColor, clearColor >> 24); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + } + + public static void resetCamera() { + // reset viewport + Minecraft minecraft = Minecraft.getMinecraft(); + GL11.glViewport(0, 0, minecraft.displayWidth, minecraft.displayHeight); + + // reset modelview matrix + GL11.glMatrixMode(GL11.GL_MODELVIEW); + GL11.glPopMatrix(); + + // reset projection matrix + GL11.glMatrixMode(GL11.GL_PROJECTION); + GL11.glPopMatrix(); + + GL11.glMatrixMode(GL11.GL_MODELVIEW); + + // reset attributes + GL11.glPopClientAttrib(); + GL11.glPopAttrib(); + } + + protected void drawWorld() { + final Vector3i pos = new Vector3i(); + + if (beforeRender != null) { + beforeRender.accept(this); + } + + Minecraft mc = Minecraft.getMinecraft(); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.disableStandardItemLighting(); + mc.entityRenderer.disableLightmap(0); + mc.renderEngine.bindTexture(TextureMap.locationBlocksTexture); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_ALPHA_TEST); + + final int savedAo = mc.gameSettings.ambientOcclusion; + mc.gameSettings.ambientOcclusion = 0; + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + try { + tessellator.setBrightness(15 << 20 | 15 << 4); + RenderBlocks renderBlocks = new RenderBlocks(world); + for (final long longPos : renderedBlocks) { + CoordinatePacker.unpack(longPos, pos); + Block block = world.getBlock(pos.x, pos.y, pos.z); + if (block.equals(Blocks.air)) continue; + + renderBlocks.setRenderBounds(0, 0, 0, 1, 1, 1); + renderBlocks.renderAllFaces = renderAllFaces; + renderBlocks.renderBlockByRenderType(block, pos.x, pos.y, pos.z); + } + if (onRender != null) { + onRender.accept(this); + } + } finally { + mc.gameSettings.ambientOcclusion = savedAo; + tessellator.draw(); + tessellator.setTranslation(0, 0, 0); + } + + RenderHelper.enableStandardItemLighting(); + GL11.glEnable(GL11.GL_LIGHTING); + + // render TESR + TileEntityRendererDispatcher tesr = TileEntityRendererDispatcher.instance; + for (int pass = 0; pass < 2; pass++) { + ForgeHooksClient.setRenderPass(pass); + int finalPass = pass; + renderedBlocks.forEach(longPos -> { + CoordinatePacker.unpack(longPos, pos); + setDefaultPassRenderState(finalPass); + TileEntity tile = world.getTileEntity(pos.x, pos.y, pos.z); + if (tile != null && tesr.hasSpecialRenderer(tile)) { + if (tile.shouldRenderInPass(finalPass)) { + tesr.renderTileEntityAt(tile, pos.x, pos.y, pos.z, 0); + } + } + }); + } + ForgeHooksClient.setRenderPass(-1); + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glDisable(GL11.GL_BLEND); + GL11.glDepthMask(true); + } + + public static void setDefaultPassRenderState(int pass) { + GL11.glColor4f(1, 1, 1, 1); + if (pass == 0) { // SOLID + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glDisable(GL11.GL_BLEND); + GL11.glDepthMask(true); + } else { // TRANSLUCENT + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDepthMask(false); + } + } + + private final MutVec3 startPos = new MutVec3(0, 0, 0); + private final MutVec3 endPos = new MutVec3(0, 0, 0); + + public MovingObjectPosition rayTrace(Vector3f lookVec) { + startPos.set(eyePos); + // range: 100 Blocks + endPos.set(lookVec.mul(100)).add(eyePos); + return this.world.rayTraceBlockswithTargetMap(startPos, endPos, renderedBlocks); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/ChunkProviderStructure.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/ChunkProviderStructure.java new file mode 100644 index 00000000..2ea41174 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/ChunkProviderStructure.java @@ -0,0 +1,90 @@ +package com.gtnewhorizon.structurelib.client.world; + +import java.util.List; + +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.util.IProgressUpdate; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.ChunkPosition; +import net.minecraft.world.World; +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +public class ChunkProviderStructure implements IChunkProvider { + + private final StructureWorld world; + private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); + + public ChunkProviderStructure(StructureWorld world) { + this.world = world; + } + + @Override + public boolean chunkExists(int x, int z) { + return true; + } + + @Override + public Chunk provideChunk(int x, int z) { + return chunks.computeIfAbsent(ChunkCoordIntPair.chunkXZ2Int(x, z), k -> new Chunk(world, x, z)); + } + + @Override + public Chunk loadChunk(int x, int z) { + return chunks.get(ChunkCoordIntPair.chunkXZ2Int(x, z)); + } + + @Override + public void populate(IChunkProvider provider, int x, int z) { + + } + + @Override + public boolean saveChunks(boolean saveAll, IProgressUpdate progress) { + return false; + } + + @Override + public boolean unloadQueuedChunks() { + return false; + } + + @Override + public boolean canSave() { + return false; + } + + @Override + public String makeString() { + return "ChunkProviderStructure"; + } + + @Override + public List getPossibleCreatures(EnumCreatureType creatureType, int x, int y, int z) { + return null; + } + + @Override + public ChunkPosition func_147416_a(World world, String structure, int x, int y, int z) { + return null; + } + + @Override + public int getLoadedChunkCount() { + return 0; + } + + @Override + public void recreateStructures(int x, int z) { + + } + + @Override + public void saveExtraData() { + + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/CreativeItemSource.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/CreativeItemSource.java new file mode 100644 index 00000000..bba573cf --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/CreativeItemSource.java @@ -0,0 +1,63 @@ +package com.gtnewhorizon.structurelib.client.world; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +import net.minecraft.item.ItemStack; + +import org.jetbrains.annotations.NotNull; + +import com.gtnewhorizon.structurelib.structure.IItemSource; + +import codechicken.nei.ItemList; + +public class CreativeItemSource implements IItemSource { + + public static CreativeItemSource instance; + + static { + instance = new CreativeItemSource(); + } + + @NotNull + @Override + public Map take(Predicate predicate, boolean b, int i) { + Map store = new HashMap<>(); + if (!ItemList.loadFinished) return store; + + // This seems terribly inefficient - TODO: Rewrite + for (ItemStack itemStack : ItemList.items) { + if (predicate.test(itemStack)) { + store.put(itemStack, Integer.MAX_VALUE); + return store; + } + } + + return store; + } + + public Map takeEverythingMatches(Predicate predicate, boolean b, int i) { + Map store = new HashMap<>(); + if (!ItemList.loadFinished) return store; + + // This seems terribly inefficient - TODO: Rewrite + for (ItemStack itemStack : ItemList.items) { + if (predicate.test(itemStack)) { + store.put(itemStack, Integer.MAX_VALUE); + } + } + + return store; + } + + @Override + public boolean takeOne(ItemStack stack, boolean simulate) { + return true; + } + + @Override + public boolean takeAll(ItemStack stack, boolean simulate) { + return true; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureFakePlayer.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureFakePlayer.java new file mode 100644 index 00000000..b3cd51d2 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureFakePlayer.java @@ -0,0 +1,54 @@ +package com.gtnewhorizon.structurelib.client.world; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ChunkCoordinates; +import net.minecraft.util.DamageSource; +import net.minecraft.util.IChatComponent; +import net.minecraft.world.World; + +import com.mojang.authlib.GameProfile; + +public class StructureFakePlayer extends EntityPlayer { + + public StructureFakePlayer(World world, GameProfile profile) { + super(world, profile); + this.capabilities.isCreativeMode = true; + } + + @Override + public void addChatMessage(IChatComponent message) { + + } + + @Override + public boolean canCommandSenderUseCommand(int permissionLevel, String command) { + return false; + } + + @Override + public ChunkCoordinates getPlayerCoordinates() { + return new ChunkCoordinates(0, 0, 0); + } + + @Override + public void openGui(Object mod, int modGuiId, World world, int x, int y, int z) {} + + @Override + public boolean isEntityInvulnerable() { + return true; + } + + @Override + public boolean canAttackPlayer(EntityPlayer player) { + return false; + } + + @Override + public void onDeath(DamageSource source) {} + + @Override + public void onUpdate() {} + + @Override + public void travelToDimension(int dim) {} +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/StructurePreviewUpdater.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructurePreviewUpdater.java new file mode 100644 index 00000000..162b1b82 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructurePreviewUpdater.java @@ -0,0 +1,73 @@ +package com.gtnewhorizon.structurelib.client.world; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.world.IWorldAccess; + +import org.joml.Vector3i; + +import com.gtnewhorizon.structurelib.ClientProxy; + +public class StructurePreviewUpdater implements IWorldAccess { + + public static final StructurePreviewUpdater INSTANCE = new StructurePreviewUpdater(); + + @Override + public void markBlockForUpdate(int x, int y, int z) { + markBlocksForUpdate(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1); + } + + @Override + public void markBlockForRenderUpdate(int x, int y, int z) { + markBlocksForUpdate(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1); + } + + @Override + public void markBlockRangeForRenderUpdate(int x0, int y0, int z0, int x1, int y1, int z1) { + markBlocksForUpdate(x0 - 1, y0 - 1, z0 - 1, x1 + 1, y1 + 1, z1 + 1); + } + + private final AxisAlignedBB box = AxisAlignedBB.getBoundingBox(0, 0, 0, 0, 0, 0); + + private void markBlocksForUpdate(int x0, int y0, int z0, int x1, int y1, int z1) { + final StructureWorld structure = ClientProxy.getStructureWorld(); + if (structure == null) return; + + final Vector3i loc = structure.getRenderLocation(); + box.setBounds(x0 - loc.x, y0 - loc.y, z0 - loc.z, x1 - loc.x, y1 - loc.y, z1 - loc.z); + // TODO: Update chunk renderers + + } + + @Override + public void playSound(String soundName, double x, double y, double z, float volume, float pitch) {} + + @Override + public void playSoundToNearExcept(EntityPlayer player, String soundName, double x, double y, double z, float volume, + float piatch) {} + + @Override + public void spawnParticle(final String type, double x, double y, double z, double velX, double velY, double velZ) {} + + @Override + public void onEntityCreate(Entity entity) {} + + @Override + public void onEntityDestroy(Entity entity) {} + + @Override + public void playRecord(String recordName, int x, int y, int z) {} + + @Override + public void broadcastSound(int id, int x, int y, int z, int par5) {} + + @Override + public void playAuxSFX(EntityPlayer player, int od, int x, int y, int z, int par6) {} + + @Override + public void destroyBlockPartially(int id, int x, int y, int z, int partialDamage) {} + + @Override + public void onStaticEntitiesChanged() {} +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureSaveHandler.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureSaveHandler.java new file mode 100644 index 00000000..0b2f3ec2 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureSaveHandler.java @@ -0,0 +1,62 @@ +package com.gtnewhorizon.structurelib.client.world; + +import java.io.File; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.MinecraftException; +import net.minecraft.world.WorldProvider; +import net.minecraft.world.chunk.storage.IChunkLoader; +import net.minecraft.world.storage.IPlayerFileData; +import net.minecraft.world.storage.ISaveHandler; +import net.minecraft.world.storage.WorldInfo; + +public class StructureSaveHandler implements ISaveHandler { + + @Override + public WorldInfo loadWorldInfo() { + return null; + } + + @Override + public void checkSessionLock() throws MinecraftException { + + } + + @Override + public IChunkLoader getChunkLoader(WorldProvider p_75763_1_) { + return null; + } + + @Override + public void saveWorldInfoWithPlayer(WorldInfo p_75755_1_, NBTTagCompound p_75755_2_) { + + } + + @Override + public void saveWorldInfo(WorldInfo p_75761_1_) { + + } + + @Override + public IPlayerFileData getSaveHandler() { + return null; + } + + @Override + public void flush() {} + + @Override + public File getWorldDirectory() { + return null; + } + + @Override + public File getMapFileFromName(String p_75758_1_) { + return null; + } + + @Override + public String getWorldDirectoryName() { + return null; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureWorld.java b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureWorld.java new file mode 100644 index 00000000..30dd7623 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/client/world/StructureWorld.java @@ -0,0 +1,460 @@ +package com.gtnewhorizon.structurelib.client.world; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import net.minecraft.block.Block; +import net.minecraft.entity.Entity; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.profiler.Profiler; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.MathHelper; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; +import net.minecraft.world.EnumSkyBlock; +import net.minecraft.world.World; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.IChunkProvider; +import net.minecraft.world.storage.ISaveHandler; +import net.minecraftforge.common.util.ForgeDirection; + +import org.joml.Vector3d; +import org.joml.Vector3f; +import org.joml.Vector3i; + +import com.gtnewhorizon.gtnhlib.util.CoordinatePacker; +import com.gtnewhorizon.structurelib.StructureLibAPI; +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider; +import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable; +import com.mojang.authlib.GameProfile; + +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.IGregTechTileEntity; +import gregtech.api.interfaces.tileentity.ITurnable; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; + +public class StructureWorld extends World { + + // Static variables + public static final Vector3i PLACE_POSITION = new Vector3i(0, 64, 0); + public static final int MAX_PLACE_ROUNDS = 2000; + public final Vector3i renderPosition = new Vector3i(0, 0, 0); + + private static final ISaveHandler STRUCTURE_SAVE_HANDLER = new StructureSaveHandler(); + private static final WorldSettings WORLD_SETTINGS = new WorldSettings( + 0, + WorldSettings.GameType.CREATIVE, + false, + false, + WorldType.FLAT); + + // Instance variables + private boolean rendering = false; + private final StructureFakePlayer fakeMultiblockBuilder; + private final Vector3i renderLocation = new Vector3i(); + public final LongSet placedBlocks = new LongOpenHashSet(); + + private final Vector3f minPos = new Vector3f(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + private final Vector3f maxPos = new Vector3f(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); + + private final Vector3f size = new Vector3f(); + + public boolean isRendering() { + return rendering; + } + + public void setRendering(boolean rendering) { + rendering = rendering; + } + + public StructureFakePlayer getFakeMultiblockBuilder() { + return fakeMultiblockBuilder; + } + + public List getTileEntities() { + // TODO: Replace this with something less stupid + return new ArrayList<>(); + } + + private void updateSize() { + size.set(maxPos.x - minPos.x + 1, maxPos.y - minPos.y + 1, maxPos.z - minPos.z + 1); + + } + + public int getWidth() { + return (int) size.x; + } + + public int getHeight() { + return (int) size.y; + } + + public int getLength() { + return (int) size.z; + } + + public Vector3i getRenderLocation() { + return renderLocation; + } + + public void setRenderLocation(Vector3i renderLocation) { + this.renderLocation.set(renderLocation); + } + + public void setRenderLocation(int x, int y, int z) { + this.renderLocation.set(x, y, z); + } + + public Vector3f getSize() { + return size; + } + + public Vector3f getMinPos() { + return minPos; + } + + public Vector3f getMaxPos() { + return maxPos; + } + + public StructureWorld() { + super(STRUCTURE_SAVE_HANDLER, "StructureLibFakeWorld", WORLD_SETTINGS, null, new Profiler()); + // Guarantee the dimension ID was not reset by the provider + this.provider.setDimension(Integer.MAX_VALUE); + int providerDim = this.provider.dimensionId; + this.provider.worldObj = this; + this.provider.setDimension(providerDim); + this.chunkProvider = this.createChunkProvider(); + this.calculateInitialSkylight(); + this.calculateInitialWeatherBody(); + this.fakeMultiblockBuilder = createFakeBuilder(); + this.unloadEntities(Collections.singletonList(fakeMultiblockBuilder)); + + updateEntitiesForNEI(); + } + + protected StructureFakePlayer createFakeBuilder() { + final String name = StructureLibAPI.MOD_NAME; + return new StructureFakePlayer(this, new GameProfile(UUID.nameUUIDFromBytes(name.getBytes()), name)); + } + + @Override + public void updateEntities() {} + + public void updateEntitiesForNEI() { + super.updateEntities(); + } + + @Override + public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) {} + + @Override + protected IChunkProvider createChunkProvider() { + return new ChunkProviderStructure(this); + } + + @Override + protected int func_152379_p() { + return 0; + } + + @Override + public Entity getEntityByID(int p_73045_1_) { + return null; + } + + @Override + public Block getBlock(int x, int y, int z) { + if (x > maxPos.x || y > maxPos.y || z > maxPos.z || x < minPos.x || y < minPos.y || z < minPos.z) { + return Blocks.air; + } + final Block block = super.getBlock(x, y, z); + return block != null ? block : Blocks.air; + } + + @Override + public boolean setBlock(int x, int y, int z, Block blockIn, int metadataIn, int flags) { + if (x < -30000000 || z < -30000000 || x >= 30000000 || z >= 30000000 || y < 0 || y > 255) { + return false; + } + + final long longPos = CoordinatePacker.pack(x, y, z); + if (blockIn == Blocks.air) { + placedBlocks.remove(longPos); + } else { + placedBlocks.add(longPos); + } + + minPos.x = Math.min(minPos.x, x); + minPos.y = Math.min(minPos.y, y); + minPos.z = Math.min(minPos.z, z); + maxPos.x = Math.max(maxPos.x, x); + maxPos.y = Math.max(maxPos.y, y); + maxPos.z = Math.max(maxPos.z, z); + updateSize(); + + return super.setBlock(x, y, z, blockIn, metadataIn, flags); + } + + @Override + public boolean updateLightByType(EnumSkyBlock skyBlock, int x, int y, int z) { + return true; + } + + private final int tierIndex = 1; + private ItemStack triggerStack = null; + + public ItemStack getBuildTriggerStack() { + if (triggerStack == null) { + triggerStack = new ItemStack(StructureLibAPI.getDefaultHologramItem(), tierIndex); + } + return triggerStack; + } + + public boolean placeMultiBlock(ItemStack stack) { + final Block block = Block.getBlockFromItem(stack.getItem()); + final int metadata = stack.getItemDamage(); + + IConstructable constructable = null; + final ItemStack stackCopy = stack.copy(); + // GT Multiblock only + stackCopy.getItem().onItemUse( + stackCopy, + fakeMultiblockBuilder, + this, + PLACE_POSITION.x, + PLACE_POSITION.y, + PLACE_POSITION.z, + 0, + PLACE_POSITION.x, + PLACE_POSITION.y, + PLACE_POSITION.z); + // Other Multiblocks + // setBlock(PLACE_LOCATION.x, PLACE_LOCATION.y, PLACE_LOCATION.z, block, metadata, 3); + final TileEntity te = getTileEntity(PLACE_POSITION.x, PLACE_POSITION.y, PLACE_POSITION.z); + if (te == null) return false; + + if (te instanceof ITurnable turnable) { + turnable.setFrontFacing(ForgeDirection.SOUTH); + } + + if (!StructureLibAPI.isInstrumentEnabled()) { + StructureLibAPI.enableInstrument(StructureLibAPI.MOD_ID); + } + + if (te instanceof IGregTechTileEntity gregTechTileEntity) { + final IMetaTileEntity mte = gregTechTileEntity.getMetaTileEntity(); + if (mte instanceof ISurvivalConstructable survivalConstructable) { + constructable = survivalConstructable; + // int result, iterations = 0; + // do { + // result = survivalConstructable.survivalConstruct( + // getBuildTriggerStack(), + // Integer.MAX_VALUE, + // ISurvivalBuildEnvironment.create(CreativeItemSource.instance, fakeMultiblockBuilder)); + // iterations++; + // } while (result > 0 && iterations < MAX_PLACE_ROUNDS); + } else if (te instanceof IConstructableProvider iConstructableProvider) { + constructable = iConstructableProvider.getConstructable(); + } else if (te instanceof IConstructable iConstructable) { + constructable = iConstructable; + } + } + + if (constructable != null) { + constructable.construct(getBuildTriggerStack(), false); + } + + if (StructureLibAPI.isInstrumentEnabled()) { + StructureLibAPI.disableInstrument(); + } + + // A single tick is needed for some non GT multiblocks to complete + updateEntitiesForNEI(); + + return true; + } + + public MovingObjectPosition rayTraceBlockswithTargetMap(Vec3 start, Vec3 end, LongSet targetedBlocks) { + return rayTraceBlockswithTargetMap(start, end, targetedBlocks, false, false, false); + } + + public MovingObjectPosition rayTraceBlockswithTargetMap(Vec3 start, Vec3 end, LongSet targetedBlocks, + boolean stopOnLiquid, boolean ignoreBlockWithoutBoundingBox, boolean returnLastUncollidableBlock) { + if (!Double.isNaN(start.xCoord) && !Double.isNaN(start.yCoord) && !Double.isNaN(start.zCoord)) { + if (!Double.isNaN(end.xCoord) && !Double.isNaN(end.yCoord) && !Double.isNaN(end.zCoord)) { + int i = MathHelper.floor_double(end.xCoord); + int j = MathHelper.floor_double(end.yCoord); + int k = MathHelper.floor_double(end.zCoord); + int l = MathHelper.floor_double(start.xCoord); + int i1 = MathHelper.floor_double(start.yCoord); + int j1 = MathHelper.floor_double(start.zCoord); + Block block = this.getBlock(l, i1, j1); + int k1 = this.getBlockMetadata(l, i1, j1); + + if ((!ignoreBlockWithoutBoundingBox || block.getCollisionBoundingBoxFromPool(this, l, i1, j1) != null) + && block.canCollideCheck(k1, stopOnLiquid)) { + MovingObjectPosition movingobjectposition = block.collisionRayTrace(this, l, i1, j1, start, end); + + if (movingobjectposition != null && isBlockTargeted(movingobjectposition, targetedBlocks)) { + return movingobjectposition; + } + } + + MovingObjectPosition movingobjectposition2 = null; + k1 = 200; + + while (k1-- >= 0) { + if (Double.isNaN(start.xCoord) || Double.isNaN(start.yCoord) || Double.isNaN(start.zCoord)) { + return null; + } + + if (l == i && i1 == j && j1 == k) { + return returnLastUncollidableBlock ? movingobjectposition2 : null; + } + + boolean flag6 = true; + boolean flag3 = true; + boolean flag4 = true; + double d0 = 999.0D; + double d1 = 999.0D; + double d2 = 999.0D; + + if (i > l) { + d0 = (double) l + 1.0D; + } else if (i < l) { + d0 = (double) l + 0.0D; + } else { + flag6 = false; + } + + if (j > i1) { + d1 = (double) i1 + 1.0D; + } else if (j < i1) { + d1 = (double) i1 + 0.0D; + } else { + flag3 = false; + } + + if (k > j1) { + d2 = (double) j1 + 1.0D; + } else if (k < j1) { + d2 = (double) j1 + 0.0D; + } else { + flag4 = false; + } + + double d3 = 999.0D; + double d4 = 999.0D; + double d5 = 999.0D; + double d6 = end.xCoord - start.xCoord; + double d7 = end.yCoord - start.yCoord; + double d8 = end.zCoord - start.zCoord; + + if (flag6) { + d3 = (d0 - start.xCoord) / d6; + } + + if (flag3) { + d4 = (d1 - start.yCoord) / d7; + } + + if (flag4) { + d5 = (d2 - start.zCoord) / d8; + } + + boolean flag5 = false; + byte b0; + + if (d3 < d4 && d3 < d5) { + if (i > l) { + b0 = 4; + } else { + b0 = 5; + } + + start.xCoord = d0; + start.yCoord += d7 * d3; + start.zCoord += d8 * d3; + } else if (d4 < d5) { + if (j > i1) { + b0 = 0; + } else { + b0 = 1; + } + + start.xCoord += d6 * d4; + start.yCoord = d1; + start.zCoord += d8 * d4; + } else { + if (k > j1) { + b0 = 2; + } else { + b0 = 3; + } + + start.xCoord += d6 * d5; + start.yCoord += d7 * d5; + start.zCoord = d2; + } + + Vector3d vec32 = new Vector3d(start.xCoord, start.yCoord, start.zCoord); + l = (int) (vec32.x = MathHelper.floor_double(start.xCoord)); + + if (b0 == 5) { + --l; + ++vec32.x; + } + + i1 = (int) (vec32.y = MathHelper.floor_double(start.yCoord)); + + if (b0 == 1) { + --i1; + ++vec32.y; + } + + j1 = (int) (vec32.z = MathHelper.floor_double(start.zCoord)); + + if (b0 == 3) { + --j1; + ++vec32.z; + } + + Block block1 = this.getBlock(l, i1, j1); + int l1 = this.getBlockMetadata(l, i1, j1); + + if (!ignoreBlockWithoutBoundingBox + || block1.getCollisionBoundingBoxFromPool(this, l, i1, j1) != null) { + if (block1.canCollideCheck(l1, stopOnLiquid)) { + MovingObjectPosition movingobjectposition1 = block1 + .collisionRayTrace(this, l, i1, j1, start, end); + + if (movingobjectposition1 != null + && isBlockTargeted(movingobjectposition1, targetedBlocks)) { + return movingobjectposition1; + } + } else { + movingobjectposition2 = new MovingObjectPosition(l, i1, j1, b0, start, false); + } + } + } + + return returnLastUncollidableBlock ? movingobjectposition2 : null; + } else { + return null; + } + } else { + return null; + } + } + + private boolean isBlockTargeted(MovingObjectPosition result, LongSet targetedBlocks) { + return targetedBlocks.contains(CoordinatePacker.pack(result.blockX, result.blockY, result.blockZ)); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java b/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java index 60181b57..4fcde574 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java +++ b/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java @@ -35,14 +35,11 @@ public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List aList, boolean p_77624_4_) { - aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0")); // Triggers Front Rotation Interface - aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1")); // Rotates - // only the - // front - // panel, - aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.2")); // which - // allows - // structure - // rotation. + // Triggers Front Rotation Interface + aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0")); + // Rotates only the front panel, + aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1")); + // which allows structure rotation. + aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.2")); } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/PositionedIStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/util/PositionedIStructureElement.java new file mode 100644 index 00000000..e95a4257 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/PositionedIStructureElement.java @@ -0,0 +1,17 @@ +package com.gtnewhorizon.structurelib.util; + +import org.joml.Vector3i; + +import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable; +import com.gtnewhorizon.structurelib.structure.IStructureElement; + +public class PositionedIStructureElement extends Vector3i { + + public final IStructureElement element; + + public PositionedIStructureElement(int x, int y, int z, IStructureElement element) { + super(x, y, z); + this.element = element; + } + +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/PositionedRect.java b/src/main/java/com/gtnewhorizon/structurelib/util/PositionedRect.java new file mode 100644 index 00000000..f48fcd7e --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/PositionedRect.java @@ -0,0 +1,51 @@ +package com.gtnewhorizon.structurelib.util; + +import org.joml.Vector4i; + +public class PositionedRect extends Vector4i { + + public PositionedRect(int x, int y, int width, int height) { + super(x, y, width, height); + } + + public int getLeft() { + return x; + } + + public int getTop() { + return y; + } + + public int getRight() { + return x + z; + } + + public int getBottom() { + return y + w; + } + + public int getWidth() { + return z; + } + + public int getHeight() { + return w; + } + + public boolean contains(int x, int y) { + return x >= getLeft() && x < getRight() && y >= getTop() && y < getBottom(); + } + + public boolean contains(PositionedRect rect) { + return getLeft() <= rect.getLeft() && getTop() <= rect.getTop() + && getRight() >= rect.getRight() + && getBottom() >= rect.getBottom(); + } + + public boolean intersects(PositionedRect rect) { + return getLeft() < rect.getRight() && getRight() > rect.getLeft() + && getTop() < rect.getBottom() + && getBottom() > rect.getTop(); + } + +} diff --git a/src/main/resources/META-INF/structurelib_at.cfg b/src/main/resources/META-INF/structurelib_at.cfg new file mode 100644 index 00000000..c30baef9 --- /dev/null +++ b/src/main/resources/META-INF/structurelib_at.cfg @@ -0,0 +1,2 @@ +public net.minecraft.client.gui.inventory.GuiContainer field_147003_i #guiLeft +public net.minecraft.client.gui.inventory.GuiContainer field_147009_r #guitop diff --git a/src/main/resources/assets/structurelib/shaders/alpha.frag b/src/main/resources/assets/structurelib/shaders/alpha.frag new file mode 100644 index 00000000..155116b9 --- /dev/null +++ b/src/main/resources/assets/structurelib/shaders/alpha.frag @@ -0,0 +1,9 @@ +#version 120 + +uniform sampler2D texture; +uniform float alpha_multiplier; + +void main() { + vec4 tex = texture2D(texture, gl_TexCoord[0].xy) * gl_Color; + gl_FragColor = vec4(tex.r, tex.g, tex.b, tex.a * alpha_multiplier); +} diff --git a/src/main/resources/assets/structurelib/textures/void.png b/src/main/resources/assets/structurelib/textures/void.png new file mode 100644 index 00000000..cf532b71 Binary files /dev/null and b/src/main/resources/assets/structurelib/textures/void.png differ