diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index 11553a07..3814fa80 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -77,7 +77,7 @@ public class Event { String boatsXMLFile = "mock/mockXML/boatTest.xml"; String regattaXMLFile = "mock/mockXML/regattaTest.xml"; switch (mapIndex){ - case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + case 0:raceXMLFile = "mock/mockXML/ac35MapLayout.xml"; break; case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml"; break; @@ -90,13 +90,12 @@ public class Event { boatsXMLFile = "mock/mockXML/boatTutorial.xml"; regattaXMLFile = "mock/mockXML/regattaTutorial.xml"; break; - default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + default: raceXMLFile = "mock/mockXML/ac35MapLayout.xml"; } if (singlePlayer) { - raceXMLFile = "mock/mockXML/raceSinglePlayer.xml"; boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml"; } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index a0140189..969d9d94 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -109,8 +109,6 @@ public class MockRace extends RaceState { colliderRegistry.addCollider(mark.getMark1()); if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2()); } - - this.colliderRegistry.addAllColliders(boats); } @@ -133,9 +131,9 @@ public class MockRace extends RaceState { this.boats.add(mockBoat); this.activeObserverCommands.put(boat.getSourceID(), new ActiveObserverCommand()); + this.colliderRegistry.addCollider(mockBoat); getRaceDataSource().incrementSequenceNumber(); - } /** diff --git a/racevisionGame/src/main/java/shared/model/Boat.java b/racevisionGame/src/main/java/shared/model/Boat.java index 99871678..a094bcc9 100644 --- a/racevisionGame/src/main/java/shared/model/Boat.java +++ b/racevisionGame/src/main/java/shared/model/Boat.java @@ -418,7 +418,7 @@ public class Boat extends Collider { @Override public boolean rayCast(Boat boat) { if(boat != this) { - return rayCast(boat, 15); + return rayCast(boat, 50); } else return false; } diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java index 6d1c35a8..e62a2e04 100644 --- a/racevisionGame/src/main/java/shared/model/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -28,7 +28,7 @@ public class Constants { * Frame periods are multiplied by this to get the amount of time a single frame represents. * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. */ - public static final int RaceTimeScale = 2; + public static final int RaceTimeScale = 1; /** * The race pre-start time, in milliseconds. 30 seconds. diff --git a/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java b/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java index de11c170..2b8fd823 100644 --- a/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java +++ b/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java @@ -17,8 +17,9 @@ public class MarkRoundingSequence { /** * For each leg, mark rounding information. + * Maps between leg number and rounding data. */ - private Map roundingPoints; + private Map roundingPoints; @@ -34,7 +35,7 @@ public class MarkRoundingSequence { * @return Rounding points for leg. */ public MarkRoundingData getRoundingData(Leg leg) { - return roundingPoints.get(leg); + return roundingPoints.get(leg.getLegNumber()); } @@ -139,7 +140,7 @@ public class MarkRoundingSequence { roundingData.setRoundCheck2Halfway(roundCheck2Halfway); - this.roundingPoints.put(currentLeg, roundingData); + this.roundingPoints.put(currentLeg.getLegNumber(), roundingData); //Rounding points: diff --git a/racevisionGame/src/main/java/shared/model/RaceClock.java b/racevisionGame/src/main/java/shared/model/RaceClock.java index 3ce61245..9a1d4e84 100644 --- a/racevisionGame/src/main/java/shared/model/RaceClock.java +++ b/racevisionGame/src/main/java/shared/model/RaceClock.java @@ -68,11 +68,11 @@ public class RaceClock { /** * Format string used for duration before it has started. */ - private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d"; + private String durationBeforeStartFormat = "%02d:%02d:%02d"; /** * Format string used for duration once the race has started. */ - private String durationAfterStartFormat = "Time: %02d:%02d:%02d"; + private String durationAfterStartFormat = "%02d:%02d:%02d"; diff --git a/racevisionGame/src/main/java/visualiser/Controllers/NextMarkController.java b/racevisionGame/src/main/java/visualiser/Controllers/NextMarkController.java index 20da6f3c..a51e519d 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/NextMarkController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/NextMarkController.java @@ -67,9 +67,9 @@ public class NextMarkController { Shape3D arrow = Assets3D.loadX3d(arrowPath); - arrow.setScaleX(25); + arrow.setScaleX(15); arrow.setScaleY(25); - arrow.setScaleZ(100); + arrow.setScaleZ(50); arrow.setRotationAxis(new Point3D(1,0,0)); arrowStackPane3d.getChildren().add(arrow); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java index df9a605e..545e5f08 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceViewController.java @@ -1,9 +1,7 @@ package visualiser.Controllers; import com.interactivemesh.jfx.importer.stl.StlMeshImporter; -import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; import eu.hansolo.medusa.*; -import eu.hansolo.medusa.events.UpdateEvent; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -104,6 +102,7 @@ public class RaceViewController extends Controller { private @FXML NextMarkController nextMarkController; private @FXML GridPane canvasBase; private @FXML GridPane canvasBase1; + private @FXML GridPane canvasBase2; private @FXML SplitPane racePane; private @FXML StackPane arrowPane; private @FXML Pane nextMarkPane; @@ -145,9 +144,9 @@ public class RaceViewController extends Controller { deathPane.setVisible(false); tutorialCheck(); initKeypressHandler(); - initialiseRaceVisuals(); - initialiseRaceCanvas(); healthLoop(); + initialiseRaceVisuals(); + } /** @@ -301,7 +300,7 @@ public class RaceViewController extends Controller { //Create a gauge with a frame and background that utilizes a Medusa gauge fGauge = FGaugeBuilder .create() - .prefSize(200, 200) + .prefSize(190, 190) .gauge(gauge) .gaugeDesign(GaugeDesign.METAL) .gaugeBackground(GaugeDesign.GaugeBackground.CARBON) @@ -322,12 +321,11 @@ public class RaceViewController extends Controller { initialiseHealthPane(); initialiseRaceClock(); initialiseSpeedometer(); + initialiseRaceCanvas(); raceTimer(); // start the timer - nextMarkPane.toFront(); + //nextMarkPane.toFront(); speedometerLoop(); new Sparkline(this.raceState, this.sparklineChart); - timeZone.setText(this.raceState.getRaceClock().getTimeZone()); - arrowController.setWindProperty(this.raceState.windProperty()); } @@ -466,7 +464,7 @@ public class RaceViewController extends Controller { // Track player boat with camera viewSubjects.add(boatModel); Platform.runLater(() -> { - view3D.trackSubject(boatModel); + view3D.trackSubject(boatModel, 0); view3D.setThirdPerson(); }); @@ -646,7 +644,12 @@ public class RaceViewController extends Controller { // Bind zooming to scrolling view3D.setOnScroll(e -> { - view3D.updateDistance(e.getDeltaY()); + //view3D.updateDistance(e.getDeltaY()); + if (e.getDeltaY() > 0) { + view3D.zoomIn(); + } else { + view3D.zoomOut(); + } }); // Bind zooming to keypress (Z/X default) @@ -1226,22 +1229,23 @@ public class RaceViewController extends Controller { private void bigMap(){ if (mapToggle){ - raceCanvas.widthProperty().bind(canvasBase.widthProperty()); - raceCanvas.heightProperty().bind(canvasBase.heightProperty()); + raceCanvas.widthProperty().bind(canvasBase2.widthProperty()); + raceCanvas.heightProperty().bind(canvasBase2.heightProperty()); raceCanvas.setFullScreen(true); raceCanvas.setOpacity(0.6); canvasBase1.getChildren().remove(raceCanvas); - canvasBase.getChildren().add(1, raceCanvas); + canvasBase2.getChildren().add(0, raceCanvas); }else{ raceCanvas.widthProperty().bind(canvasBase1.widthProperty()); raceCanvas.heightProperty().bind(canvasBase1.heightProperty()); raceCanvas.setFullScreen(false); + raceCanvas.setOpacity(1); - canvasBase.getChildren().remove(raceCanvas); + canvasBase2.getChildren().remove(raceCanvas); canvasBase1.getChildren().add(0, raceCanvas); } mapToggle = !mapToggle; diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java index 4f39ea65..29326fd3 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java @@ -1,17 +1,31 @@ package visualiser.Controllers; +import com.interactivemesh.jfx.importer.stl.StlMeshImporter; +import javafx.animation.AnimationTimer; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Label; +import javafx.scene.AmbientLight; +import javafx.scene.PointLight; +import javafx.scene.control.Button; import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.media.AudioClip; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; +import javafx.scene.paint.Color; +import javafx.scene.shape.MeshView; import javafx.stage.Modality; import mock.exceptions.EventConstructionException; import visualiser.app.App; +import visualiser.layout.SeaSurface; +import visualiser.layout.SkyBox; +import visualiser.layout.Subject3D; +import visualiser.layout.View3D; import java.io.File; import java.io.IOException; @@ -30,9 +44,69 @@ import java.util.logging.Logger; public class TitleController extends Controller { private @FXML RadioButton dayModeRD; private @FXML RadioButton nightModeRD; - private @FXML Label tutorialLabel; - private @FXML Pane menuPane; + private @FXML Button tutorialButton; private @FXML ImageView imgSun; + private @FXML GridPane view3DContainer; + private ToggleGroup toggleGroup = new ToggleGroup(); + + public void initialize() { + dayModeRD.setToggleGroup(toggleGroup); + nightModeRD.setToggleGroup(toggleGroup); + + AmbientLight ambientLight = new AmbientLight(Color.web("#CCCCFF")); + ambientLight.setTranslateX(250); + ambientLight.setTranslateZ(210); + ambientLight.setLightOn(true); + + PointLight pointLight = new PointLight(Color.web("#AAAAFF")); + pointLight.setTranslateX(250); + pointLight.setTranslateZ(210); + pointLight.setLightOn(true); + + ObservableList subjects = FXCollections.observableArrayList(); + View3D view3D = new View3D(); + view3D.addAmbientLight(ambientLight); + view3D.addPointLight(pointLight); + view3D.setDistance(10); + view3D.setPitch(5); + view3D.setItems(subjects); + + SkyBox skyBox = new SkyBox(750,200,250,0,250); + subjects.addAll(skyBox.getSkyBoxPlanes()); + + SeaSurface seaSurface = new SeaSurface(750, 200); + seaSurface.setX(250); + seaSurface.setZ(250); + subjects.add(seaSurface); + + URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); + StlMeshImporter importer = new StlMeshImporter(); + importer.read(asset); + Subject3D boat = new Subject3D(new MeshView(importer.getImport()), 0); + + double radius = 100; + boat.setX(0); + boat.setZ(radius); + boat.setScale(0.1); + + subjects.add(boat); + view3D.trackSubject(boat, -45); + + view3DContainer.add(view3D, 0, 0); + + AnimationTimer loop = new AnimationTimer() { + double angle = -90; + double offset = 0.05; + @Override + public void handle(long now) { + boat.setX(radius * Math.cos(angle * Math.PI/180)); + boat.setZ(radius * Math.sin(angle * Math.PI/180)); + boat.setHeading(-angle); + angle += offset; + } + }; + loop.start(); + } /** * Method called when the 'host a game' button is pressed. @@ -58,10 +132,7 @@ 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); App.dayMode = true; } @@ -71,10 +142,7 @@ 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); App.dayMode = false; } diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index c704ff49..49c1712d 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -134,7 +134,6 @@ public class App extends Application { FXMLLoader loader = new FXMLLoader(getClass().getResource ("/visualiser/scenes/title.fxml")); Parent root = loader.load(); - stage.setResizable(false); Scene scene = new Scene(root); stage.setTitle("The Boat Game - Burgers & Boats"); stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png"))); diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java index 8501cdaa..8bc92b2b 100644 --- a/racevisionGame/src/main/java/visualiser/layout/View3D.java +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -174,7 +174,7 @@ public class View3D extends Pane { PickResult result = e.getPickResult(); if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { untrackSubject(); - trackSubject(shapeMap.get(result.getIntersectedNode())); + trackSubject(shapeMap.get(result.getIntersectedNode()), 0); setThirdPerson(); } }); @@ -215,15 +215,16 @@ public class View3D extends Pane { /** * Set camera to follow the selected subject * @param subject to track + * @param yaw to add to boat heading */ - public void trackSubject(Subject3D subject) { + public void trackSubject(Subject3D subject, double yaw) { target.set(subject); this.trackBoat = new AnimationTimer() { @Override public void handle(long now) { updatePivot(target.get().getPosition()); - setYaw(target.get().getHeading().getAngle()); + setYaw(target.get().getHeading().getAngle() + yaw); } }; trackBoat.start(); @@ -268,8 +269,10 @@ public class View3D extends Pane { public void updateDistance(double delta) { double newDistance = -this.distance.getZ() + delta; if (target.get() == null){ - if (newDistance > MAX_ZOOM_LIMIT){ + if (newDistance > MAX_ZOOM_LIMIT) { setDistance(MAX_ZOOM_LIMIT); + } else if (newDistance <= ZOOM_IN_LIMIT) { + setDistance(ZOOM_IN_LIMIT); } else { setDistance(newDistance); } @@ -372,4 +375,4 @@ public class View3D extends Pane { public void addPointLight(PointLight pointLight) { this.world.getChildren().add(pointLight); } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/visualiser/layout/WindCompass.java b/racevisionGame/src/main/java/visualiser/layout/WindCompass.java index 6a42fb2d..dcb05298 100644 --- a/racevisionGame/src/main/java/visualiser/layout/WindCompass.java +++ b/racevisionGame/src/main/java/visualiser/layout/WindCompass.java @@ -25,7 +25,7 @@ public class WindCompass extends View3D { }; public WindCompass(View3D view3D, Property wind){ - super(); + super(false); this.wind = wind; this.view3D = view3D; this.followView3D.start(); diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index 822addbc..b6f41a07 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -647,7 +647,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { //rounding lines if (isFullScreen){ - drawRoundingLines(); +// drawRoundingLines(); drawRaceLine(); } @@ -794,18 +794,11 @@ public class ResizableRaceCanvas extends ResizableCanvas { } private Color getLineColor(Leg leg) { - for (VisualiserBoat boat : raceState.getBoats()) { - if (boat.isClientBoat()) { - if (boat.getCurrentLeg() == leg) { - return Color.ORANGE; - } else { - return Color.MEDIUMAQUAMARINE; - } - }else{ - return Color.MEDIUMAQUAMARINE; - } + if(ThisBoat.getInstance().getLegNumber() == leg.getLegNumber()){ + return Color.ORANGE; + }else{ + return Color.MEDIUMAQUAMARINE; } - return Color.MEDIUMAQUAMARINE; } private void drawArrowHead(GPSCoordinate start, GPSCoordinate end){ diff --git a/racevisionGame/src/main/java/visualiser/model/ThisBoat.java b/racevisionGame/src/main/java/visualiser/model/ThisBoat.java index 920dcbd9..c8fa493e 100644 --- a/racevisionGame/src/main/java/visualiser/model/ThisBoat.java +++ b/racevisionGame/src/main/java/visualiser/model/ThisBoat.java @@ -35,6 +35,14 @@ public class ThisBoat { } } + public int getLegNumber(){ + if(this.boat != null){ + return this.boat.getCurrentLeg().getLegNumber(); + }else{ + return 0; + } + } + public void setBoat(VisualiserBoat boat) { this.boat = boat; } diff --git a/racevisionGame/src/main/resources/images/raceViewUI_LowerLeft.png b/racevisionGame/src/main/resources/images/raceViewUI_LowerLeft.png new file mode 100644 index 00000000..591f1e4b Binary files /dev/null and b/racevisionGame/src/main/resources/images/raceViewUI_LowerLeft.png differ diff --git a/racevisionGame/src/main/resources/images/raceViewUI_LowerMiddle.png b/racevisionGame/src/main/resources/images/raceViewUI_LowerMiddle.png new file mode 100644 index 00000000..be33ffe0 Binary files /dev/null and b/racevisionGame/src/main/resources/images/raceViewUI_LowerMiddle.png differ diff --git a/racevisionGame/src/main/resources/images/raceViewUI_LowerRight.png b/racevisionGame/src/main/resources/images/raceViewUI_LowerRight.png new file mode 100644 index 00000000..8a3c0372 Binary files /dev/null and b/racevisionGame/src/main/resources/images/raceViewUI_LowerRight.png differ diff --git a/racevisionGame/src/main/resources/images/raceViewUI_UpperMiddle.png b/racevisionGame/src/main/resources/images/raceViewUI_UpperMiddle.png new file mode 100644 index 00000000..3cb7a5be Binary files /dev/null and b/racevisionGame/src/main/resources/images/raceViewUI_UpperMiddle.png differ diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/ac35MapLayout.xml similarity index 76% rename from racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml rename to racevisionGame/src/main/resources/mock/mockXML/ac35MapLayout.xml index 6ae04ef8..6931f080 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/ac35MapLayout.xml @@ -37,16 +37,16 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml deleted file mode 100644 index c4efcd17..00000000 --- a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 5326 - FLEET - RACE_CREATION_TIME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml deleted file mode 100644 index c4efcd17..00000000 --- a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 5326 - FLEET - RACE_CREATION_TIME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/images/arrowSmall.png b/racevisionGame/src/main/resources/visualiser/images/arrowSmall.png new file mode 100644 index 00000000..8f7841fd Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/arrowSmall.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/tomato.png b/racevisionGame/src/main/resources/visualiser/images/tomato.png index 92cf4c1d..80b9b804 100644 Binary files a/racevisionGame/src/main/resources/visualiser/images/tomato.png and b/racevisionGame/src/main/resources/visualiser/images/tomato.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/tomato_old.png b/racevisionGame/src/main/resources/visualiser/images/tomato_old.png new file mode 100644 index 00000000..92cf4c1d Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/tomato_old.png differ diff --git a/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml index 196c4c10..4367855f 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml @@ -1,49 +1,64 @@ - - + + + + - + + - - + + - + + + + + + + - + - + + - - - - + @@ -100,6 +88,21 @@ + + + + + + + + + + + @@ -111,44 +114,16 @@ - - - - - - - - - - - - - -