Spell HUD rewrite and retexturing:

- Replaces the old hardcoded drawing methods with a new json-based system with support for multiple inbuilt and resource pack-defined skins.
- Adds a config option for choosing the spell HUD skin, along with a custom config GUI screen allowing the user to select from a list of available skins with a preview of the currently selected skin.
- Replaces the HUD texture file with a spell_hud subfolder, containing all the necessary textures and json files for the 12 inbuilt skins.
- Adds a subtle sound effect and scroling animation when switching spells
- Reorganises the control event code into a single handler class and converts this class and the spell HUD class to static event handlers.
- Fixes the HUD position option which got broken in the previous spell HUD commit
This commit is contained in:
Electroblob
2018-07-02 22:33:09 +01:00
parent b7cd3db8bc
commit af01beb583
42 changed files with 1393 additions and 223 deletions
@@ -46,13 +46,14 @@ public class CommonProxy {
public void registerKeyBindings(){
}
public void registerSpellHUD(){}
public net.minecraft.client.model.ModelBiped getWizardArmourModel(){
return null;
}
public void initGuiBits(){}
public void registerResourceReloadListener(){}
// SECTION Particles
// ===============================================================================================================
@@ -126,6 +127,8 @@ public class CommonProxy {
public void setToNumberSliderEntry(Property property){
}
public void setToHUDChooserEntry(Property property){}
// public void setToEntityNameEntry(Property property){}
/**
@@ -148,4 +151,9 @@ public class CommonProxy {
return null;
}
/** Returns an unmodifiable set of the string keys for all of the loaded spell HUD skins. */
public Set<String> getSpellHUDSkins(){
return null;
}
}
@@ -164,6 +164,10 @@ public final class Settings {
public boolean showSummonedCreatureNames = true;
/** <b>[Client-only]</b> The position of the spell HUD. */
public GuiPosition spellHUDPosition = GuiPosition.BOTTOM_LEFT;
public static final String DEFAULT_HUD_SKIN_KEY = "default"; // Defined here so it's not in a client-only class.
/** <b>[Client-only]</b> The string identifier of the skin used for the spell HUD. */
public String spellHUDSkin = DEFAULT_HUD_SKIN_KEY;
/** Set of constants for each of the four positions that the spell HUD can be in. */
public enum GuiPosition {
@@ -190,6 +194,8 @@ public final class Settings {
GuiPosition(String name, boolean flipX, boolean flipY){
this.name = name;
this.flipX = flipX;
this.flipY = flipY;
}
/**
@@ -458,6 +464,12 @@ public final class Settings {
property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_position");
spellHUDPosition = GuiPosition.fromName(property.getString());
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "spellHUDSkin", DEFAULT_HUD_SKIN_KEY, "The skin used for the spell HUD.", Wizardry.proxy.getSpellHUDSkins().toArray(new String[0]));
property.setLanguageKey("config." + Wizardry.MODID + ".spell_hud_skin");
Wizardry.proxy.setToHUDChooserEntry(property);
spellHUDSkin = property.getString();
propOrder.add(property.getName());
property = config.get(CLIENT_CATEGORY, "showSummonedCreatureNames", true, "Whether to show summoned creatures' names and owners above their heads.");
property.setLanguageKey("config." + Wizardry.MODID + ".show_summoned_creature_names");
@@ -120,6 +120,8 @@ public class Wizardry {
public void preInit(FMLPreInitializationEvent event){
logger = event.getModLog();
proxy.registerResourceReloadListener();
settings.initConfig(event);
@@ -162,9 +164,7 @@ public class Wizardry {
// Event Handlers
GameRegistry.registerWorldGenerator(generator, 0);
MinecraftForge.EVENT_BUS.register(new WizardryKeyHandler());
MinecraftForge.EVENT_BUS.register(instance);
proxy.registerSpellHUD(); // This can't easily be converted to use the new @Mod.EventBusSubscriber system
NetworkRegistry.INSTANCE.registerGuiHandler(this, new WizardryGuiHandler());
WizardryPacketHandler.initPackets();
@@ -1,68 +0,0 @@
package electroblob.wizardry;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import net.minecraft.client.Minecraft;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
public class WizardryKeyHandler {
boolean NkeyPressed = false;
boolean BkeyPressed = false;
boolean NkeyAlreadyPressed = false;
boolean BkeyAlreadyPressed = false;
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event){
// Key pressed
if(Keyboard.getEventKeyState()){
if(Wizardry.proxy instanceof ClientProxy){
if(ClientProxy.NEXT_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){
NkeyPressed = true;
}else{
NkeyAlreadyPressed = true;
}
if(!NkeyAlreadyPressed){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
}
if(ClientProxy.PREVIOUS_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){
BkeyPressed = true;
}else{
BkeyAlreadyPressed = true;
}
if(!BkeyAlreadyPressed){
// Packet building
IMessage msg = new PacketControlInput.Message(
PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
}
}
}
// Key released
else{
if(NkeyPressed){
NkeyPressed = false;
NkeyAlreadyPressed = false;
}else if(BkeyPressed){
BkeyPressed = false;
BkeyAlreadyPressed = false;
}
}
}
}
@@ -130,6 +130,8 @@ import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.RenderBlaze;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
@@ -185,17 +187,20 @@ public class ClientProxy extends CommonProxy {
ClientRegistry.registerKeyBinding(PREVIOUS_SPELL);
}
@Override
public void registerSpellHUD(){
MinecraftForge.EVENT_BUS.register(new GuiSpellDisplay(Minecraft.getMinecraft()));
}
@Override
public void initGuiBits(){
mixedFontRenderer = new MixedFontRenderer(Minecraft.getMinecraft().gameSettings, new ResourceLocation("textures/font/ascii.png"),
Minecraft.getMinecraft().renderEngine, false);
GuiWizardHandbook.initDisplayRecipes();
}
@Override
public void registerResourceReloadListener(){
IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
if(manager instanceof IReloadableResourceManager){
((IReloadableResourceManager)manager).registerReloadListener(GuiSpellDisplay::loadSkins);
}
}
// SECTION Misc
// ===============================================================================================================
@@ -204,6 +209,11 @@ public class ClientProxy extends CommonProxy {
public void setToNumberSliderEntry(Property property){
property.setConfigEntryClass(NumberSliderEntry.class);
}
@Override
public void setToHUDChooserEntry(Property property){
property.setConfigEntryClass(SpellHUDSkinChooserEntry.class);
}
@Override
public World getTheWorld(){
@@ -214,6 +224,11 @@ public class ClientProxy extends CommonProxy {
public void playMovingSound(Entity entity, SoundEvent sound, float volume, float pitch, boolean repeat){
Minecraft.getMinecraft().getSoundHandler().playSound(new MovingSoundEntity(entity, sound, volume, pitch, repeat));
}
@Override
public Set<String> getSpellHUDSkins(){
return GuiSpellDisplay.getSkinKeys();
}
// SECTION Items
// ===============================================================================================================
@@ -0,0 +1,38 @@
package electroblob.wizardry.client;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import electroblob.wizardry.client.gui.GuiSelectHUDSkin;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import net.minecraftforge.client.gui.ForgeGuiFactory.ForgeConfigGui.ModIDEntry;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
import net.minecraftforge.fml.client.config.IConfigElement;
/**
* Custom config GUI for spell HUD skin selection; displays a list of all the loaded skins and a preview of the currently
* selected skin. based off of {@link ModIDEntry} from Forge.
*/
public class SpellHUDSkinChooserEntry extends SelectValueEntry {
public SpellHUDSkinChooserEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
{
super(owningScreen, owningEntryList, prop, getSelectableValues());
if (this.selectableValues.size() == 0)
this.btnValue.enabled = false;
}
private static Map<Object, String> getSelectableValues(){
return GuiSpellDisplay.getSkins().entrySet().stream().collect(Collectors.toMap(Entry::getKey,
e -> e.getValue().getName()));
}
@Override // Copied from superclass to use custom child screen GUI class
public void valueButtonPressed(int slotIndex){
mc.displayGuiScreen(new GuiSelectHUDSkin(this.owningScreen, configElement, slotIndex, selectableValues, currentValue, enabled()));
}
}
@@ -7,8 +7,6 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.item.ItemSpectralBow;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Flight;
@@ -38,7 +36,6 @@ import net.minecraft.util.math.RayTraceResult;
import net.minecraft.village.MerchantRecipe;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiContainerEvent;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderLivingEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
@@ -47,7 +44,6 @@ import net.minecraftforge.client.event.TextureStitchEvent;
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.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -101,37 +97,6 @@ public final class WizardryClientEventHandler {
// }
}
// Shift-scrolling to change spells
@SubscribeEvent
public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.enableShiftScrolling){
event.setCanceled(true);
if(event.getDwheel() > 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}else if(event.getDwheel() < 0){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
}
}
}
@SubscribeEvent
public static void onFOVUpdateEvent(FOVUpdateEvent event){
@@ -0,0 +1,132 @@
package electroblob.wizardry.client;
import org.lwjgl.input.Keyboard;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.gui.GuiSpellDisplay;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.WandHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.PositionedSoundRecord;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraftforge.client.event.MouseEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/** Event handler class responsible for handling wizardry's controls. */
@SideOnly(Side.CLIENT)
@Mod.EventBusSubscriber(Side.CLIENT)
public class WizardryControlHandler {
static boolean NkeyPressed = false;
static boolean BkeyPressed = false;
static boolean NkeyAlreadyPressed = false;
static boolean BkeyAlreadyPressed = false;
@SubscribeEvent
public static void onKeyInput(InputEvent.KeyInputEvent event){
// Key pressed
if(Keyboard.getEventKeyState()){
if(Wizardry.proxy instanceof ClientProxy){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(ClientProxy.NEXT_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!NkeyPressed){
NkeyPressed = true;
}else{
NkeyAlreadyPressed = true;
}
if(!NkeyAlreadyPressed){
selectNextSpell(wand);
}
}
if(ClientProxy.PREVIOUS_SPELL.isPressed() && Minecraft.getMinecraft().inGameHasFocus){
if(!BkeyPressed){
BkeyPressed = true;
}else{
BkeyAlreadyPressed = true;
}
if(!BkeyAlreadyPressed){
selectPreviousSpell(wand);
}
}
}
}
// Key released
else{
if(NkeyPressed){
NkeyPressed = false;
NkeyAlreadyPressed = false;
}else if(BkeyPressed){
BkeyPressed = false;
BkeyAlreadyPressed = false;
}
}
}
// Shift-scrolling to change spells
@SubscribeEvent
public static void onMouseEvent(MouseEvent event){
EntityPlayer player = Minecraft.getMinecraft().player;
ItemStack wand = player.getHeldItemMainhand();
if(!(wand.getItem() instanceof ItemWand)){
wand = player.getHeldItemOffhand();
// If the player isn't holding a wand, then nothing else needs to be done.
if(!(wand.getItem() instanceof ItemWand)) return;
}
if(Minecraft.getMinecraft().inGameHasFocus && !wand.isEmpty() && event.getDwheel() != 0 && player.isSneaking()
&& Wizardry.settings.enableShiftScrolling){
event.setCanceled(true);
if(event.getDwheel() > 0){
selectNextSpell(wand);
}else if(event.getDwheel() < 0){
selectPreviousSpell(wand);
}
}
}
private static void selectNextSpell(ItemStack wand){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.NEXT_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation
WandHelper.selectNextSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(true);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.SELECT_SPELL, 1));
}
private static void selectPreviousSpell(ItemStack wand){
// Packet building
IMessage msg = new PacketControlInput.Message(PacketControlInput.ControlType.PREVIOUS_SPELL_KEY);
WizardryPacketHandler.net.sendToServer(msg);
// GUI switch animation
WandHelper.selectPreviousSpell(wand); // Makes sure the spell is set immediately for the client
GuiSpellDisplay.playSpellSwitchAnimation(false);
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(WizardrySounds.SELECT_SPELL, 1));
}
}
@@ -0,0 +1,106 @@
package electroblob.wizardry.client.gui;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.Lists;
import electroblob.wizardry.client.gui.GuiSpellDisplay.Skin;
import electroblob.wizardry.registry.Spells;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraftforge.fml.client.config.GuiSelectString;
import net.minecraftforge.fml.client.config.IConfigElement;
public class GuiSelectHUDSkin extends GuiSelectString {
public GuiSelectHUDSkin(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled){
super(parentScreen, configElement, slotIndex, selectableValues, currentValue, enabled);
}
@Override
public void initGui(){
super.initGui();
this.entryList.setDimensions(150, height, 43, height-43);
this.entryList.left = 10;
this.entryList.headerPadding = 5;
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks){
super.drawScreen(mouseX, mouseY, partialTicks);
GlStateManager.disableLighting();
if(this.currentValue instanceof String){
this.drawString(this.fontRenderer, "Preview:", 170, 44, 0xffffff);
int previewLeft = 170;
int previewRight = width-10;
int previewTop = 60;
int previewBottom = height-43;
this.drawGradientRect(previewLeft, previewTop, previewRight, previewBottom, 0x88000000, 0x88000000);
int previewBorder = 10;
Skin skin = GuiSpellDisplay.getSkin((String)this.currentValue);
float scale = Math.min((previewRight - previewLeft - 2*previewBorder)/(float)skin.getWidth(),
(previewBottom - previewTop - 2*previewBorder)/(float)skin.getHeight());
float x = (previewLeft + previewRight)/2 - (skin.getWidth()*scale)/2;
float y = (previewBottom + previewTop)/2 + (skin.getHeight()*scale)/2;
GlStateManager.pushMatrix();
GlStateManager.scale(scale, scale, scale);
skin.drawBackground((int)(x/scale), (int)(y/scale), false, false,
Spells.magic_missile.getIcon(), 0.6f, false);
skin.drawText((int)(x/scale), (int)(y/scale), false, false,
Spells.none.getDisplayNameWithFormatting(),
Spells.magic_missile.getDisplayNameWithFormatting(),
Spells.none.getDisplayNameWithFormatting(), 0);
GlStateManager.popMatrix();
Skin hovered = getHoveredSkin(mouseX, mouseY);
if(hovered != null){
this.drawToolTip(Lists.newArrayList("\u00A7a" + hovered.getName(), "\u00A7e" + hovered.getDescription()),
mouseX, mouseY);
}
}
GlStateManager.enableLighting();
}
/** Returns the skin corresponding to the list entry being hovered over, or null if there is none. */
@Nullable
private Skin getHoveredSkin(int mouseX, int mouseY){
int index = this.entryList.getSlotIndexFromScreenCoords(mouseX, mouseY);
if(index >= 0 && index <= this.entryList.listEntries.size() && mouseY <= this.entryList.bottom){
Object object = entryList.getListEntry(index).getValue();
if(object instanceof String){
return GuiSpellDisplay.getSkin((String)object);
}
}
return null;
}
@Override // Stops the world being visible behind the GUI when configuring from within a world
public void drawWorldBackground(int tint){
this.drawBackground(tint);
}
}
@@ -1,10 +1,25 @@
package electroblob.wizardry.client.gui;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import electroblob.wizardry.Settings;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.client.ClientProxy;
import electroblob.wizardry.client.MixedFontRenderer;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.registry.Spells;
@@ -15,42 +30,78 @@ import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
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.JsonUtils;
import net.minecraft.util.ResourceLocation;
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;
public class GuiSpellDisplay extends Gui {
@Mod.EventBusSubscriber(Side.CLIENT)
public class GuiSpellDisplay {
private Minecraft mc;
private static final ResourceLocation texture = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud.png");
// Measurements
/** Width of the used portion of the HUD texture */ private static final int HUD_WIDTH = 128;
/** Height of the used portion of the HUD texture */ private static final int HUD_HEIGHT = 64;
/** Distance (in x and y) of the spell icon from the screen edge */ private static final int SPELL_ICON_INSET = 2;
/** Line spacing for the spell name (when on two lines) */ private static final int TEXT_LINE_SPACING = 1;
/** Distance in x from the edge of the screen to the spell name */ private static final int TEXT_INSET_X = 42;
/** Distance in x of the cooldown bar from the screen edge */ private static final int COOLDOWN_BAR_INSET_X = 42;
/** Length of the cooldown bar portion of the HUD texture */ private static final int COOLDOWN_BAR_LENGTH = 79;
/** Height of the cooldown bar portion of the HUD texture */ private static final int COOLDOWN_BAR_HEIGHT = 5;
/** Width and height of the spell icon (very unlikely to change!) */private static final int SPELL_ICON_SIZE = 32;
private static final ResourceLocation INDEX = new ResourceLocation(Wizardry.MODID, "textures/gui/spell_hud/_index.json");
public GuiSpellDisplay(Minecraft minecraft){
super();
this.mc = minecraft;
/** 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. */
private static final Map<String, Skin> skins = new LinkedHashMap<>(12); // 12 is the number of skins packaged with the mod
private static final Gson gson = new Gson();
/** 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;
/** 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<String> getSkinKeys(){
return Collections.unmodifiableSet(skins.keySet());
}
/** Returns an unmodifiable view of the loaded spell HUD skins map. */
public static Map<String, Skin> getSkins(){
return Collections.unmodifiableMap(skins);
}
/** Returns the skin that corresponds to the given key. */
public static Skin getSkin(String key){
return skins.get(key);
}
// 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 void draw(RenderGameOverlayEvent event){
public static void draw(RenderGameOverlayEvent event){
Minecraft mc = Minecraft.getMinecraft();
EntityPlayer player = this.mc.player;
EntityPlayer player = mc.player;
// If the player has a wand in each hand, only displays for the one in the main hand.
@@ -65,100 +116,502 @@ public class GuiSpellDisplay extends Gui {
int width = event.getResolution().getScaledWidth();
int height = event.getResolution().getScaledHeight();
boolean flipX = Wizardry.settings.spellHUDPosition.flipX;
boolean flipY = Wizardry.settings.spellHUDPosition.flipY;
Skin skin = skins.get(Wizardry.settings.spellHUDSkin);
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;
}
}
// '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;
Spell spell = WandHelper.getCurrentSpell(wand);
int cooldown = WandHelper.getCurrentCooldown(wand);
float cooldownMultiplier = 1.0f - WandHelper.getUpgradeLevel(wand, WizardryItems.cooldown_upgrade) * Constants.COOLDOWN_REDUCTION_PER_LEVEL;
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
float animationProgress = Math.signum(switchTimer) * ((SPELL_SWITCH_TIME - Math.abs(switchTimer) +
event.getPartialTicks()) / SPELL_SWITCH_TIME);
String prevSpellName = getFormattedSpellName(WandHelper.getPreviousSpell(wand), player, WandHelper.getPreviousCooldown(wand));
String spellName = getFormattedSpellName(spell, player, cooldown);
String nextSpellName = getFormattedSpellName(WandHelper.getNextSpell(wand), player, WandHelper.getNextCooldown(wand));
if(player.isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously
cooldownMultiplier /= 2 + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier();
skin.drawText(x, y, flipX, flipY, prevSpellName, spellName, nextSpellName, animationProgress);
}else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){
float cooldownMultiplier = 1.0f - WandHelper.getUpgradeLevel(wand, WizardryItems.cooldown_upgrade) * Constants.COOLDOWN_REDUCTION_PER_LEVEL;
if(player.isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously
cooldownMultiplier /= 2 + player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier();
}
boolean discovered = true;
if(!player.capabilities.isCreativeMode && 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.capabilities.isCreativeMode && !spell.isContinuous){
// Subtracted partial tick time to make it smoother
progress = (spell.cooldown * cooldownMultiplier - (float)cooldown + event.getPartialTicks())
/(spell.cooldown * cooldownMultiplier);
}
skin.drawBackground(x, y, flipX, flipY, icon, progress, player.capabilities.isCreativeMode);
}
boolean flipX = Wizardry.settings.spellHUDPosition.flipX;
boolean flipY = Wizardry.settings.spellHUDPosition.flipY;
}
/**
* Makes the given opaque colour translucent with the given opacity.
* @param colour An integer colour code, should be a 6-digit hexadecimal (i.e. opaque).
* @param opacity The opacity to apply to the given colour, as a fraction between 0 and 1.
* @return The resulting integer colour code, which will be an 8-digit hexadecimal.
*/
private static int makeTranslucent(int colour, float opacity){
return colour + ((int)(0xff * opacity * 0x01000000));
}
/** Gets the name of the given spell, with formatted added according to its cooldown and whether the given player
* has discovered it.
* @param spell The spell to get the name of.
* @param 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.capabilities.isCreativeMode && WizardData.get(player) != null){
discovered = WizardData.get(player).hasSpellBeenDiscovered(spell);
}
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect
String format = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.element.getFormattingCode();
if(!discovered) format = "\u00A79";
String name = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
name = format + name;
if(!discovered) name = "#" + name + "#";
return name;
}
/**
* Draws the given string at the given position, scaling it if it does not fit within the given width.
* @param font A {@code FontRenderer} object.
* @param text The text to display.
* @param x The x position of the top-left corner of the text.
* @param y The y position of the top-left corner of the text.
* @param scale The scale that the text should be if it does not exceed the maximum width.
* @param colour The colour to render the text in, supports translucency.
* @param width The maximum width of the text. <b>This is not scaled; you should pass in the width of the actual
* area of the screen in which the text needs to fit.</b>
* @param centre Whether to adjust the y position such that the centre of the text lines up with where its centre
* would be if it was not scaled (automatically or manually).
* @param alignR True to right-align the text, false for normal left alignment.
*/
private static void drawScaledStringToWidth(FontRenderer font, String text, float x, float y, float scale, int colour, float width, boolean centre, boolean alignR){
float textWidth = font.getStringWidth(text) * scale;
float textHeight = font.FONT_HEIGHT * scale;
if(textWidth > width){
scale *= width/textWidth;
}else if(alignR){ // Alignment makes no difference if the string fills the entire width
x += width - textWidth;
}
if(centre) y += (font.FONT_HEIGHT - textHeight)/2;
drawScaledTranslucentString(font, text, x, y, scale, colour);
}
/** Draws the given string at the given position, scaling the text by the specified factor. Also enables blending to
* render text in semitransparent colours (e.g. 0x88ffffff). */
private static void drawScaledTranslucentString(FontRenderer font, String text, float x, float y, float scale, int colour){
GlStateManager.pushMatrix();
GlStateManager.enableBlend();
GlStateManager.scale(scale, scale, scale);
// Because we scaled it, the coordinates have to be scaled inversely
x /= scale;
y /= scale;
font.drawStringWithShadow(text, x, y, colour);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
@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 {
if(event.getType() == RenderGameOverlayEvent.ElementType.TEXT){
// Makes spells greyed out if they are in cooldown or if the player has the arcane jammer effect
String colour = cooldown > 0 || player.isPotionActive(WizardryPotions.arcane_jammer) ? "\u00A78" : spell.element.getFormattingCode();
if(!discovered) colour = "\u00A79";
String spellName = discovered ? spell.getDisplayName() : SpellGlyphData.getGlyphName(spell, player.world);
FontRenderer font = discovered ? this.mc.fontRenderer : this.mc.standardGalacticFontRenderer;
int maxWidth = HUD_WIDTH - TEXT_INSET_X;
int stringWidth = font.getStringWidth(spellName);
int iconMidpoint = (SPELL_ICON_SIZE + 2*SPELL_ICON_INSET)/2; // Should be 18
// TODO: Make a 'scrolling' animation when changing spells, using the text alpha channel to fade it in
// and out.
if(stringWidth <= maxWidth){
//GL11.glPushMatrix();
//GL11.glEnable(GL11.GL_BLEND);
//OpenGlHelper.glBlendFunc(770, 771, 1, 0); // Taken from GuiInGame. TODO: Replace with OpenGL caps.
// Single line is rendered more centrally
font.drawStringWithShadow(colour + spellName, flipX ? width - TEXT_INSET_X - stringWidth : TEXT_INSET_X,
// The text is an odd number of pixels high so we need to subtract an extra 1 when at the bottom
flipY ? iconMidpoint - font.FONT_HEIGHT/2 : height - iconMidpoint - font.FONT_HEIGHT/2 - 1, 0xffffffff);
//GL11.glDisable(GL11.GL_BLEND);
//GL11.glPopMatrix();
}else{
int lineNumber = 0;
List<String> lines = font.listFormattedStringToWidth(spellName, maxWidth);
for(String line : lines){
int lineWidth = font.getStringWidth((String)line);
font.drawStringWithShadow(colour + (String)line, flipX ? width - TEXT_INSET_X - lineWidth : TEXT_INSET_X,
// This time there are two lines so we need to subtract the full line height, and an extra 1 for the spacing
(flipY ? iconMidpoint - font.FONT_HEIGHT - 1 : height - iconMidpoint - font.FONT_HEIGHT - 1)
// Note that there should only ever be two lines maximum
+ lineNumber*(font.FONT_HEIGHT + TEXT_LINE_SPACING), 0xffffffff);
lineNumber++;
List<IResource> 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()));
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<String, JsonElement> 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));
}
}
}else if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR){
} 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. */
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()));
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 <i>of the actual texture</i>
* 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.
*/
public void drawBackground(int x, int y, boolean flipX, boolean flipY, ResourceLocation icon, float cooldownBarProgress, boolean creativeMode){
// 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
this.mc.renderEngine.bindTexture(discovered ? spell.getIcon() : Spells.none.getIcon());
mc.renderEngine.bindTexture(icon);
WizardryUtilities.drawTexturedRect(flipX ? width - SPELL_ICON_INSET - SPELL_ICON_SIZE : SPELL_ICON_INSET,
flipY ? SPELL_ICON_INSET : height - SPELL_ICON_INSET - SPELL_ICON_SIZE, 0, 0, 32, 32, 32, 32);
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;
this.mc.renderEngine.bindTexture(texture);
WizardryUtilities.drawTexturedRect(x1, y1, 0, 0, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE, SPELL_ICON_SIZE);
// Background of spell hud
WizardryUtilities.drawTexturedFlippedRect(flipX ? width-HUD_WIDTH : 0, flipY ? 0 : height-HUD_HEIGHT,
// The 128 here is a uv value, not a dimension, and hence is left as a hardcoded number.
player.capabilities.isCreativeMode ? 128 : 0, 0, HUD_WIDTH, HUD_HEIGHT, 256, 256, flipX, flipY);
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?
WizardryUtilities.drawTexturedFlippedRect(x1, y1, creativeMode ? 128 : 0, 0, width, height, 256, 256, flipX && mirrorX, flipY && mirrorY);
// Cooldown bar
if(!player.capabilities.isCreativeMode && cooldown > 0){
if(!creativeMode && cooldownBarProgress > 0 && (showCooldownWhenFull || cooldownBarProgress < 1)){
int l = (int)(((double)(spell.cooldown * cooldownMultiplier - cooldown)
/ (double)(spell.cooldown * cooldownMultiplier)) * COOLDOWN_BAR_LENGTH);
// Likewise, the 64 here is a uv value and therefore left as a hardcoded number.
WizardryUtilities.drawTexturedFlippedRect(flipX ? width - COOLDOWN_BAR_INSET_X - COOLDOWN_BAR_LENGTH : COOLDOWN_BAR_INSET_X,
flipY ? 1 : height-1-COOLDOWN_BAR_HEIGHT, 0, 64, l, COOLDOWN_BAR_HEIGHT, 256, 256, false, flipY);
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
WizardryUtilities.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 <i>of the actual texture</i>
* 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 <i>directly</i> 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 = makeTranslucent(0xffffff, SPELL_NAME_OPACITY);
drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, SPELL_NAME_SCALE, nextPrevClr, maxWidthPrev, true, flipX && mirrorX);
drawScaledStringToWidth(font, spellName, x1, y1, 1, 0xffffffff, maxWidth, true, flipX && mirrorX);
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 = makeTranslucent(0xffffff, SPELL_NAME_OPACITY + (1 - SPELL_NAME_OPACITY) * (1 - animationProgress));
int clrNext = 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
drawScaledStringToWidth(font, spellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX);
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
drawScaledStringToWidth(font, prevSpellName, xPrev, yPrev, scalePrev, clrPrev, maxWidthPrev, true, flipX && mirrorX);
drawScaledStringToWidth(font, spellName, xNext, yNext, scaleNext, clrNext, maxWidthNext, true, flipX && mirrorX);
}
}
}
}
}
@@ -37,6 +37,7 @@ public final class WizardrySounds {
public static final SoundEvent SPELL_LOOP_WIND = createSound("wind");
public static final SoundEvent SPELL_EARTHQUAKE = createSound("rumble");
public static final SoundEvent SPELL_FORCE = createSound("force");
public static final SoundEvent SELECT_SPELL = createSound("select");
/** Trick borrowed from the Twilight Forest, makes things neater. */
public static SoundEvent createSound(String name){
@@ -65,5 +66,6 @@ public final class WizardrySounds {
event.getRegistry().register(SPELL_LOOP_WIND);
event.getRegistry().register(SPELL_EARTHQUAKE);
event.getRegistry().register(SPELL_FORCE);
event.getRegistry().register(SELECT_SPELL);
}
}
@@ -4,6 +4,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
@@ -116,50 +117,82 @@ public final class WandHelper {
return Spells.none;
}
/** Returns the spell after the currently selected spell for the given wand, or the 'none' spell if the wand has no
* spell data. */
public static Spell getNextSpell(ItemStack wand){
Spell[] spells = getSpells(wand);
if(wand.getTagCompound() != null){
return spells[getNextSpellIndex(wand)];
}
return Spells.none;
}
/** Returns the spell before the currently selected spell for the given wand, or the 'none' spell if the wand has no
* spell data. */
public static Spell getPreviousSpell(ItemStack wand){
Spell[] spells = getSpells(wand);
if(wand.getTagCompound() != null){
return spells[getPreviousSpellIndex(wand)];
}
return Spells.none;
}
/** Selects the next spell in this wand's list of spells. */
public static void selectNextSpell(ItemStack wand){
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]);
if(getSpells(wand).length < 0) setSpells(wand, new Spell[ItemWand.BASE_SPELL_SLOTS]);
if(wand.getTagCompound() != null){
int numberOfSpells = getSpells(wand).length;
int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
// Greater than or equal to so that if attunement upgrades are somehow removed by NBT modification it just
// resets.
if(selectedSpell >= numberOfSpells - 1){
selectedSpell = 0;
}else{
selectedSpell++;
}
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell);
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, getNextSpellIndex(wand));
}
}
/** Selects the previous spell in this wand's list of spells. */
public static void selectPreviousSpell(ItemStack wand){
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]);
// This cannot possibly be null here, and yet I am getting an NPE...
if(getSpells(wand).length < 0) setSpells(wand, new Spell[ItemWand.BASE_SPELL_SLOTS]);
if(wand.getTagCompound() != null){
int numberOfSpells = getSpells(wand).length;
int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
if(selectedSpell <= 0){
selectedSpell = numberOfSpells - 1;
}else{
selectedSpell--;
}
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell);
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, getPreviousSpellIndex(wand));
}
}
private static int getNextSpellIndex(ItemStack wand){
int numberOfSpells = getSpells(wand).length;
int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
// Greater than or equal to so that if attunement upgrades are somehow removed by NBT modification it just
// resets.
if(spellIndex >= numberOfSpells - 1){
spellIndex = 0;
}else{
spellIndex++;
}
return spellIndex;
}
private static int getPreviousSpellIndex(ItemStack wand){
int numberOfSpells = getSpells(wand).length;
int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
if(spellIndex <= 0){
spellIndex = numberOfSpells - 1;
}else{
spellIndex--;
}
return spellIndex;
}
/**
* Returns an array of the cooldowns for each spell bound to the given wand. As of Wizardry 1.1, this array is not
@@ -210,6 +243,28 @@ public final class WandHelper {
// Don't need to check if the tag compound is null since the above check is equivalent.
return cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)];
}
/** Returns the given wand's cooldown for the spell after the currently selected spell, or 0 if the wand has no
* cooldown data. */
public static int getNextCooldown(ItemStack wand){
int[] cooldowns = getCooldowns(wand);
if(cooldowns.length == 0) return 0;
// Don't need to check if the tag compound is null since the above check is equivalent.
return cooldowns[getNextSpellIndex(wand)];
}
/** Returns the given wand's cooldown for the spell before the currently selected spell, or 0 if the wand has no
* cooldown data. */
public static int getPreviousCooldown(ItemStack wand){
int[] cooldowns = getCooldowns(wand);
if(cooldowns.length == 0) return 0;
// Don't need to check if the tag compound is null since the above check is equivalent.
return cooldowns[getPreviousSpellIndex(wand)];
}
/** Sets the given wand's cooldown for the currently selected spell. */
public static void setCurrentCooldown(ItemStack wand, int cooldown){
@@ -710,6 +710,7 @@ config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit
config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist
config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist
config.ebwizardry.spell_hud_position=Spell HUD Position
config.ebwizardry.spell_hud_skin=Spell HUD Skin
config.ebwizardry.cast_command_name=Cast Spell Command Name
config.ebwizardry.discoverspell_command_name=Discover Spell Command Name
config.ebwizardry.ally_command_name=Set Ally Command Name
@@ -746,6 +747,7 @@ config.ebwizardry.cast_command_multiplier_limit.tooltip=Upper limit for the mult
config.ebwizardry.summoned_creature_targets_whitelist.tooltip=List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry:wizard).
config.ebwizardry.summoned_creature_targets_blacklist.tooltip=List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry:wizard).
config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD.
config.ebwizardry.spell_hud_skin.tooltip=Change the look of the spell HUD...
config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead.
config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead.
config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead.
@@ -710,6 +710,7 @@ config.ebwizardry.cast_command_multiplier_limit=Cast Command Multiplier Limit
config.ebwizardry.summoned_creature_targets_whitelist=Summoned Creature Target Whitelist
config.ebwizardry.summoned_creature_targets_blacklist=Summoned Creature Target Blacklist
config.ebwizardry.spell_hud_position=Spell HUD Position
config.ebwizardry.spell_hud_skin=Spell HUD Skin
config.ebwizardry.cast_command_name=Cast Spell Command Name
config.ebwizardry.discoverspell_command_name=Discover Spell Command Name
config.ebwizardry.ally_command_name=Set Ally Command Name
@@ -746,6 +747,7 @@ config.ebwizardry.cast_command_multiplier_limit.tooltip=Upper limit for the mult
config.ebwizardry.summoned_creature_targets_whitelist.tooltip=List of names of entities which summoned creatures and wizards are allowed to attack, in addition to the defaults. Add mod creatures to this list if you want summoned creatures to attack them and they aren't already doing so. Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry:wizard).
config.ebwizardry.summoned_creature_targets_blacklist.tooltip=List of names of entities which summoned creatures and wizards are specifically not allowed to attack, overriding the defaults and the whitelist. Add creatures to this list if allowing them to be attacked causes problems or is too destructive (removing creepers from this list is done at your own risk!). Entity names are not case sensitive. For mod entities, prefix with the mod ID (e.g. ebwizardry:wizard).
config.ebwizardry.spell_hud_position.tooltip=The position of the spell HUD.
config.ebwizardry.spell_hud_skin.tooltip=Change the look of the spell HUD...
config.ebwizardry.cast_command_name.tooltip=The name of the /cast command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /cast you would type /magic instead.
config.ebwizardry.discoverspell_command_name.tooltip=The name of the /discoverspell command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /discoverspell you would type /magic instead.
config.ebwizardry.ally_command_name.tooltip=The name of the /ally command. This is what you type directly after the /; for example if this was set to 'magic' then instead of typing /ally you would type /magic instead.
@@ -35,5 +35,7 @@
"sparkle": {"category": "player","sounds": [{"name": "ebwizardry:sparkle","stream": false}]},
"wind": {"category": "player","sounds": [{"name": "ebwizardry:wind","stream": false}]},
"rumble": {"category": "player","sounds": [{"name": "ebwizardry:rumble","stream": false}]}
"rumble": {"category": "player","sounds": [{"name": "ebwizardry:rumble","stream": false}]},
"select": {"category": "player","sounds": [{"name": "ebwizardry:select","stream": false}]}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,50 @@
{
"default": {
"texture": "ebwizardry:gui/spell_hud/default",
"metadata": "ebwizardry:gui/spell_hud/default"
},
"classic": {
"texture": "ebwizardry:gui/spell_hud/classic",
"metadata": "ebwizardry:gui/spell_hud/classic"
},
"vanilla_style": {
"texture": "ebwizardry:gui/spell_hud/vanilla_style",
"metadata": "ebwizardry:gui/spell_hud/vanilla_style"
},
"redwood": {
"texture": "ebwizardry:gui/spell_hud/redwood",
"metadata": "ebwizardry:gui/spell_hud/redwood"
},
"silverwood": {
"texture": "ebwizardry:gui/spell_hud/silverwood",
"metadata": "ebwizardry:gui/spell_hud/silverwood"
},
"stone": {
"texture": "ebwizardry:gui/spell_hud/stone",
"metadata": "ebwizardry:gui/spell_hud/stone"
},
"sandstone": {
"texture": "ebwizardry:gui/spell_hud/sandstone",
"metadata": "ebwizardry:gui/spell_hud/sandstone"
},
"jungle": {
"texture": "ebwizardry:gui/spell_hud/jungle",
"metadata": "ebwizardry:gui/spell_hud/jungle"
},
"spell_book": {
"texture": "ebwizardry:gui/spell_hud/spell_book",
"metadata": "ebwizardry:gui/spell_hud/spell_book"
},
"minimal": {
"texture": "ebwizardry:gui/spell_hud/minimal",
"metadata": "ebwizardry:gui/spell_hud/minimal"
},
"no_icon": {
"texture": "ebwizardry:gui/spell_hud/no_icon",
"metadata": "ebwizardry:gui/spell_hud/no_icon"
},
"skyrim_style": {
"texture": "ebwizardry:gui/spell_hud/skyrim_style",
"metadata": "ebwizardry:gui/spell_hud/skyrim_style"
}
}
@@ -0,0 +1,33 @@
{
"name": "Classic",
"description": "The original spell HUD, for that nostalgic feel!",
"width": 128,
"height": 36,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 17
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 42,
"y": 0,
"length": 82,
"height": 6,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,33 @@
{
"name": "Default",
"description": "The default, oak wood look of the spell HUD.",
"width": 128,
"height": 50,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 2,
"y": 8
},
"cooldown_bar": {
"x": 42,
"y": 2,
"length": 79,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,33 @@
{
"name": "Jungle",
"description": "For the most intrepid explorers!",
"width": 128,
"height": 37,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 41,
"y": 2,
"length": 82,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,33 @@
{
"name": "Minimal",
"description": "A modern, sleek, minimalist look.",
"width": 128,
"height": 36,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 39,
"y": 16
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 36,
"y": 1,
"length": 86,
"height": 2,
"mirror": {
"x": true,
"y": true
},
"show_when_full": false
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,33 @@
{
"name": "No Icon",
"description": "A variation on the default skin that doesn't show the spell icon.",
"width": 96,
"height": 37,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": -999,
"y": -999
},
"text_inset": {
"x": 10,
"y": 18
},
"spell_cascade_offset": {
"x": 2,
"y": 8
},
"cooldown_bar": {
"x": 10,
"y": 2,
"length": 79,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,33 @@
{
"name": "Redwood",
"description": "A variation on the default skin with red-coloured wood and a purple-pink cooldown bar.",
"width": 128,
"height": 50,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 2,
"y": 8
},
"cooldown_bar": {
"x": 42,
"y": 2,
"length": 79,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@@ -0,0 +1,33 @@
{
"name": "Sandstone",
"description": "Explore ancient ruins with this desert-themed skin!",
"width": 128,
"height": 37,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 41,
"y": 2,
"length": 82,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,33 @@
{
"name": "Silverwood",
"description": "A variation on the default skin with silver-coloured wood and a blue cooldown bar.",
"width": 128,
"height": 50,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 2,
"y": 8
},
"cooldown_bar": {
"x": 42,
"y": 2,
"length": 79,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@@ -0,0 +1,33 @@
{
"name": "Skyrim-style",
"description": "A HUD skin in the styling of The Elder Scrolls V, for that Skyrim feel!",
"width": 128,
"height": 38,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 3,
"y": 3
},
"text_inset": {
"x": 42,
"y": 19
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 44,
"y": 2,
"length": 79,
"height": 3,
"mirror": {
"x": true,
"y": false
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,33 @@
{
"name": "Spell book",
"description": "Knowledge is power, or so they say.",
"width": 128,
"height": 38,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 41,
"y": 20
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 41,
"y": 3,
"length": 79,
"height": 3,
"mirror": {
"x": true,
"y": true
},
"show_when_full": false
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,33 @@
{
"name": "Stone",
"description": "A HUD skin hewn from solid rock, with a red cooldown bar.",
"width": 128,
"height": 37,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 42,
"y": 18
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 41,
"y": 2,
"length": 82,
"height": 3,
"mirror": {
"x": false,
"y": true
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,33 @@
{
"name": "Vanilla-style",
"description": "A vanilla Minecraft-styled skin, for the purists out there.",
"width": 128,
"height": 36,
"mirror": {
"x": true,
"y": true
},
"spell_icon_inset": {
"x": 2,
"y": 2
},
"text_inset": {
"x": 40,
"y": 18
},
"spell_cascade_offset": {
"x": 0,
"y": 8
},
"cooldown_bar": {
"x": 40,
"y": 0,
"length": 81,
"height": 5,
"mirror": {
"x": false,
"y": false
},
"show_when_full": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB