Merge branch '1273_Skybox' into 'develop'

1273 skybox

Server Discovery:
 - Created server discovery server.
 - Implemented protocols to support matchmaking & room code connection
 - Improved error handling for server disconnections

Skybox:
 - Added a skybox
 - Added a terrain mesh

See merge request !79
This commit is contained in:
William Muir
2017-09-27 21:52:34 +13:00
90 changed files with 4390 additions and 2137 deletions
@@ -1,19 +1,23 @@
package seng302.visualiser;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.XMLParser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
@@ -25,8 +29,15 @@ import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.ViewManager;
/**
* A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -44,7 +55,12 @@ public class ClientToServerThread implements Runnable {
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
void notifyDisconnection(String message);
}
@FunctionalInterface
public interface ConnectionErrorListener {
void notifyConnectionError(String message);
}
private class ByteReadException extends Exception {
@@ -56,6 +72,7 @@ public class ClientToServerThread implements Runnable {
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ClientSocketListener> listeners = new ArrayList<>();
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
private ConnectionErrorListener connectionErrorListener = null;
private Thread thread;
private Socket socket;
@@ -68,7 +85,7 @@ public class ClientToServerThread implements Runnable {
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
public static final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
@@ -133,8 +150,10 @@ public class ClientToServerThread implements Runnable {
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ClientSocketListener csl : listeners)
csl.newPacket();
synchronized (this) {
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
}
}
} else {
@@ -150,6 +169,9 @@ public class ClientToServerThread implements Runnable {
logger.warn("Closed connection to server", 1);
notifyDisconnectListeners("Connection to server was terminated");
closeSocket();
ViewManager.getInstance().goToStartView();
ViewManager.getInstance().showErrorSnackBar("Server rejected connection.");
}
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
@@ -166,11 +188,23 @@ public class ClientToServerThread implements Runnable {
private void notifyDisconnectListeners (String message) {
if (socketOpen) {
for (DisconnectedFromHostListener listener : disconnectionListeners) {
listener.notifYDisconnection(message);
listener.notifyDisconnection(message);
}
}
}
private void handleConnectionError(String message){
if (connectionErrorListener != null){
connectionErrorListener.notifyConnectionError(message);
}
try {
this.socket.close();
} catch (IOException e) {
logger.error("Couldn't close socket");
}
}
/**
* Sends a request to the server asking for a source ID
*/
@@ -191,7 +225,7 @@ public class ClientToServerThread implements Runnable {
* @param packet The registration requests packet
*/
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 4));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
@@ -210,8 +244,10 @@ public class ClientToServerThread implements Runnable {
else{
alertErrorText = "Could not connect to server";
}
handleConnectionError("Server no longer available.");
notifyDisconnectListeners(alertErrorText);
closeSocket();
System.out.println();
}
/**
@@ -318,7 +354,9 @@ public class ClientToServerThread implements Runnable {
}
public void addStreamObserver (ClientSocketListener streamListener) {
listeners.add(streamListener);
synchronized (this){
listeners.add(streamListener);
}
}
public void removeStreamObserver (ClientSocketListener streamListener) {
@@ -326,11 +364,21 @@ public class ClientToServerThread implements Runnable {
}
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.add(listener);
synchronized (this){
disconnectionListeners.add(listener);
}
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.remove(listener);
synchronized (this){
disconnectionListeners.remove(listener);
}
}
public void setConnectionErrorListener(ConnectionErrorListener listener){
synchronized (this){
connectionErrorListener = listener;
}
}
private int readByte() throws ByteReadException {
@@ -347,6 +395,7 @@ public class ClientToServerThread implements Runnable {
notifyDisconnectListeners("Cannot read from server.");
closeSocket();
logger.warn("InputStream reach end of stream", 1);
handleConnectionError("Could not connect to server. Server is no longer available.");
}
return currentByte;
}
@@ -368,4 +417,25 @@ public class ClientToServerThread implements Runnable {
public int getClientId () {
return clientId;
}
public void sendXML(String path, String serverName, Integer legRepeats, Integer maxPlayers, Boolean tokensEnabled) {
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, serverName, legRepeats, maxPlayers, tokensEnabled
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaRace.getKey());
xmlGenerator.setRaceTemplate(regattaRace.getValue());
String regatta = xmlGenerator.getRegattaAsXml();
String race = xmlGenerator.getRaceAsXml();
sendByteBuffer(
new XMLMessage(
regatta, XMLMessageSubType.REGATTA, regatta.length()
).getBuffer()
);
sendByteBuffer(
new XMLMessage(
race, XMLMessageSubType.RACE, race.length()
).getBuffer()
);
}
}
@@ -49,6 +49,12 @@ import seng302.visualiser.controllers.RaceViewController;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.*;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
* with a JavaFX Pane to insert itself into.
@@ -111,21 +117,33 @@ public class GameClient {
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
getServerThread().setConnectionErrorListener((eMessage) -> {
ViewManager.getInstance().showErrorSnackBar(eMessage);
destroyClientToServerThread();
});
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
ViewManager.getInstance().showErrorSnackBar("There are no servers currently available.");
}
}
private void destroyClientToServerThread() {
socketThread.closeSocket();
socketThread = null;
}
/**
* Connect to a game as the host at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public ServerDescription runAsHost(String ipAddress, Integer portNumber, String serverName, Integer maxPlayers) {
public ServerDescription runAsHost(
String ipAddress, Integer portNumber, String serverName, Integer maxPlayers, String race,
Integer numLegs, Boolean tokensEnabled
) {
XMLGenerator.setDefaultRaceName(serverName);
GameState.setMaxPlayers(maxPlayers);
server = new MainServerThread();
@@ -134,7 +152,7 @@ public class GameClient {
} catch (IOException e) {
showConnectionError("Cannot connect to server as host");
}
socketThread.sendXML(race, serverName, numLegs, maxPlayers, tokensEnabled);
while (regattaData == null){
try {
Thread.sleep(100);
+19 -431
View File
@@ -1,443 +1,31 @@
package seng302.visualiser;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.Group;
import javafx.scene.Node;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.*;
import java.util.*;
/**
* Created by cir27 on 20/07/17.
* Abstract class for keeping functionality common between race visualisation.
*/
public class GameView extends Pane {
public abstract class GameView {
private double bufferSize = 50;
private double horizontalBuffer = 0;
double canvasWidth, canvasHeight;
ScaledPoint scaledPoint;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
List<Limit> borderPoints;
Group gameObjects = new Group();
Group markers = new Group();
Group tokens = new Group();
List<CompoundMark> course = new ArrayList<>();
List<CompoundMark> compoundMarks = new ArrayList<>();
List<Corner> courseOrder = new ArrayList<>();
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private Polygon raceBorder = new CourseBoundary();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker2D> markerObjects;
private ObservableList<Node> gameObjects;
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private ImageView mapImage = new ImageView();
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public GameView () {
gameObjects = this.getChildren();
gameObjects.addAll(mapImage, raceBorder, markers, tokens);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* 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.
*
* @param border the race border to be drawn.
*/
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
rescaleRace(new ArrayList<>(border));
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
public void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
referencePointX += horizontalBuffer;
}
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void setSize(Double width, Double height){
this.canvasWidth = width;
this.canvasHeight = height;
}
public void setHorizontalBuffer(Double buff){
this.horizontalBuffer = buff;
}
public abstract Node getAssets();
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
public abstract void updateBorder(List<Limit> border);
}
+43 -203
View File
@@ -16,22 +16,19 @@ import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
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.*;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.Sounds;
import seng302.visualiser.cameras.ChaseCamera;
@@ -42,6 +39,7 @@ import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
import seng302.visualiser.fxObjects.assets_3D.Marker3D;
import seng302.visualiser.fxObjects.assets_3D.Model;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
@@ -49,61 +47,43 @@ import seng302.visualiser.fxObjects.assets_3D.ModelType;
* Collection of animated3D assets that displays a race.
*/
public class GameView3D {
public class GameView3D extends GameView{
private final double FOV = 60;
private final double DEFAULT_CAMERA_X = 0;
private final double DEFAULT_CAMERA_Y = 155;
private final double DEFAULT_CAMERA_Y = 100;
private Group root3D;
private SubScene view;
private Group gameObjects;
private Group raceBorder = new Group();
// 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
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker3D> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private BoatObject selectedBoat = null;
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private List<Node> mapTokens;
private AnimationTimer playerBoatAnimationTimer;
private Group trail = new Group();
private Double windDir;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private Skybox skybox;
public GameView3D () {
isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y);
topDownCam = new TopDownCamera();
chaseCam = new ChaseCamera();
canvasWidth = canvasHeight = 300;
for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) {
pc.setFarClip(600);
pc.setFarClip(100000);
pc.setNearClip(0.1);
pc.setFieldOfView(FOV);
}
@@ -111,14 +91,21 @@ public class GameView3D {
gameObjects = new Group();
root3D = new Group(isometricCam, gameObjects);
view = new SubScene(
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
);
view.setCamera(isometricCam);
skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, isometricCam);
skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
Model land = ModelFactory.importModel(ModelType.LAND_SMOOTH);
land.getAssets().getTransforms().add(new Rotate(90, Rotate.X_AXIS));
gameObjects.getChildren().addAll(
ModelFactory.importModel(ModelType.OCEAN).getAssets(),
raceBorder, trail, markers, tokens
raceBorder, trail, markers, tokens, skybox, land.getAssets()
);
view.sceneProperty().addListener((obs, old, scene) -> {
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
@@ -126,8 +113,10 @@ public class GameView3D {
});
}
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
compoundMarks = newCourse;
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
@@ -178,11 +167,13 @@ public class GameView3D {
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
}));
@@ -202,7 +193,7 @@ public class GameView3D {
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
markerObjects.put(observableMark, new Marker3D(markerType));
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
@@ -217,8 +208,8 @@ public class GameView3D {
* @return the new gate.
*/
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
Point2D m1Location = findScaledXY(m1);
Point2D m2Location = findScaledXY(m2);
Point2D m1Location = scaledPoint.findScaledXY(m1);
Point2D m2Location = scaledPoint.findScaledXY(m2);
Group barrier = ModelFactory.importModel(gateType).getAssets();
barrier.getTransforms().addAll(
@@ -276,144 +267,6 @@ public class GameView3D {
}
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
-100 + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = -100 + canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = -100 + canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = -100 + bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = -100 + canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void cameraMovement(KeyEvent event) {
GameKeyBind keyBinds = GameKeyBind.getInstance();
KeyAction keyPressed = keyBinds.getKeyAction(event.getCode());
@@ -456,19 +309,6 @@ public class GameView3D {
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
@@ -516,7 +356,7 @@ public class GameView3D {
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);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
}
@@ -529,18 +369,20 @@ public class GameView3D {
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true
);
}
List<Node> boundaryAssets = new ArrayList<>();
Point2D lastLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D lastLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(lastLocation.getX());
pylon.setLayoutY(lastLocation.getY());
boundaryAssets.add(pylon);
for (int i=1; i<border.size(); i++) {
Point2D location = findScaledXY(border.get(i).getLat(), border.get(i).getLng());
Point2D location = scaledPoint.findScaledXY(border.get(i).getLat(), border.get(i).getLng());
pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(location.getX());
pylon.setLayoutY(location.getY());
@@ -566,7 +408,7 @@ public class GameView3D {
boundaryAssets.add(pylon);
}
Point2D firstLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D firstLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
barrier.getTransforms().addAll(
new Rotate(
@@ -594,7 +436,7 @@ public class GameView3D {
public void updateTokens(List<Token> newTokens) {
mapTokens = new ArrayList<>();
for (Token token : newTokens) {
Point2D location = findScaledXY(token.getLat(), token.getLng());
Point2D location = scaledPoint.findScaledXY(token.getLat(), token.getLng());
ModelType modelType = null;
switch (token.getTokenType()) {
@@ -630,19 +472,17 @@ public class GameView3D {
playerYacht.toggleSail();
playerBoatAnimationTimer = new AnimationTimer() {
double count = 60;
Point2D lastLocation = findScaledXY(playerYacht.getLocation());
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
@Override
public void handle(long now) {
if (--count == 0) {
count = 60;
Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation());
if (Math.abs(lastLocation.distance(location)) > 2) {
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
Point2D location = findScaledXY(playerYacht.getLocation());
location = scaledPoint.findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY(), 0),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)),
new Scale(1, lastLocation.distance(location) / 5, 1)
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1))
);
trail.getChildren().add(segment);
if (trail.getChildren().size() > 50) {
@@ -0,0 +1,126 @@
package seng302.visualiser;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.scene.Node;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* Makes maps from map definition xml files.
*/
public class MapMaker {
private List<MapPreview> mapPreviews = new ArrayList<>();
private List<RaceXMLData> races = new ArrayList<>();
private List<RegattaXMLData> regattas = new ArrayList<>();
private List<String> filePaths = new ArrayList<>();
private List<Integer> maxPlayers = new ArrayList<>();
private static MapMaker instance;
private int index = 0;
private XMLGenerator xmlGenerator = new XMLGenerator();
private List<String> maps = new ArrayList<>(Arrays.asList("default.xml", "horseshoe.xml"));
public static MapMaker getInstance() {
if (instance == null) {
instance = new MapMaker();
}
return instance;
}
private MapMaker() {
for (String mapPath : maps){
String path = ("/maps/" + mapPath);
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, "", 1, null, false
);
RegattaXMLTemplate regattaTemplate = regattaRace.getKey();
filePaths.add(path);
regattas.add(new RegattaXMLData(
regattaTemplate.getRegattaId(),
regattaTemplate.getName(),
regattaTemplate.getCourseName(),
regattaTemplate.getLatitude(),
regattaTemplate.getLongitude(),
regattaTemplate.getUtcOffset()
));
RaceXMLTemplate raceTemplate = regattaRace.getValue();
xmlGenerator.setRaceTemplate(raceTemplate);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlGenerator.getRaceAsXml())));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
RaceXMLData race = XMLParser.parseRace(doc);
maxPlayers.add(XMLParser.getMaxPlayers(doc));
mapPreviews.add(new MapPreview(
new ArrayList<>(race.getCompoundMarks().values()),
race.getMarkSequence(), race.getCourseLimit()
));
races.add(race);
}
}
public void next() {
index += 1;
if (index >= mapPreviews.size()) {
index = 0;
}
}
public void previous() {
index -= 1;
if (index < 0) {
index = mapPreviews.size() - 1;
}
}
public Node getCurrentGameView() {
return mapPreviews.get(index).getAssets();
}
public RaceXMLData getCurrentRace() {
return races.get(index);
}
public RegattaXMLData getCurrentRegatta() {
return regattas.get(index);
}
public String getCurrentRacePath() {
return filePaths.get(index);
}
public Integer getMaxPlayers() {
return maxPlayers.get(index);
}
}
@@ -0,0 +1,283 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.CourseBoundary;
import seng302.visualiser.fxObjects.assets_2D.Gate;
import seng302.visualiser.fxObjects.assets_2D.Marker2D;
/**
* Created by cir27 on 20/07/17.
*/
public class MapPreview extends GameView {
private Polygon raceBorder = new CourseBoundary();
private Map<Mark, Marker2D> markerObjects;
public MapPreview(List<CompoundMark> marks, List<Corner> course, List<Limit> border) {
this.compoundMarks = marks;
this.courseOrder = course;
this.borderPoints = border;
gameObjects.getChildren().addAll(raceBorder, markers, tokens);
gameObjects.parentProperty().addListener((obs, old, parent) -> {
if (parent != null) {
canvasWidth = parent.prefWidth(1);
canvasHeight = parent.prefHeight(1);
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
});
}
@Override
public Node getAssets() {
return gameObjects;
}
public void setSize(double width, double height) {
canvasHeight = height;
canvasWidth = width;
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
if (newCourse.size() == 0) {
return;
}
compoundMarks = newCourse;
markerObjects = new HashMap<>();
courseOrder = sequence;
for (Corner corner : courseOrder) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* 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.
*
* @param border the race border to be drawn.
*/
@Override
public void updateBorder(List<Limit> border) {
if (border.size() == 0) {
return;
}
borderPoints = border;
scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false);
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
}
@@ -6,11 +6,10 @@ import seng302.gameServer.ServerDescription;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import javax.jmdns.impl.JmDNSImpl;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
@@ -18,6 +17,7 @@ import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
* Listens for servers on the local network
*/
public class ServerListener{
private static Integer SERVICE_REFRESH_INTERVAL = 5 * 1000;
private static ServerListener instance;
private ServerListenerDelegate delegate;
private JmDNS jmdns = null;
@@ -91,8 +91,16 @@ public class ServerListener{
private ServerListener() throws IOException {
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
listener = new GameServeMonitor();
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
/*new Timer().schedule(new TimerTask() {
@Override
public void run() {
refresh();
}
}, 50, SERVICE_REFRESH_INTERVAL);*/
}
public static ServerListener getInstance() throws IOException {
@@ -110,4 +118,25 @@ public class ServerListener{
public void setDelegate(ServerListenerDelegate delegate){
this.delegate = delegate;
}
public void refresh(){
ArrayList<ServerDescription> servers = new ArrayList<>(listener.servers);
for (ServerDescription serverDescription : servers){
if (serverDescription.hasExpired()){
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverDescription.getName());
}
else{
serverDescription.hasBeenRefreshed();
}
}
for (ServerDescription server : servers){
if (server.serverShouldBeRemoved()){
listener.servers.remove(server);
delegate.serverRemoved(new ArrayList<ServerDescription>(listener.servers));
}
}
}
}
@@ -9,10 +9,11 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.ClientYacht;
@@ -23,7 +24,7 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.MapPreview;
import seng302.visualiser.controllers.cells.PlayerCell;
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
@@ -33,10 +34,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
public class LobbyController implements Initializable {
private final double INITIAL_MAP_HEIGHT = 770d;
private final double INITIAL_MAP_WIDTH = 574d;
//--------FXML BEGIN--------//
@FXML
private VBox playerListVBox;
@@ -51,19 +54,21 @@ public class LobbyController implements Initializable {
@FXML
private Label mapName;
@FXML
private Pane serverMap;
private AnchorPane serverMap;
@FXML
private Label roomLabel;
//---------FXML END---------//
private RaceState raceState;
private JFXDialog customizationDialog;
public Color playersColor;
private Map<Integer, ClientYacht> playerBoats;
private Double mapWidth, mapHeight;
private GameView gameView;
private Double mapWidth = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT;
private MapPreview mapPreview;
@Override
public void initialize(URL location, ResourceBundle resources) {
roomLabel.setText("");
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
if (this.playersColor == null) {
@@ -86,6 +91,21 @@ public class LobbyController implements Initializable {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
int tries = 0;
while (DiscoveryServerClient.getRoomCode() == null && tries <= 10){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tries ++;
}
if (DiscoveryServerClient.getRoomCode() != null){
setRoomCode(DiscoveryServerClient.getRoomCode());
}
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
@@ -112,13 +132,13 @@ public class LobbyController implements Initializable {
private JFXDialog createCustomizeDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
JFXDialog customizationDialog = null;
try {
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
@@ -136,44 +156,30 @@ public class LobbyController implements Initializable {
return customizationDialog;
}
/**
*
*/
private void refreshMapView(){
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<CompoundMark>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
gameView.setSize(mapWidth, mapHeight);
// Update game view
gameView.updateBorder(border);
gameView.updateCourse(marks, corners);
}
/**
* Initializes a top down preview of the race course map.
*/
private void initMapPreview() {
gameView = new GameView();
gameView.setHorizontalBuffer(330d);
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
mapWidth = 770d;
mapHeight = 574d;
// Add game view
mapPreview = new MapPreview(marks, corners, border);
serverMap.getChildren().clear();
serverMap.getChildren().add(gameView);
serverMap.getChildren().add(mapPreview.getAssets());
mapPreview.setSize(mapWidth, mapHeight);
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
mapWidth = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
//
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
mapHeight = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
}
@@ -245,4 +251,8 @@ public class LobbyController implements Initializable {
public void closeCustomizationDialog() {
customizationDialog.close();
}
public void setRoomCode(String roomCode) {
roomLabel.setText("Room: " + roomCode);
}
}
@@ -12,8 +12,6 @@ import java.util.concurrent.TimeUnit;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -24,8 +22,6 @@ import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
@@ -166,32 +162,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Sounds.stopMusic();
Sounds.playRaceMusic();
// Load a default important annotation state
//importantAnnotations = new ImportantAnnotationsState();
//Formatting the y axis of the sparkline
// raceSparkLine.getYAxis().setRotate(180);
// raceSparkLine.getYAxis().setTickLabelRotation(180);
// raceSparkLine.getYAxis().setTranslateX(-5);
//raceSparkLine.visibleProperty().setValue(false);
//raceSparkLine.getYAxis().setAutoRanging(false);
//sparklineYAxis.setTickMarkVisible(false);
//positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// raceSparkLine.visibleProperty().setValue(false);
// raceSparkLine.getYAxis().setAutoRanging(false);
// sparklineYAxis.setTickMarkVisible(false);
// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
//selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty());
// rvAnchorPane.prefHeightProperty().bind(ViewManager.getInstance().getDecorator().heightProperty());
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// windArrowHolder.getChildren().addAll(windArrow);
// windArrow.setLayoutX(windArrowHolder.getWidth() / 2);
// windArrow.setLayoutY(windArrowHolder.getHeight() / 2);
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
if (newLen.intValue() > CHAT_LIMIT) {
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
@@ -205,26 +175,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
chatHistory.prefHeightProperty().bind(
chatHistoryHolder.heightProperty()
);
// chatHistory.setFitToWidth(true);
// chatHistory.setFitToHeight(true);
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
// chatHistory.setScrollTop(Double.MAX_VALUE);
// });
contentStackPane.setOnMouseClicked(event -> {
contentStackPane.requestFocus();
});
Platform.runLater(contentStackPane::requestFocus);
//Makes the chat history non transparent when clicked on
chatInput.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
});
}
@@ -281,7 +242,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
while (c.next()) {
if (c.wasPermutated()) {
updateOrder(raceState.getPlayerPositions());
updateSparkLine();
}
}
});
@@ -291,7 +251,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateOrder(raceState.getPlayerPositions());
gameView = new GameView3D();
// gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> {
contentStackPane.getChildren().add(0, gameView.getAssets());
((SubScene) gameView.getAssets()).widthProperty()
@@ -305,11 +264,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
// gameView.enableZoom();
gameView.setBoatAsPlayer(player);
// gameView.startRace();
// raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
@@ -322,9 +280,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindSpeed(raceState.getWindSpeed());
});
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
Platform.runLater(() -> {
initializeUpdateTimer();
});
Platform.runLater(this::initializeUpdateTimer);
}
/**
@@ -427,137 +383,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
}
private void initialiseFPSCheckBox() {
// toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
// gameView.setFPSVisibility(toggleFps.isSelected())
// );
}
private void initialiseAnnotationSlider() {
// annotationSlider.setLabelFormatter(new StringConverter<Double>() {
// @Override
// public String toString(Double n) {
// if (n == 0) {
// return "None";
// }
// if (n == 1) {
// return "Important";
// }
// if (n == 2) {
// return "All";
// }
// return "All";
// }
//
// @Override
// public Double fromString(String s) {
// switch (s) {
// case "None":
// return 0d;
// case "Important":
// return 1d;
// case "All":
// return 2d;
//
// default:
// return 2d;
// }
// }
// });
// annotationSlider.setValue(2);
// annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
// setAnnotations((int) annotationSlider.getValue())
// );
}
/**
* Used to add any new yachts into the race that may have started late or not have had data received yet
*/
private void updateSparkLine(){
// // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// // Collect the racing yachts that aren't already in the chart
// sparkLineData.clear();
// List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// // Create a new data series for new yachts
// sparkLineCandidates
// .stream()
// .filter(yacht -> yacht.getPosition() != null)
// .forEach(yacht -> {
// Series<String, Double> yachtData = new Series<>();
// yachtData.setName(yacht.getSourceId().toString());
// yachtData.getData().add(
// new Data<>(
// Integer.toString(yacht.getLegNumber()),
// 1.0 + participants.size() - yacht.getPosition()
// )
// );
// sparkLineData.add(yachtData);
// });
//
// // Lambda function to sort the series in order of leg (later legs shown more to the right)
// sparkLineData.sort((o1, o2) -> {
// Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
// Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
// if (leg2 < leg1){
// return 1;
// } else {
// return -1;
// }
// });
//
// // Adds the new data series to the sparkline (and set the colour of the series)
// Platform.runLater(() -> {
// sparkLineData
// .stream()
// .filter(spark -> !raceSparkLine.getData().contains(spark))
// .forEach(spark -> {
// raceSparkLine.getData().add(spark);
// spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
// });
// });
}
private void initialiseSparkLine() {
// sparklineYAxis.setUpperBound(participants.size() + 1);
// raceSparkLine.setCreateSymbols(false);
}
/**
* Updates the yachts sparkline of the desired yacht and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPlacing()
)
);
}
}
/**
* gets the rgb string of the yachts colour to use for the chart via css
* @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String yachtId){
Color color = participants.get(Integer.valueOf(yachtId)).getColour();
if (color == null){
return String.format("#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 )
);
}
/**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* orderings etc.. which are dependent on the info from the stream parser constantly.
@@ -23,11 +23,21 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
import seng302.gameServer.ServerDescription;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
import seng302.visualiser.controllers.cells.ServerCell;
import seng302.visualiser.controllers.dialogs.DirectConnectController;
import java.io.IOException;
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;
@@ -48,15 +58,21 @@ public class ServerListController implements Initializable, ServerListenerDelega
private JFXButton serverListHostButton;
//Direct Connect
@FXML
private JFXButton connectButton;
@FXML
private JFXTextField serverHostName;
private JFXButton directConnectButton;
@FXML
private JFXTextField serverPortNumber;
@FXML
private JFXButton roomConnectButton;
@FXML
private JFXTextField roomNumber;
@FXML
private JFXButton autoSelectGame;
//---------FXML END---------//
private Label noServersFound;
private Logger logger = LoggerFactory.getLogger(ServerListController.class);
private JFXDialog directConnectDialog;
private JFXDialog serverCreationDialog;
private List<ServerCreationDialogListener> serverCreationDialogListeners = new ArrayList<>();
@@ -72,13 +88,25 @@ public class ServerListController implements Initializable, ServerListenerDelega
serverListVBox.minWidthProperty().bind(serverListScrollPane.widthProperty());
// Set Event Bindings
connectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
directConnectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
connectButton.setOnMouseReleased(event -> {
attemptToDirectConnect();
roomNumber.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
connectToRoomCode(roomNumber.getText());
}
});
directConnectButton.setOnMouseReleased(event -> {
directConnectDialog.show();
Sounds.playButtonClick();
});
for (JFXTextField textField : Arrays.asList(serverHostName, serverPortNumber)) {
directConnectDialog = createDirectConnectDialog();
for (JFXTextField textField : Arrays.asList(roomNumber)) {
// Event for pressing enter to submit direct connection
textField.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
@@ -92,15 +120,41 @@ public class ServerListController implements Initializable, ServerListenerDelega
textField.getValidators().add(validator);
}
autoSelectGame.setOnMouseReleased(e -> {
ServerListing listing;
DiscoveryServerClient client = new DiscoveryServerClient();
try {
listing = client.getRandomServer();
} catch (Exception e1) {
ViewManager.getInstance().showErrorSnackBar("Unable to connect to matchmaking server. Are you connected to the internet?");
return;
}
if (client.didFail()){
return;
}
if (listing == null || listing.equals(ServerRegistrationMessage.getEmptyRegistration())) {
ViewManager.getInstance().showErrorSnackBar("There are currently no servers available for you to connect to.");
return;
}
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
});
/*
// Validating the hostname
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
hostNameValidator.setMessage("Host name incorrect");
serverHostName.getValidators().add(hostNameValidator);
roomCodeInput.getValidators().add(hostNameValidator);
// Validating the port number
NumberRangeValidator portNumberValidator = new NumberRangeValidator(1025, 65536);
portNumberValidator.setMessage("Port number incorrect");
serverPortNumber.getValidators().add(portNumberValidator);
TODO later
*/
// Start listening for servers on network
try {
@@ -121,6 +175,11 @@ public class ServerListController implements Initializable, ServerListenerDelega
);
serverListVBox.getChildren().add(noServersFound);
roomConnectButton.setOnMouseReleased(e -> {
String roomCode = roomNumber.getText();
connectToRoomCode(roomCode);
});
// Set up dialog for server creation
serverListHostButton.setOnAction(action -> {
showServerCreationDialog();
@@ -144,11 +203,30 @@ public class ServerListController implements Initializable, ServerListenerDelega
serverCreationDialog.show();
Sounds.playButtonClick();
} catch (IOException e) {
e.printStackTrace();
logger.warn("Could not create Server Creation Dialog.");
}
});
}
private JFXDialog createDirectConnectDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/DirectConnect.fxml"));
JFXDialog dcDialog = null;
try {
dcDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
DirectConnectController controller = dialog.getController();
return dcDialog;
}
private void closeServerCreationDialog() {
serverCreationDialog.close();
}
@@ -157,9 +235,9 @@ public class ServerListController implements Initializable, ServerListenerDelega
* Validates the connection and attempts to connect to a given hostname and port number.
*/
private void attemptToDirectConnect() {
if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
/*if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
DirectConnect();
}
}*/
}
/**
@@ -169,10 +247,40 @@ public class ServerListController implements Initializable, ServerListenerDelega
* @return boolean value if host and port number are valid values
*/
private Boolean validateDirectConnection(String hostName, String portNumber) {
Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
/*Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
*
Boolean portNumberValid = ValidationTools.validateTextField(serverPortNumber);
return hostNameValid && portNumberValid;
return hostNameValid && portNumberValid;*/
return true;
}
private void connectToRoomCode(String roomCode){
DiscoveryServerClient client = new DiscoveryServerClient();
ServerListing serverListing;
if (client.didFail()){
return;
}
try {
serverListing = client.getServerForRoomCode(roomCode);
} catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking server. Please try again later.");
return;
}
if (serverListing == null || serverListing.equals(new ServerListing("","","", 0, 0))){
ViewManager.getInstance().showErrorSnackBar("No servers could be found with that room code.");
return;
}
try {
ViewManager.getInstance().getGameClient().runAsClient(serverListing.getAddress(), serverListing.getPortNumber());
}
catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking service.");
}
}
/**
@@ -180,7 +288,7 @@ public class ServerListController implements Initializable, ServerListenerDelega
*/
private void DirectConnect() {
Sounds.playButtonClick();
ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
// ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
}
/**
@@ -59,7 +59,7 @@ public class StartScreenController implements Initializable{
/**
* Changes the view to the Server Browser.
*/
private void goToServerBrowser() {
public void goToServerBrowser() {
try {
ViewManager.getInstance().setScene(serverList);
} catch (Exception e) {
@@ -6,16 +6,10 @@ import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXSnackbar;
import com.jfoenix.svg.SVGGlyph;
import java.io.IOException;
import java.util.HashMap;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
@@ -25,12 +19,14 @@ import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.BonjourInstallChecker;
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;
public class ViewManager {
private static ViewManager instance;
@@ -277,16 +273,9 @@ public class ViewManager {
jfxSnackbar.show(snackbarText, 1500);
}
/**
* Determines if a PC has compatibility with the bonjour protocol for server detection.
*/
private void checkCompatibility() {
if (BonjourInstallChecker.isBonjourSupported()) {
BonjourInstallChecker.openInstallUrl();
}
}
private void closeAll() {
if (stage!= null) stage.close();
try {
ServerAdvertiser.getInstance().unregister();
} catch (IOException e1) {
@@ -352,8 +341,9 @@ public class ViewManager {
logger.error("Could not load lobby view");
}
LobbyController lobbyController = loader.getController();
if (disableReadyButton) {
LobbyController lobbyController = loader.getController();
lobbyController.disableReadyButton();
}
@@ -365,7 +355,6 @@ public class ViewManager {
*
* @return A RaceViewController for the race view screen.
*/
public RaceViewController loadRaceView() {
FXMLLoader loader = loadFxml("/views/RaceView.fxml");
// have to create a new stage and set the race view maximized as JFoenix decorator has
@@ -410,6 +399,14 @@ public class ViewManager {
return loader.getController();
}
public void showErrorSnackBar(String msg){
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
JFXSnackbar bar = new JFXSnackbar(decorator);
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
}
public Stage getStage() {
return stage;
}
@@ -0,0 +1,70 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
public class DirectConnectController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private JFXTextField serverAddress;
@FXML
private JFXTextField portNumber;
@FXML
private JFXButton submitBtn;
//---------FXML END---------//
public void initialize(URL location, ResourceBundle resources) {
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Too long.");
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
fieldRequiredValidator.setMessage("Required.");
serverAddress.setValidators(fieldLengthValidator, fieldRequiredValidator);
portNumber.setValidators(fieldLengthValidator, fieldRequiredValidator);
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
connectToServer();
});
}
/**
* connects to the server
*/
private void connectToServer() {
//TODO fix port number validation
try{
Integer.parseInt(portNumber.getText());
}
catch (NumberFormatException e){
ViewManager.getInstance().showErrorSnackBar("You need to enter a valid port number");
return;
}
ViewManager.getInstance().getGameClient()
.runAsClient(serverAddress.getText(), Integer.parseInt(portNumber.getText()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -1,6 +1,7 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
@@ -10,9 +11,10 @@ import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
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;
@@ -31,15 +33,42 @@ public class ServerCreationController implements Initializable {
private JFXButton submitBtn;
@FXML
private Label closeLabel;
@FXML
private JFXButton nextMapButton;
@FXML
private JFXButton lastMapButton;
@FXML
private Label mapNameLabel;
@FXML
private JFXSlider legsSlider;
@FXML
private Label legsSliderLabel;
@FXML
private JFXCheckBox pickupsCheckBox;
@FXML
private AnchorPane mapHolder;
private MapMaker mapMaker = MapMaker.getInstance();
//---------FXML END---------//
private List<ServerCreationDialogListener> serverCreationDialogListeners;
public void initialize(URL location, ResourceBundle resources) {
maxPlayersSlider.valueProperty().addListener(
(observable, oldValue, newValue) -> updateMaxPlayerLabel()
);
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
legsSlider.valueProperty().addListener(
(obs, oldVal, newVal) -> updateLegSliderLabel()
);
legsSlider.setMax(10);
updateMaxPlayerLabel();
maxPlayersSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
updateMaxPlayerLabel();
});
updateLegSliderLabel();
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Server name too long.");
@@ -54,7 +83,20 @@ public class ServerCreationController implements Initializable {
validateServerSettings();
});
closeLabel.setOnMouseClicked(event -> notifyListeners());
nextMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
nextMap();
});
lastMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
lastMap();
});
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
pickupsCheckBox.setSelected(true);
//closeLabel.setOnMouseClicked(event -> notifyListeners());
}
/**
@@ -75,7 +117,7 @@ public class ServerCreationController implements Initializable {
private void createServer() {
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
.getValue());
.getValue(), mapMaker.getCurrentRacePath(), (int) legsSlider.getValue(), pickupsCheckBox.isSelected());
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
@@ -86,13 +128,38 @@ public class ServerCreationController implements Initializable {
*/
private void updateMaxPlayerLabel() {
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("YOU SELECTED: %.0f", maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("Max players: %.0f", maxPlayersSlider.getValue()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
private void updateLegSliderLabel() {
legsSlider.setValue(Math.floor(legsSlider.getValue()));
legsSliderLabel.setText(
String.format("A section of the race will repeat %.0f times", legsSlider.getValue())
);
}
public void playButtonHoverSound() {
Sounds.playHoverSound();
}
private void nextMap() {
mapMaker.next();
updateMap();
}
private void lastMap() {
mapMaker.previous();
updateMap();
}
private void updateMap() {
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
}
public void setListener(List<ServerCreationDialogListener> serverCreationDialogListeners) {
this.serverCreationDialogListeners = serverCreationDialogListeners;
}
@@ -26,7 +26,9 @@ public enum ModelType {
PLAYER_IDENTIFIER ("player_identifier.dae"),
PLAIN_ARROW ("arrow.dae"),
START_ARROW ("start_arrow.dae"),
FINISH_ARROW ("finish_arrow.dae");
FINISH_ARROW ("finish_arrow.dae"),
LAND("land.dae"),
LAND_SMOOTH("land_smooth.dae");
final String filename;