diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 530cb06..85b85be 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 8 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '8' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven - Install diff --git a/pom.xml b/pom.xml index 9b46a42..8692d4b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,9 @@ 4.0.0 com.redomar.game javagame - Alpha 1.8.6 + Alpha 1.8.7 + JavaGame is a game project that have been working on since May 2013. I have added many features to the game over the last year and I plan on adding even more features. This game is purely for my own sake to practice my skills in Java. + https://github.com/redomar/JavaGame 2013 @@ -24,12 +26,12 @@ org.apache.commons commons-text - 1.10.0 + 1.11.0 org.apache.commons commons-lang3 - 3.12.0 + 3.14.0 org.jetbrains @@ -49,8 +51,8 @@ - 8 - 8 + 21 + 21 UTF-8 UTF-8 @@ -68,7 +70,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.6 + 3.3.1 false @@ -81,7 +83,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.3.0 + 3.6.1 copy-dependencies @@ -102,7 +104,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.4 + 3.3.0 @@ -124,13 +126,12 @@ org.apache.maven.plugins maven-install-plugin - 2.4 + 3.1.1 install-external-non-maven-jar-MWS-Client-into-local-maven-repo clean - default com.thehowtotutorial.splashscreen JSplashScreen 1.0 @@ -144,6 +145,11 @@ + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.5.0 + diff --git a/src/com/redomar/game/Game.java b/src/com/redomar/game/Game.java index 4cfd945..f5b5709 100644 --- a/src/com/redomar/game/Game.java +++ b/src/com/redomar/game/Game.java @@ -9,6 +9,7 @@ import com.redomar.game.event.MouseHandler; import com.redomar.game.gfx.Screen; import com.redomar.game.gfx.SpriteSheet; +import com.redomar.game.gfx.lighting.Night; import com.redomar.game.level.LevelHandler; import com.redomar.game.lib.Either; import com.redomar.game.lib.Font; @@ -23,6 +24,7 @@ import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; +import java.io.Serial; /* * This module forms the core architecture of the JavaGame. It coordinates the various @@ -33,17 +35,20 @@ public class Game extends Canvas implements Runnable { // Setting the size and name of the frame/canvas + @Serial private static final long serialVersionUID = 1L; - private static final String game_Version = "v1.8.6 Alpha"; - private static final int WIDTH = 160; - private static final int HEIGHT = (WIDTH / 3 * 2); - private static final int SCALE = 3; + private static final String game_Version = "v1.8.7 Alpha"; + private static final int SCALE = 100; + private static final int WIDTH = 3 * SCALE; + private static final int SCREEN_WIDTH = WIDTH * 2; + private static final int HEIGHT = (2 * SCALE); + private static final int SCREEN_HEIGHT = (HEIGHT * 2) + 30; + private static final Screen screen = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png")); + private static final Screen screen2 = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png")); private static final String NAME = "Game"; // The name of the JFrame panel private static final Time time = new Time(); // Represents the calendar's time value, in hh:mm:ss private static final boolean[] alternateCols = new boolean[2]; // Boolean array describing shirt and face colour - private static Game game; - // The properties of the player, npc, and fps/tps private static boolean changeLevel = false; // Determines whether the player teleports to another level private static boolean npc = false; // Non-player character (NPC) initialized to non-existing @@ -55,7 +60,8 @@ public class Game extends Canvas implements Runnable { private static int steps; private static boolean devMode; // Determines whether the game is in developer mode private static boolean closingMode; // Determines whether the game will exit - + private static int tileX = 0; + private static int tileY = 0; // Audio, input, and mouse handler objects private static JFrame frame; private static AudioHandler backgroundMusic; @@ -63,17 +69,17 @@ public class Game extends Canvas implements Runnable { private static InputHandler input; // Accepts keyboard input and follows the appropriate actions private static MouseHandler mouse; // Tracks mouse movement and clicks, and follows the appropriate actions private static InputContext context; // Provides methods to control text input facilities - // Graphics - private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); + private final BufferedImage image3 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); private final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); // Array of red, green and blue values for each pixel + private final int[] pixels3 = ((DataBufferInt) image3.getRaster().getDataBuffer()).getData(); // Array of red, green and blue values for each pixel private final int[] colours = new int[6 * 6 * 6]; // Array of 216 unique colours (6 shades of red, 6 of green, and 6 of blue) private final BufferedImage image2 = new BufferedImage(WIDTH, HEIGHT - 30, BufferedImage.TYPE_INT_RGB); private final Font font = new Font(); // Font object capable of displaying 2 fonts: Arial and Segoe UI private final Printer printer = new Printer(); boolean musicPlaying = false; private int tickCount = 0; - private Screen screen; private LevelHandler level; // Loads and renders levels along with tiles, entities, projectiles and more. //The entities of the game private Player player; @@ -87,9 +93,9 @@ public Game() { context = InputContext.getInstance(); // The game can only be played in one distinct window size - setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE)); - setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE)); - setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE)); + setMinimumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); + setMaximumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); + setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); setFrame(new JFrame(NAME)); // Creates the frame with a defined name getFrame().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Exits the program when user closes the frame @@ -288,6 +294,28 @@ public static void setClosing(boolean closing) { Game.closingMode = closing; } + private static void mousePositionTracker() { + MouseHandler mouseHandler = Game.getMouse(); + int mouseX = mouseHandler.getX(); + int mouseY = mouseHandler.getY(); + + // Adjust mouse coordinates based on the current offset and scale of the game world + tileX = ((mouseX + 4 + screen.getxOffset()) / (8 * 2)) + screen.getxOffset() / 16; + tileY = ((mouseY + 4 + screen.getyOffset()) / (8 * 2)) + screen.getyOffset() / 16; + } + + public static int getTileX() { + return tileX; + } + + public static int getTileY() { + return tileY; + } + + public static Screen getScreen() { + return screen; + } + /* * This method initializes the game once it starts. It populates the colour array with actual colours (6 shades each of RGB). * This method also builds the initial game level (custom_level), spawns a new vendor NPC, and begins accepting keyboard and mouse input/tracking. @@ -301,12 +329,18 @@ public void init() { int rr = (r * 255 / 5); // Split all 256 colours into 6 shades (0, 51, 102 ... 255) int gg = (g * 255 / 5); int bb = (b * 255 / 5); - colours[index++] = rr << 16 | gg << 8 | bb; // All colour values (RGB) are placed into one 32-bit integer, populating the colour array + // All colour values (RGB) are placed into one 32-bit integer, populating the colour array + // The first 8 bits are for alpha, the next 8 for red, the next 8 for green, and the last 8 for blue + // 0xFF000000 is ignored in BufferedImage.TYPE_INT_RGB, but is used in BufferedImage.TYPE_INT_ARGB + colours[index++] = 0xFF << 24 | rr << 16 | gg << 8 | bb; } } } - screen = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png")); + screen.setViewPortHeight(SCREEN_HEIGHT); + screen2.setViewPortHeight(SCREEN_HEIGHT); + screen.setViewPortWidth(SCREEN_WIDTH); + screen2.setViewPortWidth(SCREEN_WIDTH); input = new InputHandler(this); // Input begins to record key presses setMouse(new MouseHandler(this)); // Mouse tracking and clicking is now recorded // setWindow(new WindowHandler(this)); @@ -343,12 +377,13 @@ public synchronized void stop() { */ public void run() { long lastTime = System.nanoTime(); - double nsPerTick = 1000000000D / 60D; // The number of nanoseconds in one tick (number of ticks limited to 60 per update) + int nsPerS = 1_000_000_000; + double nsPerTick = nsPerS / 60D; // The number of nanoseconds in one tick (number of ticks limited to 60 per update) // 1 billion nanoseconds in one second int ticks = 0; int frames = 0; - long lastTimer = System.currentTimeMillis(); // Used for updating ticks and frames once every second + long lastTimer = System.nanoTime(); // Used for updating ticks and frames once every second double delta = 0; init(); // Initialize the game environment @@ -371,8 +406,8 @@ public void run() { render(); } - if (System.currentTimeMillis() - lastTimer >= 1000) { // If elapsed time is greater than or equal to 1 second, update - lastTimer += 1000; // Updates in another second + if (System.nanoTime() - lastTimer >= nsPerS) { // If elapsed time is greater than or equal to 1 second, update + lastTimer += nsPerS; // Updates in another second getFrame().setTitle("JavaGame - Version " + WordUtils.capitalize(game_Version).substring(1, game_Version.length())); fps = frames; tps = ticks; @@ -393,12 +428,15 @@ public void tick() { printer.cast().print("Failed to play music", PrintTypes.MUSIC); printer.exception(exception.toString()); musicPlaying = false; - }, isPlaying -> musicPlaying = isPlaying); - + }, isPlaying -> { + musicPlaying = isPlaying; + if (musicPlaying && !Game.getBackgroundMusic().getActive()) { + input.overWriteKey(input.getM_KEY(), false); + } + }); level.tick(); } - /** * This method displays the current state of the game. */ @@ -425,6 +463,16 @@ public void render() { } } } + for (int y = 0; y < screen2.getHeight(); y++) { + for (int x = 0; x < screen2.getWidth(); x++) { + int colourCode = screen2.getPixels()[x + y * screen2.getWidth()]; + if (colourCode < 1){ + pixels3[x + y * WIDTH] = 0xff0000; + } else if (colourCode < 255) { + pixels3[x + y * WIDTH] = colours[colourCode]; + } + } + } if (isChangeLevel() && getTickCount() % 60 == 0) { Game.setChangeLevel(true); @@ -452,10 +500,10 @@ public void render() { changeLevel = false; } - Graphics g = bs.getDrawGraphics(); - g.drawRect(0, 0, getWidth(), getHeight()); + Graphics2D g = (Graphics2D) bs.getDrawGraphics(); g.drawImage(image, 0, 0, getWidth(), getHeight() - 30, null); status(g, isDevMode(), isClosing()); + overlayRender(g); g.drawImage(image2, 0, getHeight() - 30, getWidth(), getHeight(), null); g.setColor(Color.WHITE); g.setFont(font.getSegoe()); @@ -484,12 +532,24 @@ public void render() { bs.show(); } + /** + * This method renders the overlay of the game, which is a transparent layer that is drawn over the game. + */ + private void overlayRender(Graphics2D g) { + g.setColor(new Color(0f, 0f, 0f, 0f)); // Transparent color + g.fillRect(0, 0, getWidth(), getHeight()-30); + } + /* * This method displays information regarding various aspects/stats of the game, dependent upon * whether it is running in developer mode, or if the application is closing. */ - private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) { + private void status(Graphics2D g, boolean TerminalMode, boolean TerminalQuit) { if (TerminalMode) { + new Night(g, screen).render(player.getPlayerAbsX(), player.getPlayerAbsY()); + // make the background transparent + g.setColor(new Color(0, 0, 0, 100)); + g.fillRect(0, 0, 195, 165); g.setColor(Color.CYAN); g.drawString("JavaGame Stats", 0, 10); g.drawString("FPS/TPS: " + fps + "/" + tps, 0, 25); @@ -499,9 +559,29 @@ private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) { g.drawString("Foot Steps: " + steps, 0, 40); g.drawString("NPC: " + WordUtils.capitalize(String.valueOf(isNpc())), 0, 55); g.drawString("Mouse: " + getMouse().getX() + "x |" + getMouse().getY() + "y", 0, 70); - if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 85); - g.setColor(Color.CYAN); - g.fillRect(getMouse().getX() - 12, getMouse().getY() - 12, 24, 24); + g.drawString("Mouse: " + (getMouse().getX() - 639 / 2d) + "x |" + (getMouse().getY() - 423 / 2d) + "y", 0, 85); + if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 100); + mousePositionTracker(); + g.drawString("Player: " + (int) player.getX() + "x |" + (int) player.getY() + "y", 0, 115); + double angle = Math.atan2(getMouse().getY() - player.getPlayerAbsY(), getMouse().getX() - player.getPlayerAbsX()) * (180.0 / Math.PI); + g.drawString("Angle: " + angle, 0, 130); + + g.setColor(Color.cyan); + g.drawString("Player: \t\t\t\t\t\t\t\t\t\t\t\t" + player.getPlayerAbsX() + "x |" + player.getPlayerAbsY() + "y", 0, 145); + g.drawString("Player Offset: \t" + screen.getxOffset() + "x |" + screen.getyOffset() + "y", 0, 160); + + // Set a different color for the player-origin line + g.setStroke(new BasicStroke(1)); + g.setColor(Color.GREEN); // Green for the new line from the player's origin + g.drawLine(player.getPlayerAbsX(), player.getPlayerAbsY(), getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor + g.setColor(Color.DARK_GRAY); + g.drawLine(getWidth() / 2 + 8, getHeight() / 2 - 8, getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor + g.drawLine(getWidth() / 2 + 8, 0, getWidth() / 2 + 8, getHeight() - 30); + g.drawLine(0, getHeight() / 2 - 8, getWidth(), getHeight() / 2 - 8); + g.setColor(Color.yellow); + g.fillRect(player.getPlayerAbsX(), player.getPlayerAbsY(), 1, 1); + + } // If the game is shutting off if (!TerminalQuit) { @@ -529,5 +609,4 @@ public Vendor getVendor() { public void setVendor(Vendor vendor) { this.vendor = vendor; } - } diff --git a/src/com/redomar/game/audio/AudioHandler.java b/src/com/redomar/game/audio/AudioHandler.java index b3d8e2c..695918e 100644 --- a/src/com/redomar/game/audio/AudioHandler.java +++ b/src/com/redomar/game/audio/AudioHandler.java @@ -33,7 +33,7 @@ public AudioHandler(String path, boolean music) { private void check(String path) { try { - if (!path.equals("")) { + if (!path.isEmpty()) { initiate(path); } else { throw new NullPointerException(); @@ -45,11 +45,11 @@ private void check(String path) { } /** - * Initialises an audio clip from the specified file path. + * Initialises an audio clip by loading an audio file from the specified path. This method sets up the audio stream and prepares the clip for playback. * - * @param path the file path of the audio clip + * @param path the relative file path to the audio clip resource. The path must be accessible from the classpath and should not be null. */ - private void initiate(String path) { + private void initiate(@NotNull String path) { try { InputStream inputStream = new BufferedInputStream(Objects.requireNonNull(AudioHandler.class.getResourceAsStream(path))); AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream); @@ -59,6 +59,12 @@ private void initiate(String path) { AudioInputStream decodedAudioInputStream = AudioSystem.getAudioInputStream(decodeFormat, audioInputStream); clip = AudioSystem.getClip(); clip.open(decodedAudioInputStream); + clip.addLineListener(event -> { + if (event.getType() == LineEvent.Type.STOP) { + stop(); + } + }); + } catch (IOException e) { musicPrinter.cast().exception("Audio file not found " + path); musicPrinter.cast().exception(e.getMessage()); @@ -89,9 +95,18 @@ public void setVolume(float velocity) throws NullPointerException { } public void stop() { - if (clip.isRunning()) clip.stop(); - if (music) musicPrinter.print("Stopping Music"); - active = false; + try { + if (clip == null) throw new RuntimeException("Empty clip"); + if (clip.isRunning()) { + clip.stop(); + if (!music) clip.close(); + } + if (music & active) musicPrinter.print("Stopping Music"); + } catch (Exception e) { + musicPrinter.print("Audio Handler Clip not found"); + } finally { + active = false; + } } public void close() { diff --git a/src/com/redomar/game/entities/Player.java b/src/com/redomar/game/entities/Player.java index 0307749..59caf87 100644 --- a/src/com/redomar/game/entities/Player.java +++ b/src/com/redomar/game/entities/Player.java @@ -19,6 +19,8 @@ public class Player extends Mob { private static double speed = 1; private final InputHandler inputHandler; private int fireRate; + private int playerAbsX; + private int playerAbsY; public Player(LevelHandler level, int x, int y, InputHandler inputHandler, String name, int shirtColour, int faceColour) { super(level, "Player", x, y, PLAYER_TILE, speed, COLLISION_BORDERS, shirtColour, faceColour); @@ -32,9 +34,14 @@ public void tick() { double xa = 0; double ya = 0; + // Calculate and set player's absolute X and Y positions + setPlayerAbsX((((int) getX() - Game.getScreen().getxOffset()) * 2) + 8); + setPlayerAbsY((((int) getY() - Game.getScreen().getyOffset()) * 2) + 7); + + if (inputHandler != null) { - speed = inputHandler.getSHIFTED().isPressed() ? 2 : 1; + speed = inputHandler.getSHIFTED().isPressed() ? 2.5D : 1D; if (inputHandler.getUP_KEY().isPressed()) { ya -= speed; @@ -60,10 +67,22 @@ public void tick() { fireRate = Medium.FIRE_RATE; } if (!swim.isActive(swimType)) { - double dx = Game.getMouse().getX() - 480 / 2d; - double dy = Game.getMouse().getY() - 320 / 2d; + + // Cursor position + int cursorX = Game.getMouse().getX(); + int cursorY = Game.getMouse().getY(); + + // Calculate differences (dx, dy) between cursor and origin + double dx = cursorX - playerAbsX; + double dy = cursorY - playerAbsY; + + // Calculate direction using atan2 double dir = Math.atan2(dy, dx); + + // Continue with shooting logic shoot(x, y, dir, Game.getMouse().getButton()); + + entityPrinter.highlight().print("Direction: " + dir + "ยบ\t" + dx + "x\t" + dy + "y"); } } } @@ -105,4 +124,19 @@ public String getSanitisedUsername() { return this.name; } + public int getPlayerAbsX() { + return playerAbsX; + } + + public void setPlayerAbsX(int playerAbsX) { + this.playerAbsX = playerAbsX; + } + + public int getPlayerAbsY() { + return playerAbsY; + } + + public void setPlayerAbsY(int playerAbsY) { + this.playerAbsY = playerAbsY; + } } diff --git a/src/com/redomar/game/event/InputHandler.java b/src/com/redomar/game/event/InputHandler.java index e5a6f4e..93b7c65 100644 --- a/src/com/redomar/game/event/InputHandler.java +++ b/src/com/redomar/game/event/InputHandler.java @@ -133,17 +133,21 @@ private void toggleKey(int keyCode, boolean isPressed) { } + public void overWriteKey(KeyHandler key, boolean isPressed) { + key.setPressedToggle(isPressed); + } + private void quitGame() { Game.setClosing(true); - if (!inputPrinter.removeLog()) System.err.println("Could not delete Log file"); try { Thread.sleep(1000); } catch (InterruptedException e) { - e.printStackTrace(); + inputPrinter.exception(e.getMessage()); } Game.getLevel().removeEntity(Game.getPlayer().getName()); Game.getGame().stop(); Game.getFrame().dispose(); + if (!inputPrinter.removeLog()) System.err.println("Could not delete Log file"); System.exit(0); } diff --git a/src/com/redomar/game/gfx/Colours.java b/src/com/redomar/game/gfx/Colours.java index c345423..0cf6bf3 100644 --- a/src/com/redomar/game/gfx/Colours.java +++ b/src/com/redomar/game/gfx/Colours.java @@ -12,10 +12,51 @@ private static int get(int colour) { return 255; } - int r = colour / 100 % 10; - int g = colour / 10 % 10; - int b = colour % 10; + int r = Math.min(Math.abs((colour / 100 % 10)), 5); + int g = Math.min(Math.abs((colour / 10 % 10)), 5); + int b = Math.min(Math.abs((colour % 10)), 5); return r * 36 + g * 6 + b; } + + public static int highlight(int colour) { + + if (colour < 0) { + return 255; + } + + int r = Math.min(Math.abs((colour / 100 % 10) + 1), 5); + int g = Math.min(Math.abs((colour / 10 % 10) + 1), 5); + int b = Math.min(Math.abs((colour % 10) + 1), 5); + + return r * 36 + g * 6 + b; + } + + public static int[] getColours(int packedColours) { + int[] colours = new int[4]; + + // Extract each colour component + colours[3] = reverseGet((packedColours >> 24) & 0xFF); // colour4 + colours[2] = reverseGet((packedColours >> 16) & 0xFF); // colour3 + colours[1] = reverseGet((packedColours >> 8) & 0xFF); // colour2 + colours[0] = reverseGet(packedColours & 0xFF); // colour1 + + return colours; + } + + private static int reverseGet(int colour) { + if (colour == 255) { + return -1; // Original colour was less than 0 + } + + int r = colour / 36; + int g = (colour / 6) % 6; + int b = colour % 6; + + return r * 100 + g * 10 + b; + } + + public static int highlight(int colour, int colour1, int colour2, int colour3) { + return (highlight(colour3) << 24) + (highlight(colour2) << 16) + (highlight(colour1) << 8) + (highlight(colour)); + } } diff --git a/src/com/redomar/game/gfx/Screen.java b/src/com/redomar/game/gfx/Screen.java index 19a501b..695f232 100644 --- a/src/com/redomar/game/gfx/Screen.java +++ b/src/com/redomar/game/gfx/Screen.java @@ -14,6 +14,9 @@ public class Screen { private int width; private int height; + private int viewPortWidth; + private int viewPortHeight; + /** * Constructs the draw area * @@ -102,12 +105,62 @@ public void render(int xPos, int yPos, int tile, int colour, int mirrorDir, int } } + private static final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + "0123456789.,:;'\"!?$%()-=+/ "; + + public void renderText(String msg, int xPos, int yPos, int colour, int scale) { + for (int i = 0; i < msg.length(); i++) { + int charIndex = chars.indexOf(msg.charAt(i)); + if (charIndex >= 0) { // Only render if the character is found + int tileInSprite = charIndex + 30 * 32; // Calculate the tile position based on the charIndex + renderCharacter(xPos + i * (8 * scale), yPos, tileInSprite, colour, scale); + } + } + } + + private void renderCharacter(int xPos, int yPos, int tile, int colour, int scale) { + xPos -= xOffset; + yPos -= yOffset; + + int xTile = tile % 32; + int yTile = tile / 32; + int tileOffset = (xTile << 3) + (yTile << 3) * sheet.getWidth(); + + for (int y = 0; y < 8; y++) { + int yPixel = yPos + y * scale; + + for (int x = 0; x < 8; x++) { + int xPixel = xPos + x * scale; + + int col = (colour >> (sheet.pixels[x + y * sheet.getWidth() + tileOffset] * 8)) & 255; + if (col < 255) { + for (int yScale = 0; yScale < scale; yScale++) { + if (yPixel + yScale < 0 || yPixel + yScale >= height) continue; + for (int xScale = 0; xScale < scale; xScale++) { + if (xPixel + xScale < 0 || xPixel + xScale >= width) continue; + pixels[(xPixel + xScale) + (yPixel + yScale) * width] = col; + } + } + } + } + } + } + + + public void setOffset(int xOffset, int yOffset) { this.xOffset = xOffset; this.yOffset = yOffset; } + public int getxOffset() { + return xOffset; + } + + public int getyOffset() { + return yOffset; + } + public int getWidth() { return width; } @@ -131,4 +184,20 @@ public int getHeight() { public void setHeight(int height) { this.height = height; } + + public int getViewPortWidth() { + return viewPortWidth; + } + + public void setViewPortWidth(int viewPortWidth) { + this.viewPortWidth = viewPortWidth; + } + + public int getViewPortHeight() { + return viewPortHeight; + } + + public void setViewPortHeight(int viewPortHeight) { + this.viewPortHeight = viewPortHeight; + } } diff --git a/src/com/redomar/game/gfx/lighting/Night.java b/src/com/redomar/game/gfx/lighting/Night.java new file mode 100644 index 0000000..39e1699 --- /dev/null +++ b/src/com/redomar/game/gfx/lighting/Night.java @@ -0,0 +1,38 @@ +package com.redomar.game.gfx.lighting; + +import com.redomar.game.Game; +import com.redomar.game.gfx.Screen; + +import java.awt.*; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; + +public class Night { + + private final Graphics2D g; + private final Screen screen; + + public Night(Graphics2D g, Screen screen) { + this.g = g; + this.screen = screen; + } + + public void render(int playerX, int playerY) { + double size = 160; + + g.setColor(new Color(0f, 0f, 0f, 0.8f)); + Shape rectangle = new Rectangle(0, 0, screen.getViewPortWidth(), screen.getViewPortHeight() - 30); + Shape circle = new Ellipse2D.Double(playerX - size / 2, playerY - size / 2, size, size); + int mouseX = Game.getMouse().getX(); + int mouseY = Game.getMouse().getY(); + + // Adjust mouse coordinates based on the current offset and scale of the game world + int tileX = ((mouseX) / (8 * 2)); + int tileY = ((mouseY) / (8 * 2)); + Shape smallRectangle = new Rectangle(tileX * 16 - 16, tileY * 16 - 16, 48, 48); + Area area = new Area(rectangle); + area.subtract(new Area(circle)); + area.subtract(new Area(smallRectangle)); + g.fill(area); + } +} diff --git a/src/com/redomar/game/level/LevelHandler.java b/src/com/redomar/game/level/LevelHandler.java index f25e61d..d121919 100644 --- a/src/com/redomar/game/level/LevelHandler.java +++ b/src/com/redomar/game/level/LevelHandler.java @@ -13,7 +13,10 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; import java.util.logging.Level; public class LevelHandler { diff --git a/src/com/redomar/game/level/tiles/AnimatedTile.java b/src/com/redomar/game/level/tiles/AnimatedTile.java index db3a4dd..705d80f 100644 --- a/src/com/redomar/game/level/tiles/AnimatedTile.java +++ b/src/com/redomar/game/level/tiles/AnimatedTile.java @@ -7,13 +7,6 @@ public class AnimatedTile extends BasicTile { private long lastIterationTime; private final int animationSwitchDelay; - /** - * @param id Unique ID for the Tile - * @param animationCoords A 2D array of all x,y-coordinates in Integers. - * @param tileColour Colours from the SpriteSheet. - * @param levelColour Colours to be displayed in the Game World. - * @param animationSwitchDelay Length of time to be delayed to the next frame from the 2D array. - */ public AnimatedTile(int id, int[][] animationCoords, int tileColour, int levelColour, int animationSwitchDelay) { super(id, animationCoords[0][0], animationCoords[0][1], tileColour, levelColour); this.animationTileCoords = animationCoords; @@ -22,11 +15,12 @@ public AnimatedTile(int id, int[][] animationCoords, int tileColour, int levelCo this.animationSwitchDelay = animationSwitchDelay; } + @Override public void tick() { - if ((System.currentTimeMillis() - lastIterationTime) >= (animationSwitchDelay)) { + if ((System.currentTimeMillis() - lastIterationTime) >= animationSwitchDelay) { lastIterationTime = System.currentTimeMillis(); currentAnimationIndex = (currentAnimationIndex + 1) % animationTileCoords.length; - this.tileId = (animationTileCoords[currentAnimationIndex][0] + (animationTileCoords[currentAnimationIndex][1] * 32)); + this.tileId = animationTileCoords[currentAnimationIndex][0] + (animationTileCoords[currentAnimationIndex][1] * 32); } } } diff --git a/src/com/redomar/game/level/tiles/BasicTile.java b/src/com/redomar/game/level/tiles/BasicTile.java index 99f9e44..01b0447 100644 --- a/src/com/redomar/game/level/tiles/BasicTile.java +++ b/src/com/redomar/game/level/tiles/BasicTile.java @@ -1,34 +1,37 @@ package com.redomar.game.level.tiles; +import com.redomar.game.Game; +import com.redomar.game.gfx.Colours; import com.redomar.game.gfx.Screen; import com.redomar.game.level.LevelHandler; public class BasicTile extends Tile { + protected final int tileColour; protected int tileId; - protected int tileColour; - - /** - * This is a tile that does not emit light, nor is sold. This tile does not have any animations. - * - * @param id Unique ID for the Tile - * @param x Specifies the x-coordinate from the SpriteSheet, measured in 8 pixels. Must be 0 - 31 - * @param y Specifies the y-coordinate from the SpriteSheet, measured in 8 pixels. Must be 0 - 31 - * @param tileColour Colours from the SpriteSheet. - * @param levelColour Colours to be displayed in the Game World. - */ + public BasicTile(int id, int x, int y, int tileColour, int levelColour) { super(id, false, false, levelColour); - this.tileId = x + y * 32; this.tileColour = tileColour; } + @Override public void tick() { + // Basic tiles do not update } + @Override public void render(Screen screen, LevelHandler level, int x, int y) { - screen.render(x, y, tileId, tileColour, 0x00, 1); + int[] colours = Colours.getColours(tileColour); + int mouseOverTileX = Game.getTileX(); + int mouseOverTileY = Game.getTileY(); + + if (mouseOverTileX == x / 8 && mouseOverTileY == y / 8) { + screen.render(x, y, tileId, Colours.highlight(colours[0], colours[1], colours[2], colours[3]), 0x00, 1); + } else { + screen.render(x, y, tileId, Colours.get(colours[0], colours[1], colours[2], colours[3]), 0x00, 1); + } } } diff --git a/src/com/redomar/game/level/tiles/BasicSolidTile.java b/src/com/redomar/game/level/tiles/SolidTile.java similarity index 82% rename from src/com/redomar/game/level/tiles/BasicSolidTile.java rename to src/com/redomar/game/level/tiles/SolidTile.java index 220e154..a8913da 100644 --- a/src/com/redomar/game/level/tiles/BasicSolidTile.java +++ b/src/com/redomar/game/level/tiles/SolidTile.java @@ -1,6 +1,6 @@ package com.redomar.game.level.tiles; -public class BasicSolidTile extends BasicTile { +public class SolidTile extends BasicTile { /** * This is a solid tile, but does not emit any light. This tile does not have any animations. @@ -11,9 +11,8 @@ public class BasicSolidTile extends BasicTile { * @param tileColour Colours from the SpriteSheet. * @param levelColour Colours to be displayed in the Game World. */ - public BasicSolidTile(int id, int x, int y, int tileColour, int levelColour) { + public SolidTile(int id, int x, int y, int tileColour, int levelColour) { super(id, x, y, tileColour, levelColour); this.solid = true; } - } diff --git a/src/com/redomar/game/level/tiles/Tile.java b/src/com/redomar/game/level/tiles/Tile.java index 0804e1b..cd66594 100644 --- a/src/com/redomar/game/level/tiles/Tile.java +++ b/src/com/redomar/game/level/tiles/Tile.java @@ -4,126 +4,97 @@ import com.redomar.game.gfx.Screen; import com.redomar.game.level.LevelHandler; + +@SuppressWarnings({"StaticInitializerReferencesSubClass", "OctalInteger"}) public abstract class Tile { private static final Tile[] tiles = new Tile[256]; - private static final Tile VOID = new BasicSolidTile(0, 0, 0, Colours.get(0, -1, -1, -1), 0xFF000000); - private static final Tile STONE = new BasicSolidTile(1, 1, 0, Colours.get(-1, 444, 333, -1), 0xFF555555); - private static final Tile CHISELED_stone = new BasicTile(2, 2, 0, Colours.get(-1, 333, 222, -1), 0xFF666666); - private static final Tile GRASS = new BasicTile(3, 3, 0, Colours.get(-1, 131, 141, -1), 0xFF00FF00); - private static final Tile WATER = new AnimatedTile(4, new int[][] { { 0, 5 }, { 1, 5 }, { 2, 5 }, { 1, 5 } }, Colours.get(-1, 004, 115, -1), 0xFF0000FF, 1000); - private static final Tile FLOWER_rose = new BasicTile(5, 4, 0, Colours.get(131, 151, 510, 553), 0xFFCCFF33); - private static final Tile FLOWER_dandelion = new BasicTile(6, 4, 0, Colours.get(131, 151, 553, 510), 0xFFFFCC33); - private static final Tile SAND = new BasicTile(7, 5, 0, Colours.get(-1, 553, 554, 555), 0xFFFFFF99); - private static final Tile CHEST_a = new BasicSolidTile(8, 0, 1, Colours.get(333, 111, 420, 000), 0xFFFF0001); - private static final Tile CHEST_b = new BasicSolidTile(9, 1, 1, Colours.get(333, 111, 420, 000), 0xFFFF0002); - private static final Tile CARPET_red = new BasicTile(10, 5, 0, Colours.get(-1, 311, 411, 311), 0xFFAA3636); - private static final Tile PORTAL = new AnimatedTile(11, new int[][] { { 3, 5 }, { 4, 5 }, { 5, 5 }, { 6, 5 }, { 7, 5 }, { 8, 5 }, { 9, 5 }, { 10, 5 } }, Colours.get(-1, 005, 305, -1), 0xFF00EAFF, 100); - private static final Tile MAGMA = new AnimatedTile(12, new int [][] { { 0, 5 }, { 1, 5 }, { 2, 5 }, { 1, 5 } }, Colours.get(-1, 400, 511, -1), 0xFFF00F0F, 1000); - private static final Tile DIRT = new BasicTile(13, 3, 0, Colours.get(0, 210, 321, -1), 0xFF442200); - private static final Tile DIRT_WET = new AnimatedTile(14, new int[][] { { 1, 5 }, { 2, 5 } }, Colours.get(-1, 211, 322, -1), 0xFF663300, 1500); - - protected byte id; + + static { + /* VOID */ + tiles[0] = new SolidTile(0, 0, 0, Colours.get(0, -1, -1, -1), 0xFF000000); + /* STONE */ + tiles[1] = new SolidTile(1, 1, 0, Colours.get(-1, 444, 333, -1), 0xFF555555); + /* CHISELED_stone */ + tiles[2] = new BasicTile(2, 2, 0, Colours.get(-1, 333, 222, -1), 0xFF666666); + /* GRASS */ + tiles[3] = new BasicTile(3, 3, 0, Colours.get(-1, 131, 141, -1), 0xFF00FF00); + /* WATER */ + tiles[4] = new AnimatedTile(4, new int[][]{{0, 5}, {1, 5}, {2, 5}, {1, 5}}, Colours.get(-1, 004, 115, -1), 0xFF0000FF, 1000); + /* FLOWER_rose */ + tiles[5] = new BasicTile(5, 4, 0, Colours.get(131, 151, 510, 553), 0xFFCCFF33); + /* FLOWER_dandelion */ + tiles[6] = new BasicTile(6, 4, 0, Colours.get(131, 151, 553, 510), 0xFFFFCC33); + /* SAND */ + tiles[7] = new BasicTile(7, 5, 0, Colours.get(-1, 553, 554, 555), 0xFFFFFF99); + /* CHEST_a */ + tiles[8] = new SolidTile(8, 0, 1, Colours.get(333, 111, 420, 000), 0xFFFF0001); + /* CHEST_b */ + tiles[9] = new SolidTile(9, 1, 1, Colours.get(333, 111, 420, 000), 0xFFFF0002); + /* CARPET_red */ + tiles[10] = new BasicTile(10, 5, 0, Colours.get(-1, 311, 411, 311), 0xFFAA3636); + /* PORTAL */ + tiles[11] = new AnimatedTile(11, new int[][]{{3, 5}, {4, 5}, {5, 5}, {6, 5}, {7, 5}, {8, 5}, {9, 5}, {10, 5}}, Colours.get(-1, 005, 305, -1), 0xFF00EAFF, 100); + /* MAGMA */ + tiles[12] = new AnimatedTile(12, new int[][]{{0, 5}, {1, 5}, {2, 5}, {1, 5}}, Colours.get(-1, 400, 511, -1), 0xFFF00F0F, 1000); + /* DIRT */ + tiles[13] = new BasicTile(13, 3, 0, Colours.get(0, 210, 321, -1), 0xFF442200); + /* DIRT_WET */ + tiles[14] = new AnimatedTile(14, new int[][]{{1, 5}, {2, 5}}, Colours.get(-1, 211, 322, -1), 0xFF663300, 1500); + } + + protected final byte id; + protected final boolean emitter; + protected final int levelColour; protected boolean solid; - protected boolean emitter; - private int levelColour; - public Tile(int id, boolean isSolid, boolean isEmitter, int colour) { + protected Tile(int id, boolean solid, boolean emitter, int levelColour) { this.id = (byte) id; + this.solid = solid; + this.emitter = emitter; + this.levelColour = levelColour; - if (getTiles()[id] != null) { - throw new RuntimeException("Duplicate tile id on:" + id); - } - - this.solid = isSolid; - this.emitter = isEmitter; - this.levelColour = colour; - getTiles()[id] = this; - } - - public byte getId() { - return id; + tiles[id] = this; } - public boolean isSolid() { - return solid; - } - - public boolean isEmitter() { - return emitter; - } - - public abstract void tick(); - - public abstract void render(Screen screen, LevelHandler level, int x, int y); - - public int getLevelColour() { - return levelColour; + public static Tile getTile(int id) { + return tiles[id]; } public static Tile getStone() { - return STONE; - } - - public static Tile getChiseledStone() { - return CHISELED_stone; + return getTile(1); } public static Tile getGrass() { - return GRASS; - } - - public static Tile getFlowerRose() { - return FLOWER_rose; - } - - public static Tile getFlowerDandelion() { - return FLOWER_dandelion; - } - - public static Tile getSand() { - return SAND; - } - - public static Tile getWater() { - return WATER; + return getTile(2); } public static Tile getVoid() { - return VOID; + return getTile(0); } public static Tile[] getTiles() { return tiles; } - public static Tile getChestA() { - return CHEST_a; - } - - public static Tile getChestB() { - return CHEST_b; + public byte getId() { + return id; } - public static Tile getCarpetRed() { - return CARPET_red; + public boolean isSolid() { + return solid; } - public static Tile getPortal() { - return PORTAL; + public boolean isEmitter() { + return emitter; } - public static Tile getMagma() { - return MAGMA; - } + public abstract void tick(); - public static Tile getDirt() { - return DIRT; - } + public abstract void render(Screen screen, LevelHandler level, int x, int y); - public static Tile getDirtWet() { - return DIRT_WET; + public int getLevelColour() { + return levelColour; } - } diff --git a/src/com/redomar/game/log/PrintTypes.java b/src/com/redomar/game/log/PrintTypes.java index d0ea642..c2708f3 100644 --- a/src/com/redomar/game/log/PrintTypes.java +++ b/src/com/redomar/game/log/PrintTypes.java @@ -1,5 +1,5 @@ package com.redomar.game.log; public enum PrintTypes { - GAME, LEVEL, ENTITY, INPUT, MUSIC, NETWORK, SERVER, ERROR, TEST + GAME, LEVEL, ENTITY, INPUT, MUSIC, NETWORK, SERVER, ERROR, TEST, SYSTEM } diff --git a/src/com/redomar/game/log/Printer.java b/src/com/redomar/game/log/Printer.java index 73d2c3b..a27205f 100644 --- a/src/com/redomar/game/log/Printer.java +++ b/src/com/redomar/game/log/Printer.java @@ -13,6 +13,7 @@ public class Printer { private String message; private boolean evalTypeAsException = false; private boolean mute = false; + private boolean highlight = false; public Printer() { this.type = PrintTypes.GAME; @@ -34,6 +35,9 @@ public void print(String message) { } private void printOut() { + String ANSI_RESET = "\u001B[0m"; + String ANSI_CYAN = "\u001B[36m"; + String ANSI_BACKGROUND_BLACK = "\u001B[40m"; String msgTime = "[" + time.getTime() + "]"; String msgType = "[" + type.toString() + "]"; @@ -55,7 +59,11 @@ private void printOut() { logFile.log(String.format("%s%s\t%s", msgTime, msgType, message)); if (mute) return; - String formattedStringForConsole = String.format("%s%s %s%n", msgType, msgTime, message); + String formattedStringForConsole = String.format("%s %s\t %s%n", msgTime, msgType, message); + if (highlight) { + formattedStringForConsole = String.format("%s %s\t%s %s%s %s%n", msgTime, msgType, ANSI_BACKGROUND_BLACK, ANSI_CYAN, message, ANSI_RESET); + highlight = false; + } if (type.equals(PrintTypes.ERROR) || evalTypeAsException) { System.err.printf(formattedStringForConsole); @@ -73,6 +81,16 @@ private PrintToLog printToLogType(PrintTypes type) { } } + /** + * Highlight the message in the console + * + * @return Printer + */ + public Printer highlight() { + this.highlight = true; + return this; + } + public boolean removeLog() { return new File(".log.txt").delete(); } @@ -111,4 +129,6 @@ public Printer exception(String exceptionMessage) { print(exceptionMessage); return this; } + + } diff --git a/src/com/redomar/game/menu/Menu.java b/src/com/redomar/game/menu/Menu.java index c78044e..79d50f7 100644 --- a/src/com/redomar/game/menu/Menu.java +++ b/src/com/redomar/game/menu/Menu.java @@ -4,6 +4,7 @@ import com.redomar.game.audio.AudioHandler; import com.redomar.game.event.Mouse; import com.redomar.game.lib.Font; +import com.redomar.game.log.PrintTypes; import com.redomar.game.log.Printer; import com.thehowtotutorial.splashscreen.JSplash; @@ -36,6 +37,9 @@ public class Menu implements Runnable { private final Color UNSELECTED_COLOUR = new Color(0xFFCC5500); public static void play() { + Printer printer = new Printer(); + String property = System.getProperty("java.version"); + printer.print("RUNTIME JAVA VERSION " + property, PrintTypes.SYSTEM); try { // Splash screen AtomicReference splashImageResource = new AtomicReference<>(Game.class.getResource("/splash/splash.png")); @@ -49,7 +53,6 @@ public static void play() { Game.setBackgroundMusic(new AudioHandler("/music/Towards The End.wav", true)); Game.getBackgroundMusic().setVolume(VOLUME_IN_DB); } catch (Exception e) { - Printer printer = new Printer(); printer.exception(e.getMessage()); } UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); @@ -67,7 +70,7 @@ public static void play() { splash.splashOff(); splash.removeAll(); } catch (Exception e) { - e.printStackTrace(); + printer.exception(e.getMessage()); } }