/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.client.gui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Vec3;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.input.Keyboard;
import zeldaswordskills.client.RenderHelperQ;
import zeldaswordskills.client.ZSSKeyHandler;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.ModInfo;
import zeldaswordskills.songs.AbstractZeldaSong;
import zeldaswordskills.util.SongNote;
import zeldaswordskills.util.SongNote.PlayableNote;
/**
*
* Basic Zelda music GUI shows notes on the screen as played, leaving it up to
* child implementations to determine the song played and effects it should have
*
*/
@SideOnly(Side.CLIENT)
public abstract class GuiMusicBase extends GuiScreen
{
protected final Minecraft mc;
/** Maximum number of notes that can display on the GUI at any given time */
protected static final int MAX_NOTES = 8;
/** Note texture height and width */
protected static final int NOTE_SIZE = 12;
/** Y interval between lines */
protected static final int INT_Y = 5;
/** The X size of the window in pixels */
protected int xSize = 213;
/** The Y size of the window in pixels */
protected int ySize = 90;
/** Full width of texture file, in pixels */
protected int fullX = 256;
/** Full height of texture file, in pixels */
protected int fullY = 128;
/** Starting X position for the Gui */
protected int guiLeft;
/** Starting Y position for the Gui */
protected int guiTop;
/** Currently playing song, if any */
protected AbstractZeldaSong song;
/** Stores the notes played so far */
protected final List<SongNote> melody = new ArrayList<SongNote>();
/** Number of ticks since last note played; after a certain threshold, current melody clears */
protected int ticksSinceLastNote;
/** Location of the player when the gui is opened, makes it easier to handle packets and such */
protected final int x, y, z;
public GuiMusicBase(int x, int y, int z) {
mc = Minecraft.getMinecraft();
this.x = x;
this.y = y;
this.z = z;
}
@Override
public void initGui() {
super.initGui();
guiLeft = (width - xSize) / 2;
guiTop = (height - ySize) / 2 + 25;
}
@Override
public boolean doesGuiPauseGame() {
return false;
}
protected abstract ResourceLocation getTexture();
@Override
public void drawScreen(int mouseX, int mouseY, float f) {
GlStateManager.pushAttrib();
GlStateManager.disableLighting();
GlStateManager.enableAlpha();
GlStateManager.enableBlend();
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
mc.getTextureManager().bindTexture(getTexture());
RenderHelperQ.drawTexturedRect(guiLeft, guiTop, 0, 0, xSize, ySize, fullX, fullY);
GlStateManager.popAttrib();
int i1 = (melody.size() > MAX_NOTES ? ((melody.size() - 1) / MAX_NOTES) * MAX_NOTES : 0);
for (int i = 0; (i + i1) < melody.size(); ++i) {
SongNote note = melody.get(i + i1);
// j is factor of how far down the screen note should be drawn
int j = SongNote.Note.values().length - (note.note.ordinal() + 1) + (SongNote.Note.values().length * (2 - note.getOctave()));
int dy = 6 + (INT_Y * j);
int dx = 46 + (NOTE_SIZE + 8) * i;
// draw supplementary line(s) under staff and behind note
if (j > 10) { // j goes from 0-13, not 1-14
int dy2 = (10 + INT_Y * 11);
// given the control scheme, this loop is not really necessary as it's not possible to reach the low A note
for (int n = 0; n < ((j - 9) / 2); ++n) {
// each line segment is 16x5 pixels, using first line in .png file at 8,15
RenderHelperQ.drawTexturedRect(guiLeft + (dx - 2), guiTop + dy2 + (n * 2 * INT_Y), 8, 15, 16, 5, fullX, fullY);
}
}
RenderHelperQ.drawTexturedRect(guiLeft + dx, guiTop + dy, xSize, PlayableNote.getOrdinalFromNote(note) * NOTE_SIZE, NOTE_SIZE, NOTE_SIZE, fullX, fullY);
// draw additional sharp / flat if applicable
if (note.isSharp() || note.isFlat()) {
RenderHelperQ.drawTexturedRect(guiLeft + dx + NOTE_SIZE - 2, guiTop + dy, xSize + NOTE_SIZE, (note.isSharp() ? 0 : 5), 5, 5, fullX, fullY);
}
}
if (song != null) {
String s = song.getDisplayName();
fontRendererObj.drawString(s, guiLeft + (xSize / 2) - (fontRendererObj.getStringWidth(s) / 2), guiTop + 3, 0xFFFFFF);
}
super.drawScreen(mouseX, mouseY, f);
}
@Override
public void updateScreen() {
++ticksSinceLastNote;
if (song != null) {
if (ticksSinceLastNote > song.getMinDuration()) {
mc.thePlayer.closeScreen();
}
} else if (ticksSinceLastNote > Config.getNoteResetInterval()) {
ticksSinceLastNote = 0;
melody.clear();
}
}
/**
* Returning false prevents further key inputs from affecting the notes played,
* though Esc will still close the GUI; default allows input as long as song is null
*/
protected boolean allowKeyInput() {
return song == null;
}
@Override
protected void keyTyped(char c, int key) throws IOException {
if (!allowKeyInput()) {
super.keyTyped(c, key);
return;
}
PlayableNote playedNote = null;
// Change to use your own KeyBindings, of course
if (key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_ATTACK].getKeyCode()) {
playedNote = PlayableNote.D2; // high D
} else if (key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_DOWN].getKeyCode()) {
playedNote = PlayableNote.F1; // low F
} else if (key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_LEFT].getKeyCode()) {
playedNote = PlayableNote.B2; // high B
} else if (key == ZSSKeyHandler.keys[ZSSKeyHandler.KEY_RIGHT].getKeyCode()) {
playedNote = PlayableNote.A2; // high A
} else if (key == mc.gameSettings.keyBindJump.getKeyCode()) {
playedNote = PlayableNote.D1; // low D
}
// No note key was pressed, call super and get out
if (playedNote == null) {
super.keyTyped(c, key);
} else {
int modifier = 0;
// Half-step modifier keys
if (Keyboard.isKeyDown(mc.gameSettings.keyBindRight.getKeyCode())) {
++modifier;
} else if (Keyboard.isKeyDown(mc.gameSettings.keyBindLeft.getKeyCode())) {
--modifier;
}
// Whole step modifier keys are in addition to half-step modifiers
if (Keyboard.isKeyDown(mc.gameSettings.keyBindForward.getKeyCode())) {
modifier += 2;
} else if (Keyboard.isKeyDown(mc.gameSettings.keyBindBack.getKeyCode())) {
modifier -= 2;
}
SongNote note = SongNote.getNote(playedNote, modifier);
if (note != null) {
onNotePlayed(note);
onNoteAdded();
}
}
}
/**
* Called after each note is added; check if notes match a song here
*/
protected abstract void onNoteAdded();
/**
* Adds the note to the list of notes played, plays the note sound, and spawns particles.
*/
protected void onNotePlayed(SongNote note) {
melody.add(note);
ticksSinceLastNote = 0;
float f = (float) Math.pow(2.0D, (double)(note.ordinal() - 12) / 12.0D);
// TODO retrieve note to play from player's held ItemInstrument when gui constructed
mc.thePlayer.playSound(ModInfo.ID + ":note.ocarina", 3.0F, f);
Vec3 look = mc.thePlayer.getLookVec();
mc.theWorld.spawnParticle(EnumParticleTypes.NOTE,
mc.thePlayer.posX + look.xCoord + mc.theWorld.rand.nextDouble() - 0.5D,
mc.thePlayer.posY + look.yCoord + mc.thePlayer.getEyeHeight() + mc.theWorld.rand.nextDouble() - 0.5D,
mc.thePlayer.posZ + look.zCoord + mc.theWorld.rand.nextDouble() - 0.5D,
(double) note.ordinal() / 24.0D, 0.0D, 0.0D);
}
}