diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 1a70b65c..11553a07 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -66,6 +66,7 @@ public class Event { * Constructs an event, using various XML files. * @param singlePlayer Whether or not to create a single player event. * @param mapIndex Specifies which map to use. + * @param raceLength The length of the race, in milliseconds. * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ public Event(boolean singlePlayer, int mapIndex, int raceLength) throws diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java index a5dc98fe..5bf55d50 100644 --- a/racevisionGame/src/main/java/mock/model/MockBoat.java +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -27,7 +27,7 @@ public class MockBoat extends Boat { * 1: passed only first check * 2: passed first and second check */ - private Integer roundingStatus = 0; + private int roundingStatus = 0; /** * Stores whether the boat is on autoVMG or not @@ -284,8 +284,8 @@ public class MockBoat extends Boat { (this.isStarboardSide(mark1) && this.isPortSide(mark2)); } - public Integer getRoundingStatus() { - return Integer.valueOf(roundingStatus); + public int getRoundingStatus() { + return roundingStatus; } public void increaseRoundingStatus() { diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 43945a46..a0140189 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -419,6 +419,11 @@ public class MockRace extends RaceState { boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds); } + // Remove one unit of health for every frame spent outside boundary + if(!finish && !GPSCoordinate.isInsideBoundary(boat.getPosition(), getBoundary())) { + boat.updateHealth(-0.1); + } + this.updateEstimatedTime(boat); } @@ -430,7 +435,7 @@ public class MockRace extends RaceState { boat.getBearing() ), boat.getBearing()) ; if (vmg.getSpeed() > 0) { - boat.setCurrentSpeed(vmg.getSpeed()); + boat.setCurrentSpeed(vmg.getSpeed() * Math.pow(boat.getHealth() / 100, 0.3)); } } @@ -677,7 +682,7 @@ public class MockRace extends RaceState { for (MockBoat boat : this.boats) { //If the boat is currently racing, count it. - if (boat.getStatus() == BoatStatusEnum.RACING) { + if (boat.getStatus() == BoatStatusEnum.RACING && boat.getHealth()>=1) { numberOfActiveBoats++; } diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index 8814733a..4b974850 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -163,7 +163,7 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { //As long as there is at least one boat racing, we still simulate the race. if (race.getNumberOfActiveBoats() != 0) { - + //System.out.println(race.getNumberOfActiveBoats()); //Get the time period of this frame. long framePeriod = currentTime - previousFrameTime; @@ -175,12 +175,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli()); if(race.getColliderRegistry().rayCast(boat)){ - //System.out.println("Collision!"); //Add boat to list collisionBoats.add(boat); } - - //System.out.println(race.getColliderRegistry().rayCast(boat)); } diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index 103e4d66..52c4448c 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -64,6 +64,9 @@ public class RaceServer { //Parse the boat locations. snapshotMessages.addAll(parseBoatLocations()); + //Parse the boat states + snapshotMessages.addAll(parseBoatStates()); + //Parse the marks. snapshotMessages.addAll(parseMarks()); @@ -243,7 +246,29 @@ public class RaceServer { } + /** + * Generates BoatState messages for every boat in the race + * @return list of BoatState messages + */ + private List parseBoatStates() { + List boatStates = new ArrayList<>(); + for(MockBoat boat: race.getBoats()) { + boatStates.add(parseIndividualBoatState(boat)); + } + return boatStates; + } + /** + * Creates a BoatState message for the current state of the given boat + * @param boat to generate message for + * @return BoatState message + */ + private BoatState parseIndividualBoatState(MockBoat boat) { + return new BoatState( + boat.getSourceID(), + (int)boat.getHealth() + ); + } /** * Parses the race status, and returns it. diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index 502e6ab6..af7fbe45 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -177,10 +177,10 @@ public class RaceXMLCreator { } /** - * gets the destance between two marks + * gets the destance between two marks, in nautical miles. * @param a mark 1 * @param b mark 2 - * @return + * @return Distance in nautical miles. */ private static double getDistance(XMLMark a, XMLMark b){ GPSCoordinate coorda = new GPSCoordinate(a.getTargetLat(), a.getTargetLng()); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStateDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStateDecoder.java new file mode 100644 index 00000000..e543f464 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStateDecoder.java @@ -0,0 +1,38 @@ +package network.MessageDecoders; + +import network.Exceptions.InvalidMessageException; +import network.Messages.AC35Data; +import network.Messages.BoatState; + +import java.util.Arrays; + +import static network.Utils.ByteConverter.bytesToInt; + +/** + * Decoder for {@link BoatState} messages + */ +public class BoatStateDecoder implements MessageDecoder { + /** + * Decoded BoatState message + */ + private BoatState message; + + @Override + public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException { + byte[] sourceID = Arrays.copyOfRange(encodedMessage, 0, 4); + byte boatHealth = encodedMessage[4]; + + // Unpack bytes into BoatState + this.message = new BoatState( + bytesToInt(sourceID), + boatHealth + ); + + // Return BoatState + return this.message; + } + + public BoatState getMessage() { + return message; + } +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java index 24caf97c..3b8964c0 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/DecoderFactory.java @@ -59,6 +59,7 @@ public class DecoderFactory { case BOATACTION: return new BoatActionDecoder(); + case BOATSTATE: return new BoatStateDecoder(); default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); } diff --git a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java index c20c653f..ed8ba21f 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MessageDecoder.java @@ -18,6 +18,5 @@ public interface MessageDecoder { * @return The decoded message. * @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded. */ - public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException; - + AC35Data decode(byte[] encodedMessage) throws InvalidMessageException; } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStateEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStateEncoder.java new file mode 100644 index 00000000..e897f18c --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStateEncoder.java @@ -0,0 +1,32 @@ +package network.MessageEncoders; + +import network.Exceptions.InvalidMessageException; +import network.Messages.AC35Data; +import network.Messages.BoatState; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; + +/** + * Encoder for {@link BoatState} message + */ +public class BoatStateEncoder implements MessageEncoder { + @Override + public byte[] encode(AC35Data message) throws InvalidMessageException { + // Downcast message + BoatState boatState = (BoatState)message; + + //Serialise message + byte[] sourceID = intToBytes(boatState.getSourceID()); + byte boatHealth = (byte)boatState.getBoatHealth(); + + // Pack bytes into string + ByteBuffer boatStateMessage = ByteBuffer.allocate(5); + boatStateMessage.put(sourceID); + boatStateMessage.put(boatHealth); + + // Return byte string + return boatStateMessage.array(); + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index d274f435..42093111 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -59,6 +59,7 @@ public class EncoderFactory { case BOATACTION: return new BoatActionEncoder(); + case BOATSTATE: return new BoatStateEncoder(); default: throw new InvalidMessageTypeException("Unrecognised message type: " + type); } diff --git a/racevisionGame/src/main/java/network/Messages/BoatState.java b/racevisionGame/src/main/java/network/Messages/BoatState.java new file mode 100644 index 00000000..f40a5757 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/BoatState.java @@ -0,0 +1,31 @@ +package network.Messages; + +import network.Messages.Enums.MessageType; + +/** + * Represents the information in a BoatState message according to protocol meeting + */ +public class BoatState extends AC35Data { + /** + * Source ID of boat described in message + */ + private int sourceID; + /** + * Health between 0-100 of boat with above source ID + */ + private int boatHealth; + + public BoatState(int sourceID, int boatHealth) { + super(MessageType.BOATSTATE); + this.sourceID = sourceID; + this.boatHealth = boatHealth; + } + + public int getSourceID() { + return sourceID; + } + + public int getBoatHealth() { + return boatHealth; + } +} diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 0941fd08..15f9e80e 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -43,6 +43,8 @@ public enum MessageType { HOSTED_GAMES_REQUEST(109), + BOATSTATE(103), + NOTAMESSAGE(0); @@ -56,7 +58,7 @@ public enum MessageType { * Creates a MessageType enum from a given primitive integer value, cast to a byte. * @param value Integer, which is cast to byte, to construct from. */ - private MessageType(int value) { + MessageType(int value) { this.value = (byte)value; } diff --git a/racevisionGame/src/main/java/shared/model/Boat.java b/racevisionGame/src/main/java/shared/model/Boat.java index edbba025..99871678 100644 --- a/racevisionGame/src/main/java/shared/model/Boat.java +++ b/racevisionGame/src/main/java/shared/model/Boat.java @@ -103,6 +103,11 @@ public class Boat extends Collider { */ private boolean isColliding = false; + /** + * Amount of health boat currently has, between 0 and 100 + */ + private double health; + /** * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. * @@ -113,6 +118,7 @@ public class Boat extends Collider { public Boat(int sourceID, String name, String country) { this.sourceID = sourceID; + this.health = 100; this.setName(name); this.setCountry(country); @@ -419,6 +425,8 @@ public class Boat extends Collider { @Override public void onCollisionEnter(Collision e) { if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) { + // Deplete health + e.getBoat().updateHealth(-5); // Notify observers of collision this.setChanged(); notifyObservers(e); @@ -432,4 +440,22 @@ public class Boat extends Collider { public void setColliding(boolean colliding) { isColliding = colliding; } + + public double getHealth() { + return health; + } + + public void setHealth(double health) { + this.health = health; + } + + /** + * Add a given amount of HP to boat health + * @param delta amount of HP to add + */ + public void updateHealth(double delta) { + health += delta; + if(health < 0) health = 0; + else if(health > 100) health = 100; + } } diff --git a/racevisionGame/src/main/java/shared/model/Mark.java b/racevisionGame/src/main/java/shared/model/Mark.java index fd46112f..83ee1168 100644 --- a/racevisionGame/src/main/java/shared/model/Mark.java +++ b/racevisionGame/src/main/java/shared/model/Mark.java @@ -97,6 +97,7 @@ public class Mark extends Collider{ @Override public void onCollisionEnter(Collision e) { + e.getBoat().updateHealth(-10); this.setChanged(); notifyObservers(e); } diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatStateCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatStateCommand.java new file mode 100644 index 00000000..85a1e00e --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatStateCommand.java @@ -0,0 +1,30 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.BoatState; +import shared.exceptions.BoatNotFoundException; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceState; + +/** + * Updates boats on visualiser when their health changes + */ +public class BoatStateCommand implements Command { + private BoatState boatState; + private VisualiserRaceState visualiserRace; + + public BoatStateCommand(BoatState boatState, VisualiserRaceState visualiserRace) { + this.boatState = boatState; + this.visualiserRace = visualiserRace; + } + + @Override + public void execute() { + try { + VisualiserBoat boat = visualiserRace.getBoat(boatState.getSourceID()); + boat.setHealth(boatState.getBoatHealth()); + } catch (BoatNotFoundException e) { + // Fail silently + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java index 0e2f3d15..825d3397 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java @@ -24,7 +24,6 @@ public class VisualiserRaceCommandFactory { switch (message.getType()) { case BOATLOCATION: - //System.out.println("Boat location received"); return new BoatLocationCommand((BoatLocation) message, visualiserRace); case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace); @@ -36,7 +35,8 @@ public class VisualiserRaceCommandFactory { case YACHTEVENTCODE: return new BoatCollisionCommand((YachtEvent) message, visualiserRace); - + case BOATSTATE: + return new BoatStateCommand((BoatState) message, visualiserRace); default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType()); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java index c752a7bc..72c564f2 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java @@ -15,12 +15,15 @@ import javafx.scene.AmbientLight; import javafx.scene.PointLight; import javafx.scene.chart.LineChart; import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; 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.media.AudioClip; import javafx.scene.paint.Color; import javafx.scene.paint.Material; import javafx.scene.paint.PhongMaterial; @@ -30,6 +33,7 @@ import javafx.scene.shape.MeshView; import javafx.scene.shape.Shape3D; import javafx.scene.transform.Translate; import javafx.util.Callback; +import network.Messages.Enums.BoatStatusEnum; import network.Messages.Enums.RaceStatusEnum; import shared.dataInput.RaceDataSource; import shared.exceptions.BoatNotFoundException; @@ -44,6 +48,7 @@ import visualiser.model.*; import visualiser.utils.GPSConverter; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.*; import java.util.logging.Level; @@ -72,6 +77,9 @@ public class RaceViewController extends Controller { private long positionTime = 0; private ResizableRaceCanvas raceCanvas; private boolean mapToggle = true; + private GPSConverter gpsConverter; + private ArrayList healthEffectList = new ArrayList<>(); + /** * Arrow pointing to next mark in third person @@ -99,12 +107,17 @@ public class RaceViewController extends Controller { private @FXML TableColumn boatTeamColumn; private @FXML TableColumn boatMarkColumn; private @FXML TableColumn boatSpeedColumn; + private @FXML TableColumn boatHealthColumn; private @FXML LineChart sparklineChart; private @FXML Label tutorialText; private @FXML AnchorPane infoWrapper; private @FXML AnchorPane lineChartWrapper; private @FXML StackPane speedPane; private @FXML AnchorPane raceAnchorPane; + private @FXML GridPane playerHealthContainer; + private @FXML ImageView imageView; + private @FXML AnchorPane deathTransPane; + private @FXML StackPane deathPane; /** * Displays a specified race. @@ -119,11 +132,13 @@ public class RaceViewController extends Controller { this.controllerClient = controllerClient; this.isHost = isHost; keyFactory.load(); - + deathPane.setDisable(false); + deathPane.setVisible(false); tutorialCheck(); initKeypressHandler(); initialiseRaceVisuals(); initialiseRaceCanvas(); + healthLoop(); } /** @@ -135,7 +150,6 @@ public class RaceViewController extends Controller { isTutorial = true; tutorialText.setVisible(true); tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values())); - currentState = tutorialStates.get(0); tutorialStates.remove(0); searchMapForKey("Upwind"); @@ -296,6 +310,7 @@ public class RaceViewController extends Controller { initialiseFps(); initialiseInfoTable(); initialiseView3D(this.visualiserRace); + initialiseHealthPane(); initialiseRaceClock(); initialiseSpeedometer(); raceTimer(); // start the timer @@ -306,6 +321,23 @@ public class RaceViewController extends Controller { arrowController.setWindProperty(this.raceState.windProperty()); } + + + private void initialiseHealthPane() { + InputStream tomato = this.getClass().getClassLoader().getResourceAsStream("visualiser/images/tomato.png"); + HealthSlider healthSlider = new HealthSlider(new Image(tomato)); + playerHealthContainer.add(healthSlider, 0, 0); + + try { + VisualiserBoat player = raceState.getBoat(raceState.getPlayerBoatID()); + player.healthProperty().addListener((o, prev, curr) -> { + healthSlider.setCrop((double)curr/100.0); + }); + } catch (BoatNotFoundException e) { + e.printStackTrace(); + } + } + private void initialiseView3D(VisualiserRaceEvent race) { viewSubjects = FXCollections.observableArrayList(); @@ -353,7 +385,7 @@ public class RaceViewController extends Controller { // Set up projection from GPS to view RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); - final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); + gpsConverter = new GPSConverter(raceData, 450, 450); SkyBox skyBox = new SkyBox(750, 200, 250, 0, 210); viewSubjects.addAll(skyBox.getSkyBoxPlanes()); @@ -429,6 +461,11 @@ public class RaceViewController extends Controller { viewSubjects.add(boatModel); } + //Create health effect + HealthEffect healthEffect = new HealthEffect(boat.getSourceID(), System.currentTimeMillis()); + viewSubjects.add(healthEffect); + healthEffectList.add(healthEffect); + //add sail Sails3D sails3D = new Sails3D(); Subject3D sailsSubject = new Subject3D(sails3D, 0); @@ -509,6 +546,14 @@ public class RaceViewController extends Controller { boatModel.setHeading(boat.getBearing().degrees()); boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX()); boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); + boatModel.getMesh().toFront(); + + //Fire follows boat + healthEffect.setHeading(boat.getBearing().degrees()); + healthEffect.setX(gpsConverter.convertGPS(boat.getPosition()).getX()); + healthEffect.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); + healthEffect.setY(0); + } }; trackBoat.start(); @@ -701,13 +746,17 @@ public class RaceViewController extends Controller { // set table data boatInfoTable.setItems(sortedBoats); boatTeamColumn.setCellValueFactory( - cellData -> cellData.getValue().nameProperty()); + cellData -> cellData.getValue().nameProperty() + ); boatSpeedColumn.setCellValueFactory( - cellData -> cellData.getValue().currentSpeedProperty()); + cellData -> cellData.getValue().currentSpeedProperty() + ); boatMarkColumn.setCellValueFactory( - cellData -> cellData.getValue().legProperty()); - boatPlacingColumn.setCellValueFactory( - cellData -> cellData.getValue().placingProperty()); + cellData -> cellData.getValue().legProperty() + ); + boatHealthColumn.setCellValueFactory( + cellData -> cellData.getValue().healthProperty() + ); //Kind of ugly, but allows for formatting an observed speed. boatSpeedColumn.setCellFactory( @@ -839,6 +888,64 @@ public class RaceViewController extends Controller { }.start(); } + /** + * Animation timer for health + */ + private void healthLoop(){ + new AnimationTimer(){ + @Override + public void handle(long arg0){ + if (raceState.getRaceStatusEnum() == RaceStatusEnum.FINISHED) { + stop(); // stop the timer + } else { + try { + //Check if boat is dead + if(raceState.getBoat(raceState.getPlayerBoatID()).getHealth()<=0){ + if(!deathPane.isDisable()) { + deathPane.setVisible(true); + } + } + } catch (BoatNotFoundException e) { + e.printStackTrace(); + } + + for(VisualiserBoat boat : raceState.getBoats()){ + for (HealthEffect fp : healthEffectList){ + + if(fp.getSourceID()==boat.getSourceID()){ + if(boat.getHealth()<=0){ + //Boat is dead. Don't check it anymore for hp + fp.displayDeath(fp.getSourceID()==raceState.getPlayerBoatID()); + fp.setSourceID(0); + try { + raceState.getBoat(boat.getSourceID()).setStatus(BoatStatusEnum.DNF); + + raceState.updateBoatPositions(raceState.getBoats()); + } catch (BoatNotFoundException e) { + e.printStackTrace(); + } + } + else + //Speed up tick when <=10 hp + if(boat.getHealth()<=10){ + fp.flash(System.currentTimeMillis(), 300, boat.getSourceID()==raceState.getPlayerBoatID()); + } + else + //Visual indication of low hp + if(boat.getHealth()<=20) { + //fp.setVisible(true); + fp.flash(System.currentTimeMillis(), 500, boat.getSourceID()==raceState.getPlayerBoatID()); + } else { + fp.setVisible(false); + } + break; + } + } + } + } + } + }.start(); + } /** * toggles if the info table is shown @@ -994,4 +1101,12 @@ public class RaceViewController extends Controller { mapToggle = !mapToggle; } + /** + * FXML method for death button + */ + public void deathOKPressed(){ + deathPane.setDisable(true); + deathPane.setVisible(false); + } + } diff --git a/racevisionGame/src/main/java/visualiser/layout/HealthEffect.java b/racevisionGame/src/main/java/visualiser/layout/HealthEffect.java new file mode 100644 index 00000000..b3b7d70b --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/HealthEffect.java @@ -0,0 +1,111 @@ +package visualiser.layout; + +import javafx.geometry.Point3D; +import javafx.scene.image.Image; +import javafx.scene.media.AudioClip; +import javafx.scene.paint.Color; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.*; +import javafx.scene.transform.Rotate; + +/** + * Created by zwu18 on 24/09/17. + */ +public class HealthEffect extends Subject3D { + + private int sourceID; + private long currentTime; + private AudioClip warningSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/warning.mp3").toExternalForm()); + private AudioClip deadSound = new AudioClip(this.getClass().getResource("/visualiser/sounds/dead1.wav").toExternalForm()); + + public HealthEffect(int sourceID, long currentTime){ + super(createEffect(), 0); + this.sourceID = sourceID; + this.currentTime = currentTime; + } + + /** + * Initialise the mesh view with image + * @return Mesh view + */ + private static Shape3D createEffect(){ + + Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning.png")); + + Plane3D plane = new Plane3D(20, 20, 10, 10); + + PhongMaterial material = new PhongMaterial(); + material.setDiffuseColor(Color.web("#FFFFFF")); + material.setSpecularColor(Color.web("#000000")); + material.setDiffuseMap(image); + + MeshView imageSurface = new MeshView(plane); + + + imageSurface.setMaterial(material); + imageSurface.setMouseTransparent(true); + //imageSurface.toFront(); this.flashInterval = flashInterval; + + return imageSurface; + } + + public void rotateView(Double angle, Double pivotX, Double pivotY, Double pivotZ, Point3D axis){ + Rotate rotate = new Rotate(angle, axis); + rotate.setPivotX(pivotX); + rotate.setPivotY(pivotY); + rotate.setPivotZ(pivotZ); + this.getMesh().getTransforms().add(rotate); + } + + public void setVisible(boolean bool){ + this.getMesh().setVisible(bool); + } + + + public int getSourceID(){ + return sourceID; + } + + public void setSourceID(int id){ + this.sourceID = id; + } + + /** + * Display visual indication when boat dies + * @param player boolean if player is current user or not + */ + public void displayDeath(boolean player){ + Image image = new Image(HealthEffect.class.getClassLoader().getResourceAsStream("images/warning2.png")); + PhongMaterial material = (PhongMaterial) this.getMesh().getMaterial(); + material.setDiffuseColor(Color.web("#FFFFFF")); + material.setSpecularColor(Color.web("#000000")); + material.setDiffuseMap(image); + this.getMesh().setMaterial(material); + if(player) { + deadSound.play(); + } + } + + + /** + * Flash the mesh view at a certain interval + * @param checkTime The current time of flash + * @param flashInterval Desired flash interval + * @param playerBoat Whether or not this effect is for the player's boat. + */ + public void flash(long checkTime, long flashInterval, boolean playerBoat){ + if(checkTime >= (currentTime+flashInterval)){ + this.currentTime = checkTime; + if(this.getMesh().isVisible()){ + this.setVisible(false); + } else { + if(playerBoat) { + warningSound.setVolume(0.1); + warningSound.play(); + } + this.setVisible(true); + } + } + } + +} diff --git a/racevisionGame/src/main/java/visualiser/layout/HealthSlider.java b/racevisionGame/src/main/java/visualiser/layout/HealthSlider.java new file mode 100644 index 00000000..7f55607a --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/HealthSlider.java @@ -0,0 +1,66 @@ +package visualiser.layout; + +import javafx.scene.image.Image; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.*; +import javafx.scene.shape.Rectangle; + +import java.awt.image.BufferedImage; + +/** + * Created by connortaylorbrown on 21/09/17. + */ +public class HealthSlider extends Pane { + /** + * Image used to fill health slider + */ + private Image fillImage; + /** + * Size of background for image configuration + */ + private BackgroundSize backgroundSize; + /** + * Percentage of image cropped out from top + */ + private double crop; + + public HealthSlider(Image fillImage) { + this.fillImage = fillImage; + this.crop = 1; + this.backgroundSize = new BackgroundSize( + 100, + 100, + true, + true, + true, + false); + drawSlider(); + } + + public void setCrop(double crop) { + this.crop = crop; + drawSlider(); + } + + private void drawSlider() { + int top = Math.max(0,(int)(fillImage.getHeight() - crop * fillImage.getHeight())); + + WritableImage croppedImage = new WritableImage( + fillImage.getPixelReader(), + 0, + top, + (int)fillImage.getWidth(), + (int)fillImage.getHeight() - top + ); + + BackgroundImage backgroundImage = new BackgroundImage( + croppedImage, + BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, + BackgroundPosition.CENTER, + backgroundSize + ); + + this.setBackground(new Background(backgroundImage)); + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java index f5ce109b..4b580ff2 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java @@ -1,9 +1,6 @@ package visualiser.model; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.*; import javafx.scene.paint.Color; import network.Messages.Enums.BoatStatusEnum; import shared.model.*; @@ -65,6 +62,7 @@ public class VisualiserBoat extends Boat { private ObjectProperty positionProperty; private ObjectProperty bearingProperty; private BooleanProperty hasCollided; + private DoubleProperty healthProperty; /** @@ -78,6 +76,7 @@ public class VisualiserBoat extends Boat { this.color = color; this.hasCollided = new SimpleBooleanProperty(false); + this.healthProperty = new SimpleDoubleProperty(100); } @@ -284,4 +283,18 @@ public class VisualiserBoat extends Boat { public void setHasCollided(boolean hasCollided) { this.hasCollided.set(hasCollided); } + + public DoubleProperty healthProperty() { + return healthProperty; + } + + @Override + public double getHealth() { + return healthProperty.get(); + } + + @Override + public void setHealth(double healthProperty) { + this.healthProperty.set((int)healthProperty); + } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java index 4a0316ee..d6c9c1d0 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java @@ -248,7 +248,6 @@ public class VisualiserRaceState extends RaceState { if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) { - boat.setPlacing("-"); } else { diff --git a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java index 8780a676..2e97f36d 100644 --- a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java +++ b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java @@ -297,6 +297,7 @@ public class ServerConnection implements RunnableWithFramePeriod { this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages); this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages); this.messageRouter.addRoute(MessageType.ASSIGN_PLAYER_BOAT, incomingMessages); + this.messageRouter.addRoute(MessageType.BOATSTATE, incomingMessages); this.messageRouter.removeDefaultRoute(); //We no longer want to keep un-routed messages. diff --git a/racevisionGame/src/main/resources/images/warning.png b/racevisionGame/src/main/resources/images/warning.png new file mode 100644 index 00000000..fb640948 Binary files /dev/null and b/racevisionGame/src/main/resources/images/warning.png differ diff --git a/racevisionGame/src/main/resources/images/warning2.png b/racevisionGame/src/main/resources/images/warning2.png new file mode 100644 index 00000000..d8920c34 Binary files /dev/null and b/racevisionGame/src/main/resources/images/warning2.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/tomato.png b/racevisionGame/src/main/resources/visualiser/images/tomato.png new file mode 100644 index 00000000..92cf4c1d Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/tomato.png differ diff --git a/racevisionGame/src/main/resources/visualiser/scenes/newRaceView.fxml b/racevisionGame/src/main/resources/visualiser/scenes/newRaceView.fxml index 7ddcb840..3471b5ca 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/newRaceView.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/newRaceView.fxml @@ -1,5 +1,12 @@ + + + + + + + @@ -26,6 +33,7 @@ + @@ -92,23 +100,39 @@ - - -