package electroblob.wizardry.client.gui; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import electroblob.wizardry.Settings; import electroblob.wizardry.Wizardry; import electroblob.wizardry.client.ClientProxy; import electroblob.wizardry.client.DrawingUtils; import electroblob.wizardry.client.MixedFontRenderer; import electroblob.wizardry.data.SpellGlyphData; import electroblob.wizardry.data.WizardData; import electroblob.wizardry.item.ISpellCastingItem; import electroblob.wizardry.registry.Spells; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.spell.Spell; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager.DestFactor; import net.minecraft.client.renderer.GlStateManager.SourceFactor; import net.minecraft.client.resources.IResource; import net.minecraft.client.resources.IResourceManager; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumHandSide; import net.minecraft.util.JsonUtils; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.relauncher.Side; import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.Map.Entry; @Mod.EventBusSubscriber(Side.CLIENT) public class GuiSpellDisplay { private static final ResourceLocation INDEX = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud/_index.json"); private static final ResourceLocation CHARGE_METER = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_charge_meter.png"); /** A map which stores all loaded HUD skin objects. This gets wiped on resource pack reload and repopulated with * mappings as specified by {@code _index.json} (these stack between resource packs). The keys in the map correspond * to the keys in {@code _index.json}, and are sorted in that order, with skins belonging to resource packs sorted * from lowest to highest priority. The skins in the base mod will therefore always be first. (It should be noted, * however, that in the gui itself the skins are always sorted in alphabetical order for some reason.) */ private static final Map skins = new LinkedHashMap<>(14); // 14 is the number of skins packaged with the mod private static final Gson gson = new Gson(); private static final Random random = new Random(); /** Width of the charge meter. */ private static final int CHARGE_METER_WIDTH = 25; /** Height of the charge meter. */ private static final int CHARGE_METER_HEIGHT = 9; /** Width and height of the spell icon (very unlikely to change!) */ private static final int SPELL_ICON_SIZE = 32; /** Number of ticks the spell switching animation plays for. */ private static final int SPELL_SWITCH_TIME = 4; /** Scale of the next/previous spell names. */ private static final float SPELL_NAME_SCALE = 0.5f; /** Opacity of the next/previous spell names, as a fraction. */ private static final float SPELL_NAME_OPACITY = 0.3f; private static final int HALF_HOTBAR_WIDTH = 97; // Half the width of the hotbar, plus a bit for clearance private static final int OFFHAND_SLOT_WIDTH = 29; // Width of the offhand slot plus the gap between it and the hotbar /** Controls the spell switching animation. Positive when switching to the next spell, negative when switching to * the previous spell. Decremented in magnitude by 1 each tick until it reaches 0 again. */ private static int switchTimer = 0; /** * Starts the spell switching animation. * @param next True to switch to the next spell, false for the previous spell. */ public static void playSpellSwitchAnimation(boolean next){ switchTimer = next ? SPELL_SWITCH_TIME : -SPELL_SWITCH_TIME; } /** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */ public static Set getSkinKeys(){ return Collections.unmodifiableSet(skins.keySet()); } /** Returns an unmodifiable view of the loaded spell HUD skins map. */ public static Map getSkins(){ return Collections.unmodifiableMap(skins); } /** Returns the skin that corresponds to the given key. If no such skin exists, returns the default skin as a * fallback. If the fallback skin is missing, prints a warning to the console and returns null. */ @Nullable public static Skin getSkin(String key){ Skin skin = skins.get(key); if(skin == null){ Wizardry.logger.info("The spell HUD skin '" + Wizardry.settings.spellHUDSkin + "' specified in the config" + " did not match any of the loaded skins; using the default skin as a fallback."); skin = skins.get(Settings.DEFAULT_HUD_SKIN_KEY); if(skin == null){ Wizardry.logger.warn("The default spell HUD skin is missing! A resource pack must have overridden it" + " with an invalid JSON file (default.json), please try again without any resource packs."); return null; } } return skin; } // Normally when extending Gui, you'd have to have an instance to access its methods. However, we're not actually // using any of them, so this class may as well not bother and just be a static event handler. Neat! @SubscribeEvent public static void draw(RenderGameOverlayEvent.Post event){ if(!Wizardry.settings.showSpellHUD && !Wizardry.settings.showChargeMeter) return; // Optimisation EntityPlayer player = Minecraft.getMinecraft().player; if(player.isSpectator()) return; // Spectators shouldn't have the spell HUD! // If the player has a wand in each hand, only displays for the one in the main hand. ItemStack wand = player.getHeldItemMainhand(); boolean mainHand = true; if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))){ wand = player.getHeldItemOffhand(); mainHand = false; // If the player isn't holding a spellcasting item that shows the HUD, then nothing else needs to be done. if(!(wand.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)wand.getItem()).showSpellHUD(player, wand))) return; } int width = event.getResolution().getScaledWidth(); int height = event.getResolution().getScaledHeight(); switch(event.getType()){ case CROSSHAIRS: renderChargeMeter(player, wand, width, height, event.getPartialTicks()); break; case HOTBAR: renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), false); break; case TEXT: renderSpellHUD(player, wand, mainHand, width, height, event.getPartialTicks(), true); break; } } /** * Renders the spell charge meter around the crosshairs. * @param player A reference to the client player * @param wand The wand the HUD is for * @param width The width of the screen * @param height The height of the screen * @param partialTicks The current partial tick time */ private static void renderChargeMeter(EntityPlayer player, ItemStack wand, int width, int height, float partialTicks){ if(!Wizardry.settings.showChargeMeter) return; if(Minecraft.getMinecraft().gameSettings.showDebugInfo) return; // Don't show charge meter in the debug screen if(Minecraft.getMinecraft().gameSettings.thirdPersonView != 0) return; // Don't show in third person if(wand != player.getActiveItemStack()) return; // Don't show when using the other held item if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!"); Spell spell = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand); int chargeup = spell.getChargeup(); if(WizardData.get(player) != null){ // Pretty sure this is accessible client-side since it's only assigned from common code chargeup = (int)(chargeup * WizardData.get(player).itemCastingModifiers.get(SpellModifiers.CHARGEUP)); } if(chargeup <= 0) return; // WHY WHY WHY are these methods named so misleadingly?! Sort yourselves out MCP! // (getItemInUseCount returns the max count MINUS the use count, and getItemInUseMaxCount returns the use count) if(player.getItemInUseMaxCount() == 0) return; // Not charging float charge = (player.getItemInUseMaxCount() + partialTicks) / chargeup; if(charge > 1) return; // Done charging Minecraft.getMinecraft().renderEngine.bindTexture(CHARGE_METER); int x1 = width/2 - CHARGE_METER_WIDTH/2; int y = height/2 - CHARGE_METER_HEIGHT/2; int w = (int)(CHARGE_METER_WIDTH/2 * charge); int u = CHARGE_METER_WIDTH - w; DrawingUtils.drawTexturedRect(x1, y, 0, 0, w, CHARGE_METER_HEIGHT, 32, 32); DrawingUtils.drawTexturedRect(x1 + u, y, u, 0, w, CHARGE_METER_HEIGHT, 32, 32); } /** * Renders the main spell HUD in the corner of the screen. * @param player A reference to the client player * @param wand The wand the HUD is for * @param mainHand True if the wand is in the player's main hand, false if it is in their offhand * @param width The width of the screen * @param height The height of the screen * @param partialTicks The current partial tick time * @param textLayer True to render the text layer, false to render the background (hotbar layer) */ private static void renderSpellHUD(EntityPlayer player, ItemStack wand, boolean mainHand, int width, int height, float partialTicks, boolean textLayer){ if(!Wizardry.settings.showSpellHUD) return; if(!(wand.getItem() instanceof ISpellCastingItem)) throw new IllegalArgumentException("The given stack must contain an ISpellCastingItem!"); boolean flipX = Wizardry.settings.spellHUDPosition.flipX; boolean flipY = Wizardry.settings.spellHUDPosition.flipY; if(Wizardry.settings.spellHUDPosition.dynamic){ // ............. | This bit is true if the wand is on the left, false if it is on the right flipX = flipX == ((mainHand ? player.getPrimaryHand() : player.getPrimaryHand().opposite()) == EnumHandSide.LEFT); } Skin skin = getSkin(Wizardry.settings.spellHUDSkin); if(skin == null) return; // Can't draw anything if there is no skin loaded! GlStateManager.pushMatrix(); // 'Origin' of the spell hud (bottom left corner of the actual texture, always in the corner of the screen) int x = flipX ? width : 0; int y = flipY ? 0: height; // The space available to render the spell HUD float xSpace = (float)(width/2 - HALF_HOTBAR_WIDTH); if(!player.getHeldItemOffhand().isEmpty() // Tests whether the offhand slot is rendered on the same side of the hotbar as the spell HUD && (player.getPrimaryHand() == EnumHandSide.LEFT) == flipX){ xSpace -= OFFHAND_SLOT_WIDTH; } // If the skin is at the bottom and the screen width is too small, scale it to avoid the hotbar and offhand if(!flipY && skin.getWidth() > xSpace){ // width/2 - 91 - 29 taken from GuiInGame line 547 float scale = xSpace / skin.getWidth(); GlStateManager.scale(scale, scale, 1); x = MathHelper.ceil(x/scale); y = MathHelper.ceil(y/scale); } Spell spell = ((ISpellCastingItem)wand.getItem()).getCurrentSpell(wand); int cooldown = ((ISpellCastingItem)wand.getItem()).getCurrentCooldown(wand); int maxCooldown = ((ISpellCastingItem)wand.getItem()).getCurrentMaxCooldown(wand); if(textLayer){ float animationProgress = Math.signum(switchTimer) * ((SPELL_SWITCH_TIME - Math.abs(switchTimer) + partialTicks) / SPELL_SWITCH_TIME); String prevSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getPreviousSpell(wand), player, 0); String spellName = getFormattedSpellName(spell, player, cooldown); String nextSpellName = getFormattedSpellName(((ISpellCastingItem)wand.getItem()).getNextSpell(wand), player, 0); skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress); }else{ boolean discovered = true; if(!player.isCreative() && WizardData.get(player) != null){ discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); } ResourceLocation icon = discovered ? spell.getIcon() : Spells.none.getIcon(); float progress = 1; // Doesn't really matter what progress is when in creative, but we might as well avoid the calculation. if(!player.isCreative()){ // Subtracted partial tick time to make it smoother progress = maxCooldown == 0 ? 1 : (maxCooldown - (float)cooldown + partialTicks) / maxCooldown; } skin.drawBackground(x, y, flipX, flipY, icon, progress, player.isCreative(), player.isPotionActive(WizardryPotions.arcane_jammer)); } GlStateManager.popMatrix(); } /** * Gets the name of the given spell, with formatting added according to its cooldown and whether the given player * has discovered it. * @param spell The spell to get the name of. * @param player The player to test for having discovered the given spell. * @param cooldown The spell's current cooldown. * @return The spell name, with relevant formatting added, for use with the {@link MixedFontRenderer}. */ private static String getFormattedSpellName(Spell spell, EntityPlayer player, int cooldown){ boolean discovered = true; if(!player.isCreative() && WizardData.get(player) != null){ discovered = WizardData.get(player).hasSpellBeenDiscovered(spell); } // Makes spells greyed out if they are in cooldown String format = cooldown > 0 ? "\u00A78" : spell.getElement().getFormattingCode(); if(!discovered) format = "\u00A79"; // Obfuscates the spell name if the player has the arcane jammer effect if(player.isPotionActive(WizardryPotions.arcane_jammer)) format = format + "\u00A7k"; String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world); name = format + name; if(!discovered) name = "#" + name + "#"; return name; } @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ if(event.getEntity() == Minecraft.getMinecraft().player){ // Makes sure this only gets called once each tick. if(switchTimer > 0) switchTimer--; else if(switchTimer < 0) switchTimer++; } } /** Called from preInit in the main mod class (via the proxies) to initialise the HUD skins, and again on each * resource reload. */ public static void loadSkins(IResourceManager manager){ try { List indexFiles = manager.getAllResources(INDEX); skins.clear(); // Wipes the skins map before repopulating it for(IResource indexFile : indexFiles){ BufferedReader reader = new BufferedReader(new InputStreamReader(indexFile.getInputStream(), StandardCharsets.UTF_8)); JsonElement je = gson.fromJson(reader, JsonElement.class); JsonObject json = je.getAsJsonObject(); // Need to iterate over these since we don't know what they're called or how many there are for(Entry entry : json.entrySet()){ String key = entry.getKey(); // Find out what each element is called, this will be the skins map key // It's a good idea to use JsonUtils because it produces more helpful error messages (that pack // makers should understand). JsonObject skinData = JsonUtils.getJsonObject(json, key); String[] splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "texture")); ResourceLocation texture = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".png"); splitName = ResourceLocation.splitObjectName(JsonUtils.getString(skinData, "metadata")); ResourceLocation metadata = new ResourceLocation(splitName[0], "textures/" + splitName[1] + ".json"); // The nice thing about this is it overwrites the existing mapping, and since the index files are in // ascending order of resource pack priority, this means resource packs can override existing skins // by specifying one with the same key. skins.put(key, new Skin(texture, metadata)); } } } catch (IOException e){ // If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing // else needs to be done. Wizardry.logger.error("Error reading spell HUD skin index file: ", e); } } /** * Instances of this class represent individual HUD skins, complete with texture and all necessary metadata. This * class serves to separate the logic behind the spell HUD from its actual rendering. * All information and processing done within this class relates only to the actual drawing; spells and such like * must be queried outside of this class and fed into the methods as appropriate. * * @author Electroblob * @since Wizardry 4.2 */ public static class Skin { /** The texture file for this skin. */ private final ResourceLocation texture; /** The display name of the skin in the config menu. */ private String name; /** The description of the skin shown when its button is hovered over in the config menu. */ private String description; /** Width of the entire spell HUD. */ private int width; /** Height of the entire spell HUD. */ private int height; /** Whether the entire HUD is flipped when on the right-hand side of the screen. If this is false, the HUD will * still appear on the right-hand side of the screen, but in the same orientation as on the left-hand side. */ private boolean mirrorX; /** Whether the entire HUD is flipped when at the top of the screen. If this is false, the HUD will * still appear at the top of the screen, but in the same orientation as at the bottom. */ private boolean mirrorY; /** Distance of the spell icon from the left edge of the screen (or right edge when flipped). */ private int spellIconInsetX; /** Distance of the spell icon from the bottom edge of the screen (or top edge when flipped). */ private int spellIconInsetY; /** Distance of the spell name from the left edge of the screen (or right edge when flipped). */ private int textInsetX; /** Distance of the spell name from the bottom edge of the screen (or the top edge when flipped). */ private int textInsetY; /** Horizontal distance between the start of adjacent spell names. */ private int cascadeOffsetX; /** Vertical distance between the start of adjacent spell names. */ private int cascadeOffsetY; /** Distance of the cooldown bar from the left edge of the screen (or right edge when flipped). */ private int cooldownBarX; /** Distance of the cooldown bar from the bottom edge of the screen (or top edge when flipped). */ private int cooldownBarY; /** Length of the cooldown bar. */ private int cooldownBarLength; /** Height of the cooldown bar. */ private int cooldownBarHeight; /** Whether the cooldown bar is flipped horizontally when the HUD is on the right-hand side of the screen. */ private boolean cooldownBarMirrorX; /** Whether the cooldown bar is flipped vertically when the HUD is at the top of the screen. */ private boolean cooldownBarMirrorY; /** Whether the cooldown bar progress overlay is shown when the cooldown bar is full (i.e. when progress = 1). */ private boolean showCooldownWhenFull; private final Minecraft mc; /** Creates a new skin with the given texture and reads its values from the given metadata json file. */ public Skin(ResourceLocation texture, ResourceLocation metadata){ mc = Minecraft.getMinecraft(); this.texture = texture; try { // This time we only want the highest priority file IResource metadataFile = Minecraft.getMinecraft().getResourceManager().getResource(metadata); BufferedReader reader = new BufferedReader(new InputStreamReader(metadataFile.getInputStream(), StandardCharsets.UTF_8)); JsonElement je = gson.fromJson(reader, JsonElement.class); parseJson(je.getAsJsonObject()); } catch (IOException e){ // If an exception is thrown, chances are the resource pack did not have a spell_hud folder, so nothing // else needs to be done. Wizardry.logger.error("Error reading spell HUD skin metadata file: ", e); } } /** Returns the display name of this HUD skin, which is shown in the config GUI. */ public String getName(){ return name; } /** Returns the description of this HUD skin, which is shown in its tooltip in the config GUI. */ public String getDescription(){ return description; } /** Returns the overall width of this spell HUD skin. */ public int getWidth(){ return width; } /** Returns the overall height of this spell HUD skin. */ public int getHeight(){ return height; } /** Actually reads the metadata values for this skin from the json file. */ private void parseJson(JsonObject json){ // For now, all the keys must be present for the metadata file to work (the only ones that could reasonably // have a default anyway are the mirror values). name = JsonUtils.getString(json, "name"); description = JsonUtils.getString(json, "description"); width = JsonUtils.getInt(json, "width"); if(width > 128) Wizardry.logger.warn("The width of the spell HUD skin " + name + " exceeds 128, this may cause it to render strangely."); height = JsonUtils.getInt(json, "height"); JsonObject mirror = JsonUtils.getJsonObject(json, "mirror"); mirrorX = JsonUtils.getBoolean(mirror, "x"); mirrorY = JsonUtils.getBoolean(mirror, "y"); JsonObject spellIconInset = JsonUtils.getJsonObject(json, "spell_icon_inset"); spellIconInsetX = JsonUtils.getInt(spellIconInset, "x"); spellIconInsetY = JsonUtils.getInt(spellIconInset, "y"); JsonObject textInset = JsonUtils.getJsonObject(json, "text_inset"); textInsetX = JsonUtils.getInt(textInset, "x"); textInsetY = JsonUtils.getInt(textInset, "y"); JsonObject cascadeOffset = JsonUtils.getJsonObject(json, "spell_cascade_offset"); cascadeOffsetX = JsonUtils.getInt(cascadeOffset, "x"); cascadeOffsetY = JsonUtils.getInt(cascadeOffset, "y"); JsonObject cooldownBar = JsonUtils.getJsonObject(json, "cooldown_bar"); cooldownBarX = JsonUtils.getInt(cooldownBar, "x"); cooldownBarY = JsonUtils.getInt(cooldownBar, "y"); cooldownBarLength = JsonUtils.getInt(cooldownBar, "length"); cooldownBarHeight = JsonUtils.getInt(cooldownBar, "height"); JsonObject cooldownBarMirror = JsonUtils.getJsonObject(cooldownBar, "mirror"); cooldownBarMirrorX = JsonUtils.getBoolean(cooldownBarMirror, "x"); cooldownBarMirrorY = JsonUtils.getBoolean(cooldownBarMirror, "y"); showCooldownWhenFull = JsonUtils.getBoolean(cooldownBar, "show_when_full"); } // The idea of these methods is that everything in here relates only to the actual drawing of the HUD. In other // words, all processing of which spells to draw and so on is done outside of here. This means that the config // GUI can easily display its preview without having a player or wand stack object to query. /** * Draws the background layer of this HUD skin at the given position with the given orientations, with the given * spell icon and cooldown bar progress. * * @param x The x-coordinate of the corner of the spell HUD. The bottom left corner of the actual texture * will always be at this position unless mirrorX/Y is false, so for example if flipX is false and flipY is true, * this will be the corner of the HUD that is closest to the top left corner of the screen. * @param y The y-coordinate of the corner of the spell HUD; see above. * @param flipX Whether to flip the HUD horizontally. * @param flipY Whether to flip the HUD vertically. * @param icon A {@code ResourceLocation} corresponding to the icon of the selected spell. * @param cooldownBarProgress The fraction of the cooldown bar to draw; must be between 0 and 1 (inclusive). * @param creativeMode True to draw the creative mode HUD, false for the survival mode version. * @param jammed True to show the 'glitch' effect (user for arcane jammer), false to draw normally. */ public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode, boolean jammed){ // Moves the origin if the HUD does not mirror; neatens the rest of the code. if(flipX && !mirrorX) x -= width; if(flipY && !mirrorY) y += height; GlStateManager.pushMatrix(); GlStateManager.enableBlend(); GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA); GlStateManager.color(1, 1, 1); // Spell illustration - this is now done first so it is behind the HUD texture mc.renderEngine.bindTexture(icon); int x1 = flipX && mirrorX ? x - spellIconInsetX - SPELL_ICON_SIZE : x + spellIconInsetX; // y is upside-down so this is the other way round int y1 = flipY && mirrorY ? y + spellIconInsetY : y - spellIconInsetY - SPELL_ICON_SIZE; if(jammed){ random.setSeed(Minecraft.getMinecraft().world.getTotalWorldTime() / 2); DrawingUtils.drawGlitchRect(random, x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, false, false); }else{ DrawingUtils.drawTexturedRect(x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE); } // Background of spell hud mc.renderEngine.bindTexture(texture); x1 = flipX && mirrorX ? x - width : x; y1 = flipY && mirrorY ? y : y - height; // The 128 here is a uv value, not a dimension, and hence is left as a hardcoded number. // TODO: Since the HUD is wider than it is tall, perhaps the creative mode texture should be in the bottom half instead of the right half? if(jammed){ DrawingUtils.drawGlitchRect(random, x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY); }else{ DrawingUtils.drawTexturedFlippedRect(x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY); } // Cooldown bar if(!creativeMode && cooldownBarProgress > 0 && (showCooldownWhenFull || cooldownBarProgress < 1)){ int l = (int)(cooldownBarProgress * cooldownBarLength); x1 = flipX && mirrorX ? x - cooldownBarX - (cooldownBarMirrorX ? l : cooldownBarLength) : x + cooldownBarX; y1 = flipY && mirrorY ? y + cooldownBarY : y - cooldownBarY - cooldownBarHeight; int u = cooldownBarX; // This doesn't change, even when cooldownBarMirrorX is true, because it should int v = height; // always start with the left-hand in the actual texture file if(jammed){ DrawingUtils.drawGlitchRect(random, x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY); }else{ DrawingUtils.drawTexturedFlippedRect(x1, y1, u, v, l, cooldownBarHeight, 256, 256, flipX && cooldownBarMirrorX, flipY && cooldownBarMirrorY); } } GlStateManager.popMatrix(); // Blend needs to be left enabled here because otherwise the hotbar becomes opaque } /** * Draws the text layer of this HUD skin at the given position with the given orientations, with the given * spell name strings. * * @param x The x-coordinate of the corner of the spell HUD. The bottom left corner of the actual texture * will always be at this position, so for example if flipX is false and flipY is true, this will be the corner * of the HUD that is closest to the top left corner of the screen. * @param y The y-coordinate of the corner of the spell HUD; see above. * @param flipX Whether to flip the HUD horizontally. * @param flipY Whether to flip the HUD vertically. * @param prevSpellName The name of the previous spell. This string will be drawn directly using the * {@link MixedFontRenderer}; as such it should be supplied with formatting codes and # characters already * appended. * @param spellName The name of the currently selected spell; see above. * @param nextSpellName The name of the next spell; see above. * @param animationProgress The progress of the spell switching animation, as a fraction between 0 and 1 * (inclusive). Positive values indicate switching forwards, negative values indicate switching backwards, and * a value of zero indicates that the spell is not currently being switched. */ public void drawText(int x, int y, boolean flipX, boolean flipY, String prevSpellName, String spellName, String nextSpellName, float animationProgress){ // Moves the origin if the HUD does not mirror; neatens the rest of the code. if(flipX && !mirrorX) x -= width; if(flipY && !mirrorY) y += height; FontRenderer font = ClientProxy.mixedFontRenderer; // On this occasion we're client-side so this is OK // Position of the selected spell name in normal display, also used for interpolation when animating int x1 = flipX && mirrorX ? x - width : x + textInsetX; // The text is an odd number of pixels high so we need to subtract an extra 1 when not flipped int y1 = flipY && mirrorY ? y + textInsetY - font.FONT_HEIGHT/2 + 2 : y - textInsetY - font.FONT_HEIGHT/2 - 1; int maxWidth = width - textInsetX; // Maximum width of the text if(animationProgress == 0){ // Normal display float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX; float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX; // Don't ask me why adding 1 to this makes it look more even, it just does! float yPrev = y1 - (cascadeOffsetY + 1); // No need to account for flipY because previous is always above. float yNext = y1 + cascadeOffsetY; // No need to account for flipY because next is always below. float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX; float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX; int nextPrevClr = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY); DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, SPELL_NAME_SCALE, nextPrevClr, maxWidthPrev, true, flipX && mirrorX); DrawingUtils.drawScaledStringToWidth(font, spellName, x1, y1, 1, 0xffffffff, maxWidth, true, flipX && mirrorX); DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, SPELL_NAME_SCALE, nextPrevClr, maxWidthNext, true, flipX && mirrorX); }else{ // Switching spells boolean reverse = animationProgress < 0; if(reverse) animationProgress = 1 - Math.abs(animationProgress); // Simplest way of reversing the animation float xPrev = flipX && mirrorX ? x - width : x + textInsetX - (flipY ? -1 : 1) * cascadeOffsetX * animationProgress; float xNext = flipX && mirrorX ? x - width : x + textInsetX + (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress); float yPrev = y1 - (cascadeOffsetY + 1) * animationProgress; // No need to account for flipY because previous is always above. float yNext = y1 + cascadeOffsetY * (1 - animationProgress); // No need to account for flipY because next is always below. float maxWidthPrev = maxWidth + (flipY ? -1 : 1) * cascadeOffsetX * animationProgress; float maxWidthNext = maxWidth - (flipY ? -1 : 1) * cascadeOffsetX * (1 - animationProgress); float scalePrev = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (1 - animationProgress); float scaleNext = SPELL_NAME_SCALE + (1 - SPELL_NAME_SCALE) * (animationProgress); int clrPrev = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * (1 - animationProgress)); int clrNext = DrawingUtils.makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * animationProgress); if(reverse){ // Switching to previous spell // Only renders the next spell and the current one DrawingUtils.drawScaledStringToWidth(font, spellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX); DrawingUtils.drawScaledStringToWidth(font, nextSpellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX); }else{ // Switching to next spell // Only renders the previous spell and the current one DrawingUtils.drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX); DrawingUtils.drawScaledStringToWidth(font, spellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX); } } } } }