diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 0382ed5a..e958e18c 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,14 +1,7 @@ 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; -import java.util.Map; -import java.util.Random; -import java.util.Set; +import java.util.*; + import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,8 +13,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; @@ -34,6 +25,8 @@ import seng302.model.stream.xml.parser.RaceXMLData; 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; /** @@ -51,42 +44,55 @@ 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 Long POWERUP_TIMEOUT_MS = 10_000L; + //Wind Constants + private static final int MAX_WIND_SPEED = 12000; + private static final int MIN_WIND_SPEED = 8000; - 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 + //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; - private static final Integer VELOCITY_BOOST_MULTIPLIER = 2; + + //Powerup Constants + 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 = 30_000L; private static Long previousUpdateTime; public static Double windDirection; private static Double windSpeed; - private static Double speedMultiplier = 1d; + private static Double serverSpeedMultiplier; private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat. private static Boolean playerHasLeftFlag; + private static String hostIpAddress; private static List players; private static Map yachts; private static Boolean isRaceStarted; 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; @@ -101,14 +107,16 @@ public class GameState implements Runnable { players = new ArrayList<>(); customizationFlag = false; playerHasLeftFlag = false; - speedMultiplier = 1.0; + serverSpeedMultiplier = 1.0; currentStage = GameStages.LOBBYING; isRaceStarted = false; - //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); newMessageListeners = new ArrayList<>(); - allTokens = makeTokens(); + marks = new MarkOrder().getAllMarks(); + randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks()); + resetStartTime(); + //setCourseLimit("/server_config/race.xml"); new Thread(this, "GameState").start(); //Run the auto updates on the game state } @@ -120,24 +128,6 @@ public class GameState implements Runnable { courseLimit = raceXMLData.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 Set getMarks() { - return Collections.unmodifiableSet(marks); - } - public static List getPlayers() { return players; } @@ -146,6 +136,10 @@ public class GameState implements Runnable { return tokensInPlay; } + public static Set getMarks() { + return Collections.unmodifiableSet(marks); + } + public static void addPlayer(Player player) { players.add(player); String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() @@ -238,12 +232,75 @@ 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(); } } } + /** + * 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, TOKEN_SPAWN_TIME); + } + + // 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) { @@ -278,14 +335,11 @@ 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() { - Random random = new Random(); + private void spawnNewToken() { tokensInPlay.clear(); - - //Get a random token location with random type - Token token = allTokens.get(random.nextInt(allTokens.size())); - token.assignRandomType(); - + Token token = randomSpawn.getRandomToken(); +// token.assignType(TokenType.RANDOM); + logger.debug("Spawned token of type " + token.getTokenType()); tokensInPlay.add(token); } @@ -303,14 +357,12 @@ 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); - checkPowerUpTimeout(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); + preformTokenUpdates(yacht); //This update must be done before collision. Sorta hacky checkCollision(yacht); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { checkForLegProgression(yacht); @@ -324,17 +376,136 @@ public class GameState implements Runnable { } - private void checkPowerUpTimeout(ServerYacht yacht) { - if (yacht.getPowerUp() != null) { - if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) { - yacht.powerDown(); - sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired"); - logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + /** + * 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) { + Token collidedToken = checkTokenPickUp(yacht); + if (collidedToken != null) { + tokensInPlay.remove(collidedToken); + powerUpYacht(yacht, collidedToken); + } + + checkPowerUpTimeout(yacht); + TokenType powerUp = yacht.getPowerUp(); + + if (powerUp != null) { + switch (powerUp) { + case WIND_WALKER: + windWalk(yacht); + break; + case BUMPER: + ServerYacht collidedYacht = checkYachtCollision(yacht, true); + if (collidedYacht != null) { + yacht.powerDown(); + boatTempShutDown(collidedYacht); + notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht)); + notifyMessageListeners( + MessageFactory.makeStatusEffectMessage(collidedYacht, powerUp)); + } + break; + case RANDOM: + yacht.setPowerUpSpeedMultiplier(BAD_RANDOM_SPEED_PENALTY); } } } + /** + * Powers up a thisYacht with the given token type. + * + * @param thisYacht The yacht to be powered up + * @param collidedToken The token which this thisYacht collided with + */ + 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(); + } + + //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 = + thisYacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName() + + " token"; + notifyMessageListeners( + MessageFactory.makeChatterMessage(thisYacht.getSourceId(), logMessage)); + notifyMessageListeners(MessageFactory.getRaceXML()); + notifyMessageListeners(MessageFactory.makePickupMessage(thisYacht, collidedToken)); + logger.debug( + "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 :/ + + /** + * Disables the given boat for BUMPER_DISABLE_TIME ms. + * + * @param yacht The yacht to disable + */ + private void boatTempShutDown(ServerYacht yacht) { + yacht.setPowerUpSpeedMultiplier(0d); + 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. + * + * @param yacht The yacht to check to power down + */ + private void checkPowerUpTimeout(ServerYacht yacht) { + if (yacht.getPowerUp() != null) { + if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp() + .getTimeout()) { + powerDownYacht(yacht); + } + } + } + + + /** + * This function changes the wind to be at an angle that causes the yacht in question to be at + * its fastest velocity + * + * @param yacht The yacht to fix the wind for + */ + private void windWalk(ServerYacht yacht) { + Double optimalAngle = PolarTable.getOptimalAngle(); + Double heading = yacht.getHeading(); + windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L); + } + + /** * Check if the yacht has crossed the course limit * @@ -356,13 +527,15 @@ 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 + * + * @return The token collided with */ - private static Token checkTokenPickUp(ServerYacht serverYacht) { + private Token checkTokenPickUp(ServerYacht yacht) { 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; } @@ -385,9 +558,8 @@ 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); - Token collidedToken = checkTokenPickUp(serverYacht); if (collidedYacht != null) { GeoPoint originalLocation = serverYacht.getLocation(); @@ -430,50 +602,36 @@ 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 * yacht.getMaxSpeedMultiplier(); - if (yacht.getPowerUp() != null) { - if (yacht.getPowerUp().equals(TokenType.BOOST)) { - maxBoatSpeed *= VELOCITY_BOOST_MULTIPLIER; - } - } + Double maxBoatSpeed = + 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); } @@ -536,7 +694,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); @@ -572,7 +733,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; } } @@ -676,7 +839,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; } } @@ -699,6 +865,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; @@ -711,7 +878,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) { @@ -740,15 +907,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; } } @@ -784,13 +958,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+"); @@ -798,17 +965,19 @@ public class GameState implements Runnable { switch (words[2].trim()) { case "/speed": try { - setSpeedMultiplier(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; } @@ -865,11 +1034,7 @@ public class GameState implements Runnable { currentStage = GameStages.FINISHED; } - public static void setSpeedMultiplier (double multiplier) { - speedMultiplier = multiplier; - } - - public static double getSpeedMultiplier () { - return speedMultiplier; + public static double getServerSpeedMultiplier() { + return serverSpeedMultiplier; } } diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index d2b35222..e54a62e4 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -30,9 +30,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; @@ -172,63 +169,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 * diff --git a/src/main/java/seng302/gameServer/MessageFactory.java b/src/main/java/seng302/gameServer/MessageFactory.java index 254b6e22..ae6ea176 100644 --- a/src/main/java/seng302/gameServer/MessageFactory.java +++ b/src/main/java/seng302/gameServer/MessageFactory.java @@ -1,6 +1,20 @@ package seng302.gameServer; import seng302.gameServer.messages.*; +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; +import seng302.gameServer.messages.RaceStatusMessage; +import seng302.gameServer.messages.RaceType; +import seng302.gameServer.messages.XMLMessage; +import seng302.gameServer.messages.XMLMessageSubType; +import seng302.gameServer.messages.YachtEventCodeMessage; +import seng302.gameServer.messages.YachtEventType; import seng302.model.Player; import seng302.model.ServerYacht; import seng302.model.stream.xml.generator.RaceXMLTemplate; @@ -146,6 +160,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()) { @@ -167,4 +189,39 @@ 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/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index f83eb540..d7e16de9 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -71,7 +71,6 @@ public class ServerToClientThread implements Runnable { private List connectionListeners = new ArrayList<>(); private DisconnectListener disconnectListener; - private ServerYacht yacht; private Player player; private SimpleObjectProperty raceXMLProperty = new SimpleObjectProperty<>(); @@ -97,11 +96,11 @@ public class ServerToClientThread implements Runnable { } private void setUpPlayer(){ - String fName = "Player" + GameState.getNumberOfPlayers().toString(); - String lName = ""; + String shortName = "P" + sourceId; + String longName = "Player " + sourceId; + 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); @@ -293,10 +292,6 @@ public class ServerToClientThread implements Runnable { return socket; } - public ServerYacht getYacht() { - return yacht; - } - public void addConnectionListener(ConnectionListener listener) { connectionListeners.add(listener); } 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; } } diff --git a/src/main/java/seng302/gameServer/messages/YachtEventType.java b/src/main/java/seng302/gameServer/messages/YachtEventType.java index c8a997a0..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), @@ -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 c1ca53f9..9e38b781 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -19,6 +19,7 @@ import javafx.scene.paint.Color; import jdk.nashorn.internal.objects.annotations.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import seng302.model.token.TokenType; import seng302.visualiser.fxObjects.assets_3D.BoatMeshType; import seng302.model.token.TokenType; import seng302.visualiser.fxObjects.assets_3D.BoatObject; @@ -41,11 +42,26 @@ 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); + } + + + private Logger logger = LoggerFactory.getLogger(ClientYacht.class); @@ -76,6 +92,9 @@ 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 List colorChangeListeners = new ArrayList<>(); + private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper(); private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); @@ -219,6 +238,21 @@ 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) { + listener.notifyPowerDown(this); + } + } + + /** + * 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) { @@ -279,6 +313,9 @@ public class ClientYacht extends Observable { public void setColour(Color colour) { this.colour = colour; + for (ColorChangeListener listener : colorChangeListeners) { + listener.notifyColorChange(this); + } } public void updateLocation(double lat, double lng, double heading, double velocity) { @@ -308,6 +345,14 @@ public class ClientYacht extends Observable { powerUpListeners.add(listener); } + public void addPowerDownListener(PowerDownListener listener) { + 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/model/GameKeyBind.java b/src/main/java/seng302/model/GameKeyBind.java index 1c765adc..5627625c 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)); } @@ -47,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/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/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 */ diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 5822b248..c092447d 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -23,9 +23,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; @@ -55,6 +55,8 @@ public class ServerYacht { //PowerUp private TokenType powerUp; private Long powerUpStartTime; + private Double powerUpSpeedMultiplier; + private Integer powerUpHandlingMultiplier; //turning mode private Boolean continuouslyTurning; @@ -78,11 +80,11 @@ public class ServerYacht { this.legNumber = 0; this.boatColor = Colors.getColor(sourceId - 1); this.powerUp = null; - + this.powerUpSpeedMultiplier = 1d; + this.powerUpHandlingMultiplier = 1; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; this.hasPassedThroughGate = false; - this.continuouslyTurning = false; } @@ -110,13 +112,33 @@ 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 = 1d; + this.powerUpHandlingMultiplier = 1; } public Long getPowerUpStartTime() { @@ -133,7 +155,7 @@ public class ServerYacht { * @param amount the amount by which to adjust the boat heading. */ public void adjustHeading(Double amount) { - Double newVal = heading + (amount * turnStepMultiplier); + Double newVal = heading + amount * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier; lastHeading = heading; heading = (double) Math.floorMod(newVal.longValue(), 360L); } @@ -156,11 +178,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 +200,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 +288,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); } @@ -434,18 +457,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; } @@ -456,4 +479,20 @@ public class ServerYacht { public void setContinuouslyTurning(Boolean continuouslyTurning) { this.continuouslyTurning = continuouslyTurning; } + + public Double getPowerUpSpeedMultiplier() { + return powerUpSpeedMultiplier; + } + + public void setPowerUpSpeedMultiplier(Double powerUpSpeedMultiplier) { + this.powerUpSpeedMultiplier = powerUpSpeedMultiplier; + } + + public Integer getPowerUpHandlingMultiplier() { + return powerUpHandlingMultiplier; + } + + public void setPowerUpHandlingMultiplier(Integer powerUpHandlingMultiplier) { + this.powerUpHandlingMultiplier = powerUpHandlingMultiplier; + } } diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index bfcffc58..99d080ed 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -13,7 +13,9 @@ import seng302.model.stream.xml.parser.RaceXMLData; */ public class MarkOrder { private List raceMarkOrder; + private List orderedUniqueCompoundMarks; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + private List allMarks; public MarkOrder(RaceXMLData raceXMLData){ @@ -39,6 +41,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) diff --git a/src/main/java/seng302/model/token/Token.java b/src/main/java/seng302/model/token/Token.java index fcdbab5e..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; } @@ -40,5 +53,10 @@ public class Token extends GeoPoint { 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/utilities/RandomSpawn.java b/src/main/java/seng302/utilities/RandomSpawn.java new file mode 100644 index 00000000..b09fc032 --- /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 Object[] spawnCentres; + 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); + spawnCentres = spawnRadii.keySet().toArray(); + } + + private HashMap generateSpawnRadii(List 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 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; + GeoPoint randomLocation = GeoUtility + .getGeoCoordinate(randomSpawnCentre, randomAngle, randomDistance); + return new Token(randomLocation); + + } + +} diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 57c2ff38..81f23da8 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -1,14 +1,25 @@ package seng302.visualiser; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collections; +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; 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; +import javafx.scene.paint.Color; import javafx.util.Pair; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; @@ -37,6 +48,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; import java.io.IOException; import java.text.SimpleDateFormat; @@ -166,10 +178,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()); }); } @@ -413,9 +427,16 @@ 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 { + 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()) { + showDisableAlert(thisYacht); + } else { //Else all token pickup types TokenType tokenType = null; if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) { tokenType = TokenType.BOOST; @@ -429,36 +450,36 @@ public class GameClient { tokenType = TokenType.WIND_WALKER; } - showTokenPickUp(tokenType); - allBoatsMap.get(yachtEventData.getSubjectId().intValue()).setPowerUp(tokenType); + Sounds.playTokenPickupSound(); + thisYacht.setPowerUp(tokenType); } } + + /** + * 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() - ) - ); - } - - // 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; - } + raceState.storeCollision(yacht); } private void formatAndSendChatMessage(String rawChat) { diff --git a/src/main/java/seng302/visualiser/GameView3D.java b/src/main/java/seng302/visualiser/GameView3D.java index e4b3d22a..4edfc9e2 100644 --- a/src/main/java/seng302/visualiser/GameView3D.java +++ b/src/main/java/seng302/visualiser/GameView3D.java @@ -1,6 +1,8 @@ package seng302.visualiser; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -8,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; @@ -22,6 +25,9 @@ import javafx.scene.transform.Translate; import org.fxyz3d.scene.Skybox; 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.ScaledPoint; import seng302.model.mark.CompoundMark; @@ -49,17 +55,27 @@ import seng302.visualiser.fxObjects.assets_3D.ModelType; public class GameView3D extends GameView{ 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 = 100; private Group root3D; private SubScene view; - 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; + private boolean horizontalInversion = false; + + private double distanceScaleFactor; + private ScaleDirection scaleDirection; + private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint; + private double referencePointX, referencePointY; private Group raceBorder = new Group(); /* Note that if either of these is null then values for it have not been added and the other @@ -75,31 +91,28 @@ public class GameView3D extends GameView{ private Double windDir; private Skybox skybox; + private enum ScaleDirection { + HORIZONTAL, + VERTICAL + } + public GameView3D () { - canvasWidth = canvasHeight = 220; + isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y); + topDownCam = new TopDownCamera(); + chaseCam = new ChaseCamera(); - camera = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH); - camera.setFarClip(100000); - camera.setNearClip(0.1); - camera.setFieldOfView(FOV); - - camera2 = new TopDownCamera(); - camera2.setFarClip(100000); - camera2.setNearClip(0.1); - camera2.setFieldOfView(FOV); - - camera3 = new ChaseCamera(); - camera3.setFarClip(100000); - 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, 5000, 3000, true, SceneAntialiasing.BALANCED ); - view.setCamera(camera); - camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0))); + view.setCamera(isometricCam); skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, camera); skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS)); @@ -274,48 +287,44 @@ public class GameView3D extends GameView{ } 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: - ((RaceCamera) view.getCamera()).zoomIn(); - break; - case X: - ((RaceCamera) view.getCamera()).zoomOut(); - break; - case W: - ((RaceCamera) view.getCamera()).panUp(); - break; - case S: - ((RaceCamera) view.getCamera()).panDown(); - break; - case A: - ((RaceCamera) view.getCamera()).panLeft(); - break; - case D: - ((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); - } + GameKeyBind keyBinds = GameKeyBind.getInstance(); + KeyAction keyPressed = keyBinds.getKeyAction(event.getCode()); + 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; + } + } + } + + 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); } } @@ -334,16 +343,13 @@ public class GameView3D extends GameView{ 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 = scaledPoint.findScaledXY(lat, lon); - bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); - }); + clientYacht.addLocationListener(this::updateBoatLocation); + clientYacht.addColorChangeListener(this::updateBoatColor); 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(() -> { @@ -356,6 +362,23 @@ public class GameView3D extends GameView{ 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. diff --git a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java index 6c777f77..0647a473 100644 --- a/src/main/java/seng302/visualiser/cameras/ChaseCamera.java +++ b/src/main/java/seng302/visualiser/cameras/ChaseCamera.java @@ -1,22 +1,28 @@ 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 final Double ZOOM_STEP = 2.5; + private final Double PAN_STEP = 2.5; + private ObservableList transforms; private BoatObject playerBoat; - private ClientYacht playerYacht; + private Double zoomFactor; private Double horizontalPan; private Double verticalPan; @@ -25,97 +31,100 @@ 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) { + /** + * 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; - 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()); + } } + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ 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) ); } + /** + * 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 < -15.0 && zoomFactor + adjustment > -125.0) { + if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) { zoomFactor = zoomFactor + adjustment; repositionCamera(); } } + /** + * 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 >= -20 && verticalPan + adjustment <= 20) { + if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT + && verticalPan + adjustment <= VERTICAL_PAN_LIMIT) { verticalPan += adjustment; repositionCamera(); } } + /** + * 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(); + } + @Override public void zoomIn() { - adjustZoomFactor(5.0); + adjustZoomFactor(ZOOM_STEP); } @Override public void zoomOut() { - adjustZoomFactor(-5.0); + adjustZoomFactor(-ZOOM_STEP); } - - /* - 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(-PAN_STEP); } @Override public void panRight() { - this.horizontalPan += 5; - repositionCamera(); + adjustHorizontalPan(PAN_STEP); } @Override public void panUp() { - adjustVerticalPan(-5.0); + adjustVerticalPan(-PAN_STEP); } @Override public void panDown() { - adjustVerticalPan(5.0); + adjustVerticalPan(PAN_STEP); } } diff --git a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java index a4abeac4..85a0e502 100644 --- a/src/main/java/seng302/visualiser/cameras/IsometricCamera.java +++ b/src/main/java/seng302/visualiser/cameras/IsometricCamera.java @@ -1,47 +1,113 @@ 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 { - ObservableList transforms; + private final Double MIN_X = -120.0; + private final Double MAX_X = 125.0; - public IsometricCamera(Double cameraStartX, Double cameraStartY, Double cameraDepth) { + private final Double MIN_Y = 40.0; + private final Double MAX_Y = 170.0; + + private final Double PAN_LIMIT = 160.0; + private final Double NEAR_ZOOM_LIMIT = -50.0; + private final Double FAR_ZOOM_LIMIT = -160.0; + + 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(); + } + + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ + private void updateCamera() { + transforms.clear(); + transforms.addAll( + new Translate(horizontalPan, verticalPan, zoomFactor), + new Rotate(30, new Point3D(1, 0, 0)) + ); + } + + /** + * 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; + updateCamera(); + } + } + + /** + * 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; + updateCamera(); + } + } + + /** + * 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; + 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); } } diff --git a/src/main/java/seng302/visualiser/cameras/TopDownCamera.java b/src/main/java/seng302/visualiser/cameras/TopDownCamera.java index af17d553..72d58707 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,113 @@ 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; } + /** + * 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; - 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)); + /** + * Moves the camera to a new position after some change (Zooming or Panning) + */ + private void updateCamera() { + transforms.clear(); + transforms.addAll( + new Translate(playerBoat.getLayoutX() + horizontalPan, + playerBoat.getLayoutY() + verticalPan, zoomFactor) + ); + } + + /** + * 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; + updateCamera(); + } + } + + /** + * 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; + updateCamera(); + } + } + + /** + * 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; + 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/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 60887156..2eb44101 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -44,12 +44,14 @@ 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; 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; @@ -115,7 +117,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; @@ -136,6 +138,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(); @@ -213,6 +219,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel }); player.addPowerUpListener(this::displayPowerUpIcon); + player.addPowerDownListener(this::removeIcon); updateOrder(raceState.getPlayerPositions()); gameView = new GameView3D(); @@ -257,7 +264,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel */ private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) { if (yacht == player) { - final ImageView iconToDisplay; + if (iconToDisplay != null) { + iconToDisplay.setVisible(false); + } switch (tokenType) { case BOOST: @@ -272,6 +281,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel case BUMPER: iconToDisplay = bumperIcon; break; + case RANDOM: + iconToDisplay = badRandomIcon; + break; default: iconToDisplay = velocityIcon; } @@ -280,7 +292,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; @@ -290,16 +305,14 @@ 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); + iconToDisplay = null; } } diff --git a/src/main/java/seng302/visualiser/controllers/ServerListController.java b/src/main/java/seng302/visualiser/controllers/ServerListController.java index f7245ffe..2e133ceb 100644 --- a/src/main/java/seng302/visualiser/controllers/ServerListController.java +++ b/src/main/java/seng302/visualiser/controllers/ServerListController.java @@ -5,6 +5,12 @@ import com.jfoenix.controls.JFXDialog; import com.jfoenix.controls.JFXDialog.DialogTransition; import com.jfoenix.controls.JFXTextField; import com.jfoenix.validation.RequiredFieldValidator; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -32,6 +38,10 @@ import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.ResourceBundle; +import seng302.visualiser.controllers.dialogs.ServerCreationController; +import seng302.visualiser.validators.HostNameFieldValidator; +import seng302.visualiser.validators.NumberRangeValidator; +import seng302.visualiser.validators.ValidationTools; public class ServerListController implements Initializable, ServerListenerDelegate { @@ -63,6 +73,14 @@ public class ServerListController implements Initializable, ServerListenerDelega private Logger logger = LoggerFactory.getLogger(ServerListController.class); private JFXDialog directConnectDialog; + private JFXDialog serverCreationDialog; + private List serverCreationDialogListeners = new ArrayList<>(); + + @FunctionalInterface + public interface ServerCreationDialogListener { + + void notifyClosure(); + } // TODO: 12/09/17 ajm412: break this method down, its way too long. @Override @@ -166,6 +184,8 @@ public class ServerListController implements Initializable, ServerListenerDelega serverListHostButton.setOnAction(action -> { showServerCreationDialog(); }); + + addServerCreationDialogListener(this::closeServerCreationDialog); } /** @@ -176,9 +196,11 @@ public class ServerListController implements Initializable, ServerListenerDelega FXMLLoader dialogContent = new FXMLLoader(getClass().getResource( "/views/dialogs/ServerCreationDialog.fxml")); try { - JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(), + serverCreationDialog = new JFXDialog(serverListMainStackPane, dialogContent.load(), DialogTransition.CENTER); - dialog.show(); + ServerCreationController serverCreationController = dialogContent.getController(); + serverCreationController.setListener(serverCreationDialogListeners); + serverCreationDialog.show(); Sounds.playButtonClick(); } catch (IOException e) { e.printStackTrace(); @@ -205,6 +227,10 @@ public class ServerListController implements Initializable, ServerListenerDelega return dcDialog; } + private void closeServerCreationDialog() { + serverCreationDialog.close(); + } + /** * Validates the connection and attempts to connect to a given hostname and port number. */ @@ -303,4 +329,14 @@ public class ServerListController implements Initializable, ServerListenerDelega public void serverDetected(ServerDescription serverDescription, List servers) { Platform.runLater(() -> refreshServers(servers)); } + + private void addServerCreationDialogListener( + ServerCreationDialogListener serverCreationDialogListener) { + serverCreationDialogListeners.add(serverCreationDialogListener); + } + + private void removeServerCreationDialogListener( + ServerCreationDialogListener serverCreationDialogListener) { + serverCreationDialogListeners.remove(serverCreationDialogListener); + } } diff --git a/src/main/java/seng302/visualiser/controllers/ViewManager.java b/src/main/java/seng302/visualiser/controllers/ViewManager.java index edc288e1..38d21ae5 100644 --- a/src/main/java/seng302/visualiser/controllers/ViewManager.java +++ b/src/main/java/seng302/visualiser/controllers/ViewManager.java @@ -21,6 +21,7 @@ import seng302.gameServer.ServerAdvertiser; import seng302.utilities.Sounds; import seng302.visualiser.GameClient; import seng302.visualiser.controllers.dialogs.KeyBindingDialogController; +import seng302.visualiser.controllers.dialogs.PopupDialogController; import java.io.IOException; import java.util.HashMap; @@ -97,8 +98,6 @@ public class ViewManager { gameClient.stopGame(); System.exit(0); }); - - jfxSnackbar = new JFXSnackbar(decorator); } /** @@ -183,6 +182,7 @@ public class ViewManager { } }); + jfxSnackbar = new JFXSnackbar(decorator); } /** @@ -216,6 +216,7 @@ public class ViewManager { .getController(); keyBindingDialogController.setGameClient(this.gameClient); keyBindingDialog.show(); + decorator.requestFocus(); Sounds.playButtonClick(); } } @@ -225,6 +226,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/KeyBindingDialogController.java b/src/main/java/seng302/visualiser/controllers/dialogs/KeyBindingDialogController.java index 2a286ec4..f64edd3c 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,8 @@ 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(); @@ -76,12 +87,10 @@ public class KeyBindingDialogController implements Initializable { resetBtn.setOnMouseClicked(event -> { gameKeyBind.setToDefault(); loadKeyBind(); + showSnackBar("All keys reset!", false); }); closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog()); - - keyBindingDialogHeader.setFocusTraversable(true); - keyBindingDialogHeader.requestFocus(); } /** @@ -106,7 +115,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)); } } @@ -149,6 +158,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/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/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java b/src/main/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java index 717350d4..abf496c0 100644 --- a/src/main/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java +++ b/src/main/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java @@ -6,6 +6,7 @@ import com.jfoenix.controls.JFXSlider; import com.jfoenix.controls.JFXTextField; import com.jfoenix.validation.RequiredFieldValidator; import java.net.URL; +import java.util.List; import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -15,6 +16,7 @@ import javafx.scene.layout.AnchorPane; import seng302.gameServer.ServerDescription; import seng302.utilities.Sounds; import seng302.visualiser.MapMaker; +import seng302.visualiser.controllers.ServerListController.ServerCreationDialogListener; import seng302.visualiser.controllers.ViewManager; import seng302.visualiser.validators.FieldLengthValidator; import seng302.visualiser.validators.ValidationTools; @@ -31,6 +33,8 @@ public class ServerCreationController implements Initializable { @FXML private JFXButton submitBtn; @FXML + private Label closeLabel; + @FXML private JFXButton nextMapButton; @FXML private JFXButton lastMapButton; @@ -49,6 +53,8 @@ public class ServerCreationController implements Initializable { //---------FXML END---------// + private List serverCreationDialogListeners; + public void initialize(URL location, ResourceBundle resources) { legsSlider.setMax(10); legsSlider.setValue(4); @@ -86,6 +92,7 @@ public class ServerCreationController implements Initializable { mapHolder.getChildren().setAll(mapMaker.getCurrentGameView()); mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName()); + closeLabel.setOnMouseClicked(event -> notifyListeners()); } /** @@ -144,4 +151,14 @@ public class ServerCreationController implements Initializable { mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName()); } + public void setListener(List serverCreationDialogListeners) { + this.serverCreationDialogListeners = serverCreationDialogListeners; + } + + public void notifyListeners() { + for (ServerCreationDialogListener serverCreationDialogListener : serverCreationDialogListeners) { + serverCreationDialogListener.notifyClosure(); + } + } + } 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 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/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/css/dialogs/ServerCreation.css b/src/main/resources/css/dialogs/ServerCreation.css index ddbb3e1e..3bc2992c 100644 --- a/src/main/resources/css/dialogs/ServerCreation.css +++ b/src/main/resources/css/dialogs/ServerCreation.css @@ -45,3 +45,13 @@ .maxPlayers { -fx-font-size: 13px; } + +#closeLabel { + -fx-font-size: 30; + -fx-text-fill: -fx-pp-dark-text-color; +} + +#closeLabel:hover { + -fx-text-fill: red; + -fx-font-size: 33px; +} diff --git a/src/main/resources/icons/bumperIcon.png b/src/main/resources/icons/bumperIcon.png index c1da6d4c..1a723028 100644 Binary files a/src/main/resources/icons/bumperIcon.png and b/src/main/resources/icons/bumperIcon.png differ diff --git a/src/main/resources/icons/handlingIcon.png b/src/main/resources/icons/handlingIcon.png index ac7cdcda..3286804e 100644 Binary files a/src/main/resources/icons/handlingIcon.png and b/src/main/resources/icons/handlingIcon.png differ diff --git a/src/main/resources/icons/slowedIcon.png b/src/main/resources/icons/slowedIcon.png new file mode 100644 index 00000000..3ac48acf Binary files /dev/null and b/src/main/resources/icons/slowedIcon.png differ diff --git a/src/main/resources/icons/velocity.png b/src/main/resources/icons/velocity.png index f9f2c3d4..22ee1d72 100644 Binary files a/src/main/resources/icons/velocity.png and b/src/main/resources/icons/velocity.png differ diff --git a/src/main/resources/icons/windWalkerIcon.png b/src/main/resources/icons/windWalkerIcon.png index 44ef53e8..5e39ddba 100644 Binary files a/src/main/resources/icons/windWalkerIcon.png and b/src/main/resources/icons/windWalkerIcon.png differ 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 diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index 3fea7c51..2d0769af 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,5 +1,7 @@ + + @@ -7,11 +9,23 @@ + + + + + + + + + + + + + @@ -32,17 +46,15 @@ valignment="BOTTOM" vgrow="SOMETIMES"/> - + - + - + @@ -74,25 +86,20 @@ - - + + - + - + - + - + @@ -115,22 +122,21 @@ minWidth="90.0" prefWidth="90.0"/> - + - - + - + @@ -152,28 +158,27 @@ prefHeight="150.0" prefWidth="240.0" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM"> - + - + - \ No newline at end of file + 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 @@ + + + + + + - - + - - - - - - - - - - + + - - - - - 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seng302/gameServer/server/ChatCommandsTest.java b/src/test/java/seng302/gameServer/server/ChatCommandsTest.java index ae273d08..5fa601b3 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(); diff --git a/src/test/java/seng302/utilities/RandomSpawnTest.java b/src/test/java/seng302/utilities/RandomSpawnTest.java new file mode 100644 index 00000000..a50d3b11 --- /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.getRandomToken(); + Double distanceFromCentreRadius = GeoUtility.getDistance(testMidPoint, token); + assertTrue("Out of bounds token", distanceFromCentreRadius <= maxDistance); + } + + + } + +} \ No newline at end of file