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){