package de.miaurizius.jgame2d.core; import de.miaurizius.jgame2d.core.enums.GameState; import de.miaurizius.jgame2d.entity.Entity; import de.miaurizius.jgame2d.entity.item.CoinObj; import de.miaurizius.jgame2d.entity.item.HeartObj; import de.miaurizius.jgame2d.environment.Lighting; import java.awt.*; import java.awt.image.BufferedImage; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.logging.Level; public class UI { GamePanel panel; Graphics2D graphics2d; Font font; BufferedImage heart_full, heart_half, heart_blank, coin; ArrayList messages = new ArrayList<>(); ArrayList messageCounter = new ArrayList<>(); public String currentDialogue; public Entity tradingNPC; public int commandNum; public int playerSlotCol, playerSlotRow; public int npcSlotCol, npcSlotRow; private int transCount; private int sleepCount; // SUB-STATES public TradeState tradeState = TradeState.SELECT; public OptionState optionState; public UI(GamePanel panel) { this.panel = panel; font = new Font("Arial", Font.PLAIN, 40); try { font = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream("assets/font/x12y16pxMaruMonica.ttf")); } catch (FontFormatException | IOException e) { Boot.logger.log(Level.WARNING, "Could not load maruMonica.ttf", e); } // CREATE HUD OBJECT Entity heart = new HeartObj(panel); heart_full = heart.image; heart_half = heart.image2; heart_blank = heart.image3; Entity coin = new CoinObj(panel); this.coin = coin.down1; } public void draw(Graphics2D graphics2d) { this.graphics2d = graphics2d; graphics2d.setFont(font); graphics2d.setColor(Color.white); if(panel.gameState == null) return; switch (panel.gameState) { case PLAY -> drawPlayScreen(); case PAUSE -> drawPauseScreen(); case DIALOGUE -> drawDialogueScreen(); case TITLE -> drawTitleScreen(); case CHARACTER -> drawCharacterScreen(); case GAMEOVER -> drawGameOverScreen(); case TRANSITION -> drawTransitionScreen(); case TRADE -> drawTradeScreen(); case SLEEP -> drawSleepScreen(); } } // HUD private void drawPlayerLife() { int x = panel.tileSize / 2; int y = panel.tileSize / 2; int i = 0; // DRAW MAX HEART while(i 180) { messages.remove(i); messageCounter.remove(i); } } } private void drawCharStats() { // DRAW FRAME final int frameX = panel.tileSize*2; final int frameY = panel.tileSize; final int frameWidth = panel.tileSize*5; final int frameHeight = panel.tileSize*10; drawSubWindow(frameX, frameY, frameWidth, frameHeight); // TEXT graphics2d.setColor(Color.white); graphics2d.setFont(graphics2d.getFont().deriveFont(28F)); int textX = frameX + 20; int textY = frameY + panel.tileSize; final int lineHeight = 35; // NAMES String[] names = {"Level", "Life", "Strength", "Dexterity", "Attack", "Defense", "Exp", "Next Level", "Coins", "Weapon", "Shield"}; for(String name : names) { graphics2d.drawString(name, textX, textY); textY += lineHeight + (name.equals("Coins") ? 20 : (name.equals("Weapon") ? 15 : 0)); } // VALUES int tailX = (frameX + frameWidth) - 30; textY = frameY + panel.tileSize; String[] values = { String.valueOf(panel.player.level), (panel.player.life + "/" + panel.player.maxLife), String.valueOf(panel.player.strength), String.valueOf(panel.player.dexterity), String.valueOf(panel.player.attack), String.valueOf(panel.player.defense), String.valueOf(panel.player.exp), String.valueOf(panel.player.nextLevelExp), String.valueOf(panel.player.coins) }; for(String value : values) { textX = getAlignedToRightX(value, tailX); graphics2d.drawString(value, textX, textY); textY += lineHeight; } graphics2d.drawImage(panel.player.currentWeapon.down1, tailX - panel.tileSize, textY-14, null); textY += panel.tileSize; graphics2d.drawImage(panel.player.currentShield.down1, tailX - panel.tileSize, textY-14, null); } private void drawInventory(Entity entity, boolean cursor) { int frameX, frameY, frameWidth, frameHeight; int slotCol, slotRow; // DRAW FRAME if(entity == panel.player) { frameX = panel.tileSize*12; frameY = panel.tileSize; frameWidth = panel.tileSize*6; frameHeight = panel.tileSize*5; slotCol = playerSlotCol; slotRow = playerSlotRow; } else { frameX = panel.tileSize*2; frameY = panel.tileSize; frameWidth = panel.tileSize*6; frameHeight = panel.tileSize*5; slotCol = npcSlotCol; slotRow = npcSlotRow; } drawSubWindow(frameX, frameY, frameWidth, frameHeight); // SLOT final int slotXStart = frameX + 20; final int slotYStart = frameY + 20; int slotX = slotXStart; int slotY = slotYStart; int slotSize = panel.tileSize+3; // DRAW ITEMS for(int i = 0; i < entity.inventory.size(); i++) { // EQUIP CURSOR if( entity.inventory.get(i) == entity.currentWeapon || entity.inventory.get(i) == entity.currentShield || entity.inventory.get(i) == entity.currentLight ) { graphics2d.setColor(new Color(240, 190,90)); graphics2d.fillRoundRect(slotX, slotY, panel.tileSize, panel.tileSize, 10, 10); } graphics2d.drawImage(entity.inventory.get(i).down1, slotX, slotY, null); // AMOUNT if(entity == panel.player && entity.inventory.get(i).amt > 1) { graphics2d.setFont(graphics2d.getFont().deriveFont(32F)); int amtX; int amtY; String amtS = String.valueOf(entity.inventory.get(i).amt); amtX = getAlignedToRightX(amtS, slotX+44); amtY = slotY + panel.tileSize; // SHADOW graphics2d.setColor(new Color(60, 60, 60)); graphics2d.drawString(amtS, amtX, amtY); // NUMBER graphics2d.setColor(Color.white); graphics2d.drawString(amtS, amtX-3, amtY-3); } slotX += slotSize; if (i == 4 || i == 9 || i == 14) { slotX = slotXStart; slotY += slotSize; } } // CURSOR if(!cursor) return; int curserX = slotXStart + (slotSize*slotCol); int curserY = slotYStart + (slotSize*slotRow); int curserHeight = panel.tileSize; int curserWidth = panel.tileSize; graphics2d.setColor(Color.white); graphics2d.setStroke(new BasicStroke(3)); graphics2d.drawRoundRect(curserX, curserY, curserWidth, curserHeight, 10, 10); // DESCRIPTION FRAME int itemIndex = getItemIndex(slotCol, slotRow); int dFrameX = frameX; int dFrameY = frameY + frameHeight+2; int dFrameWidth = frameWidth; int dFrameHeight = panel.tileSize*3; // DRAW DESCRIPTION int textX = dFrameX + 20; int textY = dFrameY + panel.tileSize; graphics2d.setFont(graphics2d.getFont().deriveFont(28F)); if(itemIndex < entity.inventory.size()) { drawSubWindow(dFrameX, dFrameY, dFrameWidth, dFrameHeight); for(String line : entity.inventory.get(itemIndex).description.split("\n")) { graphics2d.drawString(line, textX, textY); textY += 32; } } } // GAME STATES private void drawPauseScreen() { graphics2d.setColor(Color.white); graphics2d.setFont(graphics2d.getFont().deriveFont(32F)); // SUB WINDOW int frameX = panel.tileSize*6; int frameY = panel.tileSize; int frameWidth = panel.tileSize*8; int frameHeight = panel.tileSize*10; drawSubWindow(frameX, frameY, frameWidth, frameHeight); switch(optionState) { case OVERVIEW -> optionsTop(frameX, frameY); case SCREENNF -> optionsFSNotify(frameX, frameY); case CONTROLS -> optionsControls(frameX, frameY); case QUITNF -> optionsQuitNotify(frameX, frameY); } panel.keyH.spacePressed = false; } private void drawDialogueScreen() { drawPlayerLife(); // WINDOW int x = panel.tileSize*2; int y = panel.tileSize/2; int width = panel.screenWidth - (panel.tileSize*4); int height = panel.tileSize*4; drawSubWindow(x, y, width, height); graphics2d.setFont(graphics2d.getFont().deriveFont(Font.PLAIN, 30F)); x += panel.tileSize; y += panel.tileSize; for(String line : currentDialogue.split("\n")) { graphics2d.drawString(line, x, y); y += 40; } } private void drawTitleScreen() { graphics2d.setColor(new Color(0, 0, 0)); graphics2d.fillRect(0, 0, panel.screenWidth, panel.screenHeight); // TITLE NAME graphics2d.setFont(graphics2d.getFont().deriveFont(Font.BOLD, 96F)); String text = "JGame2D"; int x = getCenteredX(text); int y = panel.tileSize*3; // SHADOW graphics2d.setColor(Color.gray); graphics2d.drawString(text, x+5, y+5); // MAIN COLOR graphics2d.setColor(Color.white); graphics2d.drawString(text, x, y); // MENU graphics2d.setFont(graphics2d.getFont().deriveFont(Font.BOLD, 48F)); text = "NEW GAME"; x = getCenteredX(text); y += panel.tileSize*4; graphics2d.drawString(text, x, y); if(commandNum == 0) graphics2d.drawString(">", x-panel.tileSize, y); text = "LOAD GAME"; x = getCenteredX(text); y += panel.tileSize; graphics2d.drawString(text, x, y); if(commandNum == 1) graphics2d.drawString(">", x-panel.tileSize, y); text = "QUIT"; x = getCenteredX(text); y += panel.tileSize; graphics2d.drawString(text, x, y); if(commandNum == 2) graphics2d.drawString(">", x-panel.tileSize, y); } private void drawCharacterScreen() { drawCharStats(); drawInventory(panel.player, true); } private void drawPlayScreen() { drawPlayerLife(); drawMessages(); } private void drawGameOverScreen() { graphics2d.setColor(new Color(0,0,0, 150)); graphics2d.fillRect(0, 0, panel.screenWidth, panel.screenHeight); int x, y; String text; graphics2d.setFont(graphics2d.getFont().deriveFont(Font.BOLD, 110F)); text = "GAME OVER"; // SHADOW graphics2d.setColor(Color.black); x = getCenteredX(text); y = panel.tileSize*3; graphics2d.drawString(text, x, y); // MAIN TEXT graphics2d.setColor(Color.white); graphics2d.drawString(text, x-4, y-4); // OPTIONS graphics2d.setFont(graphics2d.getFont().deriveFont(50F)); text = "Retry"; x = getCenteredX(text); y += panel.tileSize*4; graphics2d.drawString(text, x, y); if(commandNum == 0) graphics2d.drawString(">", x-panel.tileSize, y); text = "Return to title"; x = getCenteredX(text); y += 55; graphics2d.drawString(text, x, y); if(commandNum == 1) graphics2d.drawString(">", x-panel.tileSize, y); } private void drawTransitionScreen() { transCount++; graphics2d.setColor(new Color(0,0,0, transCount *5)); graphics2d.fillRect(0, 0, panel.screenWidth, panel.screenHeight); if(transCount != 50) return; transCount = 0; panel.gameState = GameState.PLAY; panel.currentMap = panel.eventH.tempMap; panel.player.worldX = panel.tileSize * panel.eventH.tempCol; panel.player.worldY = panel.tileSize * panel.eventH.tempRow; panel.eventH.prevEventX = panel.player.worldX; panel.eventH.prevEventY = panel.player.worldY; } private void drawTradeScreen() { switch(tradeState) { case SELECT -> tradeSelect(); case BUY -> tradeBuy(); case SELL -> tradeSell(); } panel.keyH.spacePressed = false; } private void drawSleepScreen() { sleepCount++; if(sleepCount < 120) { panel.eManager.lighting.filterAlpha += 0.01F; if(panel.eManager.lighting.filterAlpha >= 1F) panel.eManager.lighting.filterAlpha = 1F; } if(sleepCount >= 120) { panel.eManager.lighting.filterAlpha -= 0.01F; if(panel.eManager.lighting.filterAlpha <= 0F) { panel.eManager.lighting.filterAlpha = 0F; sleepCount = 0; panel.eManager.lighting.dayCount = 0; panel.eManager.lighting.dayState = Lighting.DayState.DAY; panel.gameState = GameState.PLAY; panel.player.getPlayerImage(); } } } // TRADING private void tradeSelect() { drawDialogueScreen(); int x = panel.tileSize*15; int y = panel.tileSize*4; int width = panel.tileSize*3; int height = (int)(panel.tileSize*3.5); drawSubWindow(x, y, width, height); // DRAW TEXTS x += panel.tileSize; y += panel.tileSize; graphics2d.drawString("Buy",x , y); if(commandNum == 0) { graphics2d.drawString(">", x-panel.tileSize/2, y); if(panel.keyH.spacePressed) tradeState = TradeState.BUY; } y += panel.tileSize; graphics2d.drawString("Sell",x , y); if(commandNum == 1) { graphics2d.drawString(">", x-panel.tileSize/2, y); if(panel.keyH.spacePressed) tradeState = TradeState.SELL; } y += panel.tileSize; graphics2d.drawString("Leave",x , y); if(commandNum == 2) { graphics2d.drawString(">", x-panel.tileSize/2, y); if(panel.keyH.spacePressed) { commandNum = 0; tradeState = TradeState.SELECT; panel.gameState = GameState.DIALOGUE; currentDialogue = "Come again, hehe!"; } } } private void tradeBuy() { drawInventory(panel.player, false); drawInventory(tradingNPC, true); // HINT WINDOW int x = panel.tileSize*2; int y = panel.tileSize*9; int width = panel.tileSize*6; int height = panel.tileSize*2; drawSubWindow(x, y, width, height); graphics2d.drawString("[ESC] Back", x+panel.tileSize, y+60); // PLAYER COINS x = panel.tileSize*12; drawSubWindow(x, y, width, height); graphics2d.drawString("Your Coins: " + panel.player.coins, x+panel.tileSize, y+60); // PRICE WINDOW int itemIndex = getItemIndex(npcSlotCol, npcSlotRow); if(itemIndex < tradingNPC.inventory.size()) { x = (int)(panel.tileSize*5.5); y = (int)(panel.tileSize*5.5); width = (int)(panel.tileSize*2.5); height = panel.tileSize; drawSubWindow(x, y, width, height); graphics2d.drawImage(coin, x+10, y+8, 32, 32, null); String price = String.valueOf(tradingNPC.inventory.get(itemIndex).price); graphics2d.drawString(price, getAlignedToRightX(price, panel.tileSize*8-20), y+34); // BUY if(!panel.keyH.spacePressed) return; if(tradingNPC.inventory.get(itemIndex).price > panel.player.coins) { tradeState = TradeState.SELECT; panel.gameState = GameState.DIALOGUE; currentDialogue = "You need more coins to buy that!"; drawDialogueScreen(); return; } if(panel.player.canObtainItem(tradingNPC.inventory.get(itemIndex))) { panel.player.coins -= tradingNPC.inventory.get(itemIndex).price; return; } tradeState = TradeState.SELECT; panel.gameState = GameState.DIALOGUE; currentDialogue = "Your inventory is full!"; drawDialogueScreen(); } } private void tradeSell() { drawInventory(panel.player, true); int x, y, width, height; // HINT WINDOW x = panel.tileSize*2; y = panel.tileSize*9; width = panel.tileSize*6; height = panel.tileSize*2; drawSubWindow(x, y, width, height); graphics2d.drawString("[ESC] Back", x+panel.tileSize, y+60); // PLAYER COINS x = panel.tileSize*12; drawSubWindow(x, y, width, height); graphics2d.drawString("Your Coins: " + panel.player.coins, x+panel.tileSize, y+60); // PRICE WINDOW int itemIndex = getItemIndex(playerSlotCol, playerSlotRow); if(itemIndex < panel.player.inventory.size()) { x = (int)(panel.tileSize*15.5); y = (int)(panel.tileSize*5.5); width = (int)(panel.tileSize*2.5); height = panel.tileSize; drawSubWindow(x, y, width, height); graphics2d.drawImage(coin, x+10, y+8, 32, 32, null); String price = String.valueOf(panel.player.inventory.get(itemIndex).price/2); graphics2d.drawString(price, getAlignedToRightX(price, panel.tileSize*18-20), y+34); // SELL if(!panel.keyH.spacePressed) return; if(panel.player.inventory.get(itemIndex) == panel.player.currentWeapon || panel.player.inventory.get(itemIndex) == panel.player.currentShield) { commandNum = 0; tradeState = TradeState.SELECT; panel.gameState = GameState.DIALOGUE; currentDialogue = "You cannot sell an equipped item!"; return; } if(panel.player.inventory.get(itemIndex).amt <= 1) panel.player.inventory.remove(itemIndex); else panel.player.inventory.get(itemIndex).amt--; panel.player.coins += Integer.parseInt(price); } } // UTILITY private void drawSubWindow(int x, int y, int width, int height) { graphics2d.setColor(new Color(0,0,0,210)); graphics2d.fillRoundRect(x, y, width, height, 35, 35); graphics2d.setColor(new Color(255,255,255)); graphics2d.setStroke(new BasicStroke(5)); graphics2d.drawRoundRect(x+5, y+5, width-10, height-10, 25, 25); } private int getCenteredX(String text) { return panel.screenWidth / 2 - (int) graphics2d.getFontMetrics().getStringBounds(text, graphics2d).getWidth() / 2; } private int getAlignedToRightX(String text, int tailX) { return tailX - (int) graphics2d.getFontMetrics().getStringBounds(text, graphics2d).getWidth(); } public int getItemIndex(int slotCol, int slotRow) { return slotCol + slotRow *5; } public void addMessage(String text) { messages.add(text); messageCounter.add(0); } // OPTIONS UI private void optionsTop(int frameX, int frameY) { int textX, textY; String title = "Options"; textX = getCenteredX(title); textY = frameY + panel.tileSize; graphics2d.drawString(title, textX, textY); // FULLSCREEN ON/OFF textX = frameX + panel.tileSize; textY += panel.tileSize; graphics2d.drawString("Full Screen", textX, textY); if(commandNum == 0) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) { optionState = OptionState.SCREENNF; panel.fullscreen = !panel.fullscreen; } } // MUSIC textY += panel.tileSize; graphics2d.drawString("Music", textX, textY); if(commandNum == 1) graphics2d.drawString(">", textX-25, textY); // SOUND EFFECTS textY += panel.tileSize; graphics2d.drawString("SFX", textX, textY); if(commandNum == 2) graphics2d.drawString(">", textX-25, textY); // CONTROL textY += panel.tileSize; graphics2d.drawString("Controls", textX, textY); if(commandNum == 3) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) { optionState = OptionState.CONTROLS; commandNum = 0; } } // END GAME textY += panel.tileSize; graphics2d.drawString("Title Screen", textX, textY); if(commandNum == 4) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) { optionState = OptionState.QUITNF; commandNum = 0; } } // BACK textY += panel.tileSize*2; graphics2d.drawString("Back to Game", textX, textY); if(commandNum == 5) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) panel.gameState = GameState.PLAY; } // // FUNCTIONALITY // // FULL SCREEN CHECKBOX textX = frameX + (int)(panel.tileSize*4.5); textY = frameY + panel.tileSize*2 - panel.tileSize/2; graphics2d.setStroke(new BasicStroke(3)); graphics2d.drawRect(textX, textY, panel.tileSize/2, panel.tileSize/2); if(panel.fullscreen) { graphics2d.fillRect(textX, textY, panel.tileSize/2, panel.tileSize/2); } // MUSIC VOLUME textY += panel.tileSize; graphics2d.drawRect(textX, textY, 120, panel.tileSize/2); // 120/5 = 24 graphics2d.fillRect(textX, textY, 24 * panel.music.volumeScale, panel.tileSize/2); // SFX VOLUME textY += panel.tileSize; graphics2d.drawRect(textX, textY, 120, panel.tileSize/2); // 120/5 = 24 graphics2d.fillRect(textX, textY, 24 * panel.sfx.volumeScale, panel.tileSize/2); panel.config.save(); } private void optionsControls(int frameX, int frameY) { int textX; int textY; // TITLE String text = "Controls"; textX = getCenteredX(text); textY = frameY + panel.tileSize; graphics2d.drawString(text, textX, textY); textX = frameX + panel.tileSize; textY +=panel.tileSize; String[] options = {"Move", "Confirm/Attack", "Shoot/Cast", "Inventory", "Pause/Settings"}; for(String option : options) { graphics2d.drawString(option, textX, textY); textY += panel.tileSize; } textX = frameX + panel.tileSize*6; textY = frameY + panel.tileSize*2; options = new String[]{"WASD", "SPACE", "F", "C", "ESC"}; for(String option : options) { graphics2d.drawString(option, textX, textY); textY += panel.tileSize; } // BACK textX = frameX + panel.tileSize; textY = frameY + panel.tileSize*9; graphics2d.drawString("Back", textX, textY); graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) optionState = OptionState.OVERVIEW; } private void optionsFSNotify(int frameX, int frameY) { int textX = frameX + panel.tileSize; int textY = frameY + panel.tileSize*3; currentDialogue = "The change will take \neffect after restarting \nthe game"; for(String line : currentDialogue.split("\n")) { graphics2d.drawString(line, textX, textY); textY += 40; } // BACK textY =frameY + panel.tileSize*9; graphics2d.drawString("Back", textX, textY); if(commandNum == 0) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) optionState = OptionState.OVERVIEW; } } private void optionsQuitNotify(int frameX, int frameY) { int textX = frameX + panel.tileSize; int textY = frameY + panel.tileSize*3; currentDialogue = "Quit the game and \nreturn to the title screen?"; for(String line : currentDialogue.split("\n")) { graphics2d.drawString(line, textX, textY); textY += 40; } // DISPLAY OPTIONS String text = "Yes"; textX = getCenteredX(text); textY += panel.tileSize*3; graphics2d.drawString(text, textX, textY); if(commandNum == 0) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) { panel.gameState = GameState.TITLE; panel.stopMusic(); } } text = "No"; textX = getCenteredX(text); textY += panel.tileSize; graphics2d.drawString(text, textX, textY); if(commandNum == 1) { graphics2d.drawString(">", textX-25, textY); if(panel.keyH.spacePressed) optionState = OptionState.OVERVIEW; } } public enum OptionState { OVERVIEW, SCREENNF, CONTROLS, QUITNF; } public enum TradeState { SELECT, BUY, SELL; } }