Compare commits

...

205 Commits

Author SHA1 Message Date
Peter Galloway ea0be5e952 Added pirate ship meshes to application. Updated boat model to allow for jib sails and a fixed sail. #story[1274] 2017-09-20 17:56:07 +12:00
Peter Galloway 7197bc2bee created meshes for pirate ship #story[1274] 2017-09-20 17:33:32 +12:00
Calum fba522d0c3 Fixed BoatMeshType enum names.
#fix
2017-09-20 16:44:38 +12:00
Calum 0e829874c2 Fixed BoatMeshType enum names.
#fix
2017-09-20 16:43:04 +12:00
Calum c5d56065b6 Fixed cat ate a meringue sail rotation.
#fix #story[1274]
2017-09-20 16:42:26 +12:00
Calum fe76e85c71 Merge branch 'develop' into new_meshes 2017-09-20 15:59:01 +12:00
Calum 9d61a43bd7 Added catamaran mesh to possible boat meshes. Made catamaran the default boat.
#implement #story[1274]
2017-09-20 15:58:22 +12:00
Kusal Ekanayake c39582de5c Updated PartyParrot logo 2017-09-20 15:41:20 +12:00
Peter Galloway 9ed52a1225 created catamaran mesh #story[1274] 2017-09-19 19:43:03 +12:00
Calum da263355f4 Changed raceview background.
#fix
2017-09-19 14:55:26 +12:00
Calum ebecd25ed2 Merge remote-tracking branch 'origin/develop' into develop 2017-09-19 14:54:15 +12:00
Calum 0f5137c2b6 Fixed the orientation of .stl files.
#fix
2017-09-19 14:52:02 +12:00
Kusal Ekanayake 73799954e4 Fixed tests that failed when running on lower end computers. Needed to add a couple of thread.sleeps 2017-09-15 12:47:40 +12:00
Haoming Yin edfeb2b287 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-14 15:40:26 +12:00
Haoming Yin 0355784000 Broadcast messages when boats pass legs or a token is picked up or expired.
tags: #story[1250] #pair[hyi25, zyt10]
2017-09-14 15:40:12 +12:00
Alistair McIntyre 02df69b7b4 - Merged Dev into branch
Tags: #story[1245]
2017-09-14 15:27:10 +12:00
Alistair McIntyre 242132b800 Merge remote-tracking branch 'origin/develop' into NewUI_merge
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-14 15:26:57 +12:00
Alistair McIntyre 3a671d4ed0 - Added Values to Finish Dialog.
Tags: #story[1245]
2017-09-14 15:24:58 +12:00
Alistair McIntyre 482d987839 - Fixed Null Pointer
- Build Should pass

Tags: #story[1245]
2017-09-14 15:10:48 +12:00
Haoming Yin cc124b2d19 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-09-14 14:50:49 +12:00
Haoming Yin b3320ad805 Fixed server message sender
tags: #story[1246]
2017-09-14 14:49:25 +12:00
Alistair McIntyre 33779ad5c1 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-14 14:42:57 +12:00
Alistair McIntyre 3a41c27d8d - Race Finish Dialog showing up, unsure if its actually showing the correct ordering or not.
tags : #story[1245]
2017-09-14 14:42:25 +12:00
Calum e24203904b Removed print statements.
#chore
2017-09-14 14:37:22 +12:00
Calum eb188495ce Fixed position issues on exit arrows.
#implement #story[1266] #fix
2017-09-14 14:32:45 +12:00
Calum 62a7e2b8fa Fixed position issues on entry arrows.
#implement #story[1266] #fix
2017-09-14 14:21:04 +12:00
Michael Rausch acd0e790fe Added method to send server debug messages to players
Tags: #story[1246]
2017-09-14 14:14:21 +12:00
Zhi You Tan 46013474c0 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-14 13:37:01 +12:00
Alistair McIntyre bf427f24d3 - Created a Race Finish Dialog.
tags : #story[1245]
2017-09-14 13:34:48 +12:00
Alistair McIntyre 7d0a47446d - Fixed bug in customize dialog.
tags : #story[1245]
2017-09-14 13:02:31 +12:00
Alistair McIntyre 889098bb50 - Commented out broken test (non-deterministic thing)
- Merged 3d factory branch in

tags : #story[1245]
2017-09-14 12:46:17 +12:00
Alistair McIntyre 6223a8fc0b Merge remote-tracking branch 'origin/story1266_3d_model_factory' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-14 12:40:20 +12:00
Calum 391bd33548 Fixed position issues on first 2 mark arrows.
#implement #story[1266]
2017-09-14 12:38:36 +12:00
Alistair McIntyre 7e0c2abbfd - Fixed a shutdown bug that left the game process running long after the window shut
- Changed SVG Icon for volume toggle to something a bit nicer

tags : #story[1245]
2017-09-14 12:31:49 +12:00
Calum 42a5e86bf8 Merge remote-tracking branch 'origin/NewUI_merge' into story1266_3d_model_factory
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-14 12:06:19 +12:00
Calum 20d73d8f2f Removed dead code from fxObjects and GameView 3D
#chore
2017-09-14 12:05:20 +12:00
Calum 0a62c538ca Added 3D mark arrows and cleaned up other classes. 2017-09-14 11:28:50 +12:00
Zhi You Tan 24a04aa530 Fix the wind direction arrow rotation.
#story[1245]
2017-09-14 01:05:52 +12:00
Haoming Yin 7ee6a09626 Fixed that wind speed did not updated frequently and UI polishing.
- added shadow for raceview boxes
- split up message history and input text block
- changed timer font

#story[1245]
2017-09-13 21:11:40 +12:00
Zhi You Tan 47798b19fe Added player position, boat speed, boat heading to race view.
#story[1245]
2017-09-13 11:22:05 +12:00
Zhi You Tan d9247eb031 Added player position, boat speed, boat heading to race view.
#story[1245]
2017-09-13 11:21:33 +12:00
Zhi You Tan 7b0d31438e Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge_ryan 2017-09-13 09:44:17 +12:00
Alistair McIntyre 1e74321aab - Lobby Disconnection works.
tags : #story[1245]
2017-09-12 18:26:25 +12:00
Zhi You Tan f0fc75feec [WIP] Adding player position, boat speed, boat heading
#story[1245]
2017-09-12 18:25:46 +12:00
Kusal Ekanayake c8dc448a52 Added tests for sounds and modified the transparency for the chat history window.
#story[1245]
2017-09-12 18:11:32 +12:00
Michael Rausch e518d13fd5 Fixed bug where number of players was off by 1
#story[1247]
2017-09-12 17:47:02 +12:00
Alistair McIntyre 167545cbef - Deleted old controllers that didn't do anything anymore.
tags : #story[1245]
2017-09-12 17:45:39 +12:00
Alistair McIntyre 20b656b16d - Fixed a bug in new customize dialog.
- Chat should work correctly when pressing enter now.

tags : #story[1245]
2017-09-12 17:26:52 +12:00
Alistair McIntyre 90a54beb8d Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 17:19:29 +12:00
Alistair McIntyre e375efb8e9 - Added documentation to controllers
- Moved Customization Dialog logic into LobbyController

tags : #story[1245]
2017-09-12 17:19:18 +12:00
Michael Rausch 06e9c55d2c Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 17:17:46 +12:00
Michael Rausch 9cba76f979 Fixed maven source encoding 2017-09-12 17:17:16 +12:00
Calum d5ce61a0ff Merge branch 'NewUI_merge' into story1266_3d_model_factory
# Conflicts:
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-09-12 17:09:47 +12:00
Alistair McIntyre 634322de3f Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 17:08:22 +12:00
Calum 6f534a430d Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/java/seng302/visualiser/fxObjects/assets_3D/ModelFactory.java
2017-09-12 17:04:37 +12:00
Calum 17b0864815 Moved transformation of boat icon view from controller class to model factory class.
#implement #story[1266] #refactor
2017-09-12 17:03:50 +12:00
Alistair McIntyre 3455321f01 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 16:54:55 +12:00
Alistair McIntyre 7e9e96c091 - Added documentation to controllers
tags : #story[1245]
2017-09-12 16:54:40 +12:00
Zhi You Tan b25c3367a9 Reimplemented wind. Wind arrow will rotate based on wind direction. Added labels for wind direction and wind speed.
#story[1245]
2017-09-12 16:52:44 +12:00
Kusal Ekanayake ec7ee34305 Fixed tests. 2017-09-12 16:52:26 +12:00
William Muir 9dad88e56a Fixed bug where server creation dialog validation was not working
#story[1245]
2017-09-12 16:45:52 +12:00
William Muir 6a4547f3f9 Minor fix so no lag when token is picked up
upon receiving a new RaceXML when one has been recieved we no longer update the course. Just update tokens

#story[1245]
2017-09-12 16:37:29 +12:00
Peter Galloway 71f626f57e fixed sail rotation broken from port to 3d #story[1266] 2017-09-12 16:30:20 +12:00
William Muir 1c343ec02d Changed the lobby map view to the 2D race rather than the 3D one.
Stripped back game view class appropriately.

#story[1245]
2017-09-12 16:13:14 +12:00
William Muir 5046ca6ffa Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 16:08:39 +12:00
William Muir bd31bae586 Changed the lobby map view to the 2D race rather than the 3D one.
Stripped back game view class appropriately.

#story[1245]
2017-09-12 16:08:29 +12:00
Michael Rausch 2f973deacc Merge branch 'NewUI_merge' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into NewUI_merge 2017-09-12 15:56:31 +12:00
Michael Rausch f4c5e483ce Merge remote-tracking branch 'origin/3D_view_working_with_maven' into NewUI_merge 2017-09-12 15:56:17 +12:00
Haoming Yin bb3ee424cc Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-12 15:52:34 +12:00
Haoming Yin 99f38fa05f [WIP] Added wind image to the bottom left of the race view.
tags: #story[1245]
2017-09-12 15:52:22 +12:00
Kusal Ekanayake 6a8c77c670 Made the project successfully build and deploy using new libraries on a local maven repo. Removed old unused libraries.
#story[1266]
2017-09-12 15:11:27 +12:00
Kusal Ekanayake 659a521cc9 Started making a cucumber test for allchat.
Will need to incorporate mockito in order to mock controller input. Have commented out the tests as they are not working correctly yet.

 #story[1246]
2017-09-12 14:55:49 +12:00
Calum da3613fe36 Split off marker classes. 2017-09-12 14:48:30 +12:00
Calum 8dc3e54186 Merge remote-tracking branch 'origin/story1266_3d_model_factory' into story1266_3d_model_factory 2017-09-12 14:11:11 +12:00
Calum 8fd35392b0 Added experimental assets for water.
#story[1266] #implement
2017-09-12 14:11:00 +12:00
Alistair McIntyre bc9f0ea924 Injected a simple toggle into the decorator for the window for the sound.
Unsure if this is the best way to do it but not a bad thing to try for now.

tags : #story[1245] #story[1249]
2017-09-12 13:53:24 +12:00
Haoming Yin 6d9864e677 Fixed chat history text line spacing to a smaller amount.
tags: #story[1245]
2017-09-12 11:12:35 +12:00
Haoming Yin 8c2125276e Polished race view text area and send button UI style to make them pretty
tags: #story[1245]
2017-09-12 10:59:08 +12:00
alistairjmcintyre 235d6c9cef Added 3D element to lobby screen to show the player their boat before the race.
tags: #story[1245]
2017-09-12 03:57:29 +12:00
Michael Rausch 5dd936f8f1 Added chat functionality & timer back into the game + bug fixes
- Added chat back into the game
- Fixed a bug where the timer would show "race finished" when the race hadn't started
- Fixed a bug where resources weren't being deallocated when the game was closed

Tags: #story[1246]
2017-09-12 01:31:38 +12:00
Michael Rausch d223d393fa Merge branch 'story1266_3d_model_factory' into NewUI_merge 2017-09-11 23:45:31 +12:00
Haoming Yin aa098569f3 Merge remote-tracking branch 'origin/NewUI_merge' into NewUI_merge 2017-09-11 23:43:46 +12:00
Haoming Yin d49e84e6d2 Fixed a bug that race view doesn't fit in decorator.
- recreated a new stage and decorator to fix the bug as decorator might
not be able to be reused.

#story[1245]
2017-09-11 23:42:08 +12:00
Michael Rausch 75bf92a67f Added map image to lobby screen
- Lobby screen has map of the course
- Map resizes with window

Tags: #story[1245]
2017-09-11 23:24:21 +12:00
Haoming Yin 0d7201e235 Race view is default to be full screen now and minor bug fixes.
- race view is force to be full screen and run in a new stage
- fixed button animations in lobby view
- polished UI element is boat customization dialog

#story[1245]
2017-09-11 23:04:41 +12:00
cir27 0197de6fe3 Drawing player icon assets but line segment was not added to repo some app crashes.
#implement #story[1266]
2017-09-11 22:19:08 +12:00
cir27 d79ff0f1cf Merge remote-tracking branch 'origin/story1266_3d_model_factory' into story1266_3d_model_factory
# Conflicts:
#	src/main/java/seng302/visualiser/fxObjects/assets_3D/ModelFactory.java
#	src/main/java/seng302/visualiser/fxObjects/assets_3D/ModelType.java
2017-09-11 22:03:52 +12:00
cir27 18f36d7ea4 Added assets to identify the player. Needs to merge with 3d player specification for code side to be implemented.
#story[1266] #implement
2017-09-11 22:02:21 +12:00
Michael Rausch 131cd80e02 Added button sounds on hover, click and in the lobby
- Started playing start & lobby sounds
 - Started playing button hover & click sounds

Tags: #story[1249]
2017-09-11 19:50:17 +12:00
Alistair McIntyre 3d0209300e - Validation completely done
- Some documentation added.

tags : #story[1245]
2017-09-11 19:36:16 +12:00
hyi25 6b4f7eb42b Merge develop into NewUI, and 3D
tags: #story[1245]
2017-09-11 18:56:26 +12:00
Kusal Ekanayake 8b9b3a31bd Trying to start getting the 3D branch to successfully build and run on git and on computers.
Solving maven issues.
2017-09-11 18:50:01 +12:00
Haoming Yin 0bf83aa858 Merge remote-tracking branch 'origin/develop' into NewUI_merge
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
#	src/main/resources/views/LobbyView.fxml
#	src/main/resources/views/RaceView.fxml
#	src/main/resources/views/StartScreenView.fxml
2017-09-11 18:15:08 +12:00
Calum dff261cf41 Added 3D assets for a trail and generated them at regular intervals.
#story[1266] #implement
2017-09-11 17:16:13 +12:00
William Muir 83ee217cef Merge branch 'Story1248_Game_App_Scaling' into 'develop'
Story1248 Race view scaling

# Changes
* Added listener to widthProperty and heightProperty in GameView.fxml.
* Changed some of the race view fxml hierarchy to support resizing.

# Testing
* Manual test log completed.

See merge request !70
2017-09-11 17:11:27 +12:00
William Muir d4826739e3 Merge branch 'develop' into Story1248_Game_App_Scaling 2017-09-11 16:56:49 +12:00
William Muir fdfb5959fa Minor change so that boats still are checked for collision after passing the finish line
#story[1250]
2017-09-11 16:53:12 +12:00
Calum 878c0e0f43 Added ocean asset. Moved camera to center of race.
#story[1266]
2017-09-11 16:24:21 +12:00
Zhi You Tan 1ce8df976c changed name: contentanchorpane to contentgridpane
#story[1248]
2017-09-11 16:08:24 +12:00
Zhi You Tan 6fcd4ae4cc updated raceview javafx after develop merge to bring in text chat pane.
#story[1248]
2017-09-11 16:01:07 +12:00
William Muir 534eaee8ce Merge remote-tracking branch 'origin/develop' into develop 2017-09-11 15:54:30 +12:00
William Muir ab35e3506d fixed bug
#story[1250]
2017-09-11 15:54:16 +12:00
Michael Rausch 1fec620427 Merged 3d branch into new UI
Tags: #story[1245]
2017-09-11 15:50:22 +12:00
Zhi You Tan 00100eb991 Merge remote-tracking branch 'origin/develop' into Story1248_Game_App_Scaling
# Conflicts:
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/views/RaceView.fxml
2017-09-11 15:47:47 +12:00
Kusal Ekanayake 58512fdbdf Replaced hover sound to work in .jar.
#story[1249]
2017-09-11 15:40:13 +12:00
Michael Rausch 76a1a3c7a0 Merge remote-tracking branch 'origin/story1266_3d_model_factory' into NewUI_merge
# Conflicts:
#	pom.xml
#	src/main/java/seng302/App.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/utilities/XMLGenerator.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/views/RaceView.fxml
#	src/main/resources/views/StartScreenView.fxml
2017-09-11 15:29:33 +12:00
William Muir 1b8c503712 fixed bug
#story[1250]
2017-09-11 15:29:08 +12:00
William Muir 334e13295f fixed bug
#story[1250]
2017-09-11 15:24:42 +12:00
William Muir 0c4d001510 Merge remote-tracking branch 'origin/develop' into develop 2017-09-11 15:11:49 +12:00
William Muir 24a3a54ccb Added sounds to coin pick up
Refactored check for token collision to move into the generic check for collision method
Created a YachtEventType enum to differentiate between collision and pickup on client side.
Client now plays a sound when they pick up a token

#story[1250]
2017-09-11 15:11:11 +12:00
Alistair McIntyre 40408cff27 -Validation on server list screen and server creation dialog done.
tags : #story[1245]
2017-09-11 15:08:34 +12:00
William Muir 9fcb8915c2 Added sounds to coin pick up
Refactored check for token collision to move into the generic check for collision method
Created a YachtEventType enum to differentiate between collision and pickup on client side.
Client now plays a sound when they pick up a token

#story[1250]
2017-09-11 14:45:04 +12:00
Kusal Ekanayake caf910c4c5 Adjusted volume to make more balanced.
#story[1249]
2017-09-11 14:39:40 +12:00
Kusal Ekanayake 4f07786449 Changed the hover noise from a mediaplayer to an audio clip.
Fixes #50

#story[1249]
2017-09-11 14:21:47 +12:00
Alistair McIntyre 2b53e0d5b4 Validation fixed on server list screen.
Server List Screen done.
tags : #story[1245]
2017-09-11 13:51:29 +12:00
Calum 1210f9342b Removed rounding in GameView that caused objects to be rendered at incorrect positions.
#bug
2017-09-11 11:37:50 +12:00
cir27 f136a970db Added placeholder assets for velocity pickups. 2017-09-11 03:11:42 +12:00
alistairjmcintyre 800ae2864f - Fixed a missing validation call.
tags: #story[1245]
2017-09-11 02:55:47 +12:00
alistairjmcintyre e764caee60 - Learned how JFX Validators work and implemented them for direct connection.
- Aside from the error message when a server cannot be found (which is the default javafx one) I think the serverlist is pretty much done.

tags: #story[1245]
2017-09-11 02:53:41 +12:00
cir27 78b4786482 Added assets for the border and for gates. Drew them in correct locations.
#implement
2017-09-11 02:50:31 +12:00
cir27 e3ccb570ed Merge remote-tracking branch 'origin/story1266_3d_model_factory' into story1266_3d_model_factory 2017-09-10 22:54:49 +12:00
cir27 470bf121a5 Added assets for borders. 2017-09-10 22:54:40 +12:00
alistairjmcintyre f077486e22 - Added methods for validating direct connection, Port Number Complete, Host Name not Complete.
- Added No Servers found Message
- Found a potential bug with windows machines not running the correct service to handle Bonjour Service Addresses.

tags: #story[1245]
2017-09-10 18:24:15 +12:00
Haoming Yin 717f7558d9 Fixed issue that when go back to start screen, start view doesn't fit
in to the decorator properly.

- Moved start screen view initialization logic into ViewManager.
- When go back to start screen view, a new stage within the start screen
view will be initialized.

#story[1245]
2017-09-10 12:43:17 +12:00
Zhi You Tan 05236337ba Fixed horizontal and vertical resizing not to draw larger than scene.
#story[1248]
2017-09-10 03:17:26 +12:00
Zhi You Tan 1b76d59acb Removed drawGoogleMap()
#story[1248]
2017-09-10 02:21:30 +12:00
William Muir 02e6d2a98b Fixed Client connection delegate bug
#story[1250]
2017-09-09 18:26:22 +12:00
Calum 06a4dde216 Ported game rendering to 3d environment for boats and markers.
#implement
2017-09-09 17:34:18 +12:00
Haoming Yin 1516e817b7 Fixed some UI bugs, and redesigned some UI elements.
- Changed class structure (added dialogs, cells folder)
- Changed font to Baloo as it has better font height
- Figured out a way to change the font color of max player slider thumb
- Added cursor effect when mouse hover on any button
- Fixed drop shadow bug for lobby view player cell
- Moved drop shadow effect from player cell controller to css

#story[1245]
2017-09-09 15:00:32 +12:00
William Muir bd7ea920b6 Fixed Client connection delegate bug
#story[1250]
2017-09-09 14:46:42 +12:00
William Muir 4a170f8179 Merged new develop on
#story[1250]
2017-09-09 14:13:50 +12:00
William Muir 1f9e6154ae Merge branch 'develop' into 1250_SendingGameObjects
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-09-09 14:02:12 +12:00
William Muir 5595e0439b Merge branch 'Story1249_SoundsAndMusic' into 'develop'
Story1249 sounds and music AND text chat

## Changes
* Added music for the following parts of the game:
    * Menu music
    * Race music
    * Finish screen music
* Added sound effects for the following events:
    * Button clicking
    * Button hovering (Although it is incredibly quiet)
    * Cap gun when the countdown hits 00:00
    * Ambient ocean noises
    * Collisions (between boats and boats, marks, gates, boundaries)
    * Boat rounding a mark
    * Boat finishing the race 
* Chat window now appears in lower right for all clients/host
* When the message is sent using the 'Enter' key or the send button, all clients/host receive the message in the senders boat colour
* There are two "cheat codes" which when types into chat give the following effect:
    * ">speed x" - Will multiply all boats speeds by x
    * ">finish" - Will make the race automatically finish and move to the finish screen

## Testing
* Manual test log completed
* Unit tests completed for chat cheat codes
* Cucumber tests completed for general sending of chat between clients 

See merge request !69
2017-09-09 13:17:12 +12:00
Calum 08f7127b69 Altered test to hopefully fix occasional indeterministic bug that occurred. 2017-09-09 13:13:45 +12:00
William Muir 8ba28ffda0 Minor refactor moving Spawning and token creation to game state
#story[1250]
2017-09-09 13:11:44 +12:00
Calum edd52a07a7 Added logger to HeartBeatThread. 2017-09-09 13:07:04 +12:00
Calum 875a6b4e98 Merge remote-tracking branch 'origin/Story1249_SoundsAndMusic' into Story1249_SoundsAndMusic 2017-09-09 13:04:00 +12:00
Calum faefcc7938 Fixed bug with initializing race view elements. Made chat box unselectable. Only manually tested.
#bugs #test #implement
2017-09-09 13:03:55 +12:00
William Muir 0c956f93c8 Merge request fixes. minor refactor
#story[1246]
2017-09-09 12:37:31 +12:00
Calum 0e2946f20b Added ocean object 2017-09-09 12:34:08 +12:00
Zhi You Tan f66ef3c208 [WIP]
- Horizontal and vertical resizing works fine now.
- Issue: Have to implement maximum horizontal and vertical scaling.

#story[1248]
2017-09-09 03:14:10 +12:00
Michael Rausch cf4f8813d2 re-implemented existing functionality in UI
- Correct player count is shown in server list
- Servers now advertise their capacity and number of players connected
- Players can click join on the servers in the server list
- Direct connect works
- Can set max players / server name in host dialog
- Server starts correctly when host clicked
- Implemented boat customization
- Implemented 'begin race button', and disabled it for players that aren't hosts
- Added countdown timer in lobby
- Fixed bug where app wouldn't close

Tags: #story[1245]
2017-09-08 18:00:09 +12:00
William Muir e2bc613c9d Merge request fixes
#story[1246]
2017-09-08 15:52:01 +12:00
cir27 c2c3c9eb53 Experimented with parallel camera in 3d gameview. Works ok for rendering boats in isometric view.
#test
2017-09-08 14:05:52 +12:00
cir27 cadf995bf7 Added area rounding area to mark assets.
#implement
2017-09-08 13:32:17 +12:00
cir27 62f139c604 Added 3D window to GameView3D to begin adding assets to. Used it to refine all 3D assets implemented by ModelFactory and manually test that they work.
#implement #test
2017-09-08 01:51:31 +12:00
cir27 0bf6dd9e6b Added 3D window to GameView3D to begin adding assets to. Used it to refine all 3D assets implemented by ModelFactory and manually test that they work.
#implement #test
2017-09-08 01:50:56 +12:00
Calum eed5f56690 Created factory class for making different views for 3d models of boats.
#implement
2017-09-07 19:23:07 +12:00
Michael Rausch b35126ff4e Added server list updates, and added lobby
- Server list updates when a server is added/removed
- Player can host a server
- Lobby view shows players connected

Tags: #pair[mra106, hyi25] #story[1245]
2017-09-07 19:20:36 +12:00
Calum c39499cee7 Fixed 3d tests 2017-09-07 18:17:42 +12:00
Kusal Ekanayake cff15ba07d Added missing button click.
#story[1249]
2017-09-07 17:54:28 +12:00
Kusal Ekanayake 6be7c17c40 Fixed manual test bugs in sounds.
#story[1249]
2017-09-07 17:39:59 +12:00
Calum 64811354e3 added peters boat 2017-09-07 17:09:52 +12:00
Kusal Ekanayake 3364f8b516 Made chat history transparent.
#story[1246]
2017-09-07 17:02:42 +12:00
Michael Rausch 8cc725616c Merge remote-tracking branch 'origin/Story1247_AutoDiscovery' into NewUI
# Conflicts:
#	pom.xml
#	src/main/java/seng302/App.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
#	src/main/resources/views/StartScreenView.fxml
2017-09-07 16:36:30 +12:00
Michael Rausch 0feccdc8b9 Replaced existing views with new views and controllers from the test repository.
Tags: #pair[mra106, ajm412] #story[1245]
2017-09-07 16:32:34 +12:00
Calum 1a755cec33 Fixed bug that caused mark arrows to be rendered on Marker id not LegNumber
#bugs
2017-09-07 16:25:55 +12:00
Calum d565552fcc Added jar files to repo 2017-09-07 15:34:38 +12:00
Calum 26a8d76f8b Merged with remote changes 2017-09-07 14:56:08 +12:00
Calum d87dcaa4fe Merged with remote changes 2017-09-07 14:54:23 +12:00
Kusal Ekanayake 0b61dffe71 Fixed some docstrings to make build work.
#story[1246]
2017-09-07 14:50:08 +12:00
Zhi You Tan 4e69157c09 Merge remote-tracking branch 'origin/Story1248_Game_App_Scaling' into Story1248_Game_App_Scaling
# Conflicts:
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/views/RaceView.fxml
2017-09-06 01:27:57 +12:00
Zhi You Tan d67566e217 [WIP]
- changed contentAnchorPane to Grid Pane temporarily so added child will be able to resize natively
- implemented zooming when resizing
Bugs:
- horizontal resizing works as intended, but vertical resizing will result in canvas moving far too left
- need to solve the right and bottom empty space when resizing

