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());
}
}