Merge branch 'master' into storyA_sounds

# Conflicts:
#	racevisionGame/src/main/java/mock/model/RaceServer.java
#	racevisionGame/src/main/java/visualiser/Controllers/HostController.java
#	racevisionGame/src/main/java/visualiser/Controllers/MainController.java
#	racevisionGame/src/main/java/visualiser/Controllers/TitleController.java
This commit is contained in:
Connor Taylor-Brown
2017-09-12 11:11:39 +12:00
119 changed files with 4467 additions and 1518 deletions
@@ -71,8 +71,10 @@ public class FinishController extends Controller {
//Winner label.
raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
if (boats.size() > 0) {
raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
raceWinnerLabel.setWrapText(true);
}
}
@@ -1,8 +1,13 @@
package visualiser.Controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.media.AudioClip;
import mock.app.Event;
import org.xml.sax.SAXException;
@@ -17,6 +22,8 @@ import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -36,11 +43,30 @@ public class HostController extends Controller {
@FXML
AnchorPane hostWrapper;
@FXML
Button previousButton;
@FXML
Button nextButton;
@FXML
ImageView mapImage;
private Event game;
private ArrayList<Image> listOfMaps;
private int currentMapIndex = 0;
@Override
public void initialize(URL location, ResourceBundle resources) {
public void initialize(URL location, ResourceBundle resources){
Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png"));
Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png"));
Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png"));
Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png"));
listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap));
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
/**
@@ -49,7 +75,7 @@ public class HostController extends Controller {
*/
public void hostGamePressed() throws IOException{
try {
this.game = new Event(false);
this.game = new Event(false, currentMapIndex);
connectSocket("localhost", 4942);
} catch (EventConstructionException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
@@ -82,9 +108,13 @@ public class HostController extends Controller {
* Hosts a game.
*/
public void hostGame(){
mapImage.fitWidthProperty().bind(((Stage) mapImage.getScene().getWindow()).widthProperty().multiply(0.6));
hostWrapper.setVisible(true);
}
/**
* Menu button pressed. Prompt alert then return to menu
*/
public void menuBtnPressed(){
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
@@ -92,4 +122,27 @@ public class HostController extends Controller {
parent.enterTitle();
}
public void nextImage(){
increaseIndex();
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
public void previousImage(){
decreaseIndex();
mapImage.setImage(listOfMaps.get(currentMapIndex));
}
private void increaseIndex(){
currentMapIndex = (currentMapIndex + 1)%listOfMaps.size();
}
private void decreaseIndex(){
currentMapIndex = ((((currentMapIndex - 1)%listOfMaps.size())+listOfMaps.size())%listOfMaps.size());
}
public void setGameType(int gameType){
this.currentMapIndex = gameType;
}
public int getGameType(){ return this.currentMapIndex; }
}
@@ -0,0 +1,247 @@
package visualiser.Controllers;
import javafx.application.Platform;
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.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static visualiser.app.App.keyFactory;
/**
* Controller for the scene used to display and update current key bindings.
*/
public class KeyBindingsController {
private @FXML Button btnSave;
private @FXML Button btnCancel;
private @FXML Button btnReset;
private @FXML ListView lstControl;
private @FXML ListView lstKey;
private @FXML ListView lstDescription;
private @FXML AnchorPane anchor;
private KeyFactory newKeyFactory;
private Boolean changed = false; // keyBindings have been modified
private Button currentButton = null; // last button clicked
public void initialize(){
// create new key factory to modify, keeping the existing one safe
newKeyFactory = copyExistingFactory();
initializeTable();
populateTable();
setKeyListener();
}
/**
* Sets up table before populating it.
* Set up includes headings, CSS styling and modifying default properties.
*/
public void initializeTable(){
// set the headings for each column
lstKey.getItems().add("Key");
lstControl.getItems().add("Command");
lstDescription.getItems().add("Description");
lstKey.getSelectionModel().select(0);
lstControl.getSelectionModel().select(0);
lstDescription.getSelectionModel().select(0);
// add CSS stylesheet once the scene has been created
lstKey.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
newScene.getStylesheets().add("/css/keyBindings.css");
}
});
// stop the columns from being selectable, so only the buttons are
lstKey.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstKey.getSelectionModel().select(0)));
lstDescription.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstDescription.getSelectionModel().select(0)));
lstControl.getSelectionModel().selectedItemProperty()
.addListener((observable, oldvalue, newValue) ->
Platform.runLater(() ->
lstControl.getSelectionModel().select(0)));
}
/**
* Populates the table with commands and their key binding details.
*/
public void populateTable(){
// add each command to the table
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) {
// create button for command
Button button = new Button(entry.getKey());
button.setMinWidth(120);
button.setId(entry.getValue().toString());
button.setOnAction(e -> currentButton = button);
// display details for command in table
lstControl.getItems().add(entry.getValue());
lstKey.getItems().add(button);
lstDescription.getItems().add(entry.getValue().getProtocolCode());
}
}
/**
* Makes a copy of the {@link KeyFactory} that does not modify the original.
* @return new keyfactory to be modified
*/
public KeyFactory copyExistingFactory(){
newKeyFactory = new KeyFactory();
Map<String, ControlKey> oldKeyState = keyFactory.getKeyState();
Map<String, ControlKey> newKeyState = new HashMap<>();
// copy over commands and their keys
for (Map.Entry<String, ControlKey> entry : oldKeyState.entrySet()){
newKeyState.put(entry.getKey(), entry.getValue());
}
newKeyFactory.setKeyState(newKeyState);
return newKeyFactory;
}
/**
* Creates a listener for the base anchorpane for key presses.
* It updates the current key bindings of the {@link KeyFactory} if
* required.
*/
public void setKeyListener(){
anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
// if esc, cancel current button click
if (event.getCode() == KeyCode.ESCAPE){
btnCancel.requestFocus();
currentButton = null;
}
// if a button was clicked
else if (currentButton != null) {
// check if a button is already mapped to this key
for (int i = 1; i < lstKey.getItems().size(); i++) {
Button button = (Button)lstKey.getItems().get(i);
// update buttons text and remove key binding from command
if (button.getText().equals(event.getCode().toString())) {
button.setText("");
newKeyFactory.updateKey(button.getId(), button.getId());
}
}
// update text on the button
currentButton.setText(event.getCode().toString());
// update the control key
newKeyFactory.updateKey(event.getCode().toString(),
currentButton.getId());
// remove current button selection
currentButton = null;
changed = true;
btnCancel.requestFocus();
}
event.consume();
});
}
/**
* Cancel and exits the key bindings menu. Changes are not forced to be
* saved or fixed if invalid, and instead are defaulted back to the last
* successful saved state.
*/
public void cancel(){
((Stage)btnCancel.getScene().getWindow()).close();
}
/**
* Resets all key bindings to the built-in defaults.
*/
public void reset(){
lstKey.getItems().clear();
lstControl.getItems().clear();
lstDescription.getItems().clear();
newKeyFactory = new KeyFactory();
initializeTable();
populateTable();
changed = true;
}
/**
* Replace existing {@link KeyFactory} with the modified key bindings.
*/
public void save(){
if (isFactoryValid()) {
keyFactory = newKeyFactory;
newKeyFactory = new KeyFactory();
changed = false;
keyFactory.save(); // save persistently
loadNotification("Key bindings were successfully saved.", false);
} else {
loadNotification("One or more key bindings are missing. " +
"Failed to save.", true);
}
}
/**
* Checks the {@link KeyFactory} being modified is valid and that no
* commands are missing a key binding.
* @return True if valid, false if invalid
*/
public Boolean isFactoryValid(){
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet
()) {
if (entry.getKey().toString()==entry.getValue().toString()){
return false;
}
}
return true;
}
/**
* Method used to stop a user from exiting key bindings without saving
* their changes to the {@link KeyFactory}.
* @param we {@link WindowEvent} close request to be consumed if settings
* have not been successfully saved.
*/
public void onExit(WindowEvent we){
// if modified KeyFactory hasn't been saved
if (changed){
loadNotification("Please cancel or save your changes before exiting" +
".", true);
we.consume();
}
}
/**
* Loads a popup window giving confirmation/warning of user activity.
* @param message the message to be displayed to the user
* @param warning true if the message to be displayed is due to user error
*/
public void loadNotification(String message, Boolean warning){
Parent root = null;
FXMLLoader loader = new FXMLLoader(getClass().getResource
("/visualiser/scenes/notification.fxml"));
try {
root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
NotificationController controller = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.centerOnScreen();
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
// displays given message in the window
controller.setMessage(message, warning);
}
}
@@ -22,7 +22,7 @@ import java.util.ResourceBundle;
public class LobbyController extends Controller {
@FXML
AnchorPane lobbyWrapper;
private AnchorPane lobbyWrapper;
@FXML
private TableView<RaceConnection> lobbyTable;
@FXML
@@ -43,6 +43,7 @@ public class MainController extends Controller {
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
* @param visualiserRace The object modelling the race.
* @param controllerClient Socket Client that manipulates the controller.
* @param isHost if the client is the host of a race or not.
*/
public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
raceController.startRace(visualiserRace, controllerClient, isHost);
@@ -97,6 +98,20 @@ public class MainController extends Controller {
*/
public void startCss(){titleController.setDayMode();}
/**
* host controller host a game
* @throws IOException throws exception
*/
public void beginGame() throws IOException {
hostController.hostGamePressed();
}
public void setGameType(int gameType){
hostController.setGameType(gameType);
}
public int getGameType(){ return hostController.getGameType(); }
/**
* Main Controller for the applications will house the menu and the displayed pane.
*
@@ -0,0 +1,35 @@
package visualiser.Controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
* Controller for a popup notification regarding user activity.
*/
public class NotificationController {
private @FXML Label lblDescription;
private @FXML Text txtMessage;
/**
* Closes the popup window once clicked.
*/
public void ok(){
((Stage)lblDescription.getScene().getWindow()).close();
}
/**
* Displays the appropriate popup notification.
* @param message message for the user
* @param warning if true warning text shown, if false success text shown
*/
public void setMessage(String message, Boolean warning){
lblDescription.setText(message);
if (!warning){
txtMessage.setText("Success!");
txtMessage.setFill(Color.GREEN);
}
}
}
@@ -1,6 +1,6 @@
package visualiser.Controllers;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.collections.FXCollections;
@@ -10,82 +10,81 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.RaceDataSource;
import shared.model.Leg;
import shared.model.Mark;
import visualiser.app.App;
import visualiser.enums.TutorialState;
import visualiser.gameController.ControllerClient;
import visualiser.gameController.Keys.ControlKey;
import visualiser.gameController.Keys.KeyFactory;
import visualiser.layout.Subject3D;
import visualiser.layout.View3D;
import visualiser.model.*;
import visualiser.utils.GPSConverter;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static visualiser.app.App.keyFactory;
/**
* Controller used to display a running race.
*/
public class RaceController extends Controller {
/**
* The race object which describes the currently occurring race.
*/
private VisualiserRaceEvent visualiserRace;
/**
* Service for sending keystrokes to server
*/
private ControllerClient controllerClient;
private boolean isHost;
/**
* The canvas that draws the race.
*/
private ResizableRaceCanvas raceCanvas;
private TutorialState currentState;
private ArrayList<TutorialState> tutorialStates;
private boolean isTutorial = false;
private String keyToPress;
/**
* The sparkline graph.
*/
private Sparkline sparkline;
/**
* state of the info table
*/
private boolean infoTableShow;
private View3D view3D;
private ObservableList<Subject3D> viewSubjects;
/**
* The arrow controller.
*/
@FXML private ArrowController arrowController;
@FXML private GridPane canvasBase;
@FXML private SplitPane racePane;
@FXML private SplitPane race;
@FXML private Label tutorialText;
/**
* This is the root node of the arrow control.
*/
@FXML private Pane arrow;
/**
* This is the pane we place the actual arrow control inside of.
@@ -101,9 +100,6 @@ public class RaceController extends Controller {
@FXML private TableColumn<VisualiserBoat, Leg> boatMarkColumn;
@FXML private TableColumn<VisualiserBoat, Number> boatSpeedColumn;
@FXML private LineChart<Number, Number> sparklineChart;
@FXML private AnchorPane annotationPane;
/**
* Ctor.
@@ -113,11 +109,10 @@ public class RaceController extends Controller {
@Override
public void initialize(URL location, ResourceBundle resources) {
KeyFactory keyFactory = KeyFactory.getFactory();
infoTableShow = true;
// Initialise keyboard handler
race.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
String codeString = event.getCode().toString();
if (codeString.equals("TAB")){toggleTable();}
@@ -126,13 +121,27 @@ public class RaceController extends Controller {
if(controlKey != null) {
try {
controlKey.onAction(); // Change key state if applicable
//Check if current race is a tutorial
if (isTutorial){
//Check if current tutorial state has the same boat protocol code as key press
if (controlKey.getProtocolCode().equals(currentState.getAction())){
//Update tutorial
checkTutorialState();
}
}
controllerClient.sendKey(controlKey);
event.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e);
} catch (Exception e) {
e.printStackTrace();
}
}
if(event.getCode() == KeyCode.ESCAPE) {
try {
@@ -143,7 +152,7 @@ public class RaceController extends Controller {
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
parent.endEvent();
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
} else {
@@ -152,7 +161,7 @@ public class RaceController extends Controller {
alert.setContentText("Do you wish to quit the race?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
race.setVisible(false);
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
}
@@ -176,14 +185,10 @@ public class RaceController extends Controller {
//Information table.
initialiseInfoTable(this.visualiserRace);
//Sparkline.
initialiseSparkline(this.visualiserRace);
//Arrow.
initialiseArrow(this.visualiserRace);
//Race canvas.
initialiseRaceCanvas(this.visualiserRace);
initialiseView3D(this.visualiserRace);
//Race timezone label.
initialiseRaceTimezoneLabel(this.visualiserRace);
@@ -191,11 +196,106 @@ public class RaceController extends Controller {
//Race clock.
initialiseRaceClock(this.visualiserRace);
//Start the race animation timer.
raceTimer();
}
private void initialiseView3D(VisualiserRaceEvent race) {
viewSubjects = FXCollections.observableArrayList();
// Import boat mesh
URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
StlMeshImporter importer = new StlMeshImporter();
importer.read(asset);
// Configure camera angles and control
view3D = new View3D();
view3D.setDistance(1050);
view3D.setYaw(0);
view3D.setPitch(60);
view3D.enableTracking();
//newPane.getChildren().add(view3D);
canvasBase.add(view3D, 0, 0);
// Set up projection from GPS to view
RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource();
final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450);
view3D.setItems(viewSubjects);
// Position and add each mark to view
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
Subject3D subject = new Subject3D(new Sphere(2));
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
viewSubjects.add(subject);
}
// Position and add each boat to view
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
MeshView mesh = new MeshView(importer.getImport());
Subject3D subject = new Subject3D(mesh);
viewSubjects.add(subject);
// Track this boat's movement with the new subject
AnimationTimer trackBoat = new AnimationTimer() {
@Override
public void handle(long now) {
subject.setHeading(boat.getBearing().degrees());
subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
}
};
trackBoat.start();
}
// Fix initial bird's-eye position
view3D.updatePivot(new Translate(250, 0, 210));
// Bind zooming to scrolling
view3D.setOnScroll(e -> {
view3D.updateDistance(e.getDeltaY());
});
// Bind zooming to keypress (Z/X default)
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
ControlKey key = keyFactory.getKey(e.getCode().toString());
if(key != null) {
switch (key.toString()) {
case "Zoom In":
//Check if race is a tutorial
if (isTutorial) {
//Check if the current tutorial state is zoom-in
if (currentState.equals(TutorialState.ZOOMIN)) {
try {
//Update tutorial
checkTutorialState();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
view3D.updateDistance(-10);
break;
case "Zoom Out":
//Check if race is a tutorial
if(isTutorial) {
//Check if current tutorial state is zoom-out
if (currentState.equals(TutorialState.ZOOMOUT)) {
try {
//Update tutorial
checkTutorialState();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
view3D.updateDistance(10);
break;
}
}
});
}
/**
@@ -338,42 +438,8 @@ public class RaceController extends Controller {
}
/**
* Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}.
* @param race The race to listen to.
*/
private void initialiseSparkline(VisualiserRaceEvent race) {
//The race.getBoats() we are passing in is sorted by position in race inside the race class.
this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart);
}
/**
* Initialises the {@link ResizableRaceCanvas}, provides the race to read data from.
* @param race Race to read data from.
*/
private void initialiseRaceCanvas(VisualiserRaceEvent race) {
//Create canvas.
raceCanvas = new ResizableRaceCanvas(race);
//Set properties.
raceCanvas.setMouseTransparent(true);
raceCanvas.widthProperty().bind(canvasBase.widthProperty());
raceCanvas.heightProperty().bind(canvasBase.heightProperty());
//Draw it and show it.
raceCanvas.draw();
raceCanvas.setVisible(true);
//Add to scene.
canvasBase.getChildren().add(0, raceCanvas);
}
/**
* Intialises the race time zone label with the race's time zone.
* Initialises the race time zone label with the race's time zone.
* @param race The race to get time zone from.
*/
private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) {
@@ -408,14 +474,29 @@ public class RaceController extends Controller {
this.controllerClient = controllerClient;
this.isHost = isHost;
//Check if the game is a tutorial
if (parent.getGameType()==4){
isTutorial = true;
tutorialText.setVisible(true);
tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values()));
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
searchMapForKey("Upwind");
tutorialText.setText("Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " + keyToPress + " to turn upwind.");
} else {
isTutorial = false;
tutorialText.setVisible(false);
}
initialiseRace();
//Display this controller.
race.setVisible(true);
// set up annotation displays
new Annotations(annotationPane, raceCanvas);
racePane.setVisible(true);
}
/**
@@ -423,7 +504,7 @@ public class RaceController extends Controller {
* @param boats boats there are in the race.
*/
public void finishRace(ObservableList<VisualiserBoat> boats) {
race.setVisible(false);
racePane.setVisible(false);
parent.enterFinish(boats);
}
@@ -458,18 +539,13 @@ public class RaceController extends Controller {
finishRace(visualiserRace.getVisualiserRaceState().getBoats());
} else {
//Otherwise, render the canvas.
raceCanvas.drawRace();
//Sort the tableview. Doesn't automatically work for all columns.
boatInfoTable.sort();
}
//Return to main screen if we lose connection.
if (!visualiserRace.getServerConnection().isAlive()) {
race.setVisible(false);
racePane.setVisible(false);
//parent.enterTitle();
try {
App.app.showMainStage(App.getStage());
@@ -491,10 +567,10 @@ public class RaceController extends Controller {
* toggles if the info table is shown
*/
private void toggleTable() {
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth();
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth();
if (infoTableShow){
race.setDividerPositions(tablePercent);
racePane.setDividerPositions(tablePercent);
arrowPane.setScaleX(0.5);
arrowPane.setScaleY(0.5);
@@ -502,7 +578,7 @@ public class RaceController extends Controller {
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
}else{
race.setDividerPositions(1);
racePane.setDividerPositions(1);
arrowPane.setScaleX(1);
arrowPane.setScaleY(1);
@@ -514,4 +590,108 @@ public class RaceController extends Controller {
infoTableShow = !infoTableShow;
}
/**
* Get the next tutorial state
*/
private void updateTutorialState(){
//Next tutorial state is popped from list
currentState = tutorialStates.get(0);
tutorialStates.remove(0);
}
/**
* Search key map for key given string of command
* @param command the command of the key
*/
private void searchMapForKey(String command){
//For loop through keyFactory
for (Map.Entry<String, ControlKey> entry: keyFactory.getKeyState().entrySet()){
if(entry.getValue().toString().equals(command)){
//Found next key required to press
keyToPress = entry.getKey();
}
}
}
/**
* Updates tutorial state and gui display for tutorial text
* @throws Exception Exception thrown
*/
private void checkTutorialState() throws Exception {
//Switch statement to check what the current tutorial state is
switch (currentState){
case UPWIND:
//Set next key to press as the downwind key
searchMapForKey("Downwind");
//Update tutorial text
tutorialText.setText("Nice! To turn downwind press " + keyToPress + ".");
updateTutorialState();
break;
case DOWNWIND:
//Set next key to press as the tack/gybe key
searchMapForKey("Tack/Gybe");
//Update tutorial text
tutorialText.setText("Nice! To tack or gybe press " + keyToPress + ".");
updateTutorialState();
break;
case TACKGYBE:
//Set next key to press as the VMG key
searchMapForKey("VMG");
//Update tutorial text
tutorialText.setText("Nice! To use VMG press " + keyToPress + ". This will turn the boat.");
updateTutorialState();
break;
case VMG:
//Set next key to press as the sails-in key
searchMapForKey("Toggle Sails");
//Update tutorial text
tutorialText.setText("Nice! To sails in press " + keyToPress + ". This will stop the boat.");
updateTutorialState();
break;
case SAILSIN:
//Set next key to press as the sails-out key
searchMapForKey("Toggle Sails");
//Update tutorial text
tutorialText.setText("Nice! To sails out press " + keyToPress + " again. The will start moving again.");
updateTutorialState();
break;
case SAILSOUT:
//Set next key to press as the zoom-in key
searchMapForKey("Zoom In");
//Update tutorial text
tutorialText.setText("Nice! To zoom in press " + keyToPress + ".");
updateTutorialState();
break;
case ZOOMIN:
//Set next key to press as the zoom-out key
searchMapForKey("Zoom Out");
//Update tutorial text
tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + ".");
updateTutorialState();
break;
case ZOOMOUT:
//Finished tutorial. Display pop-up for exiting the tutorial
tutorialText.setText("Congratulations! You're done!");
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Finished Tutorial");
alert.setHeaderText("You have finished the tutorial.");
alert.setContentText("Now you know the controls you are ready to race!");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
parent.endEvent();
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
}
break;
default:
//State not found. Exit tutorial to title menu
parent.endEvent();
racePane.setVisible(false);
App.app.showMainStage(App.getStage());
break;
}
}
}
@@ -86,9 +86,6 @@ public class StartController extends Controller {
/**
* Ctor.
*/
@@ -218,31 +215,30 @@ public class StartController extends Controller {
* Countdown timer until race starts.
*/
private void countdownTimer() {
new AnimationTimer() {
@Override
public void handle(long arg0) {
new AnimationTimer() {
@Override
public void handle(long arg0) {
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Get the current race status.
RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum();
//Display it.
raceStatusLabel.setText("Race Status: " + raceStatus.name());
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.WARNING
|| raceStatus == RaceStatusEnum.PREPARATORY
|| raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
startWrapper.setVisible(false);
//start.setVisible(false);//TODO is this needed?
//If the race has reached the preparatory phase, or has started...
if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) {
//Stop this timer.
stop();
//Hide this, and display the race controller.
startWrapper.setVisible(false);
//start.setVisible(false);//TODO is this needed?
parent.beginRace(visualiserRaceEvent, controllerClient, isHost);
parent.beginRace(visualiserRaceEvent, controllerClient, isHost);
}
}
}
}.start();
}.start();
}
@@ -1,17 +1,25 @@
package visualiser.Controllers;
import javafx.event.EventHandler;
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.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Modality;
import javafx.stage.Stage;
import mock.app.Event;
import mock.exceptions.EventConstructionException;
import javafx.stage.WindowEvent;
import visualiser.app.App;
import java.io.File;
@@ -19,6 +27,8 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Controller for the opening title window.
@@ -36,6 +46,12 @@ public class TitleController extends Controller {
RadioButton dayModeRD;
@FXML
RadioButton nightModeRD;
@FXML
Label tutorialLabel;
@FXML
Pane menuPane;
@FXML
ImageView imgSun;
/**
* Method called when the 'host a game' button is pressed.
@@ -44,8 +60,8 @@ public class TitleController extends Controller {
* @throws IOException if main has problems
*/
public void hostAGame() throws IOException, URISyntaxException {
titleWrapper.setVisible(false);
parent.setGameType(0);
parent.hostGame();
App.getStage().setResizable(true);
}
@@ -72,7 +88,10 @@ public class TitleController extends Controller {
*/
public void setDayMode(){
dayModeRD.getScene().getStylesheets().clear();
menuPane.getStylesheets().clear();
imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sun.png").toExternalForm()));
dayModeRD.getScene().getStylesheets().add("/css/dayMode.css");
menuPane.setStyle("-fx-background-color: #6be6ff;");
nightModeRD.setSelected(false);
}
@@ -81,12 +100,16 @@ public class TitleController extends Controller {
*/
public void setNightMode(){
nightModeRD.getScene().getStylesheets().clear();
menuPane.getStylesheets().clear();
imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sunsleep.png").toExternalForm()));
nightModeRD.getScene().getStylesheets().add("/css/nightMode.css");
menuPane.setStyle("-fx-background-color: #1f2c60;");
dayModeRD.setSelected(false);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
tutorialLabel.setWrapText(true);
}
@@ -94,23 +117,39 @@ public class TitleController extends Controller {
* Called when control button is pressed. New pop up window displaying controls
*/
public void controlBtnPressed(){
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml"));
Parent layout;
try {
layout = loader.load();
AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
sound.play();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml"));
Parent layout = loader.load();
Scene scene = new Scene(layout);
Stage popupStage = new Stage();
popupStage.setResizable(false);
popupStage.setTitle("Game Controls");
popupStage.initModality(Modality.WINDOW_MODAL);
popupStage.centerOnScreen();
popupStage.setScene(scene);
popupStage.showAndWait();
popupStage.show();
KeyBindingsController controller = loader.getController();
popupStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) {
controller.onExit(we);
}
}
});
} catch (Exception e){
e.printStackTrace();
}
}
public void tutorialStartPressed() throws IOException {
titleWrapper.setVisible(false);
parent.setGameType(4);
parent.beginGame();
}
}