diff --git a/README.md b/README.md index 52b4fda..45459ba 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,4 @@ The Inventory Tweaks code is very unpleasant to work with. I rather write my own ### Credits Dropoff Feature - https://www.curseforge.com/minecraft/mc-mods/dropoff - https://github.com/SCP002/DropOff/tree/1.7.10 +Usage Ticker - Quark - https://github.com/VazkiiMods/Quark/blob/1.14/src/main/java/vazkii/quark/client/module/UsageTickerModule.java diff --git a/dependencies.gradle b/dependencies.gradle index 16a2fdd..55dd170 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -60,8 +60,8 @@ dependencies { compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.13.3-GTNH:dev") compileOnly("com.github.GTNewHorizons:CodeChickenCore:1.4.1:dev") compileOnly("com.github.GTNewHorizons:NotEnoughItems:2.7.18-GTNH:dev") - devOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.51.67:dev") - runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.12.3-GTNH:dev") + compileOnly("com.github.GTNewHorizons:GT5-Unofficial:5.09.51.67:dev") + compileOnly("com.github.GTNewHorizons:Botania:1.12.3-GTNH:dev") diff --git a/src/main/java/com/cleanroommc/bogosorter/client/usageticker/LICENSE.md b/src/main/java/com/cleanroommc/bogosorter/client/usageticker/LICENSE.md index b189805..9fa28bd 100644 --- a/src/main/java/com/cleanroommc/bogosorter/client/usageticker/LICENSE.md +++ b/src/main/java/com/cleanroommc/bogosorter/client/usageticker/LICENSE.md @@ -1,4 +1,4 @@ -All code in this directory is under the following license and the entire module can be disable: +All code in this directory is under the following license and the entire module can be disable in the config file: # Attribution-NonCommercial-ShareAlike 3.0 Unported diff --git a/src/main/java/com/cleanroommc/bogosorter/client/usageticker/UsageTicker.java b/src/main/java/com/cleanroommc/bogosorter/client/usageticker/UsageTicker.java index e69de29..750ac72 100644 --- a/src/main/java/com/cleanroommc/bogosorter/client/usageticker/UsageTicker.java +++ b/src/main/java/com/cleanroommc/bogosorter/client/usageticker/UsageTicker.java @@ -0,0 +1,223 @@ +package com.cleanroommc.bogosorter.client.usageticker; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.item.ItemStack; +import net.minecraftforge.client.event.RenderGameOverlayEvent; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import com.gtnewhorizon.gtnhlib.eventbus.EventBusSubscriber; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; + +// todo: backhand compat +@EventBusSubscriber(side = { Side.CLIENT }) +public class UsageTicker { + + public static boolean enableMainHand = true; + public static boolean enableArmor = true; + public static boolean enableModule = true; + + private static List elements = new ArrayList<>(); + + public static void reloadElements() { + elements = new ArrayList<>(); + + if (!enableModule) return; + + if (enableMainHand) { + elements.add(new Element(EquipmentSlotType.MAINHAND)); + } + if (enableArmor) { + elements.add(new Element(EquipmentSlotType.HEAD)); + elements.add(new Element(EquipmentSlotType.CHEST)); + elements.add(new Element(EquipmentSlotType.LEGS)); + elements.add(new Element(EquipmentSlotType.FEET)); + } + } + + @EventBusSubscriber.Condition + public static boolean enableModule() { + return enableModule; + } + + @SubscribeEvent + public static void clientTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + Minecraft mc = Minecraft.getMinecraft(); + if (mc.thePlayer != null) { + elements.forEach(e -> e.tick(mc.thePlayer)); + } + } + } + + @SubscribeEvent + public static void renderHUD(RenderGameOverlayEvent.Post event) { + if (event.type == RenderGameOverlayEvent.ElementType.HOTBAR) { + EntityClientPlayerMP player = Minecraft.getMinecraft().thePlayer; + ScaledResolution res = event.resolution; + float partialTicks = event.partialTicks; + elements.forEach(e -> e.render(res, player, partialTicks)); + } + } + + public static class Element { + + private static final int MAX_ANIM_TICKS = 60; + private static final int ANIM_TICKS = 5; + + EquipmentSlotType slot; + ItemStack currentStack = null; + int currentCount; + int showTicks; + + public Element(EquipmentSlotType slotType) { + this.slot = slotType; + } + + public void render(ScaledResolution resolution, EntityClientPlayerMP player, float partialTicks) { + if (showTicks > 0) { + + float animProgress; + + if (showTicks < ANIM_TICKS) animProgress = Math.max(0, showTicks - partialTicks) / ANIM_TICKS; + else animProgress = Math.min(ANIM_TICKS, (MAX_ANIM_TICKS - showTicks) + partialTicks) / ANIM_TICKS; + + float anim = -animProgress * (animProgress - 2) * 19F; + + float x = resolution.getScaledWidth() / 2f; + float y = resolution.getScaledHeight() - anim; + + int barWidth = 190; + boolean armor = slot != EquipmentSlotType.MAINHAND; + + int slots = armor ? 4 : 1; + int index = slots - slot.ordinal() - 1; + + Minecraft mc = Minecraft.getMinecraft(); + + x -= (barWidth / 2f) - index * 20; + x -= slots * 20; + + ItemStack stack = getRenderedStack(player); + if (stack == null) return; + + RenderItem renderer = new RenderItem(); + + GL11.glPushMatrix(); + GL11.glTranslatef(x, y, 0); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.enableGUIStandardItemLighting(); + renderer.renderItemAndEffectIntoGUI(mc.fontRenderer, mc.renderEngine, stack, 0, 0); + renderer.renderItemOverlayIntoGUI(mc.fontRenderer, mc.renderEngine, stack, 0, 0); + RenderHelper.disableStandardItemLighting(); + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + GL11.glPopMatrix(); + } + } + + public void tick(EntityClientPlayerMP player) { + ItemStack stack = getStack(player); + + int count = getStackCount(player, stack); + stack = getDisplayedStack(stack, count); + if (stack == null) { + showTicks = 0; + } else if (stackChanged(stack, currentStack, count, currentCount)) { + boolean done = showTicks == 0; + boolean animatingIn = showTicks > MAX_ANIM_TICKS - ANIM_TICKS; + boolean animatingOut = showTicks < ANIM_TICKS && !done; + if (animatingOut) showTicks = MAX_ANIM_TICKS - showTicks; + else if (!animatingIn) { + if (!done) showTicks = MAX_ANIM_TICKS - ANIM_TICKS; + else showTicks = MAX_ANIM_TICKS; + } + } else if (showTicks > 0) { + showTicks--; + } + + currentStack = stack; + currentCount = count; + } + + private boolean stackChanged(ItemStack currentStack, ItemStack prevStack, int currentCount, int pastCount) { + if (currentStack == null && prevStack == null) return false; + if (currentStack == null) return true; + if (prevStack == null) return true; + + return !prevStack.isItemEqual(currentStack) || currentCount != pastCount; + } + + @Nullable + public ItemStack getStack(EntityClientPlayerMP player) { + if (slot == EquipmentSlotType.MAINHAND) { + return player.inventory.getCurrentItem(); + } + + return player.inventory.armorItemInSlot(slot.ordinal() - 1); + } + + public ItemStack getRenderedStack(EntityClientPlayerMP player) { + ItemStack curStack = getStack(player); + if (curStack == null) return null; + int count = getStackCount(player, curStack); + ItemStack disStack = getDisplayedStack(curStack, count); + if (disStack == null) { + return null; + } + // only change the stack size of the copy + disStack = disStack.copy(); + if (disStack != curStack) { + count = getStackCount(player, disStack); + } + disStack.stackSize = count; + return disStack; + } + + public ItemStack getDisplayedStack(ItemStack stack, int count) { + if (stack == null) return null; + if (slot == EquipmentSlotType.MAINHAND && !stack.isStackable()) return null; + if (count == stack.stackSize) return null; + + return stack; + } + + private int getStackCount(EntityClientPlayerMP player, ItemStack stack) { + if (stack == null) return 0; + + int total = 0; + for (int i = 0; i < player.inventory.mainInventory.length; i++) { + ItemStack invStack = player.inventory.getStackInSlot(i); + if (invStack == null) continue; + + if (invStack.isItemEqual(stack) && invStack.getItemDamage() == stack.getItemDamage()) { + total += invStack.stackSize; + } + } + + return total; + } + + } + + public enum EquipmentSlotType { + MAINHAND, + HEAD, + CHEST, + LEGS, + FEET + } + +} diff --git a/src/main/java/com/cleanroommc/bogosorter/common/config/BogoSorterConfig.java b/src/main/java/com/cleanroommc/bogosorter/common/config/BogoSorterConfig.java index 19ad9a1..31800f2 100644 --- a/src/main/java/com/cleanroommc/bogosorter/common/config/BogoSorterConfig.java +++ b/src/main/java/com/cleanroommc/bogosorter/common/config/BogoSorterConfig.java @@ -10,6 +10,7 @@ import com.cleanroommc.bogosorter.BogoSortAPI; import com.cleanroommc.bogosorter.api.SortRule; +import com.cleanroommc.bogosorter.client.usageticker.UsageTicker; import com.cleanroommc.bogosorter.common.HotbarSwap; import com.cleanroommc.bogosorter.common.dropoff.DropOffButtonHandler; import com.cleanroommc.bogosorter.common.dropoff.DropOffHandler; @@ -79,6 +80,12 @@ public static void save(JsonObject json) { } } json.add("NbtSortRules", jsonRules); + + JsonObject usageTicker = new JsonObject(); + usageTicker.addProperty("enableModule", UsageTicker.enableModule); + usageTicker.addProperty("enableMainHand", UsageTicker.enableMainHand); + usageTicker.addProperty("enableArmor", UsageTicker.enableArmor); + json.add("UsageTicker", usageTicker); } @SideOnly(Side.CLIENT) @@ -149,6 +156,12 @@ public static void load(JsonObject json) { } } } + if (json.has("UsageTicker")) { + JsonObject ticker = json.getAsJsonObject("UsageTicker"); + UsageTicker.enableModule = JsonHelper.getBoolean(ticker, true, "enableModule"); + UsageTicker.enableMainHand = JsonHelper.getBoolean(ticker, true, "enableMainHand"); + UsageTicker.enableArmor = JsonHelper.getBoolean(ticker, true, "enableArmor"); + } } public static void saveOrePrefixes(JsonObject json) { diff --git a/src/main/java/com/cleanroommc/bogosorter/common/config/ConfigGui.java b/src/main/java/com/cleanroommc/bogosorter/common/config/ConfigGui.java index b5cf7b8..b763a8f 100644 --- a/src/main/java/com/cleanroommc/bogosorter/common/config/ConfigGui.java +++ b/src/main/java/com/cleanroommc/bogosorter/common/config/ConfigGui.java @@ -14,6 +14,7 @@ import com.cleanroommc.bogosorter.BogoSorter; import com.cleanroommc.bogosorter.ClientEventHandler; import com.cleanroommc.bogosorter.api.SortRule; +import com.cleanroommc.bogosorter.client.usageticker.UsageTicker; import com.cleanroommc.bogosorter.common.HotbarSwap; import com.cleanroommc.bogosorter.common.SortConfigChangeEvent; import com.cleanroommc.bogosorter.common.dropoff.DropOffButtonHandler; @@ -288,6 +289,64 @@ public IWidget createGeneralConfigUI(GuiContext context) { .background(IDrawable.EMPTY)) .child( IKey.lang("bogosort.gui.dropoff_chatmessage") + .asWidget() + .height(14) + .marginLeft(10) + .expanded())) + .child( + new Row().widthRel(1f) + .height(14) + .margin(0, 2) + .child(new CycleButtonWidget().value(new BoolValue.Dynamic(() -> UsageTicker.enableModule, val -> { + UsageTicker.enableModule = val; + UsageTicker.reloadElements(); + })) + .stateOverlay(TOGGLE_BUTTON) + .disableHoverBackground() + .size(14, 14) + .margin(8, 0) + .background(IDrawable.EMPTY)) + .child( + IKey.lang("bogosort.gui.usageticker_enable") + .asWidget() + .height(14) + .marginLeft(10) + .expanded())) + .child( + new Row().widthRel(1f) + .height(14) + .margin(0, 2) + .child( + new CycleButtonWidget().value(new BoolValue.Dynamic(() -> UsageTicker.enableMainHand, val -> { + UsageTicker.enableMainHand = val; + UsageTicker.reloadElements(); + })) + .stateOverlay(TOGGLE_BUTTON) + .disableHoverBackground() + .size(14, 14) + .margin(8, 0) + .background(IDrawable.EMPTY)) + .child( + IKey.lang("bogosort.gui.usageticker_mainhand") + .asWidget() + .height(14) + .marginLeft(10) + .expanded())) + .child( + new Row().widthRel(1f) + .height(14) + .margin(0, 2) + .child(new CycleButtonWidget().value(new BoolValue.Dynamic(() -> UsageTicker.enableArmor, val -> { + UsageTicker.enableArmor = val; + UsageTicker.reloadElements(); + })) + .stateOverlay(TOGGLE_BUTTON) + .disableHoverBackground() + .size(14, 14) + .margin(8, 0) + .background(IDrawable.EMPTY)) + .child( + IKey.lang("bogosort.gui.usageticker_armor") .asWidget() .height(14) .marginLeft(10) diff --git a/src/main/java/com/cleanroommc/bogosorter/common/config/Serializer.java b/src/main/java/com/cleanroommc/bogosorter/common/config/Serializer.java index e88bf23..420a965 100644 --- a/src/main/java/com/cleanroommc/bogosorter/common/config/Serializer.java +++ b/src/main/java/com/cleanroommc/bogosorter/common/config/Serializer.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.StringUtils; +import com.cleanroommc.bogosorter.client.usageticker.UsageTicker; import com.cleanroommc.bogosorter.common.network.NetworkUtils; import com.cleanroommc.bogosorter.common.sort.SortHandler; import com.cleanroommc.bogosorter.core.BogoSorterCore; @@ -63,6 +64,7 @@ public static void loadConfig() { BogoSorterCore.LOGGER.error("Error loading config!"); } else { BogoSorterConfig.load(jsonElement.getAsJsonObject()); + UsageTicker.reloadElements(); SortHandler.cacheItemSortRules.put(Minecraft.getMinecraft().thePlayer, BogoSorterConfig.sortRules); SortHandler.cacheNbtSortRules.put(Minecraft.getMinecraft().thePlayer, BogoSorterConfig.nbtSortRules); } diff --git a/src/main/resources/assets/bogosorter/lang/en_US.lang b/src/main/resources/assets/bogosorter/lang/en_US.lang index cb0553d..a6e7209 100644 --- a/src/main/resources/assets/bogosorter/lang/en_US.lang +++ b/src/main/resources/assets/bogosorter/lang/en_US.lang @@ -23,6 +23,9 @@ bogosort.gui.dropoff_enable=Enable Dropoff bogosort.gui.dropoffbutton_enable=Show Dropoff Button in Inventory bogosort.gui.dropoff_render=Show Dropoff Targets bogosort.gui.dropoff_chatmessage=Show Dropoff Chat Message +bogosort.gui.usageticker_enable=Enable Usage Ticker +bogosort.gui.usageticker_mainhand=Enable Usage Ticker Mainhand +bogosort.gui.usageticker_armor=Enable Usage Ticker Armor