From 66d9a06f9ef6113579d0cd82a75b408ba5b33050 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 20 Sep 2017 17:19:14 +1200 Subject: [PATCH 01/33] Adding icon files to git #story[1245] --- src/main/resources/icons/bumperIcon.png | Bin 0 -> 3580 bytes src/main/resources/icons/handlingIcon.png | Bin 0 -> 3205 bytes src/main/resources/icons/velocity.png | Bin 0 -> 4328 bytes src/main/resources/icons/windWalkerIcon.png | Bin 0 -> 3939 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/icons/bumperIcon.png create mode 100644 src/main/resources/icons/handlingIcon.png create mode 100644 src/main/resources/icons/velocity.png create mode 100644 src/main/resources/icons/windWalkerIcon.png diff --git a/src/main/resources/icons/bumperIcon.png b/src/main/resources/icons/bumperIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1da6d4cf8d72949af1b64aa7e60335804d5d7b5 GIT binary patch literal 3580 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 Date: Wed, 20 Sep 2017 17:52:02 +1200 Subject: [PATCH 02/33] Adding icon files to git #story[1245] --- src/main/java/seng302/gameServer/GameState.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 1dd9963a..0badbe59 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -38,8 +38,6 @@ public class GameState implements Runnable { static final int PREPATORY_TIME = 5 * -1000; private static final int TIME_TILL_START = 10 * 1000; - private static final Long POWERUP_TIMEOUT_MS = 10_000L; - private static final Integer STATE_UPDATES_PER_SECOND = 60; private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further private static final Double MARK_COLLISION_DISTANCE = 15d; @@ -327,7 +325,8 @@ public class GameState implements Runnable { private void checkPowerUpTimeout(ServerYacht yacht) { if (yacht.getPowerUp() != null) { - if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) { + if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() + .getTimeout()) { yacht.powerDown(); sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired"); logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); From 061e49bab96728ad2b84f91cc230ddddcff0da63 Mon Sep 17 00:00:00 2001 From: William Muir Date: Fri, 22 Sep 2017 23:44:03 +1200 Subject: [PATCH 03/33] Implemented wind walker algorithm. Refactored some GameState updating logic to allow for better token logic integration GameState: Moved all token logic into its own function startPoint so that it is dsijoint from other updating logic GameState: Implemented wind walker algorithm. GameState: Changed Generic 'speedMultiplier' to 'serverSpeedMultiplier' to make it obviously disjoint from a boats speed multiplier MessageFactory: Moved some found message creation (Chatter Message) server side into MessageFactory that wasnt already there ServerYacht: Added a speed multiplier and a handling multiplier to the serveryacht class that is set and reset upon powerup / down #story[1293] --- .../java/seng302/gameServer/GameState.java | 172 +++++++++++++----- .../seng302/gameServer/MessageFactory.java | 5 + src/main/java/seng302/model/ServerYacht.java | 24 ++- src/main/java/seng302/model/token/Token.java | 2 - .../gameServer/server/ChatCommandsTest.java | 6 +- 5 files changed, 158 insertions(+), 51 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 3afd76ff..8f772653 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -46,12 +46,14 @@ public class GameState implements Runnable { private static final Double BOUNCE_DISTANCE_MARK = 20.0; public static final Double BOUNCE_DISTANCE_YACHT = 30.0; private static final Double COLLISION_VELOCITY_PENALTY = 0.3; + private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; + private static final Integer HANDLING_BOOST_MULTIPLIER = 2; private static Long previousUpdateTime; public static Double windDirection; private static Double windSpeed; - private static Double speedMultiplier = 1d; + private static Double serverSpeedMultiplier = 1d; private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat. private static Boolean playerHasLeftFlag; @@ -85,7 +87,7 @@ public class GameState implements Runnable { GameState.hostIpAddress = hostIpAddress; customizationFlag = false; playerHasLeftFlag = false; - speedMultiplier = 1.0; + serverSpeedMultiplier = 1.0; currentStage = GameStages.LOBBYING; isRaceStarted = false; //set this when game stage changes to prerace @@ -283,9 +285,11 @@ public class GameState implements Runnable { tokensInPlay.clear(); //Get a random token location with random type - Token token = allTokens.get(random.nextInt(allTokens.size())); + Token token = allTokens.get(random.nextInt(allTokens.size() - 1) + 1); token.assignRandomType(); + logger.debug("Spawned token of type " + token.getTokenType()); + tokensInPlay.add(token); } @@ -308,10 +312,10 @@ public class GameState implements Runnable { } for (ServerYacht yacht : yachts.values()) { updateVelocity(yacht); - checkPowerUpTimeout(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); checkCollision(yacht); + preformTokenUpdates(yacht); //This update must always be done lsat if (yacht.getBoatStatus() != BoatStatus.FINISHED) { checkForLegProgression(yacht); raceFinished = false; @@ -324,13 +328,77 @@ public class GameState implements Runnable { } - private void checkPowerUpTimeout(ServerYacht yacht) { + /** + * All token functionality entry points is taken care of here. So can be disabled and enabled + * easily + * + * @param yacht The yacht to perform token checks on + */ + private void preformTokenUpdates(ServerYacht yacht) { + checkTokenPickUp(yacht); + if (yacht.getPowerUp() != null) { - if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() - .getTimeout()) { - yacht.powerDown(); - sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired"); - logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + switch (yacht.getPowerUp()) { + case WIND_WALKER: + windWalk(yacht); + break; + case BUMPER: + break; + } + + checkPowerUpTimeout(yacht); + } + + + } + + + /** + * Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the + * yacht down. WARNING. Do not call if the yacht does not have an active power up. Check with + * yacht.getPowerup != null first + * + * @param yacht The yacht to check to power down + */ + private void checkPowerUpTimeout(ServerYacht yacht) { + if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() + .getTimeout()) { + yacht.powerDown(); + String logMessage = yacht.getBoatName() + "'s power-up token expired"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + } + } + + + /** + * This function changes the wind to be at an angle that causes the yacht in question to be at + * VMG. + * + * @param yacht The yacht to fix the wind for + */ + private void windWalk(ServerYacht yacht) { + HashMap upwindOptimal = PolarTable.getOptimalUpwindVMG(windSpeed); + Double optimalAngle = null; + for (Double windAngle : upwindOptimal.keySet()) { + optimalAngle = windAngle; + } + + Double heading = yacht.getHeading(); + if (heading < windDirection) { + Double diff = Math.abs(optimalAngle - (windDirection - heading)); + if (windDirection - heading < optimalAngle) { + windDirection = (double) Math.floorMod(Math.round(windDirection + diff), 360L); + } else { + windDirection = (double) Math.floorMod(Math.round(windDirection - diff), 360L); + } + } else { + Double diff = Math.abs(optimalAngle - (heading - windDirection)); + if (heading - windDirection < optimalAngle) { + windDirection = (double) Math.floorMod(Math.round(windDirection - diff), 360L); + } else { + windDirection = (double) Math.floorMod(Math.round(windDirection + diff), 360L); } } } @@ -357,19 +425,53 @@ public class GameState implements Runnable { } /** - * Checks all tokensInPlay to see if a yacht has picked one up - * @return Token which was collided with - * @param serverYacht The yacht to check for collision with a token + * Checks all tokensInPlay to see if a yacht has picked one up. If so, the yacht is powered up + * in the appropriate way + * @param yacht The yacht to check for collision with a token */ - private static Token checkTokenPickUp(ServerYacht serverYacht) { + private void checkTokenPickUp(ServerYacht yacht) { + Token collidedToken = null; for (Token token : tokensInPlay) { - Double distance = GeoUtility.getDistance(token, serverYacht.getLocation()); + Double distance = GeoUtility.getDistance(token, yacht.getLocation()); if (distance < YACHT_COLLISION_DISTANCE) { - return token; + collidedToken = token; } } - return null; + if (collidedToken != null) { + tokensInPlay.remove(collidedToken); + if (collidedToken.getTokenType() == TokenType.RANDOM) { + collidedToken.realiseRandom(); + } + + TokenType tokenType = collidedToken.getTokenType(); + switch (tokenType) { + case BOOST: + yacht.setSpeedMultiplier(VELOCITY_BOOST_MULTIPLIER); + break; + case BUMPER: + // TODO: 22/09/17 wmu16 + break; + case HANDLING: + yacht.setHandlingMultiplier(HANDLING_BOOST_MULTIPLIER); + break; + case WIND_WALKER: + // TODO: 22/09/17 wmu16 + break; + } + yacht.powerUp(tokenType); + + String logMessage = + yacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() + + " token"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + notifyMessageListeners(MessageFactory.getRaceXML()); + notifyMessageListeners(MessageFactory.makePickupMessage(yacht, collidedToken)); + + logger.debug("Yacht: " + yacht.getShortName() + " got powerup " + collidedToken + .getTokenType()); + } } @@ -388,7 +490,6 @@ public class GameState implements Runnable { //Yacht Collision ServerYacht collidedYacht = checkYachtCollision(serverYacht); Mark collidedMark = checkMarkCollision(serverYacht); - Token collidedToken = checkTokenPickUp(serverYacht); if (collidedYacht != null) { GeoPoint originalLocation = serverYacht.getLocation(); @@ -431,34 +532,14 @@ public class GameState implements Runnable { ); notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht)); } - - //Token Collision - if (collidedToken != null) { - if (collidedToken.getTokenType() == TokenType.RANDOM) { - collidedToken.realiseRandom(); - } - sendServerMessage(serverYacht.getSourceId(), - serverYacht.getBoatName() + " has picked up a " + collidedToken.getTokenType() - .getName() + " token"); - tokensInPlay.remove(collidedToken); - serverYacht.powerUp(collidedToken.getTokenType()); - logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken - .getTokenType()); - notifyMessageListeners(MessageFactory.getRaceXML()); - notifyMessageListeners(MessageFactory.makePickupMessage(serverYacht, collidedToken)); - } } private void updateVelocity(ServerYacht yacht) { Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); - Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier; - if (yacht.getPowerUp() != null) { - if (yacht.getPowerUp().equals(TokenType.BOOST)) { - maxBoatSpeed *= VELOCITY_BOOST_MULTIPLIER; - } - } + Double maxBoatSpeed = + GeoUtility.knotsToMMS(boatSpeedInKnots) * serverSpeedMultiplier * yacht.getSpeedMultiplier(); Double currentVelocity = yacht.getCurrentVelocity(); // TODO: 15/08/17 remove magic numbers from these equations. @@ -700,6 +781,7 @@ public class GameState implements Runnable { String name = new String(customizeData); playerYacht.setBoatName(name); } else if (requestType.equals(CustomizeRequestType.COLOR)) { + //This low level stuff shouldnt be here alistair! In fact no logic LIKE THIS should! - wmu16 int red = customizeData[0] & 0xFF; int green = customizeData[1] & 0xFF; int blue = customizeData[2] & 0xFF; @@ -799,7 +881,7 @@ public class GameState implements Runnable { switch (words[2].trim()) { case "/speed": try { - setSpeedMultiplier(Double.valueOf(words[3])); + setServerSpeedMultiplier(Double.valueOf(words[3])); sendServerMessage(chatterMessage.getMessage_type(), "Speed modifier set to x" + words[3]); } catch (Exception e) { @@ -866,11 +948,11 @@ public class GameState implements Runnable { currentStage = GameStages.FINISHED; } - public static void setSpeedMultiplier (double multiplier) { - speedMultiplier = multiplier; + public static void setServerSpeedMultiplier(double multiplier) { + serverSpeedMultiplier = multiplier; } - public static double getSpeedMultiplier () { - return speedMultiplier; + public static double getServerSpeedMultiplier() { + return serverSpeedMultiplier; } } diff --git a/src/main/java/seng302/gameServer/MessageFactory.java b/src/main/java/seng302/gameServer/MessageFactory.java index 2ef38d29..d883d06c 100644 --- a/src/main/java/seng302/gameServer/MessageFactory.java +++ b/src/main/java/seng302/gameServer/MessageFactory.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import seng302.gameServer.messages.BoatLocationMessage; import seng302.gameServer.messages.BoatSubMessage; +import seng302.gameServer.messages.ChatterMessage; import seng302.gameServer.messages.RaceStartNotificationType; import seng302.gameServer.messages.RaceStartStatusMessage; import seng302.gameServer.messages.RaceStatus; @@ -157,4 +158,8 @@ public class MessageFactory { } return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType); } + + public static ChatterMessage makeChatterMessage(Integer messageType, String message) { + return new ChatterMessage(messageType, "SERVER: " + message); + } } diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index e7d936ca..21508774 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -56,6 +56,8 @@ public class ServerYacht { //PowerUp private TokenType powerUp; private Long powerUpStartTime; + private Integer speedMultiplier; + private Integer handlingMultiplier; public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName, @@ -77,6 +79,8 @@ public class ServerYacht { this.legNumber = 0; this.boatColor = Colors.getColor(sourceId - 1); this.powerUp = null; + this.speedMultiplier = 1; + this.handlingMultiplier = 1; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; @@ -114,6 +118,8 @@ public class ServerYacht { public void powerDown() { this.powerUp = null; + this.speedMultiplier = 1; + this.handlingMultiplier = 1; } public Long getPowerUpStartTime() { @@ -130,7 +136,7 @@ public class ServerYacht { * @param amount the amount by which to adjust the boat heading. */ public void adjustHeading(Double amount) { - Double newVal = heading + amount; + Double newVal = heading + amount * handlingMultiplier; lastHeading = heading; heading = (double) Math.floorMod(newVal.longValue(), 360L); } @@ -429,4 +435,20 @@ public class ServerYacht { public BoatMeshType getBoatType() { return boatType; } + + public Integer getSpeedMultiplier() { + return speedMultiplier; + } + + public void setSpeedMultiplier(Integer speedMultiplier) { + this.speedMultiplier = speedMultiplier; + } + + public Integer getHandlingMultiplier() { + return handlingMultiplier; + } + + public void setHandlingMultiplier(Integer handlingMultiplier) { + this.handlingMultiplier = handlingMultiplier; + } } diff --git a/src/main/java/seng302/model/token/Token.java b/src/main/java/seng302/model/token/Token.java index fcdbab5e..e1a16bd3 100644 --- a/src/main/java/seng302/model/token/Token.java +++ b/src/main/java/seng302/model/token/Token.java @@ -39,6 +39,4 @@ public class Token extends GeoPoint { tokenTypeList.remove(TokenType.RANDOM); tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size())); } - - } diff --git a/src/test/java/seng302/gameServer/server/ChatCommandsTest.java b/src/test/java/seng302/gameServer/server/ChatCommandsTest.java index cb0bc930..5c67c649 100644 --- a/src/test/java/seng302/gameServer/server/ChatCommandsTest.java +++ b/src/test/java/seng302/gameServer/server/ChatCommandsTest.java @@ -110,7 +110,7 @@ public class ChatCommandsTest { } catch (InterruptedException ie) { ie.printStackTrace(); } - Assert.assertEquals(5.0, GameState.getSpeedMultiplier(), 0.00001); + Assert.assertEquals(5.0, GameState.getServerSpeedMultiplier(), 0.00001); mst.terminate(); try { Thread.sleep(200); @@ -150,7 +150,7 @@ public class ChatCommandsTest { ie.printStackTrace(); } mst.terminate(); - Assert.assertEquals(1.0, GameState.getSpeedMultiplier(), 0.00001); + Assert.assertEquals(1.0, GameState.getServerSpeedMultiplier(), 0.00001); try { Thread.sleep(2000); } catch (InterruptedException ie) { @@ -194,7 +194,7 @@ public class ChatCommandsTest { } catch (InterruptedException ie) { ie.printStackTrace(); } - Assert.assertEquals(1.0, GameState.getSpeedMultiplier(), 0.00001); + Assert.assertEquals(1.0, GameState.getServerSpeedMultiplier(), 0.00001); mst.terminate(); host.setSocketToClose(); client.setSocketToClose(); From e61b6d50a13253368af6c2042975a6fd276dbdbd Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 23 Sep 2017 11:33:01 +1200 Subject: [PATCH 04/33] Small refactor. Fixed tokens to spawn on the minute mark. Moved updates of wind and token timers into gamestate from mainserver thread. Now triggered upon GameState change to start #story[1293] --- .../java/seng302/gameServer/GameState.java | 88 +++++++++++++++++-- .../seng302/gameServer/MainServerThread.java | 62 ------------- 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 8f772653..59056da6 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -35,25 +35,35 @@ public class GameState implements Runnable { private static Logger logger = LoggerFactory.getLogger(GameState.class); + private static final Integer STATE_UPDATES_PER_SECOND = 60; + + //Scheduling constants static final int WARNING_TIME = 10 * -1000; static final int PREPATORY_TIME = 5 * -1000; private static final int TIME_TILL_START = 10 * 1000; - private static final Integer STATE_UPDATES_PER_SECOND = 60; - private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further + //Wind Constants + private static final int MAX_WIND_SPEED = 12000; + private static final int MIN_WIND_SPEED = 8000; + + //Rounding Constants + private static final Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further + + //Collision constants private static final Double MARK_COLLISION_DISTANCE = 15d; public static final Double YACHT_COLLISION_DISTANCE = 25.0; private static final Double BOUNCE_DISTANCE_MARK = 20.0; public static final Double BOUNCE_DISTANCE_YACHT = 30.0; private static final Double COLLISION_VELOCITY_PENALTY = 0.3; + //Powerup Constants private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; private static final Integer HANDLING_BOOST_MULTIPLIER = 2; private static Long previousUpdateTime; public static Double windDirection; private static Double windSpeed; - private static Double serverSpeedMultiplier = 1d; + private static Double serverSpeedMultiplier; private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat. private static Boolean playerHasLeftFlag; @@ -90,7 +100,6 @@ public class GameState implements Runnable { serverSpeedMultiplier = 1.0; currentStage = GameStages.LOBBYING; isRaceStarted = false; - //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? newMessageListeners = new ArrayList<>(); @@ -242,7 +251,15 @@ public class GameState implements Runnable { } catch (InterruptedException e) { System.out.println("[GameState] interrupted exception"); } - if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) { + if (currentStage == GameStages.PRE_RACE) { + update(); + if (System.currentTimeMillis() > startTime) { + startSpawningTokens(); + startUpdatingWind(); + GameState.setCurrentStage(GameStages.RACING); + } + } + if (currentStage == GameStages.RACING) { update(); } @@ -252,6 +269,61 @@ public class GameState implements Runnable { } } + /** + * Start spawning coins every 60s after the first minute + */ + private void startSpawningTokens() { + Timer timer = new Timer("Token Spawning Timer"); + timer.schedule(new TimerTask() { + @Override + public void run() { + spawnNewToken(); + notifyMessageListeners(MessageFactory.getRaceXML()); + } + }, 0, 60000); + } + + // TODO: 29/08/17 wmu16 - This sort of update should be in game state + private static void startUpdatingWind() { + Timer timer = new Timer("Wind Updating Timer"); + timer.schedule(new TimerTask() { + @Override + public void run() { + updateWind(); + } + }, 0, 500); + } + + + private static void updateWind() { + Integer direction = GameState.getWindDirection().intValue(); + Integer windSpeed = GameState.getWindSpeedMMS().intValue(); + + Random random = new Random(); + + if (Math.floorMod(random.nextInt(), 2) == 0) { + direction += random.nextInt(4); + windSpeed += random.nextInt(20) + 459; + } else { + direction -= random.nextInt(4); + windSpeed -= random.nextInt(20) + 459; + } + + direction = Math.floorMod(direction, 360); + + if (windSpeed > MAX_WIND_SPEED) { + windSpeed -= random.nextInt(500); + } + + if (windSpeed <= MIN_WIND_SPEED) { + windSpeed += random.nextInt(500); + } + + GameState.setWindSpeed(Double.valueOf(windSpeed)); + GameState.setWindDirection(direction.doubleValue()); + } + + public static void updateBoat(Integer sourceId, BoatAction actionType) { ServerYacht playerYacht = yachts.get(sourceId); switch (actionType) { @@ -285,7 +357,7 @@ public class GameState implements Runnable { tokensInPlay.clear(); //Get a random token location with random type - Token token = allTokens.get(random.nextInt(allTokens.size() - 1) + 1); + Token token = allTokens.get(random.nextInt(allTokens.size())); token.assignRandomType(); logger.debug("Spawned token of type " + token.getTokenType()); @@ -307,9 +379,7 @@ public class GameState implements Runnable { Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0; previousUpdateTime = System.currentTimeMillis(); - if (System.currentTimeMillis() > startTime) { - GameState.setCurrentStage(GameStages.RACING); - } + for (ServerYacht yacht : yachts.values()) { updateVelocity(yacht); yacht.runAutoPilot(); diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index b5d8aa25..b4876748 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -37,9 +37,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { private static final int PORT = 4942; private static final Integer CLIENT_UPDATES_PER_SECOND = 60; - private static final int MAX_WIND_SPEED = 12000; - private static final int MIN_WIND_SPEED = 8000; - private boolean terminated; private Thread thread; @@ -101,8 +98,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { GameState.addMessageEventListener(this::broadcastMessage); terminated = false; thread = new Thread(this, "MainServer"); - startUpdatingWind(); - startSpawningTokens(); thread.start(); } @@ -187,63 +182,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { } } - private static void updateWind(){ - Integer direction = GameState.getWindDirection().intValue(); - Integer windSpeed = GameState.getWindSpeedMMS().intValue(); - - Random random = new Random(); - - if (Math.floorMod(random.nextInt(), 2) == 0){ - direction += random.nextInt(4); - windSpeed += random.nextInt(20) + 459; - } - else{ - direction -= random.nextInt(4); - windSpeed -= random.nextInt(20) + 459; - } - - direction = Math.floorMod(direction, 360); - - if (windSpeed > MAX_WIND_SPEED){ - windSpeed -= random.nextInt(500); - } - - if (windSpeed <= MIN_WIND_SPEED){ - windSpeed += random.nextInt(500); - } - - GameState.setWindSpeed(Double.valueOf(windSpeed)); - GameState.setWindDirection(direction.doubleValue()); - } - - - - - // TODO: 29/08/17 wmu16 - This sort of update should be in game state - private static void startUpdatingWind(){ - Timer timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - updateWind(); - } - }, 0, 500); - } - - /** - * Start spawning coins every 60s after the first minute - */ - private void startSpawningTokens() { - Timer timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - GameState.spawnNewToken(); - broadcastMessage(MessageFactory.getRaceXML()); - } - }, 10000, 60000); - } - /** * A client has tried to connect to the server * From ecb3d4ecbf0a349cdbfcf6ae5bbfd6267ab92334 Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 23 Sep 2017 11:49:56 +1200 Subject: [PATCH 05/33] Removed sendServerMessage to be replaced with notifyMessageListeners. Minor structure move arounds #story[1293] --- .../java/seng302/gameServer/GameState.java | 38 +++++++++---------- .../gameServer/messages/ChatterMessage.java | 2 +- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 59056da6..cfba8af1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -92,7 +92,6 @@ public class GameState implements Runnable { windSpeed = 10000d; yachts = new HashMap<>(); tokensInPlay = new ArrayList<>(); - players = new ArrayList<>(); GameState.hostIpAddress = hostIpAddress; customizationFlag = false; @@ -688,7 +687,10 @@ public class GameState implements Runnable { if (hasProgressed) { if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) { - sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber()); + + String logMessage = yacht.getBoatName() + " passed leg " + yacht.getLegNumber(); + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); } yacht.incrementLegNumber(); sendMarkRoundingMessage(yacht); @@ -724,7 +726,9 @@ public class GameState implements Runnable { if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) { yacht.setClosestCurrentMark(mark1); yacht.setBoatStatus(BoatStatus.RACING); - sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed start line"); + String logMessage = yacht.getBoatName() + " passed start line"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); return true; } } @@ -828,7 +832,10 @@ public class GameState implements Runnable { if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { yacht.setClosestCurrentMark(mark1); yacht.setBoatStatus(BoatStatus.FINISHED); - sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed finish line"); + + String logMessage = yacht.getBoatName() + " passed finish line"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); return true; } } @@ -937,13 +944,6 @@ public class GameState implements Runnable { roundingMark.getSourceID())); } - - public static void sendServerMessage(Integer messageType, String message) { - notifyMessageListeners(new ChatterMessage( - messageType, "SERVER: " + message - )); - } - public static void processChatter(ChatterMessage chatterMessage, boolean isHost) { String chatterText = chatterMessage.getMessage(); String[] words = chatterText.split("\\s+"); @@ -951,17 +951,19 @@ public class GameState implements Runnable { switch (words[2].trim()) { case "/speed": try { - setServerSpeedMultiplier(Double.valueOf(words[3])); - sendServerMessage(chatterMessage.getMessage_type(), - "Speed modifier set to x" + words[3]); + serverSpeedMultiplier = Double.valueOf(words[3]); + String logMessage = "Speed modifier set to x" + words[3]; + notifyMessageListeners(MessageFactory + .makeChatterMessage(chatterMessage.getMessageType(), logMessage)); } catch (Exception e) { Logger logger = LoggerFactory.getLogger(GameState.class); logger.error("cannot parse >speed value"); } return; case "/finish": - sendServerMessage(chatterMessage.getMessage_type(), - "Game will now finish"); + String logMessage = "Game will now finish"; + notifyMessageListeners(MessageFactory + .makeChatterMessage(chatterMessage.getMessageType(), logMessage)); endRace(); return; } @@ -1018,10 +1020,6 @@ public class GameState implements Runnable { currentStage = GameStages.FINISHED; } - public static void setServerSpeedMultiplier(double multiplier) { - serverSpeedMultiplier = multiplier; - } - public static double getServerSpeedMultiplier() { return serverSpeedMultiplier; } diff --git a/src/main/java/seng302/gameServer/messages/ChatterMessage.java b/src/main/java/seng302/gameServer/messages/ChatterMessage.java index 266fca62..4a3aec39 100644 --- a/src/main/java/seng302/gameServer/messages/ChatterMessage.java +++ b/src/main/java/seng302/gameServer/messages/ChatterMessage.java @@ -40,7 +40,7 @@ public class ChatterMessage extends Message { return message; } - public int getMessage_type() { + public int getMessageType() { return message_type; } } From 8c7f9a878db16e5042ab4a8d234d8dbdbaf07956 Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 23 Sep 2017 13:23:16 +1200 Subject: [PATCH 06/33] Created Boat bumper logic. Refactored logic for powering up / dpwn YachtEventType: Added some new events, a generic power down event and a bumper_crash event for an affected boat GameState: Implemented boat bumper logic MessageFactory: Made new messages for powerdown and status effect ClientYacht: Had to create another powerDown functional interface to inform the race view controller when to turn off the icon RaceViewController/GameClient: Now waits for a message about powering down before turning off rather than waiting time client side #story[1293] --- .../java/seng302/gameServer/GameState.java | 66 ++++++++++++++----- .../seng302/gameServer/MessageFactory.java | 39 +++++++++++ .../gameServer/messages/YachtEventType.java | 5 +- src/main/java/seng302/model/ClientYacht.java | 24 +++++++ src/main/java/seng302/model/token/Token.java | 7 ++ .../java/seng302/visualiser/GameClient.java | 9 ++- .../controllers/RaceViewController.java | 27 ++++---- 7 files changed, 147 insertions(+), 30 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index cfba8af1..496c7cb9 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,5 +1,6 @@ package seng302.gameServer; +import com.sun.corba.se.spi.activation.Server; import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +60,7 @@ public class GameState implements Runnable { //Powerup Constants private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; private static final Integer HANDLING_BOOST_MULTIPLIER = 2; + private static final Long BUMPER_DISABLE_TIME = 5_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -279,7 +281,7 @@ public class GameState implements Runnable { spawnNewToken(); notifyMessageListeners(MessageFactory.getRaceXML()); } - }, 0, 60000); + }, 0, 15_000); } // TODO: 29/08/17 wmu16 - This sort of update should be in game state @@ -351,13 +353,14 @@ public class GameState implements Runnable { * Randomly select a subset of tokensInPlay from a pre defined superset * Broadasts a new race status message to show this update */ - public static void spawnNewToken() { + private void spawnNewToken() { Random random = new Random(); tokensInPlay.clear(); //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); - token.assignRandomType(); +// token.assignRandomType(); + token.assignType(TokenType.BUMPER); logger.debug("Spawned token of type " + token.getTokenType()); @@ -383,8 +386,9 @@ public class GameState implements Runnable { updateVelocity(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); + preformTokenUpdates( + yacht); //This update must be done before collision. Sorry sorta hacky rn. checkCollision(yacht); - preformTokenUpdates(yacht); //This update must always be done lsat if (yacht.getBoatStatus() != BoatStatus.FINISHED) { checkForLegProgression(yacht); raceFinished = false; @@ -405,38 +409,66 @@ public class GameState implements Runnable { */ private void preformTokenUpdates(ServerYacht yacht) { checkTokenPickUp(yacht); + checkPowerUpTimeout(yacht); + TokenType powerUp = yacht.getPowerUp(); - if (yacht.getPowerUp() != null) { - switch (yacht.getPowerUp()) { + if (powerUp != null) { + switch (powerUp) { case WIND_WALKER: windWalk(yacht); break; case BUMPER: + ServerYacht collidedYacht = checkYachtCollision(yacht); + if (collidedYacht != null) { + yacht.powerDown(); + boatTempShutDown(collidedYacht); + notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); + notifyMessageListeners( + MessageFactory.makeStatusEffectMessage(yacht, powerUp)); + } break; } - - checkPowerUpTimeout(yacht); } + } + // TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/ + /** + * Disables the given boat for BUMPER_DISABLE_TIME ms. + * + * @param yacht The yacht to disable + */ + private void boatTempShutDown(ServerYacht yacht) { + yacht.setSpeedMultiplier(0); + Timer shutDownTimer = new Timer("Shutdown Timer"); + shutDownTimer.schedule(new TimerTask() { + @Override + public void run() { + yacht.powerDown(); //Note this actually resets the boat to normal. + } + }, BUMPER_DISABLE_TIME); } /** * Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the - * yacht down. WARNING. Do not call if the yacht does not have an active power up. Check with - * yacht.getPowerup != null first + * yacht down. * * @param yacht The yacht to check to power down */ private void checkPowerUpTimeout(ServerYacht yacht) { - if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() - .getTimeout()) { - yacht.powerDown(); - String logMessage = yacht.getBoatName() + "'s power-up token expired"; - notifyMessageListeners( - MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); - logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + if (yacht.getPowerUp() != null) { + if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() + .getTimeout()) { + String logMessage = + yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); + logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + + yacht.powerDown(); + } } } diff --git a/src/main/java/seng302/gameServer/MessageFactory.java b/src/main/java/seng302/gameServer/MessageFactory.java index d883d06c..2bb0d474 100644 --- a/src/main/java/seng302/gameServer/MessageFactory.java +++ b/src/main/java/seng302/gameServer/MessageFactory.java @@ -137,6 +137,14 @@ public class MessageFactory { return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION); } + + /** + * Constructs a message to be sent out whenever a yacht picks up a boost + * + * @param serverYacht The yacht that has picked up a power up + * @param token The token which they picked up + * @return The corresponding YachtEventCodeMessage + */ public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) { YachtEventType yachtEventType = null; switch (token.getTokenType()) { @@ -159,6 +167,37 @@ public class MessageFactory { return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType); } + /** + * Constructs a message representing a certain buff / debuff for a given yacht. For now this is + * just for the bumper debuff so the affected boat is aware that it has been crashed. This could + * however be extended to render affects for all boats given a certain debuff. + * + * @param yacht The yacht affected by some status + * @param token The token indicating what status they have + * @return A YachtEventCodeMessage + */ + public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht, + TokenType token) { + YachtEventType yachtEventType = null; + switch (token) { + case BUMPER: + yachtEventType = YachtEventType.BUMPER_CRASH; + break; + } + return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType); + } + + + /** + * Constructs a message to be sent out when a given yacht powers down (From a boost of any type) + * + * @param yacht The yacht that is powering down + * @return A YachtEventCodeMessage representing this action + */ + public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) { + return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN); + } + public static ChatterMessage makeChatterMessage(Integer messageType, String message) { return new ChatterMessage(messageType, "SERVER: " + message); } diff --git a/src/main/java/seng302/gameServer/messages/YachtEventType.java b/src/main/java/seng302/gameServer/messages/YachtEventType.java index c8a997a0..facda84e 100644 --- a/src/main/java/seng302/gameServer/messages/YachtEventType.java +++ b/src/main/java/seng302/gameServer/messages/YachtEventType.java @@ -9,7 +9,10 @@ public enum YachtEventType { TOKEN_BUMPER(35), TOKEN_HANDLING(36), TOKEN_WIND_WALKER(37), - TOKEN_RANDOM(38); + TOKEN_RANDOM(38), + POWER_DOWN(39), + BUMPER_CRASH(40); + private int code; diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java index b926f2ff..f626db30 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -39,11 +39,22 @@ public class ClientYacht extends Observable { void notifyRounding(ClientYacht yacht, int legNumber); } + //This notifies RaceViewController so it can display icon - wmu16 @FunctionalInterface public interface PowerUpListener { + void notifyPowerUp(ClientYacht yacht, TokenType tokenType); } + //This notifies RaceViewController so it can remove token icon - wmu16 + @FunctionalInterface + public interface PowerDownListener { + + void notifyPowerDown(ClientYacht yacht); + } + + + private Logger logger = LoggerFactory.getLogger(ClientYacht.class); @@ -70,6 +81,8 @@ public class ClientYacht extends Observable { private List locationListeners = new ArrayList<>(); private List markRoundingListeners = new ArrayList<>(); private List powerUpListeners = new ArrayList<>(); + private List powerDownListeners = new ArrayList<>(); + private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); @@ -211,6 +224,13 @@ public class ClientYacht extends Observable { this.position = position; } + public void powerDown() { + this.powerUp = null; + for (PowerDownListener listener : powerDownListeners) { + listener.notifyPowerDown(this); + } + } + public void setPowerUp(TokenType tokenType) { this.powerUp = tokenType; for (PowerUpListener listener : powerUpListeners) { @@ -295,6 +315,10 @@ public class ClientYacht extends Observable { powerUpListeners.add(listener); } + public void addPowerDownListener(PowerDownListener listener) { + powerDownListeners.add(listener); + } + public void removeMarkRoundingListener(MarkRoundingListener listener) { markRoundingListeners.remove(listener); } diff --git a/src/main/java/seng302/model/token/Token.java b/src/main/java/seng302/model/token/Token.java index e1a16bd3..ed8b87eb 100644 --- a/src/main/java/seng302/model/token/Token.java +++ b/src/main/java/seng302/model/token/Token.java @@ -39,4 +39,11 @@ public class Token extends GeoPoint { tokenTypeList.remove(TokenType.RANDOM); tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size())); } + + /** + * Exists for testing purposes only + */ + public void assignType(TokenType tokenType) { + this.tokenType = tokenType; + } } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 9abf282a..9947719e 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -410,8 +410,15 @@ public class GameClient { * @param yachtEventData The YachtEvent data packet */ private void processYachtEvent(YachtEventData yachtEventData) { + ClientYacht thisYacht = allBoatsMap.get(yachtEventData.getSubjectId().intValue()); + if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) { showCollisionAlert(yachtEventData); + } else if (yachtEventData.getEventId() == YachtEventType.POWER_DOWN.getCode()) { + thisYacht.powerDown(); + Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound + } else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) { + // TODO: 23/09/17 notify the client that the yacht has been disabled } else { TokenType tokenType = null; if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) { @@ -427,7 +434,7 @@ public class GameClient { } showTokenPickUp(tokenType); - allBoatsMap.get(yachtEventData.getSubjectId().intValue()).setPowerUp(tokenType); + thisYacht.setPowerUp(tokenType); } } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index e45dd091..1819ff48 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -48,6 +48,7 @@ import javafx.scene.shape.Polyline; import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.stage.StageStyle; +import javax.swing.ImageIcon; import seng302.model.ClientYacht; import seng302.model.ClientYacht.PowerUpListener; import seng302.model.RaceState; @@ -140,6 +141,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private JFXDialog finishScreenDialog; private FinishDialogController finishDialogController; + //Icon stuff + private Timer blinkingTimer = new Timer(); + private ImageView iconToDisplay; + public void initialize() { Sounds.stopMusic(); Sounds.playRaceMusic(); @@ -253,6 +258,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel }); player.addPowerUpListener(this::displayPowerUpIcon); + player.addPowerDownListener(this::removeIcon); updateOrder(raceState.getPlayerPositions()); gameView = new GameView3D(); @@ -301,7 +307,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel */ private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) { if (yacht == player) { - final ImageView iconToDisplay; switch (tokenType) { case BOOST: @@ -324,7 +329,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel iconToDisplay.setVisible(true); //Start blinking icon towards end - Timer blinkingTimer = new Timer(); + if (blinkingTimer != null) { + blinkingTimer.cancel(); + } + blinkingTimer = new Timer("Blinking Timer"); blinkingTimer.schedule(new TimerTask() { Boolean isVisible = true; @@ -334,16 +342,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel iconToDisplay.setVisible(isVisible); } }, (int) (tokenType.getTimeout() * ICON_BLINK_TIMEOUT_RATIO), ICON_BLINK_PERIOD); + } + } - //Turn icon off after the time out - Timer switchOffTimer = new Timer(); - switchOffTimer.schedule(new TimerTask() { - @Override - public void run() { - blinkingTimer.cancel(); - iconToDisplay.setVisible(false); - } - }, tokenType.getTimeout()); + public void removeIcon(ClientYacht yacht) { + if (yacht == player) { + blinkingTimer.cancel(); + iconToDisplay.setVisible(false); } } From c47e5b145019010b5a29629aaaa61a4fa34bc89c Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 23 Sep 2017 16:31:18 +1200 Subject: [PATCH 07/33] Boat now changes color when it is bumped for a time ClientYacht: Added ColorChangeListener from GameView3D to re paint the boat when the color attribute is changed #story[1293] --- .../java/seng302/gameServer/GameState.java | 11 +++--- src/main/java/seng302/model/ClientYacht.java | 24 +++++++++++-- .../java/seng302/visualiser/GameClient.java | 34 ++++++++++++++----- .../java/seng302/visualiser/GameView3D.java | 24 ++++++++++--- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 496c7cb9..150fd652 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -60,7 +60,7 @@ public class GameState implements Runnable { //Powerup Constants private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; private static final Integer HANDLING_BOOST_MULTIPLIER = 2; - private static final Long BUMPER_DISABLE_TIME = 5_000L; + public static final Long BUMPER_DISABLE_TIME = 5_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -359,8 +359,8 @@ public class GameState implements Runnable { //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); -// token.assignRandomType(); - token.assignType(TokenType.BUMPER); + token.assignRandomType(); +// token.assignType(TokenType.BUMPER); logger.debug("Spawned token of type " + token.getTokenType()); @@ -386,8 +386,7 @@ public class GameState implements Runnable { updateVelocity(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); - preformTokenUpdates( - yacht); //This update must be done before collision. Sorry sorta hacky rn. + preformTokenUpdates(yacht); //This update must be done before collision. Sorta hacky checkCollision(yacht); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { checkForLegProgression(yacht); @@ -424,7 +423,7 @@ public class GameState implements Runnable { boatTempShutDown(collidedYacht); notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); notifyMessageListeners( - MessageFactory.makeStatusEffectMessage(yacht, powerUp)); + MessageFactory.makeStatusEffectMessage(collidedYacht, powerUp)); } break; } diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java index f626db30..0b5449ae 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -39,17 +39,21 @@ public class ClientYacht extends Observable { void notifyRounding(ClientYacht yacht, int legNumber); } + @FunctionalInterface + public interface ColorChangeListener { + + void notifyColorChange(ClientYacht yacht); + } + //This notifies RaceViewController so it can display icon - wmu16 @FunctionalInterface public interface PowerUpListener { - void notifyPowerUp(ClientYacht yacht, TokenType tokenType); } //This notifies RaceViewController so it can remove token icon - wmu16 @FunctionalInterface public interface PowerDownListener { - void notifyPowerDown(ClientYacht yacht); } @@ -82,6 +86,7 @@ public class ClientYacht extends Observable { private List markRoundingListeners = new ArrayList<>(); private List powerUpListeners = new ArrayList<>(); private List powerDownListeners = new ArrayList<>(); + private List colorChangeListeners = new ArrayList<>(); private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); @@ -224,6 +229,9 @@ public class ClientYacht extends Observable { this.position = position; } + /** + * Powers down the boat and notifies the raceViewController to display + */ public void powerDown() { this.powerUp = null; for (PowerDownListener listener : powerDownListeners) { @@ -231,6 +239,11 @@ public class ClientYacht extends Observable { } } + /** + * powers up the boat and notifies the raceViewController to display + * + * @param tokenType The type of token that this boat is being powered up with + */ public void setPowerUp(TokenType tokenType) { this.powerUp = tokenType; for (PowerUpListener listener : powerUpListeners) { @@ -290,6 +303,9 @@ public class ClientYacht extends Observable { public void setColour(Color colour) { this.colour = colour; + for (ColorChangeListener listener : colorChangeListeners) { + listener.notifyColorChange(this); + } } @@ -319,6 +335,10 @@ public class ClientYacht extends Observable { powerDownListeners.add(listener); } + public void addColorChangeListener(ColorChangeListener listener) { + colorChangeListeners.add(listener); + } + public void removeMarkRoundingListener(MarkRoundingListener listener) { markRoundingListeners.remove(listener); } diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 9947719e..0e4b4a7a 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -10,6 +10,8 @@ import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -19,6 +21,7 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; import javafx.util.Pair; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; @@ -413,12 +416,12 @@ public class GameClient { ClientYacht thisYacht = allBoatsMap.get(yachtEventData.getSubjectId().intValue()); if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) { - showCollisionAlert(yachtEventData); + showCollisionAlert(thisYacht); } else if (yachtEventData.getEventId() == YachtEventType.POWER_DOWN.getCode()) { thisYacht.powerDown(); Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound } else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) { - // TODO: 23/09/17 notify the client that the yacht has been disabled + showDisableAlert(thisYacht); } else { TokenType tokenType = null; if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) { @@ -438,16 +441,31 @@ public class GameClient { } } + + /** + * Turns a disabled boat black until the bumper affect wears off + * + * @param yacht The yacht to show as disabled + */ + private void showDisableAlert(ClientYacht yacht) { + Color originalColor = yacht.getColour(); + yacht.setColour(Color.BLACK); + + Timer disableTimer = new Timer("Disable Timer"); + disableTimer.schedule(new TimerTask() { + @Override + public void run() { + yacht.setColour(originalColor); + } + }, GameState.BUMPER_DISABLE_TIME); + } + /** * Tells race view to show a collision animation. */ - private void showCollisionAlert(YachtEventData yachtEventData) { + private void showCollisionAlert(ClientYacht yacht) { Sounds.playCrashSound(); - raceState.storeCollision( - allBoatsMap.get( - yachtEventData.getSubjectId().intValue() - ) - ); + raceState.storeCollision(yacht); } // TODO: 11/09/17 wmu16 - Add in functionality to viually indicate a pickup to a user diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index 486f0717..3c662dfb 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -467,11 +467,8 @@ public class GameView3D { wakesGroup.getChildren().add(newBoat.getWake()); wakes.add(newBoat.getWake()); boatObjectGroup.getChildren().add(newBoat); - clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> { - BoatObject bo = boatObjects.get(boat); - Point2D p2d = findScaledXY(lat, lon); - bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); - }); + clientYacht.addLocationListener(this::updateBoatLocation); + clientYacht.addColorChangeListener(this::updateBoatColor); } Platform.runLater(() -> { gameObjects.getChildren().addAll(wakes); @@ -483,6 +480,23 @@ public class GameView3D { return view; } + /** + * Updates the boatObjects color with that of the clientYachts object. Used in notification from + * a listener on this attribute in clientYacht to re paint the boat mesh + * + * @param clientYacht The yacht to update the colour for + */ + private void updateBoatColor(ClientYacht clientYacht) { + boatObjects.get(clientYacht).setFill(clientYacht.getColour()); + } + + private void updateBoatLocation(ClientYacht boat, Double lat, Double lon, Double heading, + Boolean sailIn, Double velocity) { + BoatObject bo = boatObjects.get(boat); + Point2D p2d = findScaledXY(lat, lon); + bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); + } + /** * Adds a border to the GameView and rescales to the size of the border, does not rescale if a * border already exists. Assumes the border is larger than the course. From 78259f8e3327d7fe08528c694ee9cbf819e58f0a Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 23 Sep 2017 16:54:03 +1200 Subject: [PATCH 08/33] Boat now changes color when it is bumped for a time ClientYacht: Added ColorChangeListener from GameView3D to re paint the boat when the color attribute is changed #story[1293] --- .../java/seng302/visualiser/GameClient.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 0e4b4a7a..061b593a 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -422,7 +422,7 @@ public class GameClient { Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound } else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) { showDisableAlert(thisYacht); - } else { + } else { //Else all token pickup types TokenType tokenType = null; if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) { tokenType = TokenType.BOOST; @@ -436,7 +436,7 @@ public class GameClient { tokenType = TokenType.WIND_WALKER; } - showTokenPickUp(tokenType); + Sounds.playTokenPickupSound(); thisYacht.setPowerUp(tokenType); } } @@ -468,21 +468,6 @@ public class GameClient { raceState.storeCollision(yacht); } - // TODO: 11/09/17 wmu16 - Add in functionality to viually indicate a pickup to a user - private void showTokenPickUp(TokenType tokenType) { - Sounds.playTokenPickupSound(); - switch (tokenType) { - case BOOST: - break; - case HANDLING: - break; - case WIND_WALKER: - break; - case BUMPER: - break; - } - } - private void formatAndSendChatMessage(String rawChat) { if (rawChat.length() > 0) { socketThread.sendChatterMessage( From 29b97a194dca9385ac75be56db3e526a2dcf77f7 Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 25 Sep 2017 11:26:44 +1300 Subject: [PATCH 09/33] Merged dev back on #story[1293] --- .../java/seng302/gameServer/GameState.java | 24 +++++---- src/main/java/seng302/model/ServerYacht.java | 54 +++++++++---------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 14550218..5943625d 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -433,7 +433,7 @@ public class GameState implements Runnable { * @param yacht The yacht to disable */ private void boatTempShutDown(ServerYacht yacht) { - yacht.setSpeedMultiplier(0); + yacht.setPowerUpSpeedMultiplier(0); Timer shutDownTimer = new Timer("Shutdown Timer"); shutDownTimer.schedule(new TimerTask() { @Override @@ -542,13 +542,13 @@ public class GameState implements Runnable { TokenType tokenType = collidedToken.getTokenType(); switch (tokenType) { case BOOST: - yacht.setSpeedMultiplier(VELOCITY_BOOST_MULTIPLIER); + yacht.setPowerUpSpeedMultiplier(VELOCITY_BOOST_MULTIPLIER); break; case BUMPER: // TODO: 22/09/17 wmu16 break; case HANDLING: - yacht.setHandlingMultiplier(HANDLING_BOOST_MULTIPLIER); + yacht.setPowerUpHandlingMultiplier(HANDLING_BOOST_MULTIPLIER); break; case WIND_WALKER: // TODO: 22/09/17 wmu16 @@ -634,23 +634,29 @@ public class GameState implements Runnable { Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); Double maxBoatSpeed = - GeoUtility.knotsToMMS(boatSpeedInKnots) * serverSpeedMultiplier * yacht.getSpeedMultiplier() * yacht.getMaxSpeedMultiplier(); + GeoUtility.knotsToMMS(boatSpeedInKnots) * serverSpeedMultiplier * yacht + .getPowerUpSpeedMultiplier() * yacht.getBoatTypeSpeedMultiplier(); Double currentVelocity = yacht.getCurrentVelocity(); // TODO: 15/08/17 remove magic numbers from these equations. if (yacht.getSailIn()) { if (currentVelocity < maxBoatSpeed - 500) { - yacht.changeVelocity((maxBoatSpeed / 100) * yacht.getAccelerationMultiplier()); + yacht.changeVelocity( + (maxBoatSpeed / 100) * yacht.getBoatTypeAccelerationMultiplier()); } else if (currentVelocity > maxBoatSpeed + 500) { - yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier()); + yacht.changeVelocity( + (-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier()); } else { - yacht.setCurrentVelocity((maxBoatSpeed) * yacht.getAccelerationMultiplier()); + yacht + .setCurrentVelocity((maxBoatSpeed) * yacht.getBoatTypeAccelerationMultiplier()); } } else { if (currentVelocity > 3000) { - yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier()); + yacht.changeVelocity( + (-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier()); } else if (currentVelocity > 100) { - yacht.changeVelocity((-currentVelocity / 50) * yacht.getAccelerationMultiplier()); + yacht.changeVelocity( + (-currentVelocity / 50) * yacht.getBoatTypeAccelerationMultiplier()); } else if (currentVelocity <= 100) { yacht.setCurrentVelocity(0d); } diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index d1f36c7c..ee692d63 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -11,10 +11,6 @@ import seng302.utilities.GeoUtility; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; import java.util.HashMap; -import java.util.Objects; -import java.util.Observable; -import java.util.Observer; -import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; /** * Yacht class for the racing boat.

Class created to store more variables (eg. boat statuses) @@ -28,9 +24,9 @@ public class ServerYacht { //Boat info private BoatMeshType boatType; private Double turnStep = 5.0; - private Double maxSpeedMultiplier = 1.0; - private Double turnStepMultiplier = 1.0; - private Double accelerationMultiplier = 1.0; + private Double boatTypeSpeedMultiplier = 1.0; + private Double boatTypeTurnStepMultiplier = 1.0; + private Double boatTypeAccelerationMultiplier = 1.0; private Integer sourceId; private String hullID; //matches HullNum in the XML spec. private String shortName; @@ -60,8 +56,8 @@ public class ServerYacht { //PowerUp private TokenType powerUp; private Long powerUpStartTime; - private Integer speedMultiplier; - private Integer handlingMultiplier; + private Integer powerUpSpeedMultiplier; + private Integer powerUpHandlingMultiplier; public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName, @@ -83,8 +79,8 @@ public class ServerYacht { this.legNumber = 0; this.boatColor = Colors.getColor(sourceId - 1); this.powerUp = null; - this.speedMultiplier = 1; - this.handlingMultiplier = 1; + this.powerUpSpeedMultiplier = 1; + this.powerUpHandlingMultiplier = 1; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; @@ -122,8 +118,8 @@ public class ServerYacht { public void powerDown() { this.powerUp = null; - this.speedMultiplier = 1; - this.handlingMultiplier = 1; + this.powerUpSpeedMultiplier = 1; + this.powerUpHandlingMultiplier = 1; } public Long getPowerUpStartTime() { @@ -140,7 +136,7 @@ public class ServerYacht { * @param amount the amount by which to adjust the boat heading. */ public void adjustHeading(Double amount) { - Double newVal = heading + amount * handlingMultiplier * turnStepMultiplier; + Double newVal = heading + amount * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier; lastHeading = heading; heading = (double) Math.floorMod(newVal.longValue(), 360L); } @@ -433,18 +429,18 @@ public class ServerYacht { } public void setBoatType(BoatMeshType boatType) { - this.accelerationMultiplier = boatType.accelerationMultiplier; - this.maxSpeedMultiplier = boatType.maxSpeedMultiplier; - this.turnStepMultiplier = boatType.turnStep; + this.boatTypeAccelerationMultiplier = boatType.accelerationMultiplier; + this.boatTypeSpeedMultiplier = boatType.maxSpeedMultiplier; + this.boatTypeTurnStepMultiplier = boatType.turnStep; this.boatType = boatType; } - public Double getMaxSpeedMultiplier() { - return maxSpeedMultiplier; + public Double getBoatTypeSpeedMultiplier() { + return boatTypeSpeedMultiplier; } - public Double getAccelerationMultiplier(){ - return accelerationMultiplier; + public Double getBoatTypeAccelerationMultiplier() { + return boatTypeAccelerationMultiplier; } @@ -452,19 +448,19 @@ public class ServerYacht { return boatType; } - public Integer getSpeedMultiplier() { - return speedMultiplier; + public Integer getPowerUpSpeedMultiplier() { + return powerUpSpeedMultiplier; } - public void setSpeedMultiplier(Integer speedMultiplier) { - this.speedMultiplier = speedMultiplier; + public void setPowerUpSpeedMultiplier(Integer powerUpSpeedMultiplier) { + this.powerUpSpeedMultiplier = powerUpSpeedMultiplier; } - public Integer getHandlingMultiplier() { - return handlingMultiplier; + public Integer getPowerUpHandlingMultiplier() { + return powerUpHandlingMultiplier; } - public void setHandlingMultiplier(Integer handlingMultiplier) { - this.handlingMultiplier = handlingMultiplier; + public void setPowerUpHandlingMultiplier(Integer powerUpHandlingMultiplier) { + this.powerUpHandlingMultiplier = powerUpHandlingMultiplier; } } From 0211f2df38384c61a5e6c3967c0e74fd25d0f290 Mon Sep 17 00:00:00 2001 From: William Muir Date: Mon, 25 Sep 2017 17:38:09 +1300 Subject: [PATCH 10/33] Merged dev back on #story[1293] --- .../java/seng302/gameServer/GameState.java | 2 + src/main/resources/views/RaceView.fxml | 553 +++++++++--------- 2 files changed, 277 insertions(+), 278 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index fe603783..a8a4c642 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -8,6 +8,8 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import javafx.scene.paint.Color; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index 03728bde..9a02f517 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -24,285 +24,282 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + \ No newline at end of file From 7a4cdbe0c9b0c47ec46be06391bcb57b2833e617 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 26 Sep 2017 11:12:29 +1300 Subject: [PATCH 11/33] Fixed WindWalker #story[1293] --- .../java/seng302/gameServer/GameState.java | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index a8a4c642..a7026682 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -81,6 +81,7 @@ public class GameState implements Runnable { private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; private static final Integer HANDLING_BOOST_MULTIPLIER = 2; public static final Long BUMPER_DISABLE_TIME = 5_000L; + private static final Long TOKEN_SPAWN_TIME = 15_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -297,7 +298,7 @@ public class GameState implements Runnable { spawnNewToken(); notifyMessageListeners(MessageFactory.getRaceXML()); } - }, 0, 15_000); + }, 0, TOKEN_SPAWN_TIME); } // TODO: 29/08/17 wmu16 - This sort of update should be in game state @@ -382,7 +383,7 @@ public class GameState implements Runnable { //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); token.assignRandomType(); -// token.assignType(TokenType.BUMPER); + token.assignType(TokenType.WIND_WALKER); logger.debug("Spawned token of type " + token.getTokenType()); @@ -508,21 +509,7 @@ public class GameState implements Runnable { } Double heading = yacht.getHeading(); - if (heading < windDirection) { - Double diff = Math.abs(optimalAngle - (windDirection - heading)); - if (windDirection - heading < optimalAngle) { - windDirection = (double) Math.floorMod(Math.round(windDirection + diff), 360L); - } else { - windDirection = (double) Math.floorMod(Math.round(windDirection - diff), 360L); - } - } else { - Double diff = Math.abs(optimalAngle - (heading - windDirection)); - if (heading - windDirection < optimalAngle) { - windDirection = (double) Math.floorMod(Math.round(windDirection - diff), 360L); - } else { - windDirection = (double) Math.floorMod(Math.round(windDirection + diff), 360L); - } - } + windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L); } From 1bd4db73cd3d30e5e19d83e44c9f4b7bf3b3994c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 26 Sep 2017 14:44:37 +1300 Subject: [PATCH 12/33] Issue 66: client side error pop ups use default javaFx style - created JFeonix style pop up to replace the default one tags: #story[1273] --- .../java/seng302/visualiser/GameClient.java | 13 +++-- .../visualiser/controllers/ViewManager.java | 21 +++++++ .../dialogs/PopupDialogController.java | 56 +++++++++++++++++++ src/main/resources/css/dialogs/Popup.css | 33 +++++++++++ .../resources/views/dialogs/PopupDialog.fxml | 47 ++++++++++++++++ 5 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 src/main/java/seng302/visualiser/controllers/dialogs/PopupDialogController.java create mode 100644 src/main/resources/css/dialogs/Popup.css create mode 100644 src/main/resources/views/dialogs/PopupDialog.fxml diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index ad3a37d6..9d64b50d 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -14,8 +14,6 @@ import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; @@ -46,6 +44,7 @@ import seng302.utilities.XMLParser; import seng302.visualiser.controllers.LobbyController; import seng302.visualiser.controllers.RaceViewController; import seng302.visualiser.controllers.ViewManager; +import seng302.visualiser.controllers.dialogs.PopupDialogController; /** * This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated @@ -169,10 +168,12 @@ public class GameClient { private void showConnectionError (String message) { Platform.runLater(() -> { - Alert alert = new Alert(AlertType.ERROR); - alert.setHeaderText("Connection Error"); - alert.setContentText(message); - alert.showAndWait(); + PopupDialogController controller = ViewManager.getInstance().showPopupDialog(); + controller.setHeader("Oops"); + controller.setContent(message); + controller.setOptionButtonText("GO HOME"); + controller + .setOptionButtonEventHandler(event -> ViewManager.getInstance().goToStartView()); }); } diff --git a/src/main/java/seng302/visualiser/controllers/ViewManager.java b/src/main/java/seng302/visualiser/controllers/ViewManager.java index 8f98824c..5e3d6748 100644 --- a/src/main/java/seng302/visualiser/controllers/ViewManager.java +++ b/src/main/java/seng302/visualiser/controllers/ViewManager.java @@ -28,6 +28,7 @@ import seng302.utilities.BonjourInstallChecker; import seng302.utilities.Sounds; import seng302.visualiser.GameClient; import seng302.visualiser.controllers.dialogs.KeyBindingDialogController; +import seng302.visualiser.controllers.dialogs.PopupDialogController; public class ViewManager { @@ -229,6 +230,26 @@ public class ViewManager { keyBindingDialog.close(); } + public PopupDialogController showPopupDialog() { + FXMLLoader dialogContent = new FXMLLoader( + getClass().getResource("/views/dialogs/PopupDialog.fxml")); + for (Node node : decorator.getChildren()) { + if (node instanceof StackPane) { + try { + JFXDialog dialog = new JFXDialog((StackPane) node, dialogContent.load(), + DialogTransition.CENTER); + PopupDialogController popupDialogController = dialogContent.getController(); + popupDialogController.setPopupDialog(dialog); + dialog.show(); + return popupDialogController; + } catch (IOException e) { + logger.error("Cannot load Popup dialog"); + } + } + } + return null; + } + /** * Show a snackbar at the bottom of the app for 1 second. * diff --git a/src/main/java/seng302/visualiser/controllers/dialogs/PopupDialogController.java b/src/main/java/seng302/visualiser/controllers/dialogs/PopupDialogController.java new file mode 100644 index 00000000..6d294e50 --- /dev/null +++ b/src/main/java/seng302/visualiser/controllers/dialogs/PopupDialogController.java @@ -0,0 +1,56 @@ +package seng302.visualiser.controllers.dialogs; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialog; +import java.net.URL; +import java.util.ResourceBundle; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; + +public class PopupDialogController implements Initializable { + + @FXML + private Label headerLabel; + @FXML + private Label contentLabel; + @FXML + private Label closeLabel; + @FXML + private JFXButton optionButton; + + @FXML + private JFXDialog popupDialog; + + @Override + public void initialize(URL location, ResourceBundle resources) { + + } + + public void setContent(String content) { + this.contentLabel.setText(content); + } + + public void setHeader(String header) { + this.headerLabel.setText(header); + } + + public void setOptionButton(JFXButton jfxButton) { + this.optionButton = jfxButton; + } + + public void setOptionButtonText(String text) { + this.optionButton.setText(text); + } + + public void setOptionButtonEventHandler(EventHandler eventHandler) { + this.optionButton.setOnMouseClicked(eventHandler); + } + + public void setPopupDialog(JFXDialog popupDialog) { + this.popupDialog = popupDialog; + this.closeLabel.setOnMouseClicked(event -> this.popupDialog.close()); + } +} diff --git a/src/main/resources/css/dialogs/Popup.css b/src/main/resources/css/dialogs/Popup.css new file mode 100644 index 00000000..d0dd364b --- /dev/null +++ b/src/main/resources/css/dialogs/Popup.css @@ -0,0 +1,33 @@ +#headerLabel { + -fx-font-size: 20px; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#closeLabel { + -fx-font-size: 22px; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#closeLabel:hover { + -fx-font-size: 24px; + -fx-text-fill: red; +} + +#contentLabel { + -fx-font-size: 22px; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#optionButton { + -fx-background-color: -fx-pp-theme-color; + -fx-text-fill: -fx-pp-light-text-color; + -fx-font-size: 18px; + -fx-effect: -fx-pp-dropshadow-light; + -fx-max-height: 55; + -fx-focus-traversable: false; +} + +#optionButton:hover { + -fx-font-size: 20px !important; + -fx-background-color: -fx-pp-light-theme-color; +} \ No newline at end of file diff --git a/src/main/resources/views/dialogs/PopupDialog.fxml b/src/main/resources/views/dialogs/PopupDialog.fxml new file mode 100644 index 00000000..00574bc4 --- /dev/null +++ b/src/main/resources/views/dialogs/PopupDialog.fxml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2e7487fdfcff2de498a66aa746dad68378bb2dec Mon Sep 17 00:00:00 2001 From: alistairjmcintyre Date: Tue, 26 Sep 2017 15:06:21 +1300 Subject: [PATCH 13/33] - Chase Camera: - Has panning bounds, zoom bounds, and general tidy up. - Now correctly observes the boat object rather than getting information from both the boat object AND the client yacht model. Top Down Camera: - Can only pan within certain bounds now, and will continue to follow the boat regardless. - Can only zoom within certain bounds now. Isometric Camera: - Nothing changed. #tags [1273] --- .../java/seng302/visualiser/GameView3D.java | 68 ++++++++------- .../visualiser/cameras/ChaseCamera.java | 66 ++++++-------- .../visualiser/cameras/IsometricCamera.java | 7 ++ .../visualiser/cameras/TopDownCamera.java | 86 +++++++++++-------- .../fxObjects/assets_3D/BoatObject.java | 9 ++ 5 files changed, 130 insertions(+), 106 deletions(-) diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index 78e43b9a..5094e41c 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -1,6 +1,7 @@ package seng302.visualiser; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -9,6 +10,7 @@ import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.geometry.Point2D; import javafx.geometry.Point3D; +import javafx.scene.Camera; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.PerspectiveCamera; @@ -53,12 +55,13 @@ public class GameView3D { private Group root3D; private SubScene view; - // ParallelCamera camera; - private PerspectiveCamera camera; - private PerspectiveCamera camera2; - private PerspectiveCamera camera3; private Group gameObjects; + // Cameras + private PerspectiveCamera isometricCam; + private PerspectiveCamera topDownCam; + private PerspectiveCamera chaseCam; + private double bufferSize = 0; private double canvasWidth = 200; private double canvasHeight = 200; @@ -93,28 +96,25 @@ public class GameView3D { } public GameView3D () { - camera = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH); - camera.setFarClip(600); - camera.setNearClip(0.1); - camera.setFieldOfView(FOV); + isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, + DEFAULT_CAMERA_DEPTH); + topDownCam = new TopDownCamera(); + chaseCam = new ChaseCamera(); - camera2 = new TopDownCamera(); - camera2.setFarClip(600); - camera2.setNearClip(0.1); - camera2.setFieldOfView(FOV); - - camera3 = new ChaseCamera(); - camera3.setFarClip(600); - camera3.setNearClip(0.1); - camera3.setFieldOfView(FOV); + for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) { + pc.setFarClip(600); + pc.setNearClip(0.1); + pc.setFieldOfView(FOV); + } gameObjects = new Group(); - root3D = new Group(camera, gameObjects); + root3D = new Group(isometricCam, gameObjects); view = new SubScene( root3D, 1000, 1000, true, SceneAntialiasing.BALANCED ); - view.setCamera(camera); - camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0))); + view.setCamera(isometricCam); + isometricCam.getTransforms() + .add(new Rotate(30, new Point3D(1, 0, 0))); //todo: move this into isometric cam? gameObjects.getChildren().addAll( ModelFactory.importModel(ModelType.OCEAN).getAssets(), @@ -449,16 +449,20 @@ public class GameView3D { ((RaceCamera) view.getCamera()).panRight(); break; case F1: - if (view.getCamera().equals(camera)) { - view.setCamera(camera2); - if (view.getCamera() instanceof TopDownCamera) { - ((RaceCamera) view.getCamera()).zoomIn(); - } - } else if (view.getCamera().equals(camera2)) { - view.setCamera(camera3); - } else { - view.setCamera(camera); - } + toggleCamera(); + break; + } + } + + private void toggleCamera() { + Camera currCamera = view.getCamera(); + + if (currCamera.equals(isometricCam)) { + view.setCamera(topDownCam); + } else if (currCamera.equals(topDownCam)) { + view.setCamera(chaseCam); + } else { + view.setCamera(isometricCam); } } @@ -498,8 +502,8 @@ public class GameView3D { if (clientYacht.getSourceId().equals( ViewManager.getInstance().getGameClient().getServerThread().getClientId())) { - ((ChaseCamera) camera3).setPlayerBoat(newBoat, clientYacht); - ((TopDownCamera) camera2).setPlayerBoat(newBoat); + ((ChaseCamera) chaseCam).setPlayerBoat(newBoat); + ((TopDownCamera) topDownCam).setPlayerBoat(newBoat); } } Platform.runLater(() -> { diff --git a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java index 6c777f77..daddfaca 100644 --- a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java +++ b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java @@ -1,22 +1,25 @@ package seng302.visualiser.cameras; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; +import java.util.Arrays; +import javafx.beans.property.DoubleProperty; import javafx.collections.ObservableList; import javafx.geometry.Point3D; import javafx.scene.PerspectiveCamera; import javafx.scene.transform.Rotate; import javafx.scene.transform.Transform; import javafx.scene.transform.Translate; -import seng302.model.ClientYacht; import seng302.visualiser.fxObjects.assets_3D.BoatObject; public class ChaseCamera extends PerspectiveCamera implements RaceCamera { + private final Double VERTICAL_PAN_LIMIT = 20.0; + private final Double NEAR_ZOOM_LIMIT = -15.0; + private final Double FAR_ZOOM_LIMIT = -125.0; + private ObservableList transforms; private BoatObject playerBoat; - private ClientYacht playerYacht; + private Double zoomFactor; private Double horizontalPan; private Double verticalPan; @@ -25,43 +28,27 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { public ChaseCamera() { super(true); transforms = this.getTransforms(); - this.zoomFactor = -75.0; + + zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0; this.horizontalPan = 0.0; this.verticalPan = 0.0; } - public void setPlayerBoat(BoatObject playerBoat, ClientYacht playerYacht) { + public void setPlayerBoat(BoatObject playerBoat) { this.playerBoat = playerBoat; - this.playerYacht = playerYacht; - this.playerYacht.getHeadingProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, - Number newValue) { - repositionCamera(); - } - }); - this.playerBoat.layoutXProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, - Number newValue) { - repositionCamera(); - } - }); - this.playerBoat.layoutYProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, - Number newValue) { - repositionCamera(); - } - }); + for (DoubleProperty o : Arrays + .asList(playerBoat.getRotationProperty(), playerBoat.layoutYProperty(), + playerBoat.layoutXProperty())) { + o.addListener((obs, oldVal, newVal) -> repositionCamera()); + } } private void repositionCamera() { transforms.clear(); transforms.addAll( new Translate(playerBoat.getLayoutX(), playerBoat.getLayoutY(), 0), - new Rotate(playerYacht.getHeadingProperty().getValue() + horizontalPan, + new Rotate(playerBoat.getRotationProperty().getValue() + horizontalPan, new Point3D(0, 0, 1)), new Rotate(60 + verticalPan, new Point3D(1, 0, 0)), new Translate(0, 0, zoomFactor) @@ -69,19 +56,25 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { } private void adjustZoomFactor(Double adjustment) { - if (zoomFactor + adjustment < -15.0 && zoomFactor + adjustment > -125.0) { + if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { zoomFactor = zoomFactor + adjustment; repositionCamera(); } } private void adjustVerticalPan(Double adjustment) { - if (verticalPan + adjustment >= -20 && verticalPan + adjustment <= 20) { + if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT + && verticalPan + adjustment <= VERTICAL_PAN_LIMIT) { verticalPan += adjustment; repositionCamera(); } } + private void adjustHorizontalPan(Double adjustment) { + this.horizontalPan += adjustment; + repositionCamera(); + } + @Override public void zoomIn() { adjustZoomFactor(5.0); @@ -92,21 +85,14 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { adjustZoomFactor(-5.0); } - - /* - These have been left intentionally empty for now. it would be cool to be able to pan around the boat and have the camera move around the boat though. - */ - @Override public void panLeft() { - this.horizontalPan -= 5; - repositionCamera(); + adjustHorizontalPan(-5.0); } @Override public void panRight() { - this.horizontalPan += 5; - repositionCamera(); + adjustHorizontalPan(5.0); } @Override diff --git a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java index a4abeac4..434032b7 100644 --- a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java +++ b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java @@ -7,6 +7,13 @@ import javafx.scene.transform.Translate; public class IsometricCamera extends PerspectiveCamera implements RaceCamera { + private final Double PAN_LIMIT = 50.0; + private final Double NEAR_ZOOM_LIMIT = -15.0; + private final Double FAR_ZOOM_LIMIT = -125.0; + + private Double horizontalAdjustment; + private Double verticalAdjustment; + ObservableList transforms; public IsometricCamera(Double cameraStartX, Double cameraStartY, Double cameraDepth) { diff --git a/src/main/java/seng302/visualiser/cameras/TopDownCamera.java b/src/main/java/seng302/visualiser/cameras/TopDownCamera.java index af17d553..09455345 100644 --- a/src/main/java/seng302/visualiser/cameras/TopDownCamera.java +++ b/src/main/java/seng302/visualiser/cameras/TopDownCamera.java @@ -1,8 +1,8 @@ package seng302.visualiser.cameras; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; +import java.util.Arrays; +import javafx.beans.property.DoubleProperty; import javafx.collections.ObservableList; import javafx.scene.PerspectiveCamera; import javafx.scene.transform.Transform; @@ -11,75 +11,93 @@ import seng302.visualiser.fxObjects.assets_3D.BoatObject; public class TopDownCamera extends PerspectiveCamera implements RaceCamera { + private final Double PAN_LIMIT = 30.0; + private final Double NEAR_ZOOM_LIMIT = -30.0; + private final Double FAR_ZOOM_LIMIT = -130.0; + private final Double ZOOM_STEP = 2.5; + private ObservableList transforms; private BoatObject playerBoat; + private Double zoomFactor; + private Double horizontalPan; + private Double verticalPan; + public TopDownCamera() { super(true); transforms = this.getTransforms(); - transforms.add(new Translate(0, 0, -125)); + + zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0; + horizontalPan = 0.0; + verticalPan = 0.0; } public void setPlayerBoat(BoatObject playerBoat) { this.playerBoat = playerBoat; - this.playerBoat.layoutXProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, - Number newValue) { - updateCameraX((Double) oldValue, (Double) newValue); - } - }); - this.playerBoat.layoutYProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Number oldValue, - Number newValue) { - updateCameraY((Double) oldValue, (Double) newValue); - } - }); - } - - private void updateCameraX(Double oldXValue, Double newXValue) { - if (transforms.size() == 0) { // boat is placed and then moved at start, - transforms.addAll( - new Translate(playerBoat.getLayoutX(), playerBoat.getLayoutY(), -125) - ); - } else { - transforms.addAll(new Translate(newXValue - oldXValue, 0, 0)); + for (DoubleProperty o : Arrays + .asList(playerBoat.layoutXProperty(), playerBoat.layoutYProperty())) { + o.addListener((obs, oldVal, newVal) -> updateCamera()); } } - private void updateCameraY(Double oldYValue, Double newYValue) { - transforms.addAll(new Translate(0, (newYValue - oldYValue), 0)); + private void updateCamera() { + transforms.clear(); + transforms.addAll( + new Translate(playerBoat.getLayoutX() + horizontalPan, + playerBoat.getLayoutY() + verticalPan, zoomFactor) + ); + } + + private void adjustZoomFactor(Double adjustment) { + if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { + zoomFactor = zoomFactor + adjustment; + updateCamera(); + } + } + + private void adjustVerticalPan(Double adjustment) { + if (verticalPan + adjustment >= -PAN_LIMIT && verticalPan + adjustment <= PAN_LIMIT) { + verticalPan += adjustment; + updateCamera(); + } + } + + private void adjustHorizontalPan(Double adjustment) { + if (horizontalPan + adjustment >= -PAN_LIMIT && horizontalPan + adjustment <= PAN_LIMIT) { + horizontalPan += adjustment; + updateCamera(); + } } @Override public void zoomIn() { - transforms.addAll(new Translate(0, 0, 1.5)); + adjustZoomFactor(ZOOM_STEP); } @Override public void zoomOut() { - transforms.addAll(new Translate(0, 0, -1.5)); + adjustZoomFactor(-ZOOM_STEP); } @Override public void panLeft() { - transforms.addAll(new Translate(-1, 0, 0)); + adjustHorizontalPan(-1.0); } @Override public void panRight() { - transforms.addAll(new Translate(1, 0, 0)); + adjustHorizontalPan(1.0); } @Override public void panUp() { - transforms.addAll(new Translate(0, -1, 0)); + adjustVerticalPan(-1.0); } @Override public void panDown() { - transforms.addAll(new Translate(0, 1, 0)); + adjustVerticalPan(1.0); } + } diff --git a/src/main/java/seng302/visualiser/fxObjects/assets_3D/BoatObject.java b/src/main/java/seng302/visualiser/fxObjects/assets_3D/BoatObject.java index abf969a8..025ab820 100644 --- a/src/main/java/seng302/visualiser/fxObjects/assets_3D/BoatObject.java +++ b/src/main/java/seng302/visualiser/fxObjects/assets_3D/BoatObject.java @@ -3,6 +3,7 @@ package seng302.visualiser.fxObjects.assets_3D; import java.util.ArrayList; import java.util.List; import javafx.application.Platform; +import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.geometry.Point3D; import javafx.scene.Group; import javafx.scene.paint.Color; @@ -30,12 +31,15 @@ public class BoatObject extends Group { private Boolean isSelected = false; private Rotate rotation = new Rotate(0, new Point3D(0,0,1)); + private ReadOnlyDoubleWrapper rotationProperty; + private List selectedBoatListenerListeners = new ArrayList<>(); /** * Creates a BoatGroup with the default triangular boat polygon. */ public BoatObject(BoatMeshType boatMeshType) { + rotationProperty = new ReadOnlyDoubleWrapper(0.0); boatAssets = ModelFactory.boatGameView(boatMeshType, colour); boatAssets.hideSail(); boatAssets.getAssets().getTransforms().addAll( @@ -83,6 +87,7 @@ public class BoatObject extends Group { private void rotateTo(double heading, boolean sailsIn, double windDir) { + rotationProperty.set(heading); rotation.setAngle(heading); wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1))); if (sailsIn) { @@ -130,4 +135,8 @@ public class BoatObject extends Group { public void addSelectedBoatListener(SelectedBoatListener sbl) { selectedBoatListenerListeners.add(sbl); } + + public ReadOnlyDoubleWrapper getRotationProperty() { + return rotationProperty; + } } \ No newline at end of file From 132a729758caf242464b2d9c684a879497040150 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 26 Sep 2017 15:49:16 +1300 Subject: [PATCH 14/33] Temporarily added more options in key binding dialog. But still need to implement binding after camera story has been merged into develop tags: #story[1273] --- src/main/java/seng302/model/GameKeyBind.java | 7 +- src/main/java/seng302/model/KeyAction.java | 7 +- .../dialogs/KeyBindingDialogController.java | 16 +- src/main/resources/css/Master.css | 5 + .../css/dialogs/KeyBindingDialog.css | 9 +- .../views/dialogs/KeyBindingDialog.fxml | 293 ++++++++++++------ 6 files changed, 239 insertions(+), 98 deletions(-) diff --git a/src/main/java/seng302/model/GameKeyBind.java b/src/main/java/seng302/model/GameKeyBind.java index 1c765adc..c25f6202 100644 --- a/src/main/java/seng302/model/GameKeyBind.java +++ b/src/main/java/seng302/model/GameKeyBind.java @@ -30,7 +30,12 @@ public class GameKeyBind { keys.add(KeyCode.ENTER); keys.add(KeyCode.PAGE_UP); keys.add(KeyCode.PAGE_DOWN); - for (int i = 0; i < 7; i++) { + keys.add(KeyCode.F1); + keys.add(KeyCode.D); + keys.add(KeyCode.A); + keys.add(KeyCode.W); + keys.add(KeyCode.S); + for (int i = 0; i < 12; i++) { actionToKeyMap.put(KeyAction.getType(i + 1), keys.get(i)); keyToActionMap.put(keys.get(i), KeyAction.getType(i + 1)); } diff --git a/src/main/java/seng302/model/KeyAction.java b/src/main/java/seng302/model/KeyAction.java index 1b8c2fa1..51509027 100644 --- a/src/main/java/seng302/model/KeyAction.java +++ b/src/main/java/seng302/model/KeyAction.java @@ -10,7 +10,12 @@ public enum KeyAction { SAILS_STATE(4), TACK_GYBE(5), UPWIND(6), - DOWNWIND(7); + DOWNWIND(7), + VIEW(8), + RIGHT(9), + LEFT(10), + FORWARD(11), + BACKWARD(12); private final int type; private static final Map intToTypeMap = new HashMap<>(); diff --git a/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java index 2a286ec4..c650bc59 100644 --- a/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java +++ b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java @@ -48,6 +48,16 @@ public class KeyBindingDialogController implements Initializable { private Label downwindLabel; @FXML private JFXToggleButton turningToggle; + @FXML + private JFXButton viewButton; + @FXML + private JFXButton rightButton; + @FXML + private JFXButton leftButton; + @FXML + private JFXButton forwardButton; + @FXML + private JFXButton backwardButton; //---------FXML END---------// private GameKeyBind gameKeyBind; @@ -60,7 +70,9 @@ public class KeyBindingDialogController implements Initializable { gameKeyBind = GameKeyBind.getInstance(); buttons = new ArrayList<>(); Collections.addAll(buttons, - zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn); + zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn, + viewButton, + rightButton, leftButton, forwardButton, backwardButton); bindButtonWithAction(); loadKeyBind(); @@ -106,7 +118,7 @@ public class KeyBindingDialogController implements Initializable { */ private void bindButtonWithAction() { buttonActionMap = new HashMap<>(); - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 12; i++) { buttonActionMap.put(buttons.get(i), KeyAction.getType(i + 1)); } } diff --git a/src/main/resources/css/Master.css b/src/main/resources/css/Master.css index cb50a645..478d7cf0 100644 --- a/src/main/resources/css/Master.css +++ b/src/main/resources/css/Master.css @@ -51,6 +51,11 @@ /********* customised scroll bar for scroll pane ***********/ +.scroll-pane { + -fx-focus-traversable: false; + -fx-border-style: none; +} + /* The main scrollbar **track** CSS class */ .scroll-bar:horizontal .track, .scroll-bar:vertical .track { diff --git a/src/main/resources/css/dialogs/KeyBindingDialog.css b/src/main/resources/css/dialogs/KeyBindingDialog.css index 8d09e130..6c7bff26 100644 --- a/src/main/resources/css/dialogs/KeyBindingDialog.css +++ b/src/main/resources/css/dialogs/KeyBindingDialog.css @@ -9,8 +9,13 @@ } #closeLabel:hover { - -fx-text-fill: -fx-pp-theme-color; - -fx-font-size: 33; + -fx-text-fill: red; + -fx-font-size: 33px; +} + +.sectionLabel { + -fx-text-fill: -fx-pp-dark-text-color; + -fx-font-size: 20px; } JFXButton { diff --git a/src/main/resources/views/dialogs/KeyBindingDialog.fxml b/src/main/resources/views/dialogs/KeyBindingDialog.fxml index 4a677797..7fcf2e7f 100644 --- a/src/main/resources/views/dialogs/KeyBindingDialog.fxml +++ b/src/main/resources/views/dialogs/KeyBindingDialog.fxml @@ -6,117 +6,226 @@ + + + + + + - - + - - - - - - - - - - + + - - - - - From 1a53579317af7868e6cd806bff3ac2478e6233d3 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 26 Sep 2017 16:57:07 +1300 Subject: [PATCH 15/33] - Isometric Camera - Zoom Bounds added. - Camera Panning Bounds added. Need to be tested with extra maps. tags : #story[1273] --- .../java/seng302/visualiser/GameView3D.java | 6 +- .../visualiser/cameras/IsometricCamera.java | 75 +++++++++++++++---- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index 5094e41c..a7633b3f 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -49,7 +49,6 @@ import seng302.visualiser.fxObjects.assets_3D.ModelType; public class GameView3D { private final double FOV = 60; - private final double DEFAULT_CAMERA_DEPTH = -125; private final double DEFAULT_CAMERA_X = 0; private final double DEFAULT_CAMERA_Y = 155; @@ -96,8 +95,7 @@ public class GameView3D { } public GameView3D () { - isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, - DEFAULT_CAMERA_DEPTH); + isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y); topDownCam = new TopDownCamera(); chaseCam = new ChaseCamera(); @@ -113,8 +111,6 @@ public class GameView3D { root3D, 1000, 1000, true, SceneAntialiasing.BALANCED ); view.setCamera(isometricCam); - isometricCam.getTransforms() - .add(new Rotate(30, new Point3D(1, 0, 0))); //todo: move this into isometric cam? gameObjects.getChildren().addAll( ModelFactory.importModel(ModelType.OCEAN).getAssets(), diff --git a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java index 434032b7..9814079c 100644 --- a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java +++ b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java @@ -1,54 +1,101 @@ package seng302.visualiser.cameras; import javafx.collections.ObservableList; +import javafx.geometry.Point3D; import javafx.scene.PerspectiveCamera; +import javafx.scene.transform.Rotate; import javafx.scene.transform.Transform; import javafx.scene.transform.Translate; public class IsometricCamera extends PerspectiveCamera implements RaceCamera { - private final Double PAN_LIMIT = 50.0; - private final Double NEAR_ZOOM_LIMIT = -15.0; - private final Double FAR_ZOOM_LIMIT = -125.0; + private final Double MIN_X = -120.0; + private final Double MAX_X = 125.0; - private Double horizontalAdjustment; - private Double verticalAdjustment; + private final Double MIN_Y = 40.0; + private final Double MAX_Y = 170.0; - ObservableList transforms; + private final Double PAN_LIMIT = 160.0; + private final Double NEAR_ZOOM_LIMIT = -50.0; + private final Double FAR_ZOOM_LIMIT = -160.0; - public IsometricCamera(Double cameraStartX, Double cameraStartY, Double cameraDepth) { + private Double horizontalPan; + private Double verticalPan; + private Double zoomFactor; + + private ObservableList transforms; + + public IsometricCamera(Double cameraStartX, Double cameraStartY) { super(true); transforms = this.getTransforms(); - transforms.addAll(new Translate(cameraStartX, cameraStartY, cameraDepth)); + + zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0; + horizontalPan = cameraStartX; + verticalPan = cameraStartY; + + updateCamera(); + } + + private void updateCamera() { + System.out.println("----"); + System.out.println(horizontalPan); + System.out.println(verticalPan); + System.out.println("----"); + transforms.clear(); + transforms.addAll( + new Translate(horizontalPan, verticalPan, zoomFactor), + new Rotate(30, new Point3D(1, 0, 0)) + ); + } + + private void adjustZoomFactor(Double adjustment) { + if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { + zoomFactor = zoomFactor + adjustment; + updateCamera(); + } + } + + private void adjustVerticalPan(Double adjustment) { + if (verticalPan + adjustment >= MIN_Y && verticalPan + adjustment <= MAX_Y) { + verticalPan += adjustment; + updateCamera(); + } + } + + private void adjustHorizontalPan(Double adjustment) { + if (horizontalPan + adjustment >= MIN_X && horizontalPan + adjustment <= MIN_Y) { + this.horizontalPan += adjustment; + updateCamera(); + } } @Override public void zoomIn() { - transforms.addAll(new Translate(0, 0, 1.5)); + adjustZoomFactor(-2.5); } @Override public void zoomOut() { - transforms.addAll(new Translate(0, 0, -1.5)); + adjustZoomFactor(2.5); } @Override public void panLeft() { - transforms.addAll(new Translate(-1, 0, 0)); + adjustHorizontalPan(-2.5); } @Override public void panRight() { - transforms.addAll(new Translate(1, 0, 0)); + adjustHorizontalPan(2.5); } @Override public void panUp() { - transforms.addAll(new Translate(0, -1, 0)); + adjustVerticalPan(-2.5); } @Override public void panDown() { - transforms.addAll(new Translate(0, 1, 0)); + adjustVerticalPan(2.5); } } From dd43097677a37f546706273ff48e17bafab91c13 Mon Sep 17 00:00:00 2001 From: Peter Galloway Date: Tue, 26 Sep 2017 17:07:55 +1300 Subject: [PATCH 16/33] the boat can now turn after snapping to starboard vmg #story[1273] --- src/main/java/seng302/model/ServerYacht.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 5822b248..f7cdaeba 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -156,11 +156,11 @@ public class ServerYacht { /** * Enables the boats auto pilot feature, which will move the boat towards a given heading. * - * @param thisHeading The heading to move the boat towards. + * @param newHeading The heading to move the boat towards. */ - private void setAutoPilot(Double thisHeading) { + private void setAutoPilot(Double newHeading) { isAuto = true; - autoHeading = thisHeading; + autoHeading = newHeading; } /** @@ -178,8 +178,9 @@ public class ServerYacht { if (isAuto) { turnTowardsHeading(autoHeading); if (Math.abs(heading - autoHeading) - <= turnStep) { //Cancel when within 1 turn step of target. + <= turnStep*1.5) { isAuto = false; + setHeading(autoHeading); } } } @@ -265,7 +266,7 @@ public class ServerYacht { // Take optimal heading and turn into a boat heading rather than a wind heading. optimalHeading = - optimalHeading + GameState.getWindDirection(); + (optimalHeading + GameState.getWindDirection()) % 360; setAutoPilot(optimalHeading); } From 8ba44d7476a718a964a56d1ea577861333a29b34 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 26 Sep 2017 17:19:45 +1300 Subject: [PATCH 17/33] Minor commit for testing #story[1293] --- .../java/seng302/gameServer/GameState.java | 4 +-- .../gameServer/ServerToClientThread.java | 32 ++----------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index a7026682..ec761931 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -382,8 +382,8 @@ public class GameState implements Runnable { //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); - token.assignRandomType(); - token.assignType(TokenType.WIND_WALKER); +// token.assignRandomType(); + token.assignType(TokenType.BUMPER); logger.debug("Spawned token of type " + token.getTokenType()); diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index eb5a9ad0..09923b57 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -78,7 +78,6 @@ public class ServerToClientThread implements Runnable { private List connectionListeners = new ArrayList<>(); private DisconnectListener disconnectListener; - private ServerYacht yacht; private Player player; public ServerToClientThread(Socket socket) { @@ -101,33 +100,12 @@ public class ServerToClientThread implements Runnable { } private void setUpPlayer(){ - BufferedReader fn; - String fName = ""; - BufferedReader ln; - String lName = ""; + String shortName = "p" + sourceId; + String longName = "player " + sourceId; - fn = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_First_Names.csv" - ) - ) - ); - List all = fn.lines().collect(Collectors.toList()); - fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); - ln = new BufferedReader( - new InputStreamReader( - ServerToClientThread.class.getResourceAsStream( - "/server_config/CSV_Database_of_Last_Names.csv" - ) - ) - ); - all = ln.lines().collect(Collectors.toList()); - lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size())); ServerYacht yacht = new ServerYacht( - BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" - ); + BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "NZ"); player = new Player(socket, yacht); GameState.addYacht(sourceId, yacht); @@ -318,10 +296,6 @@ public class ServerToClientThread implements Runnable { return socket; } - public ServerYacht getYacht() { - return yacht; - } - public void addConnectionListener(ConnectionListener listener) { connectionListeners.add(listener); } From 66e6a8a2a4fce06ffcd4f24df8c8d774ec62a00e Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 26 Sep 2017 17:35:36 +1300 Subject: [PATCH 18/33] - Small Changes tags : #story[1273] --- .../seng302/visualiser/cameras/ChaseCamera.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java index daddfaca..fddd1810 100644 --- a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java +++ b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java @@ -17,6 +17,9 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { private final Double NEAR_ZOOM_LIMIT = -15.0; private final Double FAR_ZOOM_LIMIT = -125.0; + private final Double ZOOM_STEP = 2.5; + private final Double PAN_STEP = 2.5; + private ObservableList transforms; private BoatObject playerBoat; @@ -77,31 +80,31 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { @Override public void zoomIn() { - adjustZoomFactor(5.0); + adjustZoomFactor(ZOOM_STEP); } @Override public void zoomOut() { - adjustZoomFactor(-5.0); + adjustZoomFactor(-ZOOM_STEP); } @Override public void panLeft() { - adjustHorizontalPan(-5.0); + adjustHorizontalPan(-PAN_STEP); } @Override public void panRight() { - adjustHorizontalPan(5.0); + adjustHorizontalPan(PAN_STEP); } @Override public void panUp() { - adjustVerticalPan(-5.0); + adjustVerticalPan(-PAN_STEP); } @Override public void panDown() { - adjustVerticalPan(5.0); + adjustVerticalPan(PAN_STEP); } } From 99ce4fa11d3b3a289d2911b22f00db08d26c1aab Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 26 Sep 2017 18:01:39 +1300 Subject: [PATCH 19/33] Merged develop in and reimplemented keybindings for moving the camera. tags : #story[1273] --- src/main/java/seng302/model/GameKeyBind.java | 4 +++ .../java/seng302/visualiser/GameView3D.java | 35 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/main/java/seng302/model/GameKeyBind.java b/src/main/java/seng302/model/GameKeyBind.java index c25f6202..5627625c 100644 --- a/src/main/java/seng302/model/GameKeyBind.java +++ b/src/main/java/seng302/model/GameKeyBind.java @@ -52,6 +52,10 @@ public class GameKeyBind { return instance.actionToKeyMap.get(keyAction); } + public KeyAction getKeyAction(KeyCode keyCode) { + return instance.keyToActionMap.get(keyCode); + } + /** * Binds a key to a key action * diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index a7633b3f..8a944c20 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -23,7 +23,9 @@ import javafx.scene.transform.Scale; import javafx.scene.transform.Translate; import seng302.gameServer.messages.RoundingSide; import seng302.model.ClientYacht; +import seng302.model.GameKeyBind; import seng302.model.GeoPoint; +import seng302.model.KeyAction; import seng302.model.Limit; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; @@ -121,6 +123,8 @@ public class GameView3D { scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement); } }); + + } public void updateCourse(List newCourse, List sequence) { @@ -345,7 +349,6 @@ public class GameView3D { * it to distanceScaleFactor Returns the max horizontal distance of the map. */ private double scaleRaceExtremities() { - double vertAngle = Math.abs( GeoUtility.getBearingRad(minLatPoint, maxLatPoint) ); @@ -413,38 +416,28 @@ public class GameView3D { } public void cameraMovement(KeyEvent event) { - switch (event.getCode()) { - case NUMPAD8: - view.getCamera().getTransforms().addAll(new Rotate(0.5, new Point3D(1, 0, 0))); - break; - case NUMPAD2: - view.getCamera().getTransforms().addAll(new Rotate(-0.5, new Point3D(1, 0, 0))); - break; - case NUMPAD4: - view.getCamera().getTransforms().addAll(new Rotate(-0.5, new Point3D(0, 1, 0))); - break; - case NUMPAD6: - view.getCamera().getTransforms().addAll(new Rotate(0.5, new Point3D(0, 1, 0))); - break; - case Z: + GameKeyBind keyBinds = GameKeyBind.getInstance(); + KeyAction keyPressed = keyBinds.getKeyAction(event.getCode()); + switch (keyPressed) { + case ZOOM_IN: ((RaceCamera) view.getCamera()).zoomIn(); break; - case X: + case ZOOM_OUT: ((RaceCamera) view.getCamera()).zoomOut(); break; - case W: + case FORWARD: ((RaceCamera) view.getCamera()).panUp(); break; - case S: + case BACKWARD: ((RaceCamera) view.getCamera()).panDown(); break; - case A: + case LEFT: ((RaceCamera) view.getCamera()).panLeft(); break; - case D: + case RIGHT: ((RaceCamera) view.getCamera()).panRight(); break; - case F1: + case VIEW: toggleCamera(); break; } From f11c457d28218f7159fadf96b2cc70905b3570e0 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 26 Sep 2017 18:31:47 +1300 Subject: [PATCH 20/33] removed print statements tags : #story[1273] --- src/main/java/seng302/visualiser/cameras/IsometricCamera.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java index 9814079c..41a1d563 100644 --- a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java +++ b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java @@ -37,10 +37,6 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera { } private void updateCamera() { - System.out.println("----"); - System.out.println(horizontalPan); - System.out.println(verticalPan); - System.out.println("----"); transforms.clear(); transforms.addAll( new Translate(horizontalPan, verticalPan, zoomFactor), From 9b00f769072009e2470c5e9b9bec61ffffcf5368 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 26 Sep 2017 18:36:13 +1300 Subject: [PATCH 21/33] removed print statements and added documentation. tags : #story[1273] --- .../visualiser/cameras/ChaseCamera.java | 20 +++++++++++++++++++ .../visualiser/cameras/IsometricCamera.java | 16 +++++++++++++++ .../visualiser/cameras/TopDownCamera.java | 20 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java index fddd1810..0647a473 100644 --- a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java +++ b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java @@ -37,6 +37,11 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { this.verticalPan = 0.0; } + /** + * Sets a player boat object to observe and update the camera with. + * + * @param playerBoat The player boat to be observed. + */ public void setPlayerBoat(BoatObject playerBoat) { this.playerBoat = playerBoat; @@ -47,6 +52,9 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ private void repositionCamera() { transforms.clear(); transforms.addAll( @@ -58,6 +66,10 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { ); } + /** + * Adjusts the zoom amount (camera depth) by some adjustment value + * @param adjustment the adjustment to be made to the camera + */ private void adjustZoomFactor(Double adjustment) { if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { zoomFactor = zoomFactor + adjustment; @@ -65,6 +77,10 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Vertical Panning of the Camera + * @param adjustment the adjustment to be made to the camera + */ private void adjustVerticalPan(Double adjustment) { if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT && verticalPan + adjustment <= VERTICAL_PAN_LIMIT) { @@ -73,6 +89,10 @@ public class ChaseCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Horizontal Panning of the Camera. + * @param adjustment the adjustment to be made to the camera + */ private void adjustHorizontalPan(Double adjustment) { this.horizontalPan += adjustment; repositionCamera(); diff --git a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java index 41a1d563..85a0e502 100644 --- a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java +++ b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java @@ -36,6 +36,9 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera { updateCamera(); } + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ private void updateCamera() { transforms.clear(); transforms.addAll( @@ -44,6 +47,11 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera { ); } + /** + * Adjusts the zoom amount (camera depth) by some adjustment value + * + * @param adjustment the adjustment to be made to the camera + */ private void adjustZoomFactor(Double adjustment) { if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { zoomFactor = zoomFactor + adjustment; @@ -51,6 +59,10 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Vertical Panning of the Camera + * @param adjustment the adjustment to be made to the camera + */ private void adjustVerticalPan(Double adjustment) { if (verticalPan + adjustment >= MIN_Y && verticalPan + adjustment <= MAX_Y) { verticalPan += adjustment; @@ -58,6 +70,10 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Horizontal Panning of the Camera. + * @param adjustment the adjustment to be made to the camera + */ private void adjustHorizontalPan(Double adjustment) { if (horizontalPan + adjustment >= MIN_X && horizontalPan + adjustment <= MIN_Y) { this.horizontalPan += adjustment; diff --git a/src/main/java/seng302/visualiser/cameras/TopDownCamera.java b/src/main/java/seng302/visualiser/cameras/TopDownCamera.java index 09455345..72d58707 100644 --- a/src/main/java/seng302/visualiser/cameras/TopDownCamera.java +++ b/src/main/java/seng302/visualiser/cameras/TopDownCamera.java @@ -32,6 +32,11 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera { verticalPan = 0.0; } + /** + * Sets a player boat object to observe and update the camera with. + * + * @param playerBoat The player boat to be observed. + */ public void setPlayerBoat(BoatObject playerBoat) { this.playerBoat = playerBoat; @@ -41,6 +46,9 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ private void updateCamera() { transforms.clear(); transforms.addAll( @@ -49,6 +57,10 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera { ); } + /** + * Adjusts the zoom amount (camera depth) by some adjustment value + * @param adjustment the adjustment to be made to the camera + */ private void adjustZoomFactor(Double adjustment) { if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { zoomFactor = zoomFactor + adjustment; @@ -56,6 +68,10 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Vertical Panning of the Camera + * @param adjustment the adjustment to be made to the camera + */ private void adjustVerticalPan(Double adjustment) { if (verticalPan + adjustment >= -PAN_LIMIT && verticalPan + adjustment <= PAN_LIMIT) { verticalPan += adjustment; @@ -63,6 +79,10 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera { } } + /** + * Adjusts the Horizontal Panning of the Camera. + * @param adjustment the adjustment to be made to the camera + */ private void adjustHorizontalPan(Double adjustment) { if (horizontalPan + adjustment >= -PAN_LIMIT && horizontalPan + adjustment <= PAN_LIMIT) { horizontalPan += adjustment; From b5076bc9762d83a01d625158ce2a7392a2511360 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 26 Sep 2017 18:57:15 +1300 Subject: [PATCH 22/33] Fixed wind walker and bumper Added a hack to bumper so that the collision distance is larger than the regular collision distance Wind walker now makes you go at you max speed rather than VMG speed #story[1293] --- .../java/seng302/gameServer/GameState.java | 29 +++++++------ src/main/java/seng302/model/PolarTable.java | 41 +++++++++++++++++-- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index ec761931..56f01fe1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -382,8 +382,8 @@ public class GameState implements Runnable { //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); -// token.assignRandomType(); - token.assignType(TokenType.BUMPER); + token.assignRandomType(); +// token.assignType(TokenType.WIND_WALKER); logger.debug("Spawned token of type " + token.getTokenType()); @@ -440,8 +440,9 @@ public class GameState implements Runnable { windWalk(yacht); break; case BUMPER: - ServerYacht collidedYacht = checkYachtCollision(yacht); + ServerYacht collidedYacht = checkYachtCollision(yacht, true); if (collidedYacht != null) { + System.out.println("WE OUT HERE"); yacht.powerDown(); boatTempShutDown(collidedYacht); notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); @@ -497,17 +498,12 @@ public class GameState implements Runnable { /** * This function changes the wind to be at an angle that causes the yacht in question to be at - * VMG. + * its fastest velocity * * @param yacht The yacht to fix the wind for */ private void windWalk(ServerYacht yacht) { - HashMap upwindOptimal = PolarTable.getOptimalUpwindVMG(windSpeed); - Double optimalAngle = null; - for (Double windAngle : upwindOptimal.keySet()) { - optimalAngle = windAngle; - } - + Double optimalAngle = PolarTable.getOptimalAngle(); Double heading = yacht.getHeading(); windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L); } @@ -597,7 +593,7 @@ public class GameState implements Runnable { */ public static void checkCollision(ServerYacht serverYacht) { //Yacht Collision - ServerYacht collidedYacht = checkYachtCollision(serverYacht); + ServerYacht collidedYacht = checkYachtCollision(serverYacht, false); Mark collidedMark = checkMarkCollision(serverYacht); if (collidedYacht != null) { @@ -946,15 +942,22 @@ public class GameState implements Runnable { * Collision detection which iterates through all the yachts and check if any yacht collided * with this yacht. Return collided yacht or null if no collision. * + * UPDATE: HACK!!! wmu16 - forBumperCollision is (the goddamn dirtiest) dirty flag to fix a + * weird bug where the bumper collision would not be registerd but the knock back collision would. + * In other words, only set the 'forBumperCollision' flag true if used for the bumper power up. + * * @return yacht to compare to all other yachts. */ - private static ServerYacht checkYachtCollision(ServerYacht yacht) { + private static ServerYacht checkYachtCollision(ServerYacht yacht, Boolean forBumperCollision) { + Double collisionDistance = + (forBumperCollision) ? YACHT_COLLISION_DISTANCE + 2.5 : YACHT_COLLISION_DISTANCE; for (ServerYacht otherYacht : GameState.getYachts().values()) { if (otherYacht != yacht) { Double distance = GeoUtility .getDistance(otherYacht.getLocation(), yacht.getLocation()); - if (distance < YACHT_COLLISION_DISTANCE) { + ; + if (distance < collisionDistance) { return otherYacht; } } diff --git a/src/main/java/seng302/model/PolarTable.java b/src/main/java/seng302/model/PolarTable.java index 9334cc54..6d91cd71 100644 --- a/src/main/java/seng302/model/PolarTable.java +++ b/src/main/java/seng302/model/PolarTable.java @@ -4,7 +4,9 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** * A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised @@ -17,6 +19,7 @@ public final class PolarTable { private static HashMap> polarTable; private static HashMap> upwindOptimal; private static HashMap> downwindOptimal; + private static Double optimalAngle; private static int upTwaIndex; private static int dnTwaIndex; @@ -33,11 +36,13 @@ public final class PolarTable { upwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>(); - String line; + String line = null; + String check; Boolean isHeaderLine = true; try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) { - while ((line = br.readLine()) != null) { + while ((check = br.readLine()) != null) { + line = check; String[] thisLine = line.split(","); //Initial line in file @@ -66,7 +71,10 @@ public final class PolarTable { upwindOptimal.put(thisWindSpeed, thisUpWindPolar); downwindOptimal.put(thisWindSpeed, thisDnWindPolar); } + + } + getMaxSpeedAngle(line); } catch (IOException e) { System.out.println("[PolarTable] IO exception"); @@ -74,6 +82,27 @@ public final class PolarTable { } + /** + * Passes the final line of the polar table and iterates over the speeds for each + * angle, velocity pair to find the angle that produces the highest velocity + * + * @param line The last line of the polar file + */ + private static void getMaxSpeedAngle(String line) { + String[] theLastLine = line.split(","); + Double maxWindVal = Double.parseDouble(theLastLine[0]); + Double optimalAngle = Double.parseDouble(theLastLine[1]); + Double maxSpeed = Double.parseDouble(theLastLine[2]); + for (Map.Entry entry : polarTable.get(maxWindVal).entrySet()) { + if (entry.getValue() > maxSpeed) { + maxSpeed = entry.getValue(); + optimalAngle = entry.getKey(); + } + } + PolarTable.optimalAngle = optimalAngle; + } + + /** * Parses the header line of a polar file @@ -85,14 +114,18 @@ public final class PolarTable { String thisItem = thisLine[i]; if (thisItem.toLowerCase().startsWith("uptwa")) { upTwaIndex = i; - } - else if (thisItem.toLowerCase().startsWith("dntwa")) { + } else if (thisItem.toLowerCase().startsWith("dntwa")) { dnTwaIndex = i; } } } + public static Double getOptimalAngle() { + return optimalAngle; + } + + /** * @return The entire polar table */ From 4b7dfe38c491c9d57e119a26fedc2a4e6b28eabd Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 26 Sep 2017 19:43:32 +1300 Subject: [PATCH 23/33] Fixed Boats powering up and down correctly. Icons respond correctly #story[1293] --- .../java/seng302/gameServer/GameState.java | 18 ++---------------- src/main/java/seng302/model/ServerYacht.java | 18 ++++++++++++++++++ .../controllers/RaceViewController.java | 5 +++++ src/main/resources/meshes/turning_pickup.dae | 14 +++++++------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 56f01fe1..afd3a4e7 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -78,8 +78,8 @@ public class GameState implements Runnable { private static final Double COLLISION_VELOCITY_PENALTY = 0.3; //Powerup Constants - private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; - private static final Integer HANDLING_BOOST_MULTIPLIER = 2; + public static final Integer VELOCITY_BOOST_MULTIPLIER = 2; + public static final Integer HANDLING_BOOST_MULTIPLIER = 2; public static final Long BUMPER_DISABLE_TIME = 5_000L; private static final Long TOKEN_SPAWN_TIME = 15_000L; @@ -550,20 +550,6 @@ public class GameState implements Runnable { } TokenType tokenType = collidedToken.getTokenType(); - switch (tokenType) { - case BOOST: - yacht.setPowerUpSpeedMultiplier(VELOCITY_BOOST_MULTIPLIER); - break; - case BUMPER: - // TODO: 22/09/17 wmu16 - break; - case HANDLING: - yacht.setPowerUpHandlingMultiplier(HANDLING_BOOST_MULTIPLIER); - break; - case WIND_WALKER: - // TODO: 22/09/17 wmu16 - break; - } yacht.powerUp(tokenType); String logMessage = diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 8d77dbda..0b15cb08 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -112,11 +112,29 @@ public class ServerYacht { location = geoPoint; } + /** + * Powers up a yacht with a given yacht, only after powering it down first to avoid double power + * ups + * + * @param powerUp The given power up + */ public void powerUp(TokenType powerUp) { + powerDown(); + switch (powerUp) { + case BOOST: + powerUpSpeedMultiplier = GameState.VELOCITY_BOOST_MULTIPLIER; + break; + case HANDLING: + powerUpHandlingMultiplier = GameState.HANDLING_BOOST_MULTIPLIER; + break; + } this.powerUp = powerUp; powerUpStartTime = System.currentTimeMillis(); } + /** + * Powers down a yacht, returning its power multipliers back to 1 + */ public void powerDown() { this.powerUp = null; this.powerUpSpeedMultiplier = 1; diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 76de92d3..716bc9fd 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -55,6 +55,7 @@ import seng302.model.RaceState; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.model.token.Token; import seng302.model.token.TokenType; import seng302.utilities.Sounds; import seng302.visualiser.GameView3D; @@ -307,6 +308,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel */ private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) { if (yacht == player) { + if (iconToDisplay != null) { + iconToDisplay.setVisible(false); + } switch (tokenType) { case BOOST: @@ -349,6 +353,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel if (yacht == player) { blinkingTimer.cancel(); iconToDisplay.setVisible(false); + iconToDisplay = null; } } diff --git a/src/main/resources/meshes/turning_pickup.dae b/src/main/resources/meshes/turning_pickup.dae index 2c4305f5..24e9fa08 100644 --- a/src/main/resources/meshes/turning_pickup.dae +++ b/src/main/resources/meshes/turning_pickup.dae @@ -5,8 +5,8 @@ Blender User Blender 2.78.0 commit date:2016-09-26, commit time:12:42, hash:4bb1e22 - 2017-09-19T15:45:46 - 2017-09-19T15:45:46 + 2017-09-26T19:13:35 + 2017-09-26T19:13:35 Z_UP @@ -23,10 +23,10 @@ 0 0 0 1 - 0.004555753 0.0885511 0.003947978 1 + 0.01630632 0.52949 0.0134405 1 - 0.25 0.25 0.25 1 + 0.125 0.125 0.125 1 50 @@ -49,10 +49,10 @@ 0 0 0 1 - 0.64 0.1458963 0.001825521 1 + 0.64 0.5334254 0 1 - 0.25 0.25 0.25 1 + 0.125 0.125 0.125 1 50 @@ -87,7 +87,7 @@ - 0.7002241 -0.2680317 -0.6616988 0.9049891 -0.2680316 -0.3303847 0.02474653 -0.9435215 -0.330386 -0.8896973 -0.3150947 -0.3303849 -0.5746018 0.7487837 -0.3303875 0.5345759 0.7778646 -0.3303867 0.4089462 -0.6284253 0.6616985 -0.4712997 -0.5831224 0.6616985 -0.7002241 0.2680317 0.6616988 0.03853034 0.7487788 0.6616991 0.7240421 0.1947362 0.6616954 0.4911195 0.356821 0.7946575 0.4089463 0.6284252 0.6616985 -0.1875942 0.5773453 0.7946577 -0.4712997 0.5831224 0.6616985 -0.6070605 0 0.7946557 -0.7002241 -0.2680318 0.6616988 -0.1875942 -0.5773453 0.7946577 0.03853034 -0.7487788 0.6616991 0.4911194 -0.356821 0.7946576 0.7240421 -0.1947363 0.6616954 0.8896973 0.3150946 0.3303849 0.7946556 0.5773479 0.1875951 0.5746018 0.7487836 0.3303875 -0.02474653 0.9435214 0.3303861 -0.3035309 0.9341714 0.1875976 -0.5345759 0.7778646 0.3303867 -0.9049891 0.2680316 0.3303846 -0.9822458 0 0.1875985 -0.9049891 -0.2680316 0.3303846 -0.5345759 -0.7778646 0.3303867 -0.3035309 -0.9341714 0.1875975 -0.02474653 -0.9435214 0.3303861 0.5746018 -0.7487836 0.3303875 0.7946556 -0.5773479 0.1875951 0.8896973 -0.3150946 0.3303849 0.3035309 0.9341714 -0.1875975 0.02474653 0.9435215 -0.330386 -0.7946556 0.5773479 -0.1875951 -0.8896973 0.3150945 -0.3303849 -0.7946556 -0.5773479 -0.1875951 -0.5746018 -0.7487836 -0.3303875 0.3035309 -0.9341714 -0.1875976 0.5345759 -0.7778645 -0.3303867 0.9822458 0 -0.1875985 0.9049891 0.2680316 -0.3303847 0.4712997 0.5831224 -0.6616986 0.1875942 0.5773453 -0.7946577 -0.0385304 0.7487788 -0.6616991 -0.4089462 0.6284252 -0.6616984 -0.4911194 0.356821 -0.7946576 -0.7240421 0.1947362 -0.6616954 -0.7240421 -0.1947362 -0.6616954 -0.4911195 -0.356821 -0.7946575 -0.4089462 -0.6284252 -0.6616984 0.7002241 0.2680318 -0.6616988 0.6070605 0 -0.7946556 -0.0385304 -0.7487788 -0.6616991 0.1875942 -0.5773453 -0.7946577 0.4712997 -0.5831224 -0.6616986 0.1023808 -0.3150898 -0.9435235 -0.2680341 -0.1947365 -0.9435229 -0.2680341 0.1947365 -0.9435229 0.1023808 0.3150898 -0.9435235 0.802609 -0.5831265 -0.1256273 -0.306569 -0.9435216 -0.1256289 -0.9920774 0 -0.1256284 -0.306569 0.9435216 -0.1256289 0.802609 0.5831265 -0.1256273 0.2680341 0.1947365 0.9435229 -0.1023808 0.3150899 0.9435235 -0.3313045 0 0.943524 -0.1023808 -0.3150898 0.9435235 0.2680341 -0.1947365 0.9435229 0.306569 0.9435216 0.1256289 -0.802609 0.5831265 0.1256274 -0.802609 -0.5831265 0.1256274 0.306569 -0.9435216 0.1256289 0.9920774 0 0.1256284 0.3313045 0 -0.943524 + 0.7002241 -0.2680317 -0.6616988 0.9049891 -0.2680316 -0.3303847 0.02474653 -0.9435215 -0.330386 -0.8896973 -0.3150947 -0.3303849 -0.5746018 0.7487837 -0.3303875 0.5345759 0.7778646 -0.3303867 0.4089462 -0.6284252 0.6616985 -0.4712997 -0.5831224 0.6616985 -0.7002241 0.2680317 0.6616988 0.03853034 0.7487788 0.6616992 0.7240421 0.1947362 0.6616954 0.4911194 0.356821 0.7946576 0.4089462 0.6284253 0.6616984 -0.1875943 0.5773454 0.7946577 -0.4712997 0.5831224 0.6616985 -0.6070605 0 0.7946557 -0.7002241 -0.2680318 0.6616988 -0.1875943 -0.5773454 0.7946577 0.03853034 -0.7487788 0.6616992 0.4911193 -0.356821 0.7946577 0.7240421 -0.1947363 0.6616954 0.8896973 0.3150946 0.3303849 0.7946556 0.5773479 0.1875951 0.5746018 0.7487836 0.3303875 -0.02474653 0.9435214 0.3303861 -0.3035309 0.9341714 0.1875976 -0.5345759 0.7778646 0.3303867 -0.9049891 0.2680316 0.3303846 -0.9822458 0 0.1875985 -0.9049891 -0.2680316 0.3303846 -0.5345759 -0.7778646 0.3303867 -0.3035309 -0.9341714 0.1875975 -0.02474653 -0.9435214 0.3303861 0.5746018 -0.7487836 0.3303875 0.7946556 -0.5773479 0.1875951 0.8896973 -0.3150946 0.3303849 0.3035309 0.9341714 -0.1875975 0.02474653 0.9435215 -0.330386 -0.7946556 0.5773479 -0.1875951 -0.8896973 0.3150945 -0.3303849 -0.7946556 -0.5773479 -0.1875951 -0.5746018 -0.7487836 -0.3303875 0.3035309 -0.9341714 -0.1875976 0.5345759 -0.7778645 -0.3303867 0.9822458 0 -0.1875985 0.9049891 0.2680316 -0.3303847 0.4712997 0.5831224 -0.6616986 0.1875943 0.5773454 -0.7946577 -0.0385304 0.7487789 -0.6616991 -0.4089462 0.6284252 -0.6616984 -0.4911193 0.356821 -0.7946577 -0.7240421 0.1947362 -0.6616954 -0.7240421 -0.1947362 -0.6616954 -0.4911194 -0.356821 -0.7946576 -0.4089462 -0.6284252 -0.6616984 0.7002241 0.2680318 -0.6616988 0.6070605 0 -0.7946556 -0.0385304 -0.7487789 -0.6616991 0.1875943 -0.5773454 -0.7946577 0.4712997 -0.5831224 -0.6616986 0.1023808 -0.3150898 -0.9435235 -0.2680341 -0.1947365 -0.9435229 -0.2680341 0.1947365 -0.9435229 0.1023808 0.3150898 -0.9435235 0.802609 -0.5831265 -0.1256273 -0.306569 -0.9435216 -0.1256289 -0.9920774 0 -0.1256284 -0.306569 0.9435216 -0.1256289 0.802609 0.5831265 -0.1256273 0.2680341 0.1947365 0.9435229 -0.1023808 0.3150898 0.9435235 -0.3313045 0 0.943524 -0.1023808 -0.3150899 0.9435235 0.2680341 -0.1947365 0.9435229 0.306569 0.9435216 0.1256289 -0.802609 0.5831265 0.1256274 -0.802609 -0.5831265 0.1256274 0.306569 -0.9435216 0.1256289 0.9920774 0 0.1256284 0.3313045 0 -0.943524 From 330ccd272d6ea129835fb3eca13be3e383c0a796 Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 26 Sep 2017 21:13:35 +1300 Subject: [PATCH 24/33] The random token now has a 50% chance of causing your boat to have a speed penalty #story[1293] --- .../java/seng302/gameServer/GameState.java | 71 ++++++----- .../gameServer/messages/YachtEventType.java | 2 +- src/main/java/seng302/model/ServerYacht.java | 10 +- .../java/seng302/visualiser/GameView3D.java | 46 +++---- .../controllers/RaceViewController.java | 5 +- src/main/resources/views/RaceView.fxml | 116 +++++++++--------- 6 files changed, 131 insertions(+), 119 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index afd3a4e7..dfb8dae4 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -78,10 +78,11 @@ public class GameState implements Runnable { private static final Double COLLISION_VELOCITY_PENALTY = 0.3; //Powerup Constants - public static final Integer VELOCITY_BOOST_MULTIPLIER = 2; + public static final Double VELOCITY_BOOST_MULTIPLIER = 2d; public static final Integer HANDLING_BOOST_MULTIPLIER = 2; + private static final Double BAD_RANDOM_SPEED_PENALTY = 0.3; public static final Long BUMPER_DISABLE_TIME = 5_000L; - private static final Long TOKEN_SPAWN_TIME = 15_000L; + private static final Long TOKEN_SPAWN_TIME = 30_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -383,7 +384,7 @@ public class GameState implements Runnable { //Get a random token location with random type Token token = allTokens.get(random.nextInt(allTokens.size())); token.assignRandomType(); -// token.assignType(TokenType.WIND_WALKER); +// token.assignType(TokenType.RANDOM); logger.debug("Spawned token of type " + token.getTokenType()); @@ -430,7 +431,12 @@ public class GameState implements Runnable { * @param yacht The yacht to perform token checks on */ private void preformTokenUpdates(ServerYacht yacht) { - checkTokenPickUp(yacht); + Token collidedToken = checkTokenPickUp(yacht); + if (collidedToken != null) { + tokensInPlay.remove(collidedToken); + powerUpYacht(yacht, collidedToken); + } + checkPowerUpTimeout(yacht); TokenType powerUp = yacht.getPowerUp(); @@ -442,7 +448,6 @@ public class GameState implements Runnable { case BUMPER: ServerYacht collidedYacht = checkYachtCollision(yacht, true); if (collidedYacht != null) { - System.out.println("WE OUT HERE"); yacht.powerDown(); boatTempShutDown(collidedYacht); notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); @@ -450,10 +455,36 @@ public class GameState implements Runnable { MessageFactory.makeStatusEffectMessage(collidedYacht, powerUp)); } break; + case RANDOM: + yacht.setPowerUpSpeedMultiplier(BAD_RANDOM_SPEED_PENALTY); } } } + + /** + * Powers up a yacht with the given token type. + * + * @param yacht The yacht to be powered up + * @param collidedToken The token which this yacht collided with + */ + private void powerUpYacht(ServerYacht yacht, Token collidedToken) { + //The random token has a 50% chance of becoming another token else becoming a speed detriment! + if (collidedToken.getTokenType() == TokenType.RANDOM && new Random().nextBoolean()) { + collidedToken.realiseRandom(); + } + + yacht.powerUp(collidedToken.getTokenType()); + String logMessage = + yacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() + + " token"; + notifyMessageListeners(MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + notifyMessageListeners(MessageFactory.getRaceXML()); + notifyMessageListeners(MessageFactory.makePickupMessage(yacht, collidedToken)); + logger.debug( + "Yacht: " + yacht.getShortName() + " got powerup " + collidedToken.getTokenType()); + } + // TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/ /** @@ -462,7 +493,7 @@ public class GameState implements Runnable { * @param yacht The yacht to disable */ private void boatTempShutDown(ServerYacht yacht) { - yacht.setPowerUpSpeedMultiplier(0); + yacht.setPowerUpSpeedMultiplier(0d); Timer shutDownTimer = new Timer("Shutdown Timer"); shutDownTimer.schedule(new TimerTask() { @Override @@ -533,36 +564,18 @@ public class GameState implements Runnable { * Checks all tokensInPlay to see if a yacht has picked one up. If so, the yacht is powered up * in the appropriate way * @param yacht The yacht to check for collision with a token + * + * @return The token collided with */ - private void checkTokenPickUp(ServerYacht yacht) { - Token collidedToken = null; + private Token checkTokenPickUp(ServerYacht yacht) { for (Token token : tokensInPlay) { Double distance = GeoUtility.getDistance(token, yacht.getLocation()); if (distance < YACHT_COLLISION_DISTANCE) { - collidedToken = token; + return token; } } - if (collidedToken != null) { - tokensInPlay.remove(collidedToken); - if (collidedToken.getTokenType() == TokenType.RANDOM) { - collidedToken.realiseRandom(); - } - - TokenType tokenType = collidedToken.getTokenType(); - yacht.powerUp(tokenType); - - String logMessage = - yacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() - + " token"; - notifyMessageListeners( - MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); - notifyMessageListeners(MessageFactory.getRaceXML()); - notifyMessageListeners(MessageFactory.makePickupMessage(yacht, collidedToken)); - - logger.debug("Yacht: " + yacht.getShortName() + " got powerup " + collidedToken - .getTokenType()); - } + return null; } diff --git a/src/main/java/seng302/gameServer/messages/YachtEventType.java b/src/main/java/seng302/gameServer/messages/YachtEventType.java index facda84e..9fd5170b 100644 --- a/src/main/java/seng302/gameServer/messages/YachtEventType.java +++ b/src/main/java/seng302/gameServer/messages/YachtEventType.java @@ -1,7 +1,7 @@ package seng302.gameServer.messages; /** - * Created by wmu16 on 11/09/17. + * Enum for different event types for the yacht */ public enum YachtEventType { COLLISION(33), diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 0b15cb08..c092447d 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -55,7 +55,7 @@ public class ServerYacht { //PowerUp private TokenType powerUp; private Long powerUpStartTime; - private Integer powerUpSpeedMultiplier; + private Double powerUpSpeedMultiplier; private Integer powerUpHandlingMultiplier; //turning mode @@ -80,7 +80,7 @@ public class ServerYacht { this.legNumber = 0; this.boatColor = Colors.getColor(sourceId - 1); this.powerUp = null; - this.powerUpSpeedMultiplier = 1; + this.powerUpSpeedMultiplier = 1d; this.powerUpHandlingMultiplier = 1; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; @@ -137,7 +137,7 @@ public class ServerYacht { */ public void powerDown() { this.powerUp = null; - this.powerUpSpeedMultiplier = 1; + this.powerUpSpeedMultiplier = 1d; this.powerUpHandlingMultiplier = 1; } @@ -480,11 +480,11 @@ public class ServerYacht { this.continuouslyTurning = continuouslyTurning; } - public Integer getPowerUpSpeedMultiplier() { + public Double getPowerUpSpeedMultiplier() { return powerUpSpeedMultiplier; } - public void setPowerUpSpeedMultiplier(Integer powerUpSpeedMultiplier) { + public void setPowerUpSpeedMultiplier(Double powerUpSpeedMultiplier) { this.powerUpSpeedMultiplier = powerUpSpeedMultiplier; } diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index 027e92ad..5978675c 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -417,28 +417,30 @@ public class GameView3D { public void cameraMovement(KeyEvent event) { GameKeyBind keyBinds = GameKeyBind.getInstance(); KeyAction keyPressed = keyBinds.getKeyAction(event.getCode()); - switch (keyPressed) { - case ZOOM_IN: - ((RaceCamera) view.getCamera()).zoomIn(); - break; - case ZOOM_OUT: - ((RaceCamera) view.getCamera()).zoomOut(); - break; - case FORWARD: - ((RaceCamera) view.getCamera()).panUp(); - break; - case BACKWARD: - ((RaceCamera) view.getCamera()).panDown(); - break; - case LEFT: - ((RaceCamera) view.getCamera()).panLeft(); - break; - case RIGHT: - ((RaceCamera) view.getCamera()).panRight(); - break; - case VIEW: - toggleCamera(); - break; + if (keyPressed != null) { + switch (keyPressed) { + case ZOOM_IN: + ((RaceCamera) view.getCamera()).zoomIn(); + break; + case ZOOM_OUT: + ((RaceCamera) view.getCamera()).zoomOut(); + break; + case FORWARD: + ((RaceCamera) view.getCamera()).panUp(); + break; + case BACKWARD: + ((RaceCamera) view.getCamera()).panDown(); + break; + case LEFT: + ((RaceCamera) view.getCamera()).panLeft(); + break; + case RIGHT: + ((RaceCamera) view.getCamera()).panRight(); + break; + case VIEW: + toggleCamera(); + break; + } } } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 716bc9fd..7395d5df 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -121,7 +121,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel @FXML private Label positionLabel, boatSpeedLabel, boatHeadingLabel; @FXML - private ImageView velocityIcon, handlingIcon, windWalkerIcon, bumperIcon; + private ImageView velocityIcon, handlingIcon, windWalkerIcon, bumperIcon, badRandomIcon; //Race Data private Map participants; @@ -325,6 +325,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel case BUMPER: iconToDisplay = bumperIcon; break; + case RANDOM: + iconToDisplay = badRandomIcon; + break; default: iconToDisplay = velocityIcon; } diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index 9a02f517..7e53d81b 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -25,9 +25,8 @@ @@ -48,17 +47,15 @@ valignment="BOTTOM" vgrow="SOMETIMES"/> - + - + - + @@ -90,25 +87,20 @@ - - + + - + - + - + - + @@ -131,18 +123,16 @@ minWidth="90.0" prefWidth="90.0"/> - + - + - + - + - + - \ No newline at end of file + From 5248921576e6823ea55b23877d4b7c0f3bf1a0fe Mon Sep 17 00:00:00 2001 From: Zhi You Tan Date: Tue, 26 Sep 2017 22:52:58 +1300 Subject: [PATCH 25/33] Multiple fixes: - fix last button no longer default focused when loading keybinding dialog - fix click and mouse exit still focused on button - closing keybinding does not focus on chat anymore #story[1273] --- .../visualiser/controllers/ViewManager.java | 1 + .../dialogs/KeyBindingDialogController.java | 7 +- src/main/resources/views/RaceView.fxml | 85 ++++++++----------- 3 files changed, 40 insertions(+), 53 deletions(-) diff --git a/src/main/java/seng302/visualiser/controllers/ViewManager.java b/src/main/java/seng302/visualiser/controllers/ViewManager.java index 5e3d6748..2f843a4c 100644 --- a/src/main/java/seng302/visualiser/controllers/ViewManager.java +++ b/src/main/java/seng302/visualiser/controllers/ViewManager.java @@ -221,6 +221,7 @@ public class ViewManager { .getController(); keyBindingDialogController.setGameClient(this.gameClient); keyBindingDialog.show(); + decorator.requestFocus(); Sounds.playButtonClick(); } } diff --git a/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java index c650bc59..1f638c5a 100644 --- a/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java +++ b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java @@ -71,8 +71,7 @@ public class KeyBindingDialogController implements Initializable { buttons = new ArrayList<>(); Collections.addAll(buttons, zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn, - viewButton, - rightButton, leftButton, forwardButton, backwardButton); + viewButton, rightButton, leftButton, forwardButton, backwardButton); bindButtonWithAction(); loadKeyBind(); @@ -91,9 +90,6 @@ public class KeyBindingDialogController implements Initializable { }); closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog()); - - keyBindingDialogHeader.setFocusTraversable(true); - keyBindingDialogHeader.requestFocus(); } /** @@ -161,6 +157,7 @@ public class KeyBindingDialogController implements Initializable { + "-fx-background-color: -fx-pp-front-color; " + "-fx-text-fill: -fx-pp-theme-color; " + "-fx-font-size: 13;"); + keyBindingDialogHeader.requestFocus(); } /** diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index 879b2f22..4ee22b65 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -7,17 +7,16 @@ - + @@ -38,17 +37,15 @@ valignment="BOTTOM" vgrow="SOMETIMES"/> - + - + - + @@ -80,25 +77,20 @@ - - + + - + - + - + - + @@ -121,22 +113,21 @@ minWidth="90.0" prefWidth="90.0"/> - + - - + - + @@ -158,29 +149,27 @@ prefHeight="150.0" prefWidth="240.0" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM"> - - + - + - + - - + + From 6d51ea35745d465db2ccea5a24d2d3422fcfa1a1 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 01:44:01 +1300 Subject: [PATCH 27/33] Created a random place token generator. Generates a location between any leg of the race in a random angle / distance of the radius of the centre point of the two gates to one of the gates #story[1293] --- .../java/seng302/gameServer/GameState.java | 47 +++----------- .../java/seng302/model/mark/MarkOrder.java | 14 ++-- src/main/java/seng302/model/token/Token.java | 13 ++++ .../java/seng302/utilities/RandomSpawn.java | 64 +++++++++++++++++++ 4 files changed, 97 insertions(+), 41 deletions(-) create mode 100644 src/main/java/seng302/utilities/RandomSpawn.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index dfb8dae4..5236a14e 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; @@ -38,6 +39,7 @@ import seng302.model.mark.MarkOrder; import seng302.model.token.Token; import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; +import seng302.utilities.RandomSpawn; import seng302.utilities.XMLParser; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; @@ -82,7 +84,7 @@ public class GameState implements Runnable { public static final Integer HANDLING_BOOST_MULTIPLIER = 2; private static final Double BAD_RANDOM_SPEED_PENALTY = 0.3; public static final Long BUMPER_DISABLE_TIME = 5_000L; - private static final Long TOKEN_SPAWN_TIME = 30_000L; + private static final Long TOKEN_SPAWN_TIME = 15_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -99,13 +101,12 @@ public class GameState implements Runnable { private static GameStages currentStage; private static MarkOrder markOrder; private static long startTime; - private static Set marks; + private static List marks; private static List courseLimit; private static Integer maxPlayers = 8; - - private static List allTokens; private static List tokensInPlay; + private static RandomSpawn randomSpawn; private static List newMessageListeners; @@ -126,14 +127,12 @@ public class GameState implements Runnable { previousUpdateTime = System.currentTimeMillis(); markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? newMessageListeners = new ArrayList<>(); - allTokens = makeTokens(); + marks = new MarkOrder().getAllMarks(); + randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks()); resetStartTime(); - - new Thread(this, "GameState").start(); //Run the auto updates on the game state - - marks = new MarkOrder().getAllMarks(); setCourseLimit("/server_config/race.xml"); + new Thread(this, "GameState").start(); //Run the auto updates on the game state } private void setCourseLimit(String url) { @@ -151,29 +150,10 @@ public class GameState implements Runnable { courseLimit = XMLParser.parseRace(document).getCourseLimit(); } - - /** - * Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each - * race ideally - * - * @return A list of possible tokensInPlay for this race - */ - private ArrayList makeTokens() { - Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154); - Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382); - Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965); - Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214); - return new ArrayList<>(Arrays.asList(token1, token2, token3, token4)); - } - public static String getHostIpAddress() { return hostIpAddress; } - public static Set getMarks() { - return Collections.unmodifiableSet(marks); - } - public static List getPlayers() { return players; } @@ -378,16 +358,9 @@ public class GameState implements Runnable { * Broadasts a new race status message to show this update */ private void spawnNewToken() { - Random random = new Random(); tokensInPlay.clear(); - - //Get a random token location with random type - Token token = allTokens.get(random.nextInt(allTokens.size())); - token.assignRandomType(); -// token.assignType(TokenType.RANDOM); - + Token token = randomSpawn.getRandomTokenLocation(); logger.debug("Spawned token of type " + token.getTokenType()); - tokensInPlay.add(token); } @@ -912,7 +885,7 @@ public class GameState implements Runnable { } private static Mark checkMarkCollision(ServerYacht yacht) { - Set marksInRace = GameState.getMarks(); + Set marksInRace = new HashSet<>(marks); for (Mark mark : marksInRace) { if (GeoUtility.getDistance(yacht.getLocation(), mark) <= MARK_COLLISION_DISTANCE) { diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index 0e250e99..7f296aee 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -24,8 +24,9 @@ import java.util.*; */ public class MarkOrder { private List raceMarkOrder; + private List orderedUniqueCompoundMarks; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); - private Set allMarks; + private List allMarks; public MarkOrder(){ loadRaceProperties(); @@ -44,6 +45,10 @@ public class MarkOrder { return Collections.unmodifiableList(raceMarkOrder); } + public List getOrderedUniqueCompoundMarks() { + return orderedUniqueCompoundMarks; + } + /** * @param seqID The seqID of the current mark the boat is heading to * @return A Boolean indicating if this coming mark is the last one (finish line) @@ -75,8 +80,8 @@ public class MarkOrder { return raceMarkOrder.get(currentSeqID + 1); } - public Set getAllMarks(){ - return Collections.unmodifiableSet(allMarks); + public List getAllMarks() { + return Collections.unmodifiableList(allMarks); } /** @@ -89,7 +94,7 @@ public class MarkOrder { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; Document doc; - allMarks = new HashSet<>(); + allMarks = new ArrayList<>(); try { db = dbf.newDocumentBuilder(); @@ -105,6 +110,7 @@ public class MarkOrder { logger.debug("Loaded RaceXML for mark order"); List corners = data.getMarkSequence(); Map marks = data.getCompoundMarks(); + orderedUniqueCompoundMarks = new ArrayList<>(marks.values()); List course = new ArrayList<>(); for (Corner corner : corners){ CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); diff --git a/src/main/java/seng302/model/token/Token.java b/src/main/java/seng302/model/token/Token.java index ed8b87eb..0e6e86b8 100644 --- a/src/main/java/seng302/model/token/Token.java +++ b/src/main/java/seng302/model/token/Token.java @@ -15,11 +15,24 @@ public class Token extends GeoPoint { private TokenType tokenType; private Random random = new Random(); + //Constructor for creating a specific type client side public Token(TokenType tokenType, double lat, double lng) { super(lat, lng); this.tokenType = tokenType; } + //Making random type server side + public Token(double lat, double lng) { + super(lat, lng); + assignRandomType(); + } + + //Making random type server side + public Token(GeoPoint geoPoint) { + super(geoPoint.getLat(), geoPoint.getLng()); + assignRandomType(); + } + public TokenType getTokenType() { return tokenType; } diff --git a/src/main/java/seng302/utilities/RandomSpawn.java b/src/main/java/seng302/utilities/RandomSpawn.java new file mode 100644 index 00000000..0a0f0702 --- /dev/null +++ b/src/main/java/seng302/utilities/RandomSpawn.java @@ -0,0 +1,64 @@ +package seng302.utilities; + +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import seng302.model.GeoPoint; +import seng302.model.mark.CompoundMark; +import seng302.model.token.Token; + +/** + * A class for generating and spawning tokens in random locations + * Created by wmu16 on 27/09/17. + */ +public class RandomSpawn { + + private static final Integer DEGREES_IN_CIRCLE = 360; + + private HashMap spawnRadii; + private Random random; + + /** + * @param markOrder this must be the ORDERED list of marks. Better yet UNIQUE to avoid over + * computation + */ + public RandomSpawn(List markOrder) { + this.spawnRadii = new HashMap<>(); + random = new Random(); + + spawnRadii = generateSpawnRadii(markOrder); + } + + private HashMap generateSpawnRadii(List markOrder) { + System.out.println(markOrder); + HashMap spawnRadii = new HashMap<>(); + for (int i = 0; i < markOrder.size() - 1; i++) { + GeoPoint spawnCentre = GeoUtility.getDirtyMidPoint( + markOrder.get(i).getMidPoint(), + markOrder.get(i + 1).getMidPoint()); + + Double distance = GeoUtility.getDistance(spawnCentre, markOrder.get(i).getMidPoint()); + spawnRadii.put(spawnCentre, distance); + } + + return spawnRadii; + } + + + /** + * @return A random token type at a random location in a random radii of the set of possible + * radii + */ + public Token getRandomTokenLocation() { + Object[] keys = spawnRadii.keySet().toArray(); + GeoPoint randomSpawnCentre = (GeoPoint) keys[random.nextInt(keys.length)]; + Double spawnRadius = spawnRadii.get(randomSpawnCentre); + Double randomDistance = spawnRadius * random.nextDouble(); + Double randomAngle = random.nextDouble() * DEGREES_IN_CIRCLE; + GeoPoint randomLocation = GeoUtility + .getGeoCoordinate(randomSpawnCentre, randomAngle, randomDistance); + return new Token(randomLocation); + + } + +} From 10fa51a10547481b0c7765374c5743b5334ee8f9 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 02:27:30 +1300 Subject: [PATCH 28/33] Added some testing for the Random spawner #story[1293] --- .../java/seng302/utilities/RandomSpawn.java | 1 - .../seng302/utilities/RandomSpawnTest.java | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/test/java/seng302/utilities/RandomSpawnTest.java diff --git a/src/main/java/seng302/utilities/RandomSpawn.java b/src/main/java/seng302/utilities/RandomSpawn.java index 0a0f0702..929e5766 100644 --- a/src/main/java/seng302/utilities/RandomSpawn.java +++ b/src/main/java/seng302/utilities/RandomSpawn.java @@ -30,7 +30,6 @@ public class RandomSpawn { } private HashMap generateSpawnRadii(List markOrder) { - System.out.println(markOrder); HashMap spawnRadii = new HashMap<>(); for (int i = 0; i < markOrder.size() - 1; i++) { GeoPoint spawnCentre = GeoUtility.getDirtyMidPoint( diff --git a/src/test/java/seng302/utilities/RandomSpawnTest.java b/src/test/java/seng302/utilities/RandomSpawnTest.java new file mode 100644 index 00000000..60966ab8 --- /dev/null +++ b/src/test/java/seng302/utilities/RandomSpawnTest.java @@ -0,0 +1,50 @@ +package seng302.utilities; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import seng302.model.GeoPoint; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; +import seng302.model.token.Token; + +/** + * Created by wmu16 on 27/09/17. + */ +public class RandomSpawnTest { + + private RandomSpawn randomSpawn; + + Mark mark1 = new Mark("mark1", 0, 57.670333, 11.827833, 0); + Mark mark2 = new Mark("mark2", 1, 57.671829, 11.842049, 1); + CompoundMark compoundMark1 = new CompoundMark(0, "mark1", + new ArrayList<>(Arrays.asList(mark1))); + CompoundMark compoundMark2 = new CompoundMark(0, "mark1", + new ArrayList<>(Arrays.asList(mark2))); + + List markOrder = new ArrayList<>(Arrays.asList(compoundMark1, compoundMark2)); + + @Before + public void setup() { + randomSpawn = new RandomSpawn(markOrder); + } + + @Test + public void testGetRandomTokenLocation() { + GeoPoint testMidPoint = GeoUtility + .getDirtyMidPoint(compoundMark1.getMidPoint(), compoundMark2.getMidPoint()); + Double maxDistance = GeoUtility.getDistance(testMidPoint, compoundMark2.getMidPoint()); + for (int i = 0; i < 1000; i++) { + Token token = randomSpawn.getRandomTokenLocation(); + Double distanceFromCentreRadius = GeoUtility.getDistance(testMidPoint, token); + assertTrue("Out of bounds token", distanceFromCentreRadius <= maxDistance); + } + + + } + +} \ No newline at end of file From 658a342118c35b0a61b409d511edda5755ed5f05 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 03:01:23 +1300 Subject: [PATCH 29/33] Added the bad random to git sorree forgot to git add #story[1293] --- src/main/resources/icons/ayy_lmao.gif | Bin 0 -> 23971 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/icons/ayy_lmao.gif diff --git a/src/main/resources/icons/ayy_lmao.gif b/src/main/resources/icons/ayy_lmao.gif new file mode 100644 index 0000000000000000000000000000000000000000..884b1f5098b7b3acaa6e3a4e984bde490f32e7d9 GIT binary patch literal 23971 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 literal 0 HcmV?d00001 From 85ca91db964670edcd9efd70a88bf95a3dc48d1a Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 11:55:40 +1300 Subject: [PATCH 30/33] Minor fixes. Only one person can have wind walker now. Tokens spawn at 30s rather than 15 #story[1293] --- .../java/seng302/gameServer/GameState.java | 51 ++++++++++++------- .../java/seng302/model/token/TokenType.java | 2 +- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 5236a14e..795d42dc 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -84,7 +84,7 @@ public class GameState implements Runnable { public static final Integer HANDLING_BOOST_MULTIPLIER = 2; private static final Double BAD_RANDOM_SPEED_PENALTY = 0.3; public static final Long BUMPER_DISABLE_TIME = 5_000L; - private static final Long TOKEN_SPAWN_TIME = 15_000L; + private static final Long TOKEN_SPAWN_TIME = 30_000L; private static Long previousUpdateTime; public static Double windDirection; @@ -360,6 +360,7 @@ public class GameState implements Runnable { private void spawnNewToken() { tokensInPlay.clear(); Token token = randomSpawn.getRandomTokenLocation(); + token.assignType(TokenType.WIND_WALKER); logger.debug("Spawned token of type " + token.getTokenType()); tokensInPlay.add(token); } @@ -436,26 +437,47 @@ public class GameState implements Runnable { /** - * Powers up a yacht with the given token type. + * Powers up a thisYacht with the given token type. * - * @param yacht The yacht to be powered up - * @param collidedToken The token which this yacht collided with + * @param thisYacht The yacht to be powered up + * @param collidedToken The token which this thisYacht collided with */ - private void powerUpYacht(ServerYacht yacht, Token collidedToken) { + private void powerUpYacht(ServerYacht thisYacht, Token collidedToken) { //The random token has a 50% chance of becoming another token else becoming a speed detriment! if (collidedToken.getTokenType() == TokenType.RANDOM && new Random().nextBoolean()) { collidedToken.realiseRandom(); } - yacht.powerUp(collidedToken.getTokenType()); + //If another yacht has the wind walker token, They should be powered down. Only one allowed! + else if (collidedToken.getTokenType() == TokenType.WIND_WALKER) { + for (ServerYacht otherYacht : yachts.values()) { + if (otherYacht != thisYacht && otherYacht.getPowerUp() == TokenType.WIND_WALKER) { + powerDownYacht(otherYacht); + } + } + } + + thisYacht.powerUp(collidedToken.getTokenType()); String logMessage = - yacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() + thisYacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() + " token"; - notifyMessageListeners(MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + notifyMessageListeners( + MessageFactory.makeChatterMessage(thisYacht.getSourceId(), logMessage)); notifyMessageListeners(MessageFactory.getRaceXML()); - notifyMessageListeners(MessageFactory.makePickupMessage(yacht, collidedToken)); + notifyMessageListeners(MessageFactory.makePickupMessage(thisYacht, collidedToken)); logger.debug( - "Yacht: " + yacht.getShortName() + " got powerup " + collidedToken.getTokenType()); + "Yacht: " + thisYacht.getShortName() + " got powerup " + collidedToken.getTokenType()); + } + + private void powerDownYacht(ServerYacht yacht) { + String logMessage = + yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); + notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); + logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + + yacht.powerDown(); } // TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/ @@ -487,14 +509,7 @@ public class GameState implements Runnable { if (yacht.getPowerUp() != null) { if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() .getTimeout()) { - String logMessage = - yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired"; - notifyMessageListeners( - MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage)); - notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); - logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); - - yacht.powerDown(); + powerDownYacht(yacht); } } } diff --git a/src/main/java/seng302/model/token/TokenType.java b/src/main/java/seng302/model/token/TokenType.java index 8ae60dcc..0ce24d48 100644 --- a/src/main/java/seng302/model/token/TokenType.java +++ b/src/main/java/seng302/model/token/TokenType.java @@ -9,7 +9,7 @@ public enum TokenType { BOOST(0, "Boost", 10_000), HANDLING(1, "Handling", 10_000), BUMPER(2, "Bumper", 10_000), - WIND_WALKER(3, "Wind Walker", 10_000), + WIND_WALKER(3, "Wind Walker", 30_000), RANDOM(4, "Random", 10_000); private int value; From e5af7bf6669552ff7245f21c652a30736703eee9 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 12:06:16 +1300 Subject: [PATCH 31/33] Minor fixes. Only one person can have wind walker now. Tokens spawn at 30s rather than 15 #story[1293] --- src/main/java/seng302/gameServer/GameState.java | 2 +- src/main/java/seng302/model/token/TokenType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 795d42dc..965c57ed 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -360,7 +360,7 @@ public class GameState implements Runnable { private void spawnNewToken() { tokensInPlay.clear(); Token token = randomSpawn.getRandomTokenLocation(); - token.assignType(TokenType.WIND_WALKER); +// token.assignType(TokenType.WIND_WALKER); logger.debug("Spawned token of type " + token.getTokenType()); tokensInPlay.add(token); } diff --git a/src/main/java/seng302/model/token/TokenType.java b/src/main/java/seng302/model/token/TokenType.java index 0ce24d48..8ae60dcc 100644 --- a/src/main/java/seng302/model/token/TokenType.java +++ b/src/main/java/seng302/model/token/TokenType.java @@ -9,7 +9,7 @@ public enum TokenType { BOOST(0, "Boost", 10_000), HANDLING(1, "Handling", 10_000), BUMPER(2, "Bumper", 10_000), - WIND_WALKER(3, "Wind Walker", 30_000), + WIND_WALKER(3, "Wind Walker", 10_000), RANDOM(4, "Random", 10_000); private int value; From 6f62efcc93059edbf1f3dc95d11cc48ce0fbd4ce Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 12:44:22 +1300 Subject: [PATCH 32/33] Updated the icons to be more uniform and transparent #story[1293] --- .../java/seng302/gameServer/GameState.java | 8 ++------ .../java/seng302/utilities/RandomSpawn.java | 7 ++++--- src/main/resources/icons/ayy_lmao.gif | Bin 23971 -> 0 bytes src/main/resources/icons/bumperIcon.png | Bin 3580 -> 7950 bytes src/main/resources/icons/handlingIcon.png | Bin 3205 -> 9321 bytes src/main/resources/icons/velocity.png | Bin 4328 -> 10136 bytes src/main/resources/icons/windWalkerIcon.png | Bin 3939 -> 8965 bytes src/main/resources/views/RaceView.fxml | 2 +- .../seng302/utilities/RandomSpawnTest.java | 2 +- 9 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 src/main/resources/icons/ayy_lmao.gif diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 965c57ed..f28b29e1 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,8 +1,6 @@ package seng302.gameServer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -26,8 +24,6 @@ import seng302.gameServer.messages.MarkRoundingMessage; import seng302.gameServer.messages.MarkType; import seng302.gameServer.messages.Message; import seng302.gameServer.messages.RoundingBoatStatus; -import seng302.gameServer.messages.YachtEventCodeMessage; -import seng302.gameServer.messages.YachtEventType; import seng302.model.GeoPoint; import seng302.model.Limit; import seng302.model.Player; @@ -359,8 +355,8 @@ public class GameState implements Runnable { */ private void spawnNewToken() { tokensInPlay.clear(); - Token token = randomSpawn.getRandomTokenLocation(); -// token.assignType(TokenType.WIND_WALKER); + Token token = randomSpawn.getRandomToken(); +// token.assignType(TokenType.RANDOM); logger.debug("Spawned token of type " + token.getTokenType()); tokensInPlay.add(token); } diff --git a/src/main/java/seng302/utilities/RandomSpawn.java b/src/main/java/seng302/utilities/RandomSpawn.java index 929e5766..b09fc032 100644 --- a/src/main/java/seng302/utilities/RandomSpawn.java +++ b/src/main/java/seng302/utilities/RandomSpawn.java @@ -16,6 +16,7 @@ public class RandomSpawn { private static final Integer DEGREES_IN_CIRCLE = 360; private HashMap spawnRadii; + private Object[] spawnCentres; private Random random; /** @@ -27,6 +28,7 @@ public class RandomSpawn { random = new Random(); spawnRadii = generateSpawnRadii(markOrder); + spawnCentres = spawnRadii.keySet().toArray(); } private HashMap generateSpawnRadii(List markOrder) { @@ -48,9 +50,8 @@ public class RandomSpawn { * @return A random token type at a random location in a random radii of the set of possible * radii */ - public Token getRandomTokenLocation() { - Object[] keys = spawnRadii.keySet().toArray(); - GeoPoint randomSpawnCentre = (GeoPoint) keys[random.nextInt(keys.length)]; + public Token getRandomToken() { + GeoPoint randomSpawnCentre = (GeoPoint) spawnCentres[random.nextInt(spawnCentres.length)]; Double spawnRadius = spawnRadii.get(randomSpawnCentre); Double randomDistance = spawnRadius * random.nextDouble(); Double randomAngle = random.nextDouble() * DEGREES_IN_CIRCLE; diff --git a/src/main/resources/icons/ayy_lmao.gif b/src/main/resources/icons/ayy_lmao.gif deleted file mode 100644 index 884b1f5098b7b3acaa6e3a4e984bde490f32e7d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23971 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 diff --git a/src/main/resources/icons/bumperIcon.png b/src/main/resources/icons/bumperIcon.png index c1da6d4cf8d72949af1b64aa7e60335804d5d7b5..1a72302880f8d7f272fffb92548db8a422240c6e 100644 GIT binary patch literal 7950 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 U4yb(b?=Ga7k(FT`>~{SB0iX?9EdT%j literal 3580 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 literal 3205 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 literal 4328 zcmVP)@*930Wn(Z|Qf@$vB}C@A{+`h$amBO@b9N=obN>)hPjadC03uCDd< z_1W3k;^N|*oSebI!L+orVPRocS65F@PdGR@dwY9DMMXV5J&lcxX=!Psq@)-a7?hNh zZEbCTe}51V5W__54gdfUE=fc|RCt{2UFm|NFcQWj+?U)Sm$bU?|A>o6w~z`X3JA07 z_iv_EQlwIMRV3+)FTVKVi!Z+T1Pou(oEySGQI#OP|3lM!W9_9)hJU3gZp{5LJ(Lx) ze&j2DEL4?;^td;h_)4$8_A=jt@2asE6={hrix7+vLc1s0e_>3BW#KX{A}?0me~iqq z-M6$vmQHY&E*20bx`oQL-Oq6CWedVrn@ehAol9GvP959nrBOG(m7%xK${fE$-IGe3 zm)YLi@K*XsdR^u_)ODytyENJ2P8ynWLGmQ(o>a={qG;RFBu>y|KV%N_9z>F#tO=a8 z$@)W>6uE_GBkvAuEU^BPi%ZR;cC-C^0W_EGu_#YD@VKN=XB z$_H(LFgg(N#Ua#s4>F}Nar-|ydF2?B!{@}RcNE>gmNwbSv;C>{08drt{|`;;J)ZVi zZppf+GR5fVeS|)@sDw5U5!uR$C~lqjTeu?0JAbRQ1#T70Q~ECHTH*iIIvPpfqvgSH%8+4}^juptT`aFV@U z?YM6b>-C~SH8^urMC;#`$4Fs)mqXzdN^~r_{-t#e7NgHdY<-+QJwKk}0p%<}VQWUj zr(@1BBy>AjNN+e18RbxdvpN};1VR+HaFaU2qp}Q>Xc03`DMkez*3U^WW?P9;*luKy zoJWs4A_emxOhcTFK2uIr&U+Q7u!Wa2|KW=87b$dqc#o8$*<32E<`THWDeqTar0~c$ zedW27e`tz$f?Ph0yXPwBephXafD5h|Nay{a6TIJm9O5ULk9() zCz?0fe&9SHy_S@|03tPKV_Fig+m0~eY`0POpK66tJ*2M4ev-KHFYI@RZU;%b^cI3j zo}CfcRgQ3#@;(>$l|8w%M5u=W_tCmcP#VU7nn3yege(1(=mn2s!lbE9u5sWLJr@R* zHzU+7ayE%Fk9S50Y}f?ivbmGuNBu*J<}nskDroi4>`qg%WrF;+b>r=rFO3}3-(L*+ zLY)+Is9>XSui^ApEEy_0#D{ioMyL|D=}YX6fNt{MqwG2SHmxVVQQkcN|5ALilG4l9y9{a2HzHLT04BA{Usn4iG!1f2)sS^OCzc|IHM zfNiavm|DYXSidz4l@-%NIg5hmk3@2wC$ZrEiA-cMtRFvOk%;h1M=@k-9z9~}o#t+D zBE5X$xU=BL*_}j!OcqlZ5lh_Q@`d+n6s(^ZN>dtMC0j&Cm-$Mzg1u!~x_%r;8v0CI zOE1I~f5c?;L3M6~if7jDoY8hC_M#eQ27aLxOfkXp-~JG@RJ}g#;Tp4tp+|qwNf}(m zH~k+7Q-M#bAWjsBo!o47cgxS#^&Sqear=6)QqEu`2O_)ehp_^tY~f3A|qMEH;roskILI9 zf`kYe`6;~~|4zWj7Fe?On}YJeO&rQmAaYo-PL~5lJZU(W z!7>saH14uFbIP-#0gN|jf;q)VRuW-e%5N$ zxA6XI@=Z1*NhhH9*_)`2Z{cklH|b+YK4TLZN6KeLpXhh9O-A*=Ch7&`wm__~+1sK> z)fj3YTBj$lg{1GtH-ZE9S{Ay$wgLD+}!`P|aa9(6H%y7{D?7tN#g`!;=H>OxdOP;3fsq zQR@hpP*4V76Dca_+hdyi(oBnZ+O{5sv~RYoG=m@9hIm7 zCPh$Tt_35kk@e78I3G@+ooJQVt>RD*qdUANG-a><7VVAusPLq9KJDfGzX{Yde*{LD z9OXX=f_BBIBnNEzjMqRpL|!axSOmTjWu3+nVn^dymj+Prrw>992hcy;bjyO4n0B`d ziKH<{&}p>g_SD`WV@O*(pGVjntDfDjk8|h^w7h&~6BBxSt z2XKUc`v-iGZ~9r&yf)O7n!SW@C1guEbXrM&)W?Lh5JL89j=bS`C=9E zaxR%u8uW_oDGkA=1BU2UuNczD(E6;K>qix0cBmu$<*=6uXu1A^N*zyBE!Xc3;0Gpw z(&f%!m57$_BcS8^@^!c9dgoh^?K23~Js!#>5N_`f10dvft&KKlQAz~$>r@zlms&&V zE~H=H;UtG{L?fml8MORaaPUMJC!Xge*CCg_ddh}pytTgkTZ_PP7<7xrH7{blo~(wy8`)#IJ#PJo^Pti47lU z_Y?3Mc@?OyLm8d>@&v-gbbFCjh^;qIPfWnadf!1kS?(}u>(~U6x3qldT{_DxXi0+$ ziE&0S`2$APR1-&V3&~q9O_-K>XSvXdz+g5gHuU5C?z_v###cXzIg$`=s>IMuao9o8-vkV0FX=k&s=wU0)>Nx$9Iqy zqfK3Ke)zSOMm5H!1EPs< z&q&UQpzeABH7O%CoZ3&Mbux9x^cb5?WK!vt4nI7Xdno7)!$+VsNgcXRgflpc%;xvU z7EX@|8QtZ&4hScnf%l}(-i&7qwmFa&rXUDD3s6`{bS=#SQH*fF_P~==1rs2OLF$i0 zq7>%vQ(Vm-)ni4X=tfiS=)8B3Fy=dvB}Q|DLRu@9Tsk7WNR_O5q}*>zj*C2pHelpH z(x@VoWn$jf%LY0%YFJT7}Q(}w^u zPxq=XP`QU@nSQti96IWC*9$<0?BuOYy%L2Bj^X{l!Q&}B*==J0d-cd?1+aMd-xM{# zctQ4E@r59TxPf+yci@ADi$`%I93{C@8T3@pRhG|Toe7;EE~s`O#&j2Dl97fjD5{uco5;lNL=2V%CU{P)ZnhgN#scr(m^ z4G)vrEWjAAo}+7mRvb#2262n6bk1IjjlEc*9Cg5?sGIf#GqCzKm|>LeFhcg*3HN!S zM8}UT4NJV$14jbO%+Ge!dZ($q5`=xA%;I!k(g6Yoa&~pM!I5^|? z1u6T#OePaP z5uM^UP}XqUF0H}+ezJw{$P@sTa78-4Hh+Oei*_@pFM^3+*Kt|OfgXYS!G@Xv09ufI zP<=S442)3<0h{V|1Mf{=KbXp>lf$?MJ(N2gEEHbsR56N|$x7FpKf$U5Y>y*YD6~64 z*pvzD^?tIM;WIBrfD46s4fJ2Kfb#X74xyT3W*=-KP^^izFfSt#xZ+x7<(J0`&~7g# z-vWV>WR>cFoTLzdz z)OjhDxxT&Dg=&u6VULFD;s-Yo>F0qPU)F;bpNFnQ9NNMbHTx@i?yaroS#T1@kcr!d z3^z8x5t3I1m0!LN>r>E7!mv}2Jt8m|ElPN^^xe=v3kQ!)VuKTTclbli>+gXvWF1wb z30_zmj`Fqrr7;9>PClm|$bjP1+T{G_2f%C{{)H+kj0W&-CteXgtgt3Ly=?$ze|?8( z7zUW{Z1GE+UAmxG-a-U%2z1I29|WWZ6}3XI^piNs9C$%^hkfCXu!zv0f?2|&PMp{* z<#|w=vFJnl!56_Gi_+~`5We)mEoA}ER1Ap7rJ#dv|hT|rxqb*u*INaLwKIUkOnh(+Z zOxm*PhRW-FQz#DoBJ>T_xx(R+b70Y?ffw86s;qa(}S1G%hCR1d1)BwcafY+FTG zk}`W{W^;*I#hx|0W^ax_NeUA4V9k;swGL*o&Dk8j6R2|_SZaLD7hinw#TQ@v-{QZP WwD@H(bH7^v0000IV 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

NjDcovL8dV#(yV*wW1+P!0ZtW(I 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 IBis`H2b;K4)&Kwi literal 3939 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 - + diff --git a/src/test/java/seng302/utilities/RandomSpawnTest.java b/src/test/java/seng302/utilities/RandomSpawnTest.java index 60966ab8..a50d3b11 100644 --- a/src/test/java/seng302/utilities/RandomSpawnTest.java +++ b/src/test/java/seng302/utilities/RandomSpawnTest.java @@ -39,7 +39,7 @@ public class RandomSpawnTest { .getDirtyMidPoint(compoundMark1.getMidPoint(), compoundMark2.getMidPoint()); Double maxDistance = GeoUtility.getDistance(testMidPoint, compoundMark2.getMidPoint()); for (int i = 0; i < 1000; i++) { - Token token = randomSpawn.getRandomTokenLocation(); + Token token = randomSpawn.getRandomToken(); Double distanceFromCentreRadius = GeoUtility.getDistance(testMidPoint, token); assertTrue("Out of bounds token", distanceFromCentreRadius <= maxDistance); } From fd53fd52a4340212f44edb62701c1ad9be94ea22 Mon Sep 17 00:00:00 2001 From: William Muir Date: Wed, 27 Sep 2017 12:44:50 +1300 Subject: [PATCH 33/33] Forgot to git add the new icon.. again.. sorree #story[1293] --- src/main/resources/icons/slowedIcon.png | Bin 0 -> 9582 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/icons/slowedIcon.png diff --git a/src/main/resources/icons/slowedIcon.png b/src/main/resources/icons/slowedIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ac48acf05e175bebfa9d7e1b07c8e6be81eabcf GIT binary patch literal 9582 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