#story[1248]
2017-09-06 01:26:12 +12:00
Kusal Ekanayake 56f1dd2efb Reverted change, didn't work.
#story[1246]
2017-09-05 18:20:56 +12:00
Kusal Ekanayake 302bc91461 Tried to fix build by removing invalid '>' char.
#story[1246]
2017-09-05 18:17:20 +12:00
Kusal Ekanayake 488ab47c8d Finished off chat cucumber test.
#story[1246]
2017-09-05 18:00:43 +12:00
Kusal Ekanayake 0650ba30e2 Fixed chat tests
#story[1246]
2017-09-05 17:27:34 +12:00
Calum 04518c35b0 Created new yacht shape and altered the number of lights.
#implement
2017-09-05 17:17:42 +12:00
Kusal Ekanayake f33e4cc137 Added finish music and merged with text chat.
#story[1249]
2017-09-05 15:24:56 +12:00
Kusal Ekanayake 7a4b3f0ad9 Merge branch 'text_chat' into Story1249_SoundsAndMusic
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
2017-09-05 14:42:20 +12:00
Kusal Ekanayake 1619c95098 Added mark rounding noise and hover button noise.
#story[1249]
2017-09-05 14:33:37 +12:00
Kusal Ekanayake 396098e009 Added finish noise.
#story[1249]
2017-09-05 13:44:14 +12:00
Calum e4dbbd05c7 Merged with remote changes 2017-09-04 23:42:10 +12:00
Calum a1e3ec54c7 Merge remote-tracking branch 'origin/text_chat' into text_chat 2017-09-04 23:11:02 +12:00
Calum 47f3f6e27d Created a new chat history class that allows for rich formatting of individual messages. Current implementation allows for bold and coloured text only. Has been manually tested. Fixed 3/5 chat command tests.
#implement #test #story[1246]
2017-09-04 23:10:54 +12:00
Kusal Ekanayake fce7aa4f9a Started making a cucumber test for allchat.
Will need to incorporate mockito in order to mock controller input. Have commented out the tests as they are not working correctly yet.

 #story[1246]
2017-09-04 19:43:05 +12:00
Calum 81afad1bcc Wrote tests that are currently broken for sending server commands through text chat.
#tests
2017-09-04 14:43:13 +12:00
Calum 5026c568a7 Wrote tests that are currently broken for sending server commands through text chat.
#tests
2017-09-03 20:38:31 +12:00
Calum 88d1e91b6f Added processing of input from host via chat to manually trigger race finishing or to speed up boats.
#implement
2017-09-03 19:18:30 +12:00
Calum 67f39e9049 Tested several methods of creating 3D assets. Added a 3D file importer to library of project and replaced some assets with temporary 32 ones to test creating 32 objects.
#implement
2017-09-03 16:54:53 +12:00
Michael Rausch eb1d3f1a60 Fixed bug where discovery wasn't working under windows
- getLocalHost() was returning the networks public IP address, changed this to getByName() for the local IP

Tags: #story[1247]
2017-09-02 02:16:40 +12:00
Zhi You Tan 54ca12f3b6 [WIP]
- Updated raceview.fxml to scale race view when resize.
- Implemented a listener on game view to know the current window size.
Issues:
- Border updates with resizing until a certain size and it draws out of canvas
- Marks are not drawn within border
- Trails do not scale at all
2017-09-01 17:10:00 +12:00
Michael Rausch 3f910b8db7 Fixed JavaDoc errors by adding missing parameters 2017-09-01 16:13:18 +12:00
Michael Rausch b346d5a706 Implemented server re-registration when a server closes / updates
- When a server is closed, it will disappear from the server list
- When a player joins a server, the number of spaces left will decrease
- Servers now disappear instead of duplicating
- Added tests for ServerDescription
- Added documentation for new classes

Tags: #story[1247]
2017-09-01 16:05:47 +12:00
Peter Galloway 45fa73595f fixed being able to zoom while chatting by holding shift and then pressing z and x #story[1246] 2017-08-31 17:26:02 +12:00
Peter Galloway 01ca3f3453 tweaked formatting of chat history #story[1246] 2017-08-31 17:06:13 +12:00
Peter Galloway ba8e333d81 fixed issue with the chat history updating weirdly (made sure it run synchronously with platform.runlater) #story[1246] 2017-08-31 16:49:12 +12:00
Peter Galloway 24d4c1df15 made chat history always scroll to the bottom after a message is sent #story[1246] 2017-08-31 16:35:51 +12:00
Michael Rausch 0c5d661995 Fixed discovery bug, implemented server list, added server parameters
- Resolved DNS bug by updating to a newer version of JmDNS
- Added server list, this is populated with new servers as they are discovered
- Added map name and spaces remaining to server advertisement

Tags: #story[1247]
2017-08-31 01:11:17 +12:00
Michael Rausch 262f27fa8a Added basic auto discovery functionality
- Servers can advertise themselves
- Clients can detect servers on their local network

Tags: #story[1247]
2017-08-30 22:53:45 +12:00
Peter Galloway 0fcdf41419 fixed broken fps counter #story[1246] 2017-08-30 20:56:28 +12:00
Peter Galloway 4ebf7d6104 chat packets sent to server and then sent back to all clients. #story[1246] 2017-08-30 20:42:11 +12:00
Peter Galloway 353dd48829 sending chatter packets to server #story[1246] 2017-08-30 19:14:47 +12:00
William Muir 4bd7291a4a WIP: Yachts now power up upon collecting an item
Power up is speed boost x2 multiplier
Lasts 10 seconds
Do not stack

#story[1250]
2017-08-29 22:11:37 +12:00
William Muir 0d0b2e59d5 WIP: Tokens works well with collection. Dissapear upon picking up across all clients.
Still bug with mark ordering and arrows :/

#story[1250]
2017-08-29 21:40:25 +12:00
William Muir ace48a8404 Refactored MainServer Class in prep for better sending out of XML messages
All messages are now created through MessageFactory class
Minor tweaks to improve code base server side
Removed observer from Server to Client threads

#story[1250]
2017-08-29 21:23:46 +12:00
William Muir 201405d070 WIP: Marks randomly appear in course now. Mark arrows broken.
Something about sending raceXML during the course of the race breaks the mark rounding arrows functionallity, crashing the game.

#story[1250]
2017-08-29 19:13:48 +12:00
William Muir dc19310849 Tokens now appear client side
#story[1250]
2017-08-29 17:17:06 +12:00
William Muir 1c866ea8c2 GameClient now extracts Tokens client side
#story[1250]
2017-08-29 16:50:31 +12:00
William Muir 23027705da Sample Tokens are now sent out in RaceXML correctly
Token and TokenType class created

#story[1250]
2017-08-29 15:37:01 +12:00
William Muir c15f13bc2c Refactored some of the XMLGenerator code. Added tokens to the generated XML
Refactor not complete. Generation needs some tidying.

#story[1250]
2017-08-29 14:56:06 +12:00
Kusal Ekanayake 6ee2517f74 Added most sound functionality.
There is not background music and sound effects (button clicking, ocean noises, crashes for collisions). Can mute the sound and the music independently of each other from the main menu.

