mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22fdf1e4ac | |||
| da8c91f5c1 | |||
| c4a6113f6c | |||
| 307e79ecfc | |||
| 7d8a6afa5f | |||
| ea0be5e952 | |||
| 7197bc2bee | |||
| fba522d0c3 | |||
| 0e829874c2 | |||
| c5d56065b6 | |||
| 410d765745 | |||
| fe76e85c71 | |||
| 9d61a43bd7 | |||
| c39582de5c | |||
| 9ed52a1225 | |||
| da263355f4 | |||
| ebecd25ed2 | |||
| 0f5137c2b6 | |||
| 73799954e4 | |||
| edfeb2b287 | |||
| 0355784000 | |||
| 02df69b7b4 | |||
| 242132b800 | |||
| 3a671d4ed0 | |||
| 482d987839 | |||
| cc124b2d19 | |||
| b3320ad805 | |||
| 33779ad5c1 | |||
| 3a41c27d8d | |||
| e24203904b | |||
| eb188495ce | |||
| 62a7e2b8fa | |||
| acd0e790fe | |||
| 46013474c0 | |||
| bf427f24d3 | |||
| 7d0a47446d | |||
| 889098bb50 | |||
| 6223a8fc0b | |||
| 391bd33548 | |||
| 7e0c2abbfd | |||
| 42a5e86bf8 | |||
| 20d73d8f2f | |||
| 0a62c538ca | |||
| 24a04aa530 | |||
| 7ee6a09626 | |||
| 47798b19fe | |||
| d9247eb031 | |||
| 7b0d31438e | |||
| 1e74321aab | |||
| f0fc75feec | |||
| c8dc448a52 | |||
| e518d13fd5 | |||
| 167545cbef | |||
| 20b656b16d | |||
| 90a54beb8d | |||
| e375efb8e9 | |||
| 06e9c55d2c | |||
| 9cba76f979 | |||
| d5ce61a0ff | |||
| 634322de3f | |||
| 6f534a430d | |||
| 17b0864815 | |||
| 3455321f01 | |||
| 7e9e96c091 | |||
| b25c3367a9 | |||
| ec7ee34305 | |||
| 9dad88e56a | |||
| 6a4547f3f9 | |||
| 71f626f57e | |||
| 1c343ec02d | |||
| 5046ca6ffa | |||
| bd31bae586 | |||
| 2f973deacc | |||
| f4c5e483ce | |||
| bb3ee424cc | |||
| 99f38fa05f | |||
| 6a8c77c670 | |||
| 659a521cc9 | |||
| da3613fe36 | |||
| 8dc3e54186 | |||
| 8fd35392b0 | |||
| bc9f0ea924 | |||
| 6d9864e677 | |||
| 8c2125276e | |||
| 235d6c9cef | |||
| 5dd936f8f1 | |||
| d223d393fa | |||
| aa098569f3 | |||
| d49e84e6d2 | |||
| 75bf92a67f | |||
| 0d7201e235 | |||
| 0197de6fe3 | |||
| d79ff0f1cf | |||
| 18f36d7ea4 | |||
| 131cd80e02 | |||
| 3d0209300e | |||
| 6b4f7eb42b | |||
| 8b9b3a31bd | |||
| 0bf83aa858 | |||
| dff261cf41 | |||
| 83ee217cef | |||
| d4826739e3 | |||
| fdfb5959fa | |||
| 878c0e0f43 | |||
| 1ce8df976c | |||
| 6fcd4ae4cc | |||
| 534eaee8ce | |||
| ab35e3506d | |||
| 1fec620427 | |||
| 00100eb991 | |||
| 58512fdbdf | |||
| 76a1a3c7a0 | |||
| 1b8c503712 | |||
| 334e13295f | |||
| 0c4d001510 | |||
| 24a3a54ccb | |||
| 40408cff27 | |||
| 9fcb8915c2 | |||
| caf910c4c5 | |||
| 4f07786449 | |||
| 2b53e0d5b4 | |||
| 1210f9342b | |||
| f136a970db | |||
| 800ae2864f | |||
| e764caee60 | |||
| 78b4786482 | |||
| e3ccb570ed | |||
| 470bf121a5 | |||
| f077486e22 | |||
| 717f7558d9 | |||
| 05236337ba | |||
| 1b76d59acb | |||
| 02e6d2a98b | |||
| 06a4dde216 | |||
| 1516e817b7 | |||
| bd7ea920b6 | |||
| 4a170f8179 | |||
| 1f9e6154ae | |||
| 5595e0439b | |||
| 08f7127b69 | |||
| 8ba28ffda0 | |||
| edd52a07a7 | |||
| 875a6b4e98 | |||
| faefcc7938 | |||
| 0c956f93c8 | |||
| 0e2946f20b | |||
| f66ef3c208 | |||
| cf4f8813d2 | |||
| e2bc613c9d | |||
| c2c3c9eb53 | |||
| cadf995bf7 | |||
| 62f139c604 | |||
| 0bf6dd9e6b | |||
| eed5f56690 | |||
| b35126ff4e | |||
| c39499cee7 | |||
| cff15ba07d | |||
| 6be7c17c40 | |||
| 64811354e3 | |||
| 3364f8b516 | |||
| 8cc725616c | |||
| 0feccdc8b9 | |||
| 1a755cec33 | |||
| d565552fcc | |||
| 26a8d76f8b | |||
| d87dcaa4fe | |||
| 0b61dffe71 | |||
| 4e69157c09 | |||
| d67566e217 | |||
| 56f1dd2efb | |||
| 302bc91461 | |||
| 488ab47c8d | |||
| 0650ba30e2 | |||
| 04518c35b0 | |||
| f33e4cc137 | |||
| 7a4b3f0ad9 | |||
| 1619c95098 | |||
| 396098e009 | |||
| e4dbbd05c7 | |||
| a1e3ec54c7 | |||
| 47f3f6e27d | |||
| fce7aa4f9a | |||
| 81afad1bcc | |||
| 5026c568a7 | |||
| 88d1e91b6f | |||
| 67f39e9049 | |||
| eb1d3f1a60 | |||
| 54ca12f3b6 | |||
| 3f910b8db7 | |||
| b346d5a706 | |||
| 45fa73595f | |||
| 01ca3f3453 | |||
| ba8e333d81 | |||
| 24d4c1df15 | |||
| 0c5d661995 | |||
| 262f27fa8a | |||
| 0fcdf41419 | |||
| 4ebf7d6104 | |||
| 353dd48829 | |||
| 4bd7291a4a | |||
| 0d0b2e59d5 | |||
| ace48a8404 | |||
| 201405d070 | |||
| dc19310849 | |||
| 1c866ea8c2 | |||
| 23027705da | |||
| c15f13bc2c | |||
| 6ee2517f74 | |||
| 75155fe481 | |||
| 2fcff65dd6 | |||
| 4c730ea890 |
@@ -7,7 +7,6 @@
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
bc00cae65d030845973151123fd0f2b1
|
||||
@@ -0,0 +1 @@
|
||||
de6c72cb03b2216bbe03ac7b882f0c146fb76bc8
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>lib.com.interactivemesh</groupId>
|
||||
<artifactId>jimColModelImporter</artifactId>
|
||||
<version>0.7</version>
|
||||
</project>
|
||||
@@ -0,0 +1 @@
|
||||
8fc884a64856917671745720acc6048c
|
||||
@@ -0,0 +1 @@
|
||||
4b35131587917ed1a16acb1eff8cd7a213a26edc
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>lib.com.interactivemesh</groupId>
|
||||
<artifactId>jimColModelImporter</artifactId>
|
||||
<versioning>
|
||||
<release>0.7</release>
|
||||
<versions>
|
||||
<version>0.7</version>
|
||||
</versions>
|
||||
<lastUpdated>20170912024010</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1 @@
|
||||
3132c3f88de1a942ac37930b8cdaa764
|
||||
@@ -0,0 +1 @@
|
||||
20847be06b0d11b70f1fbfb1527c5efee4e9f49e
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
deec04fc74e1115465598d342810df18
|
||||
@@ -0,0 +1 @@
|
||||
ea31eabe6384ae965cd8180920f7ba0248717313
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>lib.com.interactivemesh</groupId>
|
||||
<artifactId>jimStlMeshImporter</artifactId>
|
||||
<version>0.7</version>
|
||||
</project>
|
||||
@@ -0,0 +1 @@
|
||||
82a485ac9a76d6587b1b23b7fbd8f5a0
|
||||
@@ -0,0 +1 @@
|
||||
2bac29a6598a88b2f115b72433181c13fc6201d2
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>lib.com.interactivemesh</groupId>
|
||||
<artifactId>jimStlMeshImporter</artifactId>
|
||||
<versioning>
|
||||
<release>0.7</release>
|
||||
<versions>
|
||||
<version>0.7</version>
|
||||
</versions>
|
||||
<lastUpdated>20170912024122</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
@@ -0,0 +1 @@
|
||||
cad88c5c501f771bc8d1fc085decb3c4
|
||||
@@ -0,0 +1 @@
|
||||
c6cd4fae002dbbe4246c8eac4b35de07d921fd51
|
||||
@@ -11,6 +11,7 @@
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -70,6 +71,44 @@
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.lwjgl/lwjgl -->
|
||||
<dependency>
|
||||
<groupId>org.lwjgl</groupId>
|
||||
<artifactId>lwjgl</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.javagl</groupId>
|
||||
<artifactId>obj</artifactId>
|
||||
<version>0.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.interactivemesh</groupId>
|
||||
<artifactId>jimStlMeshImporter</artifactId>
|
||||
<version>0.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.interactivemesh</groupId>
|
||||
<artifactId>jimColModelImporter</artifactId>
|
||||
<version>0.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jfoenix</groupId>
|
||||
<artifactId>jfoenix</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/javax.jmdns/jmdns -->
|
||||
<dependency>
|
||||
<groupId>javax.jmdns</groupId>
|
||||
<artifactId>jmdns</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -152,4 +191,18 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>lib</id>
|
||||
<name>third party libraries</name>
|
||||
<url>file://${basedir}/lib</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>Homer-Core</id>
|
||||
<name>Homer-core-repo</name>
|
||||
<url>https://nexus.arcsmed.at/content/repositories/homer.core</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
@@ -2,10 +2,6 @@ package seng302;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
@@ -14,7 +10,7 @@ import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.PolarTable;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
@@ -67,27 +63,10 @@ public class App extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
||||
primaryStage.setTitle("RaceVision");
|
||||
Scene scene = new Scene(root, 1530, 960);
|
||||
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
primaryStage.setScene(scene);
|
||||
// primaryStage.setMaxWidth(1530);
|
||||
// primaryStage.setMaxHeight(960);
|
||||
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
// primaryStage.setMaximized(true);
|
||||
|
||||
primaryStage.show();
|
||||
|
||||
primaryStage.setOnCloseRequest(e -> {
|
||||
// ClientPacketParser.appClose();
|
||||
// ClientPacketParser.appClose();
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
// ClientState.primaryStage = primaryStage;
|
||||
ViewManager.getInstance().initialStartView(primaryStage);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
parseArgs(args);
|
||||
|
||||
@@ -1,37 +1,25 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javafx.scene.paint.Color;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
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.model.GeoPoint;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.PolarTable;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.gameServer.messages.*;
|
||||
import seng302.model.*;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.mark.MarkOrder;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.utilities.XMLParser;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.util.*;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* A Static class to hold information about the current state of the game (model)
|
||||
* Also contains logic for updating itself on regular time intervals on its own thread
|
||||
@@ -41,26 +29,33 @@ public class GameState implements Runnable {
|
||||
|
||||
@FunctionalInterface
|
||||
interface NewMessageListener {
|
||||
|
||||
void notify(Message message);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
private static Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
|
||||
|
||||
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;
|
||||
|
||||
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||
public static Integer MAX_PLAYERS = 8;
|
||||
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||
public static final Double MARK_COLLISION_DISTANCE = 15d;
|
||||
private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||
private static final Double MARK_COLLISION_DISTANCE = 15d;
|
||||
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
|
||||
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
||||
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
||||
private static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
||||
|
||||
private static Long previousUpdateTime;
|
||||
public static Double windDirection;
|
||||
private static Double windSpeed;
|
||||
private static Double speedMultiplier = 1d;
|
||||
|
||||
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<Player> players;
|
||||
@@ -71,36 +66,34 @@ public class GameState implements Runnable {
|
||||
private static long startTime;
|
||||
private static Set<Mark> marks;
|
||||
private static List<Limit> courseLimit;
|
||||
private static Integer maxPlayers = 8;
|
||||
|
||||
private static List<NewMessageListener> markListeners;
|
||||
|
||||
private static List<Token> allTokens;
|
||||
private static List<Token> tokensInPlay;
|
||||
|
||||
private static List<NewMessageListener> newMessageListeners;
|
||||
|
||||
private static Map<Player, String> playerStringMap = new HashMap<>();
|
||||
/*
|
||||
Ideally I would like to make this class an object instantiated by the server and given to
|
||||
it's created threads if necessary. Outside of that I think the dependencies on it
|
||||
(atm only Yacht & GameClient) can be removed from most other classes. The observable list of
|
||||
players could be pulled directly from the server by the GameClient since it instantiates it
|
||||
and it is reasonable for it to pull data. The current setup of publicly available statics is
|
||||
pretty meh IMO because anything can change it making it unreliable and like people did with
|
||||
the old ServerParser class everything that needs shared just gets thrown in the static
|
||||
collections and things become a real mess.
|
||||
*/
|
||||
|
||||
public GameState(String hostIpAddress) {
|
||||
windDirection = 180d;
|
||||
windSpeed = 10000d;
|
||||
this.hostIpAddress = hostIpAddress;
|
||||
yachts = new HashMap<>();
|
||||
tokensInPlay = new ArrayList<>();
|
||||
|
||||
players = new ArrayList<>();
|
||||
GameState.hostIpAddress = hostIpAddress;
|
||||
customizationFlag = false;
|
||||
|
||||
playerHasLeftFlag = false;
|
||||
speedMultiplier = 1.0;
|
||||
currentStage = GameStages.LOBBYING;
|
||||
isRaceStarted = false;
|
||||
//set this when game stage changes to prerace
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
|
||||
markListeners = new ArrayList<>();
|
||||
newMessageListeners = new ArrayList<>();
|
||||
allTokens = makeTokens();
|
||||
|
||||
resetStartTime();
|
||||
|
||||
@@ -125,6 +118,21 @@ public class GameState implements Runnable {
|
||||
courseLimit = XMLParser.parseRace(document).getCourseLimit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
|
||||
* race ideally
|
||||
*
|
||||
* @return A list of possible tokensInPlay for this race
|
||||
*/
|
||||
private ArrayList<Token> makeTokens() {
|
||||
Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154);
|
||||
Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382);
|
||||
Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965);
|
||||
Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214);
|
||||
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
|
||||
}
|
||||
|
||||
public static String getHostIpAddress() {
|
||||
return hostIpAddress;
|
||||
}
|
||||
@@ -137,6 +145,10 @@ public class GameState implements Runnable {
|
||||
return players;
|
||||
}
|
||||
|
||||
public static List<Token> getTokensInPlay() {
|
||||
return tokensInPlay;
|
||||
}
|
||||
|
||||
public static void addPlayer(Player player) {
|
||||
players.add(player);
|
||||
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
|
||||
@@ -178,7 +190,7 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
public static void resetStartTime(){
|
||||
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
|
||||
startTime = System.currentTimeMillis() + TIME_TILL_START;
|
||||
}
|
||||
|
||||
public static Double getWindDirection() {
|
||||
@@ -264,7 +276,23 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically in this GameState thread to update the GameState values
|
||||
* 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();
|
||||
tokensInPlay.clear();
|
||||
tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically in this GameState thread to update the GameState values.
|
||||
* -Updates yachts velocity
|
||||
* -Updates locations
|
||||
* -Checks for collisions
|
||||
* -Checks for progression
|
||||
*
|
||||
* -Also checks things like the end of the race and race start time etc
|
||||
*/
|
||||
public void update() {
|
||||
Boolean raceFinished = true;
|
||||
@@ -276,15 +304,14 @@ public class GameState implements Runnable {
|
||||
}
|
||||
for (ServerYacht yacht : yachts.values()) {
|
||||
updateVelocity(yacht);
|
||||
checkPowerUpTimeout(yacht);
|
||||
yacht.runAutoPilot();
|
||||
yacht.updateLocation(timeInterval);
|
||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||
checkCollision(yacht);
|
||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||
checkForLegProgression(yacht);
|
||||
raceFinished = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (raceFinished) {
|
||||
@@ -292,6 +319,18 @@ 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the yacht has crossed the course limit
|
||||
*
|
||||
@@ -312,8 +351,39 @@ public class GameState implements Runnable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private static Token checkTokenPickUp(ServerYacht serverYacht) {
|
||||
for (Token token : tokensInPlay) {
|
||||
Double distance = GeoUtility.getDistance(token, serverYacht.getLocation());
|
||||
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks for collision with other in game objects for the given serverYacht. To be called each
|
||||
* update. If there is a collision, Notifies the server to send the appropriate messages out.
|
||||
* Checks for these items in turn:
|
||||
* - Other yachts
|
||||
* - Marks
|
||||
* - Boundary
|
||||
* - Tokens
|
||||
*
|
||||
* @param serverYacht The server yacht to check collisions with
|
||||
*/
|
||||
public static void checkCollision(ServerYacht serverYacht) {
|
||||
//Yacht Collision
|
||||
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
|
||||
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||
|
||||
if (collidedYacht != null) {
|
||||
GeoPoint originalLocation = serverYacht.getLocation();
|
||||
serverYacht.setLocation(
|
||||
@@ -329,59 +399,81 @@ public class GameState implements Runnable {
|
||||
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
} else {
|
||||
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||
if (collidedMark != null) {
|
||||
}
|
||||
|
||||
//Mark Collision
|
||||
else if (collidedMark != null) {
|
||||
serverYacht.setLocation(
|
||||
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
|
||||
);
|
||||
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
else{
|
||||
if (checkBoundaryCollision(serverYacht)) {
|
||||
|
||||
//Boundary Collision
|
||||
else if (checkBoundaryCollision(serverYacht)) {
|
||||
serverYacht.setLocation(
|
||||
calculateBounceBack(serverYacht, serverYacht.getLocation(),
|
||||
BOUNCE_DISTANCE_YACHT)
|
||||
);
|
||||
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Token Collision
|
||||
Token collidedToken = checkTokenPickUp(serverYacht);
|
||||
if (collidedToken != null) {
|
||||
sendServerMessage(serverYacht.getSourceId(), serverYacht.getBoatName() + " has picked speed-up token");
|
||||
tokensInPlay.remove(collidedToken);
|
||||
serverYacht.powerUp(collidedToken.getTokenType());
|
||||
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
|
||||
.getTokenType());
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateVelocity(ServerYacht yacht) {
|
||||
Double velocity = yacht.getCurrentVelocity();
|
||||
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
||||
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
||||
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
|
||||
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
|
||||
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
|
||||
maxBoatSpeed *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
Double currentVelocity = yacht.getCurrentVelocity();
|
||||
// TODO: 15/08/17 remove magic numbers from these equations.
|
||||
if (yacht.getSailIn()) {
|
||||
if (velocity < maxBoatSpeed - 500) {
|
||||
if (currentVelocity < maxBoatSpeed - 500) {
|
||||
yacht.changeVelocity(maxBoatSpeed / 100);
|
||||
} else if (velocity > maxBoatSpeed + 500) {
|
||||
yacht.changeVelocity(-velocity / 200);
|
||||
} else if (currentVelocity > maxBoatSpeed + 500) {
|
||||
yacht.changeVelocity(-currentVelocity / 200);
|
||||
} else {
|
||||
yacht.setCurrentVelocity(maxBoatSpeed);
|
||||
}
|
||||
} else {
|
||||
if (velocity > 3000) {
|
||||
yacht.changeVelocity(-velocity / 200);
|
||||
} else if (velocity > 100) {
|
||||
yacht.changeVelocity(-velocity / 50);
|
||||
} else if (velocity <= 100) {
|
||||
if (currentVelocity > 3000) {
|
||||
yacht.changeVelocity(-currentVelocity / 200);
|
||||
} else if (currentVelocity > 100) {
|
||||
yacht.changeVelocity(-currentVelocity / 50);
|
||||
} else if (currentVelocity <= 100) {
|
||||
yacht.setCurrentVelocity(0d);
|
||||
}
|
||||
}
|
||||
@@ -442,6 +534,9 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
if (hasProgressed) {
|
||||
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
|
||||
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber());
|
||||
}
|
||||
yacht.incrementLegNumber();
|
||||
sendMarkRoundingMessage(yacht);
|
||||
logMarkRounding(yacht);
|
||||
@@ -476,6 +571,7 @@ 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");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -579,6 +675,7 @@ 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");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -606,6 +703,9 @@ public class GameState implements Runnable {
|
||||
int blue = customizeData[2] & 0xFF;
|
||||
Color yachtColor = Color.rgb(red, green, blue);
|
||||
playerYacht.setBoatColor(yachtColor);
|
||||
} else if (requestType.equals(CustomizeRequestType.SHAPE)) {
|
||||
String type = new String(customizeData);
|
||||
playerYacht.setBoatType(BoatMeshType.valueOf(type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,8 +771,8 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
private static void notifyMessageListeners(Message message) {
|
||||
for (NewMessageListener mpl : markListeners) {
|
||||
mpl.notify(message);
|
||||
for (NewMessageListener ml : newMessageListeners) {
|
||||
ml.notify(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,8 +784,39 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
public static void addMarkPassListener(NewMessageListener listener) {
|
||||
markListeners.add(listener);
|
||||
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+");
|
||||
if (words.length > 2 && isHost) {
|
||||
switch (words[2].trim()) {
|
||||
case "/speed":
|
||||
try {
|
||||
setSpeedMultiplier(Double.valueOf(words[3]));
|
||||
sendServerMessage(chatterMessage.getMessage_type(),
|
||||
"Speed modifier set to x" + words[3]);
|
||||
} 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");
|
||||
endRace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
notifyMessageListeners(chatterMessage);
|
||||
}
|
||||
|
||||
public static void addMessageEventListener(NewMessageListener listener) {
|
||||
newMessageListeners.add(listener);
|
||||
}
|
||||
|
||||
public static void setCustomizationFlag() {
|
||||
@@ -699,4 +830,45 @@ public class GameState implements Runnable {
|
||||
public static void resetCustomizationFlag() {
|
||||
customizationFlag = false;
|
||||
}
|
||||
|
||||
public static void setPlayerHasLeftFlag(Boolean flag) {
|
||||
playerHasLeftFlag = flag;
|
||||
}
|
||||
|
||||
public static Boolean getPlayerHasLeftFlag() {
|
||||
return playerHasLeftFlag;
|
||||
}
|
||||
|
||||
public static Integer getNumberOfPlayers(){
|
||||
Integer numPlayers = 1;
|
||||
|
||||
for(Player p : getPlayers()){
|
||||
if(p.getSocket().isConnected()){
|
||||
numPlayers++;
|
||||
}
|
||||
}
|
||||
|
||||
return numPlayers;
|
||||
}
|
||||
|
||||
public static Integer getCapacity(){
|
||||
return maxPlayers;
|
||||
}
|
||||
|
||||
public static void setMaxPlayers(Integer newMax){
|
||||
maxPlayers = newMax;
|
||||
}
|
||||
|
||||
public static void endRace () {
|
||||
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
|
||||
currentStage = GameStages.FINISHED;
|
||||
}
|
||||
|
||||
public static void setSpeedMultiplier (double multiplier) {
|
||||
speedMultiplier = multiplier;
|
||||
}
|
||||
|
||||
public static double getSpeedMultiplier () {
|
||||
return speedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.Player;
|
||||
import seng302.gameServer.messages.Heartbeat;
|
||||
import seng302.gameServer.messages.Message;
|
||||
@@ -14,6 +16,9 @@ import seng302.gameServer.messages.Message;
|
||||
* cannot be sent to a player
|
||||
*/
|
||||
public class HeartbeatThread implements Runnable {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
|
||||
|
||||
private final int HEARTBEAT_PERIOD = 200;
|
||||
private ClientConnectionDelegate delegate;
|
||||
private Integer seqNum;
|
||||
@@ -44,12 +49,12 @@ public class HeartbeatThread implements Runnable {
|
||||
* The delegate is notified if a player has disconnected
|
||||
*/
|
||||
private void sendHeartbeatToAllPlayers(){
|
||||
try {
|
||||
Message heartbeat = new Heartbeat(seqNum);
|
||||
for (Player player : GameState.getPlayers()) {
|
||||
if (!player.getSocket().isConnected()) {
|
||||
playerLostConnection(player);
|
||||
}
|
||||
|
||||
try {
|
||||
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
|
||||
} catch (IOException e) {
|
||||
@@ -58,6 +63,9 @@ public class HeartbeatThread implements Runnable {
|
||||
}
|
||||
updateDelegate();
|
||||
seqNum++;
|
||||
} catch (NullPointerException ne) {
|
||||
logger.debug("Socket closed between checking for connection and sending heartbeat");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import seng302.gameServer.messages.*;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.gameServer.messages.Message;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.PolarTable;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.utilities.XMLParser;
|
||||
|
||||
/**
|
||||
* A class describing the overall server, which creates and collects server threads for each client
|
||||
@@ -19,37 +32,77 @@ import java.util.*;
|
||||
*/
|
||||
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||
|
||||
private static final int PORT = 4942;
|
||||
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||
private static final int LOG_LEVEL = 1;
|
||||
private static final int WARNING_TIME = 10 * -1000;
|
||||
private static final int PREPATORY_TIME = 5 * -1000;
|
||||
public static final int TIME_TILL_START = 10 * 1000;
|
||||
|
||||
private static final int MAX_WIND_SPEED = 12000;
|
||||
private static final int MIN_WIND_SPEED = 8000;
|
||||
|
||||
public static int windSpeed = 1000;
|
||||
|
||||
private boolean terminated;
|
||||
|
||||
private Thread thread;
|
||||
|
||||
private ServerSocket serverSocket = null;
|
||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
|
||||
private static Integer capacity;
|
||||
|
||||
private void startAdvertisingServer() {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db;
|
||||
Document doc;
|
||||
XMLGenerator generator = new XMLGenerator();
|
||||
|
||||
try {
|
||||
db = dbf.newDocumentBuilder();
|
||||
String regatta = generator.getRegattaAsXml();
|
||||
StringReader stringReader = new StringReader(regatta);
|
||||
InputSource is = new InputSource(stringReader);
|
||||
doc = db.parse(is);
|
||||
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||
logger.warn("Couldn't load race regatta");
|
||||
return;
|
||||
}
|
||||
|
||||
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
|
||||
|
||||
|
||||
Integer capacity = GameState.getCapacity();
|
||||
Integer numPlayers = GameState.getNumberOfPlayers();
|
||||
Integer spacesLeft = capacity - numPlayers;
|
||||
|
||||
// No spaces left on server
|
||||
if (spacesLeft < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start advertising server
|
||||
try {
|
||||
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setCapacity(capacity).setNumberOfPlayers(numPlayers);
|
||||
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not register server");
|
||||
}
|
||||
}
|
||||
|
||||
public MainServerThread() {
|
||||
new GameState("localhost");
|
||||
try {
|
||||
serverSocket = new ServerSocket(PORT);
|
||||
} catch (IOException e) {
|
||||
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
|
||||
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||
0);
|
||||
}
|
||||
|
||||
startAdvertisingServer();
|
||||
|
||||
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
||||
GameState.addMarkPassListener(this::broadcastMessage);
|
||||
GameState.addMessageEventListener(this::broadcastMessage);
|
||||
terminated = false;
|
||||
thread = new Thread(this, "MainServer");
|
||||
startUpdatingWind();
|
||||
startSpawningTokens();
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@@ -61,53 +114,73 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
|
||||
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||
while (!terminated) {
|
||||
if (GameState.getPlayerHasLeftFlag()) {
|
||||
for (ServerToClientThread stc : serverToClientThreads) {
|
||||
if (!stc.isSocketOpen()) {
|
||||
GameState.getYachts().remove(stc.getSourceId());
|
||||
sendSetupMessages();
|
||||
try {
|
||||
stc.getSocket().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
GameState.setPlayerHasLeftFlag(false);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
||||
} catch (InterruptedException e) {
|
||||
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
|
||||
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
|
||||
}
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
|
||||
.getCustomizationFlag()) {
|
||||
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
|
||||
for (ServerToClientThread thread : serverToClientThreads) {
|
||||
thread.sendSetupMessages();
|
||||
}
|
||||
sendSetupMessages();
|
||||
GameState.resetCustomizationFlag();
|
||||
}
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||
updateClients();
|
||||
sendBoatLocations();
|
||||
}
|
||||
|
||||
//RACING
|
||||
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||
updateClients();
|
||||
sendBoatLocations();
|
||||
}
|
||||
|
||||
//FINISHED
|
||||
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
try {
|
||||
Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus
|
||||
terminate();
|
||||
} catch (InterruptedException ie) {
|
||||
logger.trace("Thread interrupted while waiting to terminate clients", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
||||
try {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.terminate();
|
||||
}
|
||||
serverSocket.close();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
System.out.println("IO error in server thread handler upon closing socket");
|
||||
}
|
||||
}
|
||||
|
||||
public void updateClients() {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.sendBoatLocationPackets();
|
||||
private void sendBoatLocations() {
|
||||
for (ServerYacht serverYacht : GameState.getYachts().values()) {
|
||||
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSetupMessages() {
|
||||
broadcastMessage(MessageFactory.getRaceXML());
|
||||
broadcastMessage(MessageFactory.getRegattaXML());
|
||||
broadcastMessage(MessageFactory.getBoatXML());
|
||||
}
|
||||
|
||||
private void broadcastMessage(Message message) {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.sendMessage(message);
|
||||
@@ -122,27 +195,31 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
|
||||
if (Math.floorMod(random.nextInt(), 2) == 0){
|
||||
direction += random.nextInt(4);
|
||||
windSpeed += random.nextInt(20) + 50;
|
||||
windSpeed += random.nextInt(20) + 459;
|
||||
}
|
||||
else{
|
||||
direction -= random.nextInt(4);
|
||||
windSpeed -= random.nextInt(20) + 50;
|
||||
windSpeed -= random.nextInt(20) + 459;
|
||||
}
|
||||
|
||||
direction = Math.floorMod(direction, 360);
|
||||
|
||||
if (windSpeed > MAX_WIND_SPEED){
|
||||
windSpeed -= random.nextInt(1000);
|
||||
windSpeed -= random.nextInt(500);
|
||||
}
|
||||
|
||||
if (windSpeed <= MIN_WIND_SPEED){
|
||||
windSpeed += random.nextInt(1000);
|
||||
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() {
|
||||
@@ -153,12 +230,18 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
|
||||
static void serverLog(String message, int logLevel) {
|
||||
if (logLevel <= LOG_LEVEL) {
|
||||
System.out.println(
|
||||
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,14 +251,19 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
*/
|
||||
@Override
|
||||
public void clientConnected(ServerToClientThread serverToClientThread) {
|
||||
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||
serverToClientThreads.add(serverToClientThread);
|
||||
serverToClientThread.addConnectionListener(() -> {
|
||||
for (ServerToClientThread thread : serverToClientThreads) {
|
||||
thread.sendSetupMessages();
|
||||
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||
if (serverToClientThreads.size() == 0) { //Sets first client as host.
|
||||
serverToClientThread.setAsHost();
|
||||
}
|
||||
});
|
||||
serverToClientThreads.add(serverToClientThread);
|
||||
serverToClientThread.addConnectionListener(this::sendSetupMessages);
|
||||
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||
|
||||
try {
|
||||
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Couldn't update advertisement");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,12 +273,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Player player) {
|
||||
// try {
|
||||
// player.getSocket().close();
|
||||
// } catch (Exception e) {
|
||||
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
|
||||
// }
|
||||
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
||||
logger.debug("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
||||
GameState.removeYacht(player.getYacht().getSourceId());
|
||||
GameState.removePlayer(player);
|
||||
ServerToClientThread closedConnection = null;
|
||||
@@ -202,69 +285,41 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}
|
||||
}
|
||||
serverToClientThreads.remove(closedConnection);
|
||||
|
||||
try {
|
||||
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Couldn't update advertisement");
|
||||
}
|
||||
|
||||
closedConnection.terminate();
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
try {
|
||||
ServerAdvertiser.getInstance().unregister();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error unregistering server");
|
||||
}
|
||||
|
||||
initialiseBoatPositions();
|
||||
Timer t = new Timer();
|
||||
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
broadcastMessage(makeRaceStatusMessage());
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
broadcastMessage(makeRaceStartMessage());
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|
||||
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
|
||||
}
|
||||
}
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
|
||||
private RaceStartStatusMessage makeRaceStartMessage() {
|
||||
Long raceStartTime = GameState.getStartTime();
|
||||
|
||||
return new RaceStartStatusMessage(1, raceStartTime ,
|
||||
1, RaceStartNotificationType.SET_RACE_START_TIME);
|
||||
}
|
||||
|
||||
private RaceStatusMessage makeRaceStatusMessage() {
|
||||
// variables taken from GameServerThread
|
||||
|
||||
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||
RaceStatus raceStatus;
|
||||
|
||||
for (Player player : GameState.getPlayers()) {
|
||||
ServerYacht y = player.getYacht();
|
||||
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||
y.getLegNumber(),
|
||||
0, 0, 1234L,
|
||||
1234L);
|
||||
boatSubMessages.add(m);
|
||||
}
|
||||
|
||||
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
raceStatus = RaceStatus.PRESTART;
|
||||
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||
raceStatus = RaceStatus.PRESTART;
|
||||
|
||||
if (timeTillStart > WARNING_TIME) {
|
||||
raceStatus = RaceStatus.WARNING;
|
||||
sendSetupMessages();
|
||||
}
|
||||
|
||||
if (timeTillStart > PREPATORY_TIME) {
|
||||
raceStatus = RaceStatus.PREPARATORY;
|
||||
}
|
||||
} else {
|
||||
raceStatus = RaceStatus.STARTED;
|
||||
}
|
||||
|
||||
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||
GameState.getWindDirection(),
|
||||
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
@@ -275,10 +330,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||
*/
|
||||
private void initialiseBoatPositions() {
|
||||
// Getting the start line compound marks
|
||||
// if (gameClient== null) {
|
||||
// return;
|
||||
// }
|
||||
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
||||
GeoPoint startMark1 = cm.getSubMark(1);
|
||||
GeoPoint startMark2 = cm.getSubMark(2);
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import seng302.gameServer.messages.BoatLocationMessage;
|
||||
import seng302.gameServer.messages.BoatSubMessage;
|
||||
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.model.Player;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
|
||||
/**
|
||||
* A Class for interfacing between the data we have in the GameState to the messages we need to send
|
||||
* through the MainServerThread.
|
||||
*
|
||||
* WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started)
|
||||
* // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯
|
||||
* Created by wmu16 on 29/08/17.
|
||||
*/
|
||||
|
||||
/*
|
||||
Ideally this class would be created with an instance of the GameState (I tried implementing this for
|
||||
a bit) but it was too difficult to properly make GameState non static without doing some proper
|
||||
re working. To do later.
|
||||
*/
|
||||
public class MessageFactory {
|
||||
|
||||
private static XMLGenerator xmlGenerator = new XMLGenerator();
|
||||
|
||||
|
||||
public static RaceStartStatusMessage getRaceStartStatusMessage() {
|
||||
return new RaceStartStatusMessage(
|
||||
1,
|
||||
GameState.getStartTime(),
|
||||
1,
|
||||
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||
}
|
||||
|
||||
public static RaceStatusMessage getRaceStatusMessage() {
|
||||
// variables taken from GameServerThread
|
||||
|
||||
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||
RaceStatus raceStatus;
|
||||
|
||||
for (Player player : GameState.getPlayers()) {
|
||||
ServerYacht y = player.getYacht();
|
||||
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||
y.getLegNumber(),
|
||||
0, 0, 1234L,
|
||||
1234L);
|
||||
boatSubMessages.add(m);
|
||||
}
|
||||
|
||||
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
raceStatus = RaceStatus.PRESTART;
|
||||
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||
raceStatus = RaceStatus.PRESTART;
|
||||
|
||||
if (timeTillStart > GameState.WARNING_TIME) {
|
||||
raceStatus = RaceStatus.WARNING;
|
||||
}
|
||||
|
||||
if (timeTillStart > GameState.PREPATORY_TIME) {
|
||||
raceStatus = RaceStatus.PREPARATORY;
|
||||
}
|
||||
} else {
|
||||
raceStatus = RaceStatus.STARTED;
|
||||
}
|
||||
|
||||
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||
GameState.getWindDirection(),
|
||||
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||
}
|
||||
|
||||
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
|
||||
return new BoatLocationMessage(
|
||||
yacht.getSourceId(),
|
||||
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
|
||||
yacht.getLocation().getLat(),
|
||||
yacht.getLocation().getLng(),
|
||||
yacht.getHeading(),
|
||||
yacht.getCurrentVelocity().longValue());
|
||||
}
|
||||
|
||||
public static XMLMessage getRaceXML() {
|
||||
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||
List<Token> tokens = GameState.getTokensInPlay();
|
||||
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
|
||||
xmlGenerator.setRaceTemplate(raceXMLTemplate);
|
||||
|
||||
XMLMessage raceXMLMessage = new XMLMessage(
|
||||
xmlGenerator.getRaceAsXml(),
|
||||
XMLMessageSubType.RACE,
|
||||
xmlGenerator.getRaceAsXml().length());
|
||||
|
||||
return raceXMLMessage;
|
||||
}
|
||||
|
||||
public static XMLMessage getRegattaXML() {
|
||||
//@TODO calculate lat/lng values
|
||||
|
||||
return new XMLMessage(
|
||||
xmlGenerator.getRegattaAsXml(),
|
||||
XMLMessageSubType.REGATTA,
|
||||
xmlGenerator.getRegattaAsXml().length());
|
||||
}
|
||||
|
||||
public static XMLMessage getBoatXML() {
|
||||
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||
List<Token> tokens = GameState.getTokensInPlay();
|
||||
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
|
||||
xmlGenerator.setRaceTemplate(raceXMLTemplate);
|
||||
|
||||
return new XMLMessage(
|
||||
xmlGenerator.getBoatsAsXml(),
|
||||
XMLMessageSubType.BOAT,
|
||||
xmlGenerator.getBoatsAsXml().length());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* Advertises the game server on the local network
|
||||
*/
|
||||
public class ServerAdvertiser {
|
||||
/*
|
||||
Our service name & protocol
|
||||
|
||||
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
|
||||
Where Service is unique on the network, and protocol is usually _tcp.
|
||||
|
||||
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
|
||||
the lookup should be performed using an IP multicast query on the local IP network.
|
||||
|
||||
Read this before changing any of the following values
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
|
||||
*/
|
||||
private static String SERVICE = "_partyatsea";
|
||||
private static String PROTOCOL = "_tcp";
|
||||
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
|
||||
|
||||
private static ServerAdvertiser instance = null;
|
||||
private static JmDNS jmdnsInstance = null;
|
||||
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
|
||||
|
||||
private Hashtable<String ,String> props;
|
||||
|
||||
private ServerAdvertiser() throws IOException{
|
||||
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||
|
||||
props = new Hashtable<>();
|
||||
props.put("map", "");
|
||||
props.put("spacesLeft", "0");
|
||||
props.put("capacity", "0");
|
||||
props.put("players", "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
|
||||
* @return A ServerAdvertiser Instance
|
||||
* @throws IOException If there was an exception creating the instance
|
||||
*/
|
||||
public static ServerAdvertiser getInstance() throws IOException {
|
||||
if (instance == null){
|
||||
instance = new ServerAdvertiser();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the map name and broadcast an update on the network
|
||||
* @param mapName The new map name
|
||||
* @return The current ServerAdvertiser instance
|
||||
*/
|
||||
public ServerAdvertiser setMapName(String mapName){
|
||||
props.replace("map", mapName);
|
||||
|
||||
if (serviceInfo != null){
|
||||
serviceInfo.setText(props);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of players on the server and broadcast an update on the network
|
||||
* @param numPlayers The number of players on the server
|
||||
* @return The current ServerAdvertiser instance
|
||||
*/
|
||||
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
|
||||
props.replace("players", numPlayers.toString());
|
||||
|
||||
if (serviceInfo != null){
|
||||
serviceInfo.setText(props);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max capacity of the server and broadcast an update on the network
|
||||
* @param capacity The maximum capacity of the server
|
||||
* @return The current ServerAdvertiser instance
|
||||
*/
|
||||
public ServerAdvertiser setCapacity(Integer capacity){
|
||||
props.replace("capacity", capacity.toString());
|
||||
|
||||
if (serviceInfo != null){
|
||||
serviceInfo.setText(props);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this service on the network
|
||||
*
|
||||
* Note: other parameters (map name/spaces left etc) are set after the
|
||||
* service has been registered
|
||||
* @param portNo The servers port number
|
||||
* @param serverName The servers name
|
||||
*/
|
||||
public void registerGame(Integer portNo, String serverName) {
|
||||
|
||||
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
|
||||
|
||||
new java.util.Timer().schedule(
|
||||
new java.util.TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
jmdnsInstance.registerService(serviceInfo);
|
||||
} catch (IOException e) {
|
||||
System.out.println("Failed");
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the service
|
||||
*/
|
||||
public void unregister(){
|
||||
if (serviceInfo != null)
|
||||
jmdnsInstance.unregisterService(serviceInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local host ip address.
|
||||
*
|
||||
* @return the localhost ip address
|
||||
*/
|
||||
public static String getLocalHostIp() {
|
||||
String ipAddress = null;
|
||||
try {
|
||||
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||
while (e.hasMoreElements()) {
|
||||
NetworkInterface ni = e.nextElement();
|
||||
if (ni.isLoopback())
|
||||
continue;
|
||||
if(ni.isPointToPoint())
|
||||
continue;
|
||||
if(ni.isVirtual())
|
||||
continue;
|
||||
|
||||
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||
while(addresses.hasMoreElements()) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
if(address instanceof Inet4Address) { // skip all ipv6
|
||||
ipAddress = address.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (ipAddress == null) {
|
||||
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||
}
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
public class ServerDescription {
|
||||
private Integer capacity;
|
||||
private String address;
|
||||
private Integer portNum;
|
||||
private String serverName;
|
||||
private String mapName;
|
||||
private Integer numPlayers;
|
||||
|
||||
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
|
||||
this.serverName = serverName;
|
||||
this.mapName = mapName;
|
||||
this.numPlayers = numPlayers;
|
||||
this.address = address;
|
||||
this.portNum = portNum;
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public String getMapName() {
|
||||
return mapName;
|
||||
}
|
||||
|
||||
public Integer portNumber() {
|
||||
return portNum;
|
||||
}
|
||||
|
||||
public String getAddress(){
|
||||
return address;
|
||||
}
|
||||
|
||||
public Integer getNumPlayers() {
|
||||
return numPlayers;
|
||||
}
|
||||
|
||||
public Integer getCapacity(){
|
||||
return capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
final ServerDescription other = (ServerDescription) obj;
|
||||
|
||||
if (!this.getAddress().equals(other.getAddress()) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.portNumber().equals(other.portNumber())){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getMapName().equals(other.getMapName())){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getName().equals(other.getName())){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getCapacity().equals(other.getCapacity())){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getName().hashCode() + this.getAddress().hashCode() +
|
||||
this.portNumber().hashCode() + this.getMapName().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package seng302.gameServer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.ChatterMessage;
|
||||
import seng302.gameServer.messages.ClientType;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.gameServer.messages.Message;
|
||||
@@ -28,5 +29,18 @@ public class ServerPacketParser {
|
||||
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
||||
return CustomizeRequestType.getRequestType((int) type);
|
||||
}
|
||||
|
||||
public static ChatterMessage extractChatterText(byte[] payload) {
|
||||
return new ChatterMessage(
|
||||
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||
);
|
||||
}
|
||||
|
||||
public static ChatterMessage extractChatterText(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
return new ChatterMessage(
|
||||
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.CRC32;
|
||||
@@ -21,7 +19,7 @@ import java.util.zip.Checksum;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatLocationMessage;
|
||||
import seng302.gameServer.messages.ChatterMessage;
|
||||
import seng302.gameServer.messages.ClientType;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.gameServer.messages.Message;
|
||||
@@ -29,38 +27,20 @@ import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||
import seng302.gameServer.messages.XMLMessage;
|
||||
import seng302.gameServer.messages.XMLMessageSubType;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.stream.packets.PacketType;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.model.stream.xml.generator.Race;
|
||||
import seng302.model.stream.xml.generator.Regatta;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatLocationMessage;
|
||||
import seng302.gameServer.messages.ClientType;
|
||||
import seng302.gameServer.messages.Message;
|
||||
import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||
import seng302.gameServer.messages.XMLMessage;
|
||||
import seng302.gameServer.messages.XMLMessageSubType;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.stream.packets.PacketType;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.model.stream.xml.generator.Race;
|
||||
import seng302.model.stream.xml.generator.Regatta;
|
||||
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
||||
* its own thread. All server threads created and owned by the server thread handler which can
|
||||
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
||||
*/
|
||||
public class ServerToClientThread implements Runnable, Observer {
|
||||
public class ServerToClientThread implements Runnable {
|
||||
|
||||
/**
|
||||
* Called to notify listeners when this thread receives a connection correctly.
|
||||
@@ -91,8 +71,9 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
|
||||
private ClientType clientType;
|
||||
private Boolean isRegistered = false;
|
||||
private Boolean isHost = false;
|
||||
|
||||
private XMLGenerator xml;
|
||||
private XMLGenerator xmlGenerator;
|
||||
|
||||
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||
private DisconnectListener disconnectListener;
|
||||
@@ -115,6 +96,10 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public Integer getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
private void setUpPlayer(){
|
||||
BufferedReader fn;
|
||||
String fName = "";
|
||||
@@ -141,24 +126,14 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||
|
||||
ServerYacht yacht = new ServerYacht(
|
||||
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||
BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||
);
|
||||
|
||||
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
|
||||
player = new Player(socket, yacht);
|
||||
GameState.addYacht(sourceId, yacht);
|
||||
GameState.addPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
if (arg != null) {
|
||||
sendMessage((Message) arg);
|
||||
} else {
|
||||
sendSetupMessages();
|
||||
}
|
||||
}
|
||||
|
||||
private void completeRegistration(ClientType clientType) throws IOException {
|
||||
// Fail if not a player
|
||||
if (!clientType.equals(ClientType.PLAYER)){
|
||||
@@ -167,7 +142,7 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
|
||||
if (GameState.getPlayers().size() >= GameState.getCapacity()){
|
||||
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
||||
os.write(responseMessage.getBuffer());
|
||||
return;
|
||||
@@ -225,7 +200,12 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
|
||||
completeRegistration(requestedType);
|
||||
break;
|
||||
|
||||
case CHATTER_TEXT:
|
||||
ChatterMessage chatterMessage = ServerPacketParser
|
||||
.extractChatterText(
|
||||
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||
GameState.processChatter(chatterMessage, isHost);
|
||||
break;
|
||||
case RACE_CUSTOMIZATION_REQUEST:
|
||||
Long sourceID = Message
|
||||
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
|
||||
@@ -244,39 +224,31 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
closeSocket();
|
||||
GameState.setPlayerHasLeftFlag(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
GameState.setPlayerHasLeftFlag(true);
|
||||
logger.warn("Closed serverToClientThread" + thread, 1);
|
||||
}
|
||||
|
||||
public void sendSetupMessages() {
|
||||
xml = new XMLGenerator();
|
||||
Race race = new Race();
|
||||
xmlGenerator = new XMLGenerator();
|
||||
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
|
||||
|
||||
for (ServerYacht yacht : GameState.getYachts().values()) {
|
||||
race.addBoat(yacht);
|
||||
}
|
||||
|
||||
//@TODO calculate lat/lng values
|
||||
xml.setRegatta(
|
||||
new Regatta(
|
||||
"Party Parrot Test Server", "Bermuda Test Course",
|
||||
57.6679590, 11.8503233)
|
||||
);
|
||||
xml.setRace(race);
|
||||
xmlGenerator.setRaceTemplate(race);
|
||||
|
||||
XMLMessage xmlMessage;
|
||||
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
|
||||
xml.getRegattaAsXml().length());
|
||||
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
|
||||
xmlGenerator.getRegattaAsXml().length());
|
||||
sendMessage(xmlMessage);
|
||||
|
||||
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
|
||||
xml.getBoatsAsXml().length());
|
||||
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
|
||||
xmlGenerator.getBoatsAsXml().length());
|
||||
sendMessage(xmlMessage);
|
||||
|
||||
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
|
||||
xml.getRaceAsXml().length());
|
||||
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
|
||||
xmlGenerator.getRaceAsXml().length());
|
||||
sendMessage(xmlMessage);
|
||||
}
|
||||
|
||||
@@ -288,6 +260,10 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean isSocketOpen() {
|
||||
return !socket.isClosed();
|
||||
}
|
||||
|
||||
private int readByte() throws Exception {
|
||||
int currentByte = -1;
|
||||
try {
|
||||
@@ -334,23 +310,6 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
return seqNo;
|
||||
}
|
||||
|
||||
|
||||
public void sendBoatLocationPackets() {
|
||||
ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||
for (ServerYacht yacht : yachts) {
|
||||
BoatLocationMessage boatLocationMessage =
|
||||
new BoatLocationMessage(
|
||||
yacht.getSourceId(),
|
||||
getSeqNo(),
|
||||
yacht.getLocation().getLat(),
|
||||
yacht.getLocation().getLng(),
|
||||
yacht.getHeading(),
|
||||
yacht.getCurrentVelocity().longValue());
|
||||
|
||||
sendMessage(boatLocationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public Thread getThread() {
|
||||
return thread;
|
||||
}
|
||||
@@ -363,10 +322,6 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
return yacht;
|
||||
}
|
||||
|
||||
public void sendCollisionMessage(Integer yachtId) {
|
||||
sendMessage(new YachtEventCodeMessage(yachtId));
|
||||
}
|
||||
|
||||
public void addConnectionListener(ConnectionListener listener) {
|
||||
connectionListeners.add(listener);
|
||||
}
|
||||
@@ -386,4 +341,8 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
||||
this.disconnectListener = disconnectListener;
|
||||
}
|
||||
|
||||
public void setAsHost() {
|
||||
isHost = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ public class ChatterMessage extends Message {
|
||||
private int message_size = 21;
|
||||
private String message;
|
||||
|
||||
public ChatterMessage(int message_type, int message_size, String message) {
|
||||
public ChatterMessage(int message_type, String message) {
|
||||
byte[] byteMessage = message.getBytes();
|
||||
|
||||
this.message_type = message_type;
|
||||
this.message_size = message_size;
|
||||
this.message_size = byteMessage.length;
|
||||
this.message = message;
|
||||
|
||||
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
||||
@@ -23,7 +25,7 @@ public class ChatterMessage extends Message {
|
||||
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||
putInt(message_type, 1);
|
||||
putInt(message_size, 1);
|
||||
putBytes(message.getBytes());
|
||||
putBytes(byteMessage);
|
||||
|
||||
writeCRC();
|
||||
rewind();
|
||||
@@ -34,5 +36,11 @@ public class ChatterMessage extends Message {
|
||||
return MESSAGE_SIZE + message_size;
|
||||
}
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getMessage_type() {
|
||||
return message_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class MarkRoundingMessage extends Message{
|
||||
* @param roundingBoatStatus roundingBoatStatus
|
||||
* @param roundingSide roundingSide
|
||||
* @param markId markId
|
||||
* @param markType .
|
||||
*/
|
||||
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||
RoundingSide roundingSide, MarkType markType, int markId) {
|
||||
|
||||
@@ -18,13 +18,13 @@ public class YachtEventCodeMessage extends Message {
|
||||
private int eventId;
|
||||
|
||||
|
||||
public YachtEventCodeMessage(Integer subjectId) {
|
||||
public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) {
|
||||
timeStamp = System.currentTimeMillis() / 1000L;
|
||||
ack = 0;
|
||||
raceId = 1;
|
||||
destSourceId = subjectId; // collision boat source id
|
||||
incidentId = 0;
|
||||
eventId = 33;
|
||||
eventId = yachtEventType.getCode();
|
||||
|
||||
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||
allocateBuffer();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by wmu16 on 11/09/17.
|
||||
*/
|
||||
public enum YachtEventType {
|
||||
COLLISION(33),
|
||||
TOKEN(34);
|
||||
|
||||
private int code;
|
||||
|
||||
YachtEventType(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||
@@ -32,13 +32,13 @@ public class ClientYacht extends Observable {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MarkRoundingListener {
|
||||
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
|
||||
void notifyRounding(ClientYacht yacht, int legNumber);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||
|
||||
|
||||
private String boatType;
|
||||
private BoatMeshType boatType;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
@@ -63,10 +63,9 @@ public class ClientYacht extends Observable {
|
||||
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||
private CompoundMark lastMarkRounded;
|
||||
private Color colour;
|
||||
|
||||
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||
public ClientYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
this.boatType = boatType;
|
||||
this.sourceId = sourceId;
|
||||
@@ -90,7 +89,7 @@ public class ClientYacht extends Observable {
|
||||
super.addObserver(o);
|
||||
}
|
||||
|
||||
public String getBoatType() {
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
|
||||
@@ -189,14 +188,6 @@ public class ClientYacht extends Observable {
|
||||
return markRoundTime;
|
||||
}
|
||||
|
||||
public CompoundMark getLastMarkRounded() {
|
||||
return lastMarkRounded;
|
||||
}
|
||||
|
||||
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
|
||||
this.lastMarkRounded = lastMarkRounded;
|
||||
}
|
||||
|
||||
public GeoPoint getLocation() {
|
||||
return location;
|
||||
}
|
||||
@@ -263,7 +254,7 @@ public class ClientYacht extends Observable {
|
||||
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||
setLocation(lat, lng);
|
||||
this.heading = heading;
|
||||
// this.currentVelocity = velocity;
|
||||
this.currentVelocity = velocity;
|
||||
updateVelocityProperty(velocity);
|
||||
for (YachtLocationListener yll : locationListeners) {
|
||||
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
|
||||
@@ -286,13 +277,16 @@ public class ClientYacht extends Observable {
|
||||
return sailIn;
|
||||
}
|
||||
|
||||
public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) {
|
||||
public void roundMark(long markRoundTime, long timeSinceLastMark) {
|
||||
this.markRoundTime = markRoundTime;
|
||||
timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||
lastMarkRounded = mark;
|
||||
legNumber++;
|
||||
for (MarkRoundingListener listener : markRoundingListeners) {
|
||||
listener.notifyRounding(this, lastMarkRounded, legNumber);
|
||||
listener.notifyRounding(this, legNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public Double getCurrentVelocity() {
|
||||
return currentVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ package seng302.model;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.TimeZone;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
@@ -15,6 +12,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import seng302.model.stream.parser.RaceStartData;
|
||||
import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.utilities.Sounds;
|
||||
|
||||
/**
|
||||
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
||||
@@ -33,7 +31,9 @@ public class RaceState {
|
||||
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||
private long serverSystemTime;
|
||||
private long expectedStartTime;
|
||||
private boolean isRaceStarted = false;
|
||||
private boolean raceRunning = false;
|
||||
private boolean gunFired = false;
|
||||
private boolean raceFinished = false;
|
||||
long timeTillStart;
|
||||
private ObservableList<ClientYacht> playerPositions;
|
||||
private List<ClientYacht> collisions = new ArrayList<>();
|
||||
@@ -48,7 +48,7 @@ public class RaceState {
|
||||
this.windDirection.set(data.getWindDirection());
|
||||
this.serverSystemTime = data.getCurrentTime();
|
||||
this.expectedStartTime = data.getExpectedStartTime();
|
||||
this.isRaceStarted = data.isRaceStarted();
|
||||
this.raceRunning = data.isRaceStarted();
|
||||
}
|
||||
|
||||
public void setTimeZone (TimeZone timeZone) {
|
||||
@@ -64,6 +64,10 @@ public class RaceState {
|
||||
if (raceTime < 0) {
|
||||
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
||||
} else {
|
||||
if (!gunFired) {
|
||||
gunFired = true;
|
||||
Sounds.playCapGunSound();
|
||||
}
|
||||
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
||||
}
|
||||
}
|
||||
@@ -89,9 +93,12 @@ public class RaceState {
|
||||
}
|
||||
|
||||
public boolean isRaceStarted () {
|
||||
return isRaceStarted;
|
||||
return raceRunning;
|
||||
}
|
||||
|
||||
public void setRaceStarted(Boolean value) {
|
||||
this.raceRunning = value;
|
||||
}
|
||||
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||
playerPositions.setAll(clientYachts);
|
||||
}
|
||||
@@ -119,4 +126,12 @@ public class RaceState {
|
||||
public void removeCollisionListener(CollisionListener collisionListener) {
|
||||
collisionListeners.remove(collisionListener);
|
||||
}
|
||||
|
||||
public void setRaceFinished() {
|
||||
raceFinished = true;
|
||||
}
|
||||
|
||||
public Boolean getRaceFinished() {
|
||||
return raceFinished;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||
* not used anymore.
|
||||
*/
|
||||
public class ServerYacht extends Observable {
|
||||
public class ServerYacht {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
|
||||
|
||||
public static final Double TURN_STEP = 5.0;
|
||||
|
||||
//Boat info
|
||||
private String boatType;
|
||||
private BoatMeshType boatType;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
private String boatName;
|
||||
private String country;
|
||||
private BoatStatus boatStatus;
|
||||
|
||||
private Color boatColor;
|
||||
|
||||
|
||||
//Location
|
||||
private Double lastHeading;
|
||||
private Boolean sailIn;
|
||||
@@ -52,8 +53,12 @@ public class ServerYacht extends Observable {
|
||||
private Boolean hasPassedLine;
|
||||
private Boolean hasPassedThroughGate;
|
||||
|
||||
//PowerUp
|
||||
private TokenType powerUp;
|
||||
private Long powerUpStartTime;
|
||||
|
||||
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||
|
||||
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
this.boatType = boatType;
|
||||
this.boatStatus = BoatStatus.PRESTART;
|
||||
@@ -71,6 +76,7 @@ public class ServerYacht extends Observable {
|
||||
this.currentMarkSeqID = 0;
|
||||
this.legNumber = 0;
|
||||
this.boatColor = Colors.getColor(sourceId - 1);
|
||||
this.powerUp = null;
|
||||
|
||||
this.hasEnteredRoundingZone = false;
|
||||
this.hasPassedLine = false;
|
||||
@@ -101,13 +107,21 @@ public class ServerYacht extends Observable {
|
||||
location = geoPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
|
||||
* rounding package.
|
||||
*/
|
||||
@Override
|
||||
public void addObserver(Observer o) {
|
||||
super.addObserver(o);
|
||||
public void powerUp(TokenType powerUp) {
|
||||
this.powerUp = powerUp;
|
||||
powerUpStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void powerDown() {
|
||||
this.powerUp = null;
|
||||
}
|
||||
|
||||
public Long getPowerUpStartTime() {
|
||||
return powerUpStartTime;
|
||||
}
|
||||
|
||||
public TokenType getPowerUp() {
|
||||
return powerUp;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +137,7 @@ public class ServerYacht extends Observable {
|
||||
|
||||
/**
|
||||
* Swaps the boats direction from one side of the wind to the other.
|
||||
* @param windDirection .
|
||||
*/
|
||||
public void tackGybe(Double windDirection) {
|
||||
if (isAuto) {
|
||||
@@ -407,4 +422,11 @@ public class ServerYacht extends Observable {
|
||||
return boatColor;
|
||||
}
|
||||
|
||||
public void setBoatType(BoatMeshType boatType) {
|
||||
this.boatType = boatType;
|
||||
}
|
||||
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.stream.xml.generator.Race;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.utilities.XMLParser;
|
||||
import java.util.*;
|
||||
@@ -125,7 +127,10 @@ public class MarkOrder {
|
||||
private void loadRaceProperties(){
|
||||
XMLGenerator generator = new XMLGenerator();
|
||||
|
||||
generator.setRace(new Race());
|
||||
// TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data
|
||||
generator.setRaceTemplate(new RaceXMLTemplate(
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>()));
|
||||
|
||||
String raceXML = generator.getRaceAsXml();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class RaceStatusData {
|
||||
* Returns the data for boats collected form race status packets.
|
||||
*
|
||||
* @return A list of boat data. Boat data is in the form
|
||||
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
|
||||
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status].
|
||||
*/
|
||||
public List<long[]> getBoatData () {
|
||||
return boatData;
|
||||
|
||||
+15
-11
@@ -5,28 +5,23 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.token.Token;
|
||||
|
||||
/**
|
||||
* A Race object that can be parsed into XML
|
||||
*/
|
||||
public class Race {
|
||||
public class RaceXMLTemplate {
|
||||
|
||||
private List<ServerYacht> yachts;
|
||||
private LocalDateTime startTime;
|
||||
private List<Token> tokens;
|
||||
|
||||
public Race(){
|
||||
yachts = new ArrayList<>();
|
||||
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens) {
|
||||
this.yachts = yachts;
|
||||
this.tokens = tokens;
|
||||
startTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a boat to the race
|
||||
* @param yacht The boat to add
|
||||
*/
|
||||
public void addBoat(ServerYacht yacht) {
|
||||
yachts.add(yacht);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of boats in the race
|
||||
* @return A List of boats
|
||||
@@ -35,6 +30,15 @@ public class Race {
|
||||
return Collections.unmodifiableList(yachts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of tokens in the race
|
||||
*
|
||||
* @return A list of tokens
|
||||
*/
|
||||
public List<Token> getTokens() {
|
||||
return Collections.unmodifiableList(tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time until the race starts
|
||||
* @param seconds The time in seconds until the race starts
|
||||
+10
-2
@@ -3,7 +3,7 @@ package seng302.model.stream.xml.generator;
|
||||
/**
|
||||
* A Race regatta that can be parsed into XML
|
||||
*/
|
||||
public class Regatta {
|
||||
public class RegattaXMLTemplate {
|
||||
private final Double DEFAULT_ALTITUDE = 0d;
|
||||
private final Integer DEFAULT_REGATTA_ID = 0;
|
||||
|
||||
@@ -18,7 +18,7 @@ public class Regatta {
|
||||
private Integer utcOffset;
|
||||
private Double magneticVariation;
|
||||
|
||||
public Regatta(String name, String courseName, Double latitude, Double longitude) {
|
||||
public RegattaXMLTemplate(String name, String courseName, Double latitude, Double longitude) {
|
||||
this.name = name;
|
||||
this.id = DEFAULT_REGATTA_ID;
|
||||
this.courseName = courseName;
|
||||
@@ -74,4 +74,12 @@ public class Regatta {
|
||||
public Double getMagneticVariation(){
|
||||
return magneticVariation;
|
||||
}
|
||||
|
||||
public void setCourseName(String courseName) {
|
||||
this.courseName = courseName;
|
||||
}
|
||||
|
||||
public void setRegattaName(String regattaName) {
|
||||
this.name = regattaName;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Corner;
|
||||
import seng302.model.token.Token;
|
||||
|
||||
/**
|
||||
* Process a Document object containing race data in XML format and stores the data.
|
||||
@@ -13,13 +14,16 @@ import seng302.model.mark.Corner;
|
||||
public class RaceXMLData {
|
||||
|
||||
private List<Integer> participants;
|
||||
private List<Token> tokens;
|
||||
private Map<Integer, CompoundMark> compoundMarks;
|
||||
private List<Corner> markSequence;
|
||||
private List<Limit> courseLimit;
|
||||
|
||||
public RaceXMLData(List<Integer> participants, List<CompoundMark> compoundMarks,
|
||||
public RaceXMLData(List<Integer> participants, List<Token> tokens,
|
||||
List<CompoundMark> compoundMarks,
|
||||
List<Corner> markSequence, List<Limit> courseLimit) {
|
||||
this.participants = participants;
|
||||
this.tokens = tokens;
|
||||
this.markSequence = markSequence;
|
||||
this.courseLimit = courseLimit;
|
||||
this.compoundMarks = new HashMap<>();
|
||||
@@ -32,6 +36,10 @@ public class RaceXMLData {
|
||||
return participants;
|
||||
}
|
||||
|
||||
public List<Token> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public Map<Integer, CompoundMark> getCompoundMarks() {
|
||||
return compoundMarks;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package seng302.model.token;
|
||||
|
||||
import seng302.model.GeoPoint;
|
||||
|
||||
/**
|
||||
* A class describing a game token
|
||||
* Created by wmu16 on 28/08/17.
|
||||
*/
|
||||
public class Token extends GeoPoint {
|
||||
|
||||
private TokenType tokenType;
|
||||
|
||||
public Token(TokenType tokenType, double lat, double lng) {
|
||||
super(lat, lng);
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public TokenType getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package seng302.model.token;
|
||||
|
||||
/**
|
||||
* An enum describing the different types of game objects
|
||||
* Created by wmu16 on 28/08/17.
|
||||
*/
|
||||
public enum TokenType {
|
||||
BOOST(0),
|
||||
HANDLING(1);
|
||||
|
||||
private int value;
|
||||
|
||||
TokenType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static TokenType getToken(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return BOOST;
|
||||
case 1:
|
||||
return HANDLING;
|
||||
default:
|
||||
return BOOST;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package seng302.utilities;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
public class BonjourInstallChecker {
|
||||
private static String INSTALL_URL = "https://support.apple.com/kb/DL999?locale=en_US";
|
||||
private static String[] INSTALL_DIRECTORIES = {"C:/Program Files/Bonjour", "C:/Program Files (x86)/Bonjour"};
|
||||
|
||||
private static Boolean isWindows(){
|
||||
return System.getProperty("os.name").startsWith("Windows");
|
||||
}
|
||||
|
||||
private static Boolean isBonjourInstalled(){
|
||||
for (String dir : INSTALL_DIRECTORIES){
|
||||
File file = new File(dir);
|
||||
|
||||
if (file.isDirectory()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Boolean isBonjourSupported(){
|
||||
if (isWindows()){
|
||||
return isBonjourInstalled();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void openInstallUrl(){
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
|
||||
try {
|
||||
rt.exec("rundll32 url.dll,FileProtocolHandler " + INSTALL_URL);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import seng302.model.GeoPoint;
|
||||
public class GeoUtility {
|
||||
|
||||
private static double EARTH_RADIUS = 6378.137;
|
||||
// private static double EARTH_RADIUS = 6378.13712121212121212121212121212121212121;
|
||||
private static Double MS_TO_KNOTS = 1.943844492;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package seng302.utilities;
|
||||
|
||||
import javafx.scene.media.Media;
|
||||
import javafx.scene.media.MediaPlayer;
|
||||
|
||||
/**
|
||||
* Static class for playing sounds throughout the program
|
||||
*
|
||||
* Created by kre39 on 28/08/17.
|
||||
*/
|
||||
public class Sounds {
|
||||
|
||||
private static MediaPlayer musicPlayer;
|
||||
private static MediaPlayer soundEffect;
|
||||
private static MediaPlayer soundPlayer;
|
||||
private static MediaPlayer hoverSoundPlayer;
|
||||
|
||||
private static boolean hoverInitialized = false;
|
||||
private static boolean musicMuted = false;
|
||||
private static boolean soundEffectsMuted = false;
|
||||
|
||||
|
||||
public static void stopMusic() {
|
||||
if (musicPlayer != null) {
|
||||
musicPlayer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void setMutes() {
|
||||
if (soundPlayer != null) {
|
||||
soundPlayer.setMute(soundEffectsMuted);
|
||||
}
|
||||
if (soundEffect != null) {
|
||||
soundEffect.setMute(soundEffectsMuted);
|
||||
}
|
||||
if (musicPlayer != null) {
|
||||
musicPlayer.setMute(musicMuted);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopSoundEffects() {
|
||||
if (soundEffect != null) {
|
||||
soundEffect.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void toggleAllSounds() {
|
||||
toggleMuteEffects();
|
||||
toggleMuteMusic();
|
||||
}
|
||||
|
||||
static void toggleMuteMusic() {
|
||||
musicMuted = !musicMuted;
|
||||
if (musicPlayer != null) {
|
||||
musicPlayer.setMute(musicMuted);
|
||||
}
|
||||
}
|
||||
|
||||
static void toggleMuteEffects() {
|
||||
soundEffectsMuted = !soundEffectsMuted;
|
||||
if (soundPlayer != null) {
|
||||
soundPlayer.setMute(soundEffectsMuted);
|
||||
}
|
||||
if (soundEffect != null) {
|
||||
soundEffect.setMute(soundEffectsMuted);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isMusicMuted() {
|
||||
return musicMuted;
|
||||
}
|
||||
|
||||
public static boolean isSoundEffectsMuted() {
|
||||
return soundEffectsMuted;
|
||||
}
|
||||
|
||||
public static void playRaceMusic() {
|
||||
// Media menuMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Chill-house-music-loop-116-bpm.wav").toString());
|
||||
Media raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Music-loop-120-bpm.mp3").toString());
|
||||
musicPlayer = new MediaPlayer(raceMusic);
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.setVolume(0.3);
|
||||
musicPlayer.play();
|
||||
raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Sounds-of-the-ocean.mp3").toString());
|
||||
soundEffect = new MediaPlayer(raceMusic);
|
||||
soundEffect.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
soundEffect.setVolume(0.3);
|
||||
soundEffect.play();
|
||||
musicPlayer.setMute(musicMuted);
|
||||
soundEffect.setMute(soundEffectsMuted);
|
||||
}
|
||||
|
||||
public static void playMenuMusic() {
|
||||
Media menuMusic = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Elevator-music.mp3").toString());
|
||||
musicPlayer = new MediaPlayer(menuMusic);
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.setVolume(0.3);
|
||||
musicPlayer.play();
|
||||
}
|
||||
|
||||
|
||||
public static void playFinishMusic() {
|
||||
Media finishMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Happy-birthday-song.mp3").toString());
|
||||
musicPlayer = new MediaPlayer(finishMusic);
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.setVolume(0.3);
|
||||
musicPlayer.play();
|
||||
musicPlayer.setMute(musicMuted);
|
||||
}
|
||||
|
||||
public static void playButtonClick() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media buttonClick = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Button-click-sound.mp3")
|
||||
.toString());
|
||||
soundPlayer = new MediaPlayer(buttonClick);
|
||||
soundPlayer.setVolume(0.5);
|
||||
soundPlayer.play();
|
||||
soundPlayer.setMute(soundEffectsMuted);
|
||||
}
|
||||
}
|
||||
|
||||
public static void playFinishSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media finishSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Sms-notification.mp3")
|
||||
.toString());
|
||||
soundPlayer = new MediaPlayer(finishSound);
|
||||
soundPlayer.setVolume(0.5);
|
||||
soundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void playMarkRoundingSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media markRoundingSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/sms-tone.mp3").toString());
|
||||
soundPlayer = new MediaPlayer(markRoundingSound);
|
||||
soundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
public static void playCapGunSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media gunSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Gunshot-sound.mp3").toString());
|
||||
soundPlayer = new MediaPlayer(gunSound);
|
||||
soundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
public static void playCrashSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media crashSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
|
||||
.toString());
|
||||
soundPlayer = new MediaPlayer(crashSound);
|
||||
soundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
public static void playTokenPickupSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media pickupSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Coin-pick-up-sound-effect.mp3")
|
||||
.toString());
|
||||
soundPlayer = new MediaPlayer(pickupSound);
|
||||
soundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
public static void playHoverSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
if (!hoverInitialized) {
|
||||
Media crashSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
|
||||
.toString());
|
||||
hoverSoundPlayer = new MediaPlayer(crashSound);
|
||||
hoverInitialized = true;
|
||||
}
|
||||
hoverSoundPlayer.setVolume(0.5);
|
||||
if (hoverSoundPlayer != null) {
|
||||
hoverSoundPlayer.stop();
|
||||
}
|
||||
hoverSoundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.util.Pair;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
@@ -13,8 +14,12 @@ import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.model.stream.packets.PacketType;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.model.stream.parser.*;
|
||||
import seng302.model.stream.parser.MarkRoundingData;
|
||||
import seng302.model.stream.parser.PositionUpdateData;
|
||||
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
|
||||
import seng302.model.stream.parser.RaceStartData;
|
||||
import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.model.stream.parser.YachtEventData;
|
||||
|
||||
/**
|
||||
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
|
||||
@@ -35,7 +40,6 @@ public class StreamParser {
|
||||
return null;
|
||||
}
|
||||
long heartbeat = bytesToLong(packet.getPayload());
|
||||
System.out.println("heartbeat = " + heartbeat);
|
||||
return heartbeat;
|
||||
}
|
||||
|
||||
@@ -62,31 +66,10 @@ public class StreamParser {
|
||||
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
|
||||
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
|
||||
|
||||
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
// currentTime = format.format((new Date(currentTime)))
|
||||
|
||||
RaceStatusData data = new RaceStatusData(
|
||||
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
|
||||
);
|
||||
|
||||
// long timeTillStart =
|
||||
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
|
||||
//
|
||||
// if (timeTillStart > 0) {
|
||||
// timeSinceStart = timeTillStart;
|
||||
// } else {
|
||||
// if (raceStatus == 4 || raceStatus == 8) {
|
||||
// raceFinished = true;
|
||||
// raceStarted = false;
|
||||
// } else if (!raceStarted) {
|
||||
// raceStarted = true;
|
||||
// raceFinished = false;
|
||||
// }
|
||||
// timeSinceStart = timeTillStart;
|
||||
// }
|
||||
//
|
||||
|
||||
//
|
||||
int noBoats = payload[22];
|
||||
int raceType = payload[23];
|
||||
long boatID, estTimeAtNextMark, estTimeAtFinish;
|
||||
@@ -106,24 +89,6 @@ public class StreamParser {
|
||||
return data;
|
||||
}
|
||||
|
||||
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
||||
// Integer placing = 1;
|
||||
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
|
||||
// for (Yacht boat : boats.values()) {
|
||||
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
|
||||
// placing += 1;
|
||||
// }
|
||||
// }
|
||||
// updatingBoat.setPlacing(placing.toString());
|
||||
// updatingBoat.setLegNumber(leg);
|
||||
// boatsPos.putIfAbsent(placing, updatingBoat);
|
||||
// boatsPos.replace(placing, updatingBoat);
|
||||
// } else if(updatingBoat.getLegNumber() == null){
|
||||
// updatingBoat.setPlacing("1");
|
||||
// updatingBoat.setLegNumber(leg);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Parses and returns the text from a StreamPacket containing text data for display.
|
||||
*
|
||||
@@ -255,15 +220,15 @@ public class StreamParser {
|
||||
* @return Chatter text message as a string. Returns null if the packet is not of type
|
||||
* CHATTER_TEXT.
|
||||
*/
|
||||
public static String extractChatterText(StreamPacket packet) {
|
||||
public static Pair<Integer, String> extractChatterText(StreamPacket packet) {
|
||||
if (packet.getType() != PacketType.CHATTER_TEXT) {
|
||||
return null;
|
||||
}
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
int messageType = payload[1];
|
||||
int length = payload[2];
|
||||
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
|
||||
int length = (int) bytesToLong(new byte[]{payload[2]});
|
||||
return new Pair<>(messageType, new String(Arrays.copyOfRange(payload, 3, 3 + length)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,26 +357,6 @@ public class StreamParser {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static void extractBoatAction(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||
if (actionType == 1) {
|
||||
System.out.println("VMG");
|
||||
} else if (actionType == 2) {
|
||||
System.out.println("SAILS IN");
|
||||
} else if (actionType == 3) {
|
||||
System.out.println("SAILS OUT");
|
||||
} else if (actionType == 4) {
|
||||
System.out.println("TACK/GYBE");
|
||||
} else if (actionType == 5) {
|
||||
System.out.println("UPWIND");
|
||||
} else if (actionType == 6) {
|
||||
System.out.println("DOWNWIND");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
|
||||
*
|
||||
|
||||
@@ -3,13 +3,15 @@ package seng302.utilities;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateException;
|
||||
import seng302.gameServer.messages.XMLMessageSubType;
|
||||
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import seng302.model.stream.xml.generator.Race;
|
||||
import seng302.model.stream.xml.generator.Regatta;
|
||||
import seng302.gameServer.messages.XMLMessageSubType;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
|
||||
@@ -20,8 +22,13 @@ public class XMLGenerator {
|
||||
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
|
||||
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
|
||||
private Configuration configuration;
|
||||
private Regatta regatta;
|
||||
private Race race;
|
||||
private RegattaXMLTemplate regatta;
|
||||
private RaceXMLTemplate race;
|
||||
|
||||
public static RegattaXMLTemplate DEFAULT_REGATTA = new RegattaXMLTemplate("Party Parrot Test Server " + new Random().nextInt(100),
|
||||
"Bermuda",
|
||||
57.6679590,
|
||||
11.8503233);
|
||||
|
||||
/**
|
||||
* Set up a configuration instance for Apache Freemake
|
||||
@@ -48,7 +55,7 @@ public class XMLGenerator {
|
||||
* Note: This must be set before a regatta message can be generated
|
||||
* @param regatta The race regatta
|
||||
*/
|
||||
public void setRegatta(Regatta regatta){
|
||||
public void setRegattaTemplate(RegattaXMLTemplate regatta) {
|
||||
this.regatta = regatta;
|
||||
}
|
||||
|
||||
@@ -57,7 +64,7 @@ public class XMLGenerator {
|
||||
* Note: This must be set before a boat or race message can be generated
|
||||
* @param race The race
|
||||
*/
|
||||
public void setRace(Race race){
|
||||
public void setRaceTemplate(RaceXMLTemplate race) {
|
||||
this.race = race;
|
||||
}
|
||||
|
||||
@@ -106,7 +113,7 @@ public class XMLGenerator {
|
||||
public String getRegattaAsXml(){
|
||||
String result = null;
|
||||
|
||||
if (regatta == null) return null;
|
||||
if (regatta == null) regatta = DEFAULT_REGATTA;
|
||||
|
||||
try {
|
||||
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
|
||||
@@ -160,4 +167,16 @@ public class XMLGenerator {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void setDefaultRaceName(String raceName){
|
||||
DEFAULT_REGATTA.setRegattaName(raceName);
|
||||
}
|
||||
|
||||
public static void setDefaultMapName(String mapName){
|
||||
DEFAULT_REGATTA.setCourseName(mapName);
|
||||
}
|
||||
|
||||
public RegattaXMLTemplate getRegatta() {
|
||||
return regatta;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ import seng302.model.mark.Corner;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* Utilities for parsing XML documents
|
||||
@@ -137,18 +140,16 @@ public class XMLParser {
|
||||
if (currentBoat.getNodeName().equals("Boat")) {
|
||||
// Boat boat = new Boat(currentBoat);
|
||||
ClientYacht yacht = new ClientYacht(
|
||||
XMLParser.getNodeAttributeString(currentBoat, "Type"),
|
||||
BoatMeshType.valueOf(XMLParser.getNodeAttributeString(currentBoat, "Type")),
|
||||
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "Country"));
|
||||
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
|
||||
if (yacht.getBoatType().equals("Yacht")) {
|
||||
competingBoats.put(yacht.getSourceId(), yacht);
|
||||
}
|
||||
}
|
||||
}
|
||||
return competingBoats;
|
||||
}
|
||||
|
||||
@@ -182,12 +183,32 @@ public class XMLParser {
|
||||
Element docEle = doc.getDocumentElement();
|
||||
return new RaceXMLData(
|
||||
extractParticpantIDs(docEle),
|
||||
extractTokens(docEle),
|
||||
extractCompoundMarks(docEle),
|
||||
extractMarkOrder(docEle),
|
||||
extractCourseLimit(docEle)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts token data
|
||||
*/
|
||||
private static List<Token> extractTokens(Element docEle) {
|
||||
List<Token> tokens = new ArrayList<>();
|
||||
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
|
||||
for (int i = 0; i < tokenList.getLength(); i++) {
|
||||
Node tokenNode = tokenList.item(i);
|
||||
if (tokenNode.getNodeName().equals("Token")) {
|
||||
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
|
||||
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
|
||||
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
|
||||
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts course limit data
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatActionMessage;
|
||||
import seng302.gameServer.messages.ChatterMessage;
|
||||
import seng302.gameServer.messages.ClientType;
|
||||
import seng302.gameServer.messages.CustomizeRequestMessage;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
@@ -33,8 +34,6 @@ import seng302.model.stream.packets.StreamPacket;
|
||||
*/
|
||||
public class ClientToServerThread implements Runnable {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Functional interface for receiving packets from client socket.
|
||||
*/
|
||||
@@ -95,7 +94,7 @@ public class ClientToServerThread implements Runnable {
|
||||
|
||||
sendRegistrationRequest();
|
||||
|
||||
thread = new Thread(this);
|
||||
thread = new Thread(this, "ClientToServer");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@@ -283,9 +282,17 @@ public class ClientToServerThread implements Runnable {
|
||||
* @param message The given message type.
|
||||
*/
|
||||
private void sendBoatActionMessage(BoatActionMessage message) {
|
||||
sendByteBuffer(message.getBuffer());
|
||||
}
|
||||
|
||||
public void sendChatterMessage(String message) {
|
||||
sendByteBuffer(new ChatterMessage(clientId, message).getBuffer());
|
||||
}
|
||||
|
||||
private void sendByteBuffer(byte[] bytes) {
|
||||
if (clientId != -1) {
|
||||
try {
|
||||
os.write(message.getBuffer());
|
||||
os.write(bytes);
|
||||
} catch (IOException e) {
|
||||
logger.warn("IOException on attempting to sendBoatAction from Client");
|
||||
notifyDisconnectListeners("Cannot communicate with server");
|
||||
@@ -294,7 +301,7 @@ public class ClientToServerThread implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSocket() {
|
||||
public void closeSocket() {
|
||||
try {
|
||||
socket.close();
|
||||
socketOpen = false;
|
||||
@@ -359,7 +366,7 @@ public class ClientToServerThread implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
int getClientId () {
|
||||
public int getClientId () {
|
||||
return clientId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
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 javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
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.util.Pair;
|
||||
import seng302.gameServer.GameStages;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.gameServer.MainServerThread;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.gameServer.messages.YachtEventType;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
@@ -28,12 +37,13 @@ import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.model.stream.parser.YachtEventData;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.utilities.StreamParser;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.utilities.XMLParser;
|
||||
import seng302.visualiser.controllers.FinishScreenViewController;
|
||||
import seng302.visualiser.controllers.LobbyController;
|
||||
import seng302.visualiser.controllers.LobbyController.CloseStatus;
|
||||
import seng302.visualiser.controllers.RaceViewController;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
/**
|
||||
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
|
||||
@@ -52,6 +62,9 @@ public class GameClient {
|
||||
private RaceXMLData courseData;
|
||||
private RaceState raceState = new RaceState();
|
||||
private LobbyController lobbyController;
|
||||
private RaceViewController raceViewController;
|
||||
|
||||
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
|
||||
|
||||
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
|
||||
|
||||
@@ -74,28 +87,28 @@ public class GameClient {
|
||||
startClientToServerThread(ipAddress, portNumber);
|
||||
socketThread.addDisconnectionListener((cause) -> {
|
||||
showConnectionError(cause);
|
||||
tearDownConnection();
|
||||
Platform.runLater(this::loadStartScreen);
|
||||
});
|
||||
socketThread.addStreamObserver(this::parsePackets);
|
||||
LobbyController lobbyController = loadLobby();
|
||||
lobbyController.setSocketThread(socketThread);
|
||||
lobbyController.setPlayerID(socketThread.getClientId());
|
||||
lobbyController.setPlayerListSource(clientLobbyList);
|
||||
lobbyController.disableReadyButton();
|
||||
if (regattaData != null){
|
||||
lobbyController.setTitle(regattaData.getRegattaName());
|
||||
lobbyController.setCourseName(regattaData.getCourseName());
|
||||
|
||||
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||
|
||||
while (regattaData == null){
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
else{
|
||||
lobbyController.setTitle(ipAddress);
|
||||
lobbyController.setCourseName("");
|
||||
}
|
||||
|
||||
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
|
||||
this.lobbyController = lobbyController;
|
||||
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
|
||||
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
|
||||
|
||||
this.lobbyController = ViewManager.getInstance().goToLobby(true);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
showConnectionError("Unable to find server");
|
||||
Platform.runLater(this::loadStartScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,49 +117,41 @@ public class GameClient {
|
||||
* @param ipAddress IP to connect to.
|
||||
* @param portNumber Port to connect to.
|
||||
*/
|
||||
public void runAsHost(String ipAddress, Integer portNumber) {
|
||||
public ServerDescription runAsHost(String ipAddress, Integer portNumber, String serverName, Integer maxPlayers) {
|
||||
XMLGenerator.setDefaultRaceName(serverName);
|
||||
GameState.setMaxPlayers(maxPlayers);
|
||||
|
||||
server = new MainServerThread();
|
||||
|
||||
try {
|
||||
startClientToServerThread(ipAddress, portNumber);
|
||||
socketThread.addDisconnectionListener((cause) -> {
|
||||
Platform.runLater(this::loadStartScreen);
|
||||
});
|
||||
LobbyController lobbyController = loadLobby();
|
||||
lobbyController.setSocketThread(socketThread);
|
||||
lobbyController.setPlayerID(socketThread.getClientId());
|
||||
lobbyController.setPlayerListSource(clientLobbyList);
|
||||
if (regattaData != null) {
|
||||
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
|
||||
lobbyController.setCourseName(regattaData.getCourseName());
|
||||
} else {
|
||||
lobbyController.setTitle("Hosting: " + ipAddress);
|
||||
lobbyController.setCourseName("");
|
||||
}
|
||||
|
||||
lobbyController.addCloseListener(exitCause -> {
|
||||
if (exitCause == CloseStatus.READY) {
|
||||
GameState.resetStartTime();
|
||||
lobbyController.disableReadyButton();
|
||||
server.startGame();
|
||||
} else if (exitCause == CloseStatus.LEAVE) {
|
||||
server.terminate();
|
||||
server = null;
|
||||
loadStartScreen();
|
||||
}
|
||||
});
|
||||
this.lobbyController = lobbyController;
|
||||
} catch (IOException ioe) {
|
||||
startClientToServerThread(ipAddress, 4942);
|
||||
} catch (IOException e) {
|
||||
showConnectionError("Cannot connect to server as host");
|
||||
Platform.runLater(this::loadStartScreen);
|
||||
}
|
||||
|
||||
while (regattaData == null){
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStartScreen() {
|
||||
this.lobbyController = ViewManager.getInstance().goToLobby(false);
|
||||
|
||||
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(), ipAddress, 4942);
|
||||
}
|
||||
|
||||
private void tearDownConnection() {
|
||||
socketThread.setSocketToClose();
|
||||
if (server != null) {
|
||||
server.terminate();
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStartScreen() {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
getClass().getResource("/views/StartScreenView.fxml"));
|
||||
try {
|
||||
@@ -171,52 +176,8 @@ public class GameClient {
|
||||
socketThread.addStreamObserver(this::parsePackets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a view of the lobby into the clients pane
|
||||
*
|
||||
* @return the lobby controller.
|
||||
*/
|
||||
private LobbyController loadLobby() {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
GameClient.class.getResource("/views/LobbyView.fxml"));
|
||||
try {
|
||||
holderPane.getChildren().clear();
|
||||
holderPane.getChildren().add(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fxmlLoader.getController();
|
||||
}
|
||||
|
||||
private void loadRaceView() {
|
||||
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/RaceView.fxml");
|
||||
holderPane.getScene().setOnKeyPressed(this::keyPressed);
|
||||
holderPane.getScene().setOnKeyReleased(this::keyReleased);
|
||||
raceView = fxmlLoader.getController();
|
||||
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
|
||||
raceView.loadRace(allBoatsMap, courseData, raceState, player);
|
||||
}
|
||||
|
||||
private void loadFinishScreenView() {
|
||||
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
|
||||
FinishScreenViewController controller = fxmlLoader.getController();
|
||||
controller.setFinishers(raceState.getPlayerPositions());
|
||||
}
|
||||
|
||||
private FXMLLoader loadFXMLToHolder(String fxmlLocation) {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
getClass().getResource(fxmlLocation)
|
||||
);
|
||||
try {
|
||||
final Node fxmlLoaderFX = fxmlLoader.load();
|
||||
Platform.runLater(() -> {
|
||||
holderPane.getChildren().clear();
|
||||
holderPane.getChildren().add(fxmlLoaderFX);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fxmlLoader;
|
||||
public void setRaceViewController(RaceViewController controller) {
|
||||
this.raceViewController = controller;
|
||||
}
|
||||
|
||||
private void parsePackets() {
|
||||
@@ -245,11 +206,14 @@ public class GameClient {
|
||||
break;
|
||||
|
||||
case RACE_XML:
|
||||
courseData = XMLParser.parseRace(
|
||||
RaceXMLData raceXMLData = XMLParser.parseRace(
|
||||
StreamParser.extractXmlMessage(packet)
|
||||
);
|
||||
if (raceView != null) {
|
||||
raceView.updateRaceData(courseData);
|
||||
if (courseData == null) { //workaround for object comparisons. Avoid recreating
|
||||
courseData = raceXMLData;
|
||||
}
|
||||
if (raceView != null) { //Token update
|
||||
raceView.updateTokens(raceXMLData);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -262,11 +226,14 @@ public class GameClient {
|
||||
clientLobbyList.add(boat.getBoatName())
|
||||
);
|
||||
raceState.setBoats(allBoatsMap.values());
|
||||
if (lobbyController != null) {
|
||||
lobbyController.setBoats(allBoatsMap);
|
||||
}
|
||||
break;
|
||||
|
||||
case RACE_START_STATUS:
|
||||
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
|
||||
if (lobbyController != null) lobbyController.updateRaceState(raceState);
|
||||
if (lobbyController != null) Platform.runLater(() -> lobbyController.updateRaceState(raceState));
|
||||
break;
|
||||
|
||||
case BOAT_LOCATION:
|
||||
@@ -278,15 +245,37 @@ public class GameClient {
|
||||
break;
|
||||
|
||||
case YACHT_EVENT_CODE:
|
||||
YachtEventData yachtEventData = StreamParser.extractYachtEventCode(packet);
|
||||
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
|
||||
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN.getCode()) {
|
||||
showPickUp();
|
||||
}
|
||||
break;
|
||||
|
||||
case CHATTER_TEXT:
|
||||
Pair<Integer, String> playerIdMessagePair = StreamParser
|
||||
.extractChatterText(packet);
|
||||
raceView.updateChatHistory(
|
||||
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
|
||||
playerIdMessagePair.getValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startRaceIfAllDataReceived() {
|
||||
if (allXMLReceived() && raceView == null) {
|
||||
loadRaceView();
|
||||
raceView = ViewManager.getInstance().loadRaceView();
|
||||
|
||||
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
|
||||
raceView.loadRace(allBoatsMap, courseData, raceState, player);
|
||||
|
||||
raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> {
|
||||
if (isPressed) {
|
||||
formatAndSendChatMessage(raceView.readChatInput());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +309,6 @@ public class GameClient {
|
||||
if (allXMLReceived()) {
|
||||
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
|
||||
clientYacht.roundMark(
|
||||
courseData.getCompoundMarks().get(roundingData.getMarkId()),
|
||||
roundingData.getTimeStamp(),
|
||||
raceState.getRaceTime() - roundingData.getTimeStamp()
|
||||
);
|
||||
@@ -335,6 +323,9 @@ public class GameClient {
|
||||
for (ClientYacht yacht : allBoatsMap.values()) {
|
||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
|
||||
raceFinished = false;
|
||||
} else if (!finishedBoats.contains(yacht)) {
|
||||
finishedBoats.add(yacht);
|
||||
Sounds.playFinishSound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,9 +341,13 @@ public class GameClient {
|
||||
}
|
||||
|
||||
if (raceFinished) {
|
||||
raceViewController.showFinishDialog(finishedBoats);
|
||||
Sounds.playFinishSound();
|
||||
close();
|
||||
loadFinishScreenView();
|
||||
ViewManager.getInstance().getGameClient().stopGame();
|
||||
//loadFinishScreenView();
|
||||
}
|
||||
raceState.setRaceFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,12 +362,17 @@ public class GameClient {
|
||||
socketThread.setSocketToClose();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the key-pressed event from the text field.
|
||||
* @param e The key event triggering this call
|
||||
*/
|
||||
private void keyPressed(KeyEvent e) {
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (raceView.isChatInputFocused()) {
|
||||
if (e.getCode() == KeyCode.ENTER) {
|
||||
formatAndSendChatMessage(raceView.readChatInput());
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (e.getCode()) {
|
||||
case SPACE: // align with vmg
|
||||
socketThread.sendBoatAction(BoatAction.VMG); break;
|
||||
@@ -381,12 +381,16 @@ public class GameClient {
|
||||
case PAGE_DOWN: // downwind
|
||||
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
|
||||
case ENTER: // tack/gybe
|
||||
// if chat box is active take whatever is in there and send it to server
|
||||
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void keyReleased(KeyEvent e) {
|
||||
public void keyReleased(KeyEvent e) {
|
||||
if (raceView.isChatInputFocused()) {
|
||||
return;
|
||||
}
|
||||
switch (e.getCode()) {
|
||||
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
||||
case SHIFT: // sails in/sails out
|
||||
@@ -407,13 +411,49 @@ public class GameClient {
|
||||
* Tells race view to show a collision animation.
|
||||
*/
|
||||
private void showCollisionAlert(YachtEventData yachtEventData) {
|
||||
// 33 is the agreed code to show collision
|
||||
if (yachtEventData.getEventId() == 33) {
|
||||
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 showPickUp() {
|
||||
Sounds.playTokenPickupSound();
|
||||
}
|
||||
|
||||
private void formatAndSendChatMessage(String rawChat) {
|
||||
if (rawChat.length() > 0) {
|
||||
socketThread.sendChatterMessage(
|
||||
new SimpleDateFormat("[HH:mm:ss] ").format(new Date()) +
|
||||
allBoatsMap.get(socketThread.getClientId()).getShortName() + ": " + rawChat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void startGame(){
|
||||
server.startGame();
|
||||
}
|
||||
|
||||
public ClientToServerThread getServerThread() {
|
||||
return socketThread;
|
||||
}
|
||||
|
||||
public List<String> getPlayerNames(){
|
||||
return Collections.unmodifiableList(clientLobbyList.sorted());
|
||||
}
|
||||
|
||||
public void stopGame() {
|
||||
GameState.setCurrentStage(GameStages.CANCELLED);
|
||||
if (server != null) server.terminate();
|
||||
if (socketThread != null) socketThread.setSocketToClose();
|
||||
server = null;
|
||||
// socketThread = null;
|
||||
}
|
||||
|
||||
public Map<Integer, ClientYacht> getAllBoatsMap() {
|
||||
return allBoatsMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,25 @@
|
||||
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;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.Colors;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Corner;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.visualiser.fxObjects.AnnotationBox;
|
||||
import seng302.visualiser.fxObjects.BoatObject;
|
||||
import seng302.visualiser.fxObjects.CourseBoundary;
|
||||
import seng302.visualiser.fxObjects.Gate;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.Marker;
|
||||
import seng302.visualiser.map.Boundary;
|
||||
import seng302.visualiser.map.CanvasMap;
|
||||
import seng302.visualiser.fxObjects.assets_2D.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by cir27 on 20/07/17.
|
||||
@@ -51,8 +27,8 @@ import seng302.visualiser.map.CanvasMap;
|
||||
public class GameView extends Pane {
|
||||
|
||||
private double bufferSize = 50;
|
||||
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
||||
private double panelHeight = 960;
|
||||
private double horizontalBuffer = 0;
|
||||
|
||||
private double canvasWidth = 1100;
|
||||
private double canvasHeight = 920;
|
||||
private boolean horizontalInversion = false;
|
||||
@@ -61,168 +37,31 @@ public class GameView extends Pane {
|
||||
private ScaleDirection scaleDirection;
|
||||
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
|
||||
private double referencePointX, referencePointY;
|
||||
private double metersPerPixelX, metersPerPixelY;
|
||||
|
||||
final double SCALE_DELTA = 1.1;
|
||||
|
||||
private Text fpsDisplay = new Text();
|
||||
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, Marker> markerObjects;
|
||||
private Map<Mark, Marker2D> markerObjects;
|
||||
|
||||
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
|
||||
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
|
||||
private ObservableList<Node> gameObjects;
|
||||
private BoatObject selectedBoat = null;
|
||||
private Group annotationsGroup = new Group();
|
||||
private Group wakesGroup = new Group();
|
||||
private Group boatObjectGroup = new Group();
|
||||
private Group trails = new Group();
|
||||
private Group markers = new Group();
|
||||
private Group tokens = new Group();
|
||||
private List<CompoundMark> course = new ArrayList<>();
|
||||
|
||||
private ImageView mapImage = new ImageView();
|
||||
|
||||
//FRAME RATE
|
||||
|
||||
private AnimationTimer timer;
|
||||
private int NUM_SAMPLES = 10;
|
||||
private final long[] frameTimes = new long[NUM_SAMPLES];
|
||||
private Double frameRate = 60.0;
|
||||
private int frameTimeIndex = 0;
|
||||
private boolean arrayFilled = false;
|
||||
private ClientYacht playerYacht;
|
||||
private double windDir = 0.0;
|
||||
|
||||
double scaleFactor = 1;
|
||||
|
||||
private void zoomOut() {
|
||||
scaleFactor = 0.1;
|
||||
if (this.getScaleX() > 0.5) {
|
||||
this.setScaleX(this.getScaleX() - scaleFactor);
|
||||
this.setScaleY(this.getScaleY() - scaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
private void zoomIn() {
|
||||
scaleFactor = 0.10;
|
||||
if (this.getScaleX() < 2.5) {
|
||||
this.setScaleX(this.getScaleX() + scaleFactor);
|
||||
this.setScaleY(this.getScaleY() + scaleFactor);
|
||||
}
|
||||
}
|
||||
|
||||
private enum ScaleDirection {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
}
|
||||
|
||||
|
||||
private void trackBoat() {
|
||||
if (selectedBoat != null) {
|
||||
double x = selectedBoat.getBoatLayoutX();
|
||||
double y = selectedBoat.getBoatLayoutY();
|
||||
double displacementX = this.getWidth();
|
||||
double displacementY = this.getHeight();
|
||||
this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX());
|
||||
this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY());
|
||||
} else {
|
||||
this.setLayoutX(0);
|
||||
this.setLayoutY(0);
|
||||
}
|
||||
}
|
||||
|
||||
public GameView () {
|
||||
gameObjects = this.getChildren();
|
||||
// create image view for map, bind panel size to image
|
||||
gameObjects.add(mapImage);
|
||||
gameObjects.add(raceBorder);
|
||||
gameObjects.add(markers);
|
||||
initializeTimer();
|
||||
gameObjects.addAll(mapImage, raceBorder, markers, tokens);
|
||||
}
|
||||
|
||||
private void initializeTimer() {
|
||||
Arrays.fill(frameTimes, 1_000_000_000 / 60);
|
||||
timer = new AnimationTimer() {
|
||||
private long lastTime = 0;
|
||||
private int FPSCount = 30;
|
||||
private Double frameRate = 60.0;
|
||||
private int index = 0;
|
||||
private boolean arrayFilled = false;
|
||||
private long sum = 1_000_000_000 / 3;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
trackBoat();
|
||||
if (lastTime == 0) {
|
||||
lastTime = now;
|
||||
} else {
|
||||
if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized
|
||||
long oldFrameTime = frameTimes[frameTimeIndex];
|
||||
frameTimes[frameTimeIndex] = now;
|
||||
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;
|
||||
if (frameTimeIndex == 0) {
|
||||
arrayFilled = true;
|
||||
}
|
||||
long elapsedNanos;
|
||||
if (arrayFilled) {
|
||||
elapsedNanos = now - oldFrameTime;
|
||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length;
|
||||
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
|
||||
if (FPSCount-- == 0) {
|
||||
FPSCount = 30;
|
||||
drawFps(frameRate);
|
||||
}
|
||||
}
|
||||
lastTime = now;
|
||||
}
|
||||
}
|
||||
boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* First find the top right and bottom left points' geo locations, then retrieve map from google
|
||||
* to display on image view. - Haoming 22/5/2017
|
||||
*/
|
||||
private void drawGoogleMap() {
|
||||
findMetersPerPixel();
|
||||
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLat(), minLonPoint.getLng());
|
||||
// distance from top left extreme to panel origin (top left corner)
|
||||
double distanceFromTopLeftToOrigin = Math.sqrt(
|
||||
Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math
|
||||
.pow(topLeftPoint.getY() * metersPerPixelY, 2));
|
||||
// angle from top left extreme to panel origin
|
||||
double bearingFromTopLeftToOrigin = Math
|
||||
.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
|
||||
// the top left extreme
|
||||
GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
|
||||
GeoPoint originPos = GeoUtility
|
||||
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
||||
|
||||
// distance from origin corner to bottom right corner of the panel
|
||||
double distanceFromOriginToBottomRight = Math.sqrt(
|
||||
Math.pow(panelHeight * metersPerPixelY, 2) + Math
|
||||
.pow(panelWidth * metersPerPixelX, 2));
|
||||
double bearingFromOriginToBottomRight = Math
|
||||
.toDegrees(Math.atan2(panelWidth, -panelHeight));
|
||||
GeoPoint bottomRightPos = GeoUtility
|
||||
.getGeoCoordinate(originPos, bearingFromOriginToBottomRight,
|
||||
distanceFromOriginToBottomRight);
|
||||
|
||||
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(),
|
||||
bottomRightPos.getLat(), originPos.getLng());
|
||||
CanvasMap canvasMap = new CanvasMap(boundary);
|
||||
mapImage.setImage(canvasMap.getMapImage());
|
||||
mapImage.fitWidthProperty().bind(((AnchorPane) this.getParent()).heightProperty());
|
||||
mapImage.fitHeightProperty().bind(((AnchorPane) this.getParent()).heightProperty());
|
||||
}
|
||||
|
||||
// TODO: 16/08/17 Break up this function
|
||||
/**
|
||||
* 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.
|
||||
@@ -285,10 +124,10 @@ public class GameView extends Pane {
|
||||
rescaleRace(new ArrayList<>(markerObjects.keySet()));
|
||||
}
|
||||
//Move the Markers to initial position.
|
||||
markerObjects.forEach(((mark, marker) -> {
|
||||
markerObjects.forEach(((mark, marker2D) -> {
|
||||
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
|
||||
marker.setLayoutX(p2d.getX());
|
||||
marker.setLayoutY(p2d.getY());
|
||||
marker2D.setLayoutX(p2d.getX());
|
||||
marker2D.setLayoutY(p2d.getY());
|
||||
}));
|
||||
Platform.runLater(() -> {
|
||||
markers.getChildren().clear();
|
||||
@@ -306,18 +145,16 @@ public class GameView extends Pane {
|
||||
for (int i=1; i < sequence.size()-1; i++) { //General case.
|
||||
double averageLat = 0;
|
||||
double averageLng = 0;
|
||||
int numMarks = 0;
|
||||
int numMarks = course.get(i-1).getMarks().size();
|
||||
for (Mark mark : course.get(i-1).getMarks()) {
|
||||
numMarks += 1;
|
||||
averageLat += mark.getLat();
|
||||
averageLng += mark.getLng();
|
||||
}
|
||||
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
||||
numMarks = 0;
|
||||
numMarks = course.get(i+1).getMarks().size();
|
||||
averageLat = 0;
|
||||
averageLng = 0;
|
||||
for (Mark mark : course.get(i+1).getMarks()) {
|
||||
numMarks += 1;
|
||||
averageLat += mark.getLat();
|
||||
averageLng += mark.getLng();
|
||||
}
|
||||
@@ -380,9 +217,9 @@ public class GameView extends Pane {
|
||||
* @param colour The desired colour of the mark
|
||||
*/
|
||||
private void makeAndBindMarker(Mark observableMark, Paint colour) {
|
||||
Marker marker = new Marker(colour);
|
||||
Marker2D marker2D = new Marker2D(colour);
|
||||
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
|
||||
markerObjects.put(observableMark, marker);
|
||||
markerObjects.put(observableMark, marker2D);
|
||||
observableMark.addPositionListener((mark, lat, lon) -> {
|
||||
Point2D p2d = findScaledXY(lat, lon);
|
||||
markerObjects.get(mark).setLayoutX(p2d.getX());
|
||||
@@ -398,7 +235,7 @@ public class GameView extends Pane {
|
||||
* @param colour The desired colour of the gate.
|
||||
* @return the new gate.
|
||||
*/
|
||||
private Gate makeAndBindGate(Marker m1, Marker m2, Paint colour) {
|
||||
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
|
||||
Gate gate = new Gate(colour);
|
||||
gate.startXProperty().bind(
|
||||
m1.layoutXProperty()
|
||||
@@ -426,6 +263,9 @@ public class GameView extends Pane {
|
||||
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());
|
||||
@@ -435,122 +275,16 @@ public class GameView extends Pane {
|
||||
raceBorder.getPoints().setAll(boundaryPoints);
|
||||
}
|
||||
|
||||
// TODO: 16/08/17 initialize zooming internal to GameView only
|
||||
/**
|
||||
* Enables zoom. Has to be called after this is added to a scene.
|
||||
*/
|
||||
public void enableZoom () {
|
||||
if (this.getScene() != null) {
|
||||
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
|
||||
if (event.getCode() == KeyCode.Z) {
|
||||
zoomIn();
|
||||
} else if (event.getCode() == KeyCode.X) {
|
||||
zoomOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
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);
|
||||
// drawGoogleMap();
|
||||
}
|
||||
|
||||
private void setSelectedBoat(BoatObject bo, Boolean isSelected) {
|
||||
if (this.selectedBoat == bo && !isSelected) {
|
||||
this.selectedBoat = null;
|
||||
boatObjects.forEach((boat, group) ->
|
||||
group.setIsSelected(false)
|
||||
);
|
||||
} else if (isSelected) {
|
||||
this.selectedBoat = bo;
|
||||
for (BoatObject group : boatObjects.values()) {
|
||||
if (group != bo) {
|
||||
group.setIsSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all the boats.
|
||||
* @param yachts The yachts to set in the race
|
||||
*/
|
||||
public void setBoats(List<ClientYacht> yachts) {
|
||||
BoatObject newBoat;
|
||||
final List<Group> wakes = new ArrayList<>();
|
||||
for (ClientYacht clientYacht : yachts) {
|
||||
Paint colour = clientYacht.getColour();
|
||||
newBoat = new BoatObject();
|
||||
newBoat.addSelectedBoatListener(this::setSelectedBoat);
|
||||
newBoat.setFill(colour);
|
||||
boatObjects.put(clientYacht, newBoat);
|
||||
createAndBindAnnotationBox(clientYacht, colour);
|
||||
// wakesGroup.getChildren().add(newBoat.getWake());
|
||||
wakes.add(newBoat.getWake());
|
||||
boatObjectGroup.getChildren().add(newBoat);
|
||||
trails.getChildren().add(newBoat.getTrail());
|
||||
// TODO: 1/08/17 Make this less vile to look at.
|
||||
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
|
||||
BoatObject bo = boatObjects.get(boat);
|
||||
Point2D p2d = findScaledXY(lat, lon);
|
||||
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
|
||||
annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
|
||||
bo.setTrajectory(
|
||||
heading,
|
||||
velocity,
|
||||
metersPerPixelX,
|
||||
metersPerPixelY);
|
||||
});
|
||||
}
|
||||
annotationsGroup.getChildren().addAll(annotations.values());
|
||||
Platform.runLater(() -> {
|
||||
gameObjects.addAll(trails);
|
||||
gameObjects.addAll(wakes);
|
||||
gameObjects.addAll(annotationsGroup);
|
||||
gameObjects.addAll(boatObjectGroup);
|
||||
});
|
||||
}
|
||||
|
||||
private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) {
|
||||
AnnotationBox newAnnotation = new AnnotationBox();
|
||||
newAnnotation.setFill(colour);
|
||||
newAnnotation.addAnnotation(
|
||||
"name", "Player: " + clientYacht.getShortName()
|
||||
);
|
||||
// newAnnotation.addAnnotation(
|
||||
// "velocity",
|
||||
// yacht.getVelocityProperty(),
|
||||
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
|
||||
// );
|
||||
// newAnnotation.addAnnotation(
|
||||
// "nextMark",
|
||||
// yacht.timeTillNextProperty(),
|
||||
// (time) -> {
|
||||
// DateFormat format = new SimpleDateFormat("mm:ss");
|
||||
// return format.format(time);
|
||||
// }
|
||||
// );
|
||||
// newAnnotation.addAnnotation(
|
||||
// "lastMark",
|
||||
// yacht.timeTillNextProperty(),
|
||||
// (time) -> {
|
||||
// DateFormat format = new SimpleDateFormat("mm:ss");
|
||||
// return format.format(time);
|
||||
// }
|
||||
// );
|
||||
annotations.put(clientYacht, newAnnotation);
|
||||
}
|
||||
|
||||
private void drawFps(Double fps) {
|
||||
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,6 +347,7 @@ public class GameView extends Pane {
|
||||
referencePointX +=
|
||||
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
|
||||
/ 2;
|
||||
referencePointX += horizontalBuffer;
|
||||
}
|
||||
if (horizontalInversion) {
|
||||
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
|
||||
@@ -654,10 +389,6 @@ public class GameView extends Pane {
|
||||
return horiDistance;
|
||||
}
|
||||
|
||||
private Point2D findScaledXY(GeoPoint unscaled) {
|
||||
return findScaledXY(unscaled.getLat(), unscaled.getLng());
|
||||
}
|
||||
|
||||
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
|
||||
double distanceFromReference;
|
||||
double angleFromReference;
|
||||
@@ -700,148 +431,13 @@ public class GameView extends Pane {
|
||||
return new Point2D(xAxisLocation, yAxisLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the number of meters per pixel.
|
||||
*/
|
||||
private void findMetersPerPixel() {
|
||||
Point2D p1, p2;
|
||||
GeoPoint g1, g2;
|
||||
double theta, distance, dx, dy, dHorizontal, dVertical;
|
||||
g1 = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
|
||||
g2 = new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng());
|
||||
p1 = findScaledXY(new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()));
|
||||
p2 = findScaledXY(new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng()));
|
||||
theta = GeoUtility.getBearingRad(g1, g2);
|
||||
distance = GeoUtility.getDistance(g1, g2);
|
||||
dHorizontal = Math.abs(Math.sin(theta) * distance);
|
||||
dVertical = Math.abs(Math.cos(theta) * distance);
|
||||
dx = Math.abs(p1.getX() - p2.getX());
|
||||
dy = Math.abs(p1.getY() - p2.getY());
|
||||
metersPerPixelX = dHorizontal / dx;
|
||||
metersPerPixelY = dVertical / dy;
|
||||
|
||||
public void setSize(Double width, Double height){
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
}
|
||||
|
||||
public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime,
|
||||
boolean legTime, boolean trail, boolean wake) {
|
||||
for (BoatObject boatObject : boatObjects.values()) {
|
||||
boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
|
||||
}
|
||||
for (AnnotationBox ag : annotations.values()) {
|
||||
ag.setAnnotationVisibility("name", teamName);
|
||||
ag.setAnnotationVisibility("velocity", velocity);
|
||||
ag.setAnnotationVisibility("nextMark", estTime);
|
||||
ag.setAnnotationVisibility("lastMark", legTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFPSVisibility(boolean visibility) {
|
||||
fpsDisplay.setVisible(visibility);
|
||||
}
|
||||
|
||||
public void selectBoat(ClientYacht selectedClientYacht) {
|
||||
boatObjects.forEach((boat, group) ->
|
||||
group.setIsSelected(boat == selectedClientYacht)
|
||||
);
|
||||
}
|
||||
|
||||
public void pauseRace() {
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
public void setWindDir(double windDir) {
|
||||
this.windDir = windDir;
|
||||
}
|
||||
|
||||
public void startRace() {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
public ClientYacht getPlayerYacht() {
|
||||
return playerYacht;
|
||||
}
|
||||
|
||||
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||
this.playerYacht = playerYacht;
|
||||
playerYacht.toggleSail();
|
||||
boatObjects.get(playerYacht).setAsPlayer();
|
||||
CompoundMark currentMark = course.get(playerYacht.getLegNumber());
|
||||
for (Mark mark : currentMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextExitArrow();
|
||||
}
|
||||
annotations.get(playerYacht).addAnnotation(
|
||||
"velocity",
|
||||
playerYacht.getVelocityProperty(),
|
||||
(velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
|
||||
);
|
||||
Platform.runLater(() -> {
|
||||
boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht));
|
||||
gameObjects.add(boatObjects.get(playerYacht));
|
||||
annotationsGroup.getChildren().remove(annotations.get(playerYacht));
|
||||
gameObjects.add(annotations.get(playerYacht));
|
||||
});
|
||||
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
|
||||
}
|
||||
|
||||
private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) {
|
||||
//Only show arrows for this and next leg.
|
||||
if (compoundMark != null) {
|
||||
for (Mark mark : compoundMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextExitArrow();
|
||||
}
|
||||
}
|
||||
CompoundMark nextMark = null;
|
||||
if (legNumber < course.size() - 1) {
|
||||
nextMark = course.get(legNumber);
|
||||
for (Mark mark : nextMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextEnterArrow();
|
||||
}
|
||||
}
|
||||
if (legNumber - 2 >= 0) {
|
||||
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
|
||||
if (lastMark != nextMark) {
|
||||
for (Mark mark : lastMark.getMarks()) {
|
||||
markerObjects.get(mark).hideAllArrows();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and
|
||||
* display a flashing red circle on collision point.
|
||||
*
|
||||
* @param collisionPoint yacht collision point
|
||||
*/
|
||||
public void drawCollision(GeoPoint collisionPoint) {
|
||||
Point2D point = findScaledXY(collisionPoint);
|
||||
double circleRadius = 0.0;
|
||||
Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED);
|
||||
|
||||
circle.setFill(Color.TRANSPARENT);
|
||||
circle.setStroke(Color.RED);
|
||||
circle.setStrokeWidth(3);
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
timeline.setCycleCount(1);
|
||||
|
||||
KeyFrame keyframe1 = new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(circle.radiusProperty(), 0),
|
||||
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
|
||||
KeyFrame keyFrame2 = new KeyFrame(new Duration(1000),
|
||||
new KeyValue(circle.radiusProperty(), 50),
|
||||
new KeyValue(circle.strokeProperty(), Color.RED));
|
||||
KeyFrame keyFrame3 = new KeyFrame(new Duration(1500),
|
||||
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
|
||||
|
||||
timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3);
|
||||
|
||||
Platform.runLater(() -> gameObjects.add(circle));
|
||||
timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle)));
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
public void setFrameRateFXText(Text fpsDisplay) {
|
||||
this.fpsDisplay = null;
|
||||
this.fpsDisplay = fpsDisplay;
|
||||
public void setHorizontalBuffer(Double buff){
|
||||
this.horizontalBuffer = buff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,634 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.SceneAntialiasing;
|
||||
import javafx.scene.SubScene;
|
||||
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 seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Corner;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
import seng302.visualiser.fxObjects.assets_3D.Marker3D;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
/**
|
||||
* Collection of animated3D assets that displays a race.
|
||||
*/
|
||||
|
||||
public class GameView3D {
|
||||
|
||||
|
||||
private final double FOV = 60;
|
||||
private final double DEFAULT_CAMERA_DEPTH = -125;
|
||||
private final double DEFAULT_CAMERA_X = 0;
|
||||
private final double DEFAULT_CAMERA_Y = 155;
|
||||
|
||||
private Group root3D;
|
||||
private SubScene view;
|
||||
// ParallelCamera camera;
|
||||
private PerspectiveCamera camera;
|
||||
private Group gameObjects;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
public GameView3D () {
|
||||
camera = new PerspectiveCamera(true);
|
||||
camera.getTransforms().addAll(
|
||||
new Translate(DEFAULT_CAMERA_X,DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH)
|
||||
);
|
||||
camera.setFarClip(600);
|
||||
camera.setNearClip(0.1);
|
||||
camera.setFieldOfView(FOV);
|
||||
gameObjects = new Group();
|
||||
root3D = new Group(camera, gameObjects);
|
||||
view = new SubScene(
|
||||
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
|
||||
);
|
||||
view.setCamera(camera);
|
||||
camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0)));
|
||||
|
||||
gameObjects.getChildren().addAll(
|
||||
ModelFactory.importModel(ModelType.OCEAN).getAssets(),
|
||||
raceBorder, trail, markers, tokens
|
||||
);
|
||||
view.sceneProperty().addListener((obs, old, scene) -> {
|
||||
if (scene != null) {
|
||||
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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<Group> gates = new ArrayList<>();
|
||||
|
||||
//Creates new markers
|
||||
for (CompoundMark cMark : newCourse) {
|
||||
for (Mark mark : cMark.getMarks()) {
|
||||
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
||||
makeAndBindMarker(mark, ModelType.START_MARKER);
|
||||
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
||||
makeAndBindMarker(mark, ModelType.FINISH_MARKER);
|
||||
} else {
|
||||
makeAndBindMarker(mark, ModelType.PLAIN_MARKER);
|
||||
}
|
||||
}
|
||||
//Create gate line
|
||||
if (cMark.isGate()) {
|
||||
ModelType gateType;
|
||||
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
||||
gateType = ModelType.START_LINE;
|
||||
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
||||
gateType = ModelType.FINISH_LINE;
|
||||
} else {
|
||||
gateType = ModelType.GATE_LINE;
|
||||
}
|
||||
gates.add(makeGate(
|
||||
cMark.getSubMark(1), cMark.getSubMark(2), gateType
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
createMarkArrows();
|
||||
|
||||
//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, marker) -> {
|
||||
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
|
||||
marker.setLayoutX(p2d.getX());
|
||||
marker.setLayoutY(p2d.getY());
|
||||
}));
|
||||
Platform.runLater(() -> {
|
||||
markers.getChildren().clear();
|
||||
markers.getChildren().addAll(gates);
|
||||
markers.getChildren().addAll(markerObjects.values());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Marker and binds it's position to the given Mark.
|
||||
*
|
||||
* @param observableMark The mark to bind the marker to.
|
||||
* @param markerType the type of marker as a ModelType. Should be PLAIN_MARKER, START_MARKER or END_MARKER
|
||||
*/
|
||||
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
|
||||
markerObjects.put(observableMark, new Marker3D(markerType));
|
||||
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 gateType The type of model for the gate.
|
||||
* @return the new gate.
|
||||
*/
|
||||
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
|
||||
Point2D m1Location = findScaledXY(m1);
|
||||
Point2D m2Location = findScaledXY(m2);
|
||||
|
||||
Group barrier = ModelFactory.importModel(gateType).getAssets();
|
||||
barrier.getTransforms().addAll(
|
||||
new Rotate(
|
||||
Math.toDegrees(
|
||||
Math.atan2(m2Location.getY() - m1Location.getY(), m2Location.getX() - m1Location.getX())
|
||||
) + 90,
|
||||
new Point3D(0,0,1)
|
||||
),
|
||||
new Scale(1, m1Location.distance(m2Location) / 10, 1)
|
||||
);
|
||||
|
||||
Point2D midPoint = m2Location.midpoint(m1Location);
|
||||
barrier.setLayoutX(midPoint.getX());
|
||||
barrier.setLayoutY(midPoint.getY());
|
||||
return barrier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates all the data needed for to create mark arrows. Requires that a course has been
|
||||
* added to the gameview.
|
||||
*/
|
||||
private void createMarkArrows () {
|
||||
for (int i=1; i < course.size()-1; i++) { //General case.
|
||||
for (Mark mark : course.get(i).getMarks()) {
|
||||
markerObjects.get(mark).addArrows(
|
||||
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
|
||||
GeoUtility.getBearing(course.get(i-1).getMidPoint(), mark),
|
||||
GeoUtility.getBearing(mark, course.get(i+1).getMidPoint())
|
||||
);
|
||||
}
|
||||
}
|
||||
createStartLineArrows();
|
||||
createFinishLineArrows();
|
||||
}
|
||||
|
||||
private void createStartLineArrows () {
|
||||
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, course.get(1).getMidPoint())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void createFinishLineArrows () {
|
||||
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(course.get(course.size()-2).getMidPoint(), mark),
|
||||
GeoUtility.getBearing(mark, mark)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
switch (event.getCode()) {
|
||||
case NUMPAD8:
|
||||
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(1,0,0)));
|
||||
break;
|
||||
case NUMPAD2:
|
||||
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(1,0,0)));
|
||||
break;
|
||||
case NUMPAD4:
|
||||
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(0,1,0)));
|
||||
break;
|
||||
case NUMPAD6:
|
||||
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(0,1,0)));
|
||||
break;
|
||||
case Z:
|
||||
camera.getTransforms().addAll(new Translate(0, 0, 1.5));
|
||||
break;
|
||||
case X:
|
||||
camera.getTransforms().addAll(new Translate(0, 0, -1.5));
|
||||
break;
|
||||
case W:
|
||||
camera.getTransforms().addAll(new Translate(0, -1, 0));
|
||||
break;
|
||||
case S:
|
||||
camera.getTransforms().addAll(new Translate(0, 1, 0));
|
||||
break;
|
||||
case A:
|
||||
camera.getTransforms().addAll(new Translate(-1, 0, 0));
|
||||
break;
|
||||
case D:
|
||||
camera.getTransforms().addAll(new Translate(1, 0, 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void setBoats(List<ClientYacht> yachts) {
|
||||
BoatObject newBoat;
|
||||
final List<Group> wakes = new ArrayList<>();
|
||||
for (ClientYacht clientYacht : yachts) {
|
||||
Color colour = clientYacht.getColour();
|
||||
newBoat = new BoatObject(clientYacht.getBoatType());
|
||||
newBoat.setFill(colour);
|
||||
boatObjects.put(clientYacht, newBoat);
|
||||
wakesGroup.getChildren().add(newBoat.getWake());
|
||||
wakes.add(newBoat.getWake());
|
||||
boatObjectGroup.getChildren().add(newBoat);
|
||||
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
|
||||
BoatObject bo = boatObjects.get(boat);
|
||||
Point2D p2d = findScaledXY(lat, lon);
|
||||
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
|
||||
});
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
gameObjects.getChildren().addAll(wakes);
|
||||
gameObjects.getChildren().addAll(boatObjectGroup);
|
||||
});
|
||||
}
|
||||
|
||||
public Node getAssets () {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
List<Node> boundaryAssets = new ArrayList<>();
|
||||
|
||||
Point2D lastLocation = 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());
|
||||
pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
|
||||
pylon.setLayoutX(location.getX());
|
||||
pylon.setLayoutY(location.getY());
|
||||
|
||||
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
|
||||
barrier.getTransforms().addAll(
|
||||
new Rotate(
|
||||
Math.toDegrees(
|
||||
Math.atan2(location.getY() - lastLocation.getY(), location.getX() - lastLocation.getX())
|
||||
),
|
||||
new Point3D(0,0,1)
|
||||
),
|
||||
new Scale((lastLocation.distance(location) / 10)-0.2, 1, 1)
|
||||
);
|
||||
|
||||
Point2D midPoint = location.midpoint(lastLocation);
|
||||
barrier.setLayoutX(midPoint.getX());
|
||||
barrier.setLayoutY(midPoint.getY());
|
||||
|
||||
lastLocation = location;
|
||||
|
||||
boundaryAssets.add(barrier);
|
||||
boundaryAssets.add(pylon);
|
||||
}
|
||||
|
||||
Point2D firstLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
|
||||
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
|
||||
barrier.getTransforms().addAll(
|
||||
new Rotate(
|
||||
Math.toDegrees(
|
||||
Math.atan2(lastLocation.getY() - firstLocation.getY(), lastLocation.getX() - firstLocation.getX())
|
||||
),
|
||||
new Point3D(0,0,1)
|
||||
),
|
||||
new Scale((firstLocation.distance(lastLocation) / 10)-0.2, 1, 1)
|
||||
);
|
||||
|
||||
Point2D midPoint = lastLocation.midpoint(firstLocation);
|
||||
barrier.setLayoutX(midPoint.getX());
|
||||
barrier.setLayoutY(midPoint.getY());
|
||||
boundaryAssets.add(barrier);
|
||||
|
||||
Platform.runLater(() -> raceBorder.getChildren().setAll(boundaryAssets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all tokens in the course with those passed in
|
||||
*
|
||||
* @param newTokens the tokens to be put on the course.
|
||||
*/
|
||||
public void updateTokens(List<Token> newTokens) {
|
||||
mapTokens = new ArrayList<>();
|
||||
for (Token token : newTokens) {
|
||||
Point2D location = findScaledXY(token.getLat(), token.getLng());
|
||||
Node tokenObject = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
|
||||
tokenObject.setLayoutX(location.getX());
|
||||
tokenObject.setLayoutY(location.getY());
|
||||
mapTokens.add(tokenObject);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
tokens.getChildren().setAll(mapTokens);
|
||||
});
|
||||
}
|
||||
|
||||
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||
playerYacht.toggleSail();
|
||||
playerBoatAnimationTimer = new AnimationTimer() {
|
||||
|
||||
double count = 60;
|
||||
Point2D lastLocation = findScaledXY(playerYacht.getLocation());
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (--count == 0) {
|
||||
count = 60;
|
||||
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
|
||||
Point2D location = 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)
|
||||
);
|
||||
trail.getChildren().add(segment);
|
||||
if (trail.getChildren().size() > 50) {
|
||||
trail.getChildren().remove(0);
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
}
|
||||
};
|
||||
playerBoatAnimationTimer.start();
|
||||
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
|
||||
boatObjects.get(playerYacht).addSelectedBoatListener((boatObject, isSelected) -> {
|
||||
System.out.println("IS SELECTED " + isSelected);
|
||||
});
|
||||
}
|
||||
|
||||
public void setWindDir(double windDir) {
|
||||
this.windDir = windDir;
|
||||
}
|
||||
|
||||
private void updateMarkArrows (ClientYacht yacht, int legNumber) {
|
||||
CompoundMark compoundMark;
|
||||
if (legNumber - 1 >= 0) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
compoundMark = course.get(legNumber-1);
|
||||
for (Mark mark : compoundMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextExitArrow();
|
||||
}
|
||||
}
|
||||
CompoundMark nextMark = null;
|
||||
if (legNumber < course.size() - 1) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
nextMark = course.get(legNumber);
|
||||
for (Mark mark : nextMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextEnterArrow();
|
||||
}
|
||||
}
|
||||
if (legNumber - 2 >= 0) {
|
||||
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
|
||||
if (lastMark != nextMark) {
|
||||
for (Mark mark : lastMark.getMarks()) {
|
||||
markerObjects.get(mark).hideAllArrows();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import seng302.gameServer.ServerAdvertiser;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceEvent;
|
||||
import javax.jmdns.ServiceListener;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
|
||||
|
||||
/**
|
||||
* Listens for servers on the local network
|
||||
*/
|
||||
public class ServerListener{
|
||||
private static ServerListener instance;
|
||||
private ServerListenerDelegate delegate;
|
||||
private JmDNS jmdns = null;
|
||||
GameServeMonitor listener;
|
||||
|
||||
private class GameServeMonitor implements ServiceListener {
|
||||
private Set<ServerDescription> servers;
|
||||
|
||||
GameServeMonitor(){
|
||||
servers = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Service has been detected but not resolved
|
||||
* @param event The ServiceEvent
|
||||
*/
|
||||
@Override
|
||||
public void serviceAdded(ServiceEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Service has been removed / unregistered
|
||||
* @param event The ServiceEvent
|
||||
*/
|
||||
@Override
|
||||
public void serviceRemoved(ServiceEvent event) {
|
||||
String serverName = event.getInfo().getName();
|
||||
|
||||
ServerDescription toRemove = null;
|
||||
|
||||
for (ServerDescription server : servers){
|
||||
if (server.getName().equals(serverName)){
|
||||
toRemove = server;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null){
|
||||
servers.remove(toRemove);
|
||||
}
|
||||
|
||||
delegate.serverRemoved(new ArrayList<ServerDescription>(servers));
|
||||
|
||||
// Get all other servers with the same name to respond if they are up
|
||||
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverName);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A Service has been added and resolved
|
||||
* @param event The ServiceEvent
|
||||
*/
|
||||
@Override
|
||||
public void serviceResolved(ServiceEvent event) {
|
||||
String address = event.getInfo().getServer();
|
||||
Integer portNum = event.getInfo().getPort();
|
||||
|
||||
String serverName = event.getInfo().getName();
|
||||
String mapName = event.getInfo().getPropertyString("map");
|
||||
|
||||
Integer capacity = Integer.parseInt(event.getInfo().getPropertyString("capacity"));
|
||||
Integer numPlayers = Integer.parseInt(event.getInfo().getPropertyString("players"));
|
||||
|
||||
ServerDescription serverDescription = new ServerDescription(serverName, mapName, numPlayers, capacity, address, portNum);
|
||||
|
||||
servers.remove(serverDescription);
|
||||
servers.add(serverDescription);
|
||||
|
||||
delegate.serverDetected(serverDescription, new ArrayList<>(servers));
|
||||
}
|
||||
}
|
||||
|
||||
private ServerListener() throws IOException {
|
||||
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||
listener = new GameServeMonitor();
|
||||
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
|
||||
}
|
||||
|
||||
public static ServerListener getInstance() throws IOException {
|
||||
if (instance == null){
|
||||
instance = new ServerListener();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delegate to handle events
|
||||
* @param delegate .
|
||||
*/
|
||||
public void setDelegate(ServerListenerDelegate delegate){
|
||||
this.delegate = delegate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import seng302.gameServer.ServerDescription;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ServerListenerDelegate {
|
||||
void serverRemoved(List<ServerDescription> currentServers);
|
||||
void serverDetected(ServerDescription serverDescription, List<ServerDescription> servers);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.visualiser.ClientToServerThread;
|
||||
|
||||
public class CustomizationController {
|
||||
|
||||
@FXML
|
||||
private TextField nameField;
|
||||
|
||||
@FXML
|
||||
private ColorPicker boatColorPicker;
|
||||
|
||||
@FXML
|
||||
private Button customizeSubmit;
|
||||
|
||||
private LobbyController lc;
|
||||
private ClientToServerThread socketThread;
|
||||
private Stage windowStage;
|
||||
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
public void setServerThread(ClientToServerThread ctsThread) {
|
||||
this.socketThread = ctsThread;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void submitCustomization() {
|
||||
System.out.println("Attempting to send");
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes());
|
||||
// TODO: 16/08/17 ajm412: Turn colors into byte array.
|
||||
Color color = boatColorPicker.getValue();
|
||||
|
||||
short red = (short) (color.getRed() * 255);
|
||||
short green = (short) (color.getGreen() * 255);
|
||||
short blue = (short) (color.getBlue() * 255);
|
||||
|
||||
byte[] colorArray = new byte[3];
|
||||
|
||||
colorArray[0] = (byte) red;
|
||||
colorArray[1] = (byte) green;
|
||||
colorArray[2] = (byte) blue;
|
||||
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
|
||||
lc.setPlayersColor(color);
|
||||
windowStage.close();
|
||||
}
|
||||
|
||||
public void setLobbyController(LobbyController lc) {
|
||||
this.lc = lc;
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
this.windowStage = stage;
|
||||
}
|
||||
|
||||
public void setPlayerName(String name) {
|
||||
this.nameField.setText(name);
|
||||
}
|
||||
|
||||
public void setPlayerColor(Color playerColor) {
|
||||
this.boatColorPicker.setValue(playerColor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,10 +15,12 @@ import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.utilities.Sounds;
|
||||
|
||||
public class FinishScreenViewController implements Initializable {
|
||||
|
||||
@@ -40,8 +42,8 @@ public class FinishScreenViewController implements Initializable {
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
finishScreenGridPane.getStylesheets()
|
||||
.add(getClass().getResource("/css/master.css").toString());
|
||||
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
.add(getClass().getResource("/css/Master.css").toString());
|
||||
finishOrderTable.getStylesheets().add(getClass().getResource("/css/Master.css").toString());
|
||||
|
||||
// set up data for table
|
||||
finishOrderTable.setItems(data);
|
||||
@@ -85,6 +87,12 @@ public class FinishScreenViewController implements Initializable {
|
||||
}
|
||||
|
||||
public void switchToStartScreenView() {
|
||||
Sounds.playButtonClick();
|
||||
//TODO merge fix
|
||||
setContentPane("/views/StartScreenView.fxml");
|
||||
}
|
||||
|
||||
public void playButtonHoverSound(MouseEvent mouseEvent) {
|
||||
Sounds.playHoverSound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +1,248 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import com.sun.media.jfxmedia.logging.Logger;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import seng302.gameServer.GameStages;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.Colors;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.visualiser.ClientToServerThread;
|
||||
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.controllers.cells.PlayerCell;
|
||||
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
|
||||
|
||||
/**
|
||||
* A class describing the actions of the lobby screen
|
||||
* Created by wmu16 on 10/07/17.
|
||||
*/
|
||||
public class LobbyController {
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
public enum CloseStatus {
|
||||
LEAVE,
|
||||
READY
|
||||
}
|
||||
public class LobbyController implements Initializable {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LobbyCloseListener {
|
||||
void notify(CloseStatus exitCause);
|
||||
}
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private VBox playerListVBox;
|
||||
@FXML
|
||||
private ScrollPane playerListScrollPane;
|
||||
@FXML
|
||||
private JFXButton customizeButton, leaveLobbyButton, beginRaceButton;
|
||||
@FXML
|
||||
private StackPane serverListMainStackPane;
|
||||
@FXML
|
||||
private Label serverName;
|
||||
@FXML
|
||||
private Label mapName;
|
||||
@FXML
|
||||
private Pane serverMap;
|
||||
//---------FXML END---------//
|
||||
|
||||
@FXML
|
||||
private Text lobbyIpText;
|
||||
@FXML
|
||||
private Button readyButton;
|
||||
@FXML
|
||||
private Button customizeButton;
|
||||
@FXML
|
||||
private TextArea playerOneTxt;
|
||||
@FXML
|
||||
private TextArea playerTwoTxt;
|
||||
@FXML
|
||||
private TextArea playerThreeTxt;
|
||||
@FXML
|
||||
private TextArea playerFourTxt;
|
||||
@FXML
|
||||
private TextArea playerFiveTxt;
|
||||
@FXML
|
||||
private TextArea playerSixTxt;
|
||||
@FXML
|
||||
private TextArea playerSevenTxt;
|
||||
@FXML
|
||||
private TextArea playerEightTxt;
|
||||
@FXML
|
||||
private ImageView firstImageView;
|
||||
@FXML
|
||||
private ImageView secondImageView;
|
||||
@FXML
|
||||
private ImageView thirdImageView;
|
||||
@FXML
|
||||
private ImageView fourthImageView;
|
||||
@FXML
|
||||
private ImageView fifthImageView;
|
||||
@FXML
|
||||
private ImageView sixthImageView;
|
||||
@FXML
|
||||
private ImageView seventhImageView;
|
||||
@FXML
|
||||
private ImageView eighthImageView;
|
||||
@FXML
|
||||
private Text timeUntilStart;
|
||||
@FXML
|
||||
private Text courseNameText;
|
||||
|
||||
private List<ImageView> imageViews = new ArrayList<>();
|
||||
private List<TextArea> listViews = new ArrayList<>();
|
||||
private RaceState raceState;
|
||||
private JFXDialog customizationDialog;
|
||||
public Color playersColor;
|
||||
private Map<Integer, ClientYacht> playerBoats;
|
||||
private Double mapWidth, mapHeight;
|
||||
private GameView gameView;
|
||||
|
||||
private ClientToServerThread socketThread;
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
private Stage customizeStage;
|
||||
|
||||
private Color playersColor;
|
||||
|
||||
private int MAX_NUM_PLAYERS = 8;
|
||||
private Integer playerID;
|
||||
|
||||
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
|
||||
private ObservableList<String> players;
|
||||
|
||||
/**
|
||||
* Add all FXObjects to lists and initialize images.
|
||||
*/
|
||||
public void initialize() {
|
||||
Collections.addAll(listViews,
|
||||
playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
|
||||
playerSevenTxt, playerEightTxt
|
||||
);
|
||||
Collections.addAll(imageViews,
|
||||
firstImageView, secondImageView, thirdImageView, fourthImageView,
|
||||
fifthImageView, sixthImageView, seventhImageView, eighthImageView
|
||||
);
|
||||
initialiseImageView();
|
||||
|
||||
timeUntilStart.setText("Waiting For Host...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates player names.
|
||||
*/
|
||||
private void updatePlayers() {
|
||||
//Update players if one added.
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
listViews.get(i).setText(players.get(i));
|
||||
if (playerID == (i + 1)) {
|
||||
listViews.get(i).setText(listViews.get(i).getText() + " (YOU)");
|
||||
}
|
||||
imageViews.get(i).setVisible(true);
|
||||
}
|
||||
//Update empty text fields if player left.
|
||||
for (int i = MAX_NUM_PLAYERS-1; i >= players.size(); i--) {
|
||||
listViews.get(i).setText("");
|
||||
imageViews.get(i).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all images and hides them till players join.
|
||||
*/
|
||||
private void initialiseImageView() {
|
||||
for (ImageView viewer : imageViews) {
|
||||
viewer.setImage(
|
||||
new Image(
|
||||
RaceViewController.class.getResourceAsStream(
|
||||
"/pics/sail.png")
|
||||
)
|
||||
);
|
||||
viewer.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void customize() {
|
||||
Parent root;
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml"));
|
||||
root = fxmlLoader.load();
|
||||
root.getStylesheets().add("/css/master.css");
|
||||
customizeStage = new Stage();
|
||||
customizeStage.setTitle("Customize Boat");
|
||||
customizeStage.setScene(new Scene(root, 700, 450));
|
||||
CustomizationController cc = fxmlLoader.getController();
|
||||
cc.setServerThread(this.socketThread);
|
||||
cc.setPlayerName(this.players.get(playerID - 1));
|
||||
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
|
||||
|
||||
if (this.playersColor == null) {
|
||||
this.playersColor = Colors.getColor(playerID - 1);
|
||||
this.playersColor = Colors.getColor(ViewManager.getInstance().getGameClient().getServerThread().getClientId() - 1);
|
||||
}
|
||||
|
||||
cc.setPlayerColor(this.playersColor);
|
||||
cc.setStage(customizeStage); // pass the stage through so it can be closed later.
|
||||
cc.setLobbyController(this);
|
||||
customizeStage.show();
|
||||
leaveLobbyButton.setOnMouseReleased(event -> leaveLobby());
|
||||
beginRaceButton.setOnMouseReleased(event -> beginRace());
|
||||
leaveLobbyButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
leaveLobby();
|
||||
});
|
||||
|
||||
beginRaceButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
beginRace();
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
|
||||
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
|
||||
|
||||
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
|
||||
|
||||
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId();
|
||||
|
||||
playersColor = Colors.getColor(playerId - 1);
|
||||
customizationDialog = createCustomizeDialog();
|
||||
|
||||
customizeButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
customizationDialog.show();
|
||||
});
|
||||
});
|
||||
|
||||
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
customizeButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
beginRaceButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
|
||||
initMapPreview();
|
||||
}
|
||||
|
||||
private JFXDialog createCustomizeDialog() {
|
||||
FXMLLoader dialog = new FXMLLoader(
|
||||
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
|
||||
|
||||
JFXDialog customizationDialog = null;
|
||||
|
||||
try {
|
||||
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
|
||||
JFXDialog.DialogTransition.CENTER);
|
||||
} catch (IOException e) {
|
||||
Logger.logMsg(4, "Failed to load Customization View from resources.");
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
public void setSocketThread(ClientToServerThread thread) {
|
||||
this.socketThread = thread;
|
||||
BoatCustomizeController controller = dialog.getController();
|
||||
|
||||
controller.setParentController(this);
|
||||
controller.setPlayerColor(this.playersColor);
|
||||
controller.setPlayerName(this.playerBoats
|
||||
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
|
||||
.getBoatName());
|
||||
controller.setCurrentBoat(this.playerBoats.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
|
||||
.getBoatType());
|
||||
|
||||
return customizationDialog;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void leaveLobbyButtonPressed() {
|
||||
// TODO: 10/07/17 wmu16 - Finish function!
|
||||
GameState.setCurrentStage(GameStages.CANCELLED);
|
||||
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
|
||||
for (LobbyCloseListener readyListener : lobbyListeners)
|
||||
readyListener.notify(CloseStatus.LEAVE);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void readyButtonPressed() {
|
||||
/**
|
||||
* Initializes a top down preview of the race course map.
|
||||
*/
|
||||
private void initMapPreview() {
|
||||
gameView = new GameView();
|
||||
gameView.setHorizontalBuffer(330d);
|
||||
|
||||
mapWidth = 770d;
|
||||
mapHeight = 574d;
|
||||
|
||||
// Add game view
|
||||
serverMap.getChildren().clear();
|
||||
serverMap.getChildren().add(gameView);
|
||||
|
||||
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
mapWidth = newValue.doubleValue();
|
||||
refreshMapView();
|
||||
});
|
||||
|
||||
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
mapHeight = newValue.doubleValue();
|
||||
refreshMapView();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void beginRace() {
|
||||
beginRaceButton.setDisable(true);
|
||||
customizeButton.setDisable(true);
|
||||
GameState.setCurrentStage(GameStages.PRE_RACE);
|
||||
// Do countdown logic here
|
||||
|
||||
for (LobbyCloseListener readyListener : lobbyListeners)
|
||||
readyListener.notify(CloseStatus.READY);
|
||||
customizeButton.setDisable(true);
|
||||
GameState.resetStartTime();
|
||||
Platform.runLater(()-> ViewManager.getInstance().getGameClient().startGame());
|
||||
}
|
||||
|
||||
public void setTitle (String title) {
|
||||
lobbyIpText.setText(title);
|
||||
/**
|
||||
* Refreshes the list of players and their boats, as a series of VBox PlayerCell objects.
|
||||
*/
|
||||
private void refreshPlayerList() {
|
||||
playerListVBox.getChildren().clear();
|
||||
if (this.playerBoats == null || this.playerBoats.size() == 0) {
|
||||
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
|
||||
}
|
||||
// TODO: 12/09/2017 ajm412: Make it so that it only removes players who's details have changed.
|
||||
for (Integer playerId : playerBoats.keySet()) {
|
||||
VBox pane = null;
|
||||
|
||||
ClientYacht yacht = playerBoats.get(playerId);
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/views/cells/PlayerCell.fxml"));
|
||||
|
||||
loader.setController(new PlayerCell(playerId, yacht));
|
||||
|
||||
try {
|
||||
pane = loader.load();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
public void setCourseName(String courseName){
|
||||
courseNameText.setText(courseName);
|
||||
playerListVBox.getChildren().add(pane);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void addCloseListener(LobbyCloseListener listener) {
|
||||
lobbyListeners.add(listener);
|
||||
}
|
||||
private void leaveLobby() {
|
||||
|
||||
public void setPlayerListSource (ObservableList<String> players) {
|
||||
this.players = players;
|
||||
players.addListener((ListChangeListener<? super String>) (lcl) ->
|
||||
Platform.runLater(this::updatePlayers)
|
||||
);
|
||||
Platform.runLater(this::updatePlayers);
|
||||
}
|
||||
|
||||
public void setPlayerID(Integer id) {
|
||||
playerID = id;
|
||||
}
|
||||
|
||||
public void updateRaceState(RaceState raceState){
|
||||
this.raceState = raceState;
|
||||
/*if (this.customizeStage != null) {
|
||||
this.customizeStage.close();
|
||||
}*/ // TODO: 17/08/17 ajm412: close the customization window if the host starts the game while customizing
|
||||
if (!customizeButton.isDisabled()) {
|
||||
customizeButton.setDisable(true);
|
||||
}
|
||||
timeUntilStart.setText("Starting in: " + raceState.getRaceTimeStr());
|
||||
ViewManager.getInstance().getGameClient().stopGame();
|
||||
ViewManager.getInstance().goToStartView();
|
||||
}
|
||||
|
||||
public void disableReadyButton() {
|
||||
readyButton.setDisable(true);
|
||||
readyButton.setVisible(false);
|
||||
this.beginRaceButton.setDisable(true);
|
||||
this.beginRaceButton.setText("Waiting for host...");
|
||||
}
|
||||
|
||||
public void setPlayersColor(Color playerColor) {
|
||||
this.playersColor = playerColor;
|
||||
/**
|
||||
*
|
||||
* @param raceState
|
||||
*/
|
||||
public void updateRaceState(RaceState raceState){
|
||||
this.raceState = raceState;
|
||||
this.beginRaceButton.setText("Starting in: " + raceState.getRaceTimeStr());
|
||||
}
|
||||
|
||||
public void setBoats(Map<Integer, ClientYacht> boats) {
|
||||
this.playerBoats = boats;
|
||||
}
|
||||
|
||||
public void closeCustomizationDialog() {
|
||||
customizationDialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -9,6 +11,9 @@ import java.util.TimerTask;
|
||||
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;
|
||||
@@ -16,6 +21,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.SubScene;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
@@ -24,35 +30,52 @@ import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.StringConverter;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.visualiser.GameView;
|
||||
import seng302.visualiser.controllers.annotations.Annotation;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.GameView3D;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
|
||||
import seng302.visualiser.fxObjects.BoatObject;
|
||||
import seng302.visualiser.controllers.dialogs.FinishDialogController;
|
||||
import seng302.visualiser.fxObjects.ChatHistory;
|
||||
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
/**
|
||||
* Controller class that manages the display of a race
|
||||
*/
|
||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||
|
||||
private final int CHAT_LIMIT = 128;
|
||||
|
||||
@FXML
|
||||
private Pane basePane;
|
||||
@FXML
|
||||
private JFXButton chatSend;
|
||||
@FXML
|
||||
private Pane chatHistoryHolder;
|
||||
@FXML
|
||||
private TextField chatInput;
|
||||
@FXML
|
||||
private LineChart<String, Double> raceSparkLine;
|
||||
@FXML
|
||||
@@ -62,11 +85,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
@FXML
|
||||
private CheckBox toggleFps;
|
||||
@FXML
|
||||
private Text timerLabel;
|
||||
private Label timerLabel;
|
||||
@FXML
|
||||
private AnchorPane contentAnchorPane;
|
||||
private StackPane contentAnchorPane;
|
||||
|
||||
private GridPane contentGridPane;
|
||||
@FXML
|
||||
private Text windArrowText, windDirectionText;
|
||||
private AnchorPane rvAnchorPane;
|
||||
@FXML
|
||||
private AnchorPane windArrowHolder;
|
||||
@FXML
|
||||
private Slider annotationSlider;
|
||||
@FXML
|
||||
@@ -76,35 +103,123 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
@FXML
|
||||
private Text fpsDisplay;
|
||||
@FXML
|
||||
private Text windSpeedText;
|
||||
private ImageView windImageView;
|
||||
@FXML
|
||||
private Label windDirectionLabel;
|
||||
@FXML
|
||||
private Label windSpeedLabel;
|
||||
@FXML
|
||||
private Label positionLabel, boatSpeedLabel, boatHeadingLabel;
|
||||
|
||||
//Race Data
|
||||
private Map<Integer, ClientYacht> participants;
|
||||
private Map<Integer, CompoundMark> markers;
|
||||
private RaceXMLData courseData;
|
||||
private GameView gameView;
|
||||
private GameView3D gameView;
|
||||
private RaceState raceState;
|
||||
|
||||
private ChatHistory chatHistory;
|
||||
|
||||
private Timeline timerTimeline;
|
||||
private Timer timer = new Timer();
|
||||
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
|
||||
private ImportantAnnotationsState importantAnnotations;
|
||||
private Polyline windArrow = new WindArrow(Color.LIGHTGRAY);
|
||||
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
|
||||
private ClientYacht player;
|
||||
private JFXDialog finishScreenDialog;
|
||||
private FinishDialogController finishDialogController;
|
||||
|
||||
public void initialize() {
|
||||
Sounds.stopMusic();
|
||||
Sounds.playRaceMusic();
|
||||
|
||||
finishScreenDialog = createFinishDialog();
|
||||
|
||||
// Load a default important annotation state
|
||||
importantAnnotations = new ImportantAnnotationsState();
|
||||
//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);
|
||||
//raceSparkLine.visibleProperty().setValue(false);
|
||||
//raceSparkLine.getYAxis().setAutoRanging(false);
|
||||
//sparklineYAxis.setTickMarkVisible(false);
|
||||
|
||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
//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());
|
||||
//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));
|
||||
}
|
||||
});
|
||||
chatHistory = new ChatHistory();
|
||||
chatHistoryHolder.getChildren().addAll(chatHistory);
|
||||
chatHistory.prefWidthProperty().bind(
|
||||
chatHistoryHolder.widthProperty()
|
||||
);
|
||||
chatHistory.prefHeightProperty().bind(
|
||||
chatHistoryHolder.heightProperty()
|
||||
);
|
||||
// chatHistory.setFitToWidth(true);
|
||||
// chatHistory.setFitToHeight(true);
|
||||
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
|
||||
// chatHistory.setScrollTop(Double.MAX_VALUE);
|
||||
// });
|
||||
rvAnchorPane.setOnMouseClicked((event) ->
|
||||
rvAnchorPane.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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
|
||||
raceState.setRaceStarted(false);
|
||||
finishDialogController.setFinishedBoats(finishedBoats);
|
||||
finishScreenDialog.show();
|
||||
}
|
||||
|
||||
private JFXDialog createFinishDialog() {
|
||||
FXMLLoader dialog = new FXMLLoader(
|
||||
getClass().getResource("/views/dialogs/RaceFinishDialog.fxml"));
|
||||
|
||||
JFXDialog finishScreenDialog = null;
|
||||
|
||||
try {
|
||||
finishScreenDialog = new JFXDialog(contentAnchorPane, dialog.load(),
|
||||
JFXDialog.DialogTransition.CENTER);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
finishDialogController = dialog.getController();
|
||||
|
||||
return finishScreenDialog;
|
||||
}
|
||||
|
||||
public void loadRace (
|
||||
@@ -115,12 +230,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
this.courseData = raceData;
|
||||
this.markers = raceData.getCompoundMarks();
|
||||
this.raceState = raceState;
|
||||
|
||||
initializeUpdateTimer();
|
||||
initialiseFPSCheckBox();
|
||||
initialiseAnnotationSlider();
|
||||
initialiseBoatSelectionComboBox();
|
||||
initialiseSparkLine();
|
||||
this.player = player;
|
||||
|
||||
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
|
||||
while (c.next()) {
|
||||
@@ -132,29 +242,41 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
});
|
||||
|
||||
updateOrder(raceState.getPlayerPositions());
|
||||
gameView = new GameView();
|
||||
gameView.setFrameRateFXText(fpsDisplay);
|
||||
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
|
||||
gameView = new GameView3D();
|
||||
// gameView.setFrameRateFXText(fpsDisplay);
|
||||
Platform.runLater(() -> {
|
||||
contentAnchorPane.getChildren().add(0, gameView.getAssets());
|
||||
((SubScene) gameView.getAssets()).widthProperty()
|
||||
.bind(ViewManager.getInstance().getStage().widthProperty());
|
||||
((SubScene) gameView.getAssets()).heightProperty()
|
||||
.bind(ViewManager.getInstance().getStage().heightProperty());
|
||||
});
|
||||
gameView.setBoats(new ArrayList<>(participants.values()));
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
gameView.updateTokens(raceData.getTokens());
|
||||
gameView.updateCourse(
|
||||
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
|
||||
);
|
||||
gameView.enableZoom();
|
||||
// gameView.enableZoom();
|
||||
gameView.setBoatAsPlayer(player);
|
||||
gameView.startRace();
|
||||
// gameView.startRace();
|
||||
|
||||
raceState.addCollisionListener(gameView::drawCollision);
|
||||
// raceState.addCollisionListener(gameView::drawCollision);
|
||||
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
|
||||
gameView.setWindDir(newDirection.doubleValue());
|
||||
updateWindDirection(newDirection.doubleValue());
|
||||
});
|
||||
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
|
||||
updateWindSpeed(newSpeed.doubleValue());
|
||||
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
|
||||
});
|
||||
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) ->
|
||||
Platform.runLater(() -> updateWindSpeed(newSpeed.doubleValue()))
|
||||
);
|
||||
Platform.runLater(() -> {
|
||||
updateWindDirection(raceState.windDirectionProperty().doubleValue());
|
||||
updateWindSpeed(raceState.getWindSpeed());
|
||||
});
|
||||
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||
Platform.runLater(() -> {
|
||||
initializeUpdateTimer();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,46 +318,46 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
|
||||
private void initialiseFPSCheckBox() {
|
||||
toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
|
||||
gameView.setFPSVisibility(toggleFps.isSelected())
|
||||
);
|
||||
// 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())
|
||||
);
|
||||
// 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())
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
@@ -243,52 +365,52 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* 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()));
|
||||
});
|
||||
});
|
||||
// // 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);
|
||||
// sparklineYAxis.setUpperBound(participants.size() + 1);
|
||||
// raceSparkLine.setCreateSymbols(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,13 +427,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
)
|
||||
);
|
||||
}
|
||||
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
|
||||
// positionData.getData().add(
|
||||
// new XYChart.Data<>(
|
||||
// Integer.toString(legNumber),
|
||||
// 1.0 + participants.size() - yacht.getPlacing()
|
||||
// )
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
@@ -342,7 +457,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateRaceTime();
|
||||
Platform.runLater(() -> updatePosition());
|
||||
Platform.runLater(() -> updateBoatSpeed());
|
||||
Platform.runLater(() -> updateBoatHeading());
|
||||
Platform.runLater(() -> updateRaceTime());
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
@@ -381,8 +499,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* @param direction the from north angle of the wind.
|
||||
*/
|
||||
private void updateWindDirection(double direction) {
|
||||
windDirectionText.setText(String.format("%.1f°", direction));
|
||||
windArrowText.setRotate(direction);
|
||||
windDirectionLabel.setText(String.format("%.1f°", direction));
|
||||
windImageView.setRotate(direction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,7 +508,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* @param windSpeed Windspeed in knots.
|
||||
*/
|
||||
private void updateWindSpeed(double windSpeed) {
|
||||
windSpeedText.setText("Speed: " + String.format("%.1f", windSpeed) + " Knots");
|
||||
windSpeedLabel.setText(String.format("%.1f", windSpeed) + " Knots");
|
||||
}
|
||||
|
||||
|
||||
@@ -398,12 +516,57 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* Updates the clock for the race
|
||||
*/
|
||||
private void updateRaceTime() {
|
||||
// if (!raceState.isRaceStarted()) {
|
||||
// timerLabel.setFill(Color.RED);
|
||||
// timerLabel.setText("Race Finished!");
|
||||
// } else {
|
||||
if (raceState.getTimeTillStart() <= 0L && !raceState.isRaceStarted()) {
|
||||
timerLabel.setText("Race Finished!");
|
||||
} else {
|
||||
timerLabel.setText(raceState.getRaceTimeStr());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates player position with ordinal number up to 23rd position.
|
||||
*/
|
||||
private void updatePosition() {
|
||||
if (player.getPosition() == null) {
|
||||
positionLabel.setText("Position:\n-");
|
||||
} else {
|
||||
switch (player.getPosition()) {
|
||||
case 1:
|
||||
positionLabel.setText("Position:\n1st");
|
||||
break;
|
||||
case 2:
|
||||
positionLabel.setText("Position:\n2nd");
|
||||
break;
|
||||
case 3:
|
||||
positionLabel.setText("Position:\n3rd");
|
||||
break;
|
||||
case 21:
|
||||
positionLabel.setText("Position:\n21st");
|
||||
break;
|
||||
case 22:
|
||||
positionLabel.setText("Position:\n22nd");
|
||||
break;
|
||||
case 23:
|
||||
positionLabel.setText("Position:\n23rd");
|
||||
break;
|
||||
default:
|
||||
positionLabel.setText("Position:\n" + player.getPosition() + "th");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boat speed value displayed on race view.
|
||||
*/
|
||||
private void updateBoatSpeed() {
|
||||
boatSpeedLabel.setText("Boat Speed:\n" + String.valueOf(player.getCurrentVelocity()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boat heading value displayed on race view.
|
||||
*/
|
||||
private void updateBoatHeading() {
|
||||
boatHeadingLabel.setText(String.format("Boat Heading:\n%.1f°", player.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,28 +574,28 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* section
|
||||
*/
|
||||
private void updateOrder(ObservableList<ClientYacht> yachts) {
|
||||
List<Text> vboxEntries = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < yachts.size(); i++) {
|
||||
// System.out.println("yacht == null " + String.valueOf(yacht == null));
|
||||
if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
|
||||
.getCode()) { // 3 is finish status
|
||||
Text textToAdd = new Text(i + 1 + ". " +
|
||||
yachts.get(i).getShortName() + " (Finished)");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
vboxEntries.add(textToAdd);
|
||||
|
||||
} else {
|
||||
Text textToAdd = new Text(i + 1 + ". " +
|
||||
yachts.get(i).getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
vboxEntries.add(textToAdd);
|
||||
}
|
||||
}
|
||||
Platform.runLater(() ->
|
||||
positionVbox.getChildren().setAll(vboxEntries)
|
||||
);
|
||||
// List<Text> vboxEntries = new ArrayList<>();
|
||||
//
|
||||
// for (int i = 0; i < yachts.size(); i++) {
|
||||
//// System.out.println("yacht == null " + String.valueOf(yacht == null));
|
||||
// if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
|
||||
// .getCode()) { // 3 is finish status
|
||||
// Text textToAdd = new Text(i + 1 + ". " +
|
||||
// yachts.get(i).getShortName() + " (Finished)");
|
||||
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
// vboxEntries.add(textToAdd);
|
||||
//
|
||||
// } else {
|
||||
// Text textToAdd = new Text(i + 1 + ". " +
|
||||
// yachts.get(i).getShortName() + " ");
|
||||
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
// textToAdd.setStyle("");
|
||||
// vboxEntries.add(textToAdd);
|
||||
// }
|
||||
// }
|
||||
// Platform.runLater(() ->
|
||||
// positionVbox.getChildren().setAll(vboxEntries)
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
@@ -533,15 +696,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
* for the combobox to take action upon selection
|
||||
*/
|
||||
private void initialiseBoatSelectionComboBox() {
|
||||
yachtSelectionComboBox.setItems(
|
||||
FXCollections.observableArrayList(participants.values())
|
||||
);
|
||||
//Null check is if the listener is fired but nothing selected
|
||||
yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
if (selectedBoat != null) {
|
||||
gameView.selectBoat(selectedBoat);
|
||||
}
|
||||
});
|
||||
// yachtSelectionComboBox.setItems(
|
||||
// FXCollections.observableArrayList(participants.values())
|
||||
// );
|
||||
// //Null check is if the listener is fired but nothing selected
|
||||
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
// if (selectedBoat != null) {
|
||||
// gameView.selectBoat(selectedBoat);
|
||||
// }
|
||||
// });
|
||||
|
||||
//TODO uncomment out
|
||||
// selectionComboBoxList.setAll(participants.values());
|
||||
// yachtSelectionComboBox.setItems(selectionComboBoxList);
|
||||
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
// if (selectedBoat != null) {
|
||||
// gameView.selectBoat(selectedBoat);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,9 +723,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||
|
||||
try {
|
||||
contentAnchorPane.getChildren().removeAll();
|
||||
contentAnchorPane.getChildren().clear();
|
||||
contentAnchorPane.getChildren().addAll((Pane) loader.load());
|
||||
contentGridPane.getChildren().removeAll();
|
||||
contentGridPane.getChildren().clear();
|
||||
contentGridPane.getChildren().addAll((Pane) loader.load());
|
||||
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.err.println(e.getCause().toString());
|
||||
@@ -571,31 +743,31 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
|
||||
private void setAnnotations(Integer annotationLevel) {
|
||||
switch (annotationLevel) {
|
||||
// No Annotations
|
||||
case 0:
|
||||
gameView.setAnnotationVisibilities(
|
||||
false, false, false, false, false, false
|
||||
);
|
||||
break;
|
||||
// Important Annotations
|
||||
case 1:
|
||||
gameView.setAnnotationVisibilities(
|
||||
importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||
importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||
importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||
importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||
importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||
);
|
||||
break;
|
||||
// All Annotations
|
||||
case 2:
|
||||
gameView.setAnnotationVisibilities(
|
||||
true, true, true, true, true, true
|
||||
);
|
||||
break;
|
||||
}
|
||||
// switch (annotationLevel) {
|
||||
// // No Annotations
|
||||
// case 0:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// false, false, false, false, false, false
|
||||
// );
|
||||
// break;
|
||||
// // Important Annotations
|
||||
// case 1:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||
// importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||
// importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||
// importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||
// importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||
// importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||
// );
|
||||
// break;
|
||||
// // All Annotations
|
||||
// case 2:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// true, true, true, true, true, true
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -618,8 +790,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
// }
|
||||
}
|
||||
|
||||
public void updateRaceData (RaceXMLData raceData) {
|
||||
this.courseData = raceData;
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
public void updateTokens(RaceXMLData raceData) {
|
||||
gameView.updateTokens(raceData.getTokens());
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty getSendPressedProperty() {
|
||||
return chatSend.pressedProperty();
|
||||
}
|
||||
|
||||
public boolean isChatInputFocused() {
|
||||
return chatInput.focusedProperty().getValue();
|
||||
}
|
||||
|
||||
public String readChatInput() {
|
||||
String chat = chatInput.getText();
|
||||
chatInput.clear();
|
||||
rvAnchorPane.requestFocus();
|
||||
return chat;
|
||||
}
|
||||
|
||||
public void updateChatHistory(Paint playerColour, String newMessage) {
|
||||
Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.ServerListener;
|
||||
import seng302.visualiser.ServerListenerDelegate;
|
||||
import seng302.visualiser.controllers.cells.ServerCell;
|
||||
import seng302.visualiser.validators.HostNameFieldValidator;
|
||||
import seng302.visualiser.validators.NumberRangeValidator;
|
||||
import seng302.visualiser.validators.ValidationTools;
|
||||
|
||||
public class ServerListController implements Initializable, ServerListenerDelegate {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
// Layout Related
|
||||
@FXML
|
||||
private VBox serverListVBox;
|
||||
@FXML
|
||||
private ScrollPane serverListScrollPane;
|
||||
@FXML
|
||||
private StackPane serverListMainStackPane;
|
||||
// Host Button
|
||||
@FXML
|
||||
private JFXButton serverListHostButton;
|
||||
//Direct Connect
|
||||
@FXML
|
||||
private JFXButton connectButton;
|
||||
@FXML
|
||||
private JFXTextField serverHostName;
|
||||
@FXML
|
||||
private JFXTextField serverPortNumber;
|
||||
//---------FXML END---------//
|
||||
|
||||
private Label noServersFound;
|
||||
private Logger logger = LoggerFactory.getLogger(ServerListController.class);
|
||||
|
||||
// TODO: 12/09/17 ajm412: break this method down, its way too long.
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
serverListVBox.minWidthProperty().bind(serverListScrollPane.widthProperty());
|
||||
|
||||
// Set Event Bindings
|
||||
connectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||
connectButton.setOnMouseReleased(event -> {
|
||||
attemptToDirectConnect();
|
||||
Sounds.playButtonClick();
|
||||
});
|
||||
for (JFXTextField textField : Arrays.asList(serverHostName, serverPortNumber)) {
|
||||
// Event for pressing enter to submit direct connection
|
||||
textField.setOnKeyPressed(event -> {
|
||||
if (event.getCode().equals(KeyCode.ENTER)) {
|
||||
attemptToDirectConnect();
|
||||
}
|
||||
});
|
||||
|
||||
// Validators as empty fields are invalid.
|
||||
RequiredFieldValidator validator = new RequiredFieldValidator();
|
||||
validator.setMessage("Field is Required");
|
||||
textField.getValidators().add(validator);
|
||||
}
|
||||
|
||||
// Validating the hostname
|
||||
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
|
||||
hostNameValidator.setMessage("Host name incorrect");
|
||||
serverHostName.getValidators().add(hostNameValidator);
|
||||
|
||||
// Validating the port number
|
||||
NumberRangeValidator portNumberValidator = new NumberRangeValidator(1025, 65536);
|
||||
portNumberValidator.setMessage("Port number incorrect");
|
||||
serverPortNumber.getValidators().add(portNumberValidator);
|
||||
|
||||
// Start listening for servers on network
|
||||
try {
|
||||
ServerListener.getInstance().setDelegate(this);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not start Server Listener Delegate");
|
||||
}
|
||||
|
||||
// Create Label for no servers found.
|
||||
noServersFound = new Label();
|
||||
noServersFound.minWidthProperty().bind(serverListVBox.widthProperty());
|
||||
noServersFound.setAlignment(Pos.CENTER);
|
||||
noServersFound.setText("No Servers Found");
|
||||
noServersFound.setStyle(
|
||||
"-fx-font-size: 30px;"
|
||||
+ "-fx-padding:50px;"
|
||||
+ "-fx-text-fill: -fx-pp-dark-text-color;"
|
||||
);
|
||||
serverListVBox.getChildren().add(noServersFound);
|
||||
|
||||
// Set up dialog for server creation
|
||||
Platform.runLater(() -> {
|
||||
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
|
||||
"/views/dialogs/ServerCreationDialog.fxml"));
|
||||
try {
|
||||
JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
|
||||
DialogTransition.CENTER);
|
||||
serverListHostButton.setOnAction(action -> {
|
||||
dialog.show();
|
||||
Sounds.playButtonClick();
|
||||
});
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not create Server Creation Dialog.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the connection and attempts to connect to a given hostname and port number.
|
||||
*/
|
||||
private void attemptToDirectConnect() {
|
||||
if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
|
||||
DirectConnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the hostName and portNumber are valid values to connect to.
|
||||
* @param hostName host name to check.
|
||||
* @param portNumber port number to check
|
||||
* @return boolean value if host and port number are valid values
|
||||
*/
|
||||
private Boolean validateDirectConnection(String hostName, String portNumber) {
|
||||
Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
|
||||
Boolean portNumberValid = ValidationTools.validateTextField(serverPortNumber);
|
||||
|
||||
return hostNameValid && portNumberValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the user to a lobby via the Direct Connect form.
|
||||
*/
|
||||
private void DirectConnect() {
|
||||
Sounds.playButtonClick();
|
||||
ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the list of available servers.
|
||||
* @param servers A list of ServerDescription objects showing available servers.
|
||||
*/
|
||||
private void refreshServers(List<ServerDescription> servers) {
|
||||
serverListVBox.getChildren().clear();
|
||||
|
||||
if (servers.size() == 0) { // "No Servers Found"
|
||||
serverListVBox.getChildren().add(noServersFound);
|
||||
} else { // Populate the server list with a series of server cell objects.
|
||||
for (ServerDescription server : servers) {
|
||||
VBox pane = null;
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/views/cells/ServerCell.fxml"));
|
||||
|
||||
loader.setController(new ServerCell(server));
|
||||
|
||||
try {
|
||||
pane = loader.load();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
serverListVBox.getChildren().add(pane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverRemoved(List<ServerDescription> servers) {
|
||||
Platform.runLater(() -> refreshServers(servers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverDetected(ServerDescription serverDescription, List<ServerDescription> servers) {
|
||||
Platform.runLater(() -> refreshServers(servers));
|
||||
}
|
||||
}
|
||||
@@ -1,168 +1,70 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import seng302.gameServer.GameState;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.GameClient;
|
||||
|
||||
/**
|
||||
* A Class describing the actions of the start screen controller
|
||||
* Created by wmu16 on 10/07/17.
|
||||
*/
|
||||
public class StartScreenController implements Initializable{
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private TextField ipTextField;
|
||||
private Label headText;
|
||||
@FXML
|
||||
private TextField portTextField;
|
||||
@FXML
|
||||
private GridPane startScreen2;
|
||||
@FXML
|
||||
private AnchorPane holder;
|
||||
private JFXButton startBtn;
|
||||
//---------FXML END---------//
|
||||
|
||||
GameClient gameClient;
|
||||
private Node serverList;
|
||||
private Logger logger = LoggerFactory.getLogger(StartScreenController.class);
|
||||
private List<ServerDescription> servers;
|
||||
private GameClient gameClient;
|
||||
|
||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||
// gameClient = new GameClient(holder);
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// * Loads the fxml content into the parent pane
|
||||
// * @param jfxUrl
|
||||
// * @return the controller of the fxml
|
||||
// */
|
||||
// private Object setContentPane(String jfxUrl) {
|
||||
// try {
|
||||
// AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
|
||||
// contentPane.getChildren().removeAll();
|
||||
// contentPane.getChildren().clear();
|
||||
// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
|
||||
// contentPane.getChildren().addAll((Pane) fxmlLoader.load());
|
||||
//
|
||||
// return fxmlLoader.getController();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
startBtn.setOnMousePressed(event -> {
|
||||
startBtn.setText("LOADING...");
|
||||
Sounds.playButtonClick();
|
||||
});
|
||||
|
||||
startBtn.setOnMouseReleased(event -> goToServerBrowser());
|
||||
startBtn.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||
|
||||
preloadServerListView();
|
||||
|
||||
/**
|
||||
* ATTEMPTS TO:
|
||||
* Sets up a new game state with your IP address as designated as the host.
|
||||
* Starts a thread to listen for incoming connections.
|
||||
* Starts a client to server thread and connects to own ip.
|
||||
* Switches to the lobby screen
|
||||
*/
|
||||
@FXML
|
||||
public void hostButtonPressed() {
|
||||
// new GameState(getLocalHostIp());
|
||||
gameClient = new GameClient(holder);
|
||||
gameClient.runAsHost(getLocalHostIp(), 4942);
|
||||
// try {
|
||||
//// String ipAddress = InetAddress.getLocalHost().getHostAddress();
|
||||
//// new GameState(ipAddress);
|
||||
//// new MainServerThread();
|
||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
|
||||
//// controller.setClientToServerThread(clientToServerThread);
|
||||
// // get the lobby controller so that we can pass the game server thread to it
|
||||
// new GameState(getLocalHostIp());
|
||||
// MainServerThread mainServerThread = new MainServerThread();
|
||||
//// ClientState.setHost(true);
|
||||
// // host will connect and handshake to itself after setting up the server
|
||||
// // TODO: 24/07/17 wmu16 - Make port number some static global type constant?
|
||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
|
||||
//// ClientState.setConnectedToHost(true);
|
||||
//// controller.setClientToServerThread(clientToServerThread);
|
||||
// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
|
||||
// lobbyController.setMainServerThread(mainServerThread);
|
||||
// } catch (Exception e) {
|
||||
// Alert alert = new Alert(AlertType.ERROR);
|
||||
// alert.setHeaderText("Cannot host");
|
||||
// alert.setContentText("Oops, failed to host, try to restart.");
|
||||
// alert.showAndWait();
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTEMPTS TO:
|
||||
* Connect to an ip address and port using the ip and port specified on start screen.
|
||||
* Starts a Client To Server Thread to maintain connection to host.
|
||||
* Switch view to lobby view.
|
||||
* Preloads the server list view to reduce load time between start screen and server list screen.
|
||||
*/
|
||||
@FXML
|
||||
public void connectButtonPressed() {
|
||||
// TODO: 10/07/17 wmu16 - Finish function
|
||||
gameClient = new GameClient(holder);
|
||||
gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942);
|
||||
|
||||
// try {
|
||||
// String ipAddress = ipTextField.getText().trim().toLowerCase();
|
||||
// Integer port = Integer.valueOf(portTextField.getText().trim());
|
||||
//
|
||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
|
||||
//// ClientState.setHost(false);
|
||||
//// ClientState.setConnectedToHost(true);
|
||||
//
|
||||
//// controller.setClientToServerThread(clientToServerThread);
|
||||
//// setContentPane("/views/LobbyView.fxml");
|
||||
// } catch (Exception e) {
|
||||
// Alert alert = new Alert(AlertType.ERROR);
|
||||
// alert.setHeaderText("Cannot reach the host");
|
||||
// alert.setContentText("Please check your host IP address.");
|
||||
// alert.showAndWait();
|
||||
// }
|
||||
}
|
||||
|
||||
// public void setController(Controller controller) {
|
||||
// this.controller = controller;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Gets the local host ip address and sets this ip to ClientState.
|
||||
* Only runs by the host.
|
||||
*
|
||||
* @return the localhost ip address
|
||||
*/
|
||||
private String getLocalHostIp() {
|
||||
String ipAddress = null;
|
||||
private void preloadServerListView(){
|
||||
try {
|
||||
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||
while (e.hasMoreElements()) {
|
||||
NetworkInterface ni = e.nextElement();
|
||||
if (ni.isLoopback())
|
||||
continue;
|
||||
if(ni.isPointToPoint())
|
||||
continue;
|
||||
if(ni.isVirtual())
|
||||
continue;
|
||||
serverList = FXMLLoader
|
||||
.load(StartScreenController.class.getResource("/views/ServerListView.fxml"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Could not preload server list view");
|
||||
}
|
||||
}
|
||||
|
||||
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||
while(addresses.hasMoreElements()) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
if(address instanceof Inet4Address) { // skip all ipv6
|
||||
ipAddress = address.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Changes the view to the Server Browser.
|
||||
*/
|
||||
private void goToServerBrowser() {
|
||||
try {
|
||||
ViewManager.getInstance().setScene(serverList);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (ipAddress == null) {
|
||||
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||
}
|
||||
// ClientState.setHostIp(ipAddress);
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDecorator;
|
||||
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.image.Image;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.ServerAdvertiser;
|
||||
import seng302.utilities.BonjourInstallChecker;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.GameClient;
|
||||
|
||||
public class ViewManager {
|
||||
|
||||
private static ViewManager instance;
|
||||
private GameClient gameClient;
|
||||
private JFXDecorator decorator;
|
||||
private HashMap<String, String> properties; //TODO is this the best way to do this??
|
||||
private ObservableList<String> playerList;
|
||||
private Logger logger = LoggerFactory.getLogger(ViewManager.class);
|
||||
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
private Stage stage;
|
||||
|
||||
private ViewManager() {
|
||||
properties = new HashMap<>();
|
||||
}
|
||||
|
||||
private FXMLLoader loadFxml(String fxmlLocation) {
|
||||
return new FXMLLoader(
|
||||
getClass().getResource(fxmlLocation)
|
||||
);
|
||||
}
|
||||
|
||||
public static ViewManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ViewManager();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the start view in the given stage.
|
||||
*/
|
||||
public void initialStartView(Stage stage) throws Exception {
|
||||
this.stage = stage;
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
||||
stage.setTitle("Party Parrots At Sea");
|
||||
|
||||
JFXDecorator decorator = new JFXDecorator(stage, root, false, true, true);
|
||||
decorator.setCustomMaximize(true);
|
||||
decorator.applyCss();
|
||||
decorator.getStylesheets()
|
||||
.add(getClass().getResource("/css/Master.css").toExternalForm());
|
||||
gameClient = new GameClient(decorator);
|
||||
setDecorator(decorator);
|
||||
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
Scene scene = new Scene(decorator, 1200, 800, false, SceneAntialiasing.BALANCED);
|
||||
stage.setMinHeight(800);
|
||||
stage.setMinWidth(1200);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
stage.setOnCloseRequest(e -> closeAll());
|
||||
|
||||
decorator.setOnCloseButtonAction(this::closeAll);
|
||||
|
||||
// TODO Platform.runLater(this::checkCompatibility);
|
||||
|
||||
Sounds.stopMusic();
|
||||
Sounds.playMenuMusic();
|
||||
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
try {
|
||||
ServerAdvertiser.getInstance().unregister();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Couldn't unregister server");
|
||||
}
|
||||
|
||||
gameClient.stopGame();
|
||||
System.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the decorator when a new one is created (and ideally the old one destroyed)
|
||||
* Also allows injection of buttons into the decorator for custom functions.
|
||||
*
|
||||
* @param newDecorator The new JFXDecorator to handle the game window.
|
||||
*/
|
||||
private void setDecorator(JFXDecorator newDecorator) {
|
||||
decorator = newDecorator;
|
||||
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
gameClient.stopGame();
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
//Injecting a volume toggle into the decorator.
|
||||
//Get the button box
|
||||
HBox btns = (HBox) decorator.getChildren().get(0);
|
||||
|
||||
//Create new button
|
||||
JFXButton btnMute = new JFXButton();
|
||||
btnMute.setText(" Toggle Sound");
|
||||
btnMute.setStyle("-fx-text-fill:#fff");
|
||||
btnMute.getStyleClass().add("jfx-decorator-button");
|
||||
btnMute.setCursor(Cursor.HAND);
|
||||
|
||||
//Create Graphics
|
||||
SVGGlyph spacer = new SVGGlyph(0, "SPACER", "", Color.WHITE);
|
||||
SVGGlyph volumeOn = new SVGGlyph(0, "VOLUME_ON",
|
||||
"M0,6 L0,12 L4,12 L9,17 L9,1 L4,6 L0,6 L0,6 Z M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,13 C12.5,12.3 13.5,10.8 13.5,9 L13.5,9 Z M11,0.2 L11,2.3 C13.9,3.2 16,5.8 16,9 C16,12.2 13.9,14.8 11,15.7 L11,17.8 C15,16.9 18,13.3 18,9 C18,4.7 15,1.1 11,0.2 L11,0.2 Z",
|
||||
Color.WHITE);
|
||||
SVGGlyph volumeOff = new SVGGlyph(0, "VOLUME_ON",
|
||||
"M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,7.2 L13.5,9.7 L13.5,9 L13.5,9 Z M16,9 C16,9.9 15.8,10.8 15.5,11.6 L17,13.1 C17.7,11.9 18,10.4 18,8.9 C18,4.6 15,1 11,0.1 L11,2.2 C13.9,3.2 16,5.8 16,9 L16,9 Z M1.3,0 L0,1.3 L4.7,6 L0,6 L0,12 L4,12 L9,17 L9,10.3 L13.3,14.6 C12.6,15.1 11.9,15.5 11,15.8 L11,17.9 C12.4,17.6 13.6,17 14.7,16.1 L16.7,18.1 L18,16.8 L9,7.8 L1.3,0 L1.3,0 Z M9,1 L6.9,3.1 L9,5.2 L9,1 L9,1 Z",
|
||||
Color.WHITE);
|
||||
volumeOn.setSize(16, 16);
|
||||
volumeOff.setSize(16, 16);
|
||||
spacer.setSize(40, 16);
|
||||
|
||||
// Determine which graphic should go on the button
|
||||
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
|
||||
btnMute.setGraphic(volumeOff);
|
||||
} else {
|
||||
btnMute.setGraphic(volumeOn);
|
||||
}
|
||||
|
||||
// Add Buttons
|
||||
btns.getChildren().add(0, spacer);
|
||||
btns.getChildren().add(0, btnMute);
|
||||
btnMute.setOnAction((action) -> {
|
||||
Sounds.toggleAllSounds();
|
||||
if (btnMute.getGraphic().equals(volumeOff)) {
|
||||
btnMute.setGraphic(volumeOn);
|
||||
} else {
|
||||
btnMute.setGraphic(volumeOff);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a PC has compatibility with the bonjour protocol for server detection.
|
||||
*/
|
||||
private void checkCompatibility() {
|
||||
if (BonjourInstallChecker.isBonjourSupported()) {
|
||||
BonjourInstallChecker.openInstallUrl();
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
try {
|
||||
ServerAdvertiser.getInstance().unregister();
|
||||
} catch (IOException e1) {
|
||||
logger.warn("Could not un-register game");
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public JFXDecorator getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
public void setScene(Node scene) {
|
||||
Platform.runLater(() -> decorator.setContent(scene));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stage and re-initialize the start view in the new stage.
|
||||
*/
|
||||
public void goToStartView() {
|
||||
try {
|
||||
this.stage.close();
|
||||
Stage stage = new Stage();
|
||||
initialStartView(stage);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public GameClient getGameClient() {
|
||||
return gameClient;
|
||||
}
|
||||
|
||||
public String getProperty(String key) {
|
||||
return properties.get(key);
|
||||
}
|
||||
|
||||
public void setProperty(String key, String val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
|
||||
public void setPlayerList(ObservableList<String> playerList) {
|
||||
this.playerList = playerList;
|
||||
}
|
||||
|
||||
public ObservableList<String> getPlayerList() {
|
||||
return playerList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the view to the Lobby Screen
|
||||
* @param disableReadyButton Boolean value so that clients can't try start a game.
|
||||
* @return A LobbyController object for the Lobby Screen.
|
||||
*/
|
||||
public LobbyController goToLobby(Boolean disableReadyButton) {
|
||||
FXMLLoader loader = loadFxml("/views/LobbyView.fxml");
|
||||
|
||||
try {
|
||||
setScene(loader.load());
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not load lobby view");
|
||||
}
|
||||
|
||||
if (disableReadyButton) {
|
||||
LobbyController lobbyController = loader.getController();
|
||||
lobbyController.disableReadyButton();
|
||||
}
|
||||
|
||||
return loader.getController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the view for the race. Creating a new decorator and destroying the old one.
|
||||
* @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
|
||||
// bug causes stage cannot be fully maximised.
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
stage.close();
|
||||
stage = new Stage();
|
||||
JFXDecorator decorator = new JFXDecorator(stage, loader.load(), false, true, true);
|
||||
decorator.setCustomMaximize(true);
|
||||
decorator.applyCss();
|
||||
decorator.getStylesheets()
|
||||
.add(getClass().getResource("/css/Master.css").toExternalForm());
|
||||
setDecorator(decorator);
|
||||
Scene scene = new Scene(decorator);
|
||||
RaceViewController raceViewController = loader.getController();
|
||||
gameClient.setRaceViewController(raceViewController);
|
||||
// set key press event to catch key stoke
|
||||
scene.setOnKeyPressed(gameClient::keyPressed);
|
||||
scene.setOnKeyReleased(gameClient::keyReleased);
|
||||
|
||||
// uncomment to make it full screen
|
||||
// Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
|
||||
// stage.setX(visualBounds.getMinX());
|
||||
// stage.setY(visualBounds.getMinY());
|
||||
// stage.setWidth(visualBounds.getWidth());
|
||||
// stage.setHeight(visualBounds.getHeight());
|
||||
// stage.setMaximized(true);
|
||||
// stage.setFullScreen(true);
|
||||
|
||||
stage.setMinHeight(500);
|
||||
stage.setMinWidth(800);
|
||||
stage.setTitle("Party Parrots At Sea");
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
stage.setOnCloseRequest(e -> closeAll());
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
while (loader.getController() == null){
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return loader.getController();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package seng302.visualiser.controllers.cells;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
|
||||
public class PlayerCell {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private Label playerName;
|
||||
@FXML
|
||||
private GridPane playerListCell;
|
||||
@FXML
|
||||
private Pane boatPane;
|
||||
//---------FXML END---------//
|
||||
|
||||
private String name;
|
||||
private Color boatColor;
|
||||
private Integer playerId;
|
||||
private BoatMeshType boatType;
|
||||
|
||||
public PlayerCell(Integer playerId, ClientYacht yacht) {
|
||||
this.playerId = playerId;
|
||||
this.name = yacht.getBoatName();
|
||||
this.boatColor = yacht.getColour();
|
||||
this.boatType = yacht.getBoatType();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// Set Player Name
|
||||
playerName.setText(name);
|
||||
// Add Rotating Boat to Player Cell with players color on it.
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatIconView(boatType, boatColor);
|
||||
group.getChildren().add(bo.getAssets());
|
||||
}
|
||||
|
||||
public Integer getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Color getBoatColor() {
|
||||
return boatColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package seng302.visualiser.controllers.cells;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class ServerCell implements Initializable {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
//Layout
|
||||
@FXML
|
||||
private VBox serverCellVBox;
|
||||
@FXML
|
||||
private GridPane serverListCell;
|
||||
//Server Information
|
||||
@FXML
|
||||
private Label serverName;
|
||||
@FXML
|
||||
private Label mapName;
|
||||
@FXML
|
||||
private Label serverPlayerCount;
|
||||
//Server Connection
|
||||
@FXML
|
||||
private JFXButton serverConnButton;
|
||||
//---------FXML END---------//
|
||||
|
||||
private String name;
|
||||
private String mapNameString;
|
||||
|
||||
private String currPlayerCount;
|
||||
|
||||
private String hostName;
|
||||
private Integer portNumber;
|
||||
|
||||
public ServerCell(ServerDescription server) {
|
||||
this.name = server.getName();
|
||||
|
||||
this.currPlayerCount = server.getNumPlayers().toString() + "/" + server.getCapacity().toString();
|
||||
this.mapNameString = server.getMapName();
|
||||
|
||||
// Can cause issues on windows PCs without the bonjour service installed.
|
||||
this.hostName = server.getAddress();
|
||||
this.portNumber = server.portNumber();
|
||||
}
|
||||
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
serverName.setText(name);
|
||||
serverPlayerCount.setText(currPlayerCount);
|
||||
mapName.setText(mapNameString);
|
||||
|
||||
serverCellVBox.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||
|
||||
serverConnButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
joinServer();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the chosen server using the button on the serverCell.
|
||||
*/
|
||||
private void joinServer() {
|
||||
ViewManager.getInstance().getGameClient().runAsClient(hostName, portNumber);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.RequiredFieldValidator;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.ClientToServerThread;
|
||||
import seng302.visualiser.controllers.LobbyController;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.validators.FieldLengthValidator;
|
||||
import seng302.visualiser.validators.ValidationTools;
|
||||
|
||||
public class BoatCustomizeController implements Initializable{
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private JFXColorPicker colorPicker;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
@FXML
|
||||
private JFXTextField boatName;
|
||||
@FXML
|
||||
private Pane boatPane;
|
||||
@FXML
|
||||
void colorChanged() {
|
||||
refreshBoat();
|
||||
}
|
||||
//---------FXML END---------//
|
||||
|
||||
private ClientToServerThread socketThread;
|
||||
private LobbyController lobbyController;
|
||||
private BoatMeshType currentBoat;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
|
||||
|
||||
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
|
||||
playerNameReqValidator.setMessage("Player name required.");
|
||||
|
||||
FieldLengthValidator playerNameLengthValidator = new FieldLengthValidator(20);
|
||||
playerNameLengthValidator.setMessage("Player name too long.");
|
||||
|
||||
boatName.setValidators(playerNameLengthValidator, playerNameReqValidator);
|
||||
boatPane.setBackground(
|
||||
new Background(new BackgroundFill(Color.SKYBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
|
||||
submitBtn.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
submitCustomization();
|
||||
});
|
||||
|
||||
submitBtn.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to submit a valid customization packet for boat name and boat color.
|
||||
*/
|
||||
private void submitCustomization() {
|
||||
|
||||
if (ValidationTools.validateTextField(boatName)) {
|
||||
socketThread
|
||||
.sendCustomizationRequest(CustomizeRequestType.NAME, boatName.getText().getBytes());
|
||||
|
||||
Color color = colorPicker.getValue();
|
||||
short red = (short) (color.getRed() * 255);
|
||||
short green = (short) (color.getGreen() * 255);
|
||||
short blue = (short) (color.getBlue() * 255);
|
||||
|
||||
byte[] colorArray = new byte[3];
|
||||
|
||||
colorArray[0] = (byte) red;
|
||||
colorArray[1] = (byte) green;
|
||||
colorArray[2] = (byte) blue;
|
||||
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.SHAPE, currentBoat.toString().getBytes());
|
||||
lobbyController.closeCustomizationDialog();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlayerName(String name) {
|
||||
this.boatName.setText(name);
|
||||
}
|
||||
|
||||
public void setPlayerColor(Color playerColor) {
|
||||
this.colorPicker.setValue(playerColor);
|
||||
}
|
||||
|
||||
public void setParentController(LobbyController lobbyController){
|
||||
this.lobbyController = lobbyController;
|
||||
}
|
||||
|
||||
public void setCurrentBoat(BoatMeshType boatType) {
|
||||
currentBoat = boatType;
|
||||
displayCurrentBoat();
|
||||
}
|
||||
|
||||
public void nextBoat() {
|
||||
currentBoat = BoatMeshType.getNextBoatType(currentBoat);
|
||||
displayCurrentBoat();
|
||||
}
|
||||
|
||||
public void prevBoat() {
|
||||
currentBoat = BoatMeshType.getPrevBoatType(currentBoat);
|
||||
displayCurrentBoat();
|
||||
}
|
||||
|
||||
private void displayCurrentBoat() {
|
||||
boatPane.getChildren().clear();
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
|
||||
group.getChildren().add(bo.getAssets());
|
||||
group.getChildren().add(new PointLight());
|
||||
}
|
||||
|
||||
private void refreshBoat() {
|
||||
boatPane.getChildren().clear();
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
|
||||
group.getChildren().add(bo.getAssets());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class FinishDialogController implements Initializable {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private Label raceFinishLabel;
|
||||
@FXML
|
||||
private JFXListView<Label> finishersList;
|
||||
@FXML
|
||||
private JFXButton playAgain;
|
||||
//---------FXML END---------//
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
playAgain.setOnAction(event -> ViewManager.getInstance().goToStartView());
|
||||
}
|
||||
|
||||
public void setFinishedBoats(ArrayList<ClientYacht> finishedBoats) {
|
||||
finishersList.getItems().clear();
|
||||
for (int i = 0; i < finishedBoats.size(); i++) {
|
||||
finishersList.getItems().add(new Label(Integer.toString(i+1) +". " + finishedBoats.get(i).getBoatName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
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 ServerCreationController implements Initializable {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private JFXTextField serverName;
|
||||
@FXML
|
||||
private JFXSlider maxPlayersSlider;
|
||||
@FXML
|
||||
private Label maxPlayersLabel;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
//---------FXML END---------//
|
||||
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
updateMaxPlayerLabel();
|
||||
maxPlayersSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
updateMaxPlayerLabel();
|
||||
});
|
||||
|
||||
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
|
||||
fieldLengthValidator.setMessage("Server name too long.");
|
||||
|
||||
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
|
||||
fieldRequiredValidator.setMessage("Server name is required.");
|
||||
|
||||
serverName.setValidators(fieldLengthValidator, fieldRequiredValidator);
|
||||
|
||||
submitBtn.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
validateServerSettings();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a server has a valid name and creates the server.
|
||||
*/
|
||||
private void validateServerSettings() {
|
||||
submitBtn.setText("CREATING...");
|
||||
if (ValidationTools.validateTextField(serverName)) {
|
||||
createServer();
|
||||
} else {
|
||||
submitBtn.setText("SUBMIT");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a server with a given set of details.
|
||||
*/
|
||||
private void createServer() {
|
||||
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
|
||||
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
|
||||
.getValue());
|
||||
|
||||
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
|
||||
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a label as the user slides along the max players slider.
|
||||
*/
|
||||
private void updateMaxPlayerLabel() {
|
||||
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
|
||||
maxPlayersLabel.setText(String.format("YOU SELECTED: %.0f", maxPlayersSlider.getValue()));
|
||||
}
|
||||
|
||||
public void playButtonHoverSound(MouseEvent mouseEvent) {
|
||||
Sounds.playHoverSound();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.transform.Rotate;
|
||||
|
||||
/**
|
||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
||||
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
||||
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
|
||||
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
||||
* minimized in which case it attempts to store animations and apply them when the window is
|
||||
* maximised.
|
||||
*/
|
||||
public class BoatObject extends Group {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SelectedBoatListener {
|
||||
|
||||
void notifySelected(BoatObject boatObject, Boolean isSelected);
|
||||
}
|
||||
|
||||
//Constants for drawing
|
||||
private static final double BOAT_HEIGHT = 15d;
|
||||
private static final double BOAT_WIDTH = 10d;
|
||||
|
||||
private double xVelocity;
|
||||
private double yVelocity;
|
||||
private double lastHeading;
|
||||
private double sailState;
|
||||
//Graphical objects
|
||||
private Polyline trail = new Polyline();
|
||||
private Polygon boatPoly;
|
||||
private Polygon sail;
|
||||
private Wake wake;
|
||||
private Line leftLayLine;
|
||||
private Line rightLayline;
|
||||
private double distanceTravelled, lastRotation;
|
||||
private Point2D lastPoint;
|
||||
private Paint colour = Color.BLACK;
|
||||
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
|
||||
private boolean isPlayer = false;
|
||||
|
||||
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the default triangular boat polygon.
|
||||
*/
|
||||
public BoatObject() {
|
||||
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||
0.0, -BOAT_HEIGHT / 2,
|
||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
||||
* at point (0,0).
|
||||
*
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
public BoatObject(double... points) {
|
||||
initChildren(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the javafx objects that will be the in the group by default.
|
||||
*
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
private void initChildren(double... points) {
|
||||
boatPoly = new Polygon(points);
|
||||
boatPoly.setFill(colour);
|
||||
boatPoly.setFill(this.colour);
|
||||
boatPoly.setOnMouseEntered(event -> {
|
||||
boatPoly.setFill(Color.FLORALWHITE);
|
||||
boatPoly.setStroke(Color.RED);
|
||||
});
|
||||
boatPoly.setOnMouseExited(event -> {
|
||||
boatPoly.setFill(colour);
|
||||
boatPoly.setFill(this.colour);
|
||||
boatPoly.setStroke(Color.BLACK);
|
||||
});
|
||||
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||
boatPoly.setCache(true);
|
||||
// boatPoly.setCacheHint(CacheHint.SPEED);
|
||||
|
||||
// annotationBox = new AnnotationBox();
|
||||
// annotationBox.setFill(colour);
|
||||
|
||||
leftLayLine = new Line();
|
||||
rightLayline = new Line();
|
||||
trail.getStrokeDashArray().setAll(5d, 10d);
|
||||
trail.setCache(true);
|
||||
wake = new Wake(0, -BOAT_HEIGHT);
|
||||
wake.setVisible(true);
|
||||
|
||||
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
|
||||
0.0, BOAT_HEIGHT);
|
||||
sailState = 0;
|
||||
sail.setStrokeWidth(2.0);
|
||||
sail.setStroke(Color.BLACK);
|
||||
sail.setFill(Color.TRANSPARENT);
|
||||
sail.setCache(true);
|
||||
super.getChildren().clear();
|
||||
super.getChildren().addAll(boatPoly, sail);
|
||||
}
|
||||
|
||||
public void setFill (Paint value) {
|
||||
this.colour = value;
|
||||
boatPoly.setFill(colour);
|
||||
trail.setStroke(colour);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the boat and its children annotations to coordinates specified
|
||||
* @param x The X coordinate to move the boat to
|
||||
* @param y The Y coordinate to move the boat to
|
||||
* @param rotation The rotation by which the boat moves
|
||||
* @param velocity The velocity the boat is moving
|
||||
* @param sailIn Boolean to toggle sail state.
|
||||
*/
|
||||
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
||||
Double dx = Math.abs(boatPoly.getLayoutX() - x);
|
||||
Double dy = Math.abs(boatPoly.getLayoutY() - y);
|
||||
Platform.runLater(() -> {
|
||||
rotateTo(rotation, sailIn, windDir);
|
||||
boatPoly.setLayoutX(x);
|
||||
boatPoly.setLayoutY(y);
|
||||
if (sailIn) {
|
||||
// sail.getPoints().clear();
|
||||
// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
|
||||
// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
|
||||
sail.setLayoutX(x);
|
||||
sail.setLayoutY(y);
|
||||
} else {
|
||||
animateSail();
|
||||
sail.setLayoutX(x);
|
||||
sail.setLayoutY(y);
|
||||
}
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
});
|
||||
wake.setRotation(rotation, velocity);
|
||||
// rotateTo(rotation);
|
||||
// boatPoly.setLayoutX(x);
|
||||
// boatPoly.setLayoutY(y);
|
||||
// wake.setLayoutX(x);
|
||||
// wake.setLayoutY(y);
|
||||
// wake.rotate(rotation);
|
||||
|
||||
// wake.setRotation(rotation, groundSpeed);
|
||||
// isStopped = false;
|
||||
// destinationSet = true;
|
||||
lastRotation = rotation;
|
||||
|
||||
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distanceTravelled > 15 && isPlayer) {
|
||||
distanceTravelled = 0d;
|
||||
Platform.runLater(() -> trail.getPoints().addAll(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
private Double normalizeHeading(double heading, double windDirection) {
|
||||
Double normalizedHeading = heading - windDirection;
|
||||
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||
return normalizedHeading;
|
||||
}
|
||||
|
||||
|
||||
private void rotateTo(double heading, boolean sailsIn, double windDir) {
|
||||
boatPoly.getTransforms().setAll(new Rotate(heading));
|
||||
if (sailsIn) {
|
||||
Double sailWindOffset = 30.0;
|
||||
Double upwindAngleLimit = 15.0;
|
||||
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
||||
Double normalizedHeading = normalizeHeading(heading, windDir);
|
||||
if (normalizedHeading < 180) {
|
||||
sail.getTransforms().setAll(new Rotate(windDir + 90 + sailWindOffset));
|
||||
sail.getPoints().clear();
|
||||
sail.getPoints().addAll(0.0, 0.0, 4.0, -1.5, 8.0, -3.0, 12.0, -3.5, 16.0, -3.0, 20.0, -1.5, 24.0, 0.0);
|
||||
if (normalizedHeading > 90 + sailWindOffset){
|
||||
sail.getTransforms().setAll(new Rotate(heading + downwindAngleLimit));
|
||||
}
|
||||
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
||||
sail.getTransforms().setAll(new Rotate(heading + 90 - upwindAngleLimit));
|
||||
}
|
||||
} else {
|
||||
sail.getTransforms().setAll(new Rotate(windDir + 90 - sailWindOffset));
|
||||
sail.getPoints().clear();
|
||||
sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
|
||||
if (normalizedHeading < 270 - sailWindOffset){
|
||||
sail.getTransforms().setAll(new Rotate(heading + 180 - downwindAngleLimit));
|
||||
}
|
||||
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
|
||||
sail.getTransforms().setAll(new Rotate(heading + 90 + upwindAngleLimit));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sail.getTransforms().setAll(new Rotate(windDir));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void animateSail(){
|
||||
Double[] points = new Double[200];
|
||||
double amplitude = 2.0;
|
||||
double period = 10;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||
points[i * 2 + 1] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||
points[199 - (i * 2)] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||
}
|
||||
if (sailState == - 2 * Math.PI) {
|
||||
sailState = 0;
|
||||
} else {
|
||||
sailState = sailState - Math.PI / 5;
|
||||
}
|
||||
sail.getPoints().clear();
|
||||
sail.getPoints().addAll(points);
|
||||
|
||||
}
|
||||
|
||||
public void updateLocation() {
|
||||
// double dx = xVelocity / 60;
|
||||
// double dy = yVelocity / 60;
|
||||
//
|
||||
// distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
||||
// moveGroupBy(dx, dy);
|
||||
//
|
||||
// if (distanceTravelled > 70) {
|
||||
// distanceTravelled = 0d;
|
||||
//
|
||||
// if (lastPoint != null) {
|
||||
// Line l = new Line(
|
||||
// lastPoint.getX(),
|
||||
// lastPoint.getY(),
|
||||
// boatPoly.getLayoutX(),
|
||||
// boatPoly.getLayoutY()
|
||||
// );
|
||||
// l.getStrokeDashArray().setAll(3d, 7d);
|
||||
// l.setStroke(colour);
|
||||
// l.setCache(true);
|
||||
// l.setCacheHint(CacheHint.SPEED);
|
||||
// lineGroup.getChildren().add(l);
|
||||
// }
|
||||
// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
// }
|
||||
// wake.updatePosition();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
|
||||
// * gates position and the current wind
|
||||
// * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
|
||||
// * going up wind, if they are on different sides of the gate, then the boat is going downwind
|
||||
// * @param canvasController
|
||||
// */
|
||||
// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
|
||||
//
|
||||
// Double windAngle = StreamParser.getWindDirection();
|
||||
// GateMark thisGateMark = (GateMark) nextMark;
|
||||
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
||||
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
||||
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
||||
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
||||
//
|
||||
// Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||
//
|
||||
//
|
||||
// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||
// Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
|
||||
//
|
||||
//
|
||||
// /*
|
||||
// If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
|
||||
// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
||||
// with the wind.
|
||||
// */
|
||||
// return boatLineFuncResult.equals(windLineFuncResult);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
updateListener(isSelected);
|
||||
this.isSelected = isSelected;
|
||||
setLineGroupVisible(isSelected);
|
||||
setWakeVisible(isSelected);
|
||||
setLayLinesVisible(isSelected);
|
||||
}
|
||||
|
||||
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
|
||||
boolean trail, boolean wake) {
|
||||
// boatAnnotations.setVisible(teamName, velocity, estTime, legTime);
|
||||
// this.wake.setVisible(wake);
|
||||
this.trail.setVisible(trail);
|
||||
}
|
||||
|
||||
public void setLineGroupVisible(Boolean visible) {
|
||||
trail.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setWakeVisible(Boolean visible) {
|
||||
// wake.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLayLinesVisible(Boolean visible) {
|
||||
leftLayLine.setVisible(visible);
|
||||
rightLayline.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLaylines(Line line1, Line line2) {
|
||||
this.leftLayLine = line1;
|
||||
this.rightLayline = line2;
|
||||
}
|
||||
|
||||
public ArrayList<Line> getLaylines() {
|
||||
ArrayList<Line> laylines = new ArrayList<>();
|
||||
laylines.add(leftLayLine);
|
||||
laylines.add(rightLayline);
|
||||
return laylines;
|
||||
}
|
||||
|
||||
public Group getWake () {
|
||||
return wake;
|
||||
}
|
||||
|
||||
public Node getTrail() {
|
||||
return trail;
|
||||
}
|
||||
|
||||
public Double getBoatLayoutX() {
|
||||
return boatPoly.getLayoutX();
|
||||
}
|
||||
|
||||
|
||||
public Double getBoatLayoutY() {
|
||||
return boatPoly.getLayoutY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this boat to appear highlighted
|
||||
*/
|
||||
public void setAsPlayer() {
|
||||
boatPoly.getPoints().setAll(
|
||||
-BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
|
||||
0.0, -BOAT_HEIGHT / 1.75,
|
||||
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
|
||||
);
|
||||
boatPoly.setStroke(Color.BLACK);
|
||||
boatPoly.setStrokeWidth(2);
|
||||
boatPoly.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
isPlayer = true;
|
||||
animateSail();
|
||||
}
|
||||
|
||||
public void setTrajectory(double heading, double velocity, double windDir) {
|
||||
wake.setRotation(lastHeading - heading, velocity);
|
||||
rotateTo(heading, false, windDir);
|
||||
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
|
||||
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
|
||||
lastHeading = heading;
|
||||
}
|
||||
|
||||
public Boolean getSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
|
||||
// wake.setRotation(lastHeading - heading, velocity);
|
||||
// rotateTo(heading);
|
||||
// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX;
|
||||
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
|
||||
lastHeading = heading;
|
||||
}
|
||||
|
||||
private void updateListener(Boolean isSelected) {
|
||||
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
|
||||
sbl.notifySelected(this, isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSelectedBoatListener(SelectedBoatListener sbl) {
|
||||
selectedBoatListenerListeners.add(sbl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
/**
|
||||
* Extension of a ScrollPane that contains a TextFlow. Has an addMessage() function to parse and
|
||||
* display chatter text.
|
||||
*/
|
||||
public class ChatHistory extends ScrollPane {
|
||||
|
||||
private TextFlow textFlow = new TextFlow();
|
||||
|
||||
public ChatHistory() {
|
||||
this.setContent(textFlow);
|
||||
this.setFitToWidth(true);
|
||||
this.setFitToHeight(true);
|
||||
this.setMaxHeight(Double.MAX_VALUE);
|
||||
this.setMaxWidth(Double.MAX_VALUE);
|
||||
this.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
|
||||
this.setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0);");
|
||||
|
||||
this.textFlow.setStyle(
|
||||
"-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0); -fx-padding: 10px"
|
||||
);
|
||||
//This makes the window auto scroll.
|
||||
textFlow.getChildren().addListener((ListChangeListener<Node>) c ->
|
||||
this.setVvalue(1.0)
|
||||
);
|
||||
//This just makes it so that the ChatHistory is on focus it passes it off to the parent.
|
||||
this.parentProperty().addListener((obs, old, parent) ->
|
||||
this.focusedProperty().addListener((obsVal, oldVal, onFocus) -> {
|
||||
if (onFocus) {
|
||||
parent.requestFocus();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to chat history. Messages should be either of the form:
|
||||
* "[HH:MM:ss] player_name: message_text" or
|
||||
* "SERVER: message_text"
|
||||
* @param colour The colour of the user sending the message
|
||||
* @param Text The chatter text message to be displayed
|
||||
*/
|
||||
public void addMessage (Paint colour, String Text) {
|
||||
String[] words = Text.split(":");
|
||||
if (words[0].trim().equals("SERVER")) {
|
||||
Text text = new Text(Text + "\n");
|
||||
text.setStyle("-fx-font-weight: bolder");
|
||||
textFlow.getChildren().add(text);
|
||||
} else {
|
||||
Text timePlayer = new Text(
|
||||
String.join(":", Arrays.copyOfRange(words, 0, 3)) + ":"
|
||||
);
|
||||
timePlayer.setStyle("-fx-font-weight: bold");
|
||||
timePlayer.setFill(colour);
|
||||
Text message = new Text(
|
||||
String.join(":", Arrays.copyOfRange(words, 3, words.length)) + "\n"
|
||||
);
|
||||
message.wrappingWidthProperty().bind(this.widthProperty());
|
||||
textFlow.getChildren().addAll(timePlayer, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void increaseOpacity() {
|
||||
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(255, 255, 255, 0.2); -fx-background-color: rgba(255, 255, 255, 0.2);");
|
||||
|
||||
this.textFlow.setStyle(
|
||||
"-fx-background: rgba(255, 255, 255, 0.2); -fx-background-color: rgba(255, 255, 255, 0.2); -fx-padding: 10px"
|
||||
);
|
||||
}
|
||||
|
||||
public void decreaseOpacity() {
|
||||
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0);");
|
||||
|
||||
this.textFlow.setStyle(
|
||||
"-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0); -fx-padding: 10px"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,19 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.*;
|
||||
import javafx.scene.shape.Arc;
|
||||
import javafx.scene.shape.ArcType;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.visualiser.fxObjects.assets_3D.Model;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
// TODO: 16/08/17 this class used to be well written... FeelsBadMan. Maybe lose the ternary operators.
|
||||
/**
|
||||
@@ -26,6 +35,47 @@ public class MarkArrowFactory {
|
||||
public static final double ARROW_HEAD_WIDTH = 6;
|
||||
public static final double STROKE_WIDTH = 3;
|
||||
|
||||
public static Model constructEntryArrow3D (
|
||||
RoundingSide roundingSide, double angle, ModelType type) {
|
||||
Model entryArrow = ModelFactory.importModel(type);
|
||||
|
||||
double angleDeg = angle;
|
||||
angle = 180 - angle;
|
||||
angle = Math.toRadians(angle);
|
||||
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double relativeX = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
|
||||
double relativeY = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
|
||||
double xStart = relativeX + -10 * Math.sin(angle);
|
||||
double yStart = relativeY + -10 * Math.cos(angle);
|
||||
entryArrow.getAssets().getTransforms().addAll(
|
||||
new Translate(xStart, yStart, 0),
|
||||
new Rotate(angleDeg, new Point3D(0,0,1))
|
||||
);
|
||||
return entryArrow;
|
||||
}
|
||||
|
||||
public static Model constructExitArrow3D (
|
||||
RoundingSide roundingSide, double angle, ModelType type) {
|
||||
Model exitArrow = ModelFactory.importModel(type);
|
||||
|
||||
double angleDeg = angle;
|
||||
angle = 180 - angle;
|
||||
angle = Math.toRadians(angle);
|
||||
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double xStart = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
|
||||
double yStart = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
|
||||
|
||||
exitArrow.getAssets().getTransforms().addAll(
|
||||
new Translate(xStart, yStart, 0),
|
||||
new Rotate(angleDeg, new Point3D(0,0,1))
|
||||
);
|
||||
|
||||
return exitArrow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an entry arrow group showing an arrow into and out of the rounding area. It is centered on (0, 0).
|
||||
* @param roundingSide The side of the boat that will be closest to the mark.
|
||||
@@ -36,47 +86,71 @@ public class MarkArrowFactory {
|
||||
*/
|
||||
public static Group constructEntryArrow (RoundingSide roundingSide, double angleOfEntry,
|
||||
double angleOfExit, Paint colour) {
|
||||
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit && Math.abs(angleOfExit - angleOfEntry) < 180) {
|
||||
// Check to see if the the angle around mark would take you inside of it. (less than 180)
|
||||
// If so make interior angle.
|
||||
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit &&
|
||||
Math.abs(angleOfExit - angleOfEntry) < 180) {
|
||||
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
|
||||
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit && -Math.abs(angleOfEntry - angleOfExit) > -180) {
|
||||
|
||||
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit &&
|
||||
-Math.abs(angleOfEntry - angleOfExit) > -180) {
|
||||
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
|
||||
}
|
||||
|
||||
angleOfEntry = 180 - angleOfEntry;
|
||||
//Create regular exit arrow.
|
||||
Group arrow = new Group();
|
||||
Group exitSection = constructExitArrow(roundingSide, angleOfExit, colour);
|
||||
//Reverse angles to make arc
|
||||
angleOfEntry = 180 - angleOfEntry;
|
||||
angleOfExit = 180 - angleOfExit;
|
||||
//Maker the arc
|
||||
Arc roundSection = new Arc(
|
||||
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
|
||||
(roundingSide == RoundingSide.PORT ? -180 : 0) + angleOfEntry,
|
||||
//Where to start drawing arc from
|
||||
(roundingSide == RoundingSide.PORT ? 0 : angleOfEntry),
|
||||
//Which way to go around the mark. (clockwise vs anticlockwise)
|
||||
roundingSide == RoundingSide.PORT ? Math.abs(angleOfExit - angleOfEntry) : -Math.abs(angleOfEntry - angleOfExit)
|
||||
);
|
||||
roundSection.setStrokeWidth(STROKE_WIDTH);
|
||||
roundSection.setType(ArcType.OPEN);
|
||||
roundSection.setStroke(colour);
|
||||
roundSection.setFill(new Color(0,0,0,0));
|
||||
//Revert angle to normal for line segment. Invert Port/Starboard since it is an entry arrow.
|
||||
Polygon entrySection = constructLineSegment(
|
||||
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT, 180 + angleOfEntry, colour
|
||||
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT,
|
||||
180 + angleOfEntry, colour
|
||||
);
|
||||
arrow.getChildren().addAll(exitSection, roundSection, entrySection);
|
||||
return arrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an arrow when the turning is not around the outside of the mark.
|
||||
*
|
||||
* @param roundingSide side to round on.
|
||||
* @param angleOfExit angle of entry
|
||||
* @param angleOfEntry angle of exit
|
||||
* @param colour colour of arrow
|
||||
* @return the arrow.
|
||||
*/
|
||||
private static Group makeInteriorAngle (RoundingSide roundingSide, double angleOfExit, double angleOfEntry, Paint colour) {
|
||||
Group arrow = new Group();
|
||||
Polygon lineSegment;
|
||||
//Reverse angle of exit/entry to find position between them
|
||||
angleOfEntry = Math.toRadians(360 - angleOfEntry);
|
||||
angleOfExit = Math.toRadians(180 - angleOfExit);
|
||||
//Find start of entry arrow if it was a regular arrow.
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? -1 : 1;
|
||||
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfEntry + Math.PI / 2);
|
||||
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfEntry + Math.PI / 2);
|
||||
xStart = xStart + (ARROW_LENGTH * Math.sin(angleOfEntry));
|
||||
yStart = yStart + (ARROW_LENGTH * Math.cos(angleOfEntry));
|
||||
//Find of end exit arrow if it was a regular arrow.
|
||||
multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double xEnd = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfExit + Math.PI / 2);
|
||||
double yEnd = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfExit + Math.PI / 2);
|
||||
xEnd = xEnd + (ARROW_LENGTH * Math.sin(angleOfExit));
|
||||
yEnd = yEnd + (ARROW_LENGTH * Math.cos(angleOfExit));
|
||||
//Make line between these points.
|
||||
lineSegment = new Polygon(
|
||||
xStart, yStart,
|
||||
xEnd, yEnd
|
||||
@@ -85,12 +159,14 @@ public class MarkArrowFactory {
|
||||
lineSegment.setFill(Color.BLUE);
|
||||
lineSegment.setStrokeWidth(STROKE_WIDTH);
|
||||
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
//Make arrow head at the angle between these points.
|
||||
Polyline arrowHead = constructArrowHead(
|
||||
90 + Math.toDegrees(Math.atan2(yStart - yEnd, xEnd - xStart)),
|
||||
colour
|
||||
);
|
||||
arrowHead.setLayoutX(xEnd);
|
||||
arrowHead.setLayoutY(yEnd);
|
||||
//Construct arrow.
|
||||
arrow.getChildren().addAll(lineSegment, arrowHead);
|
||||
return arrow;
|
||||
}
|
||||
@@ -113,6 +189,15 @@ public class MarkArrowFactory {
|
||||
return arrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line rotated to the correct angle and and in the correct position for a mark at
|
||||
* position 0,0. Note that a line segment is assumed to be facing away from the mark so for
|
||||
* entry Starboard make the RoundingSide Port and vice versa.
|
||||
* @param roundingSide Rounding side of an exit arrow. (Reversed for entry)
|
||||
* @param angle Angle of line segment.
|
||||
* @param colour the desired colour of the line.
|
||||
* @return Line segmented at correct rotation centered at (0,0)
|
||||
*/
|
||||
private static Polygon constructLineSegment (RoundingSide roundingSide, double angle, Paint colour) {
|
||||
Polygon lineSegment;
|
||||
angle = Math.toRadians(angle);
|
||||
@@ -132,6 +217,12 @@ public class MarkArrowFactory {
|
||||
return lineSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PolyLine in the shape of an arrow head.
|
||||
* @param rotation direction for the arrow head to point.
|
||||
* @param colour colour of the arrow head
|
||||
* @return the arrowhead shaped PolyLine.
|
||||
*/
|
||||
private static Polyline constructArrowHead (double rotation, Paint colour) {
|
||||
Polyline arrow = new Polyline(
|
||||
-ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Polygon;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
+15
-8
@@ -1,4 +1,4 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -7,11 +7,12 @@ import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
|
||||
/**
|
||||
* Visual object for a mark. Contains a coloured circle and any specified arrows.
|
||||
*/
|
||||
public class Marker extends Group {
|
||||
public class Marker2D extends Group {
|
||||
|
||||
private Circle mark = new Circle();
|
||||
private Paint colour = Color.BLACK;
|
||||
@@ -23,18 +24,20 @@ public class Marker extends Group {
|
||||
/**
|
||||
* Creates a new Marker containing only a circle. The default colour is black.
|
||||
*/
|
||||
public Marker() {
|
||||
public Marker2D() {
|
||||
mark.setRadius(5);
|
||||
mark.setCenterX(0);
|
||||
mark.setCenterY(0);
|
||||
Platform.runLater(() -> this.getChildren().addAll(mark, new Group())); //Empty group placeholder or arrows.
|
||||
Platform.runLater(() -> this.getChildren()
|
||||
.addAll(mark, new Group())); //Empty group placeholder or arrows.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Marker containing only a circle of the given colour.
|
||||
*
|
||||
* @param colour the desired colour for the marker.
|
||||
*/
|
||||
public Marker(Paint colour) {
|
||||
public Marker2D(Paint colour) {
|
||||
this();
|
||||
this.colour = colour;
|
||||
mark.setFill(colour);
|
||||
@@ -43,6 +46,7 @@ public class Marker extends Group {
|
||||
/**
|
||||
* Adds an exit and entry arrow pair to the mark. Arrows are hidden and shown in the order they
|
||||
* are created by calling showNextEnterArrow() or showNextExitArrow()
|
||||
*
|
||||
* @param roundingSide the side the marker will be from the perspective of the arrow.
|
||||
* @param entryAngle The angle the arrow will point towards a marker
|
||||
* @param exitAngle The angle the arrow wil point from the marker.
|
||||
@@ -59,7 +63,8 @@ public class Marker extends Group {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows
|
||||
* become hidden.
|
||||
*/
|
||||
public void showNextEnterArrow() {
|
||||
showArrow(enterArrows, enterArrowIndex);
|
||||
@@ -67,7 +72,8 @@ public class Marker extends Group {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become
|
||||
* hidden.
|
||||
*/
|
||||
public void showNextExitArrow() {
|
||||
showArrow(exitArrows, exitArrowIndex);
|
||||
@@ -76,7 +82,8 @@ public class Marker extends Group {
|
||||
|
||||
private void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
if (arrowListIndex < arrowList.size()) {
|
||||
if (arrowListIndex == 1) {;
|
||||
if (arrowListIndex == 1) {
|
||||
;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().remove(1);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.CacheHint;
|
||||
@@ -0,0 +1,25 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.shape.StrokeLineJoin;
|
||||
|
||||
/**
|
||||
* Created by cir27 on 5/09/17.
|
||||
*/
|
||||
public class WindArrow extends Polyline {
|
||||
public WindArrow(Paint fill) {
|
||||
this.getPoints().addAll(
|
||||
-10d, 15d,
|
||||
0d, 25d,
|
||||
0d, -25d,
|
||||
0d, 25d,
|
||||
10d, 15d
|
||||
);
|
||||
this.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
this.setStroke(fill);
|
||||
this.setStrokeWidth(5);
|
||||
this.setStrokeLineJoin(StrokeLineJoin.ROUND);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
/**
|
||||
* Enum for boat meshes. Enum values should be of the form :
|
||||
* ENUM_VALUE (hull file, mast file, Y offset of mast CoR from origin, sail file, Y offset of sail CoR from origin, jib file, fixed sail)
|
||||
* Files must be valid .stl files.
|
||||
*/
|
||||
public enum BoatMeshType {
|
||||
|
||||
DINGHY("dinghy_hull.stl", "dinghy_mast.stl", 1.36653, "dinghy_sail.stl", 1.36653, null, false),
|
||||
CAT_ATE_A_MERINGUE("catamaran_hull.stl", "catamaran_mast.stl", 0.997, "catamaran_sail.stl",
|
||||
0.997, null, false),
|
||||
PIRATE_SHIP("pirateship_hull.stl", "pirateship_mast.stl", -0.5415, "pirateship_mainsail.stl",
|
||||
-0.5415, "pirateship_frontsail.stl", true);
|
||||
|
||||
final String hullFile, mastFile, sailFile, jibFile;
|
||||
final double mastOffset, sailOffset;
|
||||
final boolean fixedSail;
|
||||
final static BoatMeshType[] boatTypes = new BoatMeshType[]{DINGHY, CAT_ATE_A_MERINGUE, PIRATE_SHIP};
|
||||
|
||||
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile,
|
||||
double sailOffset, String jibFile, boolean fixedSail) {
|
||||
this.hullFile = hullFile;
|
||||
this.mastFile = mastFile;
|
||||
this.mastOffset = mastOffset;
|
||||
this.sailFile = sailFile;
|
||||
this.sailOffset = sailOffset;
|
||||
this.jibFile = jibFile;
|
||||
this.fixedSail = fixedSail;
|
||||
}
|
||||
|
||||
|
||||
//TODO kre39 make something not terrible to cycle through boat types
|
||||
public static BoatMeshType getNextBoatType(BoatMeshType boatType) {
|
||||
for (int i = 0; i < boatTypes.length; i++) {
|
||||
if (i == boatTypes.length -1) {
|
||||
return boatTypes[0];
|
||||
} else if (boatType == boatTypes[i]) {
|
||||
return boatTypes[i+1];
|
||||
}
|
||||
}
|
||||
return boatType;
|
||||
}
|
||||
|
||||
public static BoatMeshType getPrevBoatType(BoatMeshType boatType) {
|
||||
for (int i = 0; i < boatTypes.length; i++) {
|
||||
if (i == 0 && boatType == boatTypes[i]) {
|
||||
return boatTypes[boatTypes.length -1];
|
||||
} else if (boatType == boatTypes[i]) {
|
||||
return boatTypes[i-1];
|
||||
}
|
||||
}
|
||||
return boatType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.transform.Rotate;
|
||||
|
||||
/**
|
||||
* Container class for a group of 3d objects representing a boat and it's animation.
|
||||
*/
|
||||
public class BoatModel extends Model {
|
||||
|
||||
private static final int HULL_INDEX = 0;
|
||||
private static final int MAST_INDEX = 1;
|
||||
private static final int SAIL_INDEX = 2;
|
||||
|
||||
private BoatMeshType meshType;
|
||||
|
||||
/**
|
||||
* Stores a model and it's optional animation.
|
||||
* @param boatAssets The group with 3d assets for the boat.
|
||||
* @param animation Animation, can be null.
|
||||
*/
|
||||
BoatModel(Group boatAssets, AnimationTimer animation, BoatMeshType meshType) {
|
||||
super(boatAssets, animation);
|
||||
this.meshType = meshType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the sail of this model by the given amount.
|
||||
* @param degrees The rotation of the sail in degrees
|
||||
*/
|
||||
public void rotateSail(double degrees) {
|
||||
if (!meshType.fixedSail) {
|
||||
MeshView mast = getMeshViewChild(MAST_INDEX);
|
||||
MeshView sail = getMeshViewChild(SAIL_INDEX);
|
||||
mast.getTransforms().setAll(
|
||||
new Rotate(degrees, 0, -meshType.mastOffset, 0, new Point3D(0, 0, 1))
|
||||
);
|
||||
sail.getTransforms().setAll(
|
||||
new Rotate(degrees, 0, -meshType.sailOffset,0, new Point3D(0, 0, 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSail() {
|
||||
getMeshViewChild(SAIL_INDEX).setVisible(false);
|
||||
}
|
||||
|
||||
public void showSail() {
|
||||
getMeshViewChild(SAIL_INDEX).setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the colour of the model in this class.
|
||||
* @param newColour the new colour for the boat.
|
||||
*/
|
||||
public void changeColour(Color newColour) {
|
||||
changeColourChild(HULL_INDEX, newColour);
|
||||
changeColourChild(MAST_INDEX, newColour);
|
||||
}
|
||||
|
||||
private void changeColourChild(int index, Color newColour) {
|
||||
MeshView meshView = getMeshViewChild(index);
|
||||
meshView.setMaterial(new PhongMaterial(newColour));
|
||||
}
|
||||
|
||||
private MeshView getMeshViewChild(int index) {
|
||||
return (MeshView) assets.getChildren().get(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.transform.Rotate;
|
||||
|
||||
/**
|
||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
||||
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
||||
* a wake object and two text labels to annotate the boat teams name and the boatTypes velocity. The
|
||||
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
||||
* minimized in which case it attempts to store animations and apply them when the window is
|
||||
* maximised.
|
||||
*/
|
||||
public class BoatObject extends Group {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SelectedBoatListener {
|
||||
|
||||
void notifySelected(BoatObject boatObject, Boolean isSelected);
|
||||
}
|
||||
|
||||
private BoatModel boatAssets;
|
||||
private Group wake;
|
||||
private Color colour = Color.BLACK;
|
||||
private Boolean isSelected = false;
|
||||
private Rotate rotation = new Rotate(0, new Point3D(0,0,1));
|
||||
|
||||
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the default triangular boat polygon.
|
||||
*/
|
||||
public BoatObject(BoatMeshType boatMeshType) {
|
||||
boatAssets = ModelFactory.boatGameView(boatMeshType, colour);
|
||||
boatAssets.hideSail();
|
||||
boatAssets.getAssets().getTransforms().addAll(
|
||||
rotation
|
||||
);
|
||||
boatAssets.getAssets().setOnMouseClicked(event -> {
|
||||
setIsSelected(!isSelected);
|
||||
updateListeners();
|
||||
});
|
||||
boatAssets.getAssets().setCache(true);
|
||||
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
|
||||
super.getChildren().addAll(boatAssets.getAssets());
|
||||
}
|
||||
|
||||
public void setFill (Color value) {
|
||||
this.colour = value;
|
||||
boatAssets.changeColour(colour);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves the boat and its children annotations to coordinates specified
|
||||
* @param x The X coordinate to move the boat to
|
||||
* @param y The Y coordinate to move the boat to
|
||||
* @param rotation The rotation by which the boat moves
|
||||
* @param velocity The velocity the boat is moving
|
||||
* @param sailIn Boolean to toggle sail state.
|
||||
* @param windDir .
|
||||
*/
|
||||
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
||||
Platform.runLater(() -> {
|
||||
rotateTo(rotation, sailIn, windDir);
|
||||
this.layoutXProperty().setValue(x);
|
||||
this.layoutYProperty().setValue(y);
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
});
|
||||
}
|
||||
|
||||
private Double normalizeHeading(double heading, double windDirection) {
|
||||
Double normalizedHeading = heading - windDirection;
|
||||
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||
return normalizedHeading;
|
||||
}
|
||||
|
||||
|
||||
private void rotateTo(double heading, boolean sailsIn, double windDir) {
|
||||
rotation.setAngle(heading);
|
||||
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
|
||||
if (sailsIn) {
|
||||
boatAssets.showSail();
|
||||
Double sailWindOffset = 30.0;
|
||||
Double upwindAngleLimit = 15.0;
|
||||
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
||||
Double normalizedHeading = normalizeHeading(heading, windDir);
|
||||
if (normalizedHeading < 180) {
|
||||
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
||||
boatAssets.rotateSail(-upwindAngleLimit);
|
||||
} else if (normalizedHeading > 90 + sailWindOffset){
|
||||
boatAssets.rotateSail(-90 + downwindAngleLimit);
|
||||
} else {
|
||||
boatAssets.rotateSail(-heading + windDir + sailWindOffset);
|
||||
}
|
||||
} else {
|
||||
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)) {
|
||||
boatAssets.rotateSail(upwindAngleLimit);
|
||||
} else if (normalizedHeading < 270 - sailWindOffset) {
|
||||
boatAssets.rotateSail(90 - downwindAngleLimit);
|
||||
} else {
|
||||
boatAssets.rotateSail(-heading + windDir - sailWindOffset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boatAssets.hideSail();
|
||||
}
|
||||
}
|
||||
|
||||
public Group getWake () {
|
||||
return wake;
|
||||
}
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
private void updateListeners() {
|
||||
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
|
||||
sbl.notifySelected(this, this.isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSelectedBoatListener(SelectedBoatListener sbl) {
|
||||
selectedBoatListenerListeners.add(sbl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory.RoundingSide;
|
||||
|
||||
/**
|
||||
* Visual object for a mark. Contains a coloured circle and any specified arrows.
|
||||
*/
|
||||
public class Marker3D extends Group {
|
||||
|
||||
private Model mark;
|
||||
private List<Group> enterArrows = new ArrayList<>();
|
||||
private List<Group> exitArrows = new ArrayList<>();
|
||||
private int enterArrowIndex = 0;
|
||||
private int exitArrowIndex = 0;
|
||||
private ModelType markType;
|
||||
private ModelType arrowType;
|
||||
|
||||
/**
|
||||
* Creates a new Marker containing only a circle. The default colour is black.
|
||||
*/
|
||||
public Marker3D(ModelType modelType) {
|
||||
markType = modelType;
|
||||
switch (markType) {
|
||||
case PLAIN_MARKER:
|
||||
arrowType = ModelType.PLAIN_ARROW;
|
||||
break;
|
||||
case FINISH_MARKER:
|
||||
arrowType = ModelType.FINISH_ARROW;
|
||||
break;
|
||||
case START_MARKER:
|
||||
arrowType = ModelType.START_ARROW;
|
||||
break;
|
||||
}
|
||||
mark = ModelFactory.importModel(modelType);
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().addAll(mark.getAssets())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an exit and entry arrow pair to the mark. Arrows are hidden and shown in the order they
|
||||
* are created by calling showNextEnterArrow() or showNextExitArrow()
|
||||
* @param roundingSide the side the marker will be from the perspective of the arrow.
|
||||
* @param entryAngle The angle the arrow will point towards a marker
|
||||
* @param exitAngle The angle the arrow wil point from the marker.
|
||||
*/
|
||||
public void addArrows(RoundingSide roundingSide, double entryAngle,
|
||||
double exitAngle) {
|
||||
//Change Color.GRAY to this.colour to revert all gray arrows.
|
||||
enterArrows.add(
|
||||
MarkArrowFactory.constructEntryArrow3D(roundingSide, entryAngle, arrowType).getAssets()
|
||||
);
|
||||
exitArrows.add(
|
||||
MarkArrowFactory.constructExitArrow3D(roundingSide, exitAngle, arrowType).getAssets()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextEnterArrow() {
|
||||
showArrow(enterArrows, enterArrowIndex);
|
||||
enterArrowIndex++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextExitArrow() {
|
||||
showArrow(exitArrows, exitArrowIndex);
|
||||
exitArrowIndex++;
|
||||
}
|
||||
|
||||
private void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
if (arrowListIndex < arrowList.size()) {
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().setAll(mark.getAssets(), arrowList.get(arrowListIndex))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all arrows.
|
||||
*/
|
||||
public void hideAllArrows() {
|
||||
Platform.runLater(() -> this.getChildren().setAll(mark.getAssets()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.scene.Group;
|
||||
|
||||
/**
|
||||
* Class for generic imported 3D model. Animation terminates on if removed from scene.
|
||||
*/
|
||||
public class Model {
|
||||
|
||||
protected AnimationTimer animationTimer;
|
||||
protected Group assets;
|
||||
|
||||
public Model (Group assets, AnimationTimer animation) {
|
||||
this.assets = assets;
|
||||
this.animationTimer = animation;
|
||||
if (animation != null) {
|
||||
animation.start();
|
||||
assets.sceneProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal == null) {
|
||||
animationTimer.stop();
|
||||
animationTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void setAnimation(AnimationTimer animation) {
|
||||
animationTimer = animation;
|
||||
if (animation != null) {
|
||||
animation.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation of this model.
|
||||
*/
|
||||
public void stopAnimation() {
|
||||
if (animationTimer != null) {
|
||||
animationTimer.stop();
|
||||
animationTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Group getAssets() {
|
||||
return this.assets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import com.interactivemesh.jfx.importer.col.ColModelImporter;
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory class for creating 3D models of boatTypes.
|
||||
*/
|
||||
public class ModelFactory {
|
||||
|
||||
public static BoatModel boatIconView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(0,0,1));
|
||||
boatAssets.getTransforms().addAll(
|
||||
new Scale(3.3, 3.3, 3.3),
|
||||
new Rotate(-70, new Point3D(1,0,0)),
|
||||
new Translate(13,50, 0),
|
||||
animationRotate
|
||||
);
|
||||
|
||||
boatAssets.getTransforms().add(animationRotate);
|
||||
BoatModel bo = new BoatModel(boatAssets, null, boatType);
|
||||
bo.rotateSail(45);
|
||||
|
||||
bo.setAnimation(new AnimationTimer() {
|
||||
double boatAngle = 0;
|
||||
Rotate rotate = animationRotate;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
boatAngle += 0.5;
|
||||
rotate.setAngle(boatAngle);
|
||||
}
|
||||
});
|
||||
boatAssets.getChildren().addAll(
|
||||
new AmbientLight()
|
||||
);
|
||||
return bo;
|
||||
}
|
||||
|
||||
public static BoatModel boatCustomiseView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(0,0,1));
|
||||
boatAssets.getTransforms().addAll(
|
||||
new Scale(8.0, 8.0, 8.0),
|
||||
new Rotate(-70, new Point3D(1,0,0)),
|
||||
new Translate(16,50, 1),
|
||||
animationRotate
|
||||
);
|
||||
|
||||
boatAssets.getTransforms().add(animationRotate);
|
||||
BoatModel bo = new BoatModel(boatAssets, null, boatType);
|
||||
bo.rotateSail(45);
|
||||
|
||||
bo.setAnimation(new AnimationTimer() {
|
||||
double boatAngle = 0;
|
||||
Rotate rotate = animationRotate;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
boatAngle += 0.5;
|
||||
rotate.setAngle(boatAngle);
|
||||
}
|
||||
});
|
||||
boatAssets.getChildren().addAll(
|
||||
new AmbientLight()
|
||||
);
|
||||
return bo;
|
||||
}
|
||||
|
||||
public static BoatModel boatRotatingView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
boatAssets.getTransforms().addAll(
|
||||
new Scale(40, 40, 40),
|
||||
new Rotate(90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(0, 1, 0))
|
||||
);
|
||||
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(1,1,1));
|
||||
boatAssets.getTransforms().add(animationRotate);
|
||||
|
||||
return new BoatModel(boatAssets, new AnimationTimer() {
|
||||
|
||||
private double rotation = 0;
|
||||
private Rotate rotate = animationRotate;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
rotation += 0.5;
|
||||
rotate.setAngle(rotation);
|
||||
}
|
||||
}, boatType);
|
||||
}
|
||||
|
||||
public static BoatModel boatGameView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
boatAssets.getTransforms().setAll(
|
||||
new Scale(0.3, 0.3, 0.3)
|
||||
);
|
||||
return new BoatModel(boatAssets, null, boatType);
|
||||
}
|
||||
|
||||
private static Group getUnmodifiedBoatModel(BoatMeshType boatType, Color primaryColour) {
|
||||
|
||||
Group boatAssets = new Group();
|
||||
MeshView hull = importSTL(boatType.hullFile);
|
||||
hull.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView mast = importSTL(boatType.mastFile);
|
||||
mast.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView sail = importSTL(boatType.sailFile);
|
||||
sail.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
|
||||
if (boatType.jibFile != null) {
|
||||
MeshView jib = importSTL(boatType.jibFile);
|
||||
sail.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
boatAssets.getChildren().addAll(hull, mast, sail, jib);
|
||||
} else {
|
||||
boatAssets.getChildren().addAll(hull, mast, sail);
|
||||
}
|
||||
|
||||
return boatAssets;
|
||||
}
|
||||
|
||||
private static MeshView importSTL(String fileName) {
|
||||
StlMeshImporter importer = new StlMeshImporter();
|
||||
importer.read(ModelFactory.class.getResource("/meshes/boatSTLs/" + fileName));
|
||||
MeshView importedFile = new MeshView(importer.getImport());
|
||||
importedFile.setCache(true);
|
||||
importedFile.setCacheHint(CacheHint.SCALE_AND_ROTATE);
|
||||
return new MeshView(importer.getImport());
|
||||
}
|
||||
|
||||
public static Model importModel(ModelType tokenType) {
|
||||
Group assets;
|
||||
if (tokenType.filename == null) {
|
||||
assets = new Group();
|
||||
} else {
|
||||
ColModelImporter importer = new ColModelImporter();
|
||||
importer.read(ModelFactory.class.getResource("/meshes/" + tokenType.filename));
|
||||
assets = new Group(importer.getImport());
|
||||
assets.setCache(true);
|
||||
assets.setCacheHint(CacheHint.SCALE_AND_ROTATE);
|
||||
}
|
||||
switch (tokenType) {
|
||||
case VELOCITY_PICKUP:
|
||||
return makeCoinPickup(assets);
|
||||
case FINISH_MARKER:
|
||||
case PLAIN_MARKER:
|
||||
case START_MARKER:
|
||||
return makeMarker(assets);
|
||||
case OCEAN:
|
||||
return makeOcean(assets);
|
||||
case BORDER_PYLON:
|
||||
case BORDER_BARRIER:
|
||||
return makeBarrier(assets);
|
||||
case FINISH_LINE:
|
||||
case START_LINE:
|
||||
case GATE_LINE:
|
||||
return makeGate(assets);
|
||||
case WAKE:
|
||||
return makeWake(assets);
|
||||
case TRAIL_SEGMENT:
|
||||
return makeTrail(assets);
|
||||
case PLAYER_IDENTIFIER:
|
||||
return makeIdentifierIcon(assets);
|
||||
case START_ARROW:
|
||||
case FINISH_ARROW:
|
||||
case PLAIN_ARROW:
|
||||
makeArrow(assets);
|
||||
default:
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static Model makeCoinPickup(Group assets){
|
||||
assets.setRotationAxis(new Point3D(1,0,0));
|
||||
assets.setRotate(90);
|
||||
assets.setTranslateX(0.2);
|
||||
assets.setTranslateY(1);
|
||||
assets.getTransforms().addAll(
|
||||
new Translate(0,-1,0),
|
||||
new Rotate(0 ,new Point3D(1,1,1))
|
||||
);
|
||||
return new Model(new Group(assets), new AnimationTimer() {
|
||||
|
||||
private double rotation = 0;
|
||||
private Group group = assets;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
rotation += 1;
|
||||
((Rotate) group.getTransforms().get(1)).setAngle(rotation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Model makeMarker(Group marker) {
|
||||
ColModelImporter importer = new ColModelImporter();
|
||||
importer.read(ModelFactory.class.getResource("/meshes/" + ModelType.MARK_AREA.filename));
|
||||
Group area = new Group(importer.getImport());
|
||||
area.getChildren().add(marker);
|
||||
area.getTransforms().add(new Rotate(90, new Point3D(1, 0, 0)));
|
||||
return new Model(new Group(area), null);
|
||||
}
|
||||
|
||||
private static Model makeOcean(Group group) {
|
||||
Circle ocean = new Circle(
|
||||
0,0,250, Color.SKYBLUE
|
||||
);
|
||||
ocean.setStroke(Color.TRANSPARENT);
|
||||
group.getChildren().add(ocean);
|
||||
return new Model(new Group(group), null);
|
||||
}
|
||||
|
||||
private static Model makeBarrier(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
private static Model makeGate(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
private static Model makeWake(Group assets) {
|
||||
assets.getTransforms().setAll(
|
||||
new Rotate(-90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(1,0,0)),
|
||||
new Scale(0.5, 0.5, 0.5)
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
private static Model makeTrail(Group trailPiece) {
|
||||
trailPiece.getTransforms().addAll(
|
||||
new Rotate(-90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(trailPiece), null);
|
||||
}
|
||||
|
||||
private static Model makeIdentifierIcon(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0)),
|
||||
new Scale(0.5, 0.5, 0.5)
|
||||
);
|
||||
return new Model(assets, null);
|
||||
}
|
||||
|
||||
private static Model makeArrow(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
/**
|
||||
* Enum for models. Values should be the name of the file and files should be .dae files with texture
|
||||
* information included. Can be null in which case assets are assumed to be empty.
|
||||
*/
|
||||
public enum ModelType {
|
||||
|
||||
VELOCITY_PICKUP("velocity_pickup.dae"),
|
||||
FINISH_MARKER ("finish_marker.dae"),
|
||||
START_MARKER ("start_marker.dae"),
|
||||
PLAIN_MARKER ("plain_marker.dae"),
|
||||
MARK_AREA ("mark_area.dae"),
|
||||
OCEAN (null),
|
||||
BORDER_PYLON ("barrier_pole.dae"),
|
||||
BORDER_BARRIER ("barrier_segment.dae"),
|
||||
FINISH_LINE ("finish_line.dae"),
|
||||
START_LINE ("start_line.dae"),
|
||||
GATE_LINE ("gate_line.dae"),
|
||||
WAKE ("wake.dae"),
|
||||
TRAIL_SEGMENT ("trail_segment.dae"),
|
||||
PLAYER_IDENTIFIER ("player_identifier.dae"),
|
||||
PLAIN_ARROW ("arrow.dae"),
|
||||
START_ARROW ("start_arrow.dae"),
|
||||
FINISH_ARROW ("finish_arrow.dae");
|
||||
|
||||
final String filename;
|
||||
|
||||
ModelType(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by Fernflower decompiler)
|
||||
//
|
||||
|
||||
package seng302.visualiser.validators;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
@DefaultProperty("icon")
|
||||
public class FieldLengthValidator extends ValidatorBase {
|
||||
|
||||
Integer maxLength;
|
||||
|
||||
public FieldLengthValidator(Integer length) {
|
||||
maxLength = length;
|
||||
}
|
||||
|
||||
protected void eval() {
|
||||
if(this.srcControl.get() instanceof TextInputControl) {
|
||||
this.evalTextInputField();
|
||||
}
|
||||
}
|
||||
|
||||
private void evalTextInputField() {
|
||||
TextInputControl textField = (TextInputControl)this.srcControl.get();
|
||||
if(textField.getLength() > maxLength) {
|
||||
this.hasErrors.set(true);
|
||||
} else {
|
||||
this.hasErrors.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package seng302.visualiser.validators;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
@DefaultProperty("icon")
|
||||
public class HostNameFieldValidator extends ValidatorBase {
|
||||
|
||||
|
||||
public HostNameFieldValidator() {
|
||||
}
|
||||
|
||||
|
||||
protected void eval() {
|
||||
if(this.srcControl.get() instanceof TextInputControl) {
|
||||
this.evalTextInputField();
|
||||
}
|
||||
}
|
||||
|
||||
protected void evalTextInputField() {
|
||||
TextInputControl textField = (TextInputControl)this.srcControl.get();
|
||||
try{
|
||||
InetAddress.getByName(textField.getText());
|
||||
this.hasErrors.set(false);
|
||||
} catch (UnknownHostException e) {
|
||||
this.hasErrors.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package seng302.visualiser.validators;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
@DefaultProperty("icon")
|
||||
public class NumberRangeValidator extends ValidatorBase {
|
||||
|
||||
Integer lowerLimit;
|
||||
Integer upperLimit;
|
||||
|
||||
public NumberRangeValidator(Integer lower, Integer upper) {
|
||||
lowerLimit = lower;
|
||||
upperLimit = upper;
|
||||
}
|
||||
|
||||
|
||||
protected void eval() {
|
||||
if(this.srcControl.get() instanceof TextInputControl) {
|
||||
this.evalTextInputField();
|
||||
}
|
||||
}
|
||||
|
||||
protected void evalTextInputField() {
|
||||
TextInputControl textField = (TextInputControl)this.srcControl.get();
|
||||
try {
|
||||
Integer portNum = Integer.parseInt(textField.getText());
|
||||
if (lowerLimit <= portNum && portNum <= upperLimit) {
|
||||
this.hasErrors.set(false);
|
||||
} else {
|
||||
this.hasErrors.set(true);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
this.hasErrors.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package seng302.visualiser.validators;
|
||||
|
||||
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
|
||||
public class ValidationTools {
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Boolean validateTextField(JFXTextField textField) {
|
||||
textField.validate();
|
||||
for (ValidatorBase validator : textField.getValidators()) {
|
||||
if (validator.getHasErrors()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 92 KiB |
Binary file not shown.
@@ -0,0 +1,68 @@
|
||||
#serverListMainGridPane {
|
||||
-fx-background-color: -fx-pp-background-color;
|
||||
}
|
||||
|
||||
.scroll-pane {
|
||||
-fx-background: -fx-pp-background-color;
|
||||
-fx-border-style: hidden;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.scroll-pane:focused {
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.scroll-pane .corner {
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
#playerListVBox, #playerListScrollPane {
|
||||
-fx-background: transparent;
|
||||
}
|
||||
|
||||
#customizeButton, #leaveLobbyButton, #beginRaceButton {
|
||||
-fx-background-color: -fx-pp-light-text-color; /* inverted */
|
||||
-fx-text-fill: -fx-pp-theme-color; /* inverted */
|
||||
-fx-font-size: 18px;
|
||||
-fx-pref-height: 65px;
|
||||
}
|
||||
|
||||
#customizeButton:hover, #leaveLobbyButton:hover, #beginRaceButton:hover {
|
||||
-fx-font-size: 20px;
|
||||
}
|
||||
|
||||
.invertedButton .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
}
|
||||
|
||||
#connectLabel, #serverPortNumber, #serverHostName {
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#serverHostName, #serverPortNumber {
|
||||
-jfx-focus-color: -fx-pp-light-text-color;
|
||||
-jfx-unfocus-color: -fx-pp-light-text-color;
|
||||
}
|
||||
|
||||
#connectGridPane {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
#serverListMainGridPane {
|
||||
-fx-background-image: url("/images/waves.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: cover;
|
||||
}
|
||||
|
||||
#playerList, #playerListScrollPane {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
#serverMap {
|
||||
/*-fx-background-image: url("/images/mapbg.jpg");*/
|
||||
/*-fx-background-repeat: no-repeat;*/
|
||||
/*-fx-background-size: cover;*/
|
||||
-fx-background-color: dodgerblue;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
@font-face {
|
||||
src: url("Baloo-Regular.ttf");
|
||||
}
|
||||
|
||||
* {
|
||||
-fx-pp-light-text-color: #fff;
|
||||
-fx-pp-dark-text-color: #6c6c6c;
|
||||
-fx-pp-theme-color: #3493e3;
|
||||
-fx-pp-light-theme-color: #38a2fa;
|
||||
-fx-pp-background-color: transparent;
|
||||
-fx-pp-front-color: #fff;
|
||||
-fx-font-family: "Baloo";
|
||||
|
||||
-fx-pp-dropshadow-light: dropshadow(gaussian, rgba(0, 0, 0, 0.1), 10.0, 0.2, 3, 4);
|
||||
-fx-pp-dropshadow-dark: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 10.0, 0.2, 5, 6);
|
||||
-fx-pp-dropshadow-headers: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10.0, 0.2, 3, 4);
|
||||
}
|
||||
|
||||
/*GridPane .jfx-button {*/
|
||||
/*-fx-min-height: 65px; !* because we changed the font, we now need to set mini height for all buttons *!*/
|
||||
/*}*/
|
||||
|
||||
.jfx-button {
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-button:pressed {
|
||||
-fx-cursor: wait;
|
||||
}
|
||||
|
||||
.jfx-text-field {
|
||||
-jfx-label-float: true;
|
||||
}
|
||||
|
||||
.jfx-decorator {
|
||||
-fx-decorator-color: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
.jfx-decorator .jfx-decorator-buttons-container {
|
||||
-fx-background-color: -fx-decorator-color;
|
||||
}
|
||||
|
||||
.jfx-decorator .resize-border {
|
||||
-fx-border-color: -fx-decorator-color;
|
||||
-fx-border-width: 0 4 4 4;
|
||||
}
|
||||
/********* customised scroll bar for scroll pane ***********/
|
||||
|
||||
/* The main scrollbar **track** CSS class */
|
||||
.scroll-bar:horizontal .track,
|
||||
.scroll-bar:vertical .track {
|
||||
-fx-background-color: white;
|
||||
-fx-border-color: derive(-fx-pp-background-color, 50%);
|
||||
-fx-border-style: solid;
|
||||
-fx-border-width: 3px;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-border-radius: 0em;
|
||||
}
|
||||
|
||||
/* The increment and decrement button CSS class of scrollbar */
|
||||
.scroll-bar:horizontal .increment-button,
|
||||
.scroll-bar:horizontal .decrement-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-padding: 0 0 10 0;
|
||||
}
|
||||
|
||||
/* The increment and decrement button CSS class of scrollbar */
|
||||
|
||||
.scroll-bar:vertical .increment-button,
|
||||
.scroll-bar:vertical .decrement-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-padding: 0 10 0 10; /* set scroll bar width */
|
||||
|
||||
}
|
||||
|
||||
.scroll-bar .increment-arrow,
|
||||
.scroll-bar .decrement-arrow {
|
||||
-fx-shape: " ";
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */
|
||||
.scroll-bar:horizontal .thumb,
|
||||
.scroll-bar:vertical .thumb {
|
||||
-fx-background-color: derive(#c9c9cb, 0%);
|
||||
-fx-background-insets: 2, 0, 0;
|
||||
-fx-background-radius: 0em;
|
||||
}
|
||||
|
||||
.scroll-bar {
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.slider .thumb {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
.slider .track {
|
||||
-fx-background-color: -fx-pp-dark-text-color;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
@font-face {
|
||||
src: url("digital-7-mono.ttf");
|
||||
}
|
||||
|
||||
#timerGrid{
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
GridPane .timer * {
|
||||
-fx-font-family: "Digital-7 Mono" !important;
|
||||
-fx-font-size: 30;
|
||||
}
|
||||
|
||||
#timerLabel{
|
||||
-fx-font-size: 21px;
|
||||
}
|
||||
|
||||
#raceInfoArea{
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
#chatGridPane {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
#chatHistoryHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
#chatInputHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
#windGridPane {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
#windHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
#chatSend {
|
||||
-fx-background-color: -fx-pp-front-color;
|
||||
-fx-text-fill: -fx-pp-theme-color;
|
||||
-fx-font-size: 13px;
|
||||
-fx-pref-height: 35px;
|
||||
}
|
||||
|
||||
#chatSend:hover {
|
||||
-fx-font-size: 15px;
|
||||
}
|
||||
|
||||
#chatInput {
|
||||
-jfx-focus-color: -fx-pp-light-theme-color;
|
||||
-jfx-unfocus-color: -fx-pp-dark-text-color;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
#windImageView {
|
||||
-fx-image: url("/images/wind-180.png");
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#serverListMainGridPane {
|
||||
-fx-background-color: -fx-pp-background-color;
|
||||
}
|
||||
|
||||
.scroll-pane {
|
||||
-fx-background: -fx-pp-background-color;
|
||||
-fx-border-style: hidden;
|
||||
-fx-background-insets: 0;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.scroll-pane:focused {
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.scroll-pane .corner {
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
#serverListVBox, #serverListScrollPane {
|
||||
-fx-background: transparent;
|
||||
}
|
||||
|
||||
.serverListing {
|
||||
-fx-alignment: center;
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
#hostButton {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-font-size: 20px;
|
||||
-fx-pref-height: 65px;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
}
|
||||
|
||||
#hostButton:hover {
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
-fx-font-size: 23px;
|
||||
}
|
||||
|
||||
#connectButton {
|
||||
-fx-background-color: -fx-pp-light-text-color; /* inverted */
|
||||
-fx-text-fill: -fx-pp-theme-color; /* inverted */
|
||||
-fx-font-size: 20px;
|
||||
-fx-pref-height: 65px;
|
||||
}
|
||||
|
||||
#connectButton:hover {
|
||||
-fx-font-size: 23px;
|
||||
}
|
||||
|
||||
#connectLabel, #serverPortNumber, #serverHostName {
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 18px;
|
||||
}
|
||||
|
||||
#serverHostName, #serverPortNumber {
|
||||
-jfx-focus-color: -fx-pp-light-text-color;
|
||||
-jfx-unfocus-color: -fx-pp-light-text-color;
|
||||
-fx-prompt-text-fill: -fx-pp-light-text-color;
|
||||
}
|
||||
|
||||
|
||||
#serverHostName .error-label, #serverPortNumber .error-label {
|
||||
-fx-font-size: 12px;
|
||||
-fx-text-fill: lightblue;
|
||||
}
|
||||
|
||||
#connectGridPane {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
|
||||
#serverListMainGridPane{
|
||||
-fx-background-image: url("/images/waves.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: cover;
|
||||
}
|
||||
|
||||
#serverListPane{
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#startBtn {
|
||||
-fx-font-size: 20px;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
.jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-pp-light-theme-color; /* set rippler color for button */
|
||||
}
|
||||
|
||||
#startBtn:hover {
|
||||
-fx-font-size: 23px;
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
}
|
||||
|
||||
#mainStackPane {
|
||||
-fx-background-image: url("/images/waves.jpg");
|
||||
-fx-background-size: cover;
|
||||
}
|
||||
|
||||
#headText {
|
||||
-fx-background-color: transparent;
|
||||
-fx-font-size: 80px;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-effect: -fx-pp-dropshadow-headers;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#playerName {
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 18px;
|
||||
}
|
||||
|
||||
#playerListCell {
|
||||
-fx-background-color: skyblue;
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
#playerListCell:hover {
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
#playerCellVBox {
|
||||
-fx-background-color: -fx-pp-background-color;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#serverListCell {
|
||||
-fx-background-color: -fx-pp-front-color;
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
}
|
||||
|
||||
#serverListCell:hover {
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
#serverCellVBox {
|
||||
-fx-background-color: -fx-pp-background-color;
|
||||
}
|
||||
|
||||
#serverConnButton {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-font-size: 14px;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
}
|
||||
|
||||
#serverName, #serverPlayerCount, #mapName {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
#serverName {
|
||||
-fx-font-size: 20px;
|
||||
}
|
||||
|
||||
#serverPlayerCount, #mapName {
|
||||
-fx-font-size:14px;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#submitBtn {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 20px !important;
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
-fx-min-width: 130px;
|
||||
}
|
||||
|
||||
#submitBtn:hover {
|
||||
-fx-font-size: 23px !important;
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
}
|
||||
|
||||
#hostDialogHeader {
|
||||
-fx-font-size: 23px !important;
|
||||
}
|
||||
|
||||
#boatColorLabel, #colorPicker {
|
||||
-fx-font-size: 18px;
|
||||
}
|
||||
|
||||
#boatName, #boatColorLabel, #hostDialogHeader {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
#boatName {
|
||||
-fx-font-size: 18px;
|
||||
-fx-prompt-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
#boatName .error-label {
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#raceFinishLabel {
|
||||
-fx-font-size: 23px !important;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
#finishersList {
|
||||
|
||||
}
|
||||
|
||||
#playAgain {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 20px !important;
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
-fx-min-width: 130px;
|
||||
}
|
||||
|
||||
#playAgain:hover {
|
||||
-fx-font-size: 23px !important;
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#maxPlayersGridPane VBox * {
|
||||
-fx-font-family: monospace !important;
|
||||
}
|
||||
|
||||
#submitBtn {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 20px;
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
.jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-pp-light-theme-color; /* set rippler color for button */
|
||||
}
|
||||
|
||||
#submitBtn:hover {
|
||||
-fx-font-size: 23px;
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
}
|
||||
|
||||
#hostDialogHeader {
|
||||
-fx-font-size: 30px;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
|
||||
#serverName {
|
||||
-jfx-focus-color: -fx-pp-dark-text-color;
|
||||
-jfx-unfocus-color: -fx-pp-dark-text-color;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-prompt-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayersLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayerPromptLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
.maxPlayers {
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user