Merge branch 'master' into spectate_mode_basic
# Conflicts: # racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java
This commit is contained in:
@@ -81,6 +81,9 @@ public class HostGameController extends Controller {
|
||||
*/
|
||||
public void alertMatchBrowser(){
|
||||
try{
|
||||
if (matchBrowserInterface == null){
|
||||
return;//private game
|
||||
}
|
||||
matchBrowserInterface.startSendingHostData(App.game.getHostedGameData(), udpSocket);
|
||||
}catch (IOException e){
|
||||
System.err.println("failed to send out hosted game info");
|
||||
|
||||
@@ -138,11 +138,13 @@ public class InGameLobbyController extends Controller {
|
||||
playerContainer.add(playerBoatToSet, (count % 3) , row);
|
||||
playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10));
|
||||
|
||||
SeaSurface sea = new SeaSurface(750, 200, 250, 0, 210);
|
||||
subjects.add(sea.getSurface());
|
||||
SeaSurface sea = new SeaSurface(750, 200);
|
||||
sea.setX(250);
|
||||
sea.setZ(210);
|
||||
subjects.add(sea);
|
||||
|
||||
MeshView mesh = new MeshView(importer.getImport());
|
||||
Subject3D subject = new Subject3D(mesh);
|
||||
Subject3D subject = new Subject3D(mesh,0);
|
||||
subjects.add(subject);
|
||||
|
||||
playerBoatToSet.setDistance(50);
|
||||
@@ -250,7 +252,7 @@ public class InGameLobbyController extends Controller {
|
||||
visualiserRaceEvent.getVisualiserRaceState().getBoats().removeListener(lobbyUpdateListener);
|
||||
|
||||
RaceViewController rvc = (RaceViewController)
|
||||
loadScene("raceView.fxml");
|
||||
loadScene("newRaceView.fxml");
|
||||
rvc.startRace(visualiserRaceEvent, controllerClient,
|
||||
isHost);
|
||||
} catch (IOException e) {
|
||||
@@ -327,7 +329,7 @@ public class InGameLobbyController extends Controller {
|
||||
* @param interval Countdown interval to check
|
||||
*/
|
||||
private void countdownText(long interval){
|
||||
|
||||
countdownTenPane.setVisible(false);
|
||||
//Do nothing if 5 seconds or less to go
|
||||
if (interval <=5000){
|
||||
countdownTenPane.setVisible(false);
|
||||
|
||||
@@ -7,7 +7,9 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import network.Messages.Enums.RequestToJoinEnum;
|
||||
import javafx.scene.media.AudioClip;
|
||||
import network.Messages.HostGame;
|
||||
import visualiser.app.MatchBrowserSingleton;
|
||||
import visualiser.model.RaceConnection;
|
||||
@@ -36,6 +38,8 @@ public class LobbyController extends Controller {
|
||||
private ObservableList<RaceConnection> connections;
|
||||
private ObservableList<RaceConnection> customConnections;
|
||||
|
||||
private AudioClip sound;
|
||||
|
||||
//the socket for match browser
|
||||
private DatagramSocket udpSocket;
|
||||
private MatchBrowserLobbyInterface matchBrowserLobbyInterface;
|
||||
@@ -72,6 +76,8 @@ public class LobbyController extends Controller {
|
||||
* Refreshes the connections in the lobby
|
||||
*/
|
||||
public void refreshBtnPressed(){
|
||||
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
|
||||
sound.play();
|
||||
addServerGames();
|
||||
for(RaceConnection connection: connections) {
|
||||
connection.check();
|
||||
@@ -116,6 +122,8 @@ public class LobbyController extends Controller {
|
||||
}
|
||||
|
||||
public void menuBtnPressed() throws IOException {
|
||||
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
|
||||
sound.play();
|
||||
matchBrowserLobbyInterface.closeSocket();
|
||||
loadScene("title.fxml");
|
||||
}
|
||||
@@ -124,6 +132,8 @@ public class LobbyController extends Controller {
|
||||
* adds a new connection
|
||||
*/
|
||||
public void addConnectionPressed(){
|
||||
sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
|
||||
sound.play();
|
||||
String hostName = addressFld.getText();
|
||||
String portString = portFld.getText();
|
||||
try {
|
||||
|
||||
@@ -8,27 +8,32 @@ import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Material;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.shape.Sphere;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
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 shared.exceptions.BoatNotFoundException;
|
||||
import shared.model.*;
|
||||
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.layout.*;
|
||||
import visualiser.model.Sparkline;
|
||||
import visualiser.model.VisualiserBoat;
|
||||
import visualiser.model.VisualiserRaceEvent;
|
||||
@@ -61,6 +66,15 @@ public class RaceViewController extends Controller {
|
||||
private View3D view3D;
|
||||
private ObservableList<Subject3D> viewSubjects;
|
||||
|
||||
/**
|
||||
* Arrow pointing to next mark in third person
|
||||
*/
|
||||
private Subject3D nextMarkArrow;
|
||||
/**
|
||||
* Animation loop for rotating mark arrow
|
||||
*/
|
||||
private AnimationTimer pointToMark;
|
||||
|
||||
// note: it says it's not used but it is! do not remove :)
|
||||
private @FXML ArrowController arrowController;
|
||||
private @FXML GridPane canvasBase;
|
||||
@@ -77,6 +91,8 @@ public class RaceViewController extends Controller {
|
||||
private @FXML TableColumn<VisualiserBoat, Number> boatSpeedColumn;
|
||||
private @FXML LineChart<Number, Number> sparklineChart;
|
||||
private @FXML Label tutorialText;
|
||||
private @FXML AnchorPane infoWrapper;
|
||||
private @FXML AnchorPane lineChartWrapper;
|
||||
|
||||
/**
|
||||
* Displays a specified race.
|
||||
@@ -121,9 +137,8 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the listener and actions that occur when a key is pressed.
|
||||
*/
|
||||
private AnimationTimer arrowToNextMark;
|
||||
|
||||
private void initKeypressHandler() {
|
||||
racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
String codeString = event.getCode().toString();
|
||||
@@ -189,10 +204,23 @@ public class RaceViewController extends Controller {
|
||||
* Initialises the various UI components to listen to the {@link #visualiserRace}.
|
||||
*/
|
||||
private void initialiseRaceVisuals() {
|
||||
|
||||
// Import arrow mesh
|
||||
URL asset = this.getClass().getClassLoader().getResource("assets/arrow V1.0.4.stl");
|
||||
StlMeshImporter importer = new StlMeshImporter();
|
||||
importer.read(asset);
|
||||
|
||||
MeshView arrow = new MeshView(importer.getImport());
|
||||
PhongMaterial arrowMat = new PhongMaterial(Color.RED);
|
||||
arrow.setMaterial(arrowMat);
|
||||
|
||||
this.nextMarkArrow = new Annotation3D(arrow);
|
||||
this.nextMarkArrow.setScale(0.1);
|
||||
|
||||
// initialise displays
|
||||
initialiseFps();
|
||||
initialiseInfoTable();
|
||||
initialiseView3D();
|
||||
initialiseView3D(this.visualiserRace);
|
||||
initialiseRaceClock();
|
||||
raceTimer(); // start the timer
|
||||
new Sparkline(this.raceState, this.sparklineChart);
|
||||
@@ -200,55 +228,122 @@ public class RaceViewController extends Controller {
|
||||
arrowController.setWindProperty(this.raceState.windProperty());
|
||||
}
|
||||
|
||||
private void initialiseView3D() {
|
||||
private void initialiseView3D(VisualiserRaceEvent race) {
|
||||
viewSubjects = FXCollections.observableArrayList();
|
||||
|
||||
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);
|
||||
|
||||
// Import boat mesh
|
||||
URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 " +
|
||||
"Complete Boat.stl");
|
||||
URL asset = RaceViewController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl");
|
||||
StlMeshImporter importer = new StlMeshImporter();
|
||||
importer.read(asset);
|
||||
|
||||
// Configure camera angles and control
|
||||
view3D = new View3D();
|
||||
URL markerAsset = RaceViewController.class.getClassLoader().getResource("assets/Bouy V1.1.stl");
|
||||
StlMeshImporter importerMark = new StlMeshImporter();
|
||||
importerMark.read(markerAsset);
|
||||
|
||||
URL alternateBoatAsset = RaceViewController.class.getClassLoader().getResource("assets/V1.3 BurgerBoat.stl");
|
||||
StlMeshImporter importerBurgerBoat = new StlMeshImporter();
|
||||
importerBurgerBoat.read(alternateBoatAsset);
|
||||
|
||||
view3D = new View3D(false);
|
||||
view3D.setItems(viewSubjects);
|
||||
view3D.setDistance(1050);
|
||||
view3D.setYaw(0);
|
||||
view3D.setPitch(60);
|
||||
view3D.setBirdsEye();
|
||||
view3D.enableTracking();
|
||||
view3D.addAmbientLight(ambientLight);
|
||||
view3D.addPointLight(pointLight);
|
||||
canvasBase.add(view3D, 0, 0);
|
||||
|
||||
// Set up projection from GPS to view
|
||||
RaceDataSource raceData = raceState.getRaceDataSource();
|
||||
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 : raceState.getMarks()) {
|
||||
Subject3D subject = new Subject3D(new Sphere(2));
|
||||
subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
||||
subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
||||
SkyBox skyBox = new SkyBox(750, 200, 250, 0, 210);
|
||||
viewSubjects.addAll(skyBox.getSkyBoxPlanes());
|
||||
|
||||
viewSubjects.add(subject);
|
||||
// Set up sea surface
|
||||
SeaSurface sea = new SeaSurface(750, 200);
|
||||
sea.setX(250);
|
||||
sea.setZ(210);
|
||||
viewSubjects.add(sea);
|
||||
|
||||
// Set up sea surface overlay
|
||||
SeaSurface seaOverlay = new SeaSurface(4000, 200);
|
||||
seaOverlay.setX(250);
|
||||
seaOverlay.setZ(210);
|
||||
viewSubjects.add(seaOverlay);
|
||||
|
||||
Boundary3D boundary3D = new Boundary3D(visualiserRace.getVisualiserRaceState().getRaceDataSource().getBoundary(), gpsConverter);
|
||||
for (Subject3D subject3D: boundary3D.getBoundaryNodes()){
|
||||
viewSubjects.add(subject3D);
|
||||
}
|
||||
// Position and add each mark to view
|
||||
for(Mark mark: race.getVisualiserRaceState().getMarks()) {
|
||||
MeshView mesh = new MeshView(importerMark.getImport());
|
||||
Subject3D markModel = new Subject3D(mesh, mark.getSourceID());
|
||||
|
||||
markModel.setX(gpsConverter.convertGPS(mark.getPosition()).getX());
|
||||
markModel.setZ(gpsConverter.convertGPS(mark.getPosition()).getY());
|
||||
|
||||
viewSubjects.add(markModel);
|
||||
}
|
||||
// Position and add each boat to view
|
||||
for (VisualiserBoat boat : raceState.getBoats()) {
|
||||
MeshView mesh = new MeshView(importer.getImport());
|
||||
Subject3D subject = new Subject3D(mesh);
|
||||
viewSubjects.add(subject);
|
||||
for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) {
|
||||
MeshView mesh;
|
||||
if(boat.getSourceID() == race.getVisualiserRaceState().getPlayerBoatID()) {
|
||||
mesh = new MeshView(importer.getImport());
|
||||
} else {
|
||||
mesh = new MeshView(importerBurgerBoat.getImport());
|
||||
}
|
||||
Subject3D boatModel = new Subject3D(mesh, boat.getSourceID());
|
||||
|
||||
viewSubjects.add(boatModel);
|
||||
|
||||
// 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());
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
boatModel.setHeading(boat.getBearing().degrees());
|
||||
boatModel.setX(gpsConverter.convertGPS(boat.getPosition()).getX());
|
||||
boatModel.setZ(gpsConverter.convertGPS(boat.getPosition()).getY());
|
||||
}
|
||||
};
|
||||
trackBoat.start();
|
||||
|
||||
Material markColor = new PhongMaterial(new Color(0.15,0.9,0.2,1));
|
||||
CompoundMark nextMark = boat.getCurrentLeg().getEndCompoundMark();
|
||||
view3D.getShape(nextMark.getMark1().getSourceID()).getMesh().setMaterial(markColor);
|
||||
if(nextMark.getMark2() != null) {
|
||||
view3D.getShape(nextMark.getMark2().getSourceID()).getMesh().setMaterial(markColor);
|
||||
}
|
||||
Subject3D shockwave = new Shockwave(10);
|
||||
|
||||
viewSubjects.add(shockwave);
|
||||
|
||||
boat.legProperty().addListener((o, prev, curr) -> Platform.runLater(() -> swapColours(curr)));
|
||||
boat.hasCollidedProperty().addListener((o, prev, curr) -> Platform.runLater(() -> showCollision(boat, shockwave)));
|
||||
}
|
||||
// Fix initial bird's-eye position
|
||||
view3D.updatePivot(new Translate(250, 0, 210));
|
||||
|
||||
view3D.targetProperty().addListener((o, prev, curr)-> {
|
||||
if(curr != null && visualiserRace.getVisualiserRaceState().isVisualiserBoat(curr.getSourceID())) {
|
||||
addThirdPersonAnnotations(curr);
|
||||
} else {
|
||||
removeThirdPersonAnnotations();
|
||||
}
|
||||
});
|
||||
|
||||
// Bind zooming to scrolling
|
||||
view3D.setOnScroll(e -> {
|
||||
view3D.updateDistance(e.getDeltaY());
|
||||
@@ -257,7 +352,7 @@ public class RaceViewController extends Controller {
|
||||
// Bind zooming to keypress (Z/X default)
|
||||
racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
|
||||
ControlKey key = keyFactory.getKey(e.getCode().toString());
|
||||
if (key != null) {
|
||||
if(key != null) {
|
||||
switch (key.toString()) {
|
||||
case "Zoom In":
|
||||
//Check if race is a tutorial
|
||||
@@ -294,6 +389,95 @@ public class RaceViewController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
private void showCollision(VisualiserBoat boat, Subject3D shockwave) {
|
||||
Subject3D boatModel = view3D.getShape(boat.getSourceID());
|
||||
AnimationTimer shockFront = new AnimationTimer() {
|
||||
double opacity = 1;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
shockwave.setX(boatModel.getPosition().getX());
|
||||
shockwave.setY(boatModel.getPosition().getY());
|
||||
shockwave.setZ(boatModel.getPosition().getZ());
|
||||
|
||||
if(opacity <= 0) {
|
||||
shockwave.getMesh().setMaterial(new PhongMaterial(Color.TRANSPARENT));
|
||||
boat.setHasCollided(false);
|
||||
this.stop();
|
||||
}
|
||||
else {
|
||||
shockwave.getMesh().setMaterial(new PhongMaterial(new Color(1,0,0,opacity)));
|
||||
opacity -= 0.1;
|
||||
}
|
||||
}
|
||||
};
|
||||
shockFront.start();
|
||||
}
|
||||
|
||||
private void addThirdPersonAnnotations(Subject3D subject3D) {
|
||||
viewSubjects.add(nextMarkArrow);
|
||||
final VisualiserBoat boat;
|
||||
try {
|
||||
boat = visualiserRace.getVisualiserRaceState().getBoat(subject3D.getSourceID());
|
||||
} catch (BoatNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
arrowToNextMark = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
CompoundMark target = boat.getCurrentLeg().getEndCompoundMark();
|
||||
Bearing headingToMark = GPSCoordinate.calculateBearing(boat.getPosition(), target.getAverageGPSCoordinate());
|
||||
|
||||
nextMarkArrow.setX(view3D.getPivot().getX());
|
||||
nextMarkArrow.setY(view3D.getPivot().getY());
|
||||
nextMarkArrow.setZ(view3D.getPivot().getZ() + 15);
|
||||
nextMarkArrow.setHeading(headingToMark.degrees());
|
||||
}
|
||||
};
|
||||
arrowToNextMark.start();
|
||||
}
|
||||
|
||||
private void removeThirdPersonAnnotations() {
|
||||
viewSubjects.remove(nextMarkArrow);
|
||||
if (arrowToNextMark != null) {
|
||||
arrowToNextMark.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the colour of the next mark to pass with the last mark passed
|
||||
* @param leg boat has started on
|
||||
*/
|
||||
private void swapColours(Leg leg) {
|
||||
CompoundMark start = leg.getStartCompoundMark();
|
||||
CompoundMark end = leg.getEndCompoundMark();
|
||||
|
||||
//The last leg "finish" doesn't have compound marks.
|
||||
if (start == null || end == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Shape3D start1 = view3D.getShape(start.getMark1().getSourceID()).getMesh();
|
||||
Shape3D end1 = view3D.getShape(end.getMark1().getSourceID()).getMesh();
|
||||
|
||||
Material nextMark = start1.getMaterial();
|
||||
Material lastMark = end1.getMaterial();
|
||||
|
||||
start1.setMaterial(lastMark);
|
||||
if(start.getMark2() != null) {
|
||||
Shape3D start2 = view3D.getShape(start.getMark2().getSourceID()).getMesh();
|
||||
start2.setMaterial(lastMark);
|
||||
}
|
||||
|
||||
end1.setMaterial(nextMark);
|
||||
if(end.getMark2() != null) {
|
||||
Shape3D end2 = view3D.getShape(end.getMark2().getSourceID()).getMesh();
|
||||
end2.setMaterial(nextMark);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the frame rate functionality. This allows for toggling the
|
||||
* frame rate, and connect the fps label to the race's fps property.
|
||||
@@ -398,6 +582,7 @@ public class RaceViewController extends Controller {
|
||||
|
||||
/**
|
||||
* Transition from the race view to the finish view.
|
||||
* @throws IOException Thrown if the finish scene cannot be loaded.
|
||||
*/
|
||||
private void finishRace() throws IOException {
|
||||
RaceFinishController fc =
|
||||
@@ -442,23 +627,7 @@ public class RaceViewController extends Controller {
|
||||
* toggles if the info table is shown
|
||||
*/
|
||||
private void toggleTable() {
|
||||
double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() +
|
||||
boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth()
|
||||
+ boatSpeedColumn.getPrefWidth())/racePane.getWidth();
|
||||
|
||||
if (infoTableShow) {
|
||||
racePane.setDividerPositions(tablePercent);
|
||||
arrowPane.setScaleX(0.5);
|
||||
arrowPane.setScaleY(0.5);
|
||||
arrowPane.setTranslateX(0 + (arrowPane.getScene().getWidth()/4)*tablePercent);
|
||||
arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4);
|
||||
} else {
|
||||
racePane.setDividerPositions(1);
|
||||
arrowPane.setScaleX(1);
|
||||
arrowPane.setScaleY(1);
|
||||
arrowPane.setTranslateX(0);
|
||||
arrowPane.setTranslateY(0);
|
||||
}
|
||||
infoWrapper.setVisible(infoTableShow);
|
||||
boatInfoTable.refresh();
|
||||
infoTableShow = !infoTableShow;
|
||||
}
|
||||
@@ -564,4 +733,4 @@ public class RaceViewController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,20 @@ import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
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 mock.exceptions.EventConstructionException;
|
||||
import visualiser.app.App;
|
||||
|
||||
import java.io.File;
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user