#story[1249]
2017-08-28 16:37:09 +12:00
Peter Galloway 75155fe481 implemented basic single client proof of concept for the chat history #story[1246] 2017-08-25 17:57:45 +12:00
William Muir 2fcff65dd6 Fixed null pointer when cant find a server
tags: #fix
2017-08-17 15:44:05 +12:00
William Muir 4c730ea890 Fixed null pointer when cant find a server
tags: #fix
2017-08-17 15:40:55 +12:00
183 changed files with 8698 additions and 2697 deletions
-1
View File
@@ -7,7 +7,6 @@
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
@@ -0,0 +1 @@
bc00cae65d030845973151123fd0f2b1
@@ -0,0 +1 @@
de6c72cb03b2216bbe03ac7b882f0c146fb76bc8
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>lib.com.interactivemesh</groupId>
<artifactId>jimColModelImporter</artifactId>
<version>0.7</version>
</project>
@@ -0,0 +1 @@
8fc884a64856917671745720acc6048c
@@ -0,0 +1 @@
4b35131587917ed1a16acb1eff8cd7a213a26edc
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>lib.com.interactivemesh</groupId>
<artifactId>jimColModelImporter</artifactId>
<versioning>
<release>0.7</release>
<versions>
<version>0.7</version>
</versions>
<lastUpdated>20170912024010</lastUpdated>
</versioning>
</metadata>
@@ -0,0 +1 @@
3132c3f88de1a942ac37930b8cdaa764
@@ -0,0 +1 @@
20847be06b0d11b70f1fbfb1527c5efee4e9f49e
@@ -0,0 +1 @@
deec04fc74e1115465598d342810df18
@@ -0,0 +1 @@
ea31eabe6384ae965cd8180920f7ba0248717313
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>lib.com.interactivemesh</groupId>
<artifactId>jimStlMeshImporter</artifactId>
<version>0.7</version>
</project>
@@ -0,0 +1 @@
82a485ac9a76d6587b1b23b7fbd8f5a0
@@ -0,0 +1 @@
2bac29a6598a88b2f115b72433181c13fc6201d2
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>lib.com.interactivemesh</groupId>
<artifactId>jimStlMeshImporter</artifactId>
<versioning>
<release>0.7</release>
<versions>
<version>0.7</version>
</versions>
<lastUpdated>20170912024122</lastUpdated>
</versioning>
</metadata>
@@ -0,0 +1 @@
cad88c5c501f771bc8d1fc085decb3c4
@@ -0,0 +1 @@
c6cd4fae002dbbe4246c8eac4b35de07d921fd51
+53
View File
@@ -11,6 +11,7 @@
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
@@ -70,6 +71,44 @@
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.lwjgl/lwjgl -->
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>de.javagl</groupId>
<artifactId>obj</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>com.interactivemesh</groupId>
<artifactId>jimStlMeshImporter</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>com.interactivemesh</groupId>
<artifactId>jimColModelImporter</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>com.jfoenix</groupId>
<artifactId>jfoenix</artifactId>
<version>1.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.jmdns/jmdns -->
<dependency>
<groupId>javax.jmdns</groupId>
<artifactId>jmdns</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<build>
@@ -152,4 +191,18 @@
</plugin>
</plugins>
</reporting>
<repositories>
<repository>
<id>lib</id>
<name>third party libraries</name>
<url>file://${basedir}/lib</url>
</repository>
<repository>
<id>Homer-Core</id>
<name>Homer-core-repo</name>
<url>https://nexus.arcsmed.at/content/repositories/homer.core</url>
</repository>
</repositories>
</project>
+3 -24
View File
@@ -2,10 +2,6 @@ package seng302;
import ch.qos.logback.classic.Level;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
@@ -14,7 +10,7 @@ import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.PolarTable;
import seng302.visualiser.controllers.ViewManager;
public class App extends Application {
@@ -67,27 +63,10 @@ public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
primaryStage.setTitle("RaceVision");
Scene scene = new Scene(root, 1530, 960);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
primaryStage.setScene(scene);
// primaryStage.setMaxWidth(1530);
// primaryStage.setMaxHeight(960);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
// primaryStage.setMaximized(true);
primaryStage.show();
primaryStage.setOnCloseRequest(e -> {
// ClientPacketParser.appClose();
// ClientPacketParser.appClose();
System.exit(0);
});
// ClientState.primaryStage = primaryStage;
ViewManager.getInstance().initialStartView(primaryStage);
}
public static void main(String[] args) {
try {
parseArgs(args);
+257 -89
View File
@@ -1,37 +1,24 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javafx.scene.paint.Color;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.MarkRoundingMessage;
import seng302.gameServer.messages.MarkType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoundingBoatStatus;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.gameServer.messages.*;
import seng302.model.*;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.*;
/**
* A Static class to hold information about the current state of the game (model)
* Also contains logic for updating itself on regular time intervals on its own thread
@@ -41,26 +28,33 @@ public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
private Logger logger = LoggerFactory.getLogger(GameState.class);
private static Logger logger = LoggerFactory.getLogger(GameState.class);
static final int WARNING_TIME = 10 * -1000;
static final int PREPATORY_TIME = 5 * -1000;
private static final int TIME_TILL_START = 10 * 1000;
private static final Long POWERUP_TIMEOUT_MS = 10_000L;
private static final Integer STATE_UPDATES_PER_SECOND = 60;
public static Integer MAX_PLAYERS = 8;
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
public static final Double MARK_COLLISION_DISTANCE = 15d;
private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
private static final Double MARK_COLLISION_DISTANCE = 15d;
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
private static final Double COLLISION_VELOCITY_PENALTY = 0.3;
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static Double speedMultiplier = 1d;
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
private static Boolean playerHasLeftFlag;
private static String hostIpAddress;
private static List<Player> players;
@@ -71,36 +65,34 @@ public class GameState implements Runnable {
private static long startTime;
private static Set<Mark> marks;
private static List<Limit> courseLimit;
private static Integer maxPlayers = 8;
private static List<NewMessageListener> markListeners;
private static List<Token> allTokens;
private static List<Token> tokensInPlay;
private static List<NewMessageListener> newMessageListeners;
private static Map<Player, String> playerStringMap = new HashMap<>();
/*
Ideally I would like to make this class an object instantiated by the server and given to
it's created threads if necessary. Outside of that I think the dependencies on it
(atm only Yacht & GameClient) can be removed from most other classes. The observable list of
players could be pulled directly from the server by the GameClient since it instantiates it
and it is reasonable for it to pull data. The current setup of publicly available statics is
pretty meh IMO because anything can change it making it unreliable and like people did with
the old ServerParser class everything that needs shared just gets thrown in the static
collections and things become a real mess.
*/
public GameState(String hostIpAddress) {
windDirection = 180d;
windSpeed = 10000d;
this.hostIpAddress = hostIpAddress;
yachts = new HashMap<>();
tokensInPlay = new ArrayList<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
playerHasLeftFlag = false;
speedMultiplier = 1.0;
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
//set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis();
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
markListeners = new ArrayList<>();
newMessageListeners = new ArrayList<>();
allTokens = makeTokens();
resetStartTime();
@@ -125,6 +117,21 @@ public class GameState implements Runnable {
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
/**
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
* race ideally
*
* @return A list of possible tokensInPlay for this race
*/
private ArrayList<Token> makeTokens() {
Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154);
Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382);
Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965);
Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214);
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
}
public static String getHostIpAddress() {
return hostIpAddress;
}
@@ -137,6 +144,10 @@ public class GameState implements Runnable {
return players;
}
public static List<Token> getTokensInPlay() {
return tokensInPlay;
}
public static void addPlayer(Player player) {
players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
@@ -178,7 +189,7 @@ public class GameState implements Runnable {
}
public static void resetStartTime(){
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
startTime = System.currentTimeMillis() + TIME_TILL_START;
}
public static Double getWindDirection() {
@@ -264,7 +275,23 @@ public class GameState implements Runnable {
}
/**
* Called periodically in this GameState thread to update the GameState values
* Randomly select a subset of tokensInPlay from a pre defined superset
* Broadasts a new race status message to show this update
*/
public static void spawnNewToken() {
Random random = new Random();
tokensInPlay.clear();
tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size())));
}
/**
* Called periodically in this GameState thread to update the GameState values.
* -Updates yachts velocity
* -Updates locations
* -Checks for collisions
* -Checks for progression
*
* -Also checks things like the end of the race and race start time etc
*/
public void update() {
Boolean raceFinished = true;
@@ -276,15 +303,14 @@ public class GameState implements Runnable {
}
for (ServerYacht yacht : yachts.values()) {
updateVelocity(yacht);
checkPowerUpTimeout(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
checkCollision(yacht);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
checkCollision(yacht);
checkForLegProgression(yacht);
raceFinished = false;
}
}
if (raceFinished) {
@@ -292,6 +318,18 @@ public class GameState implements Runnable {
}
}
private void checkPowerUpTimeout(ServerYacht yacht) {
if (yacht.getPowerUp() != null) {
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) {
yacht.powerDown();
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired");
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
}
}
}
/**
* Check if the yacht has crossed the course limit
*
@@ -312,8 +350,39 @@ public class GameState implements Runnable {
return false;
}
/**
* Checks all tokensInPlay to see if a yacht has picked one up
* @return Token which was collided with
* @param serverYacht The yacht to check for collision with a token
*/
private static Token checkTokenPickUp(ServerYacht serverYacht) {
for (Token token : tokensInPlay) {
Double distance = GeoUtility.getDistance(token, serverYacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return token;
}
}
return null;
}
/**
* Checks for collision with other in game objects for the given serverYacht. To be called each
* update. If there is a collision, Notifies the server to send the appropriate messages out.
* Checks for these items in turn:
* - Other yachts
* - Marks
* - Boundary
* - Tokens
*
* @param serverYacht The server yacht to check collisions with
*/
public static void checkCollision(ServerYacht serverYacht) {
//Yacht Collision
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
@@ -329,59 +398,81 @@ public class GameState implements Runnable {
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
} else {
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
else{
if (checkBoundaryCollision(serverYacht)) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, serverYacht.getLocation(),
BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
}
}
}
//Mark Collision
else if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
}
//Boundary Collision
else if (checkBoundaryCollision(serverYacht)) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, serverYacht.getLocation(),
BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
}
//Token Collision
Token collidedToken = checkTokenPickUp(serverYacht);
if (collidedToken != null) {
sendServerMessage(serverYacht.getSourceId(), serverYacht.getBoatName() + " has picked speed-up token");
tokensInPlay.remove(collidedToken);
serverYacht.powerUp(collidedToken.getTokenType());
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
.getTokenType());
notifyMessageListeners(MessageFactory.getRaceXML());
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
}
}
private void updateVelocity(ServerYacht yacht) {
Double velocity = yacht.getCurrentVelocity();
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
if (yacht.getPowerUp() != null) {
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
maxBoatSpeed *= 2;
}
}
Double currentVelocity = yacht.getCurrentVelocity();
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (velocity < maxBoatSpeed - 500) {
if (currentVelocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (velocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-velocity / 200);
} else if (currentVelocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-currentVelocity / 200);
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
}
} else {
if (velocity > 3000) {
yacht.changeVelocity(-velocity / 200);
} else if (velocity > 100) {
yacht.changeVelocity(-velocity / 50);
} else if (velocity <= 100) {
if (currentVelocity > 3000) {
yacht.changeVelocity(-currentVelocity / 200);
} else if (currentVelocity > 100) {
yacht.changeVelocity(-currentVelocity / 50);
} else if (currentVelocity <= 100) {
yacht.setCurrentVelocity(0d);
}
}
@@ -442,6 +533,9 @@ public class GameState implements Runnable {
}
if (hasProgressed) {
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber());
}
yacht.incrementLegNumber();
sendMarkRoundingMessage(yacht);
logMarkRounding(yacht);
@@ -476,6 +570,7 @@ public class GameState implements Runnable {
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.RACING);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed start line");
return true;
}
}
@@ -579,6 +674,7 @@ public class GameState implements Runnable {
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.FINISHED);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed finish line");
return true;
}
}
@@ -671,8 +767,8 @@ public class GameState implements Runnable {
}
private static void notifyMessageListeners(Message message) {
for (NewMessageListener mpl : markListeners) {
mpl.notify(message);
for (NewMessageListener ml : newMessageListeners) {
ml.notify(message);
}
}
@@ -684,8 +780,39 @@ public class GameState implements Runnable {
}
public static void addMarkPassListener(NewMessageListener listener) {
markListeners.add(listener);
public static void sendServerMessage(Integer messageType, String message) {
notifyMessageListeners(new ChatterMessage(
messageType, "SERVER: " + message
));
}
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
String chatterText = chatterMessage.getMessage();
String[] words = chatterText.split("\\s+");
if (words.length > 2 && isHost) {
switch (words[2].trim()) {
case "/speed":
try {
setSpeedMultiplier(Double.valueOf(words[3]));
sendServerMessage(chatterMessage.getMessage_type(),
"Speed modifier set to x" + words[3]);
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(GameState.class);
logger.error("cannot parse >speed value");
}
return;
case "/finish":
sendServerMessage(chatterMessage.getMessage_type(),
"Game will now finish");
endRace();
return;
}
}
notifyMessageListeners(chatterMessage);
}
public static void addMessageEventListener(NewMessageListener listener) {
newMessageListeners.add(listener);
}
public static void setCustomizationFlag() {
@@ -699,4 +826,45 @@ public class GameState implements Runnable {
public static void resetCustomizationFlag() {
customizationFlag = false;
}
public static void setPlayerHasLeftFlag(Boolean flag) {
playerHasLeftFlag = flag;
}
public static Boolean getPlayerHasLeftFlag() {
return playerHasLeftFlag;
}
public static Integer getNumberOfPlayers(){
Integer numPlayers = 1;
for(Player p : getPlayers()){
if(p.getSocket().isConnected()){
numPlayers++;
}
}
return numPlayers;
}
public static Integer getCapacity(){
return maxPlayers;
}
public static void setMaxPlayers(Integer newMax){
maxPlayers = newMax;
}
public static void endRace () {
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
currentStage = GameStages.FINISHED;
}
public static void setSpeedMultiplier (double multiplier) {
speedMultiplier = multiplier;
}
public static double getSpeedMultiplier () {
return speedMultiplier;
}
}
@@ -4,6 +4,8 @@ import java.io.IOException;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.Player;
import seng302.gameServer.messages.Heartbeat;
import seng302.gameServer.messages.Message;
@@ -14,6 +16,9 @@ import seng302.gameServer.messages.Message;
* cannot be sent to a player
*/
public class HeartbeatThread implements Runnable {
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
@@ -44,20 +49,23 @@ public class HeartbeatThread implements Runnable {
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
try {
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()) {
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
}
}
updateDelegate();
seqNum++;
} catch (NullPointerException ne) {
logger.debug("Socket closed between checking for connection and sending heartbeat");
}
updateDelegate();
seqNum++;
}
/**
@@ -1,17 +1,30 @@
package seng302.gameServer;
import seng302.gameServer.messages.*;
import java.io.IOException;
import java.io.StringReader;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.Message;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.GeoUtility;
import java.io.IOException;
import java.net.ServerSocket;
import java.time.LocalDateTime;
import java.util.*;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* A class describing the overall server, which creates and collects server threads for each client
@@ -19,37 +32,77 @@ import java.util.*;
*/
public class MainServerThread implements Runnable, ClientConnectionDelegate {
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
private static final int PORT = 4942;
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
private static final int LOG_LEVEL = 1;
private static final int WARNING_TIME = 10 * -1000;
private static final int PREPATORY_TIME = 5 * -1000;
public static final int TIME_TILL_START = 10 * 1000;
private static final int MAX_WIND_SPEED = 12000;
private static final int MIN_WIND_SPEED = 8000;
public static int windSpeed = 1000;
private boolean terminated;
private Thread thread;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
private static Integer capacity;
private void startAdvertisingServer() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
XMLGenerator generator = new XMLGenerator();
try {
db = dbf.newDocumentBuilder();
String regatta = generator.getRegattaAsXml();
StringReader stringReader = new StringReader(regatta);
InputSource is = new InputSource(stringReader);
doc = db.parse(is);
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.warn("Couldn't load race regatta");
return;
}
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
Integer capacity = GameState.getCapacity();
Integer numPlayers = GameState.getNumberOfPlayers();
Integer spacesLeft = capacity - numPlayers;
// No spaces left on server
if (spacesLeft < 1) {
return;
}
// Start advertising server
try {
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setCapacity(capacity).setNumberOfPlayers(numPlayers);
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
} catch (IOException e) {
logger.warn("Could not register server");
}
}
public MainServerThread() {
new GameState("localhost");
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
logger.trace("IO error in server thread handler upon trying to make new server socket",
0);
}
startAdvertisingServer();
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
GameState.addMessageEventListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
startUpdatingWind();
startSpawningTokens();
thread.start();
}
@@ -61,53 +114,73 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!terminated) {
if (GameState.getPlayerHasLeftFlag()) {
for (ServerToClientThread stc : serverToClientThreads) {
if (!stc.isSocketOpen()) {
GameState.getYachts().remove(stc.getSourceId());
sendSetupMessages();
try {
stc.getSocket().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
GameState.setPlayerHasLeftFlag(false);
}
try {
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
.getCustomizationFlag()) {
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
for (ServerToClientThread thread : serverToClientThreads) {
thread.sendSetupMessages();
}
sendSetupMessages();
GameState.resetCustomizationFlag();
}
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
updateClients();
sendBoatLocations();
}
//RACING
if (GameState.getCurrentStage() == GameStages.RACING) {
updateClients();
sendBoatLocations();
}
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
terminate();
broadcastMessage(MessageFactory.getRaceStatusMessage());
try {
Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus
terminate();
} catch (InterruptedException ie) {
logger.trace("Thread interrupted while waiting to terminate clients", 1);
}
}
}
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
try {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.terminate();
}
serverSocket.close();
return;
} catch (IOException e) {
System.out.println("IO error in server thread handler upon closing socket");
}
}
public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendBoatLocationPackets();
private void sendBoatLocations() {
for (ServerYacht serverYacht : GameState.getYachts().values()) {
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
}
}
private void sendSetupMessages() {
broadcastMessage(MessageFactory.getRaceXML());
broadcastMessage(MessageFactory.getRegattaXML());
broadcastMessage(MessageFactory.getBoatXML());
}
private void broadcastMessage(Message message) {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message);
@@ -122,27 +195,31 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
if (Math.floorMod(random.nextInt(), 2) == 0){
direction += random.nextInt(4);
windSpeed += random.nextInt(20) + 50;
windSpeed += random.nextInt(20) + 459;
}
else{
direction -= random.nextInt(4);
windSpeed -= random.nextInt(20) + 50;
windSpeed -= random.nextInt(20) + 459;
}
direction = Math.floorMod(direction, 360);
if (windSpeed > MAX_WIND_SPEED){
windSpeed -= random.nextInt(1000);
windSpeed -= random.nextInt(500);
}
if (windSpeed <= MIN_WIND_SPEED){
windSpeed += random.nextInt(1000);
windSpeed += random.nextInt(500);
}
GameState.setWindSpeed(Double.valueOf(windSpeed));
GameState.setWindDirection(direction.doubleValue());
}
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
private static void startUpdatingWind(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@@ -153,12 +230,18 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}, 0, 500);
}
static void serverLog(String message, int logLevel) {
if (logLevel <= LOG_LEVEL) {
System.out.println(
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
/**
* Start spawning coins every 60s after the first minute
*/
private void startSpawningTokens() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
GameState.spawnNewToken();
broadcastMessage(MessageFactory.getRaceXML());
}
}, 10000, 60000);
}
/**
@@ -168,14 +251,19 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
*/
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(() -> {
for (ServerToClientThread thread : serverToClientThreads) {
thread.sendSetupMessages();
}
});
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
}
/**
@@ -185,12 +273,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
*/
@Override
public void clientDisconnected(Player player) {
// try {
// player.getSocket().close();
// } catch (Exception e) {
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
// }
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
logger.debug("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player);
ServerToClientThread closedConnection = null;
@@ -202,69 +285,41 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}
}
serverToClientThreads.remove(closedConnection);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
closedConnection.terminate();
}
public void startGame() {
try {
ServerAdvertiser.getInstance().unregister();
} catch (IOException e) {
logger.warn("Error unregistering server");
}
initialiseBoatPositions();
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
broadcastMessage(makeRaceStatusMessage());
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
broadcastMessage(makeRaceStartMessage());
broadcastMessage(MessageFactory.getRaceStatusMessage());
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
}
}
}, 0, 500);
}
private RaceStartStatusMessage makeRaceStartMessage() {
Long raceStartTime = GameState.getStartTime();
return new RaceStartStatusMessage(1, raceStartTime ,
1, RaceStartNotificationType.SET_RACE_START_TIME);
}
private RaceStatusMessage makeRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
y.getLegNumber(),
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
raceStatus = RaceStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
raceStatus = RaceStatus.PRESTART;
if (timeTillStart > WARNING_TIME) {
raceStatus = RaceStatus.WARNING;
}
if (timeTillStart > PREPATORY_TIME) {
raceStatus = RaceStatus.PREPARATORY;
}
} else {
raceStatus = RaceStatus.STARTED;
sendSetupMessages();
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
}
public void terminate() {
@@ -275,10 +330,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
// Getting the start line compound marks
// if (gameClient== null) {
// return;
// }
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
GeoPoint startMark1 = cm.getSubMark(1);
GeoPoint startMark2 = cm.getSubMark(2);
@@ -0,0 +1,131 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.BoatSubMessage;
import seng302.gameServer.messages.RaceStartNotificationType;
import seng302.gameServer.messages.RaceStartStatusMessage;
import seng302.gameServer.messages.RaceStatus;
import seng302.gameServer.messages.RaceStatusMessage;
import seng302.gameServer.messages.RaceType;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
/**
* A Class for interfacing between the data we have in the GameState to the messages we need to send
* through the MainServerThread.
*
* WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started)
* // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯
* Created by wmu16 on 29/08/17.
*/
/*
Ideally this class would be created with an instance of the GameState (I tried implementing this for
a bit) but it was too difficult to properly make GameState non static without doing some proper
re working. To do later.
*/
public class MessageFactory {
private static XMLGenerator xmlGenerator = new XMLGenerator();
public static RaceStartStatusMessage getRaceStartStatusMessage() {
return new RaceStartStatusMessage(
1,
GameState.getStartTime(),
1,
RaceStartNotificationType.SET_RACE_START_TIME);
}
public static RaceStatusMessage getRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
y.getLegNumber(),
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
raceStatus = RaceStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
raceStatus = RaceStatus.PRESTART;
if (timeTillStart > GameState.WARNING_TIME) {
raceStatus = RaceStatus.WARNING;
}
if (timeTillStart > GameState.PREPATORY_TIME) {
raceStatus = RaceStatus.PREPARATORY;
}
} else {
raceStatus = RaceStatus.STARTED;
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
}
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
return new BoatLocationMessage(
yacht.getSourceId(),
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
yacht.getLocation().getLat(),
yacht.getLocation().getLng(),
yacht.getHeading(),
yacht.getCurrentVelocity().longValue());
}
public static XMLMessage getRaceXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
XMLMessage raceXMLMessage = new XMLMessage(
xmlGenerator.getRaceAsXml(),
XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
return raceXMLMessage;
}
public static XMLMessage getRegattaXML() {
//@TODO calculate lat/lng values
return new XMLMessage(
xmlGenerator.getRegattaAsXml(),
XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
}
public static XMLMessage getBoatXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new XMLMessage(
xmlGenerator.getBoatsAsXml(),
XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
}
}
@@ -0,0 +1,173 @@
package seng302.gameServer;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* Advertises the game server on the local network
*/
public class ServerAdvertiser {
/*
Our service name & protocol
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
Where Service is unique on the network, and protocol is usually _tcp.
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
the lookup should be performed using an IP multicast query on the local IP network.
Read this before changing any of the following values
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
*/
private static String SERVICE = "_partyatsea";
private static String PROTOCOL = "_tcp";
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
private static ServerAdvertiser instance = null;
private static JmDNS jmdnsInstance = null;
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
private Hashtable<String ,String> props;
private ServerAdvertiser() throws IOException{
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
props = new Hashtable<>();
props.put("map", "");
props.put("spacesLeft", "0");
props.put("capacity", "0");
props.put("players", "0");
}
/**
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
* @return A ServerAdvertiser Instance
* @throws IOException If there was an exception creating the instance
*/
public static ServerAdvertiser getInstance() throws IOException {
if (instance == null){
instance = new ServerAdvertiser();
}
return instance;
}
/**
* Set the map name and broadcast an update on the network
* @param mapName The new map name
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setMapName(String mapName){
props.replace("map", mapName);
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Set the number of players on the server and broadcast an update on the network
* @param numPlayers The number of players on the server
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
props.replace("players", numPlayers.toString());
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Set the max capacity of the server and broadcast an update on the network
* @param capacity The maximum capacity of the server
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setCapacity(Integer capacity){
props.replace("capacity", capacity.toString());
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Register this service on the network
*
* Note: other parameters (map name/spaces left etc) are set after the
* service has been registered
* @param portNo The servers port number
* @param serverName The servers name
*/
public void registerGame(Integer portNo, String serverName) {
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
try {
jmdnsInstance.registerService(serviceInfo);
} catch (IOException e) {
System.out.println("Failed");
}
}
}, 0);
}
/**
* Unregister the service
*/
public void unregister(){
if (serviceInfo != null)
jmdnsInstance.unregisterService(serviceInfo);
}
/**
* Gets the local host ip address.
*
* @return the localhost ip address
*/
public static String getLocalHostIp() {
String ipAddress = null;
try {
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface ni = e.nextElement();
if (ni.isLoopback())
continue;
if(ni.isPointToPoint())
continue;
if(ni.isVirtual())
continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while(addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if(address instanceof Inet4Address) { // skip all ipv6
ipAddress = address.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (ipAddress == null) {
System.out.println("[HOST] Cannot obtain local host ip address.");
}
return ipAddress;
}
}
@@ -0,0 +1,83 @@
package seng302.gameServer;
public class ServerDescription {
private Integer capacity;
private String address;
private Integer portNum;
private String serverName;
private String mapName;
private Integer numPlayers;
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
this.serverName = serverName;
this.mapName = mapName;
this.numPlayers = numPlayers;
this.address = address;
this.portNum = portNum;
this.capacity = capacity;
}
public String getName() {
return serverName;
}
public String getMapName() {
return mapName;
}
public Integer portNumber() {
return portNum;
}
public String getAddress(){
return address;
}
public Integer getNumPlayers() {
return numPlayers;
}
public Integer getCapacity(){
return capacity;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
return false;
}
final ServerDescription other = (ServerDescription) obj;
if (!this.getAddress().equals(other.getAddress()) ) {
return false;
}
if (!this.portNumber().equals(other.portNumber())){
return false;
}
if (!this.getMapName().equals(other.getMapName())){
return false;
}
if (!this.getName().equals(other.getName())){
return false;
}
if (!this.getCapacity().equals(other.getCapacity())){
return false;
}
return true;
}
@Override
public int hashCode() {
return this.getName().hashCode() + this.getAddress().hashCode() +
this.portNumber().hashCode() + this.getMapName().hashCode();
}
}
@@ -2,6 +2,7 @@ package seng302.gameServer;
import java.util.Arrays;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
@@ -28,5 +29,18 @@ public class ServerPacketParser {
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
return CustomizeRequestType.getRequestType((int) type);
}
public static ChatterMessage extractChatterText(byte[] payload) {
return new ChatterMessage(
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
);
}
public static ChatterMessage extractChatterText(StreamPacket packet) {
byte[] payload = packet.getPayload();
return new ChatterMessage(
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
);
}
}
@@ -12,8 +12,6 @@ import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
@@ -21,7 +19,7 @@ import java.util.zip.Checksum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
@@ -29,30 +27,11 @@ import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.utilities.XMLGenerator;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.gameServer.messages.YachtEventCodeMessage;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.utilities.XMLGenerator;
/**
@@ -60,7 +39,7 @@ import seng302.utilities.XMLGenerator;
* its own thread. All server threads created and owned by the server thread handler which can
* trigger client updates on its threads Created by wmu16 on 13/07/17.
*/
public class ServerToClientThread implements Runnable, Observer {
public class ServerToClientThread implements Runnable {
/**
* Called to notify listeners when this thread receives a connection correctly.
@@ -91,8 +70,9 @@ public class ServerToClientThread implements Runnable, Observer {
private ClientType clientType;
private Boolean isRegistered = false;
private Boolean isHost = false;
private XMLGenerator xml;
private XMLGenerator xmlGenerator;
private List<ConnectionListener> connectionListeners = new ArrayList<>();
private DisconnectListener disconnectListener;
@@ -115,6 +95,10 @@ public class ServerToClientThread implements Runnable, Observer {
thread.start();
}
public Integer getSourceId() {
return sourceId;
}
private void setUpPlayer(){
BufferedReader fn;
String fName = "";
@@ -144,21 +128,11 @@ public class ServerToClientThread implements Runnable, Observer {
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
player = new Player(socket, yacht);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(player);
}
@Override
public void update(Observable o, Object arg) {
if (arg != null) {
sendMessage((Message) arg);
} else {
sendSetupMessages();
}
}
private void completeRegistration(ClientType clientType) throws IOException {
// Fail if not a player
if (!clientType.equals(ClientType.PLAYER)){
@@ -167,7 +141,7 @@ public class ServerToClientThread implements Runnable, Observer {
return;
}
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
if (GameState.getPlayers().size() >= GameState.getCapacity()){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
os.write(responseMessage.getBuffer());
return;
@@ -225,7 +199,12 @@ public class ServerToClientThread implements Runnable, Observer {
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
@@ -244,39 +223,31 @@ public class ServerToClientThread implements Runnable, Observer {
}
} catch (Exception e) {
closeSocket();
GameState.setPlayerHasLeftFlag(true);
return;
}
}
GameState.setPlayerHasLeftFlag(true);
logger.warn("Closed serverToClientThread" + thread, 1);
}
public void sendSetupMessages() {
xml = new XMLGenerator();
Race race = new Race();
xmlGenerator = new XMLGenerator();
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
for (ServerYacht yacht : GameState.getYachts().values()) {
race.addBoat(yacht);
}
//@TODO calculate lat/lng values
xml.setRegatta(
new Regatta(
"Party Parrot Test Server", "Bermuda Test Course",
57.6679590, 11.8503233)
);
xml.setRace(race);
xmlGenerator.setRaceTemplate(race);
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xml.getRegattaAsXml().length());
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
xml.getBoatsAsXml().length());
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
xml.getRaceAsXml().length());
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
sendMessage(xmlMessage);
}
@@ -288,6 +259,10 @@ public class ServerToClientThread implements Runnable, Observer {
}
}
public Boolean isSocketOpen() {
return !socket.isClosed();
}
private int readByte() throws Exception {
int currentByte = -1;
try {
@@ -334,23 +309,6 @@ public class ServerToClientThread implements Runnable, Observer {
return seqNo;
}
public void sendBoatLocationPackets() {
ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
for (ServerYacht yacht : yachts) {
BoatLocationMessage boatLocationMessage =
new BoatLocationMessage(
yacht.getSourceId(),
getSeqNo(),
yacht.getLocation().getLat(),
yacht.getLocation().getLng(),
yacht.getHeading(),
yacht.getCurrentVelocity().longValue());
sendMessage(boatLocationMessage);
}
}
public Thread getThread() {
return thread;
}
@@ -363,10 +321,6 @@ public class ServerToClientThread implements Runnable, Observer {
return yacht;
}
public void sendCollisionMessage(Integer yachtId) {
sendMessage(new YachtEventCodeMessage(yachtId));
}
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
@@ -386,4 +340,8 @@ public class ServerToClientThread implements Runnable, Observer {
public void addDisconnectListener(DisconnectListener disconnectListener) {
this.disconnectListener = disconnectListener;
}
public void setAsHost() {
isHost = true;
}
}
@@ -11,9 +11,11 @@ public class ChatterMessage extends Message {
private int message_size = 21;
private String message;
public ChatterMessage(int message_type, int message_size, String message) {
public ChatterMessage(int message_type, String message) {
byte[] byteMessage = message.getBytes();
this.message_type = message_type;
this.message_size = message_size;
this.message_size = byteMessage.length;
this.message = message;
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
@@ -23,7 +25,7 @@ public class ChatterMessage extends Message {
putByte((byte) MESSAGE_VERSION_NUMBER);
putInt(message_type, 1);
putInt(message_size, 1);
putBytes(message.getBytes());
putBytes(byteMessage);
writeCRC();
rewind();
@@ -34,5 +36,11 @@ public class ChatterMessage extends Message {
return MESSAGE_SIZE + message_size;
}
public String getMessage() {
return message;
}
public int getMessage_type() {
return message_type;
}
}
@@ -22,6 +22,7 @@ public class MarkRoundingMessage extends Message{
* @param roundingBoatStatus roundingBoatStatus
* @param roundingSide roundingSide
* @param markId markId
* @param markType .
*/
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
RoundingSide roundingSide, MarkType markType, int markId) {
@@ -18,13 +18,13 @@ public class YachtEventCodeMessage extends Message {
private int eventId;
public YachtEventCodeMessage(Integer subjectId) {
public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) {
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
raceId = 1;
destSourceId = subjectId; // collision boat source id
incidentId = 0;
eventId = 33;
eventId = yachtEventType.getCode();
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
allocateBuffer();
@@ -0,0 +1,19 @@
package seng302.gameServer.messages;
/**
* Created by wmu16 on 11/09/17.
*/
public enum YachtEventType {
COLLISION(33),
TOKEN(34);
private int code;
YachtEventType(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
+9 -16
View File
@@ -15,7 +15,6 @@ import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.mark.CompoundMark;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
@@ -32,7 +31,7 @@ public class ClientYacht extends Observable {
@FunctionalInterface
public interface MarkRoundingListener {
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
void notifyRounding(ClientYacht yacht, int legNumber);
}
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
@@ -47,7 +46,7 @@ public class ClientYacht extends Observable {
private Integer position;
private Long estimateTimeAtFinish;
private Boolean sailIn = true;
private Boolean sailIn = false;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
@@ -63,7 +62,6 @@ public class ClientYacht extends Observable {
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
private CompoundMark lastMarkRounded;
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
@@ -189,14 +187,6 @@ public class ClientYacht extends Observable {
return markRoundTime;
}
public CompoundMark getLastMarkRounded() {
return lastMarkRounded;
}
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
this.lastMarkRounded = lastMarkRounded;
}
public GeoPoint getLocation() {
return location;
}
@@ -263,7 +253,7 @@ public class ClientYacht extends Observable {
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
// this.currentVelocity = velocity;
this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
@@ -286,13 +276,16 @@ public class ClientYacht extends Observable {
return sailIn;
}
public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) {
public void roundMark(long markRoundTime, long timeSinceLastMark) {
this.markRoundTime = markRoundTime;
timeSinceLastMarkProperty.set(timeSinceLastMark);
lastMarkRounded = mark;
legNumber++;
for (MarkRoundingListener listener : markRoundingListeners) {
listener.notifyRounding(this, lastMarkRounded, legNumber);
listener.notifyRounding(this, legNumber);
}
}
public Double getCurrentVelocity() {
return currentVelocity;
}
}
+21 -6
View File
@@ -3,11 +3,8 @@ package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;
import java.util.TimeZone;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
@@ -15,6 +12,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
import seng302.utilities.Sounds;
/**
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
@@ -33,7 +31,9 @@ public class RaceState {
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
private long serverSystemTime;
private long expectedStartTime;
private boolean isRaceStarted = false;
private boolean raceRunning = false;
private boolean gunFired = false;
private boolean raceFinished = false;
long timeTillStart;
private ObservableList<ClientYacht> playerPositions;
private List<ClientYacht> collisions = new ArrayList<>();
@@ -48,7 +48,7 @@ public class RaceState {
this.windDirection.set(data.getWindDirection());
this.serverSystemTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.isRaceStarted = data.isRaceStarted();
this.raceRunning = data.isRaceStarted();
}
public void setTimeZone (TimeZone timeZone) {
@@ -64,6 +64,10 @@ public class RaceState {
if (raceTime < 0) {
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
} else {
if (!gunFired) {
gunFired = true;
Sounds.playCapGunSound();
}
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
}
}
@@ -89,9 +93,12 @@ public class RaceState {
}
public boolean isRaceStarted () {
return isRaceStarted;
return raceRunning;
}
public void setRaceStarted(Boolean value) {
this.raceRunning = value;
}
public void setBoats(Collection<ClientYacht> clientYachts) {
playerPositions.setAll(clientYachts);
}
@@ -119,4 +126,12 @@ public class RaceState {
public void removeCollisionListener(CollisionListener collisionListener) {
collisionListeners.remove(collisionListener);
}
public void setRaceFinished() {
raceFinished = true;
}
public Boolean getRaceFinished() {
return raceFinished;
}
}
+27 -13
View File
@@ -1,22 +1,24 @@
package seng302.model;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.mark.Mark;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ServerYacht extends Observable {
public class ServerYacht {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
@@ -30,10 +32,8 @@ public class ServerYacht extends Observable {
private String boatName;
private String country;
private BoatStatus boatStatus;
private Color boatColor;
//Location
private Double lastHeading;
private Boolean sailIn;
@@ -52,6 +52,10 @@ public class ServerYacht extends Observable {
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
//PowerUp
private TokenType powerUp;
private Long powerUpStartTime;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
@@ -71,6 +75,7 @@ public class ServerYacht extends Observable {
this.currentMarkSeqID = 0;
this.legNumber = 0;
this.boatColor = Colors.getColor(sourceId - 1);
this.powerUp = null;
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
@@ -101,13 +106,21 @@ public class ServerYacht extends Observable {
location = geoPoint;
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
public void powerUp(TokenType powerUp) {
this.powerUp = powerUp;
powerUpStartTime = System.currentTimeMillis();
}
public void powerDown() {
this.powerUp = null;
}
public Long getPowerUpStartTime() {
return powerUpStartTime;
}
public TokenType getPowerUp() {
return powerUp;
}
/**
@@ -123,6 +136,7 @@ public class ServerYacht extends Observable {
/**
* Swaps the boats direction from one side of the wind to the other.
* @param windDirection .
*/
public void tackGybe(Double windDirection) {
if (isAuto) {
@@ -11,8 +11,10 @@ import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.stream.xml.generator.Race;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
@@ -125,7 +127,10 @@ public class MarkOrder {
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
generator.setRace(new Race());
// TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data
generator.setRaceTemplate(new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>()));
String raceXML = generator.getRaceAsXml();
@@ -57,7 +57,7 @@ public class RaceStatusData {
* Returns the data for boats collected form race status packets.
*
* @return A list of boat data. Boat data is in the form
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status].
*/
public List<long[]> getBoatData () {
return boatData;
@@ -5,28 +5,23 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.ServerYacht;
import seng302.model.token.Token;
/**
* A Race object that can be parsed into XML
*/
public class Race {
public class RaceXMLTemplate {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
private List<Token> tokens;
public Race(){
yachts = new ArrayList<>();
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens) {
this.yachts = yachts;
this.tokens = tokens;
startTime = LocalDateTime.now();
}
/**
* Add a boat to the race
* @param yacht The boat to add
*/
public void addBoat(ServerYacht yacht) {
yachts.add(yacht);
}
/**
* Get a list of boats in the race
* @return A List of boats
@@ -35,6 +30,15 @@ public class Race {
return Collections.unmodifiableList(yachts);
}
/**
* Get a list of tokens in the race
*
* @return A list of tokens
*/
public List<Token> getTokens() {
return Collections.unmodifiableList(tokens);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
@@ -3,7 +3,7 @@ package seng302.model.stream.xml.generator;
/**
* A Race regatta that can be parsed into XML
*/
public class Regatta {
public class RegattaXMLTemplate {
private final Double DEFAULT_ALTITUDE = 0d;
private final Integer DEFAULT_REGATTA_ID = 0;
@@ -18,7 +18,7 @@ public class Regatta {
private Integer utcOffset;
private Double magneticVariation;
public Regatta(String name, String courseName, Double latitude, Double longitude) {
public RegattaXMLTemplate(String name, String courseName, Double latitude, Double longitude) {
this.name = name;
this.id = DEFAULT_REGATTA_ID;
this.courseName = courseName;
@@ -74,4 +74,12 @@ public class Regatta {
public Double getMagneticVariation(){
return magneticVariation;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public void setRegattaName(String regattaName) {
this.name = regattaName;
}
}
@@ -6,6 +6,7 @@ import java.util.Map;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.token.Token;
/**
* Process a Document object containing race data in XML format and stores the data.
@@ -13,13 +14,16 @@ import seng302.model.mark.Corner;
public class RaceXMLData {
private List<Integer> participants;
private List<Token> tokens;
private Map<Integer, CompoundMark> compoundMarks;
private List<Corner> markSequence;
private List<Limit> courseLimit;
public RaceXMLData(List<Integer> participants, List<CompoundMark> compoundMarks,
public RaceXMLData(List<Integer> participants, List<Token> tokens,
List<CompoundMark> compoundMarks,
List<Corner> markSequence, List<Limit> courseLimit) {
this.participants = participants;
this.tokens = tokens;
this.markSequence = markSequence;
this.courseLimit = courseLimit;
this.compoundMarks = new HashMap<>();
@@ -32,6 +36,10 @@ public class RaceXMLData {
return participants;
}
public List<Token> getTokens() {
return tokens;
}
public Map<Integer, CompoundMark> getCompoundMarks() {
return compoundMarks;
}
@@ -0,0 +1,21 @@
package seng302.model.token;
import seng302.model.GeoPoint;
/**
* A class describing a game token
* Created by wmu16 on 28/08/17.
*/
public class Token extends GeoPoint {
private TokenType tokenType;
public Token(TokenType tokenType, double lat, double lng) {
super(lat, lng);
this.tokenType = tokenType;
}
public TokenType getTokenType() {
return tokenType;
}
}
@@ -0,0 +1,31 @@
package seng302.model.token;
/**
* An enum describing the different types of game objects
* Created by wmu16 on 28/08/17.
*/
public enum TokenType {
BOOST(0),
HANDLING(1);
private int value;
TokenType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static TokenType getToken(int value) {
switch (value) {
case 0:
return BOOST;
case 1:
return HANDLING;
default:
return BOOST;
}
}
}
@@ -0,0 +1,46 @@
package seng302.utilities;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
public class BonjourInstallChecker {
private static String INSTALL_URL = "https://support.apple.com/kb/DL999?locale=en_US";
private static String[] INSTALL_DIRECTORIES = {"C:/Program Files/Bonjour", "C:/Program Files (x86)/Bonjour"};
private static Boolean isWindows(){
return System.getProperty("os.name").startsWith("Windows");
}
private static Boolean isBonjourInstalled(){
for (String dir : INSTALL_DIRECTORIES){
File file = new File(dir);
if (file.isDirectory()){
return true;
}
}
return false;
}
public static Boolean isBonjourSupported(){
if (isWindows()){
return isBonjourInstalled();
}
return true;
}
public static void openInstallUrl(){
Runtime rt = Runtime.getRuntime();
try {
rt.exec("rundll32 url.dll,FileProtocolHandler " + INSTALL_URL);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -6,6 +6,7 @@ import seng302.model.GeoPoint;
public class GeoUtility {
private static double EARTH_RADIUS = 6378.137;
// private static double EARTH_RADIUS = 6378.13712121212121212121212121212121212121;
private static Double MS_TO_KNOTS = 1.943844492;
/**
+193
View File
@@ -0,0 +1,193 @@
package seng302.utilities;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
/**
* Static class for playing sounds throughout the program
*
* Created by kre39 on 28/08/17.
*/
public class Sounds {
private static MediaPlayer musicPlayer;
private static MediaPlayer soundEffect;
private static MediaPlayer soundPlayer;
private static MediaPlayer hoverSoundPlayer;
private static boolean hoverInitialized = false;
private static boolean musicMuted = false;
private static boolean soundEffectsMuted = false;
public static void stopMusic() {
if (musicPlayer != null) {
musicPlayer.stop();
}
}
static void setMutes() {
if (soundPlayer != null) {
soundPlayer.setMute(soundEffectsMuted);
}
if (soundEffect != null) {
soundEffect.setMute(soundEffectsMuted);
}
if (musicPlayer != null) {
musicPlayer.setMute(musicMuted);
}
}
public static void stopSoundEffects() {
if (soundEffect != null) {
soundEffect.stop();
}
}
public static void toggleAllSounds() {
toggleMuteEffects();
toggleMuteMusic();
}
static void toggleMuteMusic() {
musicMuted = !musicMuted;
if (musicPlayer != null) {
musicPlayer.setMute(musicMuted);
}
}
static void toggleMuteEffects() {
soundEffectsMuted = !soundEffectsMuted;
if (soundPlayer != null) {
soundPlayer.setMute(soundEffectsMuted);
}
if (soundEffect != null) {
soundEffect.setMute(soundEffectsMuted);
}
}
public static boolean isMusicMuted() {
return musicMuted;
}
public static boolean isSoundEffectsMuted() {
return soundEffectsMuted;
}
public static void playRaceMusic() {
// Media menuMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Chill-house-music-loop-116-bpm.wav").toString());
Media raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Music-loop-120-bpm.mp3").toString());
musicPlayer = new MediaPlayer(raceMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.setVolume(0.3);
musicPlayer.play();
raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Sounds-of-the-ocean.mp3").toString());
soundEffect = new MediaPlayer(raceMusic);
soundEffect.setCycleCount(MediaPlayer.INDEFINITE);
soundEffect.setVolume(0.3);
soundEffect.play();
musicPlayer.setMute(musicMuted);
soundEffect.setMute(soundEffectsMuted);
}
public static void playMenuMusic() {
Media menuMusic = new Media(
Sounds.class.getClassLoader().getResource("sounds/Elevator-music.mp3").toString());
musicPlayer = new MediaPlayer(menuMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.setVolume(0.3);
musicPlayer.play();
}
public static void playFinishMusic() {
Media finishMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Happy-birthday-song.mp3").toString());
musicPlayer = new MediaPlayer(finishMusic);
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
musicPlayer.setVolume(0.3);
musicPlayer.play();
musicPlayer.setMute(musicMuted);
}
public static void playButtonClick() {
if (!soundEffectsMuted) {
Media buttonClick = new Media(
Sounds.class.getClassLoader().getResource("sounds/Button-click-sound.mp3")
.toString());
soundPlayer = new MediaPlayer(buttonClick);
soundPlayer.setVolume(0.5);
soundPlayer.play();
soundPlayer.setMute(soundEffectsMuted);
}
}
public static void playFinishSound() {
if (!soundEffectsMuted) {
Media finishSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Sms-notification.mp3")
.toString());
soundPlayer = new MediaPlayer(finishSound);
soundPlayer.setVolume(0.5);
soundPlayer.play();
}
}
public static void playMarkRoundingSound() {
if (!soundEffectsMuted) {
Media markRoundingSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/sms-tone.mp3").toString());
soundPlayer = new MediaPlayer(markRoundingSound);
soundPlayer.play();
}
}
public static void playCapGunSound() {
if (!soundEffectsMuted) {
Media gunSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Gunshot-sound.mp3").toString());
soundPlayer = new MediaPlayer(gunSound);
soundPlayer.play();
}
}
public static void playCrashSound() {
if (!soundEffectsMuted) {
Media crashSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
.toString());
soundPlayer = new MediaPlayer(crashSound);
soundPlayer.play();
}
}
public static void playTokenPickupSound() {
if (!soundEffectsMuted) {
Media pickupSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Coin-pick-up-sound-effect.mp3")
.toString());
soundPlayer = new MediaPlayer(pickupSound);
soundPlayer.play();
}
}
public static void playHoverSound() {
if (!soundEffectsMuted) {
if (!hoverInitialized) {
Media crashSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
.toString());
hoverSoundPlayer = new MediaPlayer(crashSound);
hoverInitialized = true;
}
hoverSoundPlayer.setVolume(0.5);
if (hoverSoundPlayer != null) {
hoverSoundPlayer.stop();
}
hoverSoundPlayer.play();
}
}
}
@@ -5,6 +5,7 @@ import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -13,8 +14,12 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.*;
import seng302.model.stream.parser.MarkRoundingData;
import seng302.model.stream.parser.PositionUpdateData;
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStartData;
import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
/**
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
@@ -35,7 +40,6 @@ public class StreamParser {
return null;
}
long heartbeat = bytesToLong(packet.getPayload());
System.out.println("heartbeat = " + heartbeat);
return heartbeat;
}
@@ -62,31 +66,10 @@ public class StreamParser {
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// currentTime = format.format((new Date(currentTime)))
RaceStatusData data = new RaceStatusData(
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
);
// long timeTillStart =
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
//
// if (timeTillStart > 0) {
// timeSinceStart = timeTillStart;
// } else {
// if (raceStatus == 4 || raceStatus == 8) {
// raceFinished = true;
// raceStarted = false;
// } else if (!raceStarted) {
// raceStarted = true;
// raceFinished = false;
// }
// timeSinceStart = timeTillStart;
// }
//
//
int noBoats = payload[22];
int raceType = payload[23];
long boatID, estTimeAtNextMark, estTimeAtFinish;
@@ -106,24 +89,6 @@ public class StreamParser {
return data;
}
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
// Integer placing = 1;
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
// for (Yacht boat : boats.values()) {
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
// placing += 1;
// }
// }
// updatingBoat.setPlacing(placing.toString());
// updatingBoat.setLegNumber(leg);
// boatsPos.putIfAbsent(placing, updatingBoat);
// boatsPos.replace(placing, updatingBoat);
// } else if(updatingBoat.getLegNumber() == null){
// updatingBoat.setPlacing("1");
// updatingBoat.setLegNumber(leg);
// }
// }
/**
* Parses and returns the text from a StreamPacket containing text data for display.
*
@@ -255,15 +220,15 @@ public class StreamParser {
* @return Chatter text message as a string. Returns null if the packet is not of type
* CHATTER_TEXT.
*/
public static String extractChatterText(StreamPacket packet) {
public static Pair<Integer, String> extractChatterText(StreamPacket packet) {
if (packet.getType() != PacketType.CHATTER_TEXT) {
return null;
}
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
int messageType = payload[1];
int length = payload[2];
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
int length = (int) bytesToLong(new byte[]{payload[2]});
return new Pair<>(messageType, new String(Arrays.copyOfRange(payload, 3, 3 + length)));
}
/**
@@ -392,26 +357,6 @@ public class StreamParser {
};
}
public static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
*
@@ -3,13 +3,15 @@ package seng302.utilities;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.generator.Regatta;
import seng302.gameServer.messages.XMLMessageSubType;
import java.util.Random;
/**
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
@@ -20,8 +22,13 @@ public class XMLGenerator {
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
private Configuration configuration;
private Regatta regatta;
private Race race;
private RegattaXMLTemplate regatta;
private RaceXMLTemplate race;
public static RegattaXMLTemplate DEFAULT_REGATTA = new RegattaXMLTemplate("Party Parrot Test Server " + new Random().nextInt(100),
"Bermuda",
57.6679590,
11.8503233);
/**
* Set up a configuration instance for Apache Freemake
@@ -39,7 +46,7 @@ public class XMLGenerator {
/**
* Create an instance of the XML Generator
*/
public XMLGenerator(){
public XMLGenerator() {
setupConfiguration();
}
@@ -48,7 +55,7 @@ public class XMLGenerator {
* Note: This must be set before a regatta message can be generated
* @param regatta The race regatta
*/
public void setRegatta(Regatta regatta){
public void setRegattaTemplate(RegattaXMLTemplate regatta) {
this.regatta = regatta;
}
@@ -57,7 +64,7 @@ public class XMLGenerator {
* Note: This must be set before a boat or race message can be generated
* @param race The race
*/
public void setRace(Race race){
public void setRaceTemplate(RaceXMLTemplate race) {
this.race = race;
}
@@ -106,7 +113,7 @@ public class XMLGenerator {
public String getRegattaAsXml(){
String result = null;
if (regatta == null) return null;
if (regatta == null) regatta = DEFAULT_REGATTA;
try {
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
@@ -160,4 +167,16 @@ public class XMLGenerator {
return result;
}
public static void setDefaultRaceName(String raceName){
DEFAULT_REGATTA.setRegattaName(raceName);
}
public static void setDefaultMapName(String mapName){
DEFAULT_REGATTA.setCourseName(mapName);
}
public RegattaXMLTemplate getRegatta() {
return regatta;
}
}
@@ -16,6 +16,8 @@ import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
/**
* Utilities for parsing XML documents
@@ -182,12 +184,32 @@ public class XMLParser {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractTokens(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
extractCourseLimit(docEle)
);
}
/**
* Extracts token data
*/
private static List<Token> extractTokens(Element docEle) {
List<Token> tokens = new ArrayList<>();
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
for (int i = 0; i < tokenList.getLength(); i++) {
Node tokenNode = tokenList.item(i);
if (tokenNode.getNodeName().equals("Token")) {
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
}
}
return tokens;
}
/**
* Extracts course limit data
*/
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatActionMessage;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
import seng302.gameServer.messages.CustomizeRequestMessage;
import seng302.gameServer.messages.CustomizeRequestType;
@@ -33,8 +34,6 @@ import seng302.model.stream.packets.StreamPacket;
*/
public class ClientToServerThread implements Runnable {
/**
* Functional interface for receiving packets from client socket.
*/
@@ -95,7 +94,7 @@ public class ClientToServerThread implements Runnable {
sendRegistrationRequest();
thread = new Thread(this);
thread = new Thread(this, "ClientToServer");
thread.start();
}
@@ -283,9 +282,17 @@ public class ClientToServerThread implements Runnable {
* @param message The given message type.
*/
private void sendBoatActionMessage(BoatActionMessage message) {
sendByteBuffer(message.getBuffer());
}
public void sendChatterMessage(String message) {
sendByteBuffer(new ChatterMessage(clientId, message).getBuffer());
}
private void sendByteBuffer(byte[] bytes) {
if (clientId != -1) {
try {
os.write(message.getBuffer());
os.write(bytes);
} catch (IOException e) {
logger.warn("IOException on attempting to sendBoatAction from Client");
notifyDisconnectListeners("Cannot communicate with server");
@@ -294,7 +301,7 @@ public class ClientToServerThread implements Runnable {
}
}
private void closeSocket() {
public void closeSocket() {
try {
socket.close();
socketOpen = false;
@@ -359,7 +366,7 @@ public class ClientToServerThread implements Runnable {
}
}
int getClientId () {
public int getClientId () {
return clientId;
}
}
+154 -114
View File
@@ -1,23 +1,32 @@
package seng302.visualiser;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.util.Pair;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.gameServer.MainServerThread;
import seng302.gameServer.ServerDescription;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.YachtEventType;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.stream.packets.StreamPacket;
@@ -28,12 +37,13 @@ import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.parser.YachtEventData;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.Sounds;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.FinishScreenViewController;
import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.LobbyController.CloseStatus;
import seng302.visualiser.controllers.RaceViewController;
import seng302.visualiser.controllers.ViewManager;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
@@ -52,6 +62,9 @@ public class GameClient {
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private RaceViewController raceViewController;
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
@@ -74,28 +87,28 @@ public class GameClient {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
showConnectionError(cause);
tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
socketThread.addStreamObserver(this::parsePackets);
LobbyController lobbyController = loadLobby();
lobbyController.setSocketThread(socketThread);
lobbyController.setPlayerID(socketThread.getClientId());
lobbyController.setPlayerListSource(clientLobbyList);
lobbyController.disableReadyButton();
if (regattaData != null){
lobbyController.setTitle(regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
}
else{
lobbyController.setTitle(ipAddress);
lobbyController.setCourseName("");
ViewManager.getInstance().setPlayerList(clientLobbyList);
while (regattaData == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = lobbyController;
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
Platform.runLater(this::loadStartScreen);
}
}
@@ -104,49 +117,41 @@ public class GameClient {
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsHost(String ipAddress, Integer portNumber) {
server = new MainServerThread();
try {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
Platform.runLater(this::loadStartScreen);
});
LobbyController lobbyController = loadLobby();
lobbyController.setSocketThread(socketThread);
lobbyController.setPlayerID(socketThread.getClientId());
lobbyController.setPlayerListSource(clientLobbyList);
if (regattaData != null) {
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
lobbyController.setCourseName(regattaData.getCourseName());
} else {
lobbyController.setTitle("Hosting: " + ipAddress);
lobbyController.setCourseName("");
}
public ServerDescription runAsHost(String ipAddress, Integer portNumber, String serverName, Integer maxPlayers) {
XMLGenerator.setDefaultRaceName(serverName);
GameState.setMaxPlayers(maxPlayers);
lobbyController.addCloseListener(exitCause -> {
if (exitCause == CloseStatus.READY) {
GameState.resetStartTime();
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
server.terminate();
server = null;
loadStartScreen();
}
});
this.lobbyController = lobbyController;
} catch (IOException ioe) {
server = new MainServerThread();
try {
startClientToServerThread(ipAddress, 4942);
} catch (IOException e) {
showConnectionError("Cannot connect to server as host");
Platform.runLater(this::loadStartScreen);
}
while (regattaData == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.lobbyController = ViewManager.getInstance().goToLobby(false);
ViewManager.getInstance().setPlayerList(clientLobbyList);
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(), ipAddress, 4942);
}
private void loadStartScreen() {
private void tearDownConnection() {
socketThread.setSocketToClose();
if (server != null) {
server.terminate();
server = null;
}
}
private void loadStartScreen() {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("/views/StartScreenView.fxml"));
try {
@@ -171,52 +176,8 @@ public class GameClient {
socketThread.addStreamObserver(this::parsePackets);
}
/**
* Loads a view of the lobby into the clients pane
*
* @return the lobby controller.
*/
private LobbyController loadLobby() {
FXMLLoader fxmlLoader = new FXMLLoader(
GameClient.class.getResource("/views/LobbyView.fxml"));
try {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) {
e.printStackTrace();
}
return fxmlLoader.getController();
}
private void loadRaceView() {
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/RaceView.fxml");
holderPane.getScene().setOnKeyPressed(this::keyPressed);
holderPane.getScene().setOnKeyReleased(this::keyReleased);
raceView = fxmlLoader.getController();
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player);
}
private void loadFinishScreenView() {
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
FinishScreenViewController controller = fxmlLoader.getController();
controller.setFinishers(raceState.getPlayerPositions());
}
private FXMLLoader loadFXMLToHolder(String fxmlLocation) {
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource(fxmlLocation)
);
try {
final Node fxmlLoaderFX = fxmlLoader.load();
Platform.runLater(() -> {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoaderFX);
});
} catch (IOException e) {
e.printStackTrace();
}
return fxmlLoader;
public void setRaceViewController(RaceViewController controller) {
this.raceViewController = controller;
}
private void parsePackets() {
@@ -245,11 +206,14 @@ public class GameClient {
break;
case RACE_XML:
courseData = XMLParser.parseRace(
RaceXMLData raceXMLData = XMLParser.parseRace(
StreamParser.extractXmlMessage(packet)
);
if (raceView != null) {
raceView.updateRaceData(courseData);
if (courseData == null) { //workaround for object comparisons. Avoid recreating
courseData = raceXMLData;
}
if (raceView != null) { //Token update
raceView.updateTokens(raceXMLData);
}
break;
@@ -262,11 +226,14 @@ public class GameClient {
clientLobbyList.add(boat.getBoatName())
);
raceState.setBoats(allBoatsMap.values());
if (lobbyController != null) {
lobbyController.setBoats(allBoatsMap);
}
break;
case RACE_START_STATUS:
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
if (lobbyController != null) lobbyController.updateRaceState(raceState);
if (lobbyController != null) Platform.runLater(() -> lobbyController.updateRaceState(raceState));
break;
case BOAT_LOCATION:
@@ -278,15 +245,37 @@ public class GameClient {
break;
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
YachtEventData yachtEventData = StreamParser.extractYachtEventCode(packet);
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN.getCode()) {
showPickUp();
}
break;
case CHATTER_TEXT:
Pair<Integer, String> playerIdMessagePair = StreamParser
.extractChatterText(packet);
raceView.updateChatHistory(
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
playerIdMessagePair.getValue()
);
}
}
}
private void startRaceIfAllDataReceived() {
if (allXMLReceived() && raceView == null) {
loadRaceView();
raceView = ViewManager.getInstance().loadRaceView();
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
raceView.loadRace(allBoatsMap, courseData, raceState, player);
raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> {
if (isPressed) {
formatAndSendChatMessage(raceView.readChatInput());
}
});
}
}
@@ -320,7 +309,6 @@ public class GameClient {
if (allXMLReceived()) {
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
clientYacht.roundMark(
courseData.getCompoundMarks().get(roundingData.getMarkId()),
roundingData.getTimeStamp(),
raceState.getRaceTime() - roundingData.getTimeStamp()
);
@@ -335,6 +323,9 @@ public class GameClient {
for (ClientYacht yacht : allBoatsMap.values()) {
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
raceFinished = false;
} else if (!finishedBoats.contains(yacht)) {
finishedBoats.add(yacht);
Sounds.playFinishSound();
}
}
@@ -350,9 +341,13 @@ public class GameClient {
}
if (raceFinished) {
raceViewController.showFinishDialog(finishedBoats);
Sounds.playFinishSound();
close();
loadFinishScreenView();
ViewManager.getInstance().getGameClient().stopGame();
//loadFinishScreenView();
}
raceState.setRaceFinished();
}
}
@@ -367,12 +362,17 @@ public class GameClient {
socketThread.setSocketToClose();
}
/**
* Handle the key-pressed event from the text field.
* @param e The key event triggering this call
*/
private void keyPressed(KeyEvent e) {
public void keyPressed(KeyEvent e) {
if (raceView.isChatInputFocused()) {
if (e.getCode() == KeyCode.ENTER) {
formatAndSendChatMessage(raceView.readChatInput());
}
return;
}
switch (e.getCode()) {
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
@@ -381,12 +381,16 @@ public class GameClient {
case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
// if chat box is active take whatever is in there and send it to server
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
}
}
private void keyReleased(KeyEvent e) {
public void keyReleased(KeyEvent e) {
if (raceView.isChatInputFocused()) {
return;
}
switch (e.getCode()) {
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
case SHIFT: // sails in/sails out
@@ -407,13 +411,49 @@ public class GameClient {
* Tells race view to show a collision animation.
*/
private void showCollisionAlert(YachtEventData yachtEventData) {
// 33 is the agreed code to show collision
if (yachtEventData.getEventId() == 33) {
raceState.storeCollision(
allBoatsMap.get(
yachtEventData.getSubjectId().intValue()
)
Sounds.playCrashSound();
raceState.storeCollision(
allBoatsMap.get(
yachtEventData.getSubjectId().intValue()
)
);
}
// TODO: 11/09/17 wmu16 - Add in functionality to viually indicate a pickup to a user
private void showPickUp() {
Sounds.playTokenPickupSound();
}
private void formatAndSendChatMessage(String rawChat) {
if (rawChat.length() > 0) {
socketThread.sendChatterMessage(
new SimpleDateFormat("[HH:mm:ss] ").format(new Date()) +
allBoatsMap.get(socketThread.getClientId()).getShortName() + ": " + rawChat
);
}
}
public void startGame(){
server.startGame();
}
public ClientToServerThread getServerThread() {
return socketThread;
}
public List<String> getPlayerNames(){
return Collections.unmodifiableList(clientLobbyList.sorted());
}
public void stopGame() {
GameState.setCurrentStage(GameStages.CANCELLED);
if (server != null) server.terminate();
if (socketThread != null) socketThread.setSocketToClose();
server = null;
// socketThread = null;
}
public Map<Integer, ClientYacht> getAllBoatsMap() {
return allBoatsMap;
}
}
+28 -432
View File
@@ -1,49 +1,25 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.util.Duration;
import seng302.model.ClientYacht;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.AnnotationBox;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.CourseBoundary;
import seng302.visualiser.fxObjects.Gate;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.Marker;
import seng302.visualiser.map.Boundary;
import seng302.visualiser.map.CanvasMap;
import seng302.visualiser.fxObjects.assets_2D.*;
import java.util.*;
/**
* Created by cir27 on 20/07/17.
@@ -51,8 +27,8 @@ import seng302.visualiser.map.CanvasMap;
public class GameView extends Pane {
private double bufferSize = 50;
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private double panelHeight = 960;
private double horizontalBuffer = 0;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
@@ -61,168 +37,31 @@ public class GameView extends Pane {
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private double metersPerPixelX, metersPerPixelY;
final double SCALE_DELTA = 1.1;
private Text fpsDisplay = new Text();
private Polygon raceBorder = new CourseBoundary();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker> markerObjects;
private Map<Mark, Marker2D> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
private ObservableList<Node> gameObjects;
private BoatObject selectedBoat = null;
private Group annotationsGroup = new Group();
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group trails = new Group();
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private ImageView mapImage = new ImageView();
//FRAME RATE
private AnimationTimer timer;
private int NUM_SAMPLES = 10;
private final long[] frameTimes = new long[NUM_SAMPLES];
private Double frameRate = 60.0;
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
private ClientYacht playerYacht;
private double windDir = 0.0;
double scaleFactor = 1;
private void zoomOut() {
scaleFactor = 0.1;
if (this.getScaleX() > 0.5) {
this.setScaleX(this.getScaleX() - scaleFactor);
this.setScaleY(this.getScaleY() - scaleFactor);
}
}
private void zoomIn() {
scaleFactor = 0.10;
if (this.getScaleX() < 2.5) {
this.setScaleX(this.getScaleX() + scaleFactor);
this.setScaleY(this.getScaleY() + scaleFactor);
}
}
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private void trackBoat() {
if (selectedBoat != null) {
double x = selectedBoat.getBoatLayoutX();
double y = selectedBoat.getBoatLayoutY();
double displacementX = this.getWidth();
double displacementY = this.getHeight();
this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX());
this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY());
} else {
this.setLayoutX(0);
this.setLayoutY(0);
}
}
public GameView () {
gameObjects = this.getChildren();
// create image view for map, bind panel size to image
gameObjects.add(mapImage);
gameObjects.add(raceBorder);
gameObjects.add(markers);
initializeTimer();
gameObjects.addAll(mapImage, raceBorder, markers, tokens);
}
private void initializeTimer() {
Arrays.fill(frameTimes, 1_000_000_000 / 60);
timer = new AnimationTimer() {
private long lastTime = 0;
private int FPSCount = 30;
private Double frameRate = 60.0;
private int index = 0;
private boolean arrayFilled = false;
private long sum = 1_000_000_000 / 3;
@Override
public void handle(long now) {
trackBoat();
if (lastTime == 0) {
lastTime = now;
} else {
if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized
long oldFrameTime = frameTimes[frameTimeIndex];
frameTimes[frameTimeIndex] = now;
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;
if (frameTimeIndex == 0) {
arrayFilled = true;
}
long elapsedNanos;
if (arrayFilled) {
elapsedNanos = now - oldFrameTime;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length;
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
if (FPSCount-- == 0) {
FPSCount = 30;
drawFps(frameRate);
}
}
lastTime = now;
}
}
boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
}
};
}
/**
* First find the top right and bottom left points' geo locations, then retrieve map from google
* to display on image view. - Haoming 22/5/2017
*/
private void drawGoogleMap() {
findMetersPerPixel();
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLat(), minLonPoint.getLng());
// distance from top left extreme to panel origin (top left corner)
double distanceFromTopLeftToOrigin = Math.sqrt(
Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math
.pow(topLeftPoint.getY() * metersPerPixelY, 2));
// angle from top left extreme to panel origin
double bearingFromTopLeftToOrigin = Math
.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
// the top left extreme
GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
GeoPoint originPos = GeoUtility
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
// distance from origin corner to bottom right corner of the panel
double distanceFromOriginToBottomRight = Math.sqrt(
Math.pow(panelHeight * metersPerPixelY, 2) + Math
.pow(panelWidth * metersPerPixelX, 2));
double bearingFromOriginToBottomRight = Math
.toDegrees(Math.atan2(panelWidth, -panelHeight));
GeoPoint bottomRightPos = GeoUtility
.getGeoCoordinate(originPos, bearingFromOriginToBottomRight,
distanceFromOriginToBottomRight);
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(),
bottomRightPos.getLat(), originPos.getLng());
CanvasMap canvasMap = new CanvasMap(boundary);
mapImage.setImage(canvasMap.getMapImage());
mapImage.fitWidthProperty().bind(((AnchorPane) this.getParent()).heightProperty());
mapImage.fitHeightProperty().bind(((AnchorPane) this.getParent()).heightProperty());
}
// TODO: 16/08/17 Break up this function
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
@@ -285,10 +124,10 @@ public class GameView extends Pane {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
@@ -306,18 +145,16 @@ public class GameView extends Pane {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = 0;
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
@@ -380,9 +217,9 @@ public class GameView extends Pane {
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker marker = new Marker(colour);
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker);
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
@@ -398,7 +235,7 @@ public class GameView extends Pane {
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker m1, Marker m2, Paint colour) {
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
@@ -426,6 +263,9 @@ public class GameView extends Pane {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
rescaleRace(new ArrayList<>(border));
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
@@ -435,122 +275,16 @@ public class GameView extends Pane {
raceBorder.getPoints().setAll(boundaryPoints);
}
// TODO: 16/08/17 initialize zooming internal to GameView only
/**
* Enables zoom. Has to be called after this is added to a scene.
*/
public void enableZoom () {
if (this.getScene() != null) {
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
if (event.getCode() == KeyCode.Z) {
zoomIn();
} else if (event.getCode() == KeyCode.X) {
zoomOut();
}
});
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
public void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
}
private void setSelectedBoat(BoatObject bo, Boolean isSelected) {
if (this.selectedBoat == bo && !isSelected) {
this.selectedBoat = null;
boatObjects.forEach((boat, group) ->
group.setIsSelected(false)
);
} else if (isSelected) {
this.selectedBoat = bo;
for (BoatObject group : boatObjects.values()) {
if (group != bo) {
group.setIsSelected(false);
}
}
}
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
*/
public void setBoats(List<ClientYacht> yachts) {
BoatObject newBoat;
final List<Group> wakes = new ArrayList<>();
for (ClientYacht clientYacht : yachts) {
Paint colour = clientYacht.getColour();
newBoat = new BoatObject();
newBoat.addSelectedBoatListener(this::setSelectedBoat);
newBoat.setFill(colour);
boatObjects.put(clientYacht, newBoat);
createAndBindAnnotationBox(clientYacht, colour);
// wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
trails.getChildren().add(newBoat.getTrail());
// TODO: 1/08/17 Make this less vile to look at.
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
bo.setTrajectory(
heading,
velocity,
metersPerPixelX,
metersPerPixelY);
});
}
annotationsGroup.getChildren().addAll(annotations.values());
Platform.runLater(() -> {
gameObjects.addAll(trails);
gameObjects.addAll(wakes);
gameObjects.addAll(annotationsGroup);
gameObjects.addAll(boatObjectGroup);
});
}
private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) {
AnnotationBox newAnnotation = new AnnotationBox();
newAnnotation.setFill(colour);
newAnnotation.addAnnotation(
"name", "Player: " + clientYacht.getShortName()
);
// newAnnotation.addAnnotation(
// "velocity",
// yacht.getVelocityProperty(),
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
// );
// newAnnotation.addAnnotation(
// "nextMark",
// yacht.timeTillNextProperty(),
// (time) -> {
// DateFormat format = new SimpleDateFormat("mm:ss");
// return format.format(time);
// }
// );
// newAnnotation.addAnnotation(
// "lastMark",
// yacht.timeTillNextProperty(),
// (time) -> {
// DateFormat format = new SimpleDateFormat("mm:ss");
// return format.format(time);
// }
// );
annotations.put(clientYacht, newAnnotation);
}
private void drawFps(Double fps) {
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
}
/**
@@ -613,6 +347,7 @@ public class GameView extends Pane {
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
referencePointX += horizontalBuffer;
}
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
@@ -654,10 +389,6 @@ public class GameView extends Pane {
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
@@ -700,148 +431,13 @@ public class GameView extends Pane {
return new Point2D(xAxisLocation, yAxisLocation);
}
/**
* Find the number of meters per pixel.
*/
private void findMetersPerPixel() {
Point2D p1, p2;
GeoPoint g1, g2;
double theta, distance, dx, dy, dHorizontal, dVertical;
g1 = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
g2 = new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng());
p1 = findScaledXY(new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()));
p2 = findScaledXY(new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng()));
theta = GeoUtility.getBearingRad(g1, g2);
distance = GeoUtility.getDistance(g1, g2);
dHorizontal = Math.abs(Math.sin(theta) * distance);
dVertical = Math.abs(Math.cos(theta) * distance);
dx = Math.abs(p1.getX() - p2.getX());
dy = Math.abs(p1.getY() - p2.getY());
metersPerPixelX = dHorizontal / dx;
metersPerPixelY = dVertical / dy;
public void setSize(Double width, Double height){
this.canvasWidth = width;
this.canvasHeight = height;
}
public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime,
boolean legTime, boolean trail, boolean wake) {
for (BoatObject boatObject : boatObjects.values()) {
boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
}
for (AnnotationBox ag : annotations.values()) {
ag.setAnnotationVisibility("name", teamName);
ag.setAnnotationVisibility("velocity", velocity);
ag.setAnnotationVisibility("nextMark", estTime);
ag.setAnnotationVisibility("lastMark", legTime);
}
}
public void setFPSVisibility(boolean visibility) {
fpsDisplay.setVisible(visibility);
}
public void selectBoat(ClientYacht selectedClientYacht) {
boatObjects.forEach((boat, group) ->
group.setIsSelected(boat == selectedClientYacht)
);
}
public void pauseRace() {
timer.stop();
}
public void setWindDir(double windDir) {
this.windDir = windDir;
}
public void startRace() {
timer.start();
}
public ClientYacht getPlayerYacht() {
return playerYacht;
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
this.playerYacht = playerYacht;
playerYacht.toggleSail();
boatObjects.get(playerYacht).setAsPlayer();
CompoundMark currentMark = course.get(playerYacht.getLegNumber());
for (Mark mark : currentMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
annotations.get(playerYacht).addAnnotation(
"velocity",
playerYacht.getVelocityProperty(),
(velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
);
Platform.runLater(() -> {
boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht));
gameObjects.add(boatObjects.get(playerYacht));
annotationsGroup.getChildren().remove(annotations.get(playerYacht));
gameObjects.add(annotations.get(playerYacht));
});
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
}
private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) {
//Only show arrows for this and next leg.
if (compoundMark != null) {
for (Mark mark : compoundMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
}
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
}
}
if (legNumber - 2 >= 0) {
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
if (lastMark != nextMark) {
for (Mark mark : lastMark.getMarks()) {
markerObjects.get(mark).hideAllArrows();
}
}
}
}
/**
* Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and
* display a flashing red circle on collision point.
*
* @param collisionPoint yacht collision point
*/
public void drawCollision(GeoPoint collisionPoint) {
Point2D point = findScaledXY(collisionPoint);
double circleRadius = 0.0;
Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED);
circle.setFill(Color.TRANSPARENT);
circle.setStroke(Color.RED);
circle.setStrokeWidth(3);
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
KeyFrame keyframe1 = new KeyFrame(Duration.ZERO,
new KeyValue(circle.radiusProperty(), 0),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
KeyFrame keyFrame2 = new KeyFrame(new Duration(1000),
new KeyValue(circle.radiusProperty(), 50),
new KeyValue(circle.strokeProperty(), Color.RED));
KeyFrame keyFrame3 = new KeyFrame(new Duration(1500),
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3);
Platform.runLater(() -> gameObjects.add(circle));
timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle)));
timeline.play();
}
public void setFrameRateFXText(Text fpsDisplay) {
this.fpsDisplay = null;
this.fpsDisplay = fpsDisplay;
public void setHorizontalBuffer(Double buff){
this.horizontalBuffer = buff;
}
}
@@ -0,0 +1,632 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.utilities.GeoUtility;
import seng302.utilities.Sounds;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
import seng302.visualiser.fxObjects.assets_3D.Marker3D;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
/**
* Collection of animated3D assets that displays a race.
*/
public class GameView3D {
private final double FOV = 60;
private final double DEFAULT_CAMERA_DEPTH = -125;
private final double DEFAULT_CAMERA_X = 0;
private final double DEFAULT_CAMERA_Y = 155;
private Group root3D;
private SubScene view;
// ParallelCamera camera;
private PerspectiveCamera camera;
private Group gameObjects;
private double bufferSize = 0;
private double canvasWidth = 200;
private double canvasHeight = 200;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private Group raceBorder = new Group();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker3D> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private BoatObject selectedBoat = null;
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private List<Node> mapTokens;
private AnimationTimer playerBoatAnimationTimer;
private Group trail = new Group();
private Double windDir;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public GameView3D () {
camera = new PerspectiveCamera(true);
camera.getTransforms().addAll(
new Translate(DEFAULT_CAMERA_X,DEFAULT_CAMERA_Y, DEFAULT_CAMERA_DEPTH)
);
camera.setFarClip(600);
camera.setNearClip(0.1);
camera.setFieldOfView(FOV);
gameObjects = new Group();
root3D = new Group(camera, gameObjects);
view = new SubScene(
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
);
view.setCamera(camera);
camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0)));
gameObjects.getChildren().addAll(
ModelFactory.importModel(ModelType.OCEAN).getAssets(),
raceBorder, trail, markers, tokens
);
view.sceneProperty().addListener((obs, old, scene) -> {
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
}
});
}
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Group> gates = new ArrayList<>();
//Creates new markers
for (CompoundMark cMark : newCourse) {
for (Mark mark : cMark.getMarks()) {
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
makeAndBindMarker(mark, ModelType.START_MARKER);
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
makeAndBindMarker(mark, ModelType.FINISH_MARKER);
} else {
makeAndBindMarker(mark, ModelType.PLAIN_MARKER);
}
}
//Create gate line
if (cMark.isGate()) {
ModelType gateType;
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
gateType = ModelType.START_LINE;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
gateType = ModelType.FINISH_LINE;
} else {
gateType = ModelType.GATE_LINE;
}
gates.add(makeGate(
cMark.getSubMark(1), cMark.getSubMark(2), gateType
));
}
}
createMarkArrows();
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param markerType the type of marker as a ModelType. Should be PLAIN_MARKER, START_MARKER or END_MARKER
*/
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
markerObjects.put(observableMark, new Marker3D(markerType));
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param gateType The type of model for the gate.
* @return the new gate.
*/
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
Point2D m1Location = findScaledXY(m1);
Point2D m2Location = findScaledXY(m2);
Group barrier = ModelFactory.importModel(gateType).getAssets();
barrier.getTransforms().addAll(
new Rotate(
Math.toDegrees(
Math.atan2(m2Location.getY() - m1Location.getY(), m2Location.getX() - m1Location.getX())
) + 90,
new Point3D(0,0,1)
),
new Scale(1, m1Location.distance(m2Location) / 10, 1)
);
Point2D midPoint = m2Location.midpoint(m1Location);
barrier.setLayoutX(midPoint.getX());
barrier.setLayoutY(midPoint.getY());
return barrier;
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
*/
private void createMarkArrows () {
for (int i=1; i < course.size()-1; i++) { //General case.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(course.get(i-1).getMidPoint(), mark),
GeoUtility.getBearing(mark, course.get(i+1).getMidPoint())
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, course.get(1).getMidPoint())
);
}
}
private void createFinishLineArrows () {
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(course.get(course.size()-2).getMidPoint(), mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
-100 + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = -100 + canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = -100 + canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = -100 + bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = -100 + canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void cameraMovement(KeyEvent event) {
switch (event.getCode()) {
case NUMPAD8:
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(1,0,0)));
break;
case NUMPAD2:
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(1,0,0)));
break;
case NUMPAD4:
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(0,1,0)));
break;
case NUMPAD6:
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(0,1,0)));
break;
case Z:
camera.getTransforms().addAll(new Translate(0, 0, 1.5));
break;
case X:
camera.getTransforms().addAll(new Translate(0, 0, -1.5));
break;
case W:
camera.getTransforms().addAll(new Translate(0, -1, 0));
break;
case S:
camera.getTransforms().addAll(new Translate(0, 1, 0));
break;
case A:
camera.getTransforms().addAll(new Translate(-1, 0, 0));
break;
case D:
camera.getTransforms().addAll(new Translate(1, 0, 0));
break;
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
*/
public void setBoats(List<ClientYacht> yachts) {
BoatObject newBoat;
final List<Group> wakes = new ArrayList<>();
for (ClientYacht clientYacht : yachts) {
Color colour = clientYacht.getColour();
newBoat = new BoatObject();
newBoat.setFill(colour);
boatObjects.put(clientYacht, newBoat);
wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
});
}
Platform.runLater(() -> {
gameObjects.getChildren().addAll(wakes);
gameObjects.getChildren().addAll(boatObjectGroup);
});
}
public Node getAssets () {
return view;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
List<Node> boundaryAssets = new ArrayList<>();
Point2D lastLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(lastLocation.getX());
pylon.setLayoutY(lastLocation.getY());
boundaryAssets.add(pylon);
for (int i=1; i<border.size(); i++) {
Point2D location = findScaledXY(border.get(i).getLat(), border.get(i).getLng());
pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(location.getX());
pylon.setLayoutY(location.getY());
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
barrier.getTransforms().addAll(
new Rotate(
Math.toDegrees(
Math.atan2(location.getY() - lastLocation.getY(), location.getX() - lastLocation.getX())
),
new Point3D(0,0,1)
),
new Scale((lastLocation.distance(location) / 10)-0.2, 1, 1)
);
Point2D midPoint = location.midpoint(lastLocation);
barrier.setLayoutX(midPoint.getX());
barrier.setLayoutY(midPoint.getY());
lastLocation = location;
boundaryAssets.add(barrier);
boundaryAssets.add(pylon);
}
Point2D firstLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
barrier.getTransforms().addAll(
new Rotate(
Math.toDegrees(
Math.atan2(lastLocation.getY() - firstLocation.getY(), lastLocation.getX() - firstLocation.getX())
),
new Point3D(0,0,1)
),
new Scale((firstLocation.distance(lastLocation) / 10)-0.2, 1, 1)
);
Point2D midPoint = lastLocation.midpoint(firstLocation);
barrier.setLayoutX(midPoint.getX());
barrier.setLayoutY(midPoint.getY());
boundaryAssets.add(barrier);
Platform.runLater(() -> raceBorder.getChildren().setAll(boundaryAssets));
}
/**
* Replaces all tokens in the course with those passed in
*
* @param newTokens the tokens to be put on the course.
*/
public void updateTokens(List<Token> newTokens) {
mapTokens = new ArrayList<>();
for (Token token : newTokens) {
Point2D location = findScaledXY(token.getLat(), token.getLng());
Node tokenObject = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
tokenObject.setLayoutX(location.getX());
tokenObject.setLayoutY(location.getY());
mapTokens.add(tokenObject);
}
Platform.runLater(() -> {
tokens.getChildren().setAll(mapTokens);
});
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
playerBoatAnimationTimer = new AnimationTimer() {
double count = 60;
Point2D lastLocation = findScaledXY(playerYacht.getLocation());
@Override
public void handle(long now) {
if (--count == 0) {
count = 60;
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
Point2D location = findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY(), 0),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)),
new Scale(1, lastLocation.distance(location) / 5, 1)
);
trail.getChildren().add(segment);
if (trail.getChildren().size() > 50) {
trail.getChildren().remove(0);
}
lastLocation = location;
}
}
};
playerBoatAnimationTimer.start();
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
boatObjects.get(playerYacht).addSelectedBoatListener((boatObject, isSelected) -> {
System.out.println("IS SELECTED " + isSelected);
});
}
public void setWindDir(double windDir) {
this.windDir = windDir;
}
private void updateMarkArrows (ClientYacht yacht, int legNumber) {
CompoundMark compoundMark;
if (legNumber - 1 >= 0) {
Sounds.playMarkRoundingSound();
compoundMark = course.get(legNumber-1);
for (Mark mark : compoundMark.getMarks()) {
markerObjects.get(mark).showNextExitArrow();
}
}
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
Sounds.playMarkRoundingSound();
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
}
}
if (legNumber - 2 >= 0) {
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
if (lastMark != nextMark) {
for (Mark mark : lastMark.getMarks()) {
markerObjects.get(mark).hideAllArrows();
}
}
}
}
}
@@ -0,0 +1,113 @@
package seng302.visualiser;
import seng302.gameServer.ServerAdvertiser;
import seng302.gameServer.ServerDescription;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
/**
* Listens for servers on the local network
*/
public class ServerListener{
private static ServerListener instance;
private ServerListenerDelegate delegate;
private JmDNS jmdns = null;
GameServeMonitor listener;
private class GameServeMonitor implements ServiceListener {
private Set<ServerDescription> servers;
GameServeMonitor(){
servers = new HashSet<>();
}
/**
* A Service has been detected but not resolved
* @param event The ServiceEvent
*/
@Override
public void serviceAdded(ServiceEvent event) {
}
/**
* A Service has been removed / unregistered
* @param event The ServiceEvent
*/
@Override
public void serviceRemoved(ServiceEvent event) {
String serverName = event.getInfo().getName();
ServerDescription toRemove = null;
for (ServerDescription server : servers){
if (server.getName().equals(serverName)){
toRemove = server;
}
}
if (toRemove != null){
servers.remove(toRemove);
}
delegate.serverRemoved(new ArrayList<ServerDescription>(servers));
// Get all other servers with the same name to respond if they are up
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverName);
}
/**
* A Service has been added and resolved
* @param event The ServiceEvent
*/
@Override
public void serviceResolved(ServiceEvent event) {
String address = event.getInfo().getServer();
Integer portNum = event.getInfo().getPort();
String serverName = event.getInfo().getName();
String mapName = event.getInfo().getPropertyString("map");
Integer capacity = Integer.parseInt(event.getInfo().getPropertyString("capacity"));
Integer numPlayers = Integer.parseInt(event.getInfo().getPropertyString("players"));
ServerDescription serverDescription = new ServerDescription(serverName, mapName, numPlayers, capacity, address, portNum);
servers.remove(serverDescription);
servers.add(serverDescription);
delegate.serverDetected(serverDescription, new ArrayList<>(servers));
}
}
private ServerListener() throws IOException {
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
listener = new GameServeMonitor();
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
}
public static ServerListener getInstance() throws IOException {
if (instance == null){
instance = new ServerListener();
}
return instance;
}
/**
* Set the delegate to handle events
* @param delegate .
*/
public void setDelegate(ServerListenerDelegate delegate){
this.delegate = delegate;
}
}
@@ -0,0 +1,10 @@
package seng302.visualiser;
import seng302.gameServer.ServerDescription;
import java.util.List;
public interface ServerListenerDelegate {
void serverRemoved(List<ServerDescription> currentServers);
void serverDetected(ServerDescription serverDescription, List<ServerDescription> servers);
}
@@ -1,74 +0,0 @@
package seng302.visualiser.controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.visualiser.ClientToServerThread;
public class CustomizationController {
@FXML
private TextField nameField;
@FXML
private ColorPicker boatColorPicker;
@FXML
private Button customizeSubmit;
private LobbyController lc;
private ClientToServerThread socketThread;
private Stage windowStage;
public void initialize() {
}
public void setServerThread(ClientToServerThread ctsThread) {
this.socketThread = ctsThread;
}
@FXML
public void submitCustomization() {
System.out.println("Attempting to send");
socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes());
// TODO: 16/08/17 ajm412: Turn colors into byte array.
Color color = boatColorPicker.getValue();
short red = (short) (color.getRed() * 255);
short green = (short) (color.getGreen() * 255);
short blue = (short) (color.getBlue() * 255);
byte[] colorArray = new byte[3];
colorArray[0] = (byte) red;
colorArray[1] = (byte) green;
colorArray[2] = (byte) blue;
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
lc.setPlayersColor(color);
windowStage.close();
}
public void setLobbyController(LobbyController lc) {
this.lc = lc;
}
public void setStage(Stage stage) {
this.windowStage = stage;
}
public void setPlayerName(String name) {
this.nameField.setText(name);
}
public void setPlayerColor(Color playerColor) {
this.boatColorPicker.setValue(playerColor);
}
}
@@ -15,10 +15,12 @@ import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import seng302.model.ClientYacht;
import seng302.utilities.Sounds;
public class FinishScreenViewController implements Initializable {
@@ -40,8 +42,8 @@ public class FinishScreenViewController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
finishScreenGridPane.getStylesheets()
.add(getClass().getResource("/css/master.css").toString());
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
.add(getClass().getResource("/css/Master.css").toString());
finishOrderTable.getStylesheets().add(getClass().getResource("/css/Master.css").toString());
// set up data for table
finishOrderTable.setItems(data);
@@ -85,6 +87,12 @@ public class FinishScreenViewController implements Initializable {
}
public void switchToStartScreenView() {
Sounds.playButtonClick();
//TODO merge fix
setContentPane("/views/StartScreenView.fxml");
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -1,248 +1,245 @@
package seng302.visualiser.controllers;
import com.sun.media.jfxmedia.logging.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.Limit;
import seng302.model.RaceState;
import seng302.visualiser.ClientToServerThread;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.cells.PlayerCell;
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
/**
* A class describing the actions of the lobby screen
* Created by wmu16 on 10/07/17.
*/
public class LobbyController {
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
public enum CloseStatus {
LEAVE,
READY
}
public class LobbyController implements Initializable {
@FunctionalInterface
public interface LobbyCloseListener {
void notify(CloseStatus exitCause);
}
//--------FXML BEGIN--------//
@FXML
private VBox playerListVBox;
@FXML
private ScrollPane playerListScrollPane;
@FXML
private JFXButton customizeButton, leaveLobbyButton, beginRaceButton;
@FXML
private StackPane serverListMainStackPane;
@FXML
private Label serverName;
@FXML
private Label mapName;
@FXML
private Pane serverMap;
//---------FXML END---------//
@FXML
private Text lobbyIpText;
@FXML
private Button readyButton;
@FXML
private Button customizeButton;
@FXML
private TextArea playerOneTxt;
@FXML
private TextArea playerTwoTxt;
@FXML
private TextArea playerThreeTxt;
@FXML
private TextArea playerFourTxt;
@FXML
private TextArea playerFiveTxt;
@FXML
private TextArea playerSixTxt;
@FXML
private TextArea playerSevenTxt;
@FXML
private TextArea playerEightTxt;
@FXML
private ImageView firstImageView;
@FXML
private ImageView secondImageView;
@FXML
private ImageView thirdImageView;
@FXML
private ImageView fourthImageView;
@FXML
private ImageView fifthImageView;
@FXML
private ImageView sixthImageView;
@FXML
private ImageView seventhImageView;
@FXML
private ImageView eighthImageView;
@FXML
private Text timeUntilStart;
@FXML
private Text courseNameText;
private List<ImageView> imageViews = new ArrayList<>();
private List<TextArea> listViews = new ArrayList<>();
private RaceState raceState;
private JFXDialog customizationDialog;
public Color playersColor;
private Map<Integer, ClientYacht> playerBoats;
private Double mapWidth, mapHeight;
private GameView gameView;
private ClientToServerThread socketThread;
@Override
public void initialize(URL location, ResourceBundle resources) {
private Stage customizeStage;
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
private Color playersColor;
if (this.playersColor == null) {
this.playersColor = Colors.getColor(ViewManager.getInstance().getGameClient().getServerThread().getClientId() - 1);
}
private int MAX_NUM_PLAYERS = 8;
private Integer playerID;
leaveLobbyButton.setOnMouseReleased(event -> leaveLobby());
beginRaceButton.setOnMouseReleased(event -> beginRace());
leaveLobbyButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
leaveLobby();
});
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
private ObservableList<String> players;
beginRaceButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
beginRace();
});
/**
* Add all FXObjects to lists and initialize images.
*/
public void initialize() {
Collections.addAll(listViews,
playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
playerSevenTxt, playerEightTxt
);
Collections.addAll(imageViews,
firstImageView, secondImageView, thirdImageView, fourthImageView,
fifthImageView, sixthImageView, seventhImageView, eighthImageView
);
initialiseImageView();
Platform.runLater(() -> {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
timeUntilStart.setText("Waiting For Host...");
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
});
Platform.runLater(() -> {
Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId();
playersColor = Colors.getColor(playerId - 1);
customizationDialog = createCustomizeDialog();
customizeButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
customizationDialog.show();
});
});
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
customizeButton.setOnMouseEntered(e -> Sounds.playHoverSound());
beginRaceButton.setOnMouseEntered(e -> Sounds.playHoverSound());
initMapPreview();
}
/**
* Updates player names.
*/
private void updatePlayers() {
//Update players if one added.
for (int i = 0; i < players.size(); i++) {
listViews.get(i).setText(players.get(i));
if (playerID == (i + 1)) {
listViews.get(i).setText(listViews.get(i).getText() + " (YOU)");
}
imageViews.get(i).setVisible(true);
}
//Update empty text fields if player left.
for (int i = MAX_NUM_PLAYERS-1; i >= players.size(); i--) {
listViews.get(i).setText("");
imageViews.get(i).setVisible(false);
}
}
private JFXDialog createCustomizeDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
/**
* Sets all images and hides them till players join.
*/
private void initialiseImageView() {
for (ImageView viewer : imageViews) {
viewer.setImage(
new Image(
RaceViewController.class.getResourceAsStream(
"/pics/sail.png")
)
);
viewer.setVisible(false);
}
}
JFXDialog customizationDialog = null;
@FXML
public void customize() {
Parent root;
try {
FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml"));
root = fxmlLoader.load();
root.getStylesheets().add("/css/master.css");
customizeStage = new Stage();
customizeStage.setTitle("Customize Boat");
customizeStage.setScene(new Scene(root, 700, 450));
CustomizationController cc = fxmlLoader.getController();
cc.setServerThread(this.socketThread);
cc.setPlayerName(this.players.get(playerID - 1));
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
if (this.playersColor == null) {
this.playersColor = Colors.getColor(playerID - 1);
BoatCustomizeController controller = dialog.getController();
controller.setParentController(this);
controller.setPlayerColor(this.playersColor);
controller.setPlayerName(this.playerBoats
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
.getBoatName());
return customizationDialog;
}
/**
*
*/
private void refreshMapView(){
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<CompoundMark>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
gameView.setSize(mapWidth, mapHeight);
// Update game view
gameView.updateBorder(border);
gameView.updateCourse(marks, corners);
}
/**
* Initializes a top down preview of the race course map.
*/
private void initMapPreview() {
gameView = new GameView();
gameView.setHorizontalBuffer(330d);
mapWidth = 770d;
mapHeight = 574d;
// Add game view
serverMap.getChildren().clear();
serverMap.getChildren().add(gameView);
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
mapWidth = newValue.doubleValue();
refreshMapView();
});
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
mapHeight = newValue.doubleValue();
refreshMapView();
});
}
/**
*
*/
private void beginRace() {
beginRaceButton.setDisable(true);
customizeButton.setDisable(true);
GameState.setCurrentStage(GameStages.PRE_RACE);
GameState.resetStartTime();
Platform.runLater(()-> ViewManager.getInstance().getGameClient().startGame());
}
/**
* Refreshes the list of players and their boats, as a series of VBox PlayerCell objects.
*/
private void refreshPlayerList() {
playerListVBox.getChildren().clear();
if (this.playerBoats == null || this.playerBoats.size() == 0) {
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
}
// TODO: 12/09/2017 ajm412: Make it so that it only removes players who's details have changed.
for (Integer playerId : playerBoats.keySet()) {
VBox pane = null;
ClientYacht yacht = playerBoats.get(playerId);
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/views/cells/PlayerCell.fxml"));
loader.setController(new PlayerCell(playerId, yacht.getBoatName(), yacht.getColour()));
try {
pane = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
cc.setPlayerColor(this.playersColor);
cc.setStage(customizeStage); // pass the stage through so it can be closed later.
cc.setLobbyController(this);
customizeStage.show();
} catch (IOException e) {
Logger.logMsg(4, "Failed to load Customization View from resources.");
playerListVBox.getChildren().add(pane);
}
}
public void setSocketThread(ClientToServerThread thread) {
this.socketThread = thread;
private void leaveLobby() {
ViewManager.getInstance().getGameClient().stopGame();
ViewManager.getInstance().goToStartView();
}
@FXML
public void leaveLobbyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function!
GameState.setCurrentStage(GameStages.CANCELLED);
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.LEAVE);
}
@FXML
public void readyButtonPressed() {
GameState.setCurrentStage(GameStages.PRE_RACE);
// Do countdown logic here
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.READY);
customizeButton.setDisable(true);
}
public void setTitle (String title) {
lobbyIpText.setText(title);
}
public void setCourseName(String courseName){
courseNameText.setText(courseName);
}
public void addCloseListener(LobbyCloseListener listener) {
lobbyListeners.add(listener);
}
public void setPlayerListSource (ObservableList<String> players) {
this.players = players;
players.addListener((ListChangeListener<? super String>) (lcl) ->
Platform.runLater(this::updatePlayers)
);
Platform.runLater(this::updatePlayers);
}
public void setPlayerID(Integer id) {
playerID = id;
public void disableReadyButton() {
this.beginRaceButton.setDisable(true);
this.beginRaceButton.setText("Waiting for host...");
}
/**
*
* @param raceState
*/
public void updateRaceState(RaceState raceState){
this.raceState = raceState;
/*if (this.customizeStage != null) {
this.customizeStage.close();
}*/ // TODO: 17/08/17 ajm412: close the customization window if the host starts the game while customizing
if (!customizeButton.isDisabled()) {
customizeButton.setDisable(true);
}
timeUntilStart.setText("Starting in: " + raceState.getRaceTimeStr());
this.beginRaceButton.setText("Starting in: " + raceState.getRaceTimeStr());
}
public void disableReadyButton () {
readyButton.setDisable(true);
readyButton.setVisible(false);
public void setBoats(Map<Integer, ClientYacht> boats) {
this.playerBoats = boats;
}
public void setPlayersColor(Color playerColor) {
this.playersColor = playerColor;
public void closeCustomizationDialog() {
customizationDialog.close();
}
}
@@ -1,5 +1,7 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -9,6 +11,9 @@ import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -16,6 +21,7 @@ import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
@@ -24,35 +30,52 @@ import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polyline;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.StringConverter;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.ClientYacht;
import seng302.model.RaceState;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.annotations.Annotation;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView3D;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.controllers.dialogs.FinishDialogController;
import seng302.visualiser.fxObjects.ChatHistory;
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
/**
* Controller class that manages the display of a race
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
private final int CHAT_LIMIT = 128;
@FXML
private Pane basePane;
@FXML
private JFXButton chatSend;
@FXML
private Pane chatHistoryHolder;
@FXML
private TextField chatInput;
@FXML
private LineChart<String, Double> raceSparkLine;
@FXML
@@ -62,11 +85,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML
private CheckBox toggleFps;
@FXML
private Text timerLabel;
private Label timerLabel;
@FXML
private AnchorPane contentAnchorPane;
private StackPane contentAnchorPane;
private GridPane contentGridPane;
@FXML
private Text windArrowText, windDirectionText;
private AnchorPane rvAnchorPane;
@FXML
private AnchorPane windArrowHolder;
@FXML
private Slider annotationSlider;
@FXML
@@ -76,35 +103,123 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
@FXML
private Text fpsDisplay;
@FXML
private Text windSpeedText;
private ImageView windImageView;
@FXML
private Label windDirectionLabel;
@FXML
private Label windSpeedLabel;
@FXML
private Label positionLabel, boatSpeedLabel, boatHeadingLabel;
//Race Data
private Map<Integer, ClientYacht> participants;
private Map<Integer, CompoundMark> markers;
private RaceXMLData courseData;
private GameView gameView;
private GameView3D gameView;
private RaceState raceState;
private ChatHistory chatHistory;
private Timeline timerTimeline;
private Timer timer = new Timer();
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private Polyline windArrow = new WindArrow(Color.LIGHTGRAY);
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
private ClientYacht player;
private JFXDialog finishScreenDialog;
private FinishDialogController finishDialogController;
public void initialize() {
Sounds.stopMusic();
Sounds.playRaceMusic();
finishScreenDialog = createFinishDialog();
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
//importantAnnotations = new ImportantAnnotationsState();
//Formatting the y axis of the sparkline
// raceSparkLine.getYAxis().setRotate(180);
// raceSparkLine.getYAxis().setTickLabelRotation(180);
// raceSparkLine.getYAxis().setTranslateX(-5);
raceSparkLine.visibleProperty().setValue(false);
raceSparkLine.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false);
//raceSparkLine.visibleProperty().setValue(false);
//raceSparkLine.getYAxis().setAutoRanging(false);
//sparklineYAxis.setTickMarkVisible(false);
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
//positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// raceSparkLine.visibleProperty().setValue(false);
// raceSparkLine.getYAxis().setAutoRanging(false);
// sparklineYAxis.setTickMarkVisible(false);
// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
//selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty());
// rvAnchorPane.prefHeightProperty().bind(ViewManager.getInstance().getDecorator().heightProperty());
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// windArrowHolder.getChildren().addAll(windArrow);
// windArrow.setLayoutX(windArrowHolder.getWidth() / 2);
// windArrow.setLayoutY(windArrowHolder.getHeight() / 2);
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
if (newLen.intValue() > CHAT_LIMIT) {
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
}
});
chatHistory = new ChatHistory();
chatHistoryHolder.getChildren().addAll(chatHistory);
chatHistory.prefWidthProperty().bind(
chatHistoryHolder.widthProperty()
);
chatHistory.prefHeightProperty().bind(
chatHistoryHolder.heightProperty()
);
// chatHistory.setFitToWidth(true);
// chatHistory.setFitToHeight(true);
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
// chatHistory.setScrollTop(Double.MAX_VALUE);
// });
rvAnchorPane.setOnMouseClicked((event) ->
rvAnchorPane.requestFocus()
);
//Makes the chat history non transparent when clicked on
chatInput.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
}
});
}
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
raceState.setRaceStarted(false);
finishDialogController.setFinishedBoats(finishedBoats);
finishScreenDialog.show();
}
private JFXDialog createFinishDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/RaceFinishDialog.fxml"));
JFXDialog finishScreenDialog = null;
try {
finishScreenDialog = new JFXDialog(contentAnchorPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
finishDialogController = dialog.getController();
return finishScreenDialog;
}
public void loadRace (
@@ -115,12 +230,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
this.courseData = raceData;
this.markers = raceData.getCompoundMarks();
this.raceState = raceState;
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
initialiseSparkLine();
this.player = player;
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
while (c.next()) {
@@ -132,29 +242,41 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
});
updateOrder(raceState.getPlayerPositions());
gameView = new GameView();
gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
gameView = new GameView3D();
// gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> {
contentAnchorPane.getChildren().add(0, gameView.getAssets());
((SubScene) gameView.getAssets()).widthProperty()
.bind(ViewManager.getInstance().getStage().widthProperty());
((SubScene) gameView.getAssets()).heightProperty()
.bind(ViewManager.getInstance().getStage().heightProperty());
});
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateTokens(raceData.getTokens());
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
gameView.enableZoom();
// gameView.enableZoom();
gameView.setBoatAsPlayer(player);
gameView.startRace();
// gameView.startRace();
raceState.addCollisionListener(gameView::drawCollision);
// raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
updateWindDirection(newDirection.doubleValue());
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
});
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
updateWindSpeed(newSpeed.doubleValue());
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) ->
Platform.runLater(() -> updateWindSpeed(newSpeed.doubleValue()))
);
Platform.runLater(() -> {
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
});
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
Platform.runLater(() -> {
initializeUpdateTimer();
});
}
/**
@@ -196,46 +318,46 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
private void initialiseFPSCheckBox() {
toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
gameView.setFPSVisibility(toggleFps.isSelected())
);
// toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
// gameView.setFPSVisibility(toggleFps.isSelected())
// );
}
private void initialiseAnnotationSlider() {
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override
public String toString(Double n) {
if (n == 0) {
return "None";
}
if (n == 1) {
return "Important";
}
if (n == 2) {
return "All";
}
return "All";
}
@Override
public Double fromString(String s) {
switch (s) {
case "None":
return 0d;
case "Important":
return 1d;
case "All":
return 2d;
default:
return 2d;
}
}
});
annotationSlider.setValue(2);
annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
setAnnotations((int) annotationSlider.getValue())
);
// annotationSlider.setLabelFormatter(new StringConverter<Double>() {
// @Override
// public String toString(Double n) {
// if (n == 0) {
// return "None";
// }
// if (n == 1) {
// return "Important";
// }
// if (n == 2) {
// return "All";
// }
// return "All";
// }
//
// @Override
// public Double fromString(String s) {
// switch (s) {
// case "None":
// return 0d;
// case "Important":
// return 1d;
// case "All":
// return 2d;
//
// default:
// return 2d;
// }
// }
// });
// annotationSlider.setValue(2);
// annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
// setAnnotations((int) annotationSlider.getValue())
// );
}
@@ -243,52 +365,52 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* Used to add any new yachts into the race that may have started late or not have had data received yet
*/
private void updateSparkLine(){
// TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// Collect the racing yachts that aren't already in the chart
sparkLineData.clear();
List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// Create a new data series for new yachts
sparkLineCandidates
.stream()
.filter(yacht -> yacht.getPosition() != null)
.forEach(yacht -> {
Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getSourceId().toString());
yachtData.getData().add(
new Data<>(
Integer.toString(yacht.getLegNumber()),
1.0 + participants.size() - yacht.getPosition()
)
);
sparkLineData.add(yachtData);
});
// Lambda function to sort the series in order of leg (later legs shown more to the right)
sparkLineData.sort((o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
if (leg2 < leg1){
return 1;
} else {
return -1;
}
});
// Adds the new data series to the sparkline (and set the colour of the series)
Platform.runLater(() -> {
sparkLineData
.stream()
.filter(spark -> !raceSparkLine.getData().contains(spark))
.forEach(spark -> {
raceSparkLine.getData().add(spark);
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
});
});
// // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// // Collect the racing yachts that aren't already in the chart
// sparkLineData.clear();
// List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// // Create a new data series for new yachts
// sparkLineCandidates
// .stream()
// .filter(yacht -> yacht.getPosition() != null)
// .forEach(yacht -> {
// Series<String, Double> yachtData = new Series<>();
// yachtData.setName(yacht.getSourceId().toString());
// yachtData.getData().add(
// new Data<>(
// Integer.toString(yacht.getLegNumber()),
// 1.0 + participants.size() - yacht.getPosition()
// )
// );
// sparkLineData.add(yachtData);
// });
//
// // Lambda function to sort the series in order of leg (later legs shown more to the right)
// sparkLineData.sort((o1, o2) -> {
// Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
// Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
// if (leg2 < leg1){
// return 1;
// } else {
// return -1;
// }
// });
//
// // Adds the new data series to the sparkline (and set the colour of the series)
// Platform.runLater(() -> {
// sparkLineData
// .stream()
// .filter(spark -> !raceSparkLine.getData().contains(spark))
// .forEach(spark -> {
// raceSparkLine.getData().add(spark);
// spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
// });
// });
}
private void initialiseSparkLine() {
sparklineYAxis.setUpperBound(participants.size() + 1);
raceSparkLine.setCreateSymbols(false);
// sparklineYAxis.setUpperBound(participants.size() + 1);
// raceSparkLine.setCreateSymbols(false);
}
/**
@@ -305,13 +427,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
)
);
}
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - yacht.getPlacing()
// )
// );
}
@@ -342,7 +457,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
updateRaceTime();
Platform.runLater(() -> updatePosition());
Platform.runLater(() -> updateBoatSpeed());
Platform.runLater(() -> updateBoatHeading());
Platform.runLater(() -> updateRaceTime());
}
}, 0, 1000);
}
@@ -381,8 +499,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param direction the from north angle of the wind.
*/
private void updateWindDirection(double direction) {
windDirectionText.setText(String.format("%.1f°", direction));
windArrowText.setRotate(direction);
windDirectionLabel.setText(String.format("%.1f°", direction));
windImageView.setRotate(direction);
}
/**
@@ -390,7 +508,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* @param windSpeed Windspeed in knots.
*/
private void updateWindSpeed(double windSpeed) {
windSpeedText.setText("Speed: " + String.format("%.1f", windSpeed) + " Knots");
windSpeedLabel.setText(String.format("%.1f", windSpeed) + " Knots");
}
@@ -398,12 +516,57 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* Updates the clock for the race
*/
private void updateRaceTime() {
// if (!raceState.isRaceStarted()) {
// timerLabel.setFill(Color.RED);
// timerLabel.setText("Race Finished!");
// } else {
timerLabel.setText(raceState.getRaceTimeStr());
// }
if (raceState.getTimeTillStart() <= 0L && !raceState.isRaceStarted()) {
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(raceState.getRaceTimeStr());
}
}
/**
* Updates player position with ordinal number up to 23rd position.
*/
private void updatePosition() {
if (player.getPosition() == null) {
positionLabel.setText("Position:\n-");
} else {
switch (player.getPosition()) {
case 1:
positionLabel.setText("Position:\n1st");
break;
case 2:
positionLabel.setText("Position:\n2nd");
break;
case 3:
positionLabel.setText("Position:\n3rd");
break;
case 21:
positionLabel.setText("Position:\n21st");
break;
case 22:
positionLabel.setText("Position:\n22nd");
break;
case 23:
positionLabel.setText("Position:\n23rd");
break;
default:
positionLabel.setText("Position:\n" + player.getPosition() + "th");
}
}
}
/**
* Updates boat speed value displayed on race view.
*/
private void updateBoatSpeed() {
boatSpeedLabel.setText("Boat Speed:\n" + String.valueOf(player.getCurrentVelocity()));
}
/**
* Updates boat heading value displayed on race view.
*/
private void updateBoatHeading() {
boatHeadingLabel.setText(String.format("Boat Heading:\n%.1f°", player.getHeading()));
}
/**
@@ -411,28 +574,28 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* section
*/
private void updateOrder(ObservableList<ClientYacht> yachts) {
List<Text> vboxEntries = new ArrayList<>();
for (int i = 0; i < yachts.size(); i++) {
// System.out.println("yacht == null " + String.valueOf(yacht == null));
if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
.getCode()) { // 3 is finish status
Text textToAdd = new Text(i + 1 + ". " +
yachts.get(i).getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
vboxEntries.add(textToAdd);
} else {
Text textToAdd = new Text(i + 1 + ". " +
yachts.get(i).getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
vboxEntries.add(textToAdd);
}
}
Platform.runLater(() ->
positionVbox.getChildren().setAll(vboxEntries)
);
// List<Text> vboxEntries = new ArrayList<>();
//
// for (int i = 0; i < yachts.size(); i++) {
//// System.out.println("yacht == null " + String.valueOf(yacht == null));
// if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
// .getCode()) { // 3 is finish status
// Text textToAdd = new Text(i + 1 + ". " +
// yachts.get(i).getShortName() + " (Finished)");
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
// vboxEntries.add(textToAdd);
//
// } else {
// Text textToAdd = new Text(i + 1 + ". " +
// yachts.get(i).getShortName() + " ");
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
// textToAdd.setStyle("");
// vboxEntries.add(textToAdd);
// }
// }
// Platform.runLater(() ->
// positionVbox.getChildren().setAll(vboxEntries)
// );
}
@@ -533,15 +696,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
yachtSelectionComboBox.setItems(
FXCollections.observableArrayList(participants.values())
);
//Null check is if the listener is fired but nothing selected
yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
if (selectedBoat != null) {
gameView.selectBoat(selectedBoat);
}
});
// yachtSelectionComboBox.setItems(
// FXCollections.observableArrayList(participants.values())
// );
// //Null check is if the listener is fired but nothing selected
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
// if (selectedBoat != null) {
// gameView.selectBoat(selectedBoat);
// }
// });
//TODO uncomment out
// selectionComboBoxList.setAll(participants.values());
// yachtSelectionComboBox.setItems(selectionComboBoxList);
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
// if (selectedBoat != null) {
// gameView.selectBoat(selectedBoat);
// }
// });
}
/**
@@ -551,9 +723,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
contentGridPane.getChildren().removeAll();
contentGridPane.getChildren().clear();
contentGridPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause().toString());
@@ -571,31 +743,31 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
// No Annotations
case 0:
gameView.setAnnotationVisibilities(
false, false, false, false, false, false
);
break;
// Important Annotations
case 1:
gameView.setAnnotationVisibilities(
importantAnnotations.getAnnotationState(Annotation.NAME),
importantAnnotations.getAnnotationState(Annotation.SPEED),
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
importantAnnotations.getAnnotationState(Annotation.LEGTIME),
importantAnnotations.getAnnotationState(Annotation.TRACK),
importantAnnotations.getAnnotationState(Annotation.WAKE)
);
break;
// All Annotations
case 2:
gameView.setAnnotationVisibilities(
true, true, true, true, true, true
);
break;
}
// switch (annotationLevel) {
// // No Annotations
// case 0:
// gameView.setAnnotationVisibilities(
// false, false, false, false, false, false
// );
// break;
// // Important Annotations
// case 1:
// gameView.setAnnotationVisibilities(
// importantAnnotations.getAnnotationState(Annotation.NAME),
// importantAnnotations.getAnnotationState(Annotation.SPEED),
// importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
// importantAnnotations.getAnnotationState(Annotation.LEGTIME),
// importantAnnotations.getAnnotationState(Annotation.TRACK),
// importantAnnotations.getAnnotationState(Annotation.WAKE)
// );
// break;
// // All Annotations
// case 2:
// gameView.setAnnotationVisibilities(
// true, true, true, true, true, true
// );
// break;
// }
}
@@ -618,8 +790,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// }
}
public void updateRaceData (RaceXMLData raceData) {
this.courseData = raceData;
gameView.updateBorder(raceData.getCourseLimit());
public void updateTokens(RaceXMLData raceData) {
gameView.updateTokens(raceData.getTokens());
}
public ReadOnlyBooleanProperty getSendPressedProperty() {
return chatSend.pressedProperty();
}
public boolean isChatInputFocused() {
return chatInput.focusedProperty().getValue();
}
public String readChatInput() {
String chat = chatInput.getText();
chatInput.clear();
rvAnchorPane.requestFocus();
return chat;
}
public void updateChatHistory(Paint playerColour, String newMessage) {
Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage));
}
}
@@ -0,0 +1,199 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
import seng302.visualiser.controllers.cells.ServerCell;
import seng302.visualiser.validators.HostNameFieldValidator;
import seng302.visualiser.validators.NumberRangeValidator;
import seng302.visualiser.validators.ValidationTools;
public class ServerListController implements Initializable, ServerListenerDelegate {
//--------FXML BEGIN--------//
// Layout Related
@FXML
private VBox serverListVBox;
@FXML
private ScrollPane serverListScrollPane;
@FXML
private StackPane serverListMainStackPane;
// Host Button
@FXML
private JFXButton serverListHostButton;
//Direct Connect
@FXML
private JFXButton connectButton;
@FXML
private JFXTextField serverHostName;
@FXML
private JFXTextField serverPortNumber;
//---------FXML END---------//
private Label noServersFound;
private Logger logger = LoggerFactory.getLogger(ServerListController.class);
// TODO: 12/09/17 ajm412: break this method down, its way too long.
@Override
public void initialize(URL location, ResourceBundle resources) {
serverListVBox.minWidthProperty().bind(serverListScrollPane.widthProperty());
// Set Event Bindings
connectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
connectButton.setOnMouseReleased(event -> {
attemptToDirectConnect();
Sounds.playButtonClick();
});
for (JFXTextField textField : Arrays.asList(serverHostName, serverPortNumber)) {
// Event for pressing enter to submit direct connection
textField.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
attemptToDirectConnect();
}
});
// Validators as empty fields are invalid.
RequiredFieldValidator validator = new RequiredFieldValidator();
validator.setMessage("Field is Required");
textField.getValidators().add(validator);
}
// Validating the hostname
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
hostNameValidator.setMessage("Host name incorrect");
serverHostName.getValidators().add(hostNameValidator);
// Validating the port number
NumberRangeValidator portNumberValidator = new NumberRangeValidator(1025, 65536);
portNumberValidator.setMessage("Port number incorrect");
serverPortNumber.getValidators().add(portNumberValidator);
// Start listening for servers on network
try {
ServerListener.getInstance().setDelegate(this);
} catch (IOException e) {
logger.warn("Could not start Server Listener Delegate");
}
// Create Label for no servers found.
noServersFound = new Label();
noServersFound.minWidthProperty().bind(serverListVBox.widthProperty());
noServersFound.setAlignment(Pos.CENTER);
noServersFound.setText("No Servers Found");
noServersFound.setStyle(
"-fx-font-size: 30px;"
+ "-fx-padding:50px;"
+ "-fx-text-fill: -fx-pp-dark-text-color;"
);
serverListVBox.getChildren().add(noServersFound);
// Set up dialog for server creation
Platform.runLater(() -> {
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
"/views/dialogs/ServerCreationDialog.fxml"));
try {
JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
DialogTransition.CENTER);
serverListHostButton.setOnAction(action -> {
dialog.show();
Sounds.playButtonClick();
});
} catch (IOException e) {
logger.warn("Could not create Server Creation Dialog.");
}
});
}
/**
* Validates the connection and attempts to connect to a given hostname and port number.
*/
private void attemptToDirectConnect() {
if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
DirectConnect();
}
}
/**
* Checks if the hostName and portNumber are valid values to connect to.
* @param hostName host name to check.
* @param portNumber port number to check
* @return boolean value if host and port number are valid values
*/
private Boolean validateDirectConnection(String hostName, String portNumber) {
Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
Boolean portNumberValid = ValidationTools.validateTextField(serverPortNumber);
return hostNameValid && portNumberValid;
}
/**
* Connects the user to a lobby via the Direct Connect form.
*/
private void DirectConnect() {
Sounds.playButtonClick();
ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
}
/**
* Refreshes the list of available servers.
* @param servers A list of ServerDescription objects showing available servers.
*/
private void refreshServers(List<ServerDescription> servers) {
serverListVBox.getChildren().clear();
if (servers.size() == 0) { // "No Servers Found"
serverListVBox.getChildren().add(noServersFound);
} else { // Populate the server list with a series of server cell objects.
for (ServerDescription server : servers) {
VBox pane = null;
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/views/cells/ServerCell.fxml"));
loader.setController(new ServerCell(server));
try {
pane = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
serverListVBox.getChildren().add(pane);
}
}
}
@Override
public void serverRemoved(List<ServerDescription> servers) {
Platform.runLater(() -> refreshServers(servers));
}
@Override
public void serverDetected(ServerDescription serverDescription, List<ServerDescription> servers) {
Platform.runLater(() -> refreshServers(servers));
}
}
@@ -1,168 +1,70 @@
package seng302.visualiser.controllers;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import com.jfoenix.controls.JFXButton;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import seng302.gameServer.GameState;
import javafx.scene.Node;
import javafx.scene.control.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
/**
* A Class describing the actions of the start screen controller
* Created by wmu16 on 10/07/17.
*/
public class StartScreenController implements Initializable {
public class StartScreenController implements Initializable{
//--------FXML BEGIN--------//
@FXML
private TextField ipTextField;
private Label headText;
@FXML
private TextField portTextField;
@FXML
private GridPane startScreen2;
@FXML
private AnchorPane holder;
private JFXButton startBtn;
//---------FXML END---------//
GameClient gameClient;
private Node serverList;
private Logger logger = LoggerFactory.getLogger(StartScreenController.class);
private List<ServerDescription> servers;
private GameClient gameClient;
public void initialize(URL url, ResourceBundle resourceBundle) {
// gameClient = new GameClient(holder);
}
//
// /**
// * Loads the fxml content into the parent pane
// * @param jfxUrl
// * @return the controller of the fxml
// */
// private Object setContentPane(String jfxUrl) {
// try {
// AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
// contentPane.getChildren().removeAll();
// contentPane.getChildren().clear();
// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
// contentPane.getChildren().addAll((Pane) fxmlLoader.load());
//
// return fxmlLoader.getController();
// } catch (IOException e) {
// e.printStackTrace();
// }
// return null;
// }
public void initialize(URL location, ResourceBundle resources) {
startBtn.setOnMousePressed(event -> {
startBtn.setText("LOADING...");
Sounds.playButtonClick();
});
startBtn.setOnMouseReleased(event -> goToServerBrowser());
startBtn.setOnMouseEntered(event -> Sounds.playHoverSound());
preloadServerListView();
/**
* ATTEMPTS TO:
* Sets up a new game state with your IP address as designated as the host.
* Starts a thread to listen for incoming connections.
* Starts a client to server thread and connects to own ip.
* Switches to the lobby screen
*/
@FXML
public void hostButtonPressed() {
// new GameState(getLocalHostIp());
gameClient = new GameClient(holder);
gameClient.runAsHost(getLocalHostIp(), 4942);
// try {
//// String ipAddress = InetAddress.getLocalHost().getHostAddress();
//// new GameState(ipAddress);
//// new MainServerThread();
//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
//// controller.setClientToServerThread(clientToServerThread);
// // get the lobby controller so that we can pass the game server thread to it
// new GameState(getLocalHostIp());
// MainServerThread mainServerThread = new MainServerThread();
//// ClientState.setHost(true);
// // host will connect and handshake to itself after setting up the server
// // TODO: 24/07/17 wmu16 - Make port number some static global type constant?
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
//// ClientState.setConnectedToHost(true);
//// controller.setClientToServerThread(clientToServerThread);
// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
// lobbyController.setMainServerThread(mainServerThread);
// } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot host");
// alert.setContentText("Oops, failed to host, try to restart.");
// alert.showAndWait();
// e.printStackTrace();
// }
}
/**
* ATTEMPTS TO:
* Connect to an ip address and port using the ip and port specified on start screen.
* Starts a Client To Server Thread to maintain connection to host.
* Switch view to lobby view.
* Preloads the server list view to reduce load time between start screen and server list screen.
*/
@FXML
public void connectButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function
gameClient = new GameClient(holder);
gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942);
// try {
// String ipAddress = ipTextField.getText().trim().toLowerCase();
// Integer port = Integer.valueOf(portTextField.getText().trim());
//
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
//// ClientState.setHost(false);
//// ClientState.setConnectedToHost(true);
//
//// controller.setClientToServerThread(clientToServerThread);
//// setContentPane("/views/LobbyView.fxml");
// } catch (Exception e) {
// Alert alert = new Alert(AlertType.ERROR);
// alert.setHeaderText("Cannot reach the host");
// alert.setContentText("Please check your host IP address.");
// alert.showAndWait();
// }
}
// public void setController(Controller controller) {
// this.controller = controller;
// }
/**
* Gets the local host ip address and sets this ip to ClientState.
* Only runs by the host.
*
* @return the localhost ip address
*/
private String getLocalHostIp() {
String ipAddress = null;
private void preloadServerListView(){
try {
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface ni = e.nextElement();
if (ni.isLoopback())
continue;
if(ni.isPointToPoint())
continue;
if(ni.isVirtual())
continue;
serverList = FXMLLoader
.load(StartScreenController.class.getResource("/views/ServerListView.fxml"));
} catch (IOException e) {
e.printStackTrace();
logger.error("Could not preload server list view");
}
}
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while(addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if(address instanceof Inet4Address) { // skip all ipv6
ipAddress = address.getHostAddress();
}
}
}
/**
* Changes the view to the Server Browser.
*/
private void goToServerBrowser() {
try {
ViewManager.getInstance().setScene(serverList);
} catch (Exception e) {
e.printStackTrace();
}
if (ipAddress == null) {
System.out.println("[HOST] Cannot obtain local host ip address.");
}
// ClientState.setHostIp(ipAddress);
return ipAddress;
}
}
@@ -0,0 +1,299 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.svg.SVGGlyph;
import java.io.IOException;
import java.util.HashMap;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.BonjourInstallChecker;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
public class ViewManager {
private static ViewManager instance;
private GameClient gameClient;
private JFXDecorator decorator;
private HashMap<String, String> properties; //TODO is this the best way to do this??
private ObservableList<String> playerList;
private Logger logger = LoggerFactory.getLogger(ViewManager.class);
public Stage getStage() {
return stage;
}
private Stage stage;
private ViewManager() {
properties = new HashMap<>();
}
private FXMLLoader loadFxml(String fxmlLocation) {
return new FXMLLoader(
getClass().getResource(fxmlLocation)
);
}
public static ViewManager getInstance() {
if (instance == null) {
instance = new ViewManager();
}
return instance;
}
/**
* Initialize the start view in the given stage.
*/
public void initialStartView(Stage stage) throws Exception {
this.stage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
stage.setTitle("Party Parrots At Sea");
JFXDecorator decorator = new JFXDecorator(stage, root, false, true, true);
decorator.setCustomMaximize(true);
decorator.applyCss();
decorator.getStylesheets()
.add(getClass().getResource("/css/Master.css").toExternalForm());
gameClient = new GameClient(decorator);
setDecorator(decorator);
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
Scene scene = new Scene(decorator, 1200, 800, false, SceneAntialiasing.BALANCED);
stage.setMinHeight(800);
stage.setMinWidth(1200);
stage.setScene(scene);
stage.show();
stage.setOnCloseRequest(e -> closeAll());
decorator.setOnCloseButtonAction(this::closeAll);
// TODO Platform.runLater(this::checkCompatibility);
Sounds.stopMusic();
Sounds.playMenuMusic();
decorator.setOnCloseButtonAction(() -> {
try {
ServerAdvertiser.getInstance().unregister();
} catch (IOException e) {
logger.warn("Couldn't unregister server");
}
gameClient.stopGame();
System.exit(0);
});
}
/**
* Sets the decorator when a new one is created (and ideally the old one destroyed)
* Also allows injection of buttons into the decorator for custom functions.
*
* @param newDecorator The new JFXDecorator to handle the game window.
*/
private void setDecorator(JFXDecorator newDecorator) {
decorator = newDecorator;
decorator.setOnCloseButtonAction(() -> {
gameClient.stopGame();
System.exit(0);
});
//Injecting a volume toggle into the decorator.
//Get the button box
HBox btns = (HBox) decorator.getChildren().get(0);
//Create new button
JFXButton btnMute = new JFXButton();
btnMute.setText(" Toggle Sound");
btnMute.setStyle("-fx-text-fill:#fff");
btnMute.getStyleClass().add("jfx-decorator-button");
btnMute.setCursor(Cursor.HAND);
//Create Graphics
SVGGlyph spacer = new SVGGlyph(0, "SPACER", "", Color.WHITE);
SVGGlyph volumeOn = new SVGGlyph(0, "VOLUME_ON",
"M0,6 L0,12 L4,12 L9,17 L9,1 L4,6 L0,6 L0,6 Z M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,13 C12.5,12.3 13.5,10.8 13.5,9 L13.5,9 Z M11,0.2 L11,2.3 C13.9,3.2 16,5.8 16,9 C16,12.2 13.9,14.8 11,15.7 L11,17.8 C15,16.9 18,13.3 18,9 C18,4.7 15,1.1 11,0.2 L11,0.2 Z",
Color.WHITE);
SVGGlyph volumeOff = new SVGGlyph(0, "VOLUME_ON",
"M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,7.2 L13.5,9.7 L13.5,9 L13.5,9 Z M16,9 C16,9.9 15.8,10.8 15.5,11.6 L17,13.1 C17.7,11.9 18,10.4 18,8.9 C18,4.6 15,1 11,0.1 L11,2.2 C13.9,3.2 16,5.8 16,9 L16,9 Z M1.3,0 L0,1.3 L4.7,6 L0,6 L0,12 L4,12 L9,17 L9,10.3 L13.3,14.6 C12.6,15.1 11.9,15.5 11,15.8 L11,17.9 C12.4,17.6 13.6,17 14.7,16.1 L16.7,18.1 L18,16.8 L9,7.8 L1.3,0 L1.3,0 Z M9,1 L6.9,3.1 L9,5.2 L9,1 L9,1 Z",
Color.WHITE);
volumeOn.setSize(16, 16);
volumeOff.setSize(16, 16);
spacer.setSize(40, 16);
// Determine which graphic should go on the button
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
btnMute.setGraphic(volumeOff);
} else {
btnMute.setGraphic(volumeOn);
}
// Add Buttons
btns.getChildren().add(0, spacer);
btns.getChildren().add(0, btnMute);
btnMute.setOnAction((action) -> {
Sounds.toggleAllSounds();
if (btnMute.getGraphic().equals(volumeOff)) {
btnMute.setGraphic(volumeOn);
} else {
btnMute.setGraphic(volumeOff);
}
});
}
/**
* Determines if a PC has compatibility with the bonjour protocol for server detection.
*/
private void checkCompatibility() {
if (BonjourInstallChecker.isBonjourSupported()) {
BonjourInstallChecker.openInstallUrl();
}
}
private void closeAll() {
try {
ServerAdvertiser.getInstance().unregister();
} catch (IOException e1) {
logger.warn("Could not un-register game");
}
System.exit(0);
}
public JFXDecorator getDecorator() {
return decorator;
}
public void setScene(Node scene) {
Platform.runLater(() -> decorator.setContent(scene));
}
/**
* Create a new stage and re-initialize the start view in the new stage.
*/
public void goToStartView() {
try {
this.stage.close();
Stage stage = new Stage();
initialStartView(stage);
} catch (Exception e) {
e.printStackTrace();
}
}
public GameClient getGameClient() {
return gameClient;
}
public String getProperty(String key) {
return properties.get(key);
}
public void setProperty(String key, String val) {
properties.put(key, val);
}
public void setPlayerList(ObservableList<String> playerList) {
this.playerList = playerList;
}
public ObservableList<String> getPlayerList() {
return playerList;
}
/**
* Change the view to the Lobby Screen
* @param disableReadyButton Boolean value so that clients can't try start a game.
* @return A LobbyController object for the Lobby Screen.
*/
public LobbyController goToLobby(Boolean disableReadyButton) {
FXMLLoader loader = loadFxml("/views/LobbyView.fxml");
try {
setScene(loader.load());
} catch (IOException e) {
logger.error("Could not load lobby view");
}
if (disableReadyButton) {
LobbyController lobbyController = loader.getController();
lobbyController.disableReadyButton();
}
return loader.getController();
}
/**
* Sets up the view for the race. Creating a new decorator and destroying the old one.
* @return A RaceViewController for the race view screen.
*/
public RaceViewController loadRaceView() {
FXMLLoader loader = loadFxml("/views/RaceView.fxml");
// have to create a new stage and set the race view maximized as JFoenix decorator has
// bug causes stage cannot be fully maximised.
Platform.runLater(() -> {
try {
stage.close();
stage = new Stage();
JFXDecorator decorator = new JFXDecorator(stage, loader.load(), false, true, true);
decorator.setCustomMaximize(true);
decorator.applyCss();
decorator.getStylesheets()
.add(getClass().getResource("/css/Master.css").toExternalForm());
setDecorator(decorator);
Scene scene = new Scene(decorator);
RaceViewController raceViewController = loader.getController();
gameClient.setRaceViewController(raceViewController);
// set key press event to catch key stoke
scene.setOnKeyPressed(gameClient::keyPressed);
scene.setOnKeyReleased(gameClient::keyReleased);
// uncomment to make it full screen
// Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
// stage.setX(visualBounds.getMinX());
// stage.setY(visualBounds.getMinY());
// stage.setWidth(visualBounds.getWidth());
// stage.setHeight(visualBounds.getHeight());
// stage.setMaximized(true);
// stage.setFullScreen(true);
stage.setMinHeight(500);
stage.setMinWidth(800);
stage.setOnCloseRequest(e -> closeAll());
stage.setScene(scene);
stage.show();
} catch (Exception e) {
e.printStackTrace();
}
});
while (loader.getController() == null){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return loader.getController();
}
}
@@ -0,0 +1,55 @@
package seng302.visualiser.controllers.cells;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
public class PlayerCell {
//--------FXML BEGIN--------//
@FXML
private Label playerName;
@FXML
private GridPane playerListCell;
@FXML
private Pane boatPane;
//---------FXML END---------//
private String name;
private Color boatColor;
private Integer playerId;
public PlayerCell(Integer playerId, String playerName, Color color) {
this.playerId = playerId;
this.name = playerName;
this.boatColor = color;
}
public void initialize() {
// Set Player Name
playerName.setText(name);
// Add Rotating Boat to Player Cell with players color on it.
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatIconView(BoatMeshType.PIRATE_SHIP, this.boatColor);
group.getChildren().add(bo.getAssets());
}
public Integer getPlayerId() {
return playerId;
}
public String getPlayerName() {
return name;
}
public Color getBoatColor() {
return boatColor;
}
}
@@ -0,0 +1,75 @@
package seng302.visualiser.controllers.cells;
import com.jfoenix.controls.JFXButton;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.controllers.ViewManager;
public class ServerCell implements Initializable {
//--------FXML BEGIN--------//
//Layout
@FXML
private VBox serverCellVBox;
@FXML
private GridPane serverListCell;
//Server Information
@FXML
private Label serverName;
@FXML
private Label mapName;
@FXML
private Label serverPlayerCount;
//Server Connection
@FXML
private JFXButton serverConnButton;
//---------FXML END---------//
private String name;
private String mapNameString;
private String currPlayerCount;
private String hostName;
private Integer portNumber;
public ServerCell(ServerDescription server) {
this.name = server.getName();
this.currPlayerCount = server.getNumPlayers().toString() + "/" + server.getCapacity().toString();
this.mapNameString = server.getMapName();
// Can cause issues on windows PCs without the bonjour service installed.
this.hostName = server.getAddress();
this.portNumber = server.portNumber();
}
public void initialize(URL location, ResourceBundle resources) {
serverName.setText(name);
serverPlayerCount.setText(currPlayerCount);
mapName.setText(mapNameString);
serverCellVBox.setOnMouseEntered(event -> Sounds.playHoverSound());
serverConnButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
joinServer();
});
}
/**
* Attempts to connect to the chosen server using the button on the serverCell.
*/
private void joinServer() {
ViewManager.getInstance().getGameClient().runAsClient(hostName, portNumber);
}
}
@@ -0,0 +1,96 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXColorPicker;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.paint.Color;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.utilities.Sounds;
import seng302.visualiser.ClientToServerThread;
import seng302.visualiser.controllers.LobbyController;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
public class BoatCustomizeController implements Initializable{
//--------FXML BEGIN--------//
@FXML
private JFXColorPicker colorPicker;
@FXML
private JFXButton submitBtn;
@FXML
private JFXTextField boatName;
@FXML
void colorChanged(ActionEvent event) {
Color color = colorPicker.getValue();
}
//---------FXML END---------//
private ClientToServerThread socketThread;
private LobbyController lobbyController;
@Override
public void initialize(URL location, ResourceBundle resources) {
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
playerNameReqValidator.setMessage("Player name required.");
FieldLengthValidator playerNameLengthValidator = new FieldLengthValidator(20);
playerNameLengthValidator.setMessage("Player name too long.");
boatName.setValidators(playerNameLengthValidator, playerNameReqValidator);
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
submitCustomization();
});
submitBtn.setOnMouseEntered(e -> Sounds.playHoverSound());
}
/**
* Attempts to submit a valid customization packet for boat name and boat color.
*/
private void submitCustomization() {
if (ValidationTools.validateTextField(boatName)) {
socketThread
.sendCustomizationRequest(CustomizeRequestType.NAME, boatName.getText().getBytes());
Color color = colorPicker.getValue();
short red = (short) (color.getRed() * 255);
short green = (short) (color.getGreen() * 255);
short blue = (short) (color.getBlue() * 255);
byte[] colorArray = new byte[3];
colorArray[0] = (byte) red;
colorArray[1] = (byte) green;
colorArray[2] = (byte) blue;
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
lobbyController.closeCustomizationDialog();
}
}
public void setPlayerName(String name) {
this.boatName.setText(name);
}
public void setPlayerColor(Color playerColor) {
this.colorPicker.setValue(playerColor);
}
public void setParentController(LobbyController lobbyController){
this.lobbyController = lobbyController;
}
}
@@ -0,0 +1,38 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import seng302.model.ClientYacht;
import seng302.visualiser.controllers.ViewManager;
public class FinishDialogController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private Label raceFinishLabel;
@FXML
private JFXListView<Label> finishersList;
@FXML
private JFXButton playAgain;
//---------FXML END---------//
@Override
public void initialize(URL location, ResourceBundle resources) {
playAgain.setOnAction(event -> ViewManager.getInstance().goToStartView());
}
public void setFinishedBoats(ArrayList<ClientYacht> finishedBoats) {
finishersList.getItems().clear();
for (int i = 0; i < finishedBoats.size(); i++) {
finishersList.getItems().add(new Label(Integer.toString(i+1) +". " + finishedBoats.get(i).getBoatName()));
}
}
}
@@ -0,0 +1,90 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
public class ServerCreationController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private JFXTextField serverName;
@FXML
private JFXSlider maxPlayersSlider;
@FXML
private Label maxPlayersLabel;
@FXML
private JFXButton submitBtn;
//---------FXML END---------//
public void initialize(URL location, ResourceBundle resources) {
updateMaxPlayerLabel();
maxPlayersSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
updateMaxPlayerLabel();
});
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Server name too long.");
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
fieldRequiredValidator.setMessage("Server name is required.");
serverName.setValidators(fieldLengthValidator, fieldRequiredValidator);
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
validateServerSettings();
});
}
/**
* Validates that a server has a valid name and creates the server.
*/
private void validateServerSettings() {
submitBtn.setText("CREATING...");
if (ValidationTools.validateTextField(serverName)) {
createServer();
} else {
submitBtn.setText("SUBMIT");
}
}
/**
* Creates a server with a given set of details.
*/
private void createServer() {
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
.getValue());
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
}
/**
* Updates a label as the user slides along the max players slider.
*/
private void updateMaxPlayerLabel() {
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("YOU SELECTED: %.0f", maxPlayersSlider.getValue()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -1,400 +0,0 @@
package seng302.visualiser.fxObjects;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/
public class BoatObject extends Group {
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
//Constants for drawing
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
private double xVelocity;
private double yVelocity;
private double lastHeading;
private double sailState;
//Graphical objects
private Polyline trail = new Polyline();
private Polygon boatPoly;
private Polygon sail;
private Wake wake;
private Line leftLayLine;
private Line rightLayline;
private double distanceTravelled, lastRotation;
private Point2D lastPoint;
private Paint colour = Color.BLACK;
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
private boolean isPlayer = false;
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
public BoatObject() {
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0).
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
public BoatObject(double... points) {
initChildren(points);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren(double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(colour);
boatPoly.setFill(this.colour);
boatPoly.setOnMouseEntered(event -> {
boatPoly.setFill(Color.FLORALWHITE);
boatPoly.setStroke(Color.RED);
});
boatPoly.setOnMouseExited(event -> {
boatPoly.setFill(colour);
boatPoly.setFill(this.colour);
boatPoly.setStroke(Color.BLACK);
});
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
// boatPoly.setCacheHint(CacheHint.SPEED);
// annotationBox = new AnnotationBox();
// annotationBox.setFill(colour);
leftLayLine = new Line();
rightLayline = new Line();
trail.getStrokeDashArray().setAll(5d, 10d);
trail.setCache(true);
wake = new Wake(0, -BOAT_HEIGHT);
wake.setVisible(true);
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
0.0, BOAT_HEIGHT);
sailState = 0;
sail.setStrokeWidth(2.0);
sail.setStroke(Color.BLACK);
sail.setFill(Color.TRANSPARENT);
sail.setCache(true);
super.getChildren().clear();
super.getChildren().addAll(boatPoly, sail);
}
public void setFill (Paint value) {
this.colour = value;
boatPoly.setFill(colour);
trail.setStroke(colour);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The rotation by which the boat moves
* @param velocity The velocity the boat is moving
* @param sailIn Boolean to toggle sail state.
*/
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
Double dx = Math.abs(boatPoly.getLayoutX() - x);
Double dy = Math.abs(boatPoly.getLayoutY() - y);
Platform.runLater(() -> {
rotateTo(rotation, sailIn, windDir);
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
if (sailIn) {
// sail.getPoints().clear();
// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
sail.setLayoutX(x);
sail.setLayoutY(y);
} else {
animateSail();
sail.setLayoutX(x);
sail.setLayoutY(y);
}
wake.setLayoutX(x);
wake.setLayoutY(y);
});
wake.setRotation(rotation, velocity);
// rotateTo(rotation);
// boatPoly.setLayoutX(x);
// boatPoly.setLayoutY(y);
// wake.setLayoutX(x);
// wake.setLayoutY(y);
// wake.rotate(rotation);
// wake.setRotation(rotation, groundSpeed);
// isStopped = false;
// destinationSet = true;
lastRotation = rotation;
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
if (distanceTravelled > 15 && isPlayer) {
distanceTravelled = 0d;
Platform.runLater(() -> trail.getPoints().addAll(x, y));
}
}
private Double normalizeHeading(double heading, double windDirection) {
Double normalizedHeading = heading - windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
private void rotateTo(double heading, boolean sailsIn, double windDir) {
boatPoly.getTransforms().setAll(new Rotate(heading));
if (sailsIn) {
Double sailWindOffset = 30.0;
Double upwindAngleLimit = 15.0;
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
Double normalizedHeading = normalizeHeading(heading, windDir);
if (normalizedHeading < 180) {
sail.getTransforms().setAll(new Rotate(windDir + 90 + sailWindOffset));
sail.getPoints().clear();
sail.getPoints().addAll(0.0, 0.0, 4.0, -1.5, 8.0, -3.0, 12.0, -3.5, 16.0, -3.0, 20.0, -1.5, 24.0, 0.0);
if (normalizedHeading > 90 + sailWindOffset){
sail.getTransforms().setAll(new Rotate(heading + downwindAngleLimit));
}
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
sail.getTransforms().setAll(new Rotate(heading + 90 - upwindAngleLimit));
}
} else {
sail.getTransforms().setAll(new Rotate(windDir + 90 - sailWindOffset));
sail.getPoints().clear();
sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
if (normalizedHeading < 270 - sailWindOffset){
sail.getTransforms().setAll(new Rotate(heading + 180 - downwindAngleLimit));
}
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
sail.getTransforms().setAll(new Rotate(heading + 90 + upwindAngleLimit));
}
}
} else {
sail.getTransforms().setAll(new Rotate(windDir));
}
}
private void animateSail(){
Double[] points = new Double[200];
double amplitude = 2.0;
double period = 10;
for (int i = 0; i < 50; i++) {
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
points[i * 2 + 1] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2)] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
}
if (sailState == - 2 * Math.PI) {
sailState = 0;
} else {
sailState = sailState - Math.PI / 5;
}
sail.getPoints().clear();
sail.getPoints().addAll(points);
}
public void updateLocation() {
// double dx = xVelocity / 60;
// double dy = yVelocity / 60;
//
// distanceTravelled += Math.abs(dx) + Math.abs(dy);
// moveGroupBy(dx, dy);
//
// if (distanceTravelled > 70) {
// distanceTravelled = 0d;
//
// if (lastPoint != null) {
// Line l = new Line(
// lastPoint.getX(),
// lastPoint.getY(),
// boatPoly.getLayoutX(),
// boatPoly.getLayoutY()
// );
// l.getStrokeDashArray().setAll(3d, 7d);
// l.setStroke(colour);
// l.setCache(true);
// l.setCacheHint(CacheHint.SPEED);
// lineGroup.getChildren().add(l);
// }
// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
// }
// wake.updatePosition();
}
// /**
// * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
// * gates position and the current wind
// * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
// * going up wind, if they are on different sides of the gate, then the boat is going downwind
// * @param canvasController
// */
// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
//
// Double windAngle = StreamParser.getWindDirection();
// GateMark thisGateMark = (GateMark) nextMark;
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
//
// Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
//
//
// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
// Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
//
//
// /*
// If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
// with the wind.
// */
// return boatLineFuncResult.equals(windLineFuncResult);
// return true;
// }
public void setIsSelected(Boolean isSelected) {
updateListener(isSelected);
this.isSelected = isSelected;
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setLayLinesVisible(isSelected);
}
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
boolean trail, boolean wake) {
// boatAnnotations.setVisible(teamName, velocity, estTime, legTime);
// this.wake.setVisible(wake);
this.trail.setVisible(trail);
}
public void setLineGroupVisible(Boolean visible) {
trail.setVisible(visible);
}
public void setWakeVisible(Boolean visible) {
// wake.setVisible(visible);
}
public void setLayLinesVisible(Boolean visible) {
leftLayLine.setVisible(visible);
rightLayline.setVisible(visible);
}
public void setLaylines(Line line1, Line line2) {
this.leftLayLine = line1;
this.rightLayline = line2;
}
public ArrayList<Line> getLaylines() {
ArrayList<Line> laylines = new ArrayList<>();
laylines.add(leftLayLine);
laylines.add(rightLayline);
return laylines;
}
public Group getWake () {
return wake;
}
public Node getTrail() {
return trail;
}
public Double getBoatLayoutX() {
return boatPoly.getLayoutX();
}
public Double getBoatLayoutY() {
return boatPoly.getLayoutY();
}
/**
* Sets this boat to appear highlighted
*/
public void setAsPlayer() {
boatPoly.getPoints().setAll(
-BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
0.0, -BOAT_HEIGHT / 1.75,
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
);
boatPoly.setStroke(Color.BLACK);
boatPoly.setStrokeWidth(2);
boatPoly.setStrokeLineCap(StrokeLineCap.ROUND);
isPlayer = true;
animateSail();
}
public void setTrajectory(double heading, double velocity, double windDir) {
wake.setRotation(lastHeading - heading, velocity);
rotateTo(heading, false, windDir);
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
lastHeading = heading;
}
public Boolean getSelected() {
return isSelected;
}
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
// wake.setRotation(lastHeading - heading, velocity);
// rotateTo(heading);
// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX;
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
lastHeading = heading;
}
private void updateListener(Boolean isSelected) {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
}
@@ -0,0 +1,89 @@
package seng302.visualiser.fxObjects;
import java.util.Arrays;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.paint.Paint;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* Extension of a ScrollPane that contains a TextFlow. Has an addMessage() function to parse and
* display chatter text.
*/
public class ChatHistory extends ScrollPane {
private TextFlow textFlow = new TextFlow();
public ChatHistory() {
this.setContent(textFlow);
this.setFitToWidth(true);
this.setFitToHeight(true);
this.setMaxHeight(Double.MAX_VALUE);
this.setMaxWidth(Double.MAX_VALUE);
this.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
this.setHbarPolicy(ScrollBarPolicy.NEVER);
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0);");
this.textFlow.setStyle(
"-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0); -fx-padding: 10px"
);
//This makes the window auto scroll.
textFlow.getChildren().addListener((ListChangeListener<Node>) c ->
this.setVvalue(1.0)
);
//This just makes it so that the ChatHistory is on focus it passes it off to the parent.
this.parentProperty().addListener((obs, old, parent) ->
this.focusedProperty().addListener((obsVal, oldVal, onFocus) -> {
if (onFocus) {
parent.requestFocus();
}
})
);
}
/**
* Adds a message to chat history. Messages should be either of the form:
* "[HH:MM:ss] player_name: message_text" or
* "SERVER: message_text"
* @param colour The colour of the user sending the message
* @param Text The chatter text message to be displayed
*/
public void addMessage (Paint colour, String Text) {
String[] words = Text.split(":");
if (words[0].trim().equals("SERVER")) {
Text text = new Text(Text + "\n");
text.setStyle("-fx-font-weight: bolder");
textFlow.getChildren().add(text);
} else {
Text timePlayer = new Text(
String.join(":", Arrays.copyOfRange(words, 0, 3)) + ":"
);
timePlayer.setStyle("-fx-font-weight: bold");
timePlayer.setFill(colour);
Text message = new Text(
String.join(":", Arrays.copyOfRange(words, 3, words.length)) + "\n"
);
message.wrappingWidthProperty().bind(this.widthProperty());
textFlow.getChildren().addAll(timePlayer, message);
}
}
public void increaseOpacity() {
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(255, 255, 255, 0.2); -fx-background-color: rgba(255, 255, 255, 0.2);");
this.textFlow.setStyle(
"-fx-background: rgba(255, 255, 255, 0.2); -fx-background-color: rgba(255, 255, 255, 0.2); -fx-padding: 10px"
);
}
public void decreaseOpacity() {
this.lookup(".scroll-pane").setStyle("-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0);");
this.textFlow.setStyle(
"-fx-background: rgba(135, 206, 235, 0.0); -fx-background-color: rgba(135, 206, 235, 0.0); -fx-padding: 10px"
);
}
}
@@ -1,10 +1,19 @@
package seng302.visualiser.fxObjects;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.*;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.Model;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
// TODO: 16/08/17 this class used to be well written... FeelsBadMan. Maybe lose the ternary operators.
/**
@@ -26,6 +35,47 @@ public class MarkArrowFactory {
public static final double ARROW_HEAD_WIDTH = 6;
public static final double STROKE_WIDTH = 3;
public static Model constructEntryArrow3D (
RoundingSide roundingSide, double angle, ModelType type) {
Model entryArrow = ModelFactory.importModel(type);
double angleDeg = angle;
angle = 180 - angle;
angle = Math.toRadians(angle);
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double relativeX = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
double relativeY = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
double xStart = relativeX + -10 * Math.sin(angle);
double yStart = relativeY + -10 * Math.cos(angle);
entryArrow.getAssets().getTransforms().addAll(
new Translate(xStart, yStart, 0),
new Rotate(angleDeg, new Point3D(0,0,1))
);
return entryArrow;
}
public static Model constructExitArrow3D (
RoundingSide roundingSide, double angle, ModelType type) {
Model exitArrow = ModelFactory.importModel(type);
double angleDeg = angle;
angle = 180 - angle;
angle = Math.toRadians(angle);
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xStart = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
double yStart = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
exitArrow.getAssets().getTransforms().addAll(
new Translate(xStart, yStart, 0),
new Rotate(angleDeg, new Point3D(0,0,1))
);
return exitArrow;
}
/**
* Creates an entry arrow group showing an arrow into and out of the rounding area. It is centered on (0, 0).
* @param roundingSide The side of the boat that will be closest to the mark.
@@ -35,48 +85,72 @@ public class MarkArrowFactory {
* @return The group containing all JavaFX objects.
*/
public static Group constructEntryArrow (RoundingSide roundingSide, double angleOfEntry,
double angleOfExit, Paint colour) {
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit && Math.abs(angleOfExit - angleOfEntry) < 180) {
double angleOfExit, Paint colour) {
// Check to see if the the angle around mark would take you inside of it. (less than 180)
// If so make interior angle.
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit &&
Math.abs(angleOfExit - angleOfEntry) < 180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit && -Math.abs(angleOfEntry - angleOfExit) > -180) {
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit &&
-Math.abs(angleOfEntry - angleOfExit) > -180) {
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
}
angleOfEntry = 180 - angleOfEntry;
//Create regular exit arrow.
Group arrow = new Group();
Group exitSection = constructExitArrow(roundingSide, angleOfExit, colour);
//Reverse angles to make arc
angleOfEntry = 180 - angleOfEntry;
angleOfExit = 180 - angleOfExit;
//Maker the arc
Arc roundSection = new Arc(
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
(roundingSide == RoundingSide.PORT ? -180 : 0) + angleOfEntry,
//Where to start drawing arc from
(roundingSide == RoundingSide.PORT ? 0 : angleOfEntry),
//Which way to go around the mark. (clockwise vs anticlockwise)
roundingSide == RoundingSide.PORT ? Math.abs(angleOfExit - angleOfEntry) : -Math.abs(angleOfEntry - angleOfExit)
);
roundSection.setStrokeWidth(STROKE_WIDTH);
roundSection.setType(ArcType.OPEN);
roundSection.setStroke(colour);
roundSection.setFill(new Color(0,0,0,0));
//Revert angle to normal for line segment. Invert Port/Starboard since it is an entry arrow.
Polygon entrySection = constructLineSegment(
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT, 180 + angleOfEntry, colour
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT,
180 + angleOfEntry, colour
);
arrow.getChildren().addAll(exitSection, roundSection, entrySection);
return arrow;
}
/**
* Make an arrow when the turning is not around the outside of the mark.
*
* @param roundingSide side to round on.
* @param angleOfExit angle of entry
* @param angleOfEntry angle of exit
* @param colour colour of arrow
* @return the arrow.
*/
private static Group makeInteriorAngle (RoundingSide roundingSide, double angleOfExit, double angleOfEntry, Paint colour) {
Group arrow = new Group();
Polygon lineSegment;
//Reverse angle of exit/entry to find position between them
angleOfEntry = Math.toRadians(360 - angleOfEntry);
angleOfExit = Math.toRadians(180 - angleOfExit);
//Find start of entry arrow if it was a regular arrow.
int multiplier = roundingSide == RoundingSide.STARBOARD ? -1 : 1;
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfEntry + Math.PI / 2);
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfEntry + Math.PI / 2);
xStart = xStart + (ARROW_LENGTH * Math.sin(angleOfEntry));
yStart = yStart + (ARROW_LENGTH * Math.cos(angleOfEntry));
//Find of end exit arrow if it was a regular arrow.
multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
double xEnd = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfExit + Math.PI / 2);
double yEnd = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfExit + Math.PI / 2);
xEnd = xEnd + (ARROW_LENGTH * Math.sin(angleOfExit));
yEnd = yEnd + (ARROW_LENGTH * Math.cos(angleOfExit));
//Make line between these points.
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
@@ -85,12 +159,14 @@ public class MarkArrowFactory {
lineSegment.setFill(Color.BLUE);
lineSegment.setStrokeWidth(STROKE_WIDTH);
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
//Make arrow head at the angle between these points.
Polyline arrowHead = constructArrowHead(
90 + Math.toDegrees(Math.atan2(yStart - yEnd, xEnd - xStart)),
colour
);
arrowHead.setLayoutX(xEnd);
arrowHead.setLayoutY(yEnd);
//Construct arrow.
arrow.getChildren().addAll(lineSegment, arrowHead);
return arrow;
}
@@ -113,6 +189,15 @@ public class MarkArrowFactory {
return arrow;
}
/**
* Constructs a line rotated to the correct angle and and in the correct position for a mark at
* position 0,0. Note that a line segment is assumed to be facing away from the mark so for
* entry Starboard make the RoundingSide Port and vice versa.
* @param roundingSide Rounding side of an exit arrow. (Reversed for entry)
* @param angle Angle of line segment.
* @param colour the desired colour of the line.
* @return Line segmented at correct rotation centered at (0,0)
*/
private static Polygon constructLineSegment (RoundingSide roundingSide, double angle, Paint colour) {
Polygon lineSegment;
angle = Math.toRadians(angle);
@@ -122,8 +207,8 @@ public class MarkArrowFactory {
double xEnd = xStart + (ARROW_LENGTH * Math.sin(angle));
double yEnd = yStart + (ARROW_LENGTH * Math.cos(angle));
lineSegment = new Polygon(
xStart, yStart,
xEnd, yEnd
xStart, yStart,
xEnd, yEnd
);
lineSegment.setStroke(colour);
lineSegment.setFill(Color.BLUE);
@@ -132,6 +217,12 @@ public class MarkArrowFactory {
return lineSegment;
}
/**
* Constructs a PolyLine in the shape of an arrow head.
* @param rotation direction for the arrow head to point.
* @param colour colour of the arrow head
* @return the arrowhead shaped PolyLine.
*/
private static Polyline constructArrowHead (double rotation, Paint colour) {
Polyline arrow = new Polyline(
-ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH,
@@ -1,4 +1,4 @@
package seng302.visualiser.fxObjects;
package seng302.visualiser.fxObjects.assets_2D;
import java.util.HashMap;
import java.util.Map;
@@ -1,4 +1,4 @@
package seng302.visualiser.fxObjects;
package seng302.visualiser.fxObjects.assets_2D;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
@@ -1,4 +1,4 @@
package seng302.visualiser.fxObjects;
package seng302.visualiser.fxObjects.assets_2D;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
@@ -1,4 +1,4 @@
package seng302.visualiser.fxObjects;
package seng302.visualiser.fxObjects.assets_2D;
import java.util.ArrayList;
import java.util.List;
@@ -7,11 +7,12 @@ import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import seng302.visualiser.fxObjects.MarkArrowFactory;
/**
* Visual object for a mark. Contains a coloured circle and any specified arrows.
*/
public class Marker extends Group {
public class Marker2D extends Group {
private Circle mark = new Circle();
private Paint colour = Color.BLACK;
@@ -23,18 +24,20 @@ public class Marker extends Group {
/**
* Creates a new Marker containing only a circle. The default colour is black.
*/
public Marker() {
public Marker2D() {
mark.setRadius(5);
mark.setCenterX(0);
mark.setCenterY(0);
Platform.runLater(() -> this.getChildren().addAll(mark, new Group())); //Empty group placeholder or arrows.
Platform.runLater(() -> this.getChildren()
.addAll(mark, new Group())); //Empty group placeholder or arrows.
}
/**
* Creates a new Marker containing only a circle of the given colour.
*
* @param colour the desired colour for the marker.
*/
public Marker(Paint colour) {
public Marker2D(Paint colour) {
this();
this.colour = colour;
mark.setFill(colour);
@@ -43,6 +46,7 @@ public class Marker extends Group {
/**
* Adds an exit and entry arrow pair to the mark. Arrows are hidden and shown in the order they
* are created by calling showNextEnterArrow() or showNextExitArrow()
*
* @param roundingSide the side the marker will be from the perspective of the arrow.
* @param entryAngle The angle the arrow will point towards a marker
* @param exitAngle The angle the arrow wil point from the marker.
@@ -59,7 +63,8 @@ public class Marker extends Group {
}
/**
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows
* become hidden.
*/
public void showNextEnterArrow() {
showArrow(enterArrows, enterArrowIndex);
@@ -67,7 +72,8 @@ public class Marker extends Group {
}
/**
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become
* hidden.
*/
public void showNextExitArrow() {
showArrow(exitArrows, exitArrowIndex);
@@ -76,7 +82,8 @@ public class Marker extends Group {
private void showArrow(List<Group> arrowList, int arrowListIndex) {
if (arrowListIndex < arrowList.size()) {
if (arrowListIndex == 1) {;
if (arrowListIndex == 1) {
;
}
Platform.runLater(() -> {
this.getChildren().remove(1);
@@ -1,4 +1,4 @@
package seng302.visualiser.fxObjects;
package seng302.visualiser.fxObjects.assets_2D;
import javafx.application.Platform;
import javafx.scene.CacheHint;
@@ -0,0 +1,25 @@
package seng302.visualiser.fxObjects.assets_2D;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
/**
* Created by cir27 on 5/09/17.
*/
public class WindArrow extends Polyline {
public WindArrow(Paint fill) {
this.getPoints().addAll(
-10d, 15d,
0d, 25d,
0d, -25d,
0d, 25d,
10d, 15d
);
this.setStrokeLineCap(StrokeLineCap.ROUND);
this.setStroke(fill);
this.setStrokeWidth(5);
this.setStrokeLineJoin(StrokeLineJoin.ROUND);
}
}
@@ -0,0 +1,30 @@
package seng302.visualiser.fxObjects.assets_3D;
/**
* Enum for boat meshes. Enum values should be of the form :
* ENUM_VALUE (hull file, mast file, Y offset of mast CoR from origin, sail file, Y offset of sail CoR from origin, jib file, fixed sail)
* Files must be valid .stl files.
*/
public enum BoatMeshType {
DINGHY("dinghy_hull.stl", "dinghy_mast.stl", 1.36653, "dinghy_sail.stl", 1.36653, null, false),
CAT_ATE_A_MERINGUE("catamaran_hull.stl", "catamaran_mast.stl", 0.997, "catamaran_sail.stl",
0.997, null, false),
PIRATE_SHIP("pirateship_hull.stl", "pirateship_mast.stl", -0.5415, "pirateship_mainsail.stl",
-0.5415, "pirateship_frontsail.stl", true);
final String hullFile, mastFile, sailFile, jibFile;
final double mastOffset, sailOffset;
final boolean fixedSail;
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile,
double sailOffset, String jibFile, boolean fixedSail) {
this.hullFile = hullFile;
this.mastFile = mastFile;
this.mastOffset = mastOffset;
this.sailFile = sailFile;
this.sailOffset = sailOffset;
this.jibFile = jibFile;
this.fixedSail = fixedSail;
}
}
@@ -0,0 +1,74 @@
package seng302.visualiser.fxObjects.assets_3D;
import javafx.animation.AnimationTimer;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
/**
* Container class for a group of 3d objects representing a boat and it's animation.
*/
public class BoatModel extends Model {
private static final int HULL_INDEX = 0;
private static final int MAST_INDEX = 1;
private static final int SAIL_INDEX = 2;
private BoatMeshType meshType;
/**
* Stores a model and it's optional animation.
* @param boatAssets The group with 3d assets for the boat.
* @param animation Animation, can be null.
*/
BoatModel(Group boatAssets, AnimationTimer animation, BoatMeshType meshType) {
super(boatAssets, animation);
this.meshType = meshType;
}
/**
* Rotates the sail of this model by the given amount.
* @param degrees The rotation of the sail in degrees
*/
public void rotateSail(double degrees) {
if (!meshType.fixedSail) {
MeshView mast = getMeshViewChild(MAST_INDEX);
MeshView sail = getMeshViewChild(SAIL_INDEX);
mast.getTransforms().setAll(
new Rotate(degrees, 0, -meshType.mastOffset, 0, new Point3D(0, 0, 1))
);
sail.getTransforms().setAll(
new Rotate(degrees, 0, -meshType.sailOffset,0, new Point3D(0, 0, 1))
);
}
}
public void hideSail() {
getMeshViewChild(SAIL_INDEX).setVisible(false);
}
public void showSail() {
getMeshViewChild(SAIL_INDEX).setVisible(true);
}
/**
* Changes the colour of the model in this class.
* @param newColour the new colour for the boat.
*/
public void changeColour(Color newColour) {
changeColourChild(HULL_INDEX, newColour);
changeColourChild(MAST_INDEX, newColour);
}
private void changeColourChild(int index, Color newColour) {
MeshView meshView = getMeshViewChild(index);
meshView.setMaterial(new PhongMaterial(newColour));
}
private MeshView getMeshViewChild(int index) {
return (MeshView) assets.getChildren().get(index);
}
}
@@ -0,0 +1,133 @@
package seng302.visualiser.fxObjects.assets_3D;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/
public class BoatObject extends Group {
@FunctionalInterface
public interface SelectedBoatListener {
void notifySelected(BoatObject boatObject, Boolean isSelected);
}
private BoatModel boatAssets;
private Group wake;
private Color colour = Color.BLACK;
private Boolean isSelected = false;
private Rotate rotation = new Rotate(0, new Point3D(0,0,1));
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
/**
* Creates a BoatGroup with the default triangular boat polygon.
*/
public BoatObject() {
boatAssets = ModelFactory.boatGameView(BoatMeshType.PIRATE_SHIP, colour);
boatAssets.hideSail();
boatAssets.getAssets().getTransforms().addAll(
rotation
);
boatAssets.getAssets().setOnMouseClicked(event -> {
setIsSelected(!isSelected);
updateListeners();
});
boatAssets.getAssets().setCache(true);
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
super.getChildren().addAll(boatAssets.getAssets());
}
public void setFill (Color value) {
this.colour = value;
boatAssets.changeColour(colour);
}
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The rotation by which the boat moves
* @param velocity The velocity the boat is moving
* @param sailIn Boolean to toggle sail state.
* @param windDir .
*/
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
Platform.runLater(() -> {
rotateTo(rotation, sailIn, windDir);
this.layoutXProperty().setValue(x);
this.layoutYProperty().setValue(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
});
}
private Double normalizeHeading(double heading, double windDirection) {
Double normalizedHeading = heading - windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
private void rotateTo(double heading, boolean sailsIn, double windDir) {
rotation.setAngle(heading);
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
if (sailsIn) {
boatAssets.showSail();
Double sailWindOffset = 30.0;
Double upwindAngleLimit = 15.0;
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
Double normalizedHeading = normalizeHeading(heading, windDir);
if (normalizedHeading < 180) {
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
boatAssets.rotateSail(-upwindAngleLimit);
} else if (normalizedHeading > 90 + sailWindOffset){
boatAssets.rotateSail(-90 + downwindAngleLimit);
} else {
boatAssets.rotateSail(-heading + windDir + sailWindOffset);
}
} else {
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)) {
boatAssets.rotateSail(upwindAngleLimit);
} else if (normalizedHeading < 270 - sailWindOffset) {
boatAssets.rotateSail(90 - downwindAngleLimit);
} else {
boatAssets.rotateSail(-heading + windDir - sailWindOffset);
}
}
} else {
boatAssets.hideSail();
}
}
public Group getWake () {
return wake;
}
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
}
private void updateListeners() {
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
sbl.notifySelected(this, this.isSelected);
}
}
public void addSelectedBoatListener(SelectedBoatListener sbl) {
selectedBoatListenerListeners.add(sbl);
}
}
@@ -0,0 +1,93 @@
package seng302.visualiser.fxObjects.assets_3D;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.scene.Group;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.MarkArrowFactory.RoundingSide;
/**
* Visual object for a mark. Contains a coloured circle and any specified arrows.
*/
public class Marker3D extends Group {
private Model mark;
private List<Group> enterArrows = new ArrayList<>();
private List<Group> exitArrows = new ArrayList<>();
private int enterArrowIndex = 0;
private int exitArrowIndex = 0;
private ModelType markType;
private ModelType arrowType;
/**
* Creates a new Marker containing only a circle. The default colour is black.
*/
public Marker3D(ModelType modelType) {
markType = modelType;
switch (markType) {
case PLAIN_MARKER:
arrowType = ModelType.PLAIN_ARROW;
break;
case FINISH_MARKER:
arrowType = ModelType.FINISH_ARROW;
break;
case START_MARKER:
arrowType = ModelType.START_ARROW;
break;
}
mark = ModelFactory.importModel(modelType);
Platform.runLater(() ->
this.getChildren().addAll(mark.getAssets())
);
}
/**
* Adds an exit and entry arrow pair to the mark. Arrows are hidden and shown in the order they
* are created by calling showNextEnterArrow() or showNextExitArrow()
* @param roundingSide the side the marker will be from the perspective of the arrow.
* @param entryAngle The angle the arrow will point towards a marker
* @param exitAngle The angle the arrow wil point from the marker.
*/
public void addArrows(RoundingSide roundingSide, double entryAngle,
double exitAngle) {
//Change Color.GRAY to this.colour to revert all gray arrows.
enterArrows.add(
MarkArrowFactory.constructEntryArrow3D(roundingSide, entryAngle, arrowType).getAssets()
);
exitArrows.add(
MarkArrowFactory.constructExitArrow3D(roundingSide, exitAngle, arrowType).getAssets()
);
}
/**
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
*/
public void showNextEnterArrow() {
showArrow(enterArrows, enterArrowIndex);
enterArrowIndex++;
}
/**
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
*/
public void showNextExitArrow() {
showArrow(exitArrows, exitArrowIndex);
exitArrowIndex++;
}
private void showArrow(List<Group> arrowList, int arrowListIndex) {
if (arrowListIndex < arrowList.size()) {
Platform.runLater(() ->
this.getChildren().setAll(mark.getAssets(), arrowList.get(arrowListIndex))
);
}
}
/**
* Hides all arrows.
*/
public void hideAllArrows() {
Platform.runLater(() -> this.getChildren().setAll(mark.getAssets()));
}
}
@@ -0,0 +1,48 @@
package seng302.visualiser.fxObjects.assets_3D;
import javafx.animation.AnimationTimer;
import javafx.scene.Group;
/**
* Class for generic imported 3D model. Animation terminates on if removed from scene.
*/
public class Model {
protected AnimationTimer animationTimer;
protected Group assets;
public Model (Group assets, AnimationTimer animation) {
this.assets = assets;
this.animationTimer = animation;
if (animation != null) {
animation.start();
assets.sceneProperty().addListener((obs, oldVal, newVal) -> {
if (newVal == null) {
animationTimer.stop();
animationTimer = null;
}
});
}
}
void setAnimation(AnimationTimer animation) {
animationTimer = animation;
if (animation != null) {
animation.start();
}
}
/**
* Stops the animation of this model.
*/
public void stopAnimation() {
if (animationTimer != null) {
animationTimer.stop();
animationTimer = null;
}
}
public Group getAssets() {
return this.assets;
}
}
@@ -0,0 +1,243 @@
package seng302.visualiser.fxObjects.assets_3D;
import com.interactivemesh.jfx.importer.col.ColModelImporter;
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
import javafx.animation.AnimationTimer;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Circle;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
/**
* Factory class for creating 3D models of boats.
*/
public class ModelFactory {
public static BoatModel boatIconView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
final Rotate animationRotate = new Rotate(0, new Point3D(0,0,1));
boatAssets.getTransforms().addAll(
new Scale(3.3, 3.3, 3.3),
new Rotate(-70, new Point3D(1,0,0)),
new Translate(13,50, 0),
animationRotate
);
boatAssets.getTransforms().add(animationRotate);
BoatModel bo = new BoatModel(boatAssets, null, boatType);
bo.rotateSail(45);
bo.setAnimation(new AnimationTimer() {
double boatAngle = 0;
Rotate rotate = animationRotate;
@Override
public void handle(long now) {
boatAngle += 0.5;
rotate.setAngle(boatAngle);
}
});
boatAssets.getChildren().addAll(
new AmbientLight()
);
return bo;
}
public static BoatModel boatRotatingView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
boatAssets.getTransforms().addAll(
new Scale(40, 40, 40),
new Rotate(90, new Point3D(0,0,1)),
new Rotate(90, new Point3D(0, 1, 0))
);
final Rotate animationRotate = new Rotate(0, new Point3D(1,1,1));
boatAssets.getTransforms().add(animationRotate);
return new BoatModel(boatAssets, new AnimationTimer() {
private double rotation = 0;
private Rotate rotate = animationRotate;
@Override
public void handle(long now) {
rotation += 0.5;
rotate.setAngle(rotation);
}
}, boatType);
}
public static BoatModel boatGameView(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
boatAssets.getTransforms().setAll(
new Scale(0.3, 0.3, 0.3)
);
return new BoatModel(boatAssets, null, boatType);
}
private static Group getUnmodifiedBoatModel(BoatMeshType boatType, Color primaryColour) {
Group boatAssets = new Group();
MeshView hull = importSTL(boatType.hullFile);
hull.setMaterial(new PhongMaterial(primaryColour));
MeshView mast = importSTL(boatType.mastFile);
mast.setMaterial(new PhongMaterial(primaryColour));
MeshView sail = importSTL(boatType.sailFile);
sail.setMaterial(new PhongMaterial(Color.WHITE));
if (boatType.jibFile != null) {
MeshView jib = importSTL(boatType.jibFile);
sail.setMaterial(new PhongMaterial(Color.WHITE));
boatAssets.getChildren().addAll(hull, mast, sail, jib);
} else {
boatAssets.getChildren().addAll(hull, mast, sail);
}
return boatAssets;
}
private static MeshView importSTL(String fileName) {
StlMeshImporter importer = new StlMeshImporter();
importer.read(ModelFactory.class.getResource("/meshes/boatSTLs/" + fileName));
MeshView importedFile = new MeshView(importer.getImport());
importedFile.setCache(true);
importedFile.setCacheHint(CacheHint.SCALE_AND_ROTATE);
return new MeshView(importer.getImport());
}
public static Model importModel(ModelType tokenType) {
Group assets;
if (tokenType.filename == null) {
assets = new Group();
} else {
ColModelImporter importer = new ColModelImporter();
importer.read(ModelFactory.class.getResource("/meshes/" + tokenType.filename));
assets = new Group(importer.getImport());
assets.setCache(true);
assets.setCacheHint(CacheHint.SCALE_AND_ROTATE);
}
switch (tokenType) {
case VELOCITY_PICKUP:
return makeCoinPickup(assets);
case FINISH_MARKER:
case PLAIN_MARKER:
case START_MARKER:
return makeMarker(assets);
case OCEAN:
return makeOcean(assets);
case BORDER_PYLON:
case BORDER_BARRIER:
return makeBarrier(assets);
case FINISH_LINE:
case START_LINE:
case GATE_LINE:
return makeGate(assets);
case WAKE:
return makeWake(assets);
case TRAIL_SEGMENT:
return makeTrail(assets);
case PLAYER_IDENTIFIER:
return makeIdentifierIcon(assets);
case START_ARROW:
case FINISH_ARROW:
case PLAIN_ARROW:
makeArrow(assets);
default:
return new Model(new Group(assets), null);
}
}
private static Model makeCoinPickup(Group assets){
assets.setRotationAxis(new Point3D(1,0,0));
assets.setRotate(90);
assets.setTranslateX(0.2);
assets.setTranslateY(1);
assets.getTransforms().addAll(
new Translate(0,-1,0),
new Rotate(0 ,new Point3D(1,1,1))
);
return new Model(new Group(assets), new AnimationTimer() {
private double rotation = 0;
private Group group = assets;
@Override
public void handle(long now) {
rotation += 1;
((Rotate) group.getTransforms().get(1)).setAngle(rotation);
}
});
}
private static Model makeMarker(Group marker) {
ColModelImporter importer = new ColModelImporter();
importer.read(ModelFactory.class.getResource("/meshes/" + ModelType.MARK_AREA.filename));
Group area = new Group(importer.getImport());
area.getChildren().add(marker);
area.getTransforms().add(new Rotate(90, new Point3D(1, 0, 0)));
return new Model(new Group(area), null);
}
private static Model makeOcean(Group group) {
Circle ocean = new Circle(
0,0,250, Color.SKYBLUE
);
ocean.setStroke(Color.TRANSPARENT);
group.getChildren().add(ocean);
return new Model(new Group(group), null);
}
private static Model makeBarrier(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(assets), null);
}
private static Model makeGate(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(assets), null);
}
private static Model makeWake(Group assets) {
assets.getTransforms().setAll(
new Rotate(-90, new Point3D(0,0,1)),
new Rotate(90, new Point3D(1,0,0)),
new Scale(0.5, 0.5, 0.5)
);
return new Model(new Group(assets), null);
}
private static Model makeTrail(Group trailPiece) {
trailPiece.getTransforms().addAll(
new Rotate(-90, new Point3D(0,0,1)),
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(trailPiece), null);
}
private static Model makeIdentifierIcon(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0)),
new Scale(0.5, 0.5, 0.5)
);
return new Model(assets, null);
}
private static Model makeArrow(Group assets) {
assets.getTransforms().addAll(
new Rotate(90, new Point3D(1,0,0))
);
return new Model(new Group(assets), null);
}
}
@@ -0,0 +1,32 @@
package seng302.visualiser.fxObjects.assets_3D;
/**
* Enum for models. Values should be the name of the file and files should be .dae files with texture
* information included. Can be null in which case assets are assumed to be empty.
*/
public enum ModelType {
VELOCITY_PICKUP("velocity_pickup.dae"),
FINISH_MARKER ("finish_marker.dae"),
START_MARKER ("start_marker.dae"),
PLAIN_MARKER ("plain_marker.dae"),
MARK_AREA ("mark_area.dae"),
OCEAN (null),
BORDER_PYLON ("barrier_pole.dae"),
BORDER_BARRIER ("barrier_segment.dae"),
FINISH_LINE ("finish_line.dae"),
START_LINE ("start_line.dae"),
GATE_LINE ("gate_line.dae"),
WAKE ("wake.dae"),
TRAIL_SEGMENT ("trail_segment.dae"),
PLAYER_IDENTIFIER ("player_identifier.dae"),
PLAIN_ARROW ("arrow.dae"),
START_ARROW ("start_arrow.dae"),
FINISH_ARROW ("finish_arrow.dae");
final String filename;
ModelType(String filename) {
this.filename = filename;
}
}
@@ -0,0 +1,36 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package seng302.visualiser.validators;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.beans.DefaultProperty;
import javafx.scene.control.TextInputControl;
@DefaultProperty("icon")
public class FieldLengthValidator extends ValidatorBase {
Integer maxLength;
public FieldLengthValidator(Integer length) {
maxLength = length;
}
protected void eval() {
if(this.srcControl.get() instanceof TextInputControl) {
this.evalTextInputField();
}
}
private void evalTextInputField() {
TextInputControl textField = (TextInputControl)this.srcControl.get();
if(textField.getLength() > maxLength) {
this.hasErrors.set(true);
} else {
this.hasErrors.set(false);
}
}
}
@@ -0,0 +1,32 @@
package seng302.visualiser.validators;
import com.jfoenix.validation.base.ValidatorBase;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javafx.beans.DefaultProperty;
import javafx.scene.control.TextInputControl;
@DefaultProperty("icon")
public class HostNameFieldValidator extends ValidatorBase {
public HostNameFieldValidator() {
}
protected void eval() {
if(this.srcControl.get() instanceof TextInputControl) {
this.evalTextInputField();
}
}
protected void evalTextInputField() {
TextInputControl textField = (TextInputControl)this.srcControl.get();
try{
InetAddress.getByName(textField.getText());
this.hasErrors.set(false);
} catch (UnknownHostException e) {
this.hasErrors.set(true);
}
}
}
@@ -0,0 +1,40 @@
package seng302.visualiser.validators;
import com.jfoenix.validation.base.ValidatorBase;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javafx.beans.DefaultProperty;
import javafx.scene.control.TextInputControl;
@DefaultProperty("icon")
public class NumberRangeValidator extends ValidatorBase {
Integer lowerLimit;
Integer upperLimit;
public NumberRangeValidator(Integer lower, Integer upper) {
lowerLimit = lower;
upperLimit = upper;
}
protected void eval() {
if(this.srcControl.get() instanceof TextInputControl) {
this.evalTextInputField();
}
}
protected void evalTextInputField() {
TextInputControl textField = (TextInputControl)this.srcControl.get();
try {
Integer portNum = Integer.parseInt(textField.getText());
if (lowerLimit <= portNum && portNum <= upperLimit) {
this.hasErrors.set(false);
} else {
this.hasErrors.set(true);
}
} catch (NumberFormatException e) {
this.hasErrors.set(true);
}
}
}
@@ -0,0 +1,21 @@
package seng302.visualiser.validators;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.base.ValidatorBase;
public class ValidationTools {
/**
*
* @return
*/
public static Boolean validateTextField(JFXTextField textField) {
textField.validate();
for (ValidatorBase validator : textField.getValidators()) {
if (validator.getHasErrors()) {
return false;
}
}
return true;
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.
+68
View File
@@ -0,0 +1,68 @@
#serverListMainGridPane {
-fx-background-color: -fx-pp-background-color;
}
.scroll-pane {
-fx-background: -fx-pp-background-color;
-fx-border-style: hidden;
-fx-background-insets: 0;
-fx-padding: 0;
}
.scroll-pane:focused {
-fx-background-insets: 0;
}
.scroll-pane .corner {
-fx-background-insets: 0;
}
#playerListVBox, #playerListScrollPane {
-fx-background: transparent;
}
#customizeButton, #leaveLobbyButton, #beginRaceButton {
-fx-background-color: -fx-pp-light-text-color; /* inverted */
-fx-text-fill: -fx-pp-theme-color; /* inverted */
-fx-font-size: 18px;
-fx-pref-height: 65px;
}
#customizeButton:hover, #leaveLobbyButton:hover, #beginRaceButton:hover {
-fx-font-size: 20px;
}
.invertedButton .jfx-rippler {
-jfx-rippler-fill: white;
}
#connectLabel, #serverPortNumber, #serverHostName {
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 16px;
}
#serverHostName, #serverPortNumber {
-jfx-focus-color: -fx-pp-light-text-color;
-jfx-unfocus-color: -fx-pp-light-text-color;
}
#connectGridPane {
-fx-background-color: -fx-pp-theme-color;
}
#serverListMainGridPane {
-fx-background-image: url("/images/waves.png");
-fx-background-repeat: no-repeat;
-fx-background-size: cover;
}
#playerList, #playerListScrollPane {
-fx-background-color: transparent;
}
#serverMap {
/*-fx-background-image: url("/images/mapbg.jpg");*/
/*-fx-background-repeat: no-repeat;*/
/*-fx-background-size: cover;*/
-fx-background-color: dodgerblue;
}
+102
View File
@@ -0,0 +1,102 @@
@font-face {
src: url("Baloo-Regular.ttf");
}
* {
-fx-pp-light-text-color: #fff;
-fx-pp-dark-text-color: #6c6c6c;
-fx-pp-theme-color: #3493e3;
-fx-pp-light-theme-color: #38a2fa;
-fx-pp-background-color: transparent;
-fx-pp-front-color: #fff;
-fx-font-family: "Baloo";
-fx-pp-dropshadow-light: dropshadow(gaussian, rgba(0, 0, 0, 0.1), 10.0, 0.2, 3, 4);
-fx-pp-dropshadow-dark: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 10.0, 0.2, 5, 6);
-fx-pp-dropshadow-headers: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 10.0, 0.2, 3, 4);
}
/*GridPane .jfx-button {*/
/*-fx-min-height: 65px; !* because we changed the font, we now need to set mini height for all buttons *!*/
/*}*/
.jfx-button {
-fx-cursor: hand;
}
.jfx-button:pressed {
-fx-cursor: wait;
}
.jfx-text-field {
-jfx-label-float: true;
}
.jfx-decorator {
-fx-decorator-color: -fx-pp-theme-color;
}
.jfx-decorator .jfx-decorator-buttons-container {
-fx-background-color: -fx-decorator-color;
}
.jfx-decorator .resize-border {
-fx-border-color: -fx-decorator-color;
-fx-border-width: 0 4 4 4;
}
/********* customised scroll bar for scroll pane ***********/
/* The main scrollbar **track** CSS class */
.scroll-bar:horizontal .track,
.scroll-bar:vertical .track {
-fx-background-color: white;
-fx-border-color: derive(-fx-pp-background-color, 50%);
-fx-border-style: solid;
-fx-border-width: 3px;
-fx-background-radius: 0em;
-fx-border-radius: 0em;
}
/* The increment and decrement button CSS class of scrollbar */
.scroll-bar:horizontal .increment-button,
.scroll-bar:horizontal .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 0 10 0;
}
/* The increment and decrement button CSS class of scrollbar */
.scroll-bar:vertical .increment-button,
.scroll-bar:vertical .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 10 0 10; /* set scroll bar width */
}
.scroll-bar .increment-arrow,
.scroll-bar .decrement-arrow {
-fx-shape: " ";
-fx-padding: 0;
}
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */
.scroll-bar:horizontal .thumb,
.scroll-bar:vertical .thumb {
-fx-background-color: derive(#c9c9cb, 0%);
-fx-background-insets: 2, 0, 0;
-fx-background-radius: 0em;
}
.scroll-bar {
-fx-background-insets: 0;
}
.slider .thumb {
-fx-background-color: -fx-pp-theme-color;
}
.slider .track {
-fx-background-color: -fx-pp-dark-text-color;
}
+65
View File
@@ -0,0 +1,65 @@
@font-face {
src: url("digital-7-mono.ttf");
}
#timerGrid{
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
GridPane .timer * {
-fx-font-family: "Digital-7 Mono" !important;
-fx-font-size: 30;
}
#timerLabel{
-fx-font-size: 21px;
}
#raceInfoArea{
-fx-background-color: rgba(255, 255, 255, 0.6);
}
#chatGridPane {
-fx-background-color: transparent;
}
#chatHistoryHolder {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#chatInputHolder {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#windGridPane {
-fx-background-color: rgba(255, 255, 255, 0.6);
-fx-effect: -fx-pp-dropshadow-light;
}
#windHolder {
-fx-background-color: rgba(255, 255, 255, 0.5);
}
#chatSend {
-fx-background-color: -fx-pp-front-color;
-fx-text-fill: -fx-pp-theme-color;
-fx-font-size: 13px;
-fx-pref-height: 35px;
}
#chatSend:hover {
-fx-font-size: 15px;
}
#chatInput {
-jfx-focus-color: -fx-pp-light-theme-color;
-jfx-unfocus-color: -fx-pp-dark-text-color;
-fx-background-color: transparent;
}
#windImageView {
-fx-image: url("/images/wind-180.png");
}
+82
View File
@@ -0,0 +1,82 @@
#serverListMainGridPane {
-fx-background-color: -fx-pp-background-color;
}
.scroll-pane {
-fx-background: -fx-pp-background-color;
-fx-border-style: hidden;
-fx-background-insets: 0;
-fx-padding: 0;
}
.scroll-pane:focused {
-fx-background-insets: 0;
}
.scroll-pane .corner {
-fx-background-insets: 0;
}
#serverListVBox, #serverListScrollPane {
-fx-background: transparent;
}
.serverListing {
-fx-alignment: center;
-fx-background-color: transparent;
}
#hostButton {
-fx-background-color: -fx-pp-theme-color;
-fx-font-size: 20px;
-fx-pref-height: 65px;
-fx-text-fill: -fx-pp-light-text-color;
}
#hostButton:hover {
-fx-background-color: -fx-pp-light-theme-color;
-fx-font-size: 23px;
}
#connectButton {
-fx-background-color: -fx-pp-light-text-color; /* inverted */
-fx-text-fill: -fx-pp-theme-color; /* inverted */
-fx-font-size: 20px;
-fx-pref-height: 65px;
}
#connectButton:hover {
-fx-font-size: 23px;
}
#connectLabel, #serverPortNumber, #serverHostName {
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 18px;
}
#serverHostName, #serverPortNumber {
-jfx-focus-color: -fx-pp-light-text-color;
-jfx-unfocus-color: -fx-pp-light-text-color;
-fx-prompt-text-fill: -fx-pp-light-text-color;
}
#serverHostName .error-label, #serverPortNumber .error-label {
-fx-font-size: 12px;
-fx-text-fill: lightblue;
}
#connectGridPane {
-fx-background-color: -fx-pp-theme-color;
}
#serverListMainGridPane{
-fx-background-image: url("/images/waves.png");
-fx-background-repeat: no-repeat;
-fx-background-size: cover;
}
#serverListPane{
-fx-background-color: transparent;
}
@@ -0,0 +1,26 @@
#startBtn {
-fx-font-size: 20px;
-fx-text-fill: -fx-pp-light-text-color;
-fx-background-color: -fx-pp-theme-color;
}
.jfx-rippler {
-jfx-rippler-fill: -fx-pp-light-theme-color; /* set rippler color for button */
}
#startBtn:hover {
-fx-font-size: 23px;
-fx-background-color: -fx-pp-light-theme-color;
}
#mainStackPane {
-fx-background-image: url("/images/waves.jpg");
-fx-background-size: cover;
}
#headText {
-fx-background-color: transparent;
-fx-font-size: 80px;
-fx-text-fill: -fx-pp-light-text-color;
-fx-effect: -fx-pp-dropshadow-headers;
}
@@ -0,0 +1,17 @@
#playerName {
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 18px;
}
#playerListCell {
-fx-background-color: skyblue;
-fx-effect: -fx-pp-dropshadow-light;
}
#playerListCell:hover {
-fx-effect: -fx-pp-dropshadow-dark;
}
#playerCellVBox {
-fx-background-color: -fx-pp-background-color;
}
@@ -0,0 +1,30 @@
#serverListCell {
-fx-background-color: -fx-pp-front-color;
-fx-effect: -fx-pp-dropshadow-light;
}
#serverListCell:hover {
-fx-effect: -fx-pp-dropshadow-dark;
}
#serverCellVBox {
-fx-background-color: -fx-pp-background-color;
}
#serverConnButton {
-fx-background-color: -fx-pp-theme-color;
-fx-font-size: 14px;
-fx-text-fill: -fx-pp-light-text-color;
}
#serverName, #serverPlayerCount, #mapName {
-fx-text-fill: -fx-pp-dark-text-color;
}
#serverName {
-fx-font-size: 20px;
}
#serverPlayerCount, #mapName {
-fx-font-size:14px;
}
@@ -0,0 +1,34 @@
#submitBtn {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 20px !important;
-fx-effect: -fx-pp-dropshadow-dark;
-fx-min-width: 130px;
}
#submitBtn:hover {
-fx-font-size: 23px !important;
-fx-background-color: -fx-pp-light-theme-color;
}
#hostDialogHeader {
-fx-font-size: 23px !important;
}
#boatColorLabel, #colorPicker {
-fx-font-size: 18px;
}
#boatName, #boatColorLabel, #hostDialogHeader {
-fx-text-fill: -fx-pp-dark-text-color;
}
#boatName {
-fx-font-size: 18px;
-fx-prompt-text-fill: -fx-pp-dark-text-color;
}
#boatName .error-label {
-fx-font-size: 13px;
}
@@ -0,0 +1,21 @@
#raceFinishLabel {
-fx-font-size: 23px !important;
-fx-text-fill: -fx-pp-dark-text-color;
}
#finishersList {
}
#playAgain {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 20px !important;
-fx-effect: -fx-pp-dropshadow-dark;
-fx-min-width: 130px;
}
#playAgain:hover {
-fx-font-size: 23px !important;
-fx-background-color: -fx-pp-light-theme-color;
}
@@ -0,0 +1,47 @@
#maxPlayersGridPane VBox * {
-fx-font-family: monospace !important;
}
#submitBtn {
-fx-background-color: -fx-pp-theme-color;
-fx-text-fill: -fx-pp-light-text-color;
-fx-font-size: 20px;
-fx-effect: -fx-pp-dropshadow-dark;
}
.jfx-rippler {
-jfx-rippler-fill: -fx-pp-light-theme-color; /* set rippler color for button */
}
#submitBtn:hover {
-fx-font-size: 23px;
-fx-background-color: -fx-pp-light-theme-color;
}
#hostDialogHeader {
-fx-font-size: 30px;
-fx-text-fill: -fx-pp-dark-text-color;
}
#serverName {
-jfx-focus-color: -fx-pp-dark-text-color;
-jfx-unfocus-color: -fx-pp-dark-text-color;
-fx-text-fill: -fx-pp-dark-text-color;
-fx-prompt-text-fill: -fx-pp-dark-text-color;
-fx-font-size: 16px;
}
#maxPlayersLabel {
-fx-text-fill: -fx-pp-dark-text-color;
-fx-font-size: 16px;
}
#maxPlayerPromptLabel {
-fx-text-fill: -fx-pp-dark-text-color;
-fx-font-size: 16px;
}
.maxPlayers {
-fx-font-size: 13px;
}
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More