diff --git a/Team A2/Test Cases/Test Case SCP-49.md b/Team A2/Test Cases/Test Case SCP-49.md new file mode 100644 index 0000000..c11eb6d --- /dev/null +++ b/Team A2/Test Cases/Test Case SCP-49.md @@ -0,0 +1,31 @@ +### Test Case Information +| TEST CASE ID | SCP-49 | +| :--- | :--- | +| Owner of Test | Nicholas Tourony | +| Test Name | Difficulty Test | +| Date of Last Revision | 11/30/2021 | +| Test Objective | Ensure that the difficulty settings change the amount of health the cat has and changes the movement speed of the enemies and their projectiles. | + +### Procedure + +|Step | Action | Expected Result | Pass/Fail | +|:---:| :--- | :---- | :---: | +|1| Run the game| The game successfully opens |Pass| +|2| Select the difficulty menu. | The difficulty menu opens. | Pass | +|3| Select normal difficulty | The game lets you select normal difficulty and automatically puts you back to the main menu. | Pass| +|4| Start the game. | The cat is loaded into the first level with 3 health and the enemies and projectiles move at their slowest speed. | Pass| +|5| Take projectile damage until the character dies | The cat can take 3 projectile hits before dying. | Pass| +|6| Respawn and run into an enemy. | The cat instantly dies. | Pass | +|7| Select hard difficulty and start the game | The cat is loaded into the first level with 2 health and the enemies move faster. | Pass | +|8| Take projectile damage until the character dies | The cat can take 2 projectile hits before dying. | Pass| +|9| Respawn and run into an enemy. | The cat instantly dies. | Pass | +|10| Select hardcore difficulty and start the game. | The cat is loaded into the first level with 1 health and the enemies move even faster. | Pass | +|11| Take damage. | The cat dies. | Pass | +|12| Press escape. | The player is returned to the main menu. | Pass | +|13| Load into level 6, die to any enemy, and press space. | The player is restarted at the tutorial level. | Pass | +|14| Restart the game and beat each level at every difficulty to ensure the levels are still possible. (Use the level select to make hardcore mode easier) | Each level is beat on every difficulty | Passed | + +### Test Completion +- **Tester**: Thomas Kwashnak +- **Date of Test**: 11/30/2021 +- **Test Result**: Passed \ No newline at end of file diff --git a/Team A2/Test Cases/Test Case SCP-52.md b/Team A2/Test Cases/Test Case SCP-52.md new file mode 100644 index 0000000..2fc5bff --- /dev/null +++ b/Team A2/Test Cases/Test Case SCP-52.md @@ -0,0 +1,35 @@ +### Test Case Information +| TEST CASE ID | SCP-52 | +| :--- | :--- | +| Owner of Test | Thomas Kwashnak | +| Test Name | Test Speed-Running Enhancements | +| Date of Last Revision | 11/23/2021 | +| Test Objective | Testing Additions and Functionality Changes of Speed-Running Enhancements | + +#### Tested Enhancements + - Stopwatch + - Top-Right of screen when playing + - Entering from main menu resets timer + - Stores time took to complete each level, displays current level's time, as well as total time for all previous times + - Some way to display the user's final result (breakdown of levels) after beating final boss + - Deaths are in some way detrimental to a user's level time (either make death animation slower, or make death screen longer) + - Pausing the game pauses the time + - Level Completed Screens display quicker (shorter time) + +### Procedure + +|Step | Action | Expected Result | Pass/Fail | +|:---:| :--- | :---- | :---: | +|1|Run the game|The game loads up. There is no timer on the main menu.|| +|2|Click Play|The tutorial level is loaded. On the top right is one timer that times the amount of elapsed time since the player started.|| +|3|Navigate and die in the tutorial level|The dead screen has a count-down displayed.|| +|4|While the death screen's countdown is counting down, try to hit space to respawn|The player is not able to respawn until the countdown timer is over|| +|5|Navigate through the level and die once again. Once the death screen is loaded, hit escape to go back to the main menu|The main menu is loaded and the timer no longer displays|| +|6|Hit "Play" again to go back into the main menu|The tutorial level is loaded, and the timer is reset back to 0|| +|7|Complete the tutorial level|The level completed screen is shown, but only for a brief second before the next level is loaded. On the top right is now two timers, one that displays the total time, and another that displays the elapsed time for the current level|| +|8|Hit `P/Escape` to pause the game|While the game is paused, timers are also paused|| + +### Test Completion +- **Tester**: [TESTER NAME] +- **Date of Test**: DATE OF TEST +- **Test Result**: TEST RESULT \ No newline at end of file diff --git a/Team A2/Test Cases/Test Case SCP-56.md b/Team A2/Test Cases/Test Case SCP-56.md new file mode 100644 index 0000000..c32e8ac --- /dev/null +++ b/Team A2/Test Cases/Test Case SCP-56.md @@ -0,0 +1,46 @@ +### Test Case Information +| TEST CASE ID | SCP-56 | +| :--- | :--- | +| Owner of Test | Thomas Kwashnak | +| Test Name | Test Game Speed | +| Date of Last Revision | 11/16/2021 | +| Test Objective | Ensure that the game runs consistently through multiple distributions | +| Test Notes| Due to the nature of this test case, multiple runs of the test must be conducted on various machines of different make and models. Measurements will be taken using a recording software, and should not be expected to be perfect| +|Software Requirement|**Screen recording software:** Needs to be able to record the screen such that the tester can go back and accurately measure the time between events. *Suggested:* OBS| +|Prerequisites|Test Case SCP-57 must be completed prior to this merge| + + +### Procedure + +|Step | Action | Expected Result | Measurement Field | +|:---:| :--- | :---- | :----:| +|1|Run the game|The game successfully opens|| +|2|Begin recording|A screen recorder is started that records the game|| +|3|Click *Play Game*|The tutorial level is successfully opened|| +|4|Walk to the left-most edge of the level|The cat is at the left most bound of the tutorial level|| +|5|Hold `D/Right Arrow` until the cat is blocked from moving by the tree|The cat moves from the edge of the map into the tree|**Measurement 1:** The time it takes starting when the cat begins moving to when the cat finishes moving| +|6|Hold `Shift`, then hold `A/Left Arrow` until the cat stops moving at the left edge of the map|The player sprints to the left-most edge of the map|**Measurement 2:** The time it takes for the cat to move starting when the cat begins moving to when the cat is stopped by the map edge| +|7|Hold `Space/W/Up Arrow` until the cat reaches the ground again|The cat jumps and lands back on the ground|**Measurement 3:** The time it takes starting when the cat begins jumping to when the cat hits the ground again| +|8|Hold `e` until the player shoots a reasonable amount of fireballs|The cat shoots multiple fireballs|**Measurement 4:** The time between two consecutive fireballs spawning (multiple attempts can be averaged if needed)|\ +|9|Complete the tutorial level|When the tutorial level is completed, the `level complete` is shown, and after a few seconds the player is loaded into the next level|**Measurement 5:** The time that the `level complete` screen is shown + +### Test Execution +Instances of tests are listed in table below (template provided). Measurements listed in instructions above are listed below for test completer to evaluate. + +**Overall Test Completion** indicates whether all steps were completed and no errors / inability to follow instructions occurred. + + +|Date|Name|Device Information | Measure 1|Measure 2|Measure 3|Measure 5|Measure 4|Pass/Fail| +|:---:|:---|:---|:---:|:---:|:---:|:---:|:---:|:---:| +|[DATE]|[TESTER_NAME]|[CONFIG]|[MEASURE 1]|[MEASURE 2]|[MEASURE 3]|[MEASURE 4]|[MEASURE 5]|[PASS/FAIL]| +|11/29/2021 10:41 AM|Thomas Kwashnak|Kubuntu 21.10 Laptop, Intel i7 CPU, NVIDIA MX330 GPU|\~ 2 seconds|\~1 second|\~1 second|\~1-2 seconds|~3 seconds|Pass. Game runs at expected speed| + +[comment]: <> (Add test rows to end here ^^) + +### Completion Criteria + - There are no outliers on all measurements + +### Test Completion +- **Tester**: [TEST REVIEWER NAME] +- **Date of Test**: [TEST COMPLETION DATE] +- **Test Result**: [TEST RESULT] \ No newline at end of file diff --git a/Team A2/Test Cases/Test Case SCP-57.md b/Team A2/Test Cases/Test Case SCP-57.md new file mode 100644 index 0000000..d10970e --- /dev/null +++ b/Team A2/Test Cases/Test Case SCP-57.md @@ -0,0 +1,56 @@ +### Test Case Information +| TEST CASE ID | SCP-57 | +| :--- | :--- | +| Owner of Test | Thomas Kwashnak | +| Test Name | Test Player Functionality | +| Date of Last Revision | 11/16/2021 | +| Test Objective | Ensure that all current functionalities of the player is implemented in new Player class and still functional | + +Functionality of `Player.java` tested: + - Movement, and movement animations + - Jumping, including gravity and hitting floor + - Animation while jumping + - Collision with enemies + - Shooting, including animations while standing still and moving + - Collision with map boundaries and map walls + - Ability to complete all levels with physics + +### Procedure + +|Step | Action | Expected Result | Pass/Fail | +|:---:| :--- | :---- | :---: | +|0a| **Evaluated on Each Step:** | Player Animation changes as expected |`N/A`| +|1| Run the game| The game successfully opens || +|2| Click "*Play*" |The tutorial level is entered, game does not crash. 3 hearts are displayed on the top of the screen.|| +|3| Hold `A/Left Arrow` |The player moves to the left. The player does not fall off the map and is instead stopped at the edge|| +|4| Hold `D/Right Arrow`|The player moves to the right. The player is stopped once they hit the tree|| +|5| Hold both `A/Left Arrow` and `D/Right Arrow`|The player stands still|| +|6| Tap `Space/W/Up Arrow` | The player short-jumps. The player then falls back down after the button is let go and lands on the ground|| +|7|Hold `Space/W/Up Arrow` | The player high-jumps. The player falls back down after a certain height and and falls back down to the ground|| +|8|Hold `Shift` and repeat steps 3-5|The player moves in the same ways as stated above, but faster|| +|9|Hold `ctrl`|The player crouches|| +|10|Tap `A/Left Arrow`, then hit `Space/W/Up Arrow` quickly followed by `D/Right Arrow`|The player faces left, then switches to facing right while in the air|| +|11|Tap `D/Right Arrow`, then hit `Space/W/Up Arrow` quickly followed by `A/Left Arrow`|The player faces right, then switches to facing left while in the air|| +|12|Tap `A/Left Arrow`, then tap `e` (*Attack Button*)|The player faces to the left, and attacks, sending a projectile to the left|| +|13|Hold `A/Left Arrow`, then tap `e` |The player moves to the left, and attacks while moving|| +|14|Tap `Space/W/Up Arrow`, then tap `e` while in the air|The player jumps while facing left, and then shoots a projectile while in the air|| +|15|Tap `D/Right Arrow`, then tap `e` |The player faces to the right, and attacks, sending a projectile to the right|| +|16|Hold `D/Right Arrow`, then tap `e` |The player moves to the right, and attacks while moving|| +|17|Tap `Space/W/Up Arrow`, then tap `e` while in the air|The player jumps while facing right, and then shoots a projectile while in the air|| +|18|Hold `e`|The player shoots once, then cannot shoot for a specified duration of time|| +|19|Navigate and run into enemy|The player's health is set to 0 and the player dies. Death animation is played and player falls down to the ground|| +|20|Restart the game|The level is loaded again, and the player's health is back to full|| +|21|Navigate and run into a projectile from an enemy|The player's health is now 2 (reduced by 1)|| +|22|Navigate and attempt to run off the right-most edge of the map (*without hitting the gold level-complete box*)|The player is stopped and cannot run off of the map|| +|23|Hit the complete-level gold box|The level is completed and the player walks off to the right of the screen|| +|24|Hit Escape, go back to main menu|The main menu is displayed|| +|25|Hit "Play Game"|The first level is loaded|| +|26|Play all levels of the game|Each level is completable|| + + + + +### Test Completion +- **Tester**: [TESTER NAME] +- **Date of Test**: DATE OF TEST +- **Test Result**: TEST RESULT \ No newline at end of file diff --git a/src/Enemies/BugEnemy.java b/src/Enemies/BugEnemy.java index 71ff785..a46ebc9 100644 --- a/src/Enemies/BugEnemy.java +++ b/src/Enemies/BugEnemy.java @@ -1,13 +1,13 @@ package Enemies; import Builders.FrameBuilder; +import Engine.GamePanel; import Engine.ImageLoader; import GameObject.Frame; import GameObject.ImageEffect; import GameObject.SpriteSheet; import Level.Enemy; import Level.Player; -import Utils.AirGroundState; import Utils.Direction; import Utils.Point; @@ -19,10 +19,14 @@ public class BugEnemy extends Enemy { private float gravity = .5f; - private float movementSpeed = .5f; + + // different speeds depending on the difficulty + private static final float NORMAL_SPEED = 0.5f, HARD_SPEED = 0.7f, HARDCORE_SPEED = 0.9f; + float movementSpeed = NORMAL_SPEED; + private Direction startFacingDirection; private Direction facingDirection; - private AirGroundState airGroundState; + private boolean isInAir; public BugEnemy(Point location, Direction facingDirection) { super(location.x, location.y, new SpriteSheet(ImageLoader.load("BugEnemy.png"), 24, 15), "WALK_LEFT"); @@ -39,7 +43,7 @@ public void initialize() { } else if (facingDirection == Direction.LEFT) { currentAnimationName = "WALK_LEFT"; } - airGroundState = AirGroundState.GROUND; + isInAir = false; } @Override @@ -47,11 +51,21 @@ public void update(Player player) { float moveAmountX = 0; float moveAmountY = 0; + // set the movement speed of the enemy depending on what difficulty it selected + if (GamePanel.getDifficulty() == 2) + { + movementSpeed = HARD_SPEED; + } + else if (GamePanel.getDifficulty() == 1) + { + movementSpeed = HARDCORE_SPEED; + } + // add gravity (if in air, this will cause bug to fall) moveAmountY += gravity; // if on ground, walk forward based on facing direction - if (airGroundState == AirGroundState.GROUND) { + if (!isInAir) { if (facingDirection == Direction.RIGHT) { moveAmountX += movementSpeed; } else { @@ -86,11 +100,7 @@ public void onEndCollisionCheckY(boolean hasCollided, Direction direction) { // if bug is colliding with the ground, change its air ground state to GROUND // if it is not colliding with the ground, it means that it's currently in the air, so its air ground state is changed to AIR if (direction == Direction.DOWN) { - if (hasCollided) { - airGroundState = AirGroundState.GROUND; - } else { - airGroundState = AirGroundState.AIR; - } + isInAir = !hasCollided; } } diff --git a/src/Enemies/CyborgEnemy.java b/src/Enemies/CyborgEnemy.java index 2ff25f7..395beed 100644 --- a/src/Enemies/CyborgEnemy.java +++ b/src/Enemies/CyborgEnemy.java @@ -1,6 +1,7 @@ package Enemies; import Builders.FrameBuilder; +import Engine.GamePanel; import Engine.ImageLoader; import GameObject.Frame; import GameObject.ImageEffect; @@ -8,13 +9,11 @@ import Level.Enemy; import Level.Player; import Projectiles.LazerBeam; -import Utils.AirGroundState; import Utils.Direction; import Utils.Point; import Utils.Stopwatch; import java.util.HashMap; -import java.util.Timer; // This class is for the cyborg // It walks back and forth between two set points (startLocation and endLocation) @@ -29,10 +28,14 @@ public class CyborgEnemy extends Enemy { // Whether or not the game is waiting for LazerBeams to launch private boolean wait = false; - protected float movementSpeed = 0.5f; + // different speeds depending on the difficulty + private static final float NORMAL_SPEED = 0.5f, HARD_SPEED = 0.7f, HARDCORE_SPEED = 0.9f; + float movementSpeed = NORMAL_SPEED; + private static final float NORMAL_LAZER_SPEED = 1.5f, HARD_LAZER_SPEED = 1.7f, HARDCORE_LAZER_SPEED = 1.9f; + private Direction startFacingDirection; protected Direction facingDirection; - protected AirGroundState airGroundState; + protected boolean isInAir; // timer is used to determine when a lazer is to be shot out protected Stopwatch shootTimer = new Stopwatch(); @@ -62,7 +65,7 @@ public void initialize() { } else if (facingDirection == Direction.LEFT) { currentAnimationName = "WALK_LEFT"; } - airGroundState = AirGroundState.GROUND; + isInAir = false; // every 2 seconds, the lazer will be shot out shootTimer.setWaitTime(2000); @@ -72,7 +75,20 @@ public void initialize() { public void update(Player player) { float startBound = startLocation.x; float endBound = endLocation.x; + float lazerMovementSpeed = NORMAL_LAZER_SPEED; + // set the movement speed of the enemy and lazer attack depending on what difficulty it selected + if (GamePanel.getDifficulty() == 2) + { + movementSpeed = HARD_SPEED; + lazerMovementSpeed = HARD_LAZER_SPEED; + } + else if (GamePanel.getDifficulty() == 1) + { + movementSpeed = HARDCORE_SPEED; + lazerMovementSpeed = HARDCORE_LAZER_SPEED; + } + // if shoot timer is up and cyborg is not currently shooting, set its state to SHOOT if (shootTimer.isTimeUp() && cyborgState != cyborgState.SHOOT) { cyborgState = cyborgState.SHOOT; @@ -114,16 +130,14 @@ public void update(Player player) { // define where LazerBeam will spawn on map (x location) relative to cyborg enemy's location // and define its movement speed int LazerBeamX; - float movementSpeed; LazerBeamX = Math.round(getX()); - movementSpeed = 1.5f; // define where LazerBeam will spawn on the map (y location) relative to cyborg enemy's location int LazerBeamY = Math.round(getY() + 15); // create LazerBeam enemy - LazerBeam LazerBeam = new LazerBeam(new Point(LazerBeamX + 20, LazerBeamY), movementSpeed, 1000); - LazerBeam LazerBeam2 = new LazerBeam(new Point(LazerBeamX - 10, LazerBeamY), -movementSpeed, 1000); + LazerBeam LazerBeam = new LazerBeam(new Point(LazerBeamX + 20, LazerBeamY), lazerMovementSpeed, 1000); + LazerBeam LazerBeam2 = new LazerBeam(new Point(LazerBeamX - 10, LazerBeamY), -lazerMovementSpeed, 1000); // add LazerBeam enemy to the map for it to offically spawn in the level map.addProjectile(LazerBeam); diff --git a/src/Enemies/DinosaurEnemy.java b/src/Enemies/DinosaurEnemy.java index 3921528..5994cb0 100644 --- a/src/Enemies/DinosaurEnemy.java +++ b/src/Enemies/DinosaurEnemy.java @@ -1,6 +1,7 @@ package Enemies; import Builders.FrameBuilder; +import Engine.GamePanel; import Engine.ImageLoader; import GameObject.Frame; import GameObject.ImageEffect; @@ -8,7 +9,6 @@ import Level.Enemy; import Level.Player; import Projectiles.Fireball; -import Utils.AirGroundState; import Utils.Direction; import Utils.Point; import Utils.Stopwatch; @@ -25,10 +25,14 @@ public class DinosaurEnemy extends Enemy { protected Point startLocation; protected Point endLocation; - protected float movementSpeed = 1f; + // different speeds depending on the difficulty + private static final float NORMAL_SPEED = 1f, HARD_SPEED = 1.2f, HARDCORE_SPEED = 1.4f; + float movementSpeed = NORMAL_SPEED; + + private static final float NORMAL_FIREBALL_SPEED = 1.5f, HARD_FIREBALL_SPEED = 1.7f, HARDCORE_FIREBALL_SPEED = 1.9f; private Direction startFacingDirection; protected Direction facingDirection; - protected AirGroundState airGroundState; + protected boolean isInAir; // timer is used to determine when a fireball is to be shot out protected Stopwatch shootTimer = new Stopwatch(); @@ -56,7 +60,7 @@ public void initialize() { } else if (facingDirection == Direction.LEFT) { currentAnimationName = "WALK_LEFT"; } - airGroundState = AirGroundState.GROUND; + isInAir = false; // every 2 seconds, the fireball will be shot out shootTimer.setWaitTime(2000); @@ -66,7 +70,19 @@ public void initialize() { public void update(Player player) { float startBound = startLocation.x; float endBound = endLocation.x; + float fireballMovementSpeed = NORMAL_FIREBALL_SPEED; + // set the movement speed of the enemy and fireball attack depending on what difficulty it selected + if (GamePanel.getDifficulty() == 2) + { + movementSpeed = HARD_SPEED; + fireballMovementSpeed = HARD_FIREBALL_SPEED; + } + else if (GamePanel.getDifficulty() == 1) + { + movementSpeed = HARDCORE_SPEED; + fireballMovementSpeed = HARDCORE_FIREBALL_SPEED; + } // if shoot timer is up and dinosaur is not currently shooting, set its state to SHOOT if (shootTimer.isTimeUp() && dinosaurState != DinosaurState.SHOOT) { dinosaurState = DinosaurState.SHOOT; @@ -108,20 +124,18 @@ public void update(Player player) { // define where fireball will spawn on map (x location) relative to dinosaur enemy's location // and define its movement speed int fireballX; - float movementSpeed; if (facingDirection == Direction.RIGHT) { fireballX = Math.round(getX()) + getScaledWidth(); - movementSpeed = 1.5f; } else { fireballX = Math.round(getX()); - movementSpeed = -1.5f; + fireballMovementSpeed = -fireballMovementSpeed; } // define where fireball will spawn on the map (y location) relative to dinosaur enemy's location int fireballY = Math.round(getY()) + 4; // create Fireball enemy - Fireball fireball = new Fireball(new Point(fireballX, fireballY), movementSpeed, 1000); + Fireball fireball = new Fireball(new Point(fireballX, fireballY), fireballMovementSpeed, 1000); // add fireball enemy to the map for it to offically spawn in the level map.addProjectile(fireball); diff --git a/src/Enemies/Dog.java b/src/Enemies/Dog.java index 62db627..6679362 100644 --- a/src/Enemies/Dog.java +++ b/src/Enemies/Dog.java @@ -1,13 +1,13 @@ package Enemies; import Builders.FrameBuilder; +import Engine.GamePanel; import Engine.ImageLoader; import GameObject.Frame; import GameObject.SpriteSheet; import Level.Enemy; import Level.Player; import Projectiles.Bone; -import Utils.AirGroundState; import Utils.Direction; import Utils.Point; import Utils.Stopwatch; @@ -24,10 +24,14 @@ public class Dog extends Enemy { protected Point startLocation; protected Point endLocation; - protected float movementSpeed = 1f; + // different speeds depending on the difficulty + private static final float NORMAL_SPEED = 1f, HARD_SPEED = 1.2f, HARDCORE_SPEED = 1.4f; + float movementSpeed = NORMAL_SPEED; + + private static final float NORMAL_BONE_SPEED = 1.5f, HARD_BONE_SPEED = 1.7f, HARDCORE_BONE_SPEED = 1.9f; private Direction startFacingDirection; protected Direction facingDirection; - protected AirGroundState airGroundState; + protected boolean isInAir; // timer is used to determine when a bone is to be shot out protected Stopwatch shootTimer = new Stopwatch(); @@ -55,8 +59,7 @@ public void initialize() { } else if (facingDirection == Direction.LEFT) { currentAnimationName = "WALK_LEFT"; } - airGroundState = AirGroundState.GROUND; - + isInAir = false; // every 2 seconds, the bone will be shot out shootTimer.setWaitTime(2000); } @@ -65,6 +68,19 @@ public void initialize() { public void update(Player player) { float startBound = startLocation.x; float endBound = endLocation.x; + float boneMovementSpeed = NORMAL_BONE_SPEED; + + // set the movement speed of the enemy and fireball attack depending on what difficulty it selected + if (GamePanel.getDifficulty() == 2) + { + movementSpeed = HARD_SPEED; + boneMovementSpeed = HARD_BONE_SPEED; + } + else if (GamePanel.getDifficulty() == 1) + { + movementSpeed = HARDCORE_SPEED; + boneMovementSpeed = HARDCORE_BONE_SPEED; + } // if shoot timer is up and dog is not currently shooting, set its state to SHOOT if (shootTimer.isTimeUp() && dogState != dogState.SHOOT) { @@ -107,20 +123,18 @@ public void update(Player player) { // define where bone will spawn on map (x location) relative to dog enemy's location // and define its movement speed int boneX; - float movementSpeed; if (facingDirection == Direction.RIGHT) { boneX = Math.round(getX()) + getScaledWidth(); - movementSpeed = 1.5f; } else { boneX = Math.round(getX()); - movementSpeed = -1.5f; + boneMovementSpeed = -boneMovementSpeed; } // define where bone will spawn on the map (y location) relative to dog enemy's location int boneY = Math.round(getY()) + 4; // create bone enemy - Bone bone = new Bone(new Point(boneX, boneY), movementSpeed, 2000); + Bone bone = new Bone(new Point(boneX, boneY), boneMovementSpeed, 2000); // add bone enemy to the map for it to offically spawn in the level map.addProjectile(bone); diff --git a/src/Engine/CollisionType.java b/src/Engine/CollisionType.java new file mode 100644 index 0000000..c65fdcb --- /dev/null +++ b/src/Engine/CollisionType.java @@ -0,0 +1,9 @@ +package Engine; + +/** + * Indicates the type of collision that a player should have when it collides with + * a given entity. + */ +public enum CollisionType { + DEFAULT,INSTANT_DEATH,DAMAGE,PREVENT_JUMP +} diff --git a/src/Engine/DifficultyHolder.java b/src/Engine/DifficultyHolder.java new file mode 100644 index 0000000..d2263b8 --- /dev/null +++ b/src/Engine/DifficultyHolder.java @@ -0,0 +1,21 @@ +package Engine; + +@Deprecated +public class DifficultyHolder + +{ + private int difficulty = 3; + public DifficultyHolder(int difficulty) + { + this.difficulty = difficulty; + } + + public int getDifficulty() + { + return difficulty; + } + public void setDifficulty(int difficulty) + { + this.difficulty = difficulty; + } +} diff --git a/src/Engine/GamePanel.java b/src/Engine/GamePanel.java index 313e7a1..21fe1eb 100644 --- a/src/Engine/GamePanel.java +++ b/src/Engine/GamePanel.java @@ -1,55 +1,40 @@ package Engine; +import Game.GameState; +import Game.GameThread; +import Game.ScreenCoordinator; import GameObject.Rectangle; import Level.Player; -import Players.Avatar; -import Players.Cat; -import Screens.OptionsScreen; -import SpriteFont.SpriteFont; import Utils.Colors; -import Utils.Stopwatch; -import javax.imageio.ImageIO; import javax.sound.sampled.*; -//import sun.audio.AudioData; import javax.swing.*; - -import Game.GameState; -import Game.ScreenCoordinator; - import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.MouseEvent; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -/* +/** * This is where the game loop starts * The JPanel uses a timer to continually call cycles of update and draw */ -public class GamePanel extends JPanel { - // loads Screens on to the JPanel - // each screen has its own update and draw methods defined to handle a "section" - // of the game. - protected KeyLocker keyLocker = new KeyLocker(); - private ScreenManager screenManager; - // used to create the game loop and cycle between update and draw calls - private Timer timer; - // used to draw graphics to the panel - private GraphicsHandler graphicsHandler; +public class GamePanel extends JPanel implements Updatable { + private final ScreenManager screenManager; + private final GraphicsHandler graphicsHandler; private boolean doPaint = false; - protected int pointerLocationX, pointerLocationY; - protected Stopwatch keyTimer = new Stopwatch(); protected static GameWindow gameWindow; private static ScreenCoordinator coordinator; public static Clip clip; - private Point previousMousePoint = new Point(0,0); - private JLabel health; + + // these difficulty values are not only just used for the the logic of the game but + // to determine how much health the user gets at each difficulty; + private final static int NORMAL = 3, HARD = 2, HARDCORE = 1; + private static int difficulty; + private final JLabel health; + private final GameThread gameThread; - /* + /** * The JPanel and various important class instances are setup here */ public GamePanel(ScreenCoordinator c1,GameWindow gameWindow) { @@ -69,26 +54,20 @@ public GamePanel(ScreenCoordinator c1,GameWindow gameWindow) { screenManager = new ScreenManager(); coordinator = c1; - - + difficulty = NORMAL; - - timer = new Timer(1000 / Config.FPS, new ActionListener() { - public void actionPerformed(ActionEvent e) { - update(); - changeHealth(); - if(coordinator.getGameState() == GameState.LEVEL) { - health.show(); - } - else { - health.hide(); - } - repaint(); - } - }); - timer.setRepeats(true); + gameThread = new GameThread(this::repaint, this::update); } - + + public static void setDifficulty(int newDifficulty) + { + difficulty = newDifficulty; + } + public static int getDifficulty() + { + return difficulty; + } + public static ScreenCoordinator getScreenCoordinator() { return coordinator; } @@ -99,8 +78,8 @@ public void setupGame() { setBackground(Colors.CORNFLOWER_BLUE); screenManager.initialize(new Rectangle(getX(), getY(), getWidth(), getHeight())); doPaint = true; - } + public static GameWindow getGameWindow() { return gameWindow; } @@ -148,7 +127,7 @@ public static void setVolumeHigh() { // this starts the timer (the game loop is started here public void startGame() { - timer.start(); + gameThread.start(); try { music("Resources/Music/music.wav",.05); @@ -166,27 +145,26 @@ public ScreenManager getScreenManager() { } public void update() { - screenManager.update(); + screenManager.update(); + changeHealth(); } public void draw() { screenManager.draw(graphicsHandler); - - } // Checks the players health and accordingly changes to the image with the corresponding number of hearts public void changeHealth() { if(coordinator.getGameState() == GameState.LEVEL) { - if(Player.playerHealth == 3) { + if(Player.PLAYER_HEALTH == 3) { health.setIcon(new ImageIcon(ImageLoader.load("3 Hearts.png"))); } - else if(Player.playerHealth == 2) { + else if(Player.PLAYER_HEALTH == 2) { health.setIcon(new ImageIcon(ImageLoader.load("2 Hearts.png"))); } - else if(Player.playerHealth == 1) { + else if(Player.PLAYER_HEALTH == 1) { health.setIcon(new ImageIcon(ImageLoader.load("1 Heart.png"))); } @@ -195,8 +173,17 @@ else if(Player.playerHealth == 1) { } } + // Each difficulty is represented as an integer while also representing the amount of health the user has + // normal is 3 hard is 2 and hardcore is 1 + // hide the health whenever in a menu if(coordinator.getGameState() == GameState.MENU) { - Player.playerHealth = 3; + Player.PLAYER_HEALTH = difficulty; + health.hide(); + } + // show the health when in a level + else if (coordinator.getGameState() == GameState.LEVEL) + { + health.show(); } } @@ -213,7 +200,6 @@ protected void paintComponent(Graphics g) { } public static void mouseClicked(MouseEvent e) { -// System.out.println("Click: " + e.getPoint()); coordinator.mouseClicked(e); } diff --git a/src/Engine/GameWindow.java b/src/Engine/GameWindow.java index 0f12d10..e5f01d0 100644 --- a/src/Engine/GameWindow.java +++ b/src/Engine/GameWindow.java @@ -1,13 +1,12 @@ package Engine; +import Game.ScreenCoordinator; + +import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.*; - -import Game.ScreenCoordinator; - /* * The JFrame that holds the GamePanel * Just does some setup and exposes the gamePanel's screenManager to allow an external class to setup their own content and attach it to this engine. @@ -26,7 +25,6 @@ public void mouseClicked(MouseEvent e) { GamePanel.mouseClicked(e); } }); - gamePanel = new GamePanel(c1,this); gamePanel.setFocusable(true); gamePanel.requestFocusInWindow(); diff --git a/src/Engine/KeyLocker.java b/src/Engine/KeyLocker.java index 5077672..95e1b88 100644 --- a/src/Engine/KeyLocker.java +++ b/src/Engine/KeyLocker.java @@ -31,6 +31,10 @@ public boolean isActionLocked(KeyboardAction keyboardAction) { return lockedAdapters.contains(keyboardAction); } + public boolean isActionUnlocked(KeyboardAction keyboardAction) { + return !lockedAdapters.contains(keyboardAction); + } + public void clear() { lockedAdapters.clear(); } diff --git a/src/Engine/Pausable.java b/src/Engine/Pausable.java new file mode 100644 index 0000000..a09e8c3 --- /dev/null +++ b/src/Engine/Pausable.java @@ -0,0 +1,6 @@ +package Engine; + +public interface Pausable { + void resume(); + void pause(); +} diff --git a/src/Engine/Screen.java b/src/Engine/Screen.java index e29bc74..9c721b2 100644 --- a/src/Engine/Screen.java +++ b/src/Engine/Screen.java @@ -1,15 +1,17 @@ package Engine; -import java.awt.Graphics; import Level.Map; + import java.awt.event.MouseEvent; // Base Screen class // This game engine runs off the idea of "screens", which are classes that contain their own update/draw methods for a particular piece of the game // For example, there may be a "MenuScreen" or a "PlayGameScreen" -public abstract class Screen { +public abstract class Screen implements Updatable, Drawable { public abstract void initialize(); + @Override public abstract void update(); + @Override public abstract void draw(GraphicsHandler graphicsHandler); public abstract void mouseClicked(MouseEvent e); private Map background; diff --git a/src/Engine/ScreenManager.java b/src/Engine/ScreenManager.java index ee6ea2a..142edd6 100644 --- a/src/Engine/ScreenManager.java +++ b/src/Engine/ScreenManager.java @@ -1,8 +1,6 @@ package Engine; -import Game.GameState; - import GameObject.Rectangle; /* diff --git a/src/Engine/Updatable.java b/src/Engine/Updatable.java new file mode 100644 index 0000000..8d186ea --- /dev/null +++ b/src/Engine/Updatable.java @@ -0,0 +1,5 @@ +package Engine; + +public interface Updatable { + void update(); +} diff --git a/src/EnhancedMapTiles/HorizontalMovingPlatform.java b/src/EnhancedMapTiles/HorizontalMovingPlatform.java index 7fc36b6..b290500 100644 --- a/src/EnhancedMapTiles/HorizontalMovingPlatform.java +++ b/src/EnhancedMapTiles/HorizontalMovingPlatform.java @@ -6,7 +6,6 @@ import Level.EnhancedMapTile; import Level.Player; import Level.TileType; -import Utils.AirGroundState; import Utils.Direction; import Utils.Point; @@ -78,7 +77,7 @@ public void update(Player player) { // if player is on standing on top of platform, move player by the amount the platform is moving // this will cause the player to "ride" with the moving platform // without this code, the platform would slide right out from under the player - if (overlaps(player) && player.getScaledBoundsY2() == getScaledBoundsY1() && player.getAirGroundState() == AirGroundState.GROUND) { + if (overlaps(player) && player.getScaledBoundsY2() == getScaledBoundsY1() && !player.isInAir()) { player.moveXHandleCollision(moveAmountX); } diff --git a/src/Game/Game.java b/src/Game/Game.java index ca2a96f..81c6b32 100644 --- a/src/Game/Game.java +++ b/src/Game/Game.java @@ -1,9 +1,8 @@ package Game; import Engine.GameWindow; - import Engine.ScreenManager; -import Menu.DebugScreen; +import Maps.GameMaps; /* * The game starts here @@ -13,13 +12,14 @@ public class Game { public static void main(String[] args) { + //Loads all maps before anything else happens + new GameMaps(); new Game(); } public Game() { ScreenCoordinator c1 = new ScreenCoordinator(); GameWindow gameWindow = new GameWindow(c1); - gameWindow.startGame(); ScreenManager screenManager = gameWindow.getScreenManager(); screenManager.setCurrentScreen(c1); diff --git a/src/Game/GameState.java b/src/Game/GameState.java index 89b3b6b..216681b 100644 --- a/src/Game/GameState.java +++ b/src/Game/GameState.java @@ -4,5 +4,5 @@ * This is used by the ScreenCoordinator class to determine which "state" the game is currently in */ public enum GameState { - MENU, LEVEL, LEVELSELECT, CREDITS, INSTRUCTIONS , OPENING, OPTIONS; + MENU, LEVEL, LEVELSELECT, CREDITS, INSTRUCTIONS , OPENING, OPTIONS, DIFFICULTYSELECT; } diff --git a/src/Game/GameThread.java b/src/Game/GameThread.java new file mode 100644 index 0000000..f4d9452 --- /dev/null +++ b/src/Game/GameThread.java @@ -0,0 +1,79 @@ +package Game; + +/** + * Runs the base loops of the game, including updates and renders. Multiple updates may happen between frames if it takes too long, meaning the + * game will catch up once it gets too far ahead. + * @author Thomas Kwashnak + */ +public class GameThread implements Runnable { + + /** + * Static number of ms between updates + */ + private static final long UPDATE_FIXED_MS = 12; + + /** + * Main thread to run the game on + */ + private final Thread thread; + /** + * Methods to run for rendering and updating + */ + private final Runnable render, update; + /** + * Provides a means of ending the loop manually + */ + private boolean running = true; + + + /** + * Creates a new game thread + * @param render Runnable called when a render is needed + * @param update Runnable called when an update is needed + */ + public GameThread(Runnable render, Runnable update) { + thread = new Thread(this); + this.render = render; + this.update = update; + } + + /** + * Starts the thread + */ + public void start() { + thread.start(); + } + + /** + * Stops the thread + */ + public void stop() { + running = false; + } + + @Override + public void run() { + //Sets the next update to the current time + long nextUpdateTime = System.currentTimeMillis(); + long currentTime; + + //Loop until running is false + while(running) { + //Update current time + currentTime = System.currentTimeMillis(); + + //Repeat until the update time is after (in the future) current time + if(currentTime > nextUpdateTime) { + do { + update.run(); + nextUpdateTime += UPDATE_FIXED_MS; + } while(currentTime > nextUpdateTime); + //Run a render + render.run(); + } + } + //Reset running once it's set to false + running = true; + } +} + diff --git a/src/Game/ScreenCoordinator.java b/src/Game/ScreenCoordinator.java index 299ef71..43c71cf 100644 --- a/src/Game/ScreenCoordinator.java +++ b/src/Game/ScreenCoordinator.java @@ -58,6 +58,7 @@ public void update() { case LEVELSELECT -> currentScreen = new LevelSelectScreen(); case OPENING -> currentScreen = new OpeningScreen(this); case OPTIONS -> currentScreen = new OptionsScreen(); + case DIFFICULTYSELECT -> currentScreen = new DifficultySelectScreen(); } currentScreen.initialize(); } diff --git a/src/Game/TimeTracker.java b/src/Game/TimeTracker.java new file mode 100644 index 0000000..5fb2dc2 --- /dev/null +++ b/src/Game/TimeTracker.java @@ -0,0 +1,108 @@ +package Game; + +import Engine.Drawable; +import Engine.GraphicsHandler; +import Engine.ScreenManager; +import Maps.GameMaps; +import Utils.GameTimer; +import Utils.TimeParser; + +import java.awt.*; + +public class TimeTracker implements Drawable { + + private static final Font FONT_BIG, FONT_SMALL; + + static { + String fontName = "Times New Roman"; + FONT_BIG = new Font(fontName,Font.PLAIN, 40); + FONT_SMALL = new Font(fontName,Font.PLAIN,30); + } + + + private int currentLevel; + + private GameTimer[] levels; + private GameTimer total; + + private int xMap, yMap, xTotal, yTotal, charsTotal, charsMap; + + /** + * Whether the level-specific time should be listed + */ + private boolean showLevel; + + public TimeTracker() { + total = new GameTimer(); + levels = new GameTimer[GameMaps.MAPS.length]; + + for(int i = 0; i < levels.length; i++) { + levels[i] = new GameTimer(); + } + + currentLevel = -1; + showLevel = false; + } + + public void setCurrentLevel(int currentLevel) { + if(this.currentLevel != currentLevel) { + total.start(); + if(this.currentLevel > -1) { + levels[this.currentLevel].stop(); + showLevel = true; + } + this.currentLevel = currentLevel; + levels[currentLevel].start(); + } + } + + /** + * Usable when dealing with pausing + */ + public void start() { + total.start(); + levels[currentLevel].start(); + } + + public void stop() { + total.stop(); + levels[currentLevel].stop(); + } + + public TimeParser getElapsedTime() { + TimeParser timeParser = new TimeParser(); + for(GameTimer gameTimer : levels) { + timeParser.addTime(gameTimer.getElapsed()); + } + return timeParser; + } + + @Override + public void draw(GraphicsHandler graphicsHandler) { + String totalString = total.toString(); + if(totalString.length() != charsTotal) { + FontMetrics metrics = graphicsHandler.getGraphics2D().getFontMetrics(FONT_BIG); + yTotal = metrics.getHeight(); + xTotal = ScreenManager.getScreenWidth() - metrics.stringWidth(totalString.replace(' ','0')); + charsTotal = totalString.length(); + } + graphicsHandler.drawString(totalString, xTotal, yTotal, FONT_BIG, Color.white); + + + if(showLevel) { + String mapString = levels[currentLevel].toString(); + if(mapString.length() != charsMap) { + FontMetrics metrics = graphicsHandler.getGraphics2D().getFontMetrics(FONT_SMALL); + yMap = metrics.getHeight() + yTotal; + xMap = ScreenManager.getScreenWidth() - metrics.stringWidth(mapString.replace(' ','0')); + charsMap = totalString.length(); + } + graphicsHandler.drawString(mapString,xMap,yMap,FONT_SMALL,Color.white); + } + + } + + public GameTimer[] getLevels() { + return levels; + } +} diff --git a/src/GameObject/GameObject.java b/src/GameObject/GameObject.java index 1cf6e76..d1c8bf8 100644 --- a/src/GameObject/GameObject.java +++ b/src/GameObject/GameObject.java @@ -34,6 +34,8 @@ public class GameObject extends AnimatedSprite implements Drawable { // previous location the game object was in from the last frame protected float previousX, previousY; + + // the map instance this game object "belongs" to. protected Map map; diff --git a/src/Level/Camera.java b/src/Level/Camera.java index a2902cf..ffeab9e 100644 --- a/src/Level/Camera.java +++ b/src/Level/Camera.java @@ -1,6 +1,5 @@ package Level; -import Engine.Config; import Engine.GraphicsHandler; import Engine.ScreenManager; import GameObject.GameObject; diff --git a/src/Level/Enemy.java b/src/Level/Enemy.java index 4ddf6e9..c89baa4 100644 --- a/src/Level/Enemy.java +++ b/src/Level/Enemy.java @@ -1,5 +1,6 @@ package Level; +import Engine.CollisionType; import GameObject.Frame; import GameObject.ImageEffect; import GameObject.Rectangle; @@ -42,6 +43,7 @@ public Enemy(BufferedImage image, float x, float y, float scale, ImageEffect ima @Override public void initialize() { super.initialize(); + collisionType = CollisionType.INSTANT_DEATH; } public void update(Player player) { diff --git a/src/Level/LevelState.java b/src/Level/LevelState.java index ab711b7..775a9ce 100644 --- a/src/Level/LevelState.java +++ b/src/Level/LevelState.java @@ -2,5 +2,5 @@ // This enum represents the potential states a level can be public enum LevelState { - RUNNING, LEVEL_COMPLETED, PLAYER_DEAD, LEVEL_WIN_MESSAGE, LEVEL_LOSE_MESSAGE + PLAYING,DEAD,WIN } diff --git a/src/Level/Map.java b/src/Level/Map.java index 69cbfae..3365f8b 100644 --- a/src/Level/Map.java +++ b/src/Level/Map.java @@ -24,6 +24,9 @@ */ public abstract class Map implements Drawable { + + protected String name; + // the tile map (map tiles that make up the entire map image) protected MapTile[] mapTiles; @@ -31,6 +34,9 @@ public abstract class Map implements Drawable { protected int width; protected int height; + //right most bound + private int rightBound; + // the tileset this map uses for its map tiles protected Tileset tileset; @@ -61,7 +67,8 @@ public abstract class Map implements Drawable { // if set to false, camera will not move as player moves protected boolean adjustCamera = true; - public Map(String mapFileName, Tileset tileset, Point playerStartTile) { + public Map(String name, String mapFileName, Tileset tileset, Point playerStartTile) { + this.name = name; this.mapFileName = mapFileName; this.tileset = tileset; setupMap(); @@ -72,6 +79,7 @@ public Map(String mapFileName, Tileset tileset, Point playerStartTile) { this.xMidPoint = ScreenManager.getScreenWidth() / 2; this.yMidPoint = (ScreenManager.getScreenHeight() / 2); this.playerStartTile = playerStartTile; + rightBound = getWidthPixels() - getTileset().getScaledSpriteWidth(); } // sets up map by reading in the map file to create the tile map @@ -402,4 +410,16 @@ public void reset() { public void draw(GraphicsHandler graphicsHandler) { camera.draw(graphicsHandler); } + + public int getRightBound() { + return rightBound; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/src/Level/MapEntity.java b/src/Level/MapEntity.java index 0f849ea..8642e95 100644 --- a/src/Level/MapEntity.java +++ b/src/Level/MapEntity.java @@ -1,5 +1,6 @@ package Level; +import Engine.CollisionType; import GameObject.*; import java.awt.image.BufferedImage; @@ -9,6 +10,7 @@ // it is basically a game object with a few extra features for handling things like respawning public class MapEntity extends GameObject { protected MapEntityStatus mapEntityStatus = MapEntityStatus.ACTIVE; + protected CollisionType collisionType = CollisionType.DEFAULT; // if true, if entity goes out of the camera's update range, and then ends up back in range, the entity will "respawn" back to its starting parameters protected boolean isRespawnable = true; @@ -77,4 +79,12 @@ public boolean isUpdateOffScreen() { public void setIsUpdateOffScreen(boolean isUpdateOffScreen) { this.isUpdateOffScreen = isUpdateOffScreen; } + + public void setCollisionType(CollisionType collisionType) { + this.collisionType = collisionType; + } + + public CollisionType getCollisionType() { + return collisionType; + } } diff --git a/src/Level/Player.java b/src/Level/Player.java index a54b4da..d8e79cf 100644 --- a/src/Level/Player.java +++ b/src/Level/Player.java @@ -1,535 +1,203 @@ package Level; -import Engine.KeyLocker; import Engine.KeyboardAction; import GameObject.GameObject; import GameObject.SpriteSheet; -import Projectiles.Bone; -import Projectiles.Fireball; -import Utils.AirGroundState; +import Level.PlayerState.Facing; import Utils.Direction; import Utils.Point; import Utils.Stopwatch; import java.util.ArrayList; +import java.util.List; +/** + * @author Thomas Kwashnak + */ public abstract class Player extends GameObject { - // values that affect player movement - // these should be set in a subclass - protected float walkSpeed = 0; - protected float maxWalkSpeed = 0; - protected float minWalkSpeed = 0; - protected float walkAcceleration = 0; - protected float gravity = 0; - protected float jumpHeight = 0; - protected float jumpDegrade = 0; - protected float terminalVelocityX = 0; - - // Number of times a player can be hit by a projectile before dying - public static int playerHealth = 3; - - protected float momentumXIncrease = 0; - - // values used to handle player movement - protected float jumpForce = 0; - protected float momentumX = 0; - protected float terminalVelocityY = 0; - protected float momentumYIncrease = 0; - - - // values used to handle player movement - protected float momentumY = 0; - protected float moveAmountX, moveAmountY; - - protected Stopwatch attackCooldown = new Stopwatch(); - - // Variables used to apply effect to player from the Boss' bone - protected Stopwatch boneEffect = new Stopwatch(); - protected boolean canJump = true; - // values used to keep track of player's current state - protected PlayerState playerState; - protected Direction facingDirection; - protected AirGroundState airGroundState; - protected AirGroundState previousAirGroundState; - protected LevelState levelState; - - // classes that listen to player events can be added to this list - protected ArrayList listeners = new ArrayList<>(); - - // define keys - protected KeyLocker keyLocker = new KeyLocker(); - - // if true, player cannot be hurt by enemies (good for testing) - protected boolean isInvincible = false; - // added 11/19 - protected PlayerAttack currentProjectile; + private static final int ATTACK_DELAY = 1500, JUMP_DELAY = 5000; + private static final float MAX_FALL_VELOCITY = 6f, MAX_DEATH_FALL_VELOCITY = 10f, DEATH_Y_VELOCITY = -2.5f; + public static int PLAYER_HEALTH = 3; + protected float gravity, jumpHeight, walkSpeed, sprintSpeed, sprintAcceleration; + private final List playerListeners = new ArrayList<>(); + private final Stopwatch attackDelay = new Stopwatch(), jumpDelay = new Stopwatch(); + private PlayerState playerState; + private Facing facing; + private LevelState levelState; + private boolean inAir; + private float absVelocityX, velocityY; public Player(SpriteSheet spriteSheet, float x, float y, String startingAnimationName) { super(spriteSheet, x, y, startingAnimationName); - facingDirection = Direction.RIGHT; - airGroundState = AirGroundState.AIR; - previousAirGroundState = airGroundState; - playerState = PlayerState.STANDING; - levelState = LevelState.RUNNING; - this.x = x; - this.y = y; - - // 11/19 - currentProjectile = null; + + playerState = PlayerState.STAND; + levelState = LevelState.PLAYING; + facing = Facing.RIGHT; } public void update() { - moveAmountX = 0; - moveAmountY = 0; - - // if player is currently playing through level (has not won or lost) - if (levelState == LevelState.RUNNING) { - applyGravity(); - - handlePlayerState(); - - previousAirGroundState = airGroundState; - - // update player's animation - super.update(); - - // move player with respect to map collisions based on how much player needs to move this frame - super.moveYHandleCollision(moveAmountY); - super.moveXHandleCollision(moveAmountX); - - updateLockedKeys(); + super.update(); + switch (levelState) { + case PLAYING -> updatePlaying(); + case DEAD -> updateDead(); + case WIN -> updateWin(); } - // if player has beaten level - else if (levelState == LevelState.LEVEL_COMPLETED) { - updateLevelCompleted(); - } - - // if player has lost level - else if (levelState == LevelState.PLAYER_DEAD) { - updatePlayerDead(); - } - - if(boneEffect.isTimeUp()) { - canJump = true; - } - - if(PlayerAttack.dogHealth == 0) { - canJump = true; - jumpHeight = 16; - } - } - - // add gravity to player, which is a downward force - protected void applyGravity() { - moveAmountY += gravity + momentumY; - } - - // based on player's current state, call appropriate player state handling method - protected void handlePlayerState() { - switch (playerState) { - case STANDING: - playerStanding(); - break; - case WALKING: - playerWalking(); - break; - case CROUCHING: - playerCrouching(); - break; - case JUMPING: - playerJumping(); - break; - // 11/19 - case ATTACKING: - playerAttacking(); - break; - } + setCurrentAnimationName(playerState.get(facing)); } - // player STANDING state logic - protected void playerStanding() { - // sets animation to a STAND animation based on which way player is facing - currentAnimationName = facingDirection == Direction.RIGHT ? "STAND_RIGHT" : "STAND_LEFT"; + private void updatePlaying() { + applyGravity(MAX_FALL_VELOCITY); - // if walk left or walk right key is pressed, player enters WALKING state - if (KeyboardAction.GAME_MOVE_LEFT.isDown() || KeyboardAction.GAME_MOVE_RIGHT.isDown()) { - playerState = PlayerState.WALKING; + //Update Player Action and Direction + boolean moveLeftDown = KeyboardAction.GAME_MOVE_LEFT.isDown(); + boolean playerMove = moveLeftDown ^ KeyboardAction.GAME_MOVE_RIGHT.isDown(); //Only true if the player is moving in a direction + facing = playerMove ? moveLeftDown ? Facing.LEFT : Facing.RIGHT : facing; //Update facing if the player moved + //Only move if the player moved and is not going out of bounds + if (playerMove && ((facing == Facing.LEFT && x > 0) ^ (facing == Facing.RIGHT && x < map.getRightBound()))) { + playerState = PlayerState.WALK; + if (KeyboardAction.GAME_SPRINT.isDown() && absVelocityX >= walkSpeed) { + if (absVelocityX < sprintSpeed) { + absVelocityX *= sprintAcceleration; + } + } else { //If player is not sprinting, or if the player started off sprinting from rest (where velocityX is 0) + absVelocityX = walkSpeed; + } + } else { //Player is not moving + absVelocityX = 0; + playerState = PlayerState.STAND; } - // if jump key is pressed, player enters JUMPING state - else if (KeyboardAction.GAME_JUMP.isDown() && !keyLocker.isActionLocked(KeyboardAction.GAME_JUMP) && canJump == true) { - playerState = PlayerState.JUMPING; + //Update Jump + if (KeyboardAction.GAME_JUMP.isDown()) { + if (jumpDelay.isTimeUp() && !inAir) { + velocityY = -jumpHeight; + } + } else if (velocityY < 0) { //if the player releases while velocity is still up, cut short, remember that velocityY is inverse + velocityY = 0; } - // if crouch key is pressed, player enters CROUCHING state - else if (KeyboardAction.GAME_CROUCH.isDown()) { - playerState = PlayerState.CROUCHING; + //Update Attack + if (KeyboardAction.GAME_ATTACK.isDown() && attackDelay.isTimeUp()) { + int attackX = facing == Facing.RIGHT ? Math.round(getX()) + getScaledWidth() - 20 : Math.round(getX()); + map.addEnemy(new PlayerAttack(new Point(attackX, Math.round(getY()) + 10), facing.mod * 1.5f, 1000)); + attackDelay.setWaitTime(ATTACK_DELAY); } - - // enter the attacking state if the attack key is pressed and the attack cooldown is up - else if(KeyboardAction.GAME_ATTACK.isDown() && attackCooldown.isTimeUp()) { - //keyLocker.lockKey(attackKey); - playerState = PlayerState.ATTACKING; - //System.out.println(previousPlayerState.toString()); - } - } - - // player WALKING state logic - protected void playerWalking() { - // sets animation to a WALK animation based on which way player is facing - currentAnimationName = facingDirection == Direction.RIGHT ? "WALK_RIGHT" : "WALK_LEFT"; - // if the running key, shift, is held down acceleration the walk speed until the character reaches the max speed - if (KeyboardAction.GAME_SPRINT.isDown()) - { - if (walkSpeed < maxWalkSpeed) - { - walkSpeed = walkSpeed * walkAcceleration; - } - - } - // once the user lets go of the running key reset the walk speed - else { - walkSpeed = minWalkSpeed; - } - - if (KeyboardAction.GAME_MOVE_LEFT.isDown()) { - //System.out.println("s"); - moveAmountX -= walkSpeed; - facingDirection = Direction.LEFT; - } - - // if walk right key is pressed, move player to the right - else if (KeyboardAction.GAME_MOVE_RIGHT.isDown()) { - //System.out.println("d"); - moveAmountX += walkSpeed; - facingDirection = Direction.RIGHT; - } else { - playerState = PlayerState.STANDING; + //If the player is in the air, set its animation based on velocityY + if (inAir) { + playerState = velocityY < 0 ? PlayerState.JUMP : PlayerState.FALL; + } else if(KeyboardAction.GAME_CROUCH.isDown()) { + absVelocityX = 0; + playerState = PlayerState.CROUCH; } - // if jump key is pressed, player enters JUMPING state - if (KeyboardAction.GAME_JUMP.isDown() && !keyLocker.isActionLocked(KeyboardAction.GAME_JUMP) && canJump == true) { - //System.out.println("w"); - playerState = PlayerState.JUMPING; + //Updates player to death if their health hits 0 + if (PLAYER_HEALTH <= 0) { + levelState = LevelState.DEAD; } - // if crouch key is pressed, - else if (KeyboardAction.GAME_CROUCH.isDown()) { - playerState = PlayerState.CROUCHING; - } - - // enter the attacking state if the attack key is pressed and the attack cooldown is up - else if(KeyboardAction.GAME_ATTACK.isDown() && attackCooldown.isTimeUp()) { - playerState = PlayerState.ATTACKING; - //System.out.println(previousPlayerState.toString()); - } + inAir = true; //air is decided in moveYHandleCollision() + super.moveYHandleCollision(velocityY); + super.moveXHandleCollision(absVelocityX * facing.mod); } - // player CROUCHING state logic - protected void playerCrouching() { - // sets animation to a CROUCH animation based on which way player is facing - currentAnimationName = facingDirection == Direction.RIGHT ? "CROUCH_RIGHT" : "CROUCH_LEFT"; - - // if crouch key is released, player enters STANDING state - if (!KeyboardAction.GAME_CROUCH.isDown()) { - playerState = PlayerState.STANDING; - } - - // if jump key is pressed, player enters JUMPING state - if (KeyboardAction.GAME_JUMP.isDown() && !keyLocker.isActionLocked(KeyboardAction.GAME_JUMP)) { - playerState = PlayerState.JUMPING; + private void updateDead() { + playerState = PlayerState.DEATH; + if (currentFrameIndex > 0) { + if (map.getCamera().containsDraw(this)) { + applyGravity(MAX_DEATH_FALL_VELOCITY); + } else for (PlayerListener listener : playerListeners) { + listener.onDeath(); + } + } else { + velocityY = DEATH_Y_VELOCITY; } + setY(y + velocityY); } - // player JUMPING state logic - protected void playerJumping() { - // if last frame player was on ground and this frame player is still on ground, the jump needs to be setup - if (previousAirGroundState == AirGroundState.GROUND && airGroundState == AirGroundState.GROUND && canJump == true) { - // sets animation to a JUMP animation based on which way player is facing - currentAnimationName = facingDirection == Direction.RIGHT ? "JUMP_RIGHT" : "JUMP_LEFT"; + private void updateWin() { + if (map.getCamera().containsDraw(this)) { - // player is set to be in air and then player is sent into the air - airGroundState = AirGroundState.AIR; - jumpForce = jumpHeight; - if (jumpForce > 0) { - moveAmountY -= jumpForce; - jumpForce -= jumpDegrade; - if (jumpForce < 0) { - jumpForce = 0; - } - } - } - // if the player is no longer holding any of the jump keys set the jump force to 0 to stop the jump this allows the player to control how high they want to jump - if (!KeyboardAction.GAME_JUMP.isDown()) - { - jumpForce = 0; - } - - - // if player is in air (currently in a jump) and has more jumpForce, continue sending player upwards - if (airGroundState == AirGroundState.AIR) { - if (jumpForce > 0) { - moveAmountY -= jumpForce; - jumpForce -= jumpDegrade; - if (jumpForce < 0) { - jumpForce = 0; - } + for(PlayerListener listener : playerListeners) { + listener.onLevelFinished(); } - // if player is moving upwards, set player's animation to jump. if player moving downwards, set player's animation to fall - if (previousY > Math.round(y)) { - currentAnimationName = facingDirection == Direction.RIGHT ? "JUMP_RIGHT" : "JUMP_LEFT"; + facing = Facing.RIGHT; + if (inAir) { + playerState = PlayerState.FALL; + applyGravity(MAX_FALL_VELOCITY); + moveYHandleCollision(velocityY); } else { - currentAnimationName = facingDirection == Direction.RIGHT ? "FALL_RIGHT" : "FALL_LEFT"; - } - - // allows you to move left and right while in the air - if (KeyboardAction.GAME_MOVE_LEFT.isDown()) { - moveAmountX -= walkSpeed; - facingDirection = Direction.LEFT; - } - - else if (KeyboardAction.GAME_MOVE_RIGHT.isDown()) { - moveAmountX += walkSpeed; - facingDirection = Direction.RIGHT; + playerState = PlayerState.WALK; + moveXHandleCollision(walkSpeed); } - - // if player is falling, increases momentum as player falls so it falls faster over time - if (moveAmountY > 0) { - increaseMomentum(); - } - } - - // if player last frame was in air and this frame is now on ground, player enters STANDING state - else if (previousAirGroundState == AirGroundState.AIR && airGroundState == AirGroundState.GROUND) { - playerState = PlayerState.STANDING; - - } - } - - // 11/19 - public void playerAttacking() { - if (playerState == PlayerState.ATTACKING) { - - // this allows the player to still move left or right will in the attacking state - if (KeyboardAction.GAME_MOVE_LEFT.isDown()) { - moveAmountX -= walkSpeed; - facingDirection = Direction.LEFT; - } - else if (KeyboardAction.GAME_MOVE_RIGHT.isDown()) { - moveAmountX += walkSpeed; - facingDirection = Direction.RIGHT; - } - - - // define where projectile will spawn on map (x location) relative to cat's - // location - // and define its movement speed - int attackX; - float movementSpeed; - if (facingDirection == Direction.RIGHT) { - attackX = Math.round(getX()) + getScaledWidth() - 20; - movementSpeed = 1.5f; - } else { - attackX = Math.round(getX()); - movementSpeed = -1.5f; - } - - // define where projectile will spawn on the map (y location) relative to - // dinosaur enemy's location - int attackY = Math.round(getY()) + 10; - - // create projectile - PlayerAttack projectile = new PlayerAttack(new Point(attackX, attackY), movementSpeed, 1000); - currentProjectile = projectile; - - // add projectile enemy to the map for it to officially spawn in the level - map.addEnemy(projectile); - - attackCooldown.setWaitTime(1500); - - // after an attack finished set the player to a standing state - playerState = PlayerState.STANDING; - - } - } - - // while player is in air, this is called, and will increase momentumY by a set amount until player reaches terminal velocity - protected void increaseMomentum() { - momentumY += momentumYIncrease; - if (momentumY > terminalVelocityY) { - momentumY = terminalVelocityY; + } else for (PlayerListener listener : playerListeners) { + listener.onLevelCompleted(); } } - protected void increaseMomentumX() { - momentumX += momentumXIncrease; - if (momentumX > terminalVelocityX) { - momentumX = terminalVelocityX; + private void applyGravity(float maxFallVelocity) { + if (velocityY < maxFallVelocity) { + velocityY += gravity; } } - protected void updateLockedKeys() { - keyLocker.setAction(KeyboardAction.GAME_JUMP,KeyboardAction.GAME_ATTACK); - } - @Override public void onEndCollisionCheckX(boolean hasCollided, Direction direction) { - // if the player collides with the coordinates specified below, it either stops (beginning of level) or goes back to the start (end of level) - - if (direction == Direction.LEFT || direction == Direction.RIGHT) { - int rightBound = map.getWidthPixels() - map.getTileset().getScaledSpriteWidth(); - if (x < 0) { - hasCollided = true; - momentumX = 0; - setX(0); - } else if (levelState != LevelState.LEVEL_COMPLETED && x > rightBound) { - hasCollided = true; - momentumX = 0; - setX(rightBound); - } - } - if (hasCollided && MapTileCollisionHandler.lastCollidedTileX != null) { - if (MapTileCollisionHandler.lastCollidedTileX.getTileType() == TileType.LETHAL) { - levelState = LevelState.PLAYER_DEAD; - } - } + if (hasCollided) { + handleCollision(MapTileCollisionHandler.lastCollidedTileX); + if (playerState == PlayerState.WALK) { + playerState = PlayerState.STAND; + } + } } @Override public void onEndCollisionCheckY(boolean hasCollided, Direction direction) { - // if player collides with a map tile below it, it is now on the ground - // if player does not collide with a map tile below, it is in air - if (direction == Direction.DOWN) { - if (hasCollided) { - momentumY = 0; - airGroundState = AirGroundState.GROUND; - } else { - playerState = PlayerState.JUMPING; - airGroundState = AirGroundState.AIR; + if (hasCollided) { + if (direction == Direction.DOWN) { + inAir = false; } + velocityY = 0; + handleCollision(MapTileCollisionHandler.lastCollidedTileY); } + } - // if player collides with map tile upwards, it means it was jumping and then hit into a ceiling -- immediately stop upwards jump velocity - else if (direction == Direction.UP) { - if (hasCollided) { - jumpForce = 0; - } + private void handleCollision(MapTile tile) { + if (tile != null && tile.getTileType() == TileType.LETHAL) { + levelState = LevelState.DEAD; } - if (hasCollided && MapTileCollisionHandler.lastCollidedTileY != null) { - if (MapTileCollisionHandler.lastCollidedTileY.getTileType() == TileType.LETHAL) { - levelState = LevelState.PLAYER_DEAD; - } - } } - // other entities can call this method to hurt the player public void hurtPlayer(MapEntity mapEntity) { - if (!isInvincible) { - // if map entity is an enemy, kill player on touch - if (mapEntity instanceof Enemy) { - playerHealth = 0; - } - if (mapEntity instanceof Projectile) { - playerHealth -= 1; - } - if(mapEntity instanceof Bone) { - canJump = false; - boneEffect.setWaitTime(5000); - } - if (playerHealth <= 0) { - levelState = LevelState.PLAYER_DEAD; - } + switch (mapEntity.getCollisionType()) { + case DAMAGE -> PLAYER_HEALTH -= 1; + case INSTANT_DEATH -> PLAYER_HEALTH = 0; + case PREVENT_JUMP -> jumpDelay.setWaitTime(JUMP_DELAY); } } - // other entities can call this to tell the player they beat a level public void completeLevel() { - levelState = LevelState.LEVEL_COMPLETED; - - } - - // if player has beaten level, this will be the update cycle - public void updateLevelCompleted() { - // if player is not on ground, player should fall until it touches the ground - if (airGroundState != AirGroundState.GROUND && map.getCamera().containsDraw(this)) { - currentAnimationName = "FALL_RIGHT"; - applyGravity(); - increaseMomentum(); - super.update(); - moveYHandleCollision(moveAmountY); - } - // move player to the right until it walks off screen - else if (map.getCamera().containsDraw(this)) { - currentAnimationName = "WALK_RIGHT"; - super.update(); - moveXHandleCollision(walkSpeed); - } else { - // tell all player listeners that the player has finished the level - for (PlayerListener listener : listeners) { - listener.onLevelCompleted(); - } - } - } - - // if player has died, this will be the update cycle - public void updatePlayerDead() { - // change player animation to DEATH - if (!currentAnimationName.startsWith("DEATH")) { - if (facingDirection == Direction.RIGHT) { - currentAnimationName = "DEATH_RIGHT"; - } else { - currentAnimationName = "DEATH_LEFT"; - } - super.update(); - } - // if death animation not on last frame yet, continue to play out death animation - else if (currentFrameIndex != getCurrentAnimation().length - 1) { - super.update(); - } - // if death animation on last frame (it is set up not to loop back to start), player should continually fall until it goes off screen - else if (currentFrameIndex == getCurrentAnimation().length - 1) { - if (map.getCamera().containsDraw(this)) { - moveY(3); - } else { - // tell all player listeners that the player has died in the level - for (PlayerListener listener : listeners) { - listener.onDeath(); - } - } - } - } - - public PlayerState getPlayerState() { - return playerState; - } - - public void setPlayerState(PlayerState playerState) { - this.playerState = playerState; + levelState = LevelState.WIN; } - public AirGroundState getAirGroundState() { - return airGroundState; + public boolean isInAir() { + return inAir; } - public Direction getFacingDirection() { - return facingDirection; + public void addListener(PlayerListener listener) { + playerListeners.add(listener); } - public void setFacingDirection(Direction facingDirection) { - this.facingDirection = facingDirection; + public void setJumpHeight(int height) { + this.jumpHeight = height; } - public void setLevelState(LevelState levelState) { - this.levelState = levelState; + + public void setPlayerHealth(int health) + { + PLAYER_HEALTH = health; } - - public void addListener(PlayerListener listener) { - listeners.add(listener); - } } - - diff --git a/src/Level/PlayerAttack.java b/src/Level/PlayerAttack.java index 8f11657..481c423 100644 --- a/src/Level/PlayerAttack.java +++ b/src/Level/PlayerAttack.java @@ -6,13 +6,10 @@ import Engine.ImageLoader; import GameObject.Frame; import GameObject.SpriteSheet; -import Level.Enemy; -import Level.MapEntityStatus; -import Level.Player; import Utils.Direction; import Utils.Point; import Utils.Stopwatch; -import Level.Map; + import java.util.ArrayList; import java.util.HashMap; diff --git a/src/Level/PlayerListener.java b/src/Level/PlayerListener.java index fdeccd7..6f2ff55 100644 --- a/src/Level/PlayerListener.java +++ b/src/Level/PlayerListener.java @@ -2,6 +2,15 @@ // Other classes can use this interface to listen for events from the Player class public interface PlayerListener { + + /** + * Whenever the player leaves the level in its completed state + */ void onLevelCompleted(); void onDeath(); + + /** + * Whenever the player finishes the level by hitting the finish button + */ + void onLevelFinished(); } diff --git a/src/Level/PlayerState.java b/src/Level/PlayerState.java index 6a6d945..479fdfd 100644 --- a/src/Level/PlayerState.java +++ b/src/Level/PlayerState.java @@ -2,5 +2,34 @@ // This enum represents different states the Player can be in public enum PlayerState { - STANDING, WALKING, JUMPING, CROUCHING, ATTACKING + STAND, WALK, JUMP, CROUCH, FALL, + DEATH; + + private String left,right; + + PlayerState() { + left = this + "_LEFT"; + right = this + "_RIGHT"; + } + + public String getLeft() { + return left; + } + + public String getRight() { + return right; + } + + public String get(Facing facing) { + return facing == Facing.LEFT ? left : right; + } + + public enum Facing { + LEFT(-1),RIGHT(1); + + public final int mod; + Facing(int i) { + this.mod = i; + } + } } diff --git a/src/Level/Projectile.java b/src/Level/Projectile.java index d35ea92..c836077 100644 --- a/src/Level/Projectile.java +++ b/src/Level/Projectile.java @@ -1,9 +1,11 @@ package Level; +import Engine.CollisionType; import GameObject.Frame; import GameObject.ImageEffect; import GameObject.Rectangle; import GameObject.SpriteSheet; + import java.awt.image.BufferedImage; import java.util.HashMap; @@ -41,6 +43,7 @@ public Projectile(BufferedImage image, float x, float y, float scale, ImageEffec @Override public void initialize() { super.initialize(); + collisionType = CollisionType.DAMAGE; } public void update(Player player) { diff --git a/src/Maps/BossBattle.java b/src/Maps/BossBattle.java index 1997d4c..a6a9b86 100644 --- a/src/Maps/BossBattle.java +++ b/src/Maps/BossBattle.java @@ -1,25 +1,26 @@ package Maps; -import Enemies.BugEnemy; +import Enemies.CyborgEnemy; import Enemies.DinosaurEnemy; import Enemies.Dog; -import Enemies.CyborgEnemy; import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; import Level.*; -import NPCs.Walrus; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; + import java.util.ArrayList; // Represents a test map to be used in a level public class BossBattle extends Map { + private boolean bossKilled = false; + public BossBattle() { - super("BossBattle.txt", new CommonTileset(), new Point(1, 17)); + super("Final Boss","BossBattle.txt", new CommonTileset(), new Point(1, 17)); } @Override @@ -55,4 +56,13 @@ public ArrayList loadEnhancedMapTiles() { return enhancedMapTiles; } + @Override + public void update(Player player) { + super.update(player); + if(!bossKilled && PlayerAttack.dogHealth <= 0) { + bossKilled = true; + player.setJumpHeight(16); + } + } + } diff --git a/src/Maps/GameMaps.java b/src/Maps/GameMaps.java new file mode 100644 index 0000000..ff1893c --- /dev/null +++ b/src/Maps/GameMaps.java @@ -0,0 +1,60 @@ +package Maps; + +import Level.Map; + +/** + * Contains a centralized list of all game maps. Includes the name of the map, the function to create the map. + */ +public class GameMaps { + public static final MapManager[] MAPS; + + static { + /* + List of all maps registered in the game, in order + */ + MapFactory[] MAP_FACTORIES = new MapFactory[]{ + TestTutorial::new, + TestMap::new, + TestMap2::new, + TestMap3::new, + TestMap4::new, + TestMap5::new, + TestMap6::new, + TestMap7::new, + BossBattle::new + }; + + MAPS = new MapManager[MAP_FACTORIES.length]; + for(int i = 0; i < MAPS.length; i++) { + MAPS[i] = new MapManager(MAP_FACTORIES[i]); + } + } + + /** + * Sub-class that pulls out + */ + public static class MapManager implements MapFactory { + + private MapFactory mapFactory; + private String name; + + public MapManager(MapFactory mapFactory) { + this.mapFactory = mapFactory; + + Map map = mapFactory.generateMap(); + name = map.getName(); + } + + public String getName() { + return name; + } + + public Map generateMap() { + return mapFactory.generateMap(); + } + } + + public interface MapFactory { + Map generateMap(); + } +} diff --git a/src/Maps/LevelSelectMap.java b/src/Maps/LevelSelectMap.java index a2196c2..408633f 100644 --- a/src/Maps/LevelSelectMap.java +++ b/src/Maps/LevelSelectMap.java @@ -8,7 +8,7 @@ public class LevelSelectMap extends Map { public LevelSelectMap() { - super("level_select_map.txt", new CommonTileset(), new Point(5, 9)); + super("","level_select_map.txt", new CommonTileset(), new Point(5, 9)); } } diff --git a/src/Maps/TestMap.java b/src/Maps/TestMap.java index 831cd12..5835c65 100644 --- a/src/Maps/TestMap.java +++ b/src/Maps/TestMap.java @@ -17,7 +17,7 @@ // Represents a test map to be used in a level public class TestMap extends Map { public TestMap() { - super("test_map.txt", new CommonTileset(), new Point(1, 11)); + super("Level 1","test_map.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestMap2.java b/src/Maps/TestMap2.java index 23e5cd3..eaa6f66 100644 --- a/src/Maps/TestMap2.java +++ b/src/Maps/TestMap2.java @@ -2,12 +2,11 @@ import Enemies.BugEnemy; import Enemies.DinosaurEnemy; -import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; -import EnhancedMapTiles.HorizontalMovingPlatform; -import GameObject.Rectangle; -import Level.*; -import NPCs.Walrus; +import Level.Enemy; +import Level.EnhancedMapTile; +import Level.Map; +import Level.NPC; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -18,7 +17,7 @@ public class TestMap2 extends Map { public TestMap2() { - super("test_map_2.txt", new CommonTileset(), new Point(1, 11)); + super("Level 2","test_map_2.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestMap3.java b/src/Maps/TestMap3.java index 3fe66b7..7e4130e 100644 --- a/src/Maps/TestMap3.java +++ b/src/Maps/TestMap3.java @@ -7,7 +7,6 @@ import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; import Level.*; -import NPCs.Walrus; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -18,7 +17,7 @@ public class TestMap3 extends Map { public TestMap3() { - super("test_map3.txt", new CommonTileset(), new Point(1, 11)); + super("Level 3","test_map3.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestMap4.java b/src/Maps/TestMap4.java index 1c913e1..4f0622d 100644 --- a/src/Maps/TestMap4.java +++ b/src/Maps/TestMap4.java @@ -2,13 +2,11 @@ import Enemies.BugEnemy; import Enemies.CyborgEnemy; -import Enemies.DinosaurEnemy; import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; import Level.*; -import NPCs.Walrus; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -19,7 +17,7 @@ public class TestMap4 extends Map { public TestMap4() { - super("test_map4.txt", new CommonTileset(), new Point(1, 11)); + super("Level 4","test_map4.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestMap5.java b/src/Maps/TestMap5.java index c00d753..fee6dae 100644 --- a/src/Maps/TestMap5.java +++ b/src/Maps/TestMap5.java @@ -7,7 +7,6 @@ import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; import Level.*; -import NPCs.Walrus; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -18,7 +17,7 @@ public class TestMap5 extends Map { public TestMap5() { - super("test_map5.txt", new CommonTileset(), new Point(1, 11)); + super("Level 5","test_map5.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestMap6.java b/src/Maps/TestMap6.java index 1e321d8..ff04e47 100644 --- a/src/Maps/TestMap6.java +++ b/src/Maps/TestMap6.java @@ -1,14 +1,16 @@ package Maps; import Enemies.BugEnemy; -import Enemies.DinosaurEnemy; import Enemies.CyborgEnemy; +import Enemies.DinosaurEnemy; import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; -import Level.*; -import NPCs.Walrus; +import Level.Enemy; +import Level.EnhancedMapTile; +import Level.Map; +import Level.TileType; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -19,7 +21,7 @@ public class TestMap6 extends Map { public TestMap6() { - super("test_map6.txt", new CommonTileset(), new Point(1, 15)); + super("Level 6","test_map6.txt", new CommonTileset(), new Point(1, 15)); } @Override diff --git a/src/Maps/TestMap7.java b/src/Maps/TestMap7.java index a753c22..f6cbda5 100644 --- a/src/Maps/TestMap7.java +++ b/src/Maps/TestMap7.java @@ -1,14 +1,16 @@ package Maps; import Enemies.BugEnemy; -import Enemies.DinosaurEnemy; import Enemies.CyborgEnemy; +import Enemies.DinosaurEnemy; import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; -import Level.*; -import NPCs.Walrus; +import Level.Enemy; +import Level.EnhancedMapTile; +import Level.Map; +import Level.TileType; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; @@ -19,7 +21,7 @@ public class TestMap7 extends Map { public TestMap7() { - super("test_map7.txt", new CommonTileset(), new Point(1, 11)); + super("Level 7","test_map7.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TestTutorial.java b/src/Maps/TestTutorial.java index 69bb925..f1d2c4c 100644 --- a/src/Maps/TestTutorial.java +++ b/src/Maps/TestTutorial.java @@ -2,16 +2,16 @@ import Enemies.BugEnemy; import Enemies.DinosaurEnemy; -import Engine.*; +import Engine.GraphicsHandler; +import Engine.ImageLoader; import EnhancedMapTiles.EndLevelBox; import EnhancedMapTiles.HorizontalMovingPlatform; import GameObject.Rectangle; import Level.*; -import NPCs.Walrus; +import SpriteFont.SpriteFont; import Tilesets.CommonTileset; import Utils.Direction; import Utils.Point; -import SpriteFont.*; import java.awt.*; import java.util.ArrayList; @@ -20,7 +20,7 @@ public class TestTutorial extends Map { public TestTutorial() { - super("test_tutorial.txt", new CommonTileset(), new Point(1, 11)); + super("Tutorial","test_tutorial.txt", new CommonTileset(), new Point(1, 11)); } @Override diff --git a/src/Maps/TitleScreenMap.java b/src/Maps/TitleScreenMap.java index 23ac457..408b1db 100644 --- a/src/Maps/TitleScreenMap.java +++ b/src/Maps/TitleScreenMap.java @@ -8,7 +8,7 @@ public class TitleScreenMap extends Map { public TitleScreenMap() { - super("title_screen_map.txt", new CommonTileset(), new Point(1, 9)); + super("","title_screen_map.txt", new CommonTileset(), new Point(1, 9)); } } diff --git a/src/Menu/Menu.java b/src/Menu/Menu.java index a5ef189..bbeb31f 100644 --- a/src/Menu/Menu.java +++ b/src/Menu/Menu.java @@ -7,8 +7,9 @@ import java.awt.*; import java.awt.event.MouseEvent; +import java.util.List; -public abstract class Menu extends Screen { +public abstract class Menu extends Screen implements SelectableMenu { private final Stopwatch keyTimer = new Stopwatch(); private MenuOption[] menuOptions; @@ -174,23 +175,24 @@ protected void setMenuItemsAsGrid(MenuOption[][] grid) { } } - SelectFunction function = (newSelection) -> { - selectedItem.setSelected(false); - newSelection.setSelected(true); - selectedItem = newSelection; - }; - menuOptions = new MenuOption[count]; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[i].length; j++) { if (grid[i][j] != null) { menuOptions[menuOptions.length - (count--)] = grid[i][j]; - grid[i][j].setSelectFunction(function); + grid[i][j].setSelectFunction(this); } } } } + @Override + public void select(MenuOption menuOption) { + selectedItem.setSelected(false); + menuOption.setSelected(true); + selectedItem = menuOption; + } + protected void setBackground(Map background) { this.background = background; } @@ -199,8 +201,10 @@ protected void setDrawables(Drawable[] drawables) { this.drawables = drawables; } - public interface SelectFunction { - - void select(MenuOption item); + protected void setDrawables(List drawables) { + this.drawables = new Drawable[drawables.size()]; + for(int i = 0; i < this.drawables.length; i++) { + this.drawables[i] = drawables.get(i); + } } } diff --git a/src/Menu/MenuOption.java b/src/Menu/MenuOption.java index 6766842..755d45b 100644 --- a/src/Menu/MenuOption.java +++ b/src/Menu/MenuOption.java @@ -1,11 +1,14 @@ package Menu; +import Engine.Drawable; +import Engine.GamePanel; import Engine.GraphicsHandler; +import Game.GameState; import java.awt.*; import java.awt.event.MouseEvent; -public class MenuOption { +public class MenuOption implements Drawable { private static final Font DEFAULT_MENU_FONT = new Font("Comic sans", Font.PLAIN, 30); private static final Color DEFAULT_COLOR = new Color(49, 207, 240); @@ -13,7 +16,7 @@ public class MenuOption { private static final Color OUTLINE_COLOR = new Color(0, 0, 0); private static final int OUTLINE_THICKNESS = 3; - private Menu.SelectFunction selectFunction; + private SelectableMenu selectableMenu; private final MenuOption[] neighbors; @@ -29,6 +32,8 @@ public class MenuOption { private final int y; private int width; private int height; + + private CloseOnExecute actionAfterExecute = CloseOnExecute.REMAINOPEN; public MenuOption(String text, int x, int y, MenuItemListener listener) { this(text, x, y); @@ -41,6 +46,12 @@ public MenuOption(String text, int x, int y) { this.x = x; this.y = y; } + + public MenuOption(String text, int x, int y, MenuItemListener listener, CloseOnExecute closeOnExecute) { + this(text, x, y); + setListener(listener); + actionAfterExecute = closeOnExecute; + } public void setNeighborItem(MenuOption item, Direction direction) { neighbors[direction.getIndex()] = item; @@ -128,8 +139,8 @@ public void drawWithParsedNewLines(GraphicsHandler graphicsHandler) { public void mouseMoved(Point p) { softSelected = contains(p); - if (selected != softSelected && selectFunction != null) { - selectFunction.select(this); + if (selected != softSelected && selectableMenu != null) { + selectableMenu.select(this); } } @@ -147,9 +158,19 @@ public void execute() { if (listener != null) { listener.event(); } + // allows a menu option the ability to close after selecting them + if (actionAfterExecute == CloseOnExecute.CLOSE) + { + GamePanel.getScreenCoordinator().setGameState(GameState.MENU); + } } - public void setSelectFunction(Menu.SelectFunction function) { - this.selectFunction = function; + public void setSelectFunction(SelectableMenu function) { + this.selectableMenu = function; + } + + public enum CloseOnExecute + { + REMAINOPEN, CLOSE; } } diff --git a/src/Menu/SelectableMenu.java b/src/Menu/SelectableMenu.java new file mode 100644 index 0000000..f94ac0e --- /dev/null +++ b/src/Menu/SelectableMenu.java @@ -0,0 +1,5 @@ +package Menu; + +public interface SelectableMenu { + void select(MenuOption menuOption); +} diff --git a/src/Players/Avatar.java b/src/Players/Avatar.java index c39ae30..91b730a 100644 --- a/src/Players/Avatar.java +++ b/src/Players/Avatar.java @@ -6,9 +6,9 @@ //TODO make this the new Cat Options with a factory to create the player public enum Avatar { - CAT_ORANGE(p -> new Cat("Cat.png",p)), - CAT_BLUE(p -> new Cat("CatBlue.png",p)), - CAT_GREEN(p -> new Cat("CatGreen.png",p)); + CAT_ORANGE(p -> new Cat("Cat.png", p)), + CAT_BLUE(p -> new Cat("CatBlue.png", p)), + CAT_GREEN(p -> new Cat("CatGreen.png", p)); private PlayerFactory factory; diff --git a/src/Players/Cat.java b/src/Players/Cat.java index 4225bcd..a226e67 100644 --- a/src/Players/Cat.java +++ b/src/Players/Cat.java @@ -16,21 +16,25 @@ public class Cat extends Player { public Cat(String name, Point point) { - this(name,point.x,point.y); + this(name, point.x, point.y); } - public Cat(String name,float x, float y) { + public Cat(String name, float x, float y) { super(new SpriteSheet(ImageLoader.load(name), 24, 24), x, y, "STAND_RIGHT"); - + gravity = .5f; - terminalVelocityY = 6f; jumpHeight = 14.5f; - jumpDegrade = .5f; walkSpeed = 2.1f; - minWalkSpeed = 2.1f; - maxWalkSpeed = 3.3f; - walkAcceleration = 1.05f; - momentumYIncrease = .5f; + sprintSpeed = 3.3f; + sprintAcceleration = 1.05f; + // terminalVelocityY = 6f; + // jumpHeight = 14.5f; + // jumpDegrade = .5f; + // walkSpeed = 2.1f; + // minWalkSpeed = 2.1f; + // maxWalkSpeed = 3.3f; + // walkAcceleration = 1.05f; + // momentumYIncrease = .5f; } public void update() { @@ -45,133 +49,69 @@ public void draw(GraphicsHandler graphicsHandler) { @Override public HashMap getAnimations(SpriteSheet spriteSheet) { return new HashMap() {{ - put("STAND_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(0, 0), 0) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build() + put("STAND_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(0, 0), 0).withScale(3).withBounds(8, 9, 8, 9).build() }); - put("STAND_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(0, 0), 0) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build() + put("STAND_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(0, 0), 0).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build() }); - put("WALK_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(1, 0), 200) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 1), 200) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 2), 200) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 3), 200) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build() + put("WALK_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(1, 0), 200).withScale(3).withBounds(8, 9, 8, 9).build(), new FrameBuilder( + spriteSheet.getSprite(1, 1), 200).withScale(3).withBounds(8, 9, 8, 9).build(), new FrameBuilder(spriteSheet.getSprite(1, 2), 200) + .withScale(3).withBounds(8, 9, 8, 9).build(), new FrameBuilder(spriteSheet.getSprite(1, 3), 200).withScale(3).withBounds( + 8, 9, 8, 9).build() }); - put("WALK_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(1, 0), 200) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 1), 200) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 2), 200) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build(), - new FrameBuilder(spriteSheet.getSprite(1, 3), 200) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build() + put("WALK_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(1, 0), 200).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build(), + new FrameBuilder(spriteSheet.getSprite(1, 1), 200).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build(), + new FrameBuilder(spriteSheet.getSprite(1, 2), 200).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build(), + new FrameBuilder(spriteSheet.getSprite(1, 3), 200).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build() }); - put("JUMP_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(2, 0), 0) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build() + put("JUMP_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(2, 0), 0).withScale(3).withBounds(8, 9, 8, 9).build() }); - put("JUMP_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(2, 0), 0) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build() + put("JUMP_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(2, 0), 0).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build() }); - put("FALL_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(3, 0), 0) - .withScale(3) - .withBounds(8, 9, 8, 9) - .build() + put("FALL_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(3, 0), 0).withScale(3).withBounds(8, 9, 8, 9).build() }); - put("FALL_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(3, 0), 0) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 9, 8, 9) - .build() + put("FALL_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(3, 0), 0).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 9, 8, 9).build() }); - put("CROUCH_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(4, 0), 0) - .withScale(3) - .withBounds(8, 12, 8, 6) - .build() + put("CROUCH_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(4, 0), 0).withScale(3).withBounds(8, 12, 8, 6).build() }); - put("CROUCH_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(4, 0), 0) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .withBounds(8, 12, 8, 6) - .build() + put("CROUCH_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(4, 0), 0).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).withBounds( + 8, 12, 8, 6).build() }); - put("DEATH_RIGHT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(5, 0), 100) - .withScale(3) - .build(), - new FrameBuilder(spriteSheet.getSprite(5, 1), 100) - .withScale(3) - .build(), - new FrameBuilder(spriteSheet.getSprite(5, 2), -1) - .withScale(3) - .build() + put("DEATH_RIGHT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(5, 0), 100).withScale(3).build(), new FrameBuilder( + spriteSheet.getSprite(5, 1), 100).withScale(3).build(), new FrameBuilder(spriteSheet.getSprite(5, 2), -1).withScale(3).build() }); - put("DEATH_LEFT", new Frame[] { - new FrameBuilder(spriteSheet.getSprite(5, 0), 100) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .build(), - new FrameBuilder(spriteSheet.getSprite(5, 1), 100) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .build(), - new FrameBuilder(spriteSheet.getSprite(5, 2), -1) - .withScale(3) - .withImageEffect(ImageEffect.FLIP_HORIZONTAL) - .build() + put("DEATH_LEFT", new Frame[]{ + new FrameBuilder(spriteSheet.getSprite(5, 0), 100).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).build(), + new FrameBuilder(spriteSheet.getSprite(5, 1), 100).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).build(), + new FrameBuilder(spriteSheet.getSprite(5, 2), -1).withScale(3).withImageEffect(ImageEffect.FLIP_HORIZONTAL).build() }); }}; } diff --git a/src/Projectiles/Bone.java b/src/Projectiles/Bone.java index 9a73392..ab9fe55 100644 --- a/src/Projectiles/Bone.java +++ b/src/Projectiles/Bone.java @@ -1,6 +1,7 @@ package Projectiles; import Builders.FrameBuilder; +import Engine.CollisionType; import Engine.ImageLoader; import GameObject.Frame; import GameObject.ImageEffect; @@ -12,6 +13,7 @@ import Utils.Direction; import Utils.Point; import Utils.Stopwatch; + import java.util.HashMap; // This class is for the bone enemy that the dog class shoots out @@ -32,6 +34,7 @@ public Bone(Point location, float movementSpeed, int existenceTime) { isRespawnable = false; initialize(); + collisionType = CollisionType.PREVENT_JUMP; } @Override diff --git a/src/Projectiles/Fireball.java b/src/Projectiles/Fireball.java index 0d2f98a..c73ae76 100644 --- a/src/Projectiles/Fireball.java +++ b/src/Projectiles/Fireball.java @@ -4,7 +4,6 @@ import Engine.ImageLoader; import GameObject.Frame; import GameObject.SpriteSheet; -import Level.Enemy; import Level.MapEntityStatus; import Level.Player; import Level.Projectile; diff --git a/src/Projectiles/LazerBeam.java b/src/Projectiles/LazerBeam.java index 9780bbe..61c4eb1 100644 --- a/src/Projectiles/LazerBeam.java +++ b/src/Projectiles/LazerBeam.java @@ -11,6 +11,7 @@ import Utils.Direction; import Utils.Point; import Utils.Stopwatch; + import java.util.HashMap; // This class is for the lazer beam enemy that the DinosaurEnemy class shoots out diff --git a/src/Screens/DifficultySelectScreen.java b/src/Screens/DifficultySelectScreen.java new file mode 100644 index 0000000..fd4f09a --- /dev/null +++ b/src/Screens/DifficultySelectScreen.java @@ -0,0 +1,40 @@ +package Screens; + +import Engine.DifficultyHolder; +import Engine.GamePanel; +import Game.GameState; +import Maps.TitleScreenMap; +import Menu.Menu; +import Menu.Direction; +import Menu.MenuOption; +import Menu.MenuOption.CloseOnExecute; + +public class DifficultySelectScreen extends Menu { + + + //these values also correspond to the health given at each difficulty + private final static int NORMAL = 3; + private final static int HARD = 2; + private final static int HARDCORE = 1; + + public DifficultySelectScreen() { + MenuOption[][] menu = new MenuOption[][]{ + { + new MenuOption("Normal", 100, 150, () -> GamePanel.setDifficulty(NORMAL), CloseOnExecute.CLOSE), + new MenuOption("Hard", 320, 150, () -> GamePanel.setDifficulty(HARD), CloseOnExecute.CLOSE), + new MenuOption("Hardcore", 500, 150, () -> GamePanel.setDifficulty(HARDCORE), CloseOnExecute.CLOSE) + }, + { + new MenuOption( + "Back to Main Menu", 225, 375, () -> GamePanel.getScreenCoordinator().setGameState(GameState.MENU)) + } + }; + setBackground(new TitleScreenMap()); + setMenuItemsAsGrid(menu); + menu[0][0].setNeighborItem(menu[1][0], Direction.DOWN); + menu[0][1].setNeighborItem(menu[1][0], Direction.DOWN); + menu[0][2].setNeighborItem(menu[1][0], Direction.DOWN); + menu[1][0].setNeighborItem(menu[0][1], Direction.UP); + } + +} diff --git a/src/Screens/GameScoreScreen.java b/src/Screens/GameScoreScreen.java new file mode 100644 index 0000000..ac4f5db --- /dev/null +++ b/src/Screens/GameScoreScreen.java @@ -0,0 +1,73 @@ +package Screens; + +import Engine.Drawable; +import Engine.GraphicsHandler; +import Game.TimeTracker; +import Maps.GameMaps; +import Maps.TitleScreenMap; +import Menu.Menu; +import SpriteFont.SpriteFont; +import Utils.GameTimer; + +import java.awt.*; + +public class GameScoreScreen extends Menu { + + private static final Font FONT_LEVEL; + private static final Color COLOR_LEVEL; + + static { + String fontName = "Times New Roman"; + FONT_LEVEL = new Font(fontName, Font.PLAIN, 30); + COLOR_LEVEL = Color.WHITE; + } + + private TimeTracker timeTracker; + private SpriteFont levels; + + public GameScoreScreen(TimeTracker timeTracker) { + this.timeTracker = timeTracker; + setBackground(new TitleScreenMap()); + System.out.println(getLevels(timeTracker)); + } + + private String getLevels(TimeTracker timeTracker) { + StringBuilder stringBuilder = new StringBuilder(); + GameTimer[] gameTimers = timeTracker.getLevels(); + + for(int i = 0; i < GameMaps.MAPS.length; i++) { + if(gameTimers[i].getElapsed() > 0) { + if(!stringBuilder.isEmpty()) { + stringBuilder.append('\n'); + } + stringBuilder.append(GameMaps.MAPS[i].getName()).append(": ").append(gameTimers[i].toString()); + } + } + + return stringBuilder.toString(); + } + + private GameTimer getTotalTimes(TimeTracker timeTracker) { + GameTimer totalTime = new GameTimer(); + for(GameTimer gameTimer : timeTracker.getLevels()) { + totalTime.addTime(gameTimer.getElapsed()); + } + return totalTime; + } + + @Deprecated //Maybe? + public class LevelScores implements Drawable { + + private int x, y, height, width; + + + public LevelScores(GameTimer[] scores, int x, int y, int height, int width) { + + } + + @Override + public void draw(GraphicsHandler graphicsHandler) { + + } + } +} diff --git a/src/Screens/LevelLoseScreen.java b/src/Screens/LevelLoseScreen.java index 413098b..f9f8928 100644 --- a/src/Screens/LevelLoseScreen.java +++ b/src/Screens/LevelLoseScreen.java @@ -1,6 +1,7 @@ package Screens; import Engine.Drawable; +import Engine.GamePanel; import Engine.KeyboardAction; import Level.Player; import Level.PlayerAttack; @@ -25,8 +26,16 @@ public LevelLoseScreen(PlayLevelScreen playLevelScreen) { public void update() { super.update(); if (KeyboardAction.GAME_RESPAWN.isDown()) { - playLevelScreen.resetLevel(); - Player.playerHealth = 3; + // if the player is in hardcore difficulty restart them at the first level otherwise restart them on the current level + if (GamePanel.getDifficulty() == 1) + { + playLevelScreen.loadMap(0); + } + else + { + playLevelScreen.resetLevel(); + } + Player.PLAYER_HEALTH = GamePanel.getDifficulty(); PlayerAttack.dogHealth = 8; } } diff --git a/src/Screens/MenuScreen.java b/src/Screens/MenuScreen.java index 2171935..5bf6a4c 100644 --- a/src/Screens/MenuScreen.java +++ b/src/Screens/MenuScreen.java @@ -3,6 +3,7 @@ import Engine.GamePanel; import Game.GameState; import Maps.TitleScreenMap; +import Menu.Direction; import Menu.Menu; import Menu.MenuOption; @@ -10,20 +11,23 @@ public class MenuScreen extends Menu { public MenuScreen() { super(); - setMenuItemsAsGrid(new MenuOption[][]{ - { - new MenuOption("PLAY GAME", 80, 100, () -> GamePanel.getScreenCoordinator().setGameState(GameState.LEVEL)), - new MenuOption("LEVEL SELECT", 350, 100, () -> GamePanel.getScreenCoordinator().setGameState(GameState.LEVELSELECT)) - }, { - new MenuOption("CREDITS", 80, 200, () -> GamePanel.getScreenCoordinator().setGameState(GameState.CREDITS)), - new MenuOption("NARRATIVE", 350, 200, () -> GamePanel.getScreenCoordinator().setGameState(GameState.OPENING)) - }, { - new MenuOption("INSTRUCTIONS", 80, 300, () -> GamePanel.getScreenCoordinator().setGameState(GameState.INSTRUCTIONS)), - new MenuOption("OPTIONS", 350, 300, () -> GamePanel.getScreenCoordinator().setGameState(GameState.OPTIONS)) - }, { - new MenuOption("QUIT", 80, 400, () -> System.exit(0)) - } - }); + MenuOption[][] menu = new MenuOption[][]{ + { + new MenuOption("PLAY GAME", 60, 100, () -> GamePanel.getScreenCoordinator().setGameState(GameState.LEVEL)), + new MenuOption("LEVEL SELECT", 392, 100, () -> GamePanel.getScreenCoordinator().setGameState(GameState.LEVELSELECT)) + }, { + new MenuOption("CREDITS", 60, 200, () -> GamePanel.getScreenCoordinator().setGameState(GameState.CREDITS)), + new MenuOption("NARRATIVE", 392, 200, () -> GamePanel.getScreenCoordinator().setGameState(GameState.OPENING)) + }, { + new MenuOption("INSTRUCTIONS", 60, 300, () -> GamePanel.getScreenCoordinator().setGameState(GameState.INSTRUCTIONS)), + new MenuOption("OPTIONS", 392, 300, () -> GamePanel.getScreenCoordinator().setGameState(GameState.OPTIONS)) + }, + { + new MenuOption("SELECT DIFFICULTY", 60, 400, () -> GamePanel.getScreenCoordinator().setGameState(GameState.DIFFICULTYSELECT)), + new MenuOption("QUIT", 392, 400, () -> System.exit(0)) + } + }; + setMenuItemsAsGrid(menu); setBackground(new TitleScreenMap()); } } diff --git a/src/Screens/OptionsScreen.java b/src/Screens/OptionsScreen.java index 53d7330..d678ed4 100644 --- a/src/Screens/OptionsScreen.java +++ b/src/Screens/OptionsScreen.java @@ -11,14 +11,9 @@ import Menu.Menu; import Menu.MenuOption; import Players.Avatar; -import Players.Cat; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; public class OptionsScreen extends Menu { private MenuOption[][] items; diff --git a/src/Screens/PauseScreen.java b/src/Screens/PauseScreen.java index 288b477..d3963ab 100644 --- a/src/Screens/PauseScreen.java +++ b/src/Screens/PauseScreen.java @@ -21,18 +21,18 @@ public class PauseScreen extends Menu { PAUSE_INSTRUCTIONS = new SpriteFont("SOMETHING SOMETHING ", 350, 250, "Comic Sans", 30, Color.white); } - private final PlayLevelScreen playLevelScreen; + private final Pausable parent; private boolean menuEscape; - public PauseScreen(Map map, Player player, PlayLevelScreen playLevelScreen) { - this.playLevelScreen = playLevelScreen; + public PauseScreen(Map map, Player player, Pausable parent) { + this.parent = parent; menuEscape = false; setDrawables(new Drawable[]{ player, map }); setMenuItemsAsGrid(new MenuOption[][]{ { - new MenuOption("Return to Game", 100, 100, () -> this.playLevelScreen.resume()) + new MenuOption("Return to Game", 100, 100, this.parent::resume) }, { new MenuOption("Back to Menu", 100, 200, () -> GamePanel.getScreenCoordinator().setGameState(GameState.MENU)) } @@ -45,7 +45,7 @@ public PauseScreen(Map map, Player player, PlayLevelScreen playLevelScreen) { public void update() { updateMenu(); if (menuEscape && KeyboardAction.GAME_PAUSE.isDown()) { - playLevelScreen.resume(); + parent.resume(); } if (!KeyboardAction.GAME_PAUSE.isDown()) { menuEscape = true; diff --git a/src/Screens/PlayLevelScreen.java b/src/Screens/PlayLevelScreen.java index 5d397f3..3b3411e 100644 --- a/src/Screens/PlayLevelScreen.java +++ b/src/Screens/PlayLevelScreen.java @@ -2,42 +2,32 @@ import Engine.*; import Game.GameState; +import Game.TimeTracker; import Level.Map; import Level.Player; import Level.PlayerListener; -import Maps.*; +import Maps.GameMaps; import SpriteFont.SpriteFont; import Utils.Stopwatch; import java.awt.*; import java.awt.event.MouseEvent; -public class PlayLevelScreen extends Screen implements PlayerListener { +public class PlayLevelScreen extends Screen implements PlayerListener, Pausable { - private static final MapFactory[] MAPS; private static final Stopwatch screenTimer; private static final KeyLocker keyLocker; - private static final SpriteFont SPRITE_FONT_PAUSE; private static final SpriteFont[] SPRITE_FONT_INSTRUCTIONS; private static final Color COLOR_GREY_BACKGROUND; private static Map loadedMap; private static Screen alternateScreen; private static Player player; + private static TimeTracker timeTracker; static { screenTimer = new Stopwatch(); keyLocker = new KeyLocker(); - /* - * List of maps in the game, each map is given a constructor - * This is some new java funky stuff :D - */ - MAPS = new MapFactory[]{ - TestTutorial::new, TestMap::new, TestMap2::new, TestMap3::new, TestMap4::new, TestMap5::new, TestMap6::new, TestMap7::new, BossBattle::new - }; - - SPRITE_FONT_PAUSE = new SpriteFont("Pause", 350, 250, "Comic Sans", 30, Color.white); - SPRITE_FONT_INSTRUCTIONS = new SpriteFont[] { new SpriteFont("To JUMP: UP arrow key, or 'W', or SPACEBAR", 130, 140, "Times New Roman", 20, Color.white), @@ -64,7 +54,7 @@ public class PlayLevelScreen extends Screen implements PlayerListener { COLOR_GREY_BACKGROUND = new Color(0, 0, 0, 100); } - private State screenState; + private State screenState = State.RUNNING; private int currentMap; public PlayLevelScreen() { @@ -79,8 +69,8 @@ public PlayLevelScreen(int initialMap) { @Override public void initialize() { + timeTracker = new TimeTracker(); loadMap(currentMap); - screenState = State.RUNNING; } @Override @@ -88,9 +78,10 @@ public void update() { switch (screenState) { case RUNNING -> { if (KeyboardAction.GAME_PAUSE.isDown() && !keyLocker.isActionLocked(KeyboardAction.GAME_PAUSE)) { - screenState = State.PAUSE; + pause(); } else if (KeyboardAction.GAME_INSTRUCTIONS.isDown() && !keyLocker.isActionLocked(KeyboardAction.GAME_INSTRUCTIONS)) { screenState = State.INSTRUCTIONS; + timeTracker.stop(); } else { player.update(); loadedMap.update(player); @@ -101,19 +92,23 @@ public void update() { screenState = State.RUNNING; } } - case PAUSE,LEVEL_LOSE_MESSAGE -> alternateScreen.update(); + case PAUSE,LEVEL_LOSE_MESSAGE,GAME_COMPLETED -> { + if(alternateScreen != null) { + alternateScreen.update(); + } + } case PLAYER_DEAD -> screenState = State.LEVEL_LOSE_MESSAGE; case LEVEL_COMPLETED -> { alternateScreen = new LevelClearedScreen(); alternateScreen.initialize(); - screenTimer.setWaitTime(2500); + screenTimer.setWaitTime(750); screenState = State.LEVEL_WIN_MESSAGE; } case LEVEL_WIN_MESSAGE -> { alternateScreen.update(); if (screenTimer.isTimeUp()) { - nextLevel(); screenState = State.RUNNING; + nextLevel(); } } } @@ -157,9 +152,23 @@ public void draw(GraphicsHandler graphicsHandler) { } graphicsHandler.drawFilledRectangle(0, 0, ScreenManager.getScreenWidth(), ScreenManager.getScreenHeight(), COLOR_GREY_BACKGROUND); } + case GAME_COMPLETED -> { + if(!(alternateScreen instanceof GameScoreScreen)) { + alternateScreen = new GameScoreScreen(timeTracker); + alternateScreen.initialize(); + } + alternateScreen.draw(graphicsHandler); + } + } + if(timeTracker != null) { + timeTracker.draw(graphicsHandler); } } + public Map getLoadedMap() { + return loadedMap; + } + @Override public void mouseClicked(MouseEvent e) { if (alternateScreen != null) { @@ -176,11 +185,11 @@ public void nextLevel() { * * @param index index of map to load */ - private void loadMap(int index) { - if(index < MAPS.length) { + public void loadMap(int index) { + if(index < GameMaps.MAPS.length) { currentMap = index; //Load map using the MapFactory - loadedMap = MAPS[index].generateMap(); + loadedMap = GameMaps.MAPS[index].generateMap(); loadedMap.reset(); //Load the cat using the Config setting @@ -188,8 +197,21 @@ private void loadMap(int index) { player.setMap(loadedMap); player.addListener(this); screenState = State.RUNNING; + + //Update Time + timeTracker.setCurrentLevel(index); } else { - GamePanel.getScreenCoordinator().setGameState(GameState.MENU); + //Add the other menu + System.out.println("complete game"); + screenState = State.GAME_COMPLETED; + } + } + + @Override + public void onLevelFinished() { + //Only complete on final level + if(currentMap == GameMaps.MAPS.length - 1) { + timeTracker.stop(); } } @@ -211,18 +233,22 @@ public void backToMenu() { GamePanel.getScreenCoordinator().setGameState(GameState.MENU); } + @Override public void resume() { if (screenState == State.PAUSE || screenState == State.INSTRUCTIONS) { screenState = State.RUNNING; + timeTracker.start(); } } - public enum State { - RUNNING, LEVEL_COMPLETED, PLAYER_DEAD, LEVEL_WIN_MESSAGE, LEVEL_LOSE_MESSAGE, LEVEL_SELECT, PAUSE, INSTRUCTIONS, OPTIONS + @Override + public void pause() { + screenState = State.PAUSE; + timeTracker.stop(); } - private interface MapFactory { - Map generateMap(); + public enum State { + RUNNING, LEVEL_COMPLETED, PLAYER_DEAD, LEVEL_WIN_MESSAGE, LEVEL_LOSE_MESSAGE, PAUSE, INSTRUCTIONS, GAME_COMPLETED } } diff --git a/src/SpriteFont/SpriteFont.java b/src/SpriteFont/SpriteFont.java index 217e97b..f9e9a71 100644 --- a/src/SpriteFont/SpriteFont.java +++ b/src/SpriteFont/SpriteFont.java @@ -4,9 +4,11 @@ import Engine.GraphicsHandler; import java.awt.*; -import java.awt.font.FontRenderContext; +import java.util.Arrays; // This class represents a sprite font, which is graphic text (text drawn to the screen as if it were an image) + + public class SpriteFont implements Drawable { protected String text; protected Font font; @@ -16,10 +18,18 @@ public class SpriteFont implements Drawable { protected Color color; protected Color outlineColor; protected float outlineThickness = 1f; + /** + * Whether to automatically multi-line the text if `\n` is present + */ + protected boolean multiLine = false, containsNewLines; public SpriteFont(String text, float x, float y, String fontName, int fontSize, Color color) { + this(text,x,y,new Font(fontName,Font.PLAIN,fontSize),color); + } + + public SpriteFont(String text, float x, float y, Font font, Color color) { this.text = text; - font = new Font(fontName, Font.PLAIN, fontSize); + this.font = font; this.x = x; this.y = y; this.color = color; @@ -30,6 +40,10 @@ public void setColor(Color color) { this.color = color; } + public Color getColor() { + return color; + } + public String getText() { return text; } @@ -37,6 +51,11 @@ public String getText() { public void setText(String text) { this.text = text; updateDimensions(); + updateContainsNewLines(); + } + + public void updateContainsNewLines() { + containsNewLines = text.contains("\n"); } public void setFontName(String fontName) { @@ -108,11 +127,17 @@ public void moveUp(float dy) { y -= dy; } + + public void draw(GraphicsHandler graphicsHandler) { - if (outlineColor != null && !outlineColor.equals(color)) { - graphicsHandler.drawStringWithOutline(text, Math.round(x), Math.round(y), font, color, outlineColor, outlineThickness); + if(multiLine && containsNewLines) { + drawWithParsedNewLines(graphicsHandler); } else { - graphicsHandler.drawString(text, Math.round(x), Math.round(y), font, color); + if (outlineColor != null && !outlineColor.equals(color)) { + graphicsHandler.drawStringWithOutline(getText(), Math.round(x), Math.round(y), font, color, outlineColor, outlineThickness); + } else { + graphicsHandler.drawString(getText(), Math.round(x), Math.round(y), font, color); + } } } @@ -126,7 +151,7 @@ public void drawWithParsedNewLines(GraphicsHandler graphicsHandler) { if(width == -1) { FontMetrics metrics = graphicsHandler.getGraphics2D().getFontMetrics(font); height = metrics.getHeight(); - width = metrics.stringWidth(text); + width = metrics.stringWidth(getText()); } @@ -147,6 +172,14 @@ public void updateDimensions() { height = -1; } + public void setMultiLine(boolean multiLine) { + this.multiLine = multiLine; + } + + public boolean isMultiLine() { + return multiLine; + } + public boolean contains(Point point) { return point.x > x && point.y > y && point.x < x + width && point.y < y + height; } diff --git a/src/SpriteFont/TimerElement.java b/src/SpriteFont/TimerElement.java new file mode 100644 index 0000000..5f8d8d1 --- /dev/null +++ b/src/SpriteFont/TimerElement.java @@ -0,0 +1,38 @@ +package SpriteFont; + +import Utils.TimeParser; + +import java.awt.*; + +@Deprecated +public class TimerElement extends SpriteFont { + + private TimeParser timeParser; + + public TimerElement(String text, float x, float y, String fontName, int fontSize, Color color) { + super(text, x, y, fontName, fontSize, color); + } + + public TimerElement(TimeParser timeParser, float x, float y, String fontName, int fontSize, Color color) { + super(null,x,y,fontName,fontSize,color); + this.timeParser= timeParser; + } + + public TimerElement(TimeParser timeParser, float x, float y, Font font, Color color) { + super(null,x,y,font,color); + this.timeParser = timeParser; + } + + public TimeParser getGameTimer() { + return timeParser; + } + + public void setGameTimer(TimeParser timeParser) { + this.timeParser = timeParser; + } + + @Override + public String getText() { + return timeParser != null ? timeParser.toString() : "NO TIME SPECIFIED"; + } +} diff --git a/src/Tilesets/CommonTileset.java b/src/Tilesets/CommonTileset.java index 05d0cfe..fd687f6 100644 --- a/src/Tilesets/CommonTileset.java +++ b/src/Tilesets/CommonTileset.java @@ -229,7 +229,7 @@ public ArrayList defineTiles() { mapTiles.add(lethalSpikeTile); - // middle branch + // passable floating platform Frame floatingPlatformFrame = new FrameBuilder(getSubImage(3, 4), 0) .withScale(tileScale) .withBounds(0, 6, 16, 4) diff --git a/src/Utils/AirGroundState.java b/src/Utils/AirGroundState.java deleted file mode 100644 index e8b756a..0000000 --- a/src/Utils/AirGroundState.java +++ /dev/null @@ -1,7 +0,0 @@ -package Utils; - -// Simple enum for keeping track of it something is on the ground or in the air -// I don't like the name of this enum, but I couldn't think of a better name... -public enum AirGroundState { - GROUND, AIR -} diff --git a/src/Utils/GameTimer.java b/src/Utils/GameTimer.java new file mode 100644 index 0000000..7ecd9d3 --- /dev/null +++ b/src/Utils/GameTimer.java @@ -0,0 +1,43 @@ +package Utils; + +public class GameTimer extends TimeParser { + + private long startTime, endTime, addTime; + private boolean running; + + public GameTimer() { + startTime = 0; + endTime = 0; + addTime = 0; + running = false; + } + + public void start() { + if(!running) { + addTime = getElapsed(); + startTime = System.currentTimeMillis(); + running = true; + } + } + + public void stop() { + if(running) { + endTime = System.currentTimeMillis(); + running = false; + } + } + + public void reset() { + addTime = 0; + startTime = System.currentTimeMillis(); + } + + public long getElapsed() { + return (running ? System.currentTimeMillis() : endTime ) + addTime - startTime; + } + + @Override + public long getTime() { + return getElapsed(); + } +} diff --git a/src/Utils/Stopwatch.java b/src/Utils/Stopwatch.java index a57f11f..7bae0d1 100644 --- a/src/Utils/Stopwatch.java +++ b/src/Utils/Stopwatch.java @@ -4,16 +4,18 @@ public class Stopwatch { private long beforeTime = System.currentTimeMillis(); private int millisecondsToWait = 0; + private boolean timeUp = true; // tell stopwatch how many milliseconds to "time" public void setWaitTime(int millisecondsToWait) { this.millisecondsToWait = millisecondsToWait; beforeTime = System.currentTimeMillis(); + timeUp = false; } // will return true or false based on if the "time" is up (a specified number of milliseconds have passed) public boolean isTimeUp() { - return System.currentTimeMillis() - beforeTime > millisecondsToWait; + return timeUp || (timeUp = System.currentTimeMillis() - beforeTime > millisecondsToWait); } // reset timer to wait again for specified number of milliseconds diff --git a/src/Utils/TimeParser.java b/src/Utils/TimeParser.java new file mode 100644 index 0000000..7bb08e0 --- /dev/null +++ b/src/Utils/TimeParser.java @@ -0,0 +1,114 @@ +package Utils; + +public class TimeParser { + private static final long MS_PER_SECOND, SECOND_PER_MINUTE, MINUTE_PER_HOUR, MS_PER_HOUR, MS_PER_MINUTE; + + private long time; + + static { + MS_PER_SECOND = 1000; + SECOND_PER_MINUTE = 60; + MINUTE_PER_HOUR = 60; + MS_PER_MINUTE = MS_PER_SECOND * SECOND_PER_MINUTE; + MS_PER_HOUR = MS_PER_MINUTE * MINUTE_PER_HOUR; + } + + public TimeParser() { + this(0); + } + + public TimeParser(long time) { + this.time = time; + } + + public void setTime(long time) { + this.time = time; + } + + public long getTime() { + return time; + } + + public void addTime(long time) { + this.time += time; + } + + public void subtractTime(long time) { + this.time -= time; + } + + /** + * Converts the time to a readable string + * @return + */ + public String toString() { + /* + Implementation Note: + While yes, there is a more efficient method... I did it this way + Refrain from changing as the ' ' characters are important for rendering in the right spot + */ + long elapsed = getTime(); + long hours = elapsed / MS_PER_HOUR; + long minutes = (elapsed %= MS_PER_HOUR) / MS_PER_MINUTE; + long seconds = (elapsed %= MS_PER_MINUTE) / MS_PER_SECOND; + elapsed %= MS_PER_SECOND; + + StringBuilder stringBuilder = new StringBuilder(); + + /* + TODO clean / optimize + */ + + if(hours > 0) { + if(hours > 9) { + stringBuilder.append((char) ('0' + hours / 10)); + } + stringBuilder.append((char) ('0' + hours % 10)).append(':').append((char) ('0' + minutes / 10)).append((char) ('0' + minutes % 10)).append(':'); + stringBuilder.append((char) ('0' + seconds / 10)); + } else if(minutes > 0) { + if(minutes > 9) { + stringBuilder.append((char) ('0' + minutes / 10)); + } + stringBuilder.append((char) ('0' + minutes % 10)).append(':'); + stringBuilder.append((char) ('0' + seconds / 10)); + } else if(seconds > 9) { + stringBuilder.append((char) ('0' + seconds / 10)); + } + stringBuilder.append((char) ('0' + seconds % 10)).append('.'); + stringBuilder.append((char) ('0' + elapsed / 100)).append((char) ('0' + (elapsed / 10) % 10)); + +// char[] chs = new char[hours > 0 ? 11 : minutes > 0 ? 8 : 5]; +// +// //milliseconds +// chs[chs.length - 3] = '.'; +// chs[chs.length - 2] = (char) ('0' + (elapsed / 100)); +// chs[chs.length - 1] = (char) ('0' + (elapsed / 10) % 10); +// //seconds +// chs[chs.length - 5] = (char) ('0' + (seconds / 10)); +// if(minutes == 0 && hours == 0 && chs[chs.length - 5] == '0') { +// chs[chs.length - 5] = ' '; +// } +// chs[chs.length - 4] = (char) ('0' + (seconds % 10)); +// +// if(minutes > 0 || hours > 0) { +// chs[chs.length - 6] = ':'; +// chs[chs.length - 7] = (char) ('0' + (minutes % 10)); +// chs[chs.length - 8] = (char) ('0' + (minutes / 10)); +// if(chs[chs.length - 8] == '0' && hours == 0) { +// chs[chs.length - 8] = ' '; +// } +// +// if(hours > 0) { +// chs[0] = (char) ('0' + (hours / 10) % 10); +// if(chs[0] == '0') { +// chs[0] = ' '; +// } +// chs[1] = (char) ('0' + hours % 10); +// chs[2] = ':'; +// } +// } +// +// return new String(chs); + return stringBuilder.toString(); + } +}