Compare commits

...

368 Commits

Author SHA1 Message Date
Calum 81b2d285e9 Changed arrow colour.
#fix
2017-09-28 12:02:24 +13:00
Calum 5d7f307260 Changed arrow colour.
#fix
2017-09-28 12:02:09 +13:00
alistairjmcintyre 00e2af9c14 - Fixed Merge Errors and some broken lighting AGAIN
#tags [1276]
2017-09-28 02:20:34 +13:00
alistairjmcintyre 73861b2ef3 Merge branch 'develop' into 1276_Next_Mark_Indicator 2017-09-28 02:06:18 +13:00
alistairjmcintyre 4018dd783e - Fixed Merge Errors and some broken lighting
#tags [1276]
2017-09-28 02:05:47 +13:00
Michael Rausch 8b7407bf89 Various bug fixes
- Closed socket when discovery server crashes, so it can be restarted
- Flattened water on terrain mesh
- Added checks to ensure server is started before connecting
- Added a check to ensure client has started  before connecting to the server
- Fixed concurrency issue in server-> client thread list

Tags: #story[1281]
2017-09-28 01:58:49 +13:00
Calum 00ddf117b2 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/images/wind-180.png
2017-09-28 00:26:09 +13:00
Calum a266779709 Added new Wind Waker mesh
#implement #story[1293]
2017-09-28 00:24:34 +13:00
Haoming Yin df24fe072a Added animation for wind turning.
- enlarged the wind image size to centre the compass
- added animation to smooth the turning
2017-09-28 00:13:45 +13:00
Calum a1a34b9bd7 Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-09-27 23:36:14 +13:00
Calum 42d490d6fd Added new Wind Waker mesh
#implement #story[1293]
2017-09-27 23:35:51 +13:00
Haoming Yin cd4a2f8da3 Merge remote-tracking branch 'origin/develop' into develop 2017-09-27 22:47:08 +13:00
Haoming Yin 287cfd77d0 Fix: smoothing wind rotation
- added animation for wind rotation
- enlarged wind image's background to centre the compass

#story[1273]
2017-09-27 22:45:56 +13:00
alistairjmcintyre a08a38c985 - Fixed Merge Errors
#tags [1276]
2017-09-27 22:25:32 +13:00
alistairjmcintyre 810dde6a21 Merge branch 'develop' into 1276_Next_Mark_Indicator
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
#	src/main/java/seng302/visualiser/fxObjects/assets_3D/ModelType.java
2017-09-27 22:15:02 +13:00
alistairjmcintyre 7a76efa2ce - Added ability to color the indicators.
#tags [1276]
2017-09-27 22:07:24 +13:00
William Muir ff4c2cd5b6 Merge branch '1273_Skybox' into 'develop'
1273 skybox

Server Discovery:
 - Created server discovery server.
 - Implemented protocols to support matchmaking & room code connection
 - Improved error handling for server disconnections

Skybox:
 - Added a skybox
 - Added a terrain mesh

See merge request !79
2017-09-27 21:52:34 +13:00
alistairjmcintyre c5e6302f86 - Added circle to boat to indicate which boat is the players and to show where the arrow is pointing.
#tags [1276]
2017-09-27 21:31:02 +13:00
Peter Galloway cd199767ae created new models for all pickups #story[1293] 2017-09-27 20:40:40 +13:00
Michael Rausch 5cc4898ab5 Fixed failing tests & other bug fixes
- Fixed server capacity in server list
- Fixed failing unit tests for chat

Tags: #story[1281] #pair[mra106, cir27]
2017-09-27 20:37:29 +13:00
Haoming Yin 22e1e57c24 Issue #73: If you change controls to 'Continuously turning' before moving into game, it is not applied
- always send turning mode packet when race starts to sync the steering mode

#story[1278]
2017-09-27 20:18:06 +13:00
Haoming Yin c5d2016733 Merge branch 'issue_#67_early_start' into 'develop'
added a check in the start logic to make sure a boat can't cross the start line early



See merge request !74
2017-09-27 19:42:05 +13:00
Kusal Ekanayake efc71f2003 Resolved issue #69 and added dock icon for splash screen. 2017-09-27 19:27:13 +13:00
Alistair McIntyre be72062c8e Merge branch 'develop' into 1276_Next_Mark_Indicator
# Conflicts:
#	src/main/java/seng302/visualiser/fxObjects/assets_3D/ModelFactory.java
2017-09-27 19:25:08 +13:00
Alistair McIntyre 0d212a4a1d - Indicator works correctly, sits in the right place, rotates around the boat correctly and sits on top of the water now.
tags : #story[1276]
2017-09-27 19:20:33 +13:00
Alistair McIntyre 0e9b818071 - Indicator points towards the next mark. Seems to do some funny stuff when entering a compound mark(gate).
tags : #story[1276]
2017-09-27 18:50:38 +13:00
Alistair McIntyre 452e83c1c3 - Marker seems to be detecting the correct mark but somethings wrong with the maths.
tags : #story[1276]
2017-09-27 17:52:54 +13:00
Alistair McIntyre aded794a67 - Added new indicator for direction to next mark.
- Marker rotates towards a given heading.

tags : #story[1276]
2017-09-27 17:17:50 +13:00
Michael Rausch 2b3a972ed5 Various bug fixes
- Fixed bug where an invalid port number would crash the program
- Closed the stage before cleaning up resources. This speeds up closing the app.
- Added error handling for when the client looses connection to the server.

Tags: #story[1281]
2017-09-27 17:14:55 +13:00
Calum 2a523a5664 Added a duck boat mesh.
#implement #story[1274]
2017-09-27 17:03:02 +13:00
Michael Rausch 67f3124cfb Merge branch 'develop' into 1273_Skybox 2017-09-27 15:32:54 +13:00
Michael Rausch 7db387bdec Merged Host Customisation onto 1273_Skybox 2017-09-27 15:31:54 +13:00
Alistair McIntyre 2ceca2fd42 Merge branch 'loading_screens' into 'develop'
Loading screens

# Changes
- Fixed a bug with sound not being muted consistently (the music would play when you press play again even if the music was muted)
- Added a splash screen to show for ~2 seconds on launch, doesn't repeat again while playing the same instance
- Added a loading screen which disappears when the race view has finished being set up (boats in place etc)

# Testing
- Manual testing done, few irrelevant bugs have been found, will log in issue tracker

See merge request !78
2017-09-27 15:05:36 +13:00
Michael Rausch 00ff771fc3 Merge remote-tracking branch 'origin/story1275_host_customization' into 1273_Skybox
# 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/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/GameView3D.java
#	src/main/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java
2017-09-27 14:56:09 +13:00
Michael Rausch d963785679 Added files from merge 2017-09-27 14:47:32 +13:00
Michael Rausch 78f64557c3 Merged dev onto 1273_Skybox 2017-09-27 14:46:12 +13:00
Michael Rausch 982fac38a0 Merge remote-tracking branch 'origin/develop' into 1273_Skybox
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/MessageFactory.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/ClientYacht.java
#	src/main/java/seng302/model/mark/MarkOrder.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView3D.java
#	src/main/java/seng302/visualiser/controllers/ServerListController.java
#	src/main/java/seng302/visualiser/controllers/dialogs/ServerCreationController.java
#	src/main/resources/icons/bumperIcon.png
#	src/main/resources/icons/handlingIcon.png
#	src/main/resources/icons/velocity.png
#	src/main/resources/icons/windWalkerIcon.png
#	src/main/resources/views/RaceView.fxml
#	src/main/resources/views/dialogs/ServerCreationDialog.fxml
2017-09-27 14:23:38 +13:00
Kusal Ekanayake edbfb2f84f Modified timings for load screen. 2017-09-27 14:16:27 +13:00
Kusal Ekanayake c01111038f Merge branch 'develop' into loading_screens
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-27 14:05:46 +13:00
William Muir fd53fd52a4 Forgot to git add the new icon.. again.. sorree
#story[1293]
2017-09-27 12:44:50 +13:00
William Muir 6f62efcc93 Updated the icons to be more uniform and transparent
#story[1293]
2017-09-27 12:44:22 +13:00
Michael Rausch daf3867433 Server discovery bug fixes & error handling improvements
- Fixed concurrency bug that prevented players from connecting to servers
- Discovery server can restart itself if it crashes
- Added nicer error handling for server discovery.
- Using AWS to get servers external IP address.

Tags: #story[1281]
2017-09-27 12:32:17 +13:00
William Muir e5af7bf666 Minor fixes. Only one person can have wind walker now. Tokens spawn at 30s rather than 15
#story[1293]
2017-09-27 12:06:16 +13:00
William Muir 85ca91db96 Minor fixes. Only one person can have wind walker now. Tokens spawn at 30s rather than 15
#story[1293]
2017-09-27 11:55:40 +13:00
William Muir 2eb7e603f1 Merge remote-tracking branch 'origin/develop' into 1293_PowerUps 2017-09-27 03:01:41 +13:00
William Muir 658a342118 Added the bad random to git sorree forgot to git add
#story[1293]
2017-09-27 03:01:23 +13:00
Calum c0bd498f1b Added debugs for missing xml 2017-09-27 02:50:36 +13:00
Calum Irwin ea52977aeb Merge branch '1293_PowerUps' into 'develop'
1293 power ups

# Changes 
* Token Objects added to represent pickups along with their corresponding mesh objects 
* Yachts now have a powerup attribute 
* Power up attribute is checked in Game State updates and updates are now done appropriately, dependant on this power up 
* RaceViewController now has a grid pane to show the different power up icons. These will display when a token is picked up and blink when they are about to dissapear
* Token generator created. This generates token locations randomly in the map in circles centred between the legs of the race 
* Added Yacht EventCodes for the different type of power / up / down status effect actions
 
# Refactor
* Moved Timer tasks such as spawn tokens and update wind into the GameState where they belong
* Moved the creation of some messages in odd places, server side, into the MessageFactory class 
 
# Testing 
* Manual test done
* Junits created for the random token generator

See merge request !77
2017-09-27 02:47:57 +13:00
William Muir 10fa51a105 Added some testing for the Random spawner
#story[1293]
2017-09-27 02:27:30 +13:00
Calum b18d9e8573 Added empty cucumber
#test
2017-09-27 02:04:58 +13:00
Calum e9881bb24a Added per map max player count. Handles case when map cannot fit players behind start mark. Added initial implementation for spacing out yachts.
#implement  #story[1275]
2017-09-27 02:04:24 +13:00
William Muir c54a1e141d Merge remote-tracking branch 'origin/develop' into 1293_PowerUps
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-27 01:49:18 +13:00
William Muir 6d51ea3574 Created a random place token generator.
Generates a location between any leg of the race in a random angle / distance of the radius of the centre point of the two gates to one of the gates

#story[1293]
2017-09-27 01:44:01 +13:00
Zhi You Tan d56468e4aa - Fixed snackbar not showing on race view
- Added close label on server creation dialog

#story[1273]
2017-09-27 00:38:42 +13:00
Calum ab5ad58237 Merge branch 'develop' into story1275_host_customization
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-26 23:37:59 +13:00
Calum df7264cc1f Added per map max player count. Handles case when map cannot fit players behind start mark. Added initial implementation for spacing out yachts.
#implement.
2017-09-26 23:34:19 +13:00
Zhi You Tan 5248921576 Multiple fixes:
- fix last button no longer default focused when loading keybinding dialog
- fix click and mouse exit still focused on button
- closing keybinding does not focus on chat anymore

#story[1273]
2017-09-26 22:52:58 +13:00
William Muir 330ccd272d The random token now has a 50% chance of causing your boat to have a speed penalty
#story[1293]
2017-09-26 21:13:35 +13:00
William Muir 4b7dfe38c4 Fixed Boats powering up and down correctly. Icons respond correctly
#story[1293]
2017-09-26 19:43:32 +13:00
William Muir ab07c7f298 Merge remote-tracking branch 'origin/develop' into 1293_PowerUps
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
2017-09-26 19:01:53 +13:00
William Muir b5076bc976 Fixed wind walker and bumper
Added a hack to bumper so that the collision distance is larger than the regular collision distance
Wind walker now makes you go at you max speed rather than VMG speed

#story[1293]
2017-09-26 18:57:15 +13:00
Michael Rausch 2dcdd1c248 Merge branch '1273_Changing_Cameras' into 'develop'
1273 changing cameras

# Multiple Camera Views

## 3 Camera Views Implemented
1. Normal 3D (Isometric)
2. Top Down Perspective
3. Chase Cam (Over the Shoulder)

- Isometric and Top Down Perspectives can Pan within limits.
- Chase Cam rotates the camera around the boat. (While still following it)
- All camera views can Zoom.

# Testing

No testing completed other than manually testing myself. A proper manual test should be completed by the merger.

See merge request !76
2017-09-26 18:36:59 +13:00
Alistair McIntyre 9b00f76907 removed print statements and added documentation.
tags : #story[1273]
2017-09-26 18:36:13 +13:00
Alistair McIntyre f11c457d28 removed print statements
tags : #story[1273]
2017-09-26 18:31:47 +13:00
Michael Rausch 9e4fa30787 Merge branch 'issue68_boat_not_turning_on_starbord_vmg' into 'develop'
the boat can now turn after snapping to starboard vmg #story[1273]



See merge request !75
2017-09-26 18:11:24 +13:00
Alistair McIntyre 99ce4fa11d Merged develop in and reimplemented keybindings for moving the camera.
tags : #story[1273]
2017-09-26 18:01:39 +13:00
Alistair McIntyre 69d1fa9488 Merge branch 'develop' into 1273_Changing_Cameras 2017-09-26 17:36:40 +13:00
Alistair McIntyre 66e6a8a2a4 - Small Changes
tags : #story[1273]
2017-09-26 17:35:36 +13:00
Kusal Ekanayake 671efcaf08 Adding spinners to the loading screen. 2017-09-26 17:25:57 +13:00
William Muir 8ba44d7476 Minor commit for testing
#story[1293]
2017-09-26 17:19:45 +13:00
Peter Galloway dd43097677 the boat can now turn after snapping to starboard vmg #story[1273] 2017-09-26 17:07:55 +13:00
Kusal Ekanayake 98abe64f00 Added splash and loading screen 2017-09-26 17:07:02 +13:00
Alistair McIntyre 1a53579317 - Isometric Camera
- Zoom Bounds added.
 - Camera Panning Bounds added. Need to be tested with extra maps.

tags : #story[1273]
2017-09-26 16:57:07 +13:00
Haoming Yin 132a729758 Temporarily added more options in key binding dialog. But still need to implement binding after camera story has been merged into develop
tags: #story[1273]
2017-09-26 15:49:16 +13:00
Peter Galloway 870d7a6e82 added a check in the start logic to make sure a boat can't cross the start line prematurely (they will have to go back and go through it again) #fix #refactor 2017-09-26 15:25:24 +13:00
alistairjmcintyre 2e7487fdfc - Chase Camera:
- Has panning bounds, zoom bounds, and general tidy up.
 - Now correctly observes the boat object rather than getting information from both the boat object AND the client yacht model.

Top Down Camera:
 - Can only pan within certain bounds now, and will continue to follow the boat regardless.
 - Can only zoom within certain bounds now.

Isometric Camera:
 - Nothing changed.

#tags [1273]
2017-09-26 15:06:21 +13:00
Haoming Yin 1bd4db73cd Issue 66: client side error pop ups use default javaFx style
- created JFeonix style pop up to replace the default one

tags: #story[1273]
2017-09-26 14:44:37 +13:00
William Muir 7a4cdbe0c9 Fixed WindWalker
#story[1293]
2017-09-26 11:12:29 +13:00
Calum 735699dc85 Changed default number of legs. 2017-09-26 01:53:33 +13:00
Calum 81e791bd1a Fixed issues with showing race view controller. Added missing icons. Added a smooth version of land.
#fix
2017-09-26 01:49:43 +13:00
Calum 06e5f4ae00 Merge remote-tracking branch 'origin/1273_Skybox' into 1273_Skybox 2017-09-26 01:35:07 +13:00
Calum cd2b4cb93c fixed anchor pane issues 2017-09-26 01:34:59 +13:00
Calum dde1c82dbb merge with host customization and dev. 2017-09-26 01:21:55 +13:00
Calum d9c832168b Merge branch 'story1275_host_customization' into 1273_Skybox
# Conflicts:
#	src/main/java/seng302/visualiser/GameView3D.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
#	src/main/java/seng302/visualiser/controllers/ViewManager.java
#	src/main/resources/views/LobbyView.fxml
#	src/main/resources/views/RaceView.fxml
2017-09-26 01:19:47 +13:00
Michael Rausch 9cfb3b9e5d Added functionality to automatically select a server
- Added functionality on the DiscoveryServer to return a random server to the player
- Added elements to the UI to support auto-selecting a server
- Added client side code to request a random server

Tags: #story[1281]
2017-09-26 01:14:02 +13:00
Calum e990c68d40 #document 2017-09-26 01:12:43 +13:00
Calum 83871a0336 Fixed boat trials rendering abnormally
#issue[65] #fix
2017-09-26 01:07:02 +13:00
Calum 6cba024d64 Made changes to the shape of boat trials. 2017-09-26 00:59:04 +13:00
Calum 51747e2d13 Refactored the 2D and 3D game view class setups. Made scaling more logical.
#refactor #story[1275]
2017-09-26 00:55:28 +13:00
Calum b3981b19e0 Merge branch 'develop' into story1275_host_customization
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
#	src/test/java/seng302/models/YachtTest.java
2017-09-25 23:25:33 +13:00
Calum 3d7a64068f Fixed scaling issues.
#fix
2017-09-25 23:19:03 +13:00
Calum c12f7408ad experimenting with map scaling
#implement
2017-09-25 22:57:45 +13:00
Calum 7027de80c4 Fixed issues with correct protocol implementation.
#issue[64] #fix #testmanual
2017-09-25 22:09:26 +13:00
Calum 35b50d1436 Refactored 2d game view distance calculations to better size and sale the map,
#implement #refactor #story[1275]
2017-09-25 21:06:18 +13:00
Michael Rausch d0844e861d Changed water color to blue 2017-09-25 19:25:03 +13:00
Michael Rausch 5d32d76d9d Added local skybox texture, and changed water color
- Changed water color to a more sea-like blue
- Added an extra sun
- Moved skybox texture to the resources folder
2017-09-25 19:19:46 +13:00
Michael Rausch 9ca39d1a7c Merge branch '1273_Changing_Cameras' into 1273_Skybox 2017-09-25 18:40:15 +13:00
Michael Rausch 30dad8509e Added skybox & cleaned up server list UI
- Moved direct connect fields to a dialog as there was not enough room
- Moved room code to its own label
- Added a skybox texture to the game view
- Added the land mesh to the game view
2017-09-25 18:25:24 +13:00
William Muir 0211f2df38 Merged dev back on
#story[1293]
2017-09-25 17:38:09 +13:00
William Muir dba5a5680f Merge remote-tracking branch 'origin/develop' into 1293_PowerUps
# Conflicts:
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/resources/views/RaceView.fxml
2017-09-25 17:20:25 +13:00
William Muir 5c50e77efa Merge branch 'Story1278_keybindings' into 'develop'
Story1278 keybindings

# Changes

- Added customised action key binding for all boat actions
- Users can select the turning mode (continuously turning mode, and the default turning mode)
- Refined UI design for key bindings
- Added animation which mouse enters/exits the setting button, and also prompt text to guide users to press key
- Popup window to inform users if their key assignment is successful or not

# Testing

## Manual testing log as below (all passed according to the assigner)
- if a player toggles the turning mode toggle button, the labels for upwind and downwind will be changed correspondingly
- if a player clicks the "reset" button, all the key binding settings will be reset to default
- if a player assigns a key which is already is in use, a popup box will inform the assignment is failed, and action will be changed back
- if a player assigns a key successfully, a popup will inform the success, and the new key binding will appear on the button
- A player can change their key bindings before the race starts and during the race


See merge request !73
2017-09-25 17:11:12 +13:00
Zhi You Tan 0e93be7b36 Fixed send button traversable issue.
- removed sensitive characters.

#story[1278] #pair[hyi25, zyt10]
2017-09-25 17:06:36 +13:00
Zhi You Tan 191b818e38 Added close button for keyBindingDialog.
- fixed that you cannot bind the key you are using.

#story[1278] #pair[hyi25, zyt10]
2017-09-25 16:31:28 +13:00
Alistair McIntyre 00b09997b0 - Added Camera panning to chase camera mode.
tags : #story[1273]
2017-09-25 16:28:37 +13:00
Alistair McIntyre d250c635d8 - Adjusted server tick rate to test smoothing
tags : #story[1273]
2017-09-25 15:32:13 +13:00
Alistair McIntyre 376c4d25a8 Merge branch 'develop' into 1273_Changing_Cameras
# Conflicts:
#	src/main/java/seng302/model/ClientYacht.java
2017-09-25 14:40:19 +13:00
Alistair McIntyre e66abb4340 - An attempt at chase cam smoothing. needs work.
tags : #story[1273]
2017-09-25 14:39:09 +13:00
Alistair McIntyre a19e191684 - Chase cam kind of working, need to find a fix for the abrupt direction change.
tags : #story[1273]
2017-09-25 14:03:47 +13:00
William Muir 29b97a194d Merged dev back on
#story[1293]
2017-09-25 11:26:44 +13:00
William Muir 8a0ad8d6a9 Merge remote-tracking branch 'origin/develop' into 1293_PowerUps
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/model/ServerYacht.java
2017-09-25 11:19:10 +13:00
Haoming Yin 19db6668da Fixed merge conflicts in server yacht
tags #story[1278]
2017-09-25 11:09:14 +13:00
Haoming Yin 44275aec04 Merge branch 'develop' into Story1278_keybindings
# Conflicts:
#	src/main/java/seng302/model/ServerYacht.java
2017-09-25 11:06:28 +13:00
Michael Rausch ca320f7fb8 Merge remote-tracking branch 'origin/1273_Changing_Cameras' into 1281_Server_Discovery_Internet 2017-09-25 00:18:35 +13:00
Zhi You Tan 64245833cd Merge branch 'Story1274_custom_boat_stats' into 'develop'
Story1274 custom boat stats

# Changes
- Added 3 areas for game play variation with the boats (stats)
    - Max speed
    - Handling
    - Acceleration
- Added stat bars on the customize boat screen so players can see the stats

# Testing
- Manual testing
- No JUnits made since no real processing methods were added


See merge request !72
2017-09-24 18:53:15 +13:00
Kusal Ekanayake aa0149b9a7 Merge branch 'develop' into Story1274_custom_boat_stats 2017-09-24 18:08:54 +13:00
Kusal Ekanayake f6b41f0513 Fixes Issue #59 2017-09-24 18:06:26 +13:00
Calum 9b00ba654a Added a race importer. Added imported races to visualizer. Made it so that the host sets the race. Refactored server to no longer be dependant on a specific race. Tested functionality of map manually. Some bugs found and listed below.
#implement #testmanual #story[1275]

Known bugs:
 * Can't move
 * Map is off center in lobby view.
 * 3D Map is off center
2017-09-23 22:45:53 +12:00
Haoming Yin 8dfdb228e9 Fixed a bug that game client tries to send turning mode packet when there is no socketThread
- add a if statement to check if socketThread is initialized before sending packet.

#story[1278]
2017-09-23 21:04:57 +12:00
Haoming Yin 1042817e4e Snackbar's color can be changed according to the message type
- load a separate css file to change the color
- if the assignment failed, the snackbar prompt will be red, otherwise theme color

#story[1278]
2017-09-23 20:59:29 +12:00
Haoming Yin 066557584f Implemented turning mode toggle
- when the mode is toggled, a boat action package will be sent to notify server
to change the boat's turning mode
- turning mode toggle is now fully functional

#story[1245]
2017-09-23 19:37:13 +12:00
Haoming Yin 4011295b8b Slightly optimised code style and add more functionality
- optimised UI
- check conflicts when change key bind if the key has already been in use
- abstract keybind as a separate singleton class so all class can access it
- [WIP] turning mode is need to be finished

#story[1245]
2017-09-23 17:52:48 +12:00
William Muir 78259f8e33 Boat now changes color when it is bumped for a time
ClientYacht: Added ColorChangeListener from GameView3D to re paint the boat when the color attribute is changed

#story[1293]
2017-09-23 16:54:03 +12:00
William Muir c47e5b1450 Boat now changes color when it is bumped for a time
ClientYacht: Added ColorChangeListener from GameView3D to re paint the boat when the color attribute is changed

#story[1293]
2017-09-23 16:31:18 +12:00
Kusal Ekanayake 0a885dd8fd Clean up changes
#story[1274]
2017-09-23 15:37:16 +12:00
Kusal Ekanayake e9b50038a9 Balance changes. Always wanted to use that as a commit message.
#story[1274]
2017-09-23 15:09:22 +12:00
Kusal Ekanayake 364264377a Fixed merge errors and reimplemented handling multiplier
#story[1274]
2017-09-23 15:04:11 +12:00
Kusal Ekanayake 9112183ac3 Merge branch 'develop' into Story1274_custom_boat_stats
# Conflicts:
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/controllers/dialogs/BoatCustomizeController.java
2017-09-23 14:48:50 +12:00
William Muir 8c7f9a878d Created Boat bumper logic. Refactored logic for powering up / dpwn
YachtEventType: Added some new events, a generic power down event and a bumper_crash event for an affected boat
GameState: Implemented boat bumper logic
MessageFactory: Made new messages for powerdown and status effect
ClientYacht: Had to create another powerDown functional interface to inform the race view controller when to turn off the icon
RaceViewController/GameClient: Now waits for a message about powering down before turning off rather than waiting time client side

#story[1293]
2017-09-23 13:23:16 +12:00
William Muir ecb3d4ecbf Removed sendServerMessage to be replaced with notifyMessageListeners. Minor structure move arounds
#story[1293]
2017-09-23 11:49:56 +12:00
William Muir e61b6d50a1 Small refactor. Fixed tokens to spawn on the minute mark.
Moved updates of wind and token timers into gamestate from mainserver thread. Now triggered upon GameState change to start

#story[1293]
2017-09-23 11:33:01 +12:00
Zhi You Tan 957821f1f2 [WIP] Created a snackbar for notification. Currently used for keybinding success/fail. Need to show red if fails.
#story[1278]
2017-09-23 01:39:26 +12:00
William Muir 061e49bab9 Implemented wind walker algorithm. Refactored some GameState updating logic to allow for better token logic integration
GameState: Moved all token logic into its own function startPoint so that it is dsijoint from other updating logic
GameState: Implemented wind walker algorithm.
GameState: Changed Generic 'speedMultiplier' to 'serverSpeedMultiplier' to make it obviously disjoint from a boats speed multiplier
MessageFactory: Moved some found message creation (Chatter Message)  server side into MessageFactory that wasnt already there
ServerYacht: Added a speed multiplier and a handling multiplier to the serveryacht class that is set and reset upon powerup / down

#story[1293]
2017-09-22 23:44:03 +12:00
Zhi You Tan 094eb4c1cf Merge remote-tracking branch 'origin/develop' into Story1278_keybindings
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-09-22 21:22:06 +12:00
William Muir a3c555d5fe Merge remote-tracking branch 'origin/develop' into 1293_PowerUps
# Conflicts:
#	src/main/java/seng302/model/ClientYacht.java
2017-09-22 21:18:58 +12:00
William Muir 607acff7c6 Merge branch 'Custom_boat_selection' into 'develop'
Custom boat selection

# Story 1274 Feature Custom Boat Selection
## Changes
- 2 new boats!
- A boat selector in the customisation for boat dialog
- The potential for everyone to use whatever boats and for everyone to see

## Testing
- Basic JUnit tests added to test basic util methods added to the BoatMeshType class
- Manual testing completed

See merge request !71
2017-09-22 21:14:12 +12:00
William Muir 22fdf1e4ac Changed the boatType attribute from all around the place from String lit to enum
#story[1274]
2017-09-22 21:00:28 +12:00
William Muir da8c91f5c1 Review fixes for merge request.
PlayerCell now takes in the yacht for construction rather than taking in a whole lot of values extracted from the yacht

Reduced boiler plate in BoatCustomizeController

#story[1274]
2017-09-22 20:44:06 +12:00
Kusal Ekanayake 52dc7a956d Turned handling into a multiplier.
#story[1274]
2017-09-22 17:42:32 +12:00
Kusal Ekanayake 9f64b2380d Implemented acceleration and full loading bars.
#story[1274]
2017-09-22 17:28:42 +12:00
Kusal Ekanayake b05580f018 Worked on making a visual component to the stats. Need to implement acceleration.
#story[1274]
2017-09-22 16:45:10 +12:00
Haoming Yin c20c6fb264 [WIP] Added new toggle for steer turning mode and submit and reset button (restore the setting to default)
- minor fix of UI elements to make the font smaller
- centred labels and buttons

tags: #story[1246]
2017-09-22 13:53:36 +12:00
Kusal Ekanayake faeece27ff Started working on individual boat stats. Already modified turning rate. Need to add a vsual component.
#story[1274]
2017-09-22 13:52:35 +12:00
Michael Rausch 5e3ae40d03 Made discovery more reliable & added docs/tests
- Added unit tests
- Added documentation for discovery classes
- Improved error handling

Tags: #story[1281]
2017-09-22 00:01:13 +12:00
Michael Rausch 95ad7a4840 Finished implementing room codes.
- Fixed bug where room code wasn't parsed correctly
- Added room code selection to server list screen.
- Added room code to hosts lobby.
- Implemented communication protocols on the game client.

Tags: #story[1281]
2017-09-21 22:48:33 +12:00
Calum 6ca75b2cac Changed default race 2017-09-21 15:08:39 +12:00
Alistair McIntyre 6ff309a40c - Perspective Camera Works
- Top Down Camera Works
- Started on chase cam but the math is a bit tricky.

tags : #story[1273]
2017-09-21 14:40:35 +12:00
Calum 40a7f9bc5b New server creation view created. Added templates for custom races. Updated xml generator to remove all hard coded values. Updated XMLParser to parse custom race files. No unit tests exists currently.
#implement #story[1275]
2017-09-21 12:59:37 +12:00
Kusal Ekanayake c4a6113f6c Minor bug fixes (like all enemy sails being toggled in when they should be out)
#story[1274]
2017-09-20 21:07:49 +12:00
Kusal Ekanayake 307e79ecfc Completed working boat selection screen.
When a user selects a different boat, it is sent to all other clients and updates accordingly. Boats are all shown with their correct models in game.

#story[1274]
2017-09-20 20:46:23 +12:00
Michael Rausch e17e9749d8 Implemented server to manage a list of available servers on the internet.
- Implemented a server manager that keeps track of servers & room codes, and removes old servers
- Implemented queries to find a server with a specific room code
- Implemented protocol to register servers

#story[1281]
2017-09-20 20:26:14 +12:00
Kusal Ekanayake 7d8a6afa5f Merge branch 'new_meshes' into Custom_boat_selection 2017-09-20 19:40:43 +12:00
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
William Muir 3be8cd264d Adding icon files to git
#story[1245]
2017-09-20 17:52:02 +12:00
Peter Galloway 7197bc2bee created meshes for pirate ship #story[1274] 2017-09-20 17:33:32 +12:00
William Muir 66d9a06f9e Adding icon files to git
#story[1245]
2017-09-20 17:19:14 +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
Kusal Ekanayake 410d765745 Started working on the boat selection screen.
Customised the boat customisation UI to contain it. Need to have another boat to test whether we can switch boats and also if the messages get sent correctly.

#story[1274]
2017-09-20 16:36:27 +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
William Muir 034e4c252a Icons now display, blink when they are about to run off, and turn off after their time out
TokenType Enum now also has a timeout construction field
#story[1245]
2017-09-20 13:09:09 +12:00
William Muir 6cde016401 Fixed random token assigning and realisation
Token class now has two functions: assignRandomType and realiseRandomType
The former can be used to assign any random type to the token including the random type
The latter can be used to assign a concrete random type to the token (not the random type)

#story[1245]
2017-09-20 12:02:12 +12:00
Zhi You Tan d4d7ddf8e2 Keybinding now works in the actual race. A map of keybind is shared between GameClient and KeyBindDialogController.
#story[1278]
2017-09-20 11:02:13 +12:00
Zhi You Tan a1933c2869 - Created keybindingcontroller which can detect keypress and can save the keybind throughout the app
- Changed keybindingglyph to keyboard icon
- Fix button hover CSS to change text fill

#story[1278]
2017-09-20 02:42:02 +12:00
Zhi You Tan 8084a61333 Fix keybinding dialog creating on top of another keybinding dialog.
#story[1278]
2017-09-19 23:41:06 +12:00
William Muir 52d3cea592 Added preliminary icons for pickups
RaceView now has a grid pane to contain some icons to display power ups. These are just preliminary

ClientYacht now has a power up field that is set from recieveing messages in the Game Client, as well as observed by the RaceViewController to display the relevant icon when the powerup field is changed

#story[1245]
2017-09-19 23:04:17 +12:00
Zhi You Tan 03f5f91043 Done some CSS on keybindingdialog
#story[1278]
2017-09-19 19:49:36 +12:00
Peter Galloway 9ed52a1225 created catamaran mesh #story[1274] 2017-09-19 19:43:03 +12:00
Zhi You Tan 027324cc4f - Fixed JFXDialog initialised in lobby, raceview, serverlist controller by default. Now, dialog only appears when called.
- [WIP] Created keybindingdialog fxml. CSS not yet done.
 - Removed top most anchorpane on raceview so parent is stackpane like other views.

#story[1278]
2017-09-19 18:40:01 +12:00
William Muir 78596ea111 Initial commit for Power Up story
Made new preliminary models for each power up. Currently just different colour balls
Added new YachtEventTypes in the enum for each pick up to be sent out to clients
Tokens now not only randomise location but also randomise which type of token will be sent out
Added new methods to the MessageFactory class - Make collision and Make pickup Message
Game Client now checks what type of Yacht Event code has come in to respond appropriately rather than just generic collision / token.. although this has not been implemented yet
Game View loads appropriate token models depending on what is in XML

#story[1245]
2017-09-19 17:47:05 +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
274 changed files with 15926 additions and 4142 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
+59
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,50 @@
<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>
<dependency>
<groupId>org.fxyz3d</groupId>
<artifactId>fxyz3d</artifactId>
<version>0.1.1</version>
</dependency>
</dependencies>
<build>
@@ -152,4 +197,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>
+33 -25
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,11 +10,13 @@ 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.discoveryServer.DiscoveryServer;
import seng302.visualiser.controllers.ViewManager;
public class App extends Application {
private static Logger logger = LoggerFactory.getLogger(App.class);
private static boolean isRunningAsCache = false;
public static void parseArgs(String[] args) throws ParseException {
Options options = new Options();
@@ -29,9 +27,21 @@ public class App extends Application {
.getLogger(Logger.ROOT_LOGGER_NAME);
options.addOption("debugLevel", true, "Set the application debug level");
options.addOption("runAsDiscoveryServer", false, "Run as a discovery server");
options.addOption("discoveryDevMode", false, "Use a local discovery server");
cmd = parser.parse(options, args);
if (cmd.hasOption("runAsDiscoveryServer")){
isRunningAsCache = true;
rootLogger.setLevel(Level.ALL);
return;
}
if (cmd.hasOption("discoveryDevMode")) {
DiscoveryServer.DISCOVERY_SERVER = "localhost";
}
if (cmd.hasOption("debugLevel")) {
switch (cmd.getOptionValue("debugLevel")) {
@@ -67,36 +77,34 @@ 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().initialiseSplashScreen(primaryStage);
}
public static void main(String[] args) {
private static void runDiscoveryServer() throws Exception {
while (true){
try {
new DiscoveryServer();
}
catch (Exception ignored){
;
}
}
}
public static void main(String[] args) throws Exception {
try {
parseArgs(args);
} catch (ParseException e) {
logger.error("Could not parse command line arguments");
}
if (!isRunningAsCache){
launch(args);
}
else{
runDiscoveryServer();
}
}
}
@@ -0,0 +1,170 @@
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.discoveryServer.util.ServerTable;
import seng302.visualiser.ServerListener;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Timer;
public class DiscoveryServer {
public static final String ANSI_GREEN = "\u001B[32m";
public static final String ANSI_YELLOW = "\u001B[33m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_RESET = "\u001B[0m";
private static final int MAX_SERVER_TRIES = 10;
public static String DISCOVERY_SERVER = "party.sydney.srv.michaelrausch.nz";
private ServerTable serverTable;
public static final Integer PORT_NUMBER = 9969;
private ServerSocket serverSocket;
private final Logger logger = LoggerFactory.getLogger(DiscoveryServer.class);
private void displayHeader(){
String selectedColor = Arrays.asList(ANSI_BLUE, ANSI_GREEN, ANSI_YELLOW).get(new Random().nextInt(2));
System.out.println(selectedColor);
System.out.println(" .ccccc. \n" +
" .cc;'coooxkl;. \n" +
" .:c:::c:,,,,,;c;;,.'. \n" +
" .clc,',:,..:xxocc;'..c; \n" +
" .c:,';:ox:..:c,,,,,,...cd, \n" +
" .c:'.,oxxxxl::l:.,loll;..;ol. \n" +
" ;Oc..:xxxxxxxxx:.,llll,....oc \n" +
" .,;,',:loxxxxxxxxx:.,llll;.,,.'ld, \n" +
" .lo;..:xxxxxxxxxxxx:.'cllc,.:l:'cO; \n" +
" .:;...'cxxxxxxxxxxxxoc;,::,..cdl;;l' \n" +
" .cl;':,'';oxxxxxxdxxxxxx:....,cooc,cO; \n" +
" .,,,::;,lxoc:,,:lxxxxxxxxxxxo:,,;lxxl;'oNc \n" +
" .cdxo;':lxxxxxxc'';cccccoxxxxxxxxxxxxo,.;lc. " + ANSI_YELLOW + "Party-Parrots-At-Sea Discovery Server v0.1 " + selectedColor +"\n" +
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
"Ol;......................................;l' ");
System.out.println(ANSI_RESET);
}
public DiscoveryServer() throws Exception {
displayHeader();
serverTable = new ServerTable();
try{
serverSocket = new ServerSocket(PORT_NUMBER);
}
catch(java.net.BindException e){
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
System.exit(1);
return;
}
logger.info("Started successfully - Now accepting connections");
try{
while (true){
Socket clientSocket = serverSocket.accept();
parseRequest(clientSocket);
clientSocket.close();
}
}
catch (Exception e){
close();
}
}
private void parseRequest(Socket clientSocket) throws Exception {
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
if (clientSocket.isConnected() && !clientSocket.isClosed()){
PacketType parsePacketResult = parser.parse();
switch (parsePacketResult){
case SERVER_REGISTRATION:
ServerListing listing = parser.getServerListing();
if (!serverTable.getAllServers().contains(listing)){
listing.setRoomCode(serverTable.getNextRoomCode().toString());
}
serverTable.addServer(listing);
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
break;
case ROOM_CODE_REQUEST:
String desiredRoomCode = parser.getRoomCode();
ServerListing serverListing;
if (desiredRoomCode.equals("0000")){
serverListing = getRandomFreeServer();
}
else {
serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
}
Message response;
if (serverListing != null){
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
}
else{
response = ServerRegistrationMessage.getEmptyRegistration();
}
clientSocket.getOutputStream().write(response.getBuffer());
break;
}
}
}
public ServerListing getRandomFreeServer() {
ServerListing serverToJoin;
List<ServerListing> servers = serverTable.getAllServers();
if (servers.size() <= 0){
return null;
}
if (servers.size() == 1){
return servers.get(0);
}
serverToJoin = servers.get(new Random().nextInt(servers.size()));
int tries = 0;
while (serverToJoin != null && serverToJoin.isMaxPlayersReached() && tries < MAX_SERVER_TRIES){
serverToJoin = servers.get(new Random().nextInt(servers.size()));
tries++;
}
if (serverToJoin != null && serverToJoin.isMaxPlayersReached()){
return null;
}
return serverToJoin;
}
public void close(){
try {
serverSocket.close();
} catch (IOException ignored) {
;
}
}
}
@@ -0,0 +1,195 @@
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import seng302.visualiser.controllers.ViewManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
public class DiscoveryServerClient {
private final Integer UPDATE_INTERVAL_MS = 5000;
private static String roomCode = null;
private Timer serverListingUpdateTimer;
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
private String ip = "";
private Boolean isInInvalidState = false;
public DiscoveryServerClient() {
try {
ip = getInetIpAddr();
} catch (Exception e) {
failError();
}
}
public String getInetIp(){
return ip;
}
private void failError() {
isInInvalidState = true;
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
}
public boolean didFail(){
return isInInvalidState;
}
/**
* Register the server with the discovery server
* @param serverListing The listing to register
*/
public void register(ServerListing serverListing){
if (isInInvalidState) return;
if (serverListingUpdateTimer != null){
serverListingUpdateTimer.cancel();
serverListingUpdateTimer = null;
}
serverListingUpdateTimer = new Timer();
serverListingUpdateTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
sendRegistrationUpdate(serverListing);
} catch (Exception e) {
logger.debug("Could not update server listing");
}
}
}, 0, UPDATE_INTERVAL_MS);
}
/**
* Stop updating the server registration updates
*/
public void unregister(){
if (serverListingUpdateTimer != null)
serverListingUpdateTimer.cancel();
}
/**
* Gets the connection information for a server given a room code
*
* @param roomCode The room code to search for
* @return The ServerListing, or null if there was an error
* @throws Exception .
*/
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message request = new RoomCodeRequest(roomCode); //roomCode);
socket.getOutputStream().write(request.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.SERVER_REGISTRATION){
logger.debug("Wrong packet received in response to a room code request");
return null;
}
socket.close();
return parser.getServerListing();
}
public ServerListing getRandomServer() throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message request = new RoomCodeRequest("0000");
socket.getOutputStream().write(request.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.SERVER_REGISTRATION){
logger.error("Incorrect packet type received");
return null;
}
socket.close();
ServerListing serverListing = parser.getServerListing();
if (serverListing == null || serverListing.equals(ServerRegistrationMessage.getEmptyRegistration())){
return null;
}
return serverListing;
}
/**
* Sends a registration update to the discovery server.
*
* @param serverListing The server listing to send
* @throws Exception IF there was an error sending the update
*/
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message req = new ServerRegistrationMessage(serverListing);
socket.getOutputStream().write(req.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.ROOM_CODE_REQUEST){
socket.close();
return;
}
String roomCode = parser.getRoomCode();
if (roomCode.length() != 0){
DiscoveryServerClient.roomCode = roomCode;
}
socket.close();
}
/**
* @return The last room code received by the client
*/
public static String getRoomCode(){
return roomCode;
}
public static String getInetIpAddr() throws Exception {
URL myIp = new URL("http://checkip.amazonaws.com");
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(
myIp.openStream()));
String ip = in.readLine();
return ip;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@@ -0,0 +1,50 @@
package seng302.discoveryServer.util;
import java.io.InputStream;
public class ReadableByteInputStream {
private InputStream is;
public ReadableByteInputStream(InputStream is){
this.is = is;
}
/**
* Get n bytes from the input stream
* @param n number of bytes
* @return the bytes read
* @throws Exception .
*/
public byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
/**
* Skip n bytes
* @param n number of bytes to skip
* @throws Exception
*/
public void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
/**
* Read the next byte from the stream
* @return The byte that was read
* @throws Exception .
*/
public int readByte() throws Exception {
int currentByte = is.read();
if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
}
@@ -0,0 +1,117 @@
package seng302.discoveryServer.util;
public class ServerListing {
public final static int SERVER_TTL_DEFAULT = 10;
private String serverName = "";
private String mapName = "";
private String address = "";
private int portNumber = 0;
private int capacity = 0;
private int players = 0;
private String roomCode = "";
private int ttl = SERVER_TTL_DEFAULT;
public ServerListing(String serverName, String mapName, String address, int portNumber, int capacity){
this.serverName = serverName;
this.mapName = mapName;
this.address = address;
this.portNumber = portNumber;
this.capacity = capacity;
}
public ServerListing setNumberOfPlayers(int players){
this.players = players;
return this;
}
public ServerListing setRoomCode(String roomCode){
this.roomCode = roomCode;
return this;
}
public void refreshTtl(){
ttl = SERVER_TTL_DEFAULT;
}
public void decrementTtl(){
ttl--;
}
public boolean hasTtlExpired(){
return ttl < 0;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!ServerListing.class.isAssignableFrom(obj.getClass())) {
return false;
}
final ServerListing other = (ServerListing) obj;
if (this.getPortNumber() != other.getPortNumber()){
return false;
}
if (!this.getMapName().equals(other.getMapName())){
return false;
}
if (!this.getServerName().equals(other.getServerName())){
return false;
}
if (this.getCapacity() != other.getCapacity()){
return false;
}
if (!this.getAddress().equals(other.getAddress())){
return false;
}
return true;
}
@Override
public int hashCode() {
return this.getServerName().hashCode() +
this.getAddress().hashCode() + this.getMapName().hashCode();
}
public String getRoomCode() {
return roomCode;
}
public int getPortNumber() {
return portNumber;
}
public String getMapName() {
return mapName;
}
public String getServerName() {
return serverName;
}
public int getCapacity() {
return capacity;
}
public String getAddress() {
return address;
}
public void setTtl(Integer ttl){
this.ttl = ttl;
}
public boolean isMaxPlayersReached() {
return players >= capacity;
}
}
@@ -0,0 +1,109 @@
package seng302.discoveryServer.util;
import seng302.gameServer.messages.Message;
import seng302.model.stream.packets.PacketType;
import java.io.InputStream;
import java.util.Arrays;
public class ServerRepoStreamParser {
private ReadableByteInputStream inputStream;
private String roomCode;
private String mapName;
private ServerListing serverListing;
public ServerRepoStreamParser(InputStream is){
inputStream = new ReadableByteInputStream(is);
}
public PacketType parse() throws Exception {
int sync1 = inputStream.readByte();
int sync2 = inputStream.readByte();
PacketType packetType = null;
if (sync1 == 0x47 && sync2 == 0x83) {
int type = inputStream.readByte();
inputStream.skipBytes(10);
long payloadLength = Message.bytesToLong(inputStream.getBytes(2));
byte[] payload = inputStream.getBytes((int) payloadLength);
inputStream.skipBytes(4);
packetType = PacketType.assignPacketType(type, payload);
switch (packetType) {
case ROOM_CODE_REQUEST:
roomCode = parseRoomCodeRequest(payload);
break;
case LOBBY_REQUEST:
mapName = parseLobbyRequest(payload);
case SERVER_REGISTRATION:
serverListing = parseServerRegistration(payload);
break;
}
}
return packetType;
}
private String parseLobbyRequest(byte[] payload) {
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,4));
return new String(Arrays.copyOfRange(payload, 4, 4+mapNameLength));
}
private String parseRoomCodeRequest(byte[] payload) {
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,6));
return new String(Arrays.copyOfRange(payload, 6, 6+roomCodeLength));
}
public static ServerListing parseServerRegistration(byte[] payload) {
int nameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0, 6));
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 6, 12));
int addressLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 12, 18));
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 18, 24));
int portNumber = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 24, 28));
int players = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 28, 32));
int capacity = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 32, 36));
int currentPos = 36;
int nextPos = currentPos + nameLength;
String serverName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + mapNameLength;
String mapName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + addressLength;
String address = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + roomCodeLength;
String roomCode = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
ServerListing serverListing = new ServerListing(serverName, mapName, address, portNumber, capacity);
serverListing.setNumberOfPlayers(players);
serverListing.setRoomCode(roomCode);
return serverListing;
}
public String getRoomCode() {
return roomCode;
}
public String getMapName() {
return mapName;
}
public ServerListing getServerListing() {
return serverListing;
}
}
@@ -0,0 +1,97 @@
package seng302.discoveryServer.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class ServerTable {
private List<ServerListing> servers;
private int lastRoomCode = 4020;
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
public ServerTable(){
servers = new ArrayList<>();
new Timer().schedule(new TimerTask() {
@Override
public void run() {
updateServers();
}
}, 0, 1000);
}
/**
* Update the servers TTL values, and then remove expired servers
*/
private void updateServers() {
List<ServerListing> serversToRemove = new ArrayList<>();
for (ServerListing server : servers){
server.decrementTtl();
if (server.hasTtlExpired()){
logger.debug("Removed expired server - " + server.getServerName());
serversToRemove.add(server);
}
}
servers.removeAll(serversToRemove);
}
/**
* Add a server to the table
* @param server The server to add
*/
public void addServer(ServerListing server){
if (servers.contains(server)){
updateTtlForServer(server);
return;
}
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
servers.add(server);
}
/**
* Update the TTL for a given server to the default TTL value
* @param server The server to update
*/
private void updateTtlForServer(ServerListing server) {
for (ServerListing serverListing : servers){
if (server.equals(serverListing)){
serverListing.refreshTtl();
}
}
}
/**
* @return All the servers in the table
*/
public List<ServerListing> getAllServers(){
return Collections.unmodifiableList(servers);
}
/**
* Get a server from the table given its room code
* @param roomCode The room code to search for
* @return The ServerListing of the found server, or null
* the server wasn't found
*/
public ServerListing getServerByRoomCode(String roomCode){
for (ServerListing serverListing : servers){
if (serverListing.getRoomCode().equals(roomCode)){
return serverListing;
}
}
return null;
}
/**
* @return The next available room code
*/
public Integer getNextRoomCode(){
lastRoomCode += 1;
return lastRoomCode;
}
}
+468 -109
View File
@@ -1,26 +1,27 @@
package seng302.gameServer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
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.ChatterMessage;
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;
@@ -29,8 +30,12 @@ import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
import seng302.utilities.RandomSpawn;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* A Static class to hold information about the current state of the game (model)
@@ -41,102 +46,105 @@ 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);
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;
//Scheduling constants
static final int WARNING_TIME = 10 * -1000;
static final int PREPATORY_TIME = 5 * -1000;
private static final int TIME_TILL_START = 10 * 1000;
//Wind Constants
private static final int MAX_WIND_SPEED = 12000;
private static final int MIN_WIND_SPEED = 8000;
//Rounding Constants
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
//Collision constants
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;
//Powerup Constants
public static final Double VELOCITY_BOOST_MULTIPLIER = 2d;
public static final Integer HANDLING_BOOST_MULTIPLIER = 2;
private static final Double BAD_RANDOM_SPEED_PENALTY = 0.3;
public static final Long BUMPER_DISABLE_TIME = 5_000L;
private static final Long TOKEN_SPAWN_TIME = 30_000L;
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static Double serverSpeedMultiplier;
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;
private static Map<Integer, ServerYacht> yachts;
private static Boolean isRaceStarted;
private static GameStages currentStage;
private static MarkOrder markOrder;
private static long startTime;
private static Set<Mark> marks;
private static List<Limit> courseLimit;
private static Set<Mark> marks = new HashSet<>();
private static List<Limit> courseLimit = new ArrayList<>();
private static Integer maxPlayers = 8;
private static List<NewMessageListener> markListeners;
private static List<Token> tokensInPlay;
private static RandomSpawn randomSpawn;
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.
*/
private static boolean tokensEnabled = false;
public GameState(String hostIpAddress) {
public GameState() {
windDirection = 180d;
windSpeed = 10000d;
this.hostIpAddress = hostIpAddress;
yachts = new HashMap<>();
tokensInPlay = new ArrayList<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
playerHasLeftFlag = false;
serverSpeedMultiplier = 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<>();
resetStartTime();
//setCourseLimit("/server_config/race.xml");
new Thread(this, "GameState").start(); //Run the auto updates on the game state
marks = new MarkOrder().getAllMarks();
setCourseLimit("/server_config/race.xml");
}
private void setCourseLimit(String url) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
} catch (Exception e) {
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
logger.trace("Failed to load course limit for boundary collision detection.", e);
public static void setRace(RaceXMLData raceXMLData) {
markOrder = new MarkOrder(raceXMLData);
for (CompoundMark compoundMark : raceXMLData.getCompoundMarks().values()){
marks.addAll(compoundMark.getMarks());
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks());
courseLimit = raceXMLData.getCourseLimit();
}
public static List<Player> getPlayers() {
return players;
}
public static List<Token> getTokensInPlay() {
return tokensInPlay;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
}
public static void addPlayer(Player player) {
players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
@@ -157,10 +165,6 @@ public class GameState implements Runnable {
yachts.remove(yachtId);
}
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
public static GameStages getCurrentStage() {
return currentStage;
}
@@ -178,7 +182,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() {
@@ -229,16 +233,77 @@ public class GameState implements Runnable {
} catch (InterruptedException e) {
System.out.println("[GameState] interrupted exception");
}
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
if (currentStage == GameStages.PRE_RACE) {
update();
if (System.currentTimeMillis() > startTime) {
startSpawningTokens();
startUpdatingWind();
GameState.setCurrentStage(GameStages.RACING);
}
}
if (currentStage == GameStages.RACING) {
update();
}
}
}
/**
* Start spawning coins every 60s after the first minute
*/
private void startSpawningTokens() {
Timer timer = new Timer("Token Spawning Timer");
timer.schedule(new TimerTask() {
@Override
public void run() {
if (tokensEnabled) {
spawnNewToken();
notifyMessageListeners(MessageFactory.getRaceXML());
}
}
}, 0, TOKEN_SPAWN_TIME);
}
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
private static void startUpdatingWind() {
Timer timer = new Timer("Wind Updating Timer");
timer.schedule(new TimerTask() {
@Override
public void run() {
updateWind();
}
}, 0, 500);
}
private static void updateWind() {
Integer direction = GameState.getWindDirection().intValue();
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
Random random = new Random();
if (Math.floorMod(random.nextInt(), 2) == 0) {
direction += random.nextInt(4);
windSpeed += random.nextInt(20) + 459;
} else {
direction -= random.nextInt(4);
windSpeed -= random.nextInt(20) + 459;
}
direction = Math.floorMod(direction, 360);
if (windSpeed > MAX_WIND_SPEED) {
windSpeed -= random.nextInt(500);
}
if (windSpeed <= MIN_WIND_SPEED) {
windSpeed += random.nextInt(500);
}
GameState.setWindSpeed(Double.valueOf(windSpeed));
GameState.setWindDirection(direction.doubleValue());
}
public static void updateBoat(Integer sourceId, BoatAction actionType) {
ServerYacht playerYacht = yachts.get(sourceId);
switch (actionType) {
@@ -260,31 +325,53 @@ public class GameState implements Runnable {
case DOWNWIND:
playerYacht.turnDownwind();
break;
case CONTINUOUSLY_TURNING:
playerYacht.setContinuouslyTurning(true);
break;
case DEFAULT_TURNING:
playerYacht.setContinuouslyTurning(false);
break;
}
}
/**
* 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
*/
private void spawnNewToken() {
tokensInPlay.clear();
Token token = randomSpawn.getRandomToken();
// token.assignType(TokenType.WIND_WALKER);
logger.debug("Spawned token of type " + token.getTokenType());
tokensInPlay.add(token);
MessageFactory.updateTokens(tokensInPlay);
}
/**
* 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;
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
previousUpdateTime = System.currentTimeMillis();
if (System.currentTimeMillis() > startTime) {
GameState.setCurrentStage(GameStages.RACING);
}
for (ServerYacht yacht : yachts.values()) {
updateVelocity(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
preformTokenUpdates(yacht); //This update must be done before collision. Sorta hacky
checkCollision(yacht);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
checkForLegProgression(yacht);
raceFinished = false;
}
}
if (raceFinished) {
@@ -292,6 +379,139 @@ public class GameState implements Runnable {
}
}
/**
* All token functionality entry points is taken care of here. So can be disabled and enabled
* easily
*
* @param yacht The yacht to perform token checks on
*/
private void preformTokenUpdates(ServerYacht yacht) {
Token collidedToken = checkTokenPickUp(yacht);
if (collidedToken != null) {
tokensInPlay.remove(collidedToken);
powerUpYacht(yacht, collidedToken);
MessageFactory.updateTokens(tokensInPlay);
notifyMessageListeners(MessageFactory.getRaceXML());
}
checkPowerUpTimeout(yacht);
TokenType powerUp = yacht.getPowerUp();
if (powerUp != null) {
switch (powerUp) {
case WIND_WALKER:
windWalk(yacht);
break;
case BUMPER:
ServerYacht collidedYacht = checkYachtCollision(yacht, true);
if (collidedYacht != null) {
yacht.powerDown();
boatTempShutDown(collidedYacht);
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
notifyMessageListeners(
MessageFactory.makeStatusEffectMessage(collidedYacht, powerUp));
}
break;
case RANDOM:
yacht.setPowerUpSpeedMultiplier(BAD_RANDOM_SPEED_PENALTY);
}
}
}
/**
* Powers up a thisYacht with the given token type.
*
* @param thisYacht The yacht to be powered up
* @param collidedToken The token which this thisYacht collided with
*/
private void powerUpYacht(ServerYacht thisYacht, Token collidedToken) {
//The random token has a 50% chance of becoming another token else becoming a speed detriment!
if (collidedToken.getTokenType() == TokenType.RANDOM && new Random().nextBoolean()) {
collidedToken.realiseRandom();
}
//If another yacht has the wind walker token, They should be powered down. Only one allowed!
else if (collidedToken.getTokenType() == TokenType.WIND_WALKER) {
for (ServerYacht otherYacht : yachts.values()) {
if (otherYacht != thisYacht && otherYacht.getPowerUp() == TokenType.WIND_WALKER) {
powerDownYacht(otherYacht);
}
}
}
thisYacht.powerUp(collidedToken.getTokenType());
String logMessage =
thisYacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName()
+ " token";
notifyMessageListeners(
MessageFactory.makeChatterMessage(thisYacht.getSourceId(), logMessage));
notifyMessageListeners(MessageFactory.getRaceXML());
notifyMessageListeners(MessageFactory.makePickupMessage(thisYacht, collidedToken));
logger.debug(
"Yacht: " + thisYacht.getShortName() + " got powerup " + collidedToken.getTokenType());
}
private void powerDownYacht(ServerYacht yacht) {
String logMessage =
yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired";
notifyMessageListeners(
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
yacht.powerDown();
}
// TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/
/**
* Disables the given boat for BUMPER_DISABLE_TIME ms.
*
* @param yacht The yacht to disable
*/
private void boatTempShutDown(ServerYacht yacht) {
yacht.setPowerUpSpeedMultiplier(0d);
Timer shutDownTimer = new Timer("Shutdown Timer");
shutDownTimer.schedule(new TimerTask() {
@Override
public void run() {
yacht.powerDown(); //Note this actually resets the boat to normal.
}
}, BUMPER_DISABLE_TIME);
}
/**
* Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the
* yacht down.
*
* @param yacht The yacht to check to power down
*/
private void checkPowerUpTimeout(ServerYacht yacht) {
if (yacht.getPowerUp() != null) {
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp()
.getTimeout()) {
powerDownYacht(yacht);
}
}
}
/**
* This function changes the wind to be at an angle that causes the yacht in question to be at
* its fastest velocity
*
* @param yacht The yacht to fix the wind for
*/
private void windWalk(ServerYacht yacht) {
Double optimalAngle = PolarTable.getOptimalAngle();
Double heading = yacht.getHeading();
windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L);
}
/**
* Check if the yacht has crossed the course limit
*
@@ -305,6 +525,7 @@ public class GameState implements Runnable {
return true;
}
}
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
@@ -312,8 +533,41 @@ public class GameState implements Runnable {
return false;
}
/**
* Checks all tokensInPlay to see if a yacht has picked one up. If so, the yacht is powered up
* in the appropriate way
* @param yacht The yacht to check for collision with a token
*
* @return The token collided with
*/
private Token checkTokenPickUp(ServerYacht yacht) {
for (Token token : tokensInPlay) {
Double distance = GeoUtility.getDistance(token, yacht.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) {
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
//Yacht Collision
ServerYacht collidedYacht = checkYachtCollision(serverYacht, false);
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
@@ -328,60 +582,64 @@ public class GameState implements Runnable {
collidedYacht.setCurrentVelocity(
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId())
);
} else {
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedMark != null) {
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
}
//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())
);
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
}
else{
if (checkBoundaryCollision(serverYacht)) {
//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())
);
}
}
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
}
}
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) * serverSpeedMultiplier * yacht
.getPowerUpSpeedMultiplier() * yacht.getBoatTypeSpeedMultiplier();
Double currentVelocity = yacht.getCurrentVelocity();
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (velocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (velocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-velocity / 200);
if (currentVelocity < maxBoatSpeed - 500) {
yacht.changeVelocity(
(maxBoatSpeed / 100) * yacht.getBoatTypeAccelerationMultiplier());
} else if (currentVelocity > maxBoatSpeed + 500) {
yacht.changeVelocity(
(-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier());
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
yacht
.setCurrentVelocity((maxBoatSpeed) * yacht.getBoatTypeAccelerationMultiplier());
}
} 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) * yacht.getBoatTypeAccelerationMultiplier());
} else if (currentVelocity > 100) {
yacht.changeVelocity(
(-currentVelocity / 50) * yacht.getBoatTypeAccelerationMultiplier());
} else if (currentVelocity <= 100) {
yacht.setCurrentVelocity(0d);
}
}
@@ -442,6 +700,12 @@ public class GameState implements Runnable {
}
if (hasProgressed) {
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
String logMessage = yacht.getBoatName() + " passed leg " + yacht.getLegNumber();
notifyMessageListeners(
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
}
yacht.incrementLegNumber();
sendMarkRoundingMessage(yacht);
logMarkRounding(yacht);
@@ -461,6 +725,10 @@ public class GameState implements Runnable {
* @param yacht The current yacht to check for
*/
private Boolean checkStartLineCrossing(ServerYacht yacht) {
long timeTillStart = System.currentTimeMillis() - this.getStartTime();
if (timeTillStart < 0){
return false;
}
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
@@ -476,6 +744,9 @@ public class GameState implements Runnable {
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.RACING);
String logMessage = yacht.getBoatName() + " passed start line";
notifyMessageListeners(
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
return true;
}
}
@@ -579,6 +850,10 @@ public class GameState implements Runnable {
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.FINISHED);
String logMessage = yacht.getBoatName() + " passed finish line";
notifyMessageListeners(
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
return true;
}
}
@@ -601,16 +876,20 @@ public class GameState implements Runnable {
String name = new String(customizeData);
playerYacht.setBoatName(name);
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
//This low level stuff shouldnt be here alistair! In fact no logic LIKE THIS should! - wmu16
int red = customizeData[0] & 0xFF;
int green = customizeData[1] & 0xFF;
int blue = customizeData[2] & 0xFF;
Color yachtColor = Color.rgb(red, green, blue);
playerYacht.setBoatColor(yachtColor);
} else if (requestType.equals(CustomizeRequestType.SHAPE)) {
String type = new String(customizeData);
playerYacht.setBoatType(BoatMeshType.valueOf(type));
}
}
private static Mark checkMarkCollision(ServerYacht yacht) {
Set<Mark> marksInRace = GameState.getMarks();
Set<Mark> marksInRace = new HashSet<>(marks);
for (Mark mark : marksInRace) {
if (GeoUtility.getDistance(yacht.getLocation(), mark)
<= MARK_COLLISION_DISTANCE) {
@@ -639,15 +918,22 @@ public class GameState implements Runnable {
* Collision detection which iterates through all the yachts and check if any yacht collided
* with this yacht. Return collided yacht or null if no collision.
*
* UPDATE: HACK!!! wmu16 - forBumperCollision is (the goddamn dirtiest) dirty flag to fix a
* weird bug where the bumper collision would not be registerd but the knock back collision would.
* In other words, only set the 'forBumperCollision' flag true if used for the bumper power up.
*
* @return yacht to compare to all other yachts.
*/
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
private static ServerYacht checkYachtCollision(ServerYacht yacht, Boolean forBumperCollision) {
Double collisionDistance =
(forBumperCollision) ? YACHT_COLLISION_DISTANCE + 2.5 : YACHT_COLLISION_DISTANCE;
for (ServerYacht otherYacht : GameState.getYachts().values()) {
if (otherYacht != yacht) {
Double distance = GeoUtility
.getDistance(otherYacht.getLocation(), yacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
;
if (distance < collisionDistance) {
return otherYacht;
}
}
@@ -671,8 +957,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);
}
}
@@ -683,9 +969,35 @@ public class GameState implements Runnable {
roundingMark.getSourceID()));
}
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 {
serverSpeedMultiplier = Double.valueOf(words[3]);
String logMessage = "Speed modifier set to x" + words[3];
notifyMessageListeners(MessageFactory
.makeChatterMessage(chatterMessage.getMessageType(), logMessage));
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(GameState.class);
logger.error("cannot parse >speed value");
}
return;
case "/finish":
String logMessage = "Game will now finish";
notifyMessageListeners(MessageFactory
.makeChatterMessage(chatterMessage.getMessageType(), logMessage));
endRace();
return;
}
}
notifyMessageListeners(chatterMessage);
}
public static void addMarkPassListener(NewMessageListener listener) {
markListeners.add(listener);
public static void addMessageEventListener(NewMessageListener listener) {
newMessageListeners.add(listener);
}
public static void setCustomizationFlag() {
@@ -699,4 +1011,51 @@ 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;
try {
ServerAdvertiser.getInstance().setCapacity(newMax);
} catch (IOException e) {
logger.warn("Couldn't update max players");
}
}
public static void endRace () {
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
currentStage = GameStages.FINISHED;
}
public static double getServerSpeedMultiplier() {
return serverSpeedMultiplier;
}
public static void setTokensEnabled (boolean tokensEnabled) {
GameState.tokensEnabled = tokensEnabled;
}
}
@@ -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,12 +49,12 @@ public class HeartbeatThread implements Runnable {
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
try {
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
for (Player player : GameState.getPlayers()) {
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
@@ -58,6 +63,9 @@ public class HeartbeatThread implements Runnable {
}
updateDelegate();
seqNum++;
} catch (NullPointerException ne) {
logger.debug("Socket closed between checking for connection and sending heartbeat");
}
}
/**
@@ -1,17 +1,30 @@
package seng302.gameServer;
import seng302.gameServer.messages.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RaceXMLData;
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 java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
/**
* A class describing the overall server, which creates and collects server threads for each client
@@ -19,148 +32,153 @@ 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 boolean hasStarted = false;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private static Integer capacity;
private RaceXMLData raceXMLData;
private RegattaXMLData regattaXMLData;
private boolean serverStarted = false;
private void startAdvertisingServer() {
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 - 1)
.registerGame(PORT, regattaXMLData.getRegattaName());
} catch (IOException e) {
logger.warn("Could not register server");
}
}
public MainServerThread() {
new GameState("localhost");
new GameState();
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);
}
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMarkPassListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
startUpdatingWind();
thread.start();
}
private void startServer() {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
GameState.setRace(raceXMLData);
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
startAdvertisingServer();
GameState.addMessageEventListener(this::broadcastMessage);
sendSetupMessages();
}
public void run() {
new HeartbeatThread(this);
new ServerListenThread(serverSocket, this);
hasStarted = true;
//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);
}
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();
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getCustomizationFlag()) {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
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();
}
}
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
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);
}
}
}
try {
synchronized (this){
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() {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
broadcastMessage(MessageFactory.getRaceXML());
broadcastMessage(MessageFactory.getRegattaXML());
broadcastMessage(MessageFactory.getBoatXML());
}
private void broadcastMessage(Message message) {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message);
}
}
private static void updateWind(){
Integer direction = GameState.getWindDirection().intValue();
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
Random random = new Random();
if (Math.floorMod(random.nextInt(), 2) == 0){
direction += random.nextInt(4);
windSpeed += random.nextInt(20) + 50;
}
else{
direction -= random.nextInt(4);
windSpeed -= random.nextInt(20) + 50;
}
direction = Math.floorMod(direction, 360);
if (windSpeed > MAX_WIND_SPEED){
windSpeed -= random.nextInt(1000);
}
if (windSpeed <= MIN_WIND_SPEED){
windSpeed += random.nextInt(1000);
}
GameState.setWindSpeed(Double.valueOf(windSpeed));
GameState.setWindDirection(direction.doubleValue());
}
private static void startUpdatingWind(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
updateWind();
}
}, 0, 500);
}
static void serverLog(String message, int logLevel) {
if (logLevel <= LOG_LEVEL) {
System.out.println(
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
}
/**
* A client has tried to connect to the server
*
@@ -168,13 +186,45 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
*/
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(() -> {
for (ServerToClientThread thread : serverToClientThreads) {
thread.sendSetupMessages();
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
if (race != null) {
raceXMLData = race;
}
if (regattaXMLData != null) {
startServer();
}
});
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
if (regatta != null) {
regattaXMLData = regatta;
}
if (raceXMLData != null) {
startServer();
}
});
} else {
//serverToClientThread.addConnectionListener(this::sendSetupMessages);
}
serverToClientThreads.add(serverToClientThread);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
while (regattaXMLData == null && raceXMLData == null){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
}
@@ -185,12 +235,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;
@@ -201,70 +246,43 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
serverToClientThread.sendSetupMessages();
}
}
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;
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
// sendSetupMessages();
// }
}
public void terminate() {
@@ -275,43 +293,170 @@ 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);
//
// // Calculating midpoint
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
// Double length = GeoUtility.getDistance(startMark1, startMark2);
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
//
// // Setting each boats position side by side
// final double SEPARATION = 50.0; // distance apart in meters
//
// int boatIndex = 0;
// for (ServerYacht yacht : GameState.getYachts().values()) {
// int distanceApart = boatIndex / 2;
//
// if (boatIndex % 2 == 1 && boatIndex != 0) {
// distanceApart++;
// distanceApart *= -1;
// }
//
// GeoPoint spawnMark = GeoUtility
// .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * SEPARATION);
//
// if (yacht.getHeading() < perpendicularAngle) {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 90, SEPARATION);
// } else {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 270, SEPARATION);
// }
//
// yacht.setLocation(spawnMark);
// boatIndex++;
// }
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
GeoPoint startMark1 = cm.getSubMark(1);
GeoPoint startMark2 = cm.getSubMark(2);
// Calculating midpoint
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
Double length = GeoUtility.getDistance(startMark1, startMark2);
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
// final double SEPARATION = 50.0; // distance apart in meters
//
// //Reverse of the angle from start to first mark
// double angleToFirstMark = 360 - GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// );
//
// //Length of start line
// double startLineLength = GeoUtility.getDistance(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //Angle of start line
// double startMarkToMarkAngle = GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //How many yachts can fit along the start line
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
// //The free space left by the boats.
// double buffer = (startLineLength % SEPARATION) / 2;
//
// //Randomize starting order.
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
// Collections.shuffle(serverYachtList);
//
// //set the starting point away from start line.
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// angleToFirstMark, SEPARATION
// );
//
// //Move it along the start line
// startingPoint = GeoUtility.getGeoCoordinate(
// startingPoint, startMarkToMarkAngle, buffer
// );
//
// int yachtCount = 0;
// int repeats = 0;
//
// GeoPoint yachtLocation;
//
// for (ServerYacht serverYacht : serverYachtList) {
//
// //Move away from start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// startingPoint, angleToFirstMark,repeats * SEPARATION
// );
// //Move along start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
// );
// serverYacht.setLocation(yachtLocation);
// serverYacht.setHeading(GeoUtility.getBearing(
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// ));
// //Set location for next yacht
// yachtCount++;
// if (yachtCount > spacesAlongLine) {
// yachtCount = 0;
// repeats++;
// }
// }
// Setting each boats position side by side
double DISTANCE_FACTOR = 50.0; // distance apart in meters
int boatIndex = 0;
for (ServerYacht yacht : GameState.getYachts().values()) {
int distanceApart = boatIndex / 2;
final double DISTANCE_TO_START = 75d;
final double YACHT_SEPARATION = 20d;
if (boatIndex % 2 == 1 && boatIndex != 0) {
distanceApart++;
distanceApart *= -1;
//Length of start line
double startLineLength = GeoUtility.getDistance(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//How many yachts can fit along the start line
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
//Angle of start line
double startMarkToMarkAngle = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//angle from first mark to the start
double angleToStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
);
double angleFromStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
);
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
angleToStart, DISTANCE_TO_START
);
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
Collections.shuffle(randomisedYachts);
while (randomisedYachts.size() > 0) {
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
startingPoint, startMarkToMarkAngle + 180, yachtSpace
);
for (int i=0; i<numYachtsInLine; i++){
randomisedYachts.get(0).setHeading(angleFromStart);
randomisedYachts.get(0).setLocation(firstYachtPoint);
firstYachtPoint = GeoUtility.getGeoCoordinate(
firstYachtPoint, startMarkToMarkAngle, yachtSpace
);
randomisedYachts.remove(0);
}
GeoPoint spawnMark = GeoUtility
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
startingPoint = GeoUtility.getGeoCoordinate(
startingPoint, angleToStart, DISTANCE_TO_START
);
}
}
yacht.setLocation(spawnMark);
boatIndex++;
}
public boolean hasStarted() {
return hasStarted;
}
}
@@ -0,0 +1,232 @@
package seng302.gameServer;
import seng302.gameServer.messages.*;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.BoatSubMessage;
import seng302.gameServer.messages.ChatterMessage;
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.gameServer.messages.YachtEventCodeMessage;
import seng302.gameServer.messages.YachtEventType;
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.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.XMLGenerator;
import java.util.ArrayList;
import java.util.List;
/**
* 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();
private static XMLMessage race;
private static XMLMessage regatta;
private static XMLMessage boats;
public static void updateXMLGenerator(RaceXMLData race, RegattaXMLData regatta) {
xmlGenerator.setRegattaTemplate(
new RegattaXMLTemplate(
regatta.getRegattaName(),
regatta.getCourseName(),
regatta.getCentralLat(),
regatta.getCentralLng()
)
);
xmlGenerator.setRaceTemplate(
new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>(),
race.getMarkSequence(),
race.getCourseLimit(),
new ArrayList<>(race.getCompoundMarks().values()),
GameState.getCapacity(), true
)
);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
xmlStr = xmlGenerator.getRegattaAsXml();
MessageFactory.regatta = new XMLMessage(xmlStr, XMLMessageSubType.REGATTA, xmlStr.length());
xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateBoats(List<ServerYacht> yachts) {
// for (ServerYacht serverYacht : yachts) {
// System.out.println(serverYacht);
// }
xmlGenerator.getRace().setBoats(yachts);
String xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateTokens(List<Token> tokens) {
xmlGenerator.getRace().setTokens(tokens);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
}
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() {
return race;
}
public static XMLMessage getRegattaXML() {
return regatta;
}
public static XMLMessage getBoatXML() {
return boats;
}
public static YachtEventCodeMessage makeCollisionMessage(ServerYacht serverYacht) {
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
}
/**
* Constructs a message to be sent out whenever a yacht picks up a boost
*
* @param serverYacht The yacht that has picked up a power up
* @param token The token which they picked up
* @return The corresponding YachtEventCodeMessage
*/
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
YachtEventType yachtEventType = null;
switch (token.getTokenType()) {
case BOOST:
yachtEventType = YachtEventType.TOKEN_VELOCITY;
break;
case HANDLING:
yachtEventType = YachtEventType.TOKEN_HANDLING;
break;
case WIND_WALKER:
yachtEventType = YachtEventType.TOKEN_WIND_WALKER;
break;
case BUMPER:
yachtEventType = YachtEventType.TOKEN_BUMPER;
break;
case RANDOM:
yachtEventType = YachtEventType.TOKEN_RANDOM;
break;
}
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
}
/**
* Constructs a message representing a certain buff / debuff for a given yacht. For now this is
* just for the bumper debuff so the affected boat is aware that it has been crashed. This could
* however be extended to render affects for all boats given a certain debuff.
*
* @param yacht The yacht affected by some status
* @param token The token indicating what status they have
* @return A YachtEventCodeMessage
*/
public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht,
TokenType token) {
YachtEventType yachtEventType = null;
switch (token) {
case BUMPER:
yachtEventType = YachtEventType.BUMPER_CRASH;
break;
}
return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType);
}
/**
* Constructs a message to be sent out when a given yacht powers down (From a boost of any type)
*
* @param yacht The yacht that is powering down
* @return A YachtEventCodeMessage representing this action
*/
public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) {
return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN);
}
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
return new ChatterMessage(messageType, "SERVER: " + message);
}
}
@@ -0,0 +1,185 @@
package seng302.gameServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
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 DiscoveryServerClient repositoryClient;
private Hashtable<String ,String> props;
private Logger logger = LoggerFactory.getLogger(ServerAdvertiser.class);
private ServerAdvertiser() throws IOException{
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
repositoryClient = new DiscoveryServerClient();
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) {
logger.warn("Failed to register service info");
}
}
}, 0);
ServerListing serverListing = new ServerListing(serverName, props.get("map"), new DiscoveryServerClient().getInetIp(), portNo, Integer.parseInt(props.get("capacity")));
repositoryClient.register(serverListing);
}
/**
* 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,101 @@
package seng302.gameServer;
public class ServerDescription {
private Integer capacity;
private String address;
private Integer portNum;
private String serverName;
private String mapName;
private Integer numPlayers;
private Long lastUpdated;
private Long lastRefreshed;
private static Long EXPIRY_INTERVAL = 5000L;
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;
lastUpdated = System.currentTimeMillis();
}
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();
}
public Boolean hasExpired(){
return System.currentTimeMillis() - lastUpdated > EXPIRY_INTERVAL;
}
public Boolean serverShouldBeRemoved() {
if (lastRefreshed == null) return false;
return System.currentTimeMillis() - lastRefreshed > EXPIRY_INTERVAL;
}
public void hasBeenRefreshed(){
lastRefreshed = System.currentTimeMillis();
}
}
@@ -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))
);
}
}
@@ -1,66 +1,47 @@
package seng302.gameServer;
import java.io.BufferedReader;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import org.w3c.dom.Document;
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;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
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.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
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;
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.ClientType;
import seng302.gameServer.messages.CustomizeRequestType;
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.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.utilities.XMLGenerator;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on
* 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,15 +72,18 @@ 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;
private ServerYacht yacht;
private Player player;
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
public ServerToClientThread(Socket socket) {
this.socket = socket;
seqNo = 0;
@@ -115,50 +99,22 @@ public class ServerToClientThread implements Runnable, Observer {
thread.start();
}
private void setUpPlayer(){
BufferedReader fn;
String fName = "";
BufferedReader ln;
String lName = "";
public Integer getSourceId() {
return sourceId;
}
fn = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_First_Names.csv"
)
)
);
List<String> all = fn.lines().collect(Collectors.toList());
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
ln = new BufferedReader(
new InputStreamReader(
ServerToClientThread.class.getResourceAsStream(
"/server_config/CSV_Database_of_Last_Names.csv"
)
)
);
all = ln.lines().collect(Collectors.toList());
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
private void setUpPlayer(){
String shortName = "P" + sourceId;
String longName = "Player " + sourceId;
ServerYacht yacht = new ServerYacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "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 +123,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;
@@ -211,32 +167,52 @@ public class ServerToClientThread implements Runnable, Observer {
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION:
BoatAction actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
BoatAction actionType = ServerPacketParser.extractBoatAction(packet);
GameState.updateBoat(sourceId, actionType);
break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
ClientType requestedType = ServerPacketParser
.extractClientType(packet);
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(packet);
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
Long sourceID = Message.bytesToLong(
Arrays.copyOfRange(payload, 0, 3)
);
CustomizeRequestType requestType = ServerPacketParser
.extractCustomizationType(
new StreamPacket(type, payloadLength, timeStamp, payload));
.extractCustomizationType(packet);
GameState.customizePlayer(sourceID, requestType,
Arrays.copyOfRange(payload, 6, payload.length));
Arrays.copyOfRange(payload, 6, payload.length)
);
GameState.setCustomizationFlag();
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
break;
case RACE_XML:
Document document = StreamParser.extractXmlMessage(packet);
raceXMLProperty.set(
XMLParser.parseRace(document)
);
GameState.setMaxPlayers(XMLParser.getMaxPlayers(document));
GameState.setTokensEnabled(XMLParser.tokensEnabled(document));
break;
case REGATTA_XML:
regattaXMLProperty.set(
XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet)
)
);
break;
}
} else {
logger.warn("Packet has been dropped", 1);
@@ -244,40 +220,18 @@ 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();
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);
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xml.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
xml.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
xml.getRaceAsXml().length());
sendMessage(xmlMessage);
sendMessage(MessageFactory.getRegattaXML());
sendMessage(MessageFactory.getBoatXML());
sendMessage(MessageFactory.getRaceXML());
}
private void closeSocket() {
@@ -288,6 +242,10 @@ public class ServerToClientThread implements Runnable, Observer {
}
}
public Boolean isSocketOpen() {
return !socket.isClosed();
}
private int readByte() throws Exception {
int currentByte = -1;
try {
@@ -334,23 +292,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;
}
@@ -359,14 +300,6 @@ public class ServerToClientThread implements Runnable, Observer {
return socket;
}
public ServerYacht getYacht() {
return yacht;
}
public void sendCollisionMessage(Integer yachtId) {
sendMessage(new YachtEventCodeMessage(yachtId));
}
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
@@ -386,4 +319,16 @@ public class ServerToClientThread implements Runnable, Observer {
public void addDisconnectListener(DisconnectListener disconnectListener) {
this.disconnectListener = disconnectListener;
}
public void setAsHost() {
isHost = true;
}
public SimpleObjectProperty<RaceXMLData> raceXMLProperty() {
return raceXMLProperty;
}
public SimpleObjectProperty<RegattaXMLData> regattaXMLProperty() {
return regattaXMLProperty;
}
}
@@ -14,7 +14,9 @@ public enum BoatAction {
TACK_GYBE(4),
UPWIND(5),
DOWNWIND(6),
MAINTAIN_HEADING(7);
MAINTAIN_HEADING(7),
CONTINUOUSLY_TURNING(8),
DEFAULT_TURNING(9);
private final int type;
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
@@ -5,19 +5,19 @@ package seng302.gameServer.messages;
*/
public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1;
private final int MESSAGE_SIZE = 5;
private BoatAction actionType;
public BoatActionMessage(BoatAction actionType) {
public BoatActionMessage(BoatAction actionType, int sourceId) {
this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
setHeader(new Header(MessageType.BOAT_ACTION, sourceId, (short) MESSAGE_SIZE)); // the second variable is the source id
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putInt(actionType.getValue(), 1);
putInt(sourceId, 4);
writeCRC();
rewind();
}
@Override
@@ -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 getMessageType() {
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) {
@@ -21,7 +21,10 @@ public enum MessageType {
REGISTRATION_REQUEST(101),
REGISTRATION_RESPONSE(102),
CUSTOMIZATION_REQUEST(103),
CUSTOMIZATION_RESPONSE(104);
CUSTOMIZATION_RESPONSE(104),
REPO_REGISTRATION_REQUEST(201),
ROOM_CODE_REQUEST(202),
LOBBY_REQUEST(203);
private int code;
@@ -4,8 +4,8 @@ package seng302.gameServer.messages;
public class RegistrationRequestMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public RegistrationRequestMessage(ClientType type){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
public RegistrationRequestMessage(ClientType type, int clientID){
setHeader(new Header(MessageType.REGISTRATION_REQUEST, clientID, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
@@ -0,0 +1,24 @@
package seng302.gameServer.messages;
public class RoomCodeRequest extends Message{
private int size = 0;
@Override
public int getSize() {
return size;
}
public RoomCodeRequest(String roomCode){
size = roomCode.length() + 6;
setHeader(new Header(MessageType.ROOM_CODE_REQUEST, 0x01, (short)getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(roomCode.length(), 6);
putBytes(roomCode.getBytes());
writeCRC();
rewind();
}
}
@@ -0,0 +1,64 @@
package seng302.gameServer.messages;
import seng302.discoveryServer.util.ServerListing;
public class ServerRegistrationMessage extends Message {
private int size;
public ServerRegistrationMessage(ServerListing serverListing) {
String serverName = serverListing.getServerName();
String mapName = serverListing.getMapName();
String address = serverListing.getAddress();
int port = serverListing.getPortNumber();
int players = serverListing.getPortNumber();
int capacity = serverListing.getCapacity();
String roomCode = serverListing.getRoomCode();
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
}
public static ServerRegistrationMessage getEmptyRegistration() {
return new ServerRegistrationMessage("","","",0,0,0,"");
}
@Override
public int getSize() {
return size;
}
public ServerRegistrationMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
}
private void createMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
size = serverName.getBytes().length + mapName.length() + address.length() + roomCode.length() + 36;
setHeader(new Header(MessageType.REPO_REGISTRATION_REQUEST, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
int nameLength = serverName.length();
int mapNameLength = mapName.length();
int addressLength = address.length();
int roomCodeLength = roomCode.length();
// Put fields here
putInt(nameLength, 6);
putInt(mapNameLength, 6);
putInt(addressLength, 6);
putInt(roomCodeLength, 6);
putInt(port, 4);
putInt(players, 4);
putInt(capacity, 4);
putBytes(serverName.getBytes());
putBytes(mapName.getBytes());
putBytes(address.getBytes());
putBytes(roomCode.getBytes());
writeCRC();
rewind();
}
}
@@ -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,26 @@
package seng302.gameServer.messages;
/**
* Enum for different event types for the yacht
*/
public enum YachtEventType {
COLLISION(33),
TOKEN_VELOCITY(34),
TOKEN_BUMPER(35),
TOKEN_HANDLING(36),
TOKEN_WIND_WALKER(37),
TOKEN_RANDOM(38),
POWER_DOWN(39),
BUMPER_CRASH(40);
private int code;
YachtEventType(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
+110 -19
View File
@@ -6,16 +6,23 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Timer;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.beans.value.ObservableObjectValue;
import javafx.collections.FXCollections;
import javafx.scene.paint.Color;
import jdk.nashorn.internal.objects.annotations.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.mark.CompoundMark;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
@@ -32,19 +39,40 @@ public class ClientYacht extends Observable {
@FunctionalInterface
public interface MarkRoundingListener {
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
void notifyRounding(ClientYacht yacht, int legNumber);
}
@FunctionalInterface
public interface ColorChangeListener {
void notifyColorChange(ClientYacht yacht);
}
//This notifies RaceViewController so it can display icon - wmu16
@FunctionalInterface
public interface PowerUpListener {
void notifyPowerUp(ClientYacht yacht, TokenType tokenType);
}
//This notifies RaceViewController so it can remove token icon - wmu16
@FunctionalInterface
public interface PowerDownListener {
void notifyPowerDown(ClientYacht yacht);
}
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
private String boatType;
private BoatMeshType boatType;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private Integer position;
private TokenType powerUp;
private Long estimateTimeAtFinish;
private Boolean sailIn = true;
@@ -57,16 +85,24 @@ public class ClientYacht extends Observable {
private Integer boatStatus;
private Double currentVelocity;
Timer t;
private BoatObject boatObject;
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
private List<PowerUpListener> powerUpListeners = new ArrayList<>();
private List<PowerDownListener> powerDownListeners = new ArrayList<>();
private List<ColorChangeListener> colorChangeListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
private CompoundMark lastMarkRounded;
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
public ClientYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
@@ -76,6 +112,7 @@ public class ClientYacht extends Observable {
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.heading = 120.0; //In degrees
this.headingProperty.set(this.heading);
this.currentVelocity = 0d;
this.boatStatus = 1;
this.colour = Color.rgb(0, 0, 0, 1.0);
@@ -90,7 +127,7 @@ public class ClientYacht extends Observable {
super.addObserver(o);
}
public String getBoatType() {
public BoatMeshType getBoatType() {
return boatType;
}
@@ -189,14 +226,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;
}
@@ -209,6 +238,32 @@ public class ClientYacht extends Observable {
this.position = position;
}
/**
* Powers down the boat and notifies the raceViewController to display
*/
public void powerDown() {
this.powerUp = null;
for (PowerDownListener listener : powerDownListeners) {
listener.notifyPowerDown(this);
}
}
/**
* powers up the boat and notifies the raceViewController to display
*
* @param tokenType The type of token that this boat is being powered up with
*/
public void setPowerUp(TokenType tokenType) {
this.powerUp = tokenType;
for (PowerUpListener listener : powerUpListeners) {
listener.notifyPowerUp(this, tokenType);
}
}
public TokenType getPowerUp() {
return powerUp;
}
public void toggleSail() {
sailIn = !sailIn;
}
@@ -231,6 +286,7 @@ public class ClientYacht extends Observable {
public void setHeading(Double heading) {
this.heading = heading;
setHeadingProperty();
}
@Override
@@ -257,19 +313,26 @@ public class ClientYacht extends Observable {
public void setColour(Color colour) {
this.colour = colour;
for (ColorChangeListener listener : colorChangeListeners) {
listener.notifyColorChange(this);
}
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
// this.currentVelocity = velocity;
setHeadingProperty();
this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
}
}
private void setHeadingProperty() {
headingProperty.set(heading);
}
public void addLocationListener(YachtLocationListener listener) {
locationListeners.add(listener);
}
@@ -278,6 +341,18 @@ public class ClientYacht extends Observable {
markRoundingListeners.add(listener);
}
public void addPowerUpListener(PowerUpListener listener) {
powerUpListeners.add(listener);
}
public void addPowerDownListener(PowerDownListener listener) {
powerDownListeners.add(listener);
}
public void addColorChangeListener(ColorChangeListener listener) {
colorChangeListeners.add(listener);
}
public void removeMarkRoundingListener(MarkRoundingListener listener) {
markRoundingListeners.remove(listener);
}
@@ -286,13 +361,29 @@ 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;
}
public void setBoatObject(BoatObject newBoatObject) {
this.boatObject = newBoatObject;
}
public BoatObject getBoatObject() {
return this.boatObject;
}
public ReadOnlyDoubleWrapper getHeadingProperty() {
return headingProperty;
}
}
@@ -0,0 +1,87 @@
package seng302.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javafx.scene.input.KeyCode;
public class GameKeyBind {
private static GameKeyBind instance;
private Map<KeyCode, KeyAction> keyToActionMap;
private Map<KeyAction, KeyCode> actionToKeyMap;
private Boolean continuouslyTurning;
private GameKeyBind() {
setToDefault();
}
public void setToDefault() {
actionToKeyMap = new HashMap<>();
keyToActionMap = new HashMap<>();
continuouslyTurning = false;
// default key bindings
ArrayList<KeyCode> keys = new ArrayList<>();
keys.add(KeyCode.Z);
keys.add(KeyCode.X);
keys.add(KeyCode.SPACE);
keys.add(KeyCode.SHIFT);
keys.add(KeyCode.ENTER);
keys.add(KeyCode.PAGE_UP);
keys.add(KeyCode.PAGE_DOWN);
keys.add(KeyCode.F1);
keys.add(KeyCode.D);
keys.add(KeyCode.A);
keys.add(KeyCode.W);
keys.add(KeyCode.S);
for (int i = 0; i < 12; i++) {
actionToKeyMap.put(KeyAction.getType(i + 1), keys.get(i));
keyToActionMap.put(keys.get(i), KeyAction.getType(i + 1));
}
}
public static GameKeyBind getInstance() {
if (instance == null) {
instance = new GameKeyBind();
}
return instance;
}
public KeyCode getKeyCode(KeyAction keyAction) {
return instance.actionToKeyMap.get(keyAction);
}
public KeyAction getKeyAction(KeyCode keyCode) {
return instance.keyToActionMap.get(keyCode);
}
/**
* Binds a key to a key action
*
* @return true if successfully bind
*/
public boolean bindKeyToAction(KeyCode keyCode, KeyAction keyAction) {
if (instance.keyToActionMap.containsKey(keyCode)) {
// if the key has been bound to other action, return false
return false;
} else {
instance.keyToActionMap.put(keyCode, keyAction); // add key -> action
KeyCode oldKeyCode = instance.actionToKeyMap
.get(keyAction); // get old key for the action
instance.keyToActionMap.remove(oldKeyCode); // remove the old key -> action
instance.actionToKeyMap
.replace(keyAction, keyCode); // replace the old key by the newer one
return true;
}
}
public void toggleTurningMode() {
continuouslyTurning = !continuouslyTurning;
}
public Boolean isContinuouslyTurning() {
return continuouslyTurning;
}
}
@@ -0,0 +1,40 @@
package seng302.model;
import java.util.HashMap;
import java.util.Map;
public enum KeyAction {
ZOOM_IN(1),
ZOOM_OUT(2),
VMG(3),
SAILS_STATE(4),
TACK_GYBE(5),
UPWIND(6),
DOWNWIND(7),
VIEW(8),
RIGHT(9),
LEFT(10),
FORWARD(11),
BACKWARD(12);
private final int type;
private static final Map<Integer, KeyAction> intToTypeMap = new HashMap<>();
static {
for (KeyAction type : KeyAction.values()) {
intToTypeMap.put(type.getValue(), type);
}
}
KeyAction(int type) {
this.type = type;
}
public static KeyAction getType(int value) {
return intToTypeMap.get(value);
}
public int getValue() {
return this.type;
}
}
+5
View File
@@ -15,4 +15,9 @@ public class Limit extends GeoPoint {
public Integer getSeqID() {
return seqID;
}
@Override
public String toString(){
return "Limit = {seqID=" + seqID + ", lat=" + getLat() + ", lng=" + getLng() + "}";
}
}
+37 -4
View File
@@ -4,7 +4,9 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
@@ -17,6 +19,7 @@ public final class PolarTable {
private static HashMap<Double, HashMap<Double, Double>> polarTable;
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
private static Double optimalAngle;
private static int upTwaIndex;
private static int dnTwaIndex;
@@ -33,11 +36,13 @@ public final class PolarTable {
upwindOptimal = new HashMap<>();
downwindOptimal = new HashMap<>();
String line;
String line = null;
String check;
Boolean isHeaderLine = true;
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
while ((line = br.readLine()) != null) {
while ((check = br.readLine()) != null) {
line = check;
String[] thisLine = line.split(",");
//Initial line in file
@@ -66,7 +71,10 @@ public final class PolarTable {
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
}
}
getMaxSpeedAngle(line);
} catch (IOException e) {
System.out.println("[PolarTable] IO exception");
@@ -74,6 +82,27 @@ public final class PolarTable {
}
/**
* Passes the final line of the polar table and iterates over the speeds for each
* angle, velocity pair to find the angle that produces the highest velocity
*
* @param line The last line of the polar file
*/
private static void getMaxSpeedAngle(String line) {
String[] theLastLine = line.split(",");
Double maxWindVal = Double.parseDouble(theLastLine[0]);
Double optimalAngle = Double.parseDouble(theLastLine[1]);
Double maxSpeed = Double.parseDouble(theLastLine[2]);
for (Map.Entry<Double, Double> entry : polarTable.get(maxWindVal).entrySet()) {
if (entry.getValue() > maxSpeed) {
maxSpeed = entry.getValue();
optimalAngle = entry.getKey();
}
}
PolarTable.optimalAngle = optimalAngle;
}
/**
* Parses the header line of a polar file
@@ -85,14 +114,18 @@ public final class PolarTable {
String thisItem = thisLine[i];
if (thisItem.toLowerCase().startsWith("uptwa")) {
upTwaIndex = i;
}
else if (thisItem.toLowerCase().startsWith("dntwa")) {
} else if (thisItem.toLowerCase().startsWith("dntwa")) {
dnTwaIndex = i;
}
}
}
public static Double getOptimalAngle() {
return optimalAngle;
}
/**
* @return The entire polar table
*/
+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;
}
}
@@ -0,0 +1,122 @@
package seng302.model;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javafx.geometry.Point2D;
import seng302.utilities.GeoUtility;
/**
* Contains information on a scaled lat lon point for use with mapping geographical elements to a 2d plane.
*/
public class ScaledPoint extends GeoPoint {
public enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private double x, y, scaleFactor;
private ScaleDirection scaleDirection;
private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) {
super(lat, lng);
this.x = x;
this.y = y;
this.scaleFactor = scaleFactor;
this.scaleDirection = direction;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getScaleFactor() {
return scaleFactor;
}
public ScaleDirection getScaleDirection() {
return scaleDirection;
}
public Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
public Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xReference = this.getX();
double yReference = this.getY();
angleFromReference = GeoUtility.getBearingRad(
this, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
this, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
return new Point2D(xReference, yReference);
}
public static ScaledPoint makeScaledPoint(double width, double height,
List<? extends GeoPoint> points, boolean centered) {
double referencePointX, referencePointY, scaleFactor, lat, lng;
ScaleDirection scaleDirection;
points = new ArrayList<>(points);
points.sort(Comparator.comparingDouble(GeoPoint::getLat));
GeoPoint minLatPoint = points.get(0);
GeoPoint maxLatPoint = points.get(points.size() - 1);
points.sort(Comparator.comparingDouble(GeoPoint::getLng));
GeoPoint minLonPoint = points.get(0);
GeoPoint maxLonPoint = points.get(points.size() - 1);
referencePointX = centered ? 0 : width / 2;
referencePointY = centered ? 0 : height / 2;
lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat();
lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng();
GeoPoint ref = new GeoPoint(lat, lng);
double vertDistance = GeoUtility.getDistance(
ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng())
) * 2.1;
double horiDistance = GeoUtility.getDistance(
ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng())
) * 2.1;
double vertScale = height / vertDistance;
if (horiDistance * vertScale > width) {
scaleFactor = width / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
scaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection);
}
}
+125 -37
View File
@@ -1,39 +1,39 @@
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 seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* 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);
public static final Double TURN_STEP = 5.0;
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
//Boat info
private String boatType;
private BoatMeshType boatType;
private Double turnStep = 5.0;
private Double boatTypeSpeedMultiplier = 1.0;
private Double boatTypeTurnStepMultiplier = 1.0;
private Double boatTypeAccelerationMultiplier = 1.0;
private Integer sourceId;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
private BoatStatus boatStatus;
private Color boatColor;
//Location
private Double lastHeading;
private Boolean sailIn;
@@ -52,10 +52,18 @@ public class ServerYacht extends Observable {
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
//PowerUp
private TokenType powerUp;
private Long powerUpStartTime;
private Double powerUpSpeedMultiplier;
private Integer powerUpHandlingMultiplier;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
//turning mode
private Boolean continuouslyTurning;
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
setBoatType(boatType);
this.boatStatus = BoatStatus.PRESTART;
this.sourceId = sourceId;
this.hullID = hullID;
@@ -71,10 +79,13 @@ public class ServerYacht extends Observable {
this.currentMarkSeqID = 0;
this.legNumber = 0;
this.boatColor = Colors.getColor(sourceId - 1);
this.powerUp = null;
this.powerUpSpeedMultiplier = 1d;
this.powerUpHandlingMultiplier = 1;
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
this.continuouslyTurning = false;
}
@@ -102,12 +113,40 @@ public class ServerYacht extends Observable {
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
* Powers up a yacht with a given yacht, only after powering it down first to avoid double power
* ups
*
* @param powerUp The given power up
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
public void powerUp(TokenType powerUp) {
powerDown();
switch (powerUp) {
case BOOST:
powerUpSpeedMultiplier = GameState.VELOCITY_BOOST_MULTIPLIER;
break;
case HANDLING:
powerUpHandlingMultiplier = GameState.HANDLING_BOOST_MULTIPLIER;
break;
}
this.powerUp = powerUp;
powerUpStartTime = System.currentTimeMillis();
}
/**
* Powers down a yacht, returning its power multipliers back to 1
*/
public void powerDown() {
this.powerUp = null;
this.powerUpSpeedMultiplier = 1d;
this.powerUpHandlingMultiplier = 1;
}
public Long getPowerUpStartTime() {
return powerUpStartTime;
}
public TokenType getPowerUp() {
return powerUp;
}
/**
@@ -116,13 +155,14 @@ public class ServerYacht extends Observable {
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
Double newVal = heading + amount * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier;
lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L);
}
/**
* Swaps the boats direction from one side of the wind to the other.
* @param windDirection .
*/
public void tackGybe(Double windDirection) {
if (isAuto) {
@@ -138,11 +178,11 @@ public class ServerYacht extends Observable {
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
*
* @param thisHeading The heading to move the boat towards.
* @param newHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
private void setAutoPilot(Double newHeading) {
isAuto = true;
autoHeading = thisHeading;
autoHeading = newHeading;
}
/**
@@ -160,8 +200,9 @@ public class ServerYacht extends Observable {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
<= turnStep*1.5) {
isAuto = false;
setHeading(autoHeading);
}
}
}
@@ -173,44 +214,52 @@ public class ServerYacht extends Observable {
public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (continuouslyTurning) {
adjustHeading(turnStep);
} else {
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
} else {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
} else {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
} else {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
}
}
}
public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (continuouslyTurning) {
adjustHeading(-turnStep);
} else {
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
} else {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
} else {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
adjustHeading(turnStep);
} else {
adjustHeading(-TURN_STEP);
adjustHeading(-turnStep);
}
}
}
@@ -239,7 +288,7 @@ public class ServerYacht extends Observable {
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
(optimalHeading + GameState.getWindDirection()) % 360;
setAutoPilot(optimalHeading);
}
@@ -254,9 +303,9 @@ public class ServerYacht extends Observable {
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP / 5);
adjustHeading(turnStep / 5);
} else {
adjustHeading(-TURN_STEP / 5);
adjustHeading(-turnStep / 5);
}
}
@@ -407,4 +456,43 @@ public class ServerYacht extends Observable {
return boatColor;
}
public void setBoatType(BoatMeshType boatType) {
this.boatTypeAccelerationMultiplier = boatType.accelerationMultiplier;
this.boatTypeSpeedMultiplier = boatType.maxSpeedMultiplier;
this.boatTypeTurnStepMultiplier = boatType.turnStep;
this.boatType = boatType;
}
public Double getBoatTypeSpeedMultiplier() {
return boatTypeSpeedMultiplier;
}
public Double getBoatTypeAccelerationMultiplier() {
return boatTypeAccelerationMultiplier;
}
public BoatMeshType getBoatType() {
return boatType;
}
public void setContinuouslyTurning(Boolean continuouslyTurning) {
this.continuouslyTurning = continuouslyTurning;
}
public Double getPowerUpSpeedMultiplier() {
return powerUpSpeedMultiplier;
}
public void setPowerUpSpeedMultiplier(Double powerUpSpeedMultiplier) {
this.powerUpSpeedMultiplier = powerUpSpeedMultiplier;
}
public Integer getPowerUpHandlingMultiplier() {
return powerUpHandlingMultiplier;
}
public void setPowerUpHandlingMultiplier(Integer powerUpHandlingMultiplier) {
this.powerUpHandlingMultiplier = powerUpHandlingMultiplier;
}
}
@@ -1,6 +1,6 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
@@ -10,13 +10,13 @@ public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private List<Mark> marks;
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.marks.addAll(marks);
this.marks = Collections.unmodifiableList(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
@@ -32,4 +32,10 @@ public class Corner {
public Integer getZoneSize() {
return zoneSize;
}
@Override
public String toString() {
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
+ rounding +", zoneSize=" + zoneSize + "}";
}
}
+20 -79
View File
@@ -1,32 +1,33 @@
package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.RoundingSide;
import seng302.model.stream.xml.generator.Race;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
*/
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private List<CompoundMark> orderedUniqueCompoundMarks;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
private List<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
public MarkOrder(RaceXMLData raceXMLData){
raceMarkOrder = new ArrayList<>();
for (Corner corner : raceXMLData.getMarkSequence()){
CompoundMark compoundMark = raceXMLData.getCompoundMarks().get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
raceMarkOrder.add(compoundMark);
}
orderedUniqueCompoundMarks = new ArrayList<>(raceXMLData.getCompoundMarks().values());
}
/**
@@ -38,10 +39,13 @@ public class MarkOrder {
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
public List<CompoundMark> getOrderedUniqueCompoundMarks() {
return orderedUniqueCompoundMarks;
}
/**
* @param seqID The seqID of the current mark the boat is heading to
* @return A Boolean indicating if this coming mark is the last one (finish line)
@@ -72,67 +76,4 @@ public class MarkOrder {
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
public Set<Mark> getAllMarks(){
return Collections.unmodifiableSet(allMarks);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
allMarks = new HashSet<>();
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xml)));
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.error("Failed to read generated race XML");
return null;
}
RaceXMLData data = XMLParser.parseRace(doc);
if (data != null){
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
}
return course;
}
return null;
}
/**
* Load the raceXML and mark order
*/
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
generator.setRace(new Race());
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -20,7 +20,9 @@ public enum PacketType {
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE,
RACE_CUSTOMIZATION_REQUEST,
RACE_CUSTOMIZATION_RESPONSE;
RACE_CUSTOMIZATION_RESPONSE,
SERVER_REGISTRATION, ROOM_CODE_REQUEST, LOBBY_REQUEST;
public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){
@@ -65,6 +67,10 @@ public enum PacketType {
return RACE_CUSTOMIZATION_REQUEST;
case 104:
return RACE_CUSTOMIZATION_RESPONSE;
case 201:
return SERVER_REGISTRATION;
case 202:
return ROOM_CODE_REQUEST;
default:
}
return OTHER;
@@ -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;
@@ -1,53 +0,0 @@
package seng302.model.stream.xml.generator;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.ServerYacht;
/**
* A Race object that can be parsed into XML
*/
public class Race {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
public Race(){
yachts = new ArrayList<>();
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
*/
public List<ServerYacht> getBoats() {
return Collections.unmodifiableList(yachts);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
*/
public void setRaceStartDelay(Integer seconds){
startTime = startTime.plusMinutes(seconds);
}
/**
* Get the time the race starts
* @return The time the race starts
*/
public String getRaceStartTime(){
return startTime.toString();
}
}
@@ -0,0 +1,98 @@
package seng302.model.stream.xml.generator;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import seng302.model.Limit;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.token.Token;
/**
* A Race object that can be parsed into XML
*/
public class RaceXMLTemplate {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
private List<Token> tokens;
private List<Corner> roundings;
private List<Limit> courseLimit;
private List<CompoundMark> course;
private Integer maxPlayers;
private Boolean tokensEnabled;
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens, List<Corner> roundings,
List<Limit> limit, List<CompoundMark> course, Integer maxPlayers, Boolean tokensEnabled) {
this.yachts = yachts;
this.tokens = tokens;
this.roundings = roundings;
this.courseLimit = limit;
this.course = course;
startTime = LocalDateTime.now();
this.maxPlayers = maxPlayers;
this.tokensEnabled = tokensEnabled;
}
/**
* Get a list of boats in the race
* @return A List of boats
*/
public List<ServerYacht> getBoats() {
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);
}
public List<CompoundMark> getCompoundMarks() {
return Collections.unmodifiableList(course);
}
public List<Limit> getCourseLimit() {
return Collections.unmodifiableList(courseLimit);
}
public List<Corner> getRoundings() {
return Collections.unmodifiableList(roundings);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
*/
public void setRaceStartDelay(Integer seconds){
startTime = startTime.plusMinutes(seconds);
}
/**
* Get the time the race starts
* @return The time the race starts
*/
public String getRaceStartTime(){
return startTime.toString();
}
public void setBoats(List<ServerYacht> boats) {
yachts = boats;
}
public void setTokens(List<Token> tokens) {
this.tokens = tokens;
}
public String getTokensEnabled() {
return tokensEnabled.toString();
}
public String getMaxPlayers() {
return maxPlayers.toString();
}
}
@@ -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,62 @@
package seng302.model.token;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
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;
private Random random = new Random();
//Constructor for creating a specific type client side
public Token(TokenType tokenType, double lat, double lng) {
super(lat, lng);
this.tokenType = tokenType;
}
//Making random type server side
public Token(double lat, double lng) {
super(lat, lng);
assignRandomType();
}
//Making random type server side
public Token(GeoPoint geoPoint) {
super(geoPoint.getLat(), geoPoint.getLng());
assignRandomType();
}
public TokenType getTokenType() {
return tokenType;
}
/**
* Assigns a random type to the token (including the random type token)
*/
public void assignRandomType() {
tokenType = TokenType.values()[random.nextInt(TokenType.values().length)];
}
/**
* Assigns a random, concrete type to the token (cannot be the random type)
*/
public void realiseRandom() {
List<TokenType> tokenTypeList = new ArrayList<>(Arrays.asList(TokenType.values()));
tokenTypeList.remove(TokenType.RANDOM);
tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size()));
}
/**
* Exists for testing purposes only
*/
public void assignType(TokenType tokenType) {
this.tokenType = tokenType;
}
}
@@ -0,0 +1,36 @@
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, "Boost", 10_000),
HANDLING(1, "Handling", 10_000),
BUMPER(2, "Bumper", 10_000),
WIND_WALKER(3, "Wind Walker", 10_000),
RANDOM(4, "Random", 10_000);
private int value;
private String name;
private int timeout;
TokenType(int value, String name, int timeout) {
this.value = value;
this.name = name;
this.timeout = timeout;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
public int getTimeout() {
return timeout;
}
}
@@ -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;
/**
@@ -0,0 +1,64 @@
package seng302.utilities;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import seng302.model.GeoPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.token.Token;
/**
* A class for generating and spawning tokens in random locations
* Created by wmu16 on 27/09/17.
*/
public class RandomSpawn {
private static final Integer DEGREES_IN_CIRCLE = 360;
private HashMap<GeoPoint, Double> spawnRadii;
private Object[] spawnCentres;
private Random random;
/**
* @param markOrder this must be the ORDERED list of marks. Better yet UNIQUE to avoid over
* computation
*/
public RandomSpawn(List<CompoundMark> markOrder) {
this.spawnRadii = new HashMap<>();
random = new Random();
spawnRadii = generateSpawnRadii(markOrder);
spawnCentres = spawnRadii.keySet().toArray();
}
private HashMap<GeoPoint, Double> generateSpawnRadii(List<CompoundMark> markOrder) {
HashMap<GeoPoint, Double> spawnRadii = new HashMap<>();
for (int i = 0; i < markOrder.size() - 1; i++) {
GeoPoint spawnCentre = GeoUtility.getDirtyMidPoint(
markOrder.get(i).getMidPoint(),
markOrder.get(i + 1).getMidPoint());
Double distance = GeoUtility.getDistance(spawnCentre, markOrder.get(i).getMidPoint());
spawnRadii.put(spawnCentre, distance);
}
return spawnRadii;
}
/**
* @return A random token type at a random location in a random radii of the set of possible
* radii
*/
public Token getRandomToken() {
GeoPoint randomSpawnCentre = (GeoPoint) spawnCentres[random.nextInt(spawnCentres.length)];
Double spawnRadius = spawnRadii.get(randomSpawnCentre);
Double randomDistance = spawnRadius * random.nextDouble();
Double randomAngle = random.nextDouble() * DEGREES_IN_CIRCLE;
GeoPoint randomLocation = GeoUtility
.getGeoCoordinate(randomSpawnCentre, randomAngle, randomDistance);
return new Token(randomLocation);
}
}
+205
View File
@@ -0,0 +1,205 @@
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 MediaPlayer crashSoundPlayer;
private static boolean hoverInitialized = false;
private static boolean crashInitialized = 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();
musicPlayer.setMute(musicMuted);
if (soundEffect != null) {
soundEffect.stop();
}
}
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) {
if (!crashInitialized) {
Media pickupSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
.toString());
crashSoundPlayer = new MediaPlayer(pickupSound);
crashInitialized = true;
}
if (crashSoundPlayer != null) {
crashSoundPlayer.stop();
}
crashSoundPlayer.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 hoverSound = new Media(
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
.toString());
hoverSoundPlayer = new MediaPlayer(hoverSound);
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.
*
@@ -171,7 +136,6 @@ public class StreamParser {
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
@@ -255,15 +219,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 +356,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,20 @@ 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;
}
public RaceXMLTemplate getRace() {
return race;
}
}
+248 -14
View File
@@ -1,27 +1,44 @@
package seng302.utilities;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javafx.scene.paint.Color;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
/**
* Utilities for parsing XML documents
*/
public class XMLParser {
private static final int MAX_PLAYERS = 8;
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
@@ -32,7 +49,7 @@ public class XMLParser {
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
return Integer.parseInt(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -64,7 +81,7 @@ public class XMLParser {
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
return Double.parseDouble(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -136,19 +153,29 @@ public class XMLParser {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
// Boat boat = new Boat(currentBoat);
BoatMeshType boatMeshType;
try {
boatMeshType = BoatMeshType.valueOf(XMLParser.getNodeAttributeString(currentBoat, "Type"));
} catch (IllegalArgumentException e){
boatMeshType = BoatMeshType.DINGHY;
}
Color color;
try {
color = Color.web(getNodeAttributeString(currentBoat, "Color"));
} catch (NullPointerException npe) {
color = Colors.getColor(new Random().nextInt(8));
}
ClientYacht yacht = new ClientYacht(
XMLParser.getNodeAttributeString(currentBoat, "Type"),
boatMeshType,
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
XMLParser.getNodeAttributeString(currentBoat, "Country"));
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
if (yacht.getBoatType().equals("Yacht")) {
yacht.setColour(color);
competingBoats.put(yacht.getSourceId(), yacht);
}
}
}
return competingBoats;
}
@@ -172,6 +199,36 @@ public class XMLParser {
);
}
public static Boolean tokensEnabled(Document doc) {
Element docEle = doc.getDocumentElement();
try {
NamedNodeMap namedNodeMap = docEle.getElementsByTagName("Tokens").item(0).getAttributes();
Node node = namedNodeMap.getNamedItem("Enabled");
if (node != null) {
return Boolean.parseBoolean(node.getNodeValue());
}
} catch (NullPointerException npe) {
npe.printStackTrace();
return false;
}
return false;
}
public static Integer getMaxPlayers(Document doc) {
Element docEle = doc.getDocumentElement();
try {
NamedNodeMap namedNodeMap = docEle.getElementsByTagName("Participants").item(0).getAttributes();
Node node = namedNodeMap.getNamedItem("MaxPlayers");
if (node != null) {
return Integer.parseInt(node.getNodeValue());
}
} catch (NullPointerException npe) {
npe.printStackTrace();
return MAX_PLAYERS;
}
return MAX_PLAYERS;
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
@@ -181,13 +238,36 @@ public class XMLParser {
public static RaceXMLData parseRace(Document doc) {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractParticipantIDs(docEle),
extractTokens(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
extractCourseLimit(docEle)
);
}
/**
* Extracts token data
*/
private static List<Token> extractTokens(Element docEle) {
List<Token> tokens = new ArrayList<>();
try {
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));
}
}
} catch (NullPointerException npe) {
return new ArrayList<>();
}
return tokens;
}
/**
* Extracts course limit data
*/
@@ -197,13 +277,11 @@ public class XMLParser {
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(
new Limit(
courseLimit.add(new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
)
);
));
}
}
return courseLimit;
@@ -235,7 +313,7 @@ public class XMLParser {
/**
* Extracts course participants data
*/
private static List<Integer> extractParticpantIDs (Element docEle) {
private static List<Integer> extractParticipantIDs(Element docEle) {
List<Integer> boatIDs = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
@@ -257,10 +335,11 @@ public class XMLParser {
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
String name = XMLParser.getNodeAttributeString(cMarkNode, "Name");
name = (name == null || name.equals("")) ? "Mark " + i+1: name;
cMark = new CompoundMark(
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cMarkNode, "Name"),
createMarks(cMarkNode)
name, createMarks(cMarkNode)
);
allMarks.add(cMark);
}
@@ -281,14 +360,169 @@ public class XMLParser {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
seqID = (seqID == null) ? i+1 : seqID;
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
sourceID = (sourceID == null) ? i+1 : sourceID;
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
markName = (markName == null || markName.equals("")) ? cMarkName + " " + i+1: markName;
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
subMarks.add(mark);
}
}
return subMarks;
}
/**
* This ungodly combination of existing methods and code blocks parses a race definition file.
* Look upon it and despair.
* @param url The input file path
* @param serverName the name of the server
* @param repetitions the repetitions of a segment of the race def file.
* @param maxPlayers max number of players. uses the default race max if null or greater than the actual max.
* @param tokensEnabled if tokens are enabled
* @return a pair which contains regatta string, race string as key, value pair.
*/
public static Pair<RegattaXMLTemplate, RaceXMLTemplate> parseRaceDef(
String url, String serverName, Integer repetitions, Integer maxPlayers, Boolean tokensEnabled
) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(XMLParser.class.getResourceAsStream(url));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
Element docEle = doc.getDocumentElement();
RegattaXMLTemplate regattaXMLTemplate = new RegattaXMLTemplate(
serverName, XMLParser.getElementString(docEle, "CourseName"),
XMLParser.getElementDouble(docEle, "CentralLat"),
XMLParser.getElementDouble(docEle, "CentralLng")
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaXMLTemplate);
if (maxPlayers == null) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
} else if (maxPlayers > XMLParser.getElementInt(docEle, "MaxPlayers")) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
}
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(
new ArrayList<>(), new ArrayList<>(),
XMLParser.extractMarkOrderRaceDef(docEle, repetitions),
XMLParser.extractCourseLimitRaceDef(docEle),
XMLParser.extractCompoundMarksRaceDef(docEle),
maxPlayers, tokensEnabled
);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new Pair<>(regattaXMLTemplate, raceXMLTemplate);
}
private static List<Corner> extractMarkOrderRaceDef(Element docEle, int repitions){
List<Corner> compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
int seqId = 1;
final int zoneSize = 3;
for (int i=0; i<cornerList.getLength(); i++) {
Node segment = cornerList.item(i);
if (segment.getNodeName().equals("OpeningSegment") ||
segment.getNodeName().equals("ClosingSegment")) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
} else if (segment.getNodeName().equals("RepeatingSegment")) {
for (int k = 0; k < repitions; k++) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
}
}
}
return compoundMarkSequence;
}
/**
* Parses a segment of the course adding new Corners to the given list.
* @param segment Segment to parse
* @param seqID initial sequence ID
* @param course course to add corners to
* @return the last sequence id.
*/
private static int parseCourseSegment(Node segment, int seqID, List<Corner> course) {
NodeList segmentList = segment.getChildNodes();
for (int j = 0; j < segmentList.getLength(); j++) {
Node corner = segmentList.item(j);
if (corner.getNodeName().equals("Corner")) {
String rounding = XMLParser.getNodeAttributeString(corner, "Rounding");
rounding = //Converting "P" to "Port" and "S" to "Stbd"
rounding.equals("P") ? "Port" :
rounding.equals("S") ? "Stbd" : rounding;
course.add(new Corner(
seqID++, XMLParser.getNodeAttributeInt(corner, "CompoundMarkID"),
rounding, 3
));
}
}
return seqID;
}
private static List<Limit> extractCourseLimitRaceDef(Element docEle) {
List<Limit> courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
int seqId = 1;
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(new Limit(
seqId++, XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lng")
));
}
}
return courseLimit;
}
private static List<CompoundMark> extractCompoundMarksRaceDef(Element docEle){
List<CompoundMark> allMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Marks").item(0).getChildNodes();
CompoundMark cMark;
int markCount = 200;
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
Integer id = XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID");
List<Mark> subMarks = createMarksRaceDef(cMarkNode, markCount,"Mark " + id);
markCount += subMarks.size();
allMarks.add(new CompoundMark(id, "Mark " + id, subMarks));
}
}
return allMarks;
}
private static List<Mark> createMarksRaceDef(Node compoundMark, int markCount, String markName) {
List<Mark> subMarks = new ArrayList<>();
NodeList childMarks = compoundMark.getChildNodes();
int seqID = 1;
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "Lat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "Lng");
Mark mark = new Mark(markName + " subMark " + seqID, seqID, targetLat, targetLng, markCount++);
subMarks.add(mark);
seqID += 1;
}
}
return subMarks;
}
}
@@ -1,5 +1,14 @@
package seng302.visualiser;
import javafx.application.Platform;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.XMLParser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -14,18 +23,27 @@ import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.util.Pair;
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;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.ViewManager;
/**
* A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -33,7 +51,7 @@ import seng302.model.stream.packets.StreamPacket;
*/
public class ClientToServerThread implements Runnable {
private boolean isStarted = false;
/**
* Functional interface for receiving packets from client socket.
@@ -45,7 +63,12 @@ public class ClientToServerThread implements Runnable {
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
void notifyDisconnection(String message);
}
@FunctionalInterface
public interface ConnectionErrorListener {
void notifyConnectionError(String message);
}
private class ByteReadException extends Exception {
@@ -57,6 +80,7 @@ public class ClientToServerThread implements Runnable {
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ClientSocketListener> listeners = new ArrayList<>();
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
private ConnectionErrorListener connectionErrorListener = null;
private Thread thread;
private Socket socket;
@@ -69,7 +93,7 @@ public class ClientToServerThread implements Runnable {
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
public static final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
@@ -95,7 +119,7 @@ public class ClientToServerThread implements Runnable {
sendRegistrationRequest();
thread = new Thread(this);
thread = new Thread(this, "ClientToServer");
thread.start();
}
@@ -104,6 +128,8 @@ public class ClientToServerThread implements Runnable {
* variable is false.
*/
public void run() {
isStarted = true;
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
@@ -134,10 +160,12 @@ public class ClientToServerThread implements Runnable {
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
synchronized (this) {
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
}
}
} else {
logger.warn("Packet has been dropped", 1);
}
@@ -151,6 +179,11 @@ public class ClientToServerThread implements Runnable {
logger.warn("Closed connection to server", 1);
notifyDisconnectListeners("Connection to server was terminated");
closeSocket();
Platform.runLater(() -> {
ViewManager.getInstance().showErrorSnackBar("Server rejected connection.");
ViewManager.getInstance().goToStartView();
});
}
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
@@ -167,16 +200,22 @@ public class ClientToServerThread implements Runnable {
private void notifyDisconnectListeners (String message) {
if (socketOpen) {
for (DisconnectedFromHostListener listener : disconnectionListeners) {
listener.notifYDisconnection(message);
listener.notifyDisconnection(message);
}
}
}
private void handleConnectionError(String message){
if (connectionErrorListener != null){
connectionErrorListener.notifyConnectionError(message);
}
}
/**
* Sends a request to the server asking for a source ID
*/
private void sendRegistrationRequest() {
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER, clientId);
try {
os.write(requestMessage.getBuffer());
@@ -192,9 +231,8 @@ public class ClientToServerThread implements Runnable {
* @param packet The registration requests packet
*/
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 4));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
@@ -212,8 +250,10 @@ public class ClientToServerThread implements Runnable {
else{
alertErrorText = "Could not connect to server";
}
handleConnectionError("Server no longer available.");
notifyDisconnectListeners(alertErrorText);
closeSocket();
System.out.println();
}
/**
@@ -244,7 +284,7 @@ public class ClientToServerThread implements Runnable {
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND, clientId));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
@@ -257,14 +297,14 @@ public class ClientToServerThread implements Runnable {
new TimerTask() {
@Override
public void run() {
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND, clientId));
}
}, 0, PACKET_SENDING_INTERVAL_MS
);
}
break;
default:
sendBoatActionMessage(new BoatActionMessage(actionType));
sendBoatActionMessage(new BoatActionMessage(actionType, clientId));
break;
}
}
@@ -283,9 +323,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 +342,7 @@ public class ClientToServerThread implements Runnable {
}
}
private void closeSocket() {
public void closeSocket() {
try {
socket.close();
socketOpen = false;
@@ -312,20 +360,32 @@ public class ClientToServerThread implements Runnable {
}
public void addStreamObserver (ClientSocketListener streamListener) {
synchronized (this){
listeners.add(streamListener);
}
}
public void removeStreamObserver (ClientSocketListener streamListener) {
listeners.remove(streamListener);
}
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
synchronized (this){
disconnectionListeners.add(listener);
}
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
synchronized (this){
disconnectionListeners.remove(listener);
}
}
public void setConnectionErrorListener(ConnectionErrorListener listener){
synchronized (this){
connectionErrorListener = listener;
}
}
private int readByte() throws ByteReadException {
int currentByte = -1;
@@ -339,8 +399,9 @@ public class ClientToServerThread implements Runnable {
}
if (currentByte == -1) {
notifyDisconnectListeners("Cannot read from server.");
closeSocket();
logger.warn("InputStream reach end of stream", 1);
handleConnectionError("Could not connect to server. Server is no longer available.");
closeSocket();
}
return currentByte;
}
@@ -359,7 +420,32 @@ public class ClientToServerThread implements Runnable {
}
}
int getClientId () {
public int getClientId () {
return clientId;
}
public void sendXML(String path, String serverName, Integer legRepeats, Integer maxPlayers, Boolean tokensEnabled) {
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, serverName, legRepeats, maxPlayers, tokensEnabled
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaRace.getKey());
xmlGenerator.setRaceTemplate(regattaRace.getValue());
String regatta = xmlGenerator.getRegattaAsXml();
String race = xmlGenerator.getRaceAsXml();
sendByteBuffer(
new XMLMessage(
regatta, XMLMessageSubType.REGATTA, regatta.length()
).getBuffer()
);
sendByteBuffer(
new XMLMessage(
race, XMLMessageSubType.RACE, race.length()
).getBuffer()
);
}
public boolean hasStarted() {
return isStarted;
}
}
+280 -144
View File
@@ -1,24 +1,35 @@
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 java.util.Timer;
import java.util.TimerTask;
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.scene.paint.Color;
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.GameKeyBind;
import seng302.model.KeyAction;
import seng302.model.RaceState;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.parser.MarkRoundingData;
@@ -28,12 +39,21 @@ 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.model.token.TokenType;
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;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.*;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
@@ -52,6 +72,11 @@ public class GameClient {
private RaceXMLData courseData;
private RaceState raceState = new RaceState();
private LobbyController lobbyController;
private RaceViewController raceViewController;
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
private GameKeyBind gameKeyBind; // all the key binding setting.
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
@@ -62,6 +87,7 @@ public class GameClient {
*/
public GameClient(Pane holder) {
this.holderPane = holder;
this.gameKeyBind = GameKeyBind.getInstance();
}
/**
@@ -74,95 +100,117 @@ public class GameClient {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
showConnectionError(cause);
Platform.runLater(this::loadStartScreen);
tearDownConnection();
});
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());
ViewManager.getInstance().setPlayerList(clientLobbyList);
while (regattaData == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
else{
lobbyController.setTitle(ipAddress);
lobbyController.setCourseName("");
}
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = lobbyController;
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
getServerThread().setConnectionErrorListener((eMessage) -> {
ViewManager.getInstance().showErrorSnackBar(eMessage);
//destroyClientToServerThread();
});
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
Platform.runLater(this::loadStartScreen);
ViewManager.getInstance().showErrorSnackBar("There are no servers currently available.");
}
}
private void destroyClientToServerThread() {
socketThread.closeSocket();
socketThread = null;
}
/**
* Connect to a game as the host at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public void runAsHost(String ipAddress, Integer portNumber) {
public ServerDescription runAsHost(
String ipAddress, Integer portNumber, String serverName, Integer maxPlayers, String race,
Integer numLegs, Boolean tokensEnabled
) {
XMLGenerator.setDefaultRaceName(serverName);
server = new MainServerThread();
while (!server.hasStarted()){
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("");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
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) {
try {
startClientToServerThread(ipAddress, 4942);
} catch (IOException e) {
showConnectionError("Cannot connect to server as host");
Platform.runLater(this::loadStartScreen);
}
// Wait for C2S thread
while (!socketThread.hasStarted()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void loadStartScreen() {
socketThread.sendXML(race, serverName, numLegs, maxPlayers, tokensEnabled);
int triesLeft = 15;
while (regattaData == null && triesLeft > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
triesLeft--;
}
if (triesLeft <= 0){
showConnectionError("Could not launch server");
return null;
}
this.lobbyController = ViewManager.getInstance().goToLobby(false);
ViewManager.getInstance().setPlayerList(clientLobbyList);
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(), ipAddress, 4942);
}
private void tearDownConnection() {
socketThread.setSocketToClose();
if (server != null) {
server.terminate();
server = null;
}
FXMLLoader fxmlLoader = new FXMLLoader(
getClass().getResource("/views/StartScreenView.fxml"));
try {
holderPane.getChildren().clear();
holderPane.getChildren().add(fxmlLoader.load());
} catch (IOException e) {
showConnectionError("JavaFX crashed. Please restart the app");
}
}
private void showConnectionError (String message) {
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
alert.setHeaderText("Connection Error");
alert.setContentText(message);
alert.showAndWait();
PopupDialogController controller = ViewManager.getInstance().showPopupDialog();
controller.setHeader("Oops");
controller.setContent(message);
controller.setOptionButtonText("GO HOME");
controller
.setOptionButtonEventHandler(event -> ViewManager.getInstance().goToStartView());
});
}
@@ -171,52 +219,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 +249,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 +269,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 +288,33 @@ public class GameClient {
break;
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
processYachtEvent(StreamParser.extractYachtEventCode(packet));
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.showView();
raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> {
if (isPressed) {
formatAndSendChatMessage(raceView.readChatInput());
}
});
sendToggleTurningModePacket(); // notify the server about player's steering mode
}
}
@@ -320,7 +348,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 +362,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 +380,13 @@ public class GameClient {
}
if (raceFinished) {
raceViewController.showFinishDialog(finishedBoats);
Sounds.playFinishSound();
close();
loadFinishScreenView();
ViewManager.getInstance().getGameClient().stopGame();
//loadFinishScreenView();
}
raceState.setRaceFinished();
}
}
@@ -367,35 +401,46 @@ 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) {
switch (e.getCode()) {
case SPACE: // align with vmg
socketThread.sendBoatAction(BoatAction.VMG); break;
case PAGE_UP: // upwind
socketThread.sendBoatAction(BoatAction.UPWIND); break;
case PAGE_DOWN: // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
case ENTER: // tack/gybe
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
public void keyPressed(KeyEvent e) {
if (raceView.isChatInputFocused()) {
if (e.getCode() == KeyCode.ENTER) {
formatAndSendChatMessage(raceView.readChatInput());
}
return;
}
if (gameKeyBind.getKeyCode(KeyAction.VMG) == e.getCode()) { // align with vmg
socketThread.sendBoatAction(BoatAction.VMG);
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()) { // upwind
socketThread.sendBoatAction(BoatAction.UPWIND);
} else if (gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) { // downwind
socketThread.sendBoatAction(BoatAction.DOWNWIND);
} else if (gameKeyBind.getKeyCode(KeyAction.TACK_GYBE) == e.getCode()) { // tack/gybe
// if chat box is active take whatever is in there and send it to server
socketThread.sendBoatAction(BoatAction.TACK_GYBE);
}
}
private void keyReleased(KeyEvent e) {
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
public void keyReleased(KeyEvent e) {
if (raceView.isChatInputFocused()) {
return;
}
if (gameKeyBind.getKeyCode(KeyAction.SAILS_STATE) == e.getCode()) { // sails in/sails out
if (allBoatsMap.get(socketThread.getClientId()).getSailIn()) {
socketThread.sendBoatAction(BoatAction.SAILS_OUT);
} else {
socketThread.sendBoatAction(BoatAction.SAILS_IN);
}
allBoatsMap.get(socketThread.getClientId()).toggleSail();
break;
case PAGE_UP:
case PAGE_DOWN:
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()
|| gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) {
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING);
}
}
@@ -403,17 +448,108 @@ public class GameClient {
return courseData;
}
/**
* Appropriately displays the event client side given the YachtEventCode (collision / token..)
*
* @param yachtEventData The YachtEvent data packet
*/
private void processYachtEvent(YachtEventData yachtEventData) {
ClientYacht thisYacht = allBoatsMap.get(yachtEventData.getSubjectId().intValue());
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
showCollisionAlert(thisYacht);
} else if (yachtEventData.getEventId() == YachtEventType.POWER_DOWN.getCode()) {
thisYacht.powerDown();
Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound
} else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) {
showDisableAlert(thisYacht);
} else { //Else all token pickup types
TokenType tokenType = null;
if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) {
tokenType = TokenType.BOOST;
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_BUMPER.getCode()) {
tokenType = TokenType.BUMPER;
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_HANDLING.getCode()) {
tokenType = TokenType.HANDLING;
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_RANDOM.getCode()) {
tokenType = TokenType.RANDOM;
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_WIND_WALKER.getCode()) {
tokenType = TokenType.WIND_WALKER;
}
Sounds.playTokenPickupSound();
thisYacht.setPowerUp(tokenType);
}
}
/**
* Turns a disabled boat black until the bumper affect wears off
*
* @param yacht The yacht to show as disabled
*/
private void showDisableAlert(ClientYacht yacht) {
Color originalColor = yacht.getColour();
yacht.setColour(Color.BLACK);
Timer disableTimer = new Timer("Disable Timer");
disableTimer.schedule(new TimerTask() {
@Override
public void run() {
yacht.setColour(originalColor);
}
}, GameState.BUMPER_DISABLE_TIME);
}
/**
* 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()
)
private void showCollisionAlert(ClientYacht yacht) {
Sounds.playCrashSound();
raceState.storeCollision(yacht);
}
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;
}
public void sendToggleTurningModePacket() {
if (socketThread != null) {
if (gameKeyBind.isContinuouslyTurning()) {
socketThread.sendBoatAction(BoatAction.CONTINUOUSLY_TURNING);
} else {
socketThread.sendBoatAction(BoatAction.DEFAULT_TURNING);
}
}
}
}
+18 -834
View File
@@ -1,847 +1,31 @@
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.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.ScaledPoint;
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;
/**
* Created by cir27 on 20/07/17.
* Abstract class for keeping functionality common between race visualisation.
*/
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 canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
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<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 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();
}
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.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
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<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//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());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
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;
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
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, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
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(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker marker = new Marker(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker);
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 colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker m1, Marker m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* 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<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
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) {
//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))));
}
/**
* 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 =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = 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 = canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = 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 += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
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 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 abstract class GameView {
double canvasWidth, canvasHeight;
ScaledPoint scaledPoint;
List<Limit> borderPoints;
Group gameObjects = new Group();
Group markers = new Group();
Group tokens = new Group();
List<CompoundMark> course = new ArrayList<>();
List<CompoundMark> compoundMarks = new ArrayList<>();
List<Corner> courseOrder = new ArrayList<>();
public abstract Node getAssets();
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
public abstract void updateBorder(List<Limit> border);
}
@@ -0,0 +1,564 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
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 org.fxyz3d.scene.Skybox;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.GameKeyBind;
import seng302.model.KeyAction;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
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.cameras.ChaseCamera;
import seng302.visualiser.cameras.IsometricCamera;
import seng302.visualiser.cameras.RaceCamera;
import seng302.visualiser.cameras.TopDownCamera;
import seng302.visualiser.controllers.ViewManager;
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.Model;
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 extends GameView{
private final double FOV = 60;
private final double DEFAULT_CAMERA_X = 0;
private final double DEFAULT_CAMERA_Y = 100;
private Group root3D;
private SubScene view;
private Group gameObjects;
private Group raceBorder = new Group();
// Cameras
private PerspectiveCamera isometricCam;
private PerspectiveCamera topDownCam;
private PerspectiveCamera chaseCam;
/* 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 Map<Mark, Marker3D> markerObjects;
private BoatObject playerBoat;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private List<Node> mapTokens;
private AnimationTimer playerBoatAnimationTimer;
private Group trail = new Group();
private Double windDir;
private Skybox skybox;
public GameView3D () {
isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y);
topDownCam = new TopDownCamera();
chaseCam = new ChaseCamera();
canvasWidth = canvasHeight = 300;
for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) {
pc.setFarClip(100000);
pc.setNearClip(0.1);
pc.setFieldOfView(FOV);
}
gameObjects = new Group();
root3D = new Group(isometricCam, gameObjects);
view = new SubScene(
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
);
view.setCamera(isometricCam);
skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, isometricCam);
skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
Model land = ModelFactory.importModel(ModelType.LAND_SMOOTH);
land.getAssets().getTransforms().add(new Rotate(90, Rotate.X_AXIS));
gameObjects.getChildren().addAll(
raceBorder, trail, markers, tokens, skybox, land.getAssets()
);
view.sceneProperty().addListener((obs, old, scene) -> {
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
}
});
}
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
compoundMarks = newCourse;
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) {
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = scaledPoint.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 = scaledPoint.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 = scaledPoint.findScaledXY(m1);
Point2D m2Location = scaledPoint.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)
);
}
}
public void cameraMovement(KeyEvent event) {
GameKeyBind keyBinds = GameKeyBind.getInstance();
KeyAction keyPressed = keyBinds.getKeyAction(event.getCode());
if (keyPressed != null) {
switch (keyPressed) {
case ZOOM_IN:
((RaceCamera) view.getCamera()).zoomIn();
break;
case ZOOM_OUT:
((RaceCamera) view.getCamera()).zoomOut();
break;
case FORWARD:
((RaceCamera) view.getCamera()).panUp();
break;
case BACKWARD:
((RaceCamera) view.getCamera()).panDown();
break;
case LEFT:
((RaceCamera) view.getCamera()).panLeft();
break;
case RIGHT:
((RaceCamera) view.getCamera()).panRight();
break;
case VIEW:
toggleCamera();
break;
}
}
}
private void toggleCamera() {
Camera currCamera = view.getCamera();
if (currCamera.equals(isometricCam)) {
view.setCamera(topDownCam);
} else if (currCamera.equals(topDownCam)) {
view.setCamera(chaseCam);
} else {
view.setCamera(isometricCam);
}
}
/**
* 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(clientYacht.getBoatType());
newBoat.setFill(colour);
boatObjects.put(clientYacht, newBoat);
wakesGroup.getChildren().add(newBoat.getWake());
wakes.add(newBoat.getWake());
boatObjectGroup.getChildren().add(newBoat);
clientYacht.addLocationListener(this::updateBoatLocation);
clientYacht.addColorChangeListener(this::updateBoatColor);
if (clientYacht.getSourceId().equals(
ViewManager.getInstance().getGameClient().getServerThread().getClientId())) {
((ChaseCamera) chaseCam).setPlayerBoat(newBoat);
((TopDownCamera) topDownCam).setPlayerBoat(newBoat);
newBoat.setMarkIndicator(ModelFactory.importSTL("mark_pointer.stl"));
playerBoat = newBoat;
}
}
Platform.runLater(() -> {
ClientYacht playerYacht = ViewManager.getInstance().getGameClient().getAllBoatsMap()
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId());
for (ObservableValue o : Arrays
.asList(playerBoat.layoutXProperty(), playerBoat.layoutXProperty())) {
o.addListener((obs, oldVal, newVal) -> {
List<Mark> marks = course.get(playerYacht.getLegNumber()).getMarks();
Point2D midPoint = new Point2D(0, 0);
if (marks.size() == 1) {
midPoint = scaledPoint.findScaledXY(marks.get(0));
} else if (marks.size() == 2) {
midPoint = (scaledPoint.findScaledXY(marks.get(0)))
.midpoint(scaledPoint.findScaledXY(marks.get(1)));
}
if (midPoint != null) {
playerBoat.updateMarkIndicator(midPoint);
}
});
}
gameObjects.getChildren().addAll(wakes);
gameObjects.getChildren().addAll(boatObjectGroup);
});
}
public Node getAssets () {
return view;
}
/**
* Updates the boatObjects color with that of the clientYachts object. Used in notification from
* a listener on this attribute in clientYacht to re paint the boat mesh
*
* @param clientYacht The yacht to update the colour for
*/
private void updateBoatColor(ClientYacht clientYacht) {
boatObjects.get(clientYacht).setFill(clientYacht.getColour());
}
private void updateBoatLocation(ClientYacht boat, Double lat, Double lon, Double heading,
Boolean sailIn, Double velocity) {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
}
/**
* 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;
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true
);
}
List<Node> boundaryAssets = new ArrayList<>();
Point2D lastLocation = scaledPoint.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 = scaledPoint.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 = scaledPoint.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 = scaledPoint.findScaledXY(token.getLat(), token.getLng());
ModelType modelType = null;
switch (token.getTokenType()) {
case BOOST:
modelType = ModelType.VELOCITY_PICKUP;
break;
case HANDLING:
modelType = ModelType.HANDLING_PICKUP;
break;
case BUMPER:
modelType = ModelType.BUMPER_PICKUP;
break;
case RANDOM:
modelType = ModelType.RANDOM_PICKUP;
break;
case WIND_WALKER:
modelType = ModelType.WIND_WALKER_PICKUP;
break;
}
Node tokenObject = ModelFactory.importModel(modelType).getAssets();
tokenObject.setLayoutX(location.getX());
tokenObject.setLayoutY(location.getY());
mapTokens.add(tokenObject);
}
Platform.runLater(() -> {
tokens.getChildren().setAll(mapTokens);
});
}
public void setBoatAsPlayer (ClientYacht playerYacht) {
playerYacht.toggleSail();
playerBoatAnimationTimer = new AnimationTimer() {
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
@Override
public void handle(long now) {
Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation());
if (Math.abs(lastLocation.distance(location)) > 2) {
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
location = scaledPoint.findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY(), 0),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,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,126 @@
package seng302.visualiser;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.scene.Node;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* Makes maps from map definition xml files.
*/
public class MapMaker {
private List<MapPreview> mapPreviews = new ArrayList<>();
private List<RaceXMLData> races = new ArrayList<>();
private List<RegattaXMLData> regattas = new ArrayList<>();
private List<String> filePaths = new ArrayList<>();
private List<Integer> maxPlayers = new ArrayList<>();
private static MapMaker instance;
private int index = 0;
private XMLGenerator xmlGenerator = new XMLGenerator();
private List<String> maps = new ArrayList<>(Arrays.asList("default.xml", "horseshoe.xml"));
public static MapMaker getInstance() {
if (instance == null) {
instance = new MapMaker();
}
return instance;
}
private MapMaker() {
for (String mapPath : maps){
String path = ("/maps/" + mapPath);
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, "", 1, null, false
);
RegattaXMLTemplate regattaTemplate = regattaRace.getKey();
filePaths.add(path);
regattas.add(new RegattaXMLData(
regattaTemplate.getRegattaId(),
regattaTemplate.getName(),
regattaTemplate.getCourseName(),
regattaTemplate.getLatitude(),
regattaTemplate.getLongitude(),
regattaTemplate.getUtcOffset()
));
RaceXMLTemplate raceTemplate = regattaRace.getValue();
xmlGenerator.setRaceTemplate(raceTemplate);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlGenerator.getRaceAsXml())));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
RaceXMLData race = XMLParser.parseRace(doc);
maxPlayers.add(XMLParser.getMaxPlayers(doc));
mapPreviews.add(new MapPreview(
new ArrayList<>(race.getCompoundMarks().values()),
race.getMarkSequence(), race.getCourseLimit()
));
races.add(race);
}
}
public void next() {
index += 1;
if (index >= mapPreviews.size()) {
index = 0;
}
}
public void previous() {
index -= 1;
if (index < 0) {
index = mapPreviews.size() - 1;
}
}
public Node getCurrentGameView() {
return mapPreviews.get(index).getAssets();
}
public RaceXMLData getCurrentRace() {
return races.get(index);
}
public RegattaXMLData getCurrentRegatta() {
return regattas.get(index);
}
public String getCurrentRacePath() {
return filePaths.get(index);
}
public Integer getMaxPlayers() {
return maxPlayers.get(index);
}
}
@@ -0,0 +1,283 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.CourseBoundary;
import seng302.visualiser.fxObjects.assets_2D.Gate;
import seng302.visualiser.fxObjects.assets_2D.Marker2D;
/**
* Created by cir27 on 20/07/17.
*/
public class MapPreview extends GameView {
private Polygon raceBorder = new CourseBoundary();
private Map<Mark, Marker2D> markerObjects;
public MapPreview(List<CompoundMark> marks, List<Corner> course, List<Limit> border) {
this.compoundMarks = marks;
this.courseOrder = course;
this.borderPoints = border;
gameObjects.getChildren().addAll(raceBorder, markers, tokens);
gameObjects.parentProperty().addListener((obs, old, parent) -> {
if (parent != null) {
canvasWidth = parent.prefWidth(1);
canvasHeight = parent.prefHeight(1);
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
});
}
@Override
public Node getAssets() {
return gameObjects;
}
public void setSize(double width, double height) {
canvasHeight = height;
canvasWidth = width;
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
/**
* 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.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
if (newCourse.size() == 0) {
return;
}
compoundMarks = newCourse;
markerObjects = new HashMap<>();
courseOrder = sequence;
for (Corner corner : courseOrder) { //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<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
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, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
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(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = scaledPoint.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 colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* 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.
*/
@Override
public void updateBorder(List<Limit> border) {
if (border.size() == 0) {
return;
}
borderPoints = border;
scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false);
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
}
@@ -0,0 +1,142 @@
package seng302.visualiser;
import seng302.gameServer.ServerAdvertiser;
import seng302.gameServer.ServerDescription;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import javax.jmdns.impl.JmDNSImpl;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
/**
* Listens for servers on the local network
*/
public class ServerListener{
private static Integer SERVICE_REFRESH_INTERVAL = 5 * 1000;
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);
/*new Timer().schedule(new TimerTask() {
@Override
public void run() {
refresh();
}
}, 50, SERVICE_REFRESH_INTERVAL);*/
}
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;
}
public void refresh(){
ArrayList<ServerDescription> servers = new ArrayList<>(listener.servers);
for (ServerDescription serverDescription : servers){
if (serverDescription.hasExpired()){
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverDescription.getName());
}
else{
serverDescription.hasBeenRefreshed();
}
}
for (ServerDescription server : servers){
if (server.serverShouldBeRemoved()){
listener.servers.remove(server);
delegate.serverRemoved(new ArrayList<ServerDescription>(listener.servers));
}
}
}
}
@@ -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);
}
@@ -0,0 +1,130 @@
package seng302.visualiser.cameras;
import java.util.Arrays;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
public class ChaseCamera extends PerspectiveCamera implements RaceCamera {
private final Double VERTICAL_PAN_LIMIT = 20.0;
private final Double NEAR_ZOOM_LIMIT = -15.0;
private final Double FAR_ZOOM_LIMIT = -125.0;
private final Double ZOOM_STEP = 2.5;
private final Double PAN_STEP = 2.5;
private ObservableList<Transform> transforms;
private BoatObject playerBoat;
private Double zoomFactor;
private Double horizontalPan;
private Double verticalPan;
public ChaseCamera() {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
this.horizontalPan = 0.0;
this.verticalPan = 0.0;
}
/**
* Sets a player boat object to observe and update the camera with.
*
* @param playerBoat The player boat to be observed.
*/
public void setPlayerBoat(BoatObject playerBoat) {
this.playerBoat = playerBoat;
for (DoubleProperty o : Arrays
.asList(playerBoat.getRotationProperty(), playerBoat.layoutYProperty(),
playerBoat.layoutXProperty())) {
o.addListener((obs, oldVal, newVal) -> repositionCamera());
}
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void repositionCamera() {
transforms.clear();
transforms.addAll(
new Translate(playerBoat.getLayoutX(), playerBoat.getLayoutY(), 0),
new Rotate(playerBoat.getRotationProperty().getValue() + horizontalPan,
new Point3D(0, 0, 1)),
new Rotate(60 + verticalPan, new Point3D(1, 0, 0)),
new Translate(0, 0, zoomFactor)
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
repositionCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT
&& verticalPan + adjustment <= VERTICAL_PAN_LIMIT) {
verticalPan += adjustment;
repositionCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
this.horizontalPan += adjustment;
repositionCamera();
}
@Override
public void zoomIn() {
adjustZoomFactor(ZOOM_STEP);
}
@Override
public void zoomOut() {
adjustZoomFactor(-ZOOM_STEP);
}
@Override
public void panLeft() {
adjustHorizontalPan(-PAN_STEP);
}
@Override
public void panRight() {
adjustHorizontalPan(PAN_STEP);
}
@Override
public void panUp() {
adjustVerticalPan(-PAN_STEP);
}
@Override
public void panDown() {
adjustVerticalPan(PAN_STEP);
}
}
@@ -0,0 +1,113 @@
package seng302.visualiser.cameras;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
public class IsometricCamera extends PerspectiveCamera implements RaceCamera {
private final Double MIN_X = -120.0;
private final Double MAX_X = 125.0;
private final Double MIN_Y = 40.0;
private final Double MAX_Y = 170.0;
private final Double PAN_LIMIT = 160.0;
private final Double NEAR_ZOOM_LIMIT = -50.0;
private final Double FAR_ZOOM_LIMIT = -160.0;
private Double horizontalPan;
private Double verticalPan;
private Double zoomFactor;
private ObservableList<Transform> transforms;
public IsometricCamera(Double cameraStartX, Double cameraStartY) {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
horizontalPan = cameraStartX;
verticalPan = cameraStartY;
updateCamera();
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void updateCamera() {
transforms.clear();
transforms.addAll(
new Translate(horizontalPan, verticalPan, zoomFactor),
new Rotate(30, new Point3D(1, 0, 0))
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
*
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
updateCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= MIN_Y && verticalPan + adjustment <= MAX_Y) {
verticalPan += adjustment;
updateCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
if (horizontalPan + adjustment >= MIN_X && horizontalPan + adjustment <= MIN_Y) {
this.horizontalPan += adjustment;
updateCamera();
}
}
@Override
public void zoomIn() {
adjustZoomFactor(-2.5);
}
@Override
public void zoomOut() {
adjustZoomFactor(2.5);
}
@Override
public void panLeft() {
adjustHorizontalPan(-2.5);
}
@Override
public void panRight() {
adjustHorizontalPan(2.5);
}
@Override
public void panUp() {
adjustVerticalPan(-2.5);
}
@Override
public void panDown() {
adjustVerticalPan(2.5);
}
}
@@ -0,0 +1,18 @@
package seng302.visualiser.cameras;
public interface RaceCamera {
void zoomIn();
void zoomOut();
void panLeft();
void panRight();
void panUp();
void panDown();
}
@@ -0,0 +1,123 @@
package seng302.visualiser.cameras;
import java.util.Arrays;
import javafx.beans.property.DoubleProperty;
import javafx.collections.ObservableList;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
public class TopDownCamera extends PerspectiveCamera implements RaceCamera {
private final Double PAN_LIMIT = 30.0;
private final Double NEAR_ZOOM_LIMIT = -30.0;
private final Double FAR_ZOOM_LIMIT = -130.0;
private final Double ZOOM_STEP = 2.5;
private ObservableList<Transform> transforms;
private BoatObject playerBoat;
private Double zoomFactor;
private Double horizontalPan;
private Double verticalPan;
public TopDownCamera() {
super(true);
transforms = this.getTransforms();
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
horizontalPan = 0.0;
verticalPan = 0.0;
}
/**
* Sets a player boat object to observe and update the camera with.
*
* @param playerBoat The player boat to be observed.
*/
public void setPlayerBoat(BoatObject playerBoat) {
this.playerBoat = playerBoat;
for (DoubleProperty o : Arrays
.asList(playerBoat.layoutXProperty(), playerBoat.layoutYProperty())) {
o.addListener((obs, oldVal, newVal) -> updateCamera());
}
}
/**
* Moves the camera to a new position after some change (Zooming or Panning)
*/
private void updateCamera() {
transforms.clear();
transforms.addAll(
new Translate(playerBoat.getLayoutX() + horizontalPan,
playerBoat.getLayoutY() + verticalPan, zoomFactor)
);
}
/**
* Adjusts the zoom amount (camera depth) by some adjustment value
* @param adjustment the adjustment to be made to the camera
*/
private void adjustZoomFactor(Double adjustment) {
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
zoomFactor = zoomFactor + adjustment;
updateCamera();
}
}
/**
* Adjusts the Vertical Panning of the Camera
* @param adjustment the adjustment to be made to the camera
*/
private void adjustVerticalPan(Double adjustment) {
if (verticalPan + adjustment >= -PAN_LIMIT && verticalPan + adjustment <= PAN_LIMIT) {
verticalPan += adjustment;
updateCamera();
}
}
/**
* Adjusts the Horizontal Panning of the Camera.
* @param adjustment the adjustment to be made to the camera
*/
private void adjustHorizontalPan(Double adjustment) {
if (horizontalPan + adjustment >= -PAN_LIMIT && horizontalPan + adjustment <= PAN_LIMIT) {
horizontalPan += adjustment;
updateCamera();
}
}
@Override
public void zoomIn() {
adjustZoomFactor(ZOOM_STEP);
}
@Override
public void zoomOut() {
adjustZoomFactor(-ZOOM_STEP);
}
@Override
public void panLeft() {
adjustHorizontalPan(-1.0);
}
@Override
public void panRight() {
adjustHorizontalPan(1.0);
}
@Override
public void panUp() {
adjustVerticalPan(-1.0);
}
@Override
public void panDown() {
adjustVerticalPan(1.0);
}
}
@@ -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,11 @@ public class FinishScreenViewController implements Initializable {
}
public void switchToStartScreenView() {
Sounds.playButtonClick();
setContentPane("/views/StartScreenView.fxml");
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -1,248 +1,258 @@
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.AnchorPane;
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.discoveryServer.DiscoveryServerClient;
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.MapPreview;
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);
}
private final double INITIAL_MAP_HEIGHT = 770d;
private final double INITIAL_MAP_WIDTH = 574d;
//--------FXML BEGIN--------//
@FXML
private Text lobbyIpText;
private VBox playerListVBox;
@FXML
private Button readyButton;
private ScrollPane playerListScrollPane;
@FXML
private Button customizeButton;
private JFXButton customizeButton, leaveLobbyButton, beginRaceButton;
@FXML
private TextArea playerOneTxt;
private StackPane serverListMainStackPane;
@FXML
private TextArea playerTwoTxt;
private Label serverName;
@FXML
private TextArea playerThreeTxt;
private Label mapName;
@FXML
private TextArea playerFourTxt;
private AnchorPane serverMap;
@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 Label roomLabel;
//---------FXML END---------//
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 = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT;
private MapPreview mapPreview;
private ClientToServerThread socketThread;
private Stage customizeStage;
private Color playersColor;
private int MAX_NUM_PLAYERS = 8;
private Integer playerID;
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
private ObservableList<String> players;
/**
* 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();
timeUntilStart.setText("Waiting For Host...");
}
/**
* 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);
}
}
/**
* 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);
}
}
@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));
@Override
public void initialize(URL location, ResourceBundle resources) {
roomLabel.setText("");
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
if (this.playersColor == null) {
this.playersColor = Colors.getColor(playerID - 1);
this.playersColor = Colors.getColor(ViewManager.getInstance().getGameClient().getServerThread().getClientId() - 1);
}
cc.setPlayerColor(this.playersColor);
cc.setStage(customizeStage); // pass the stage through so it can be closed later.
cc.setLobbyController(this);
customizeStage.show();
leaveLobbyButton.setOnMouseReleased(event -> leaveLobby());
beginRaceButton.setOnMouseReleased(event -> beginRace());
leaveLobbyButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
leaveLobby();
});
beginRaceButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
beginRace();
});
Platform.runLater(() -> {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
int tries = 0;
while (DiscoveryServerClient.getRoomCode() == null && tries <= 10){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tries ++;
}
if (DiscoveryServerClient.getRoomCode() != null){
setRoomCode(DiscoveryServerClient.getRoomCode());
}
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
});
customizeButton.setOnMouseReleased(event -> {
customizationDialog = createCustomizeDialog();
Sounds.playButtonClick();
customizationDialog.show();
});
Platform.runLater(() -> {
Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId();
playersColor = Colors.getColor(playerId - 1);
});
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
customizeButton.setOnMouseEntered(e -> Sounds.playHoverSound());
beginRaceButton.setOnMouseEntered(e -> Sounds.playHoverSound());
initMapPreview();
}
private JFXDialog createCustomizeDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
JFXDialog customizationDialog = null;
try {
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
Logger.logMsg(4, "Failed to load Customization View from resources.");
}
e.printStackTrace();
}
public void setSocketThread(ClientToServerThread thread) {
this.socketThread = thread;
BoatCustomizeController controller = dialog.getController();
controller.setParentController(this);
controller.setPlayerColor(this.playersColor);
controller.setPlayerName(this.playerBoats
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
.getBoatName());
controller.setCurrentBoat(this.playerBoats.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
.getBoatType().toString());
return customizationDialog;
}
@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);
/**
* Initializes a top down preview of the race course map.
*/
private void initMapPreview() {
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
mapPreview = new MapPreview(marks, corners, border);
serverMap.getChildren().clear();
serverMap.getChildren().add(mapPreview.getAssets());
mapPreview.setSize(mapWidth, mapHeight);
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
mapWidth = newValue.doubleValue();
mapPreview.setSize(mapWidth, mapHeight);
});
//
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
mapHeight = newValue.doubleValue();
mapPreview.setSize(mapWidth, mapHeight);
});
}
@FXML
public void readyButtonPressed() {
GameState.setCurrentStage(GameStages.PRE_RACE);
// Do countdown logic here
for (LobbyCloseListener readyListener : lobbyListeners)
readyListener.notify(CloseStatus.READY);
/**
*
*/
private void beginRace() {
beginRaceButton.setDisable(true);
customizeButton.setDisable(true);
GameState.setCurrentStage(GameStages.PRE_RACE);
GameState.resetStartTime();
Platform.runLater(()-> ViewManager.getInstance().getGameClient().startGame());
}
public void setTitle (String title) {
lobbyIpText.setText(title);
/**
* 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));
try {
pane = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
public void setCourseName(String courseName){
courseNameText.setText(courseName);
playerListVBox.getChildren().add(pane);
}
}
public void addCloseListener(LobbyCloseListener listener) {
lobbyListeners.add(listener);
private void leaveLobby() {
ViewManager.getInstance().getGameClient().stopGame();
ViewManager.getInstance().goToStartView();
}
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();
}
public void setRoomCode(String roomCode) {
roomLabel.setText("Room: " + roomCode);
}
}
@@ -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;
@@ -7,8 +9,10 @@ import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javafx.animation.RotateTransition;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -16,43 +20,68 @@ 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;
import javafx.scene.chart.XYChart.Data;
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.Image;
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 javafx.util.Duration;
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.model.token.TokenType;
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;
private static final Double ICON_BLINK_TIMEOUT_RATIO = 0.6;
private static final Integer ICON_BLINK_PERIOD = 500;
@FXML
private AnchorPane loadingScreenPane;
@FXML
private ImageView loadingScreen;
@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 +91,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 contentStackPane;
private GridPane contentGridPane;
@FXML
private Text windArrowText, windDirectionText;
private AnchorPane rvAnchorPane;
@FXML
private AnchorPane windArrowHolder;
@FXML
private Slider annotationSlider;
@FXML
@@ -76,36 +109,130 @@ 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;
@FXML
private ImageView velocityIcon, handlingIcon, windWalkerIcon, bumperIcon, badRandomIcon;
//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;
//Icon stuff
private Timer blinkingTimer = new Timer();
private ImageView iconToDisplay;
private Double lastWindDirection;
public void initialize() {
// Load a default important annotation state
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);
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
contentStackPane.setVisible(false);
Image loadingImage = new Image("PP.png");
loadingScreen.setImage(loadingImage);
//Centers the Image within the image view
double w = 0;
double h = 0;
double ratioX = loadingScreen.getFitWidth() / loadingImage.getWidth();
double ratioY = loadingScreen.getFitHeight() / loadingImage.getHeight();
double reduceRatio = 0;
if(ratioX >= ratioY) {
reduceRatio = ratioY;
} else {
reduceRatio = ratioX;
}
w = loadingImage.getWidth() * reduceRatio;
h = loadingImage.getHeight() * reduceRatio;
loadingScreen.setX((loadingScreen.getFitWidth() - w) / 2);
loadingScreen.setY((loadingScreen.getFitHeight() - h) / 2);
Sounds.stopMusic();
Sounds.playRaceMusic();
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()
);
contentStackPane.setOnMouseClicked(event -> {
contentStackPane.requestFocus();
});
Platform.runLater(contentStackPane::requestFocus);
//Makes the chat history non transparent when clicked on
chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
});
lastWindDirection = 0d;
}
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
raceState.setRaceStarted(false);
createFinishDialog(finishedBoats);
}
public void showView(){
loadingScreenPane.setVisible(false);
contentStackPane.setVisible(true);
Platform.runLater(new Runnable() {
@Override
public void run() {
contentStackPane.requestFocus();
}
});
}
/**
* Create finishScreenDialog and set up finishDialogController.
*/
private void createFinishDialog(ArrayList<ClientYacht> finishedBoats) {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/RaceFinishDialog.fxml"));
Platform.runLater(() -> {
try {
finishScreenDialog = new JFXDialog(contentStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
finishDialogController = dialog.getController();
finishDialogController.setFinishedBoats(finishedBoats);
finishScreenDialog.show();
} catch (IOException e) {
e.printStackTrace();
}
});
}
public void loadRace (
Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
@@ -115,48 +242,115 @@ 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()) {
if (c.wasPermutated()) {
updateOrder(raceState.getPlayerPositions());
updateSparkLine();
}
}
});
player.addPowerUpListener(this::displayPowerUpIcon);
player.addPowerDownListener(this::removeIcon);
updateOrder(raceState.getPlayerPositions());
gameView = new GameView();
gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView = new GameView3D();
Platform.runLater(() -> {
contentStackPane.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.setBoatAsPlayer(player);
gameView.startRace();
raceState.addCollisionListener(gameView::drawCollision);
// raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
updateWindDirection(newDirection.doubleValue());
});
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
updateWindSpeed(newSpeed.doubleValue());
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
});
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) ->
Platform.runLater(() -> updateWindSpeed(newSpeed.doubleValue()))
);
Platform.runLater(() -> {
updateWindDirection(raceState.windDirectionProperty().doubleValue());
updateWindSpeed(raceState.getWindSpeed());
});
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
Platform.runLater(this::initializeUpdateTimer);
}
/**
* Displays the relevant icon, starts blinking it when it is close to turning off and then
* switches it off after the tokens time out
*
* @param yacht The yacht only for which we are displaying the icon
* @param tokenType The type of token, indicating what icon needs to be displayed
*/
private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) {
if (yacht == player) {
if (iconToDisplay != null) {
iconToDisplay.setVisible(false);
}
switch (tokenType) {
case BOOST:
iconToDisplay = velocityIcon;
break;
case HANDLING:
iconToDisplay = handlingIcon;
break;
case WIND_WALKER:
iconToDisplay = windWalkerIcon;
break;
case BUMPER:
iconToDisplay = bumperIcon;
break;
case RANDOM:
iconToDisplay = badRandomIcon;
break;
default:
iconToDisplay = velocityIcon;
}
//Turn icon on
iconToDisplay.setVisible(true);
//Start blinking icon towards end
if (blinkingTimer != null) {
blinkingTimer.cancel();
}
blinkingTimer = new Timer("Blinking Timer");
blinkingTimer.schedule(new TimerTask() {
Boolean isVisible = true;
@Override
public void run() {
isVisible = !isVisible;
iconToDisplay.setVisible(isVisible);
}
}, (int) (tokenType.getTimeout() * ICON_BLINK_TIMEOUT_RATIO), ICON_BLINK_PERIOD);
}
}
public void removeIcon(ClientYacht yacht) {
if (yacht == player) {
blinkingTimer.cancel();
iconToDisplay.setVisible(false);
iconToDisplay = null;
}
}
/**
* The important annotations have been changed, update this view
*
@@ -195,144 +389,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
}
private void initialiseFPSCheckBox() {
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())
);
}
/**
* 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()));
});
});
}
private void initialiseSparkLine() {
sparklineYAxis.setUpperBound(participants.size() + 1);
raceSparkLine.setCreateSymbols(false);
}
/**
* Updates the yachts sparkline of the desired yacht and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPlacing()
)
);
}
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - yacht.getPlacing()
// )
// );
}
/**
* gets the rgb string of the yachts colour to use for the chart via css
* @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String yachtId){
Color color = participants.get(Integer.valueOf(yachtId)).getColour();
if (color == null){
return String.format("#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 )
);
}
/**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* orderings etc.. which are dependent on the info from the stream parser constantly.
@@ -342,7 +398,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 +440,14 @@ 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));
RotateTransition rt = new RotateTransition(Duration.millis(300), windImageView);
rt.setByAngle(direction - lastWindDirection);
rt.setCycleCount(3);
rt.setAutoReverse(true);
rt.play();
lastWindDirection = direction;
// windImageView.setRotate(direction);
}
/**
@@ -390,7 +455,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 +463,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 {
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 +521,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 +643,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 +670,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 +690,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 +737,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();
contentStackPane.requestFocus();
return chat;
}
public void updateChatHistory(Paint playerColour, String newMessage) {
Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage));
}
}
@@ -0,0 +1,342 @@
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.ArrayList;
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.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
import seng302.gameServer.ServerDescription;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
import seng302.visualiser.controllers.cells.ServerCell;
import seng302.visualiser.controllers.dialogs.DirectConnectController;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import seng302.visualiser.controllers.dialogs.ServerCreationController;
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 directConnectButton;
@FXML
private JFXTextField serverPortNumber;
@FXML
private JFXButton roomConnectButton;
@FXML
private JFXTextField roomNumber;
@FXML
private JFXButton autoSelectGame;
//---------FXML END---------//
private Label noServersFound;
private Logger logger = LoggerFactory.getLogger(ServerListController.class);
private JFXDialog directConnectDialog;
private JFXDialog serverCreationDialog;
private List<ServerCreationDialogListener> serverCreationDialogListeners = new ArrayList<>();
@FunctionalInterface
public interface ServerCreationDialogListener {
void notifyClosure();
}
// 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
directConnectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
roomNumber.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
connectToRoomCode(roomNumber.getText());
}
});
directConnectButton.setOnMouseReleased(event -> {
directConnectDialog.show();
Sounds.playButtonClick();
});
directConnectDialog = createDirectConnectDialog();
for (JFXTextField textField : Arrays.asList(roomNumber)) {
// 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);
}
autoSelectGame.setOnMouseReleased(e -> {
ServerListing listing;
DiscoveryServerClient client = new DiscoveryServerClient();
try {
listing = client.getRandomServer();
} catch (Exception e1) {
ViewManager.getInstance().showErrorSnackBar("Unable to connect to matchmaking server. Are you connected to the internet?");
return;
}
if (client.didFail()){
return;
}
if (listing == null || listing.equals(ServerRegistrationMessage.getEmptyRegistration())) {
ViewManager.getInstance().showErrorSnackBar("There are currently no servers available for you to connect to.");
return;
}
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
});
/*
// Validating the hostname
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
hostNameValidator.setMessage("Host name incorrect");
roomCodeInput.getValidators().add(hostNameValidator);
// Validating the port number
NumberRangeValidator portNumberValidator = new NumberRangeValidator(1025, 65536);
portNumberValidator.setMessage("Port number incorrect");
serverPortNumber.getValidators().add(portNumberValidator);
TODO later
*/
// 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);
roomConnectButton.setOnMouseReleased(e -> {
String roomCode = roomNumber.getText();
connectToRoomCode(roomCode);
});
// Set up dialog for server creation
serverListHostButton.setOnAction(action -> {
showServerCreationDialog();
});
addServerCreationDialogListener(this::closeServerCreationDialog);
}
/**
* Shows Server Creation Dialog when "Host" button is clicked.
*/
private void showServerCreationDialog() {
Platform.runLater(() -> {
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
"/views/dialogs/ServerCreationDialog.fxml"));
try {
serverCreationDialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
DialogTransition.CENTER);
ServerCreationController serverCreationController = dialogContent.getController();
serverCreationController.setListener(serverCreationDialogListeners);
serverCreationDialog.show();
Sounds.playButtonClick();
} catch (IOException e) {
e.printStackTrace();
logger.warn("Could not create Server Creation Dialog.");
}
});
}
private JFXDialog createDirectConnectDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/DirectConnect.fxml"));
JFXDialog dcDialog = null;
try {
dcDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
DirectConnectController controller = dialog.getController();
return dcDialog;
}
private void closeServerCreationDialog() {
serverCreationDialog.close();
}
/**
* 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;*/
return true;
}
private void connectToRoomCode(String roomCode){
DiscoveryServerClient client = new DiscoveryServerClient();
ServerListing serverListing;
if (client.didFail()){
return;
}
try {
serverListing = client.getServerForRoomCode(roomCode);
} catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking server. Please try again later.");
return;
}
if (serverListing == null || serverListing.equals(new ServerListing("","","", 0, 0))){
ViewManager.getInstance().showErrorSnackBar("No servers could be found with that room code.");
return;
}
try {
ViewManager.getInstance().getGameClient().runAsClient(serverListing.getAddress(), serverListing.getPortNumber());
}
catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking service.");
}
}
/**
* 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));
}
private void addServerCreationDialogListener(
ServerCreationDialogListener serverCreationDialogListener) {
serverCreationDialogListeners.add(serverCreationDialogListener);
}
private void removeServerCreationDialogListener(
ServerCreationDialogListener serverCreationDialogListener) {
serverCreationDialogListeners.remove(serverCreationDialogListener);
}
}
@@ -0,0 +1,58 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.controls.JFXSnackbar;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by Kusal on 26-Sep-17.
*/
public class SplashScreenController implements Initializable{
@FXML
private StackPane rootPane;
@Override
public void initialize(URL location, ResourceBundle resources) {
new SplashScreen().start();
}
class SplashScreen extends Thread {
public void run(){
try {
Thread.sleep(2000);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
Stage stage = new Stage();
ViewManager.getInstance().initialStartView(stage);
} catch (Exception e) {
e.printStackTrace();
}
rootPane.getScene().getWindow().hide();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@@ -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.
*/
public 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,414 @@
package seng302.visualiser.controllers;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDecorator;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXSnackbar;
import com.jfoenix.svg.SVGGlyph;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.dialogs.KeyBindingDialogController;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
import java.io.IOException;
import java.util.HashMap;
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);
private Stage stage;
private JFXSnackbar jfxSnackbar;
private JFXDialog keyBindingDialog;
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;
}
public void initialiseSplashScreen(Stage stage) throws IOException {
this.stage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/views/SplashScreen.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Party Parrots At Sea");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
}
/**
* 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 settings button -- [WIP]
JFXButton btnKeyBinding = new JFXButton();
btnKeyBinding.setText(" Key Bindings");
btnKeyBinding.setStyle("-fx-text-fill:#fff");
btnKeyBinding.getStyleClass().add("jfx-decorator-button");
btnKeyBinding.setCursor(Cursor.HAND);
btnKeyBinding.setFocusTraversable(false);
btnKeyBinding.setOnMouseClicked(event -> Platform.runLater(() -> {
try {
if (!checkDialogOpened(decorator.getChildren())) {
showKeyBindingDialog();
}
} catch (IOException e) {
logger.warn("Something went wrong when opening key bind dialog");
}
}));
//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);
btnMute.setFocusTraversable(false);
//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);
SVGGlyph keyBindingGlyph = new SVGGlyph(0, "KEY_BINDING",
"M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z",
Color.WHITE);
volumeOn.setSize(16, 16);
volumeOff.setSize(16, 16);
spacer.setSize(40, 16);
keyBindingGlyph.setSize(24, 16);
// Determine which graphic should go on the button
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
btnMute.setGraphic(volumeOff);
} else {
btnMute.setGraphic(volumeOn);
}
btnKeyBinding.setGraphic(keyBindingGlyph);
// Add Buttons
btns.getChildren().add(0, spacer);
btns.getChildren().add(0, btnMute);
btns.getChildren().add(0, btnKeyBinding);
btnMute.setOnAction((action) -> {
Sounds.toggleAllSounds();
if (btnMute.getGraphic().equals(volumeOff)) {
btnMute.setGraphic(volumeOn);
} else {
btnMute.setGraphic(volumeOff);
}
});
jfxSnackbar = new JFXSnackbar(decorator);
}
/**
* Recursively find JFXDialog given a starting node. Will traverse children of StackPane.
*
* @param nodes children nodes to be check.
* @return true if node contains JFXDialog.
*/
private Boolean checkDialogOpened(ObservableList<Node> nodes) {
boolean foundJFXDialog = false;
for (Node node : nodes) {
if (node instanceof JFXDialog) {
return true;
} else if (node instanceof StackPane) {
foundJFXDialog = checkDialogOpened(((StackPane) node).getChildren());
}
}
return foundJFXDialog;
}
private void showKeyBindingDialog() throws IOException {
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
"/views/dialogs/KeyBindingDialog.fxml"));
for (Node node : decorator.getChildren()) {
if (node instanceof StackPane) {
keyBindingDialog = new JFXDialog((StackPane) node,
dialogContent.load(),
DialogTransition.CENTER);
KeyBindingDialogController keyBindingDialogController = dialogContent
.getController();
keyBindingDialogController.setGameClient(this.gameClient);
keyBindingDialog.show();
decorator.requestFocus();
Sounds.playButtonClick();
}
}
}
public void closeKeyBindingDialog() {
keyBindingDialog.close();
}
public PopupDialogController showPopupDialog() {
FXMLLoader dialogContent = new FXMLLoader(
getClass().getResource("/views/dialogs/PopupDialog.fxml"));
for (Node node : decorator.getChildren()) {
if (node instanceof StackPane) {
try {
JFXDialog dialog = new JFXDialog((StackPane) node, dialogContent.load(),
DialogTransition.CENTER);
PopupDialogController popupDialogController = dialogContent.getController();
popupDialogController.setPopupDialog(dialog);
dialog.show();
return popupDialogController;
} catch (IOException e) {
logger.error("Cannot load Popup dialog");
}
}
}
return null;
}
/**
* Show a snackbar at the bottom of the app for 1 second.
*
* @param snackbarText text to be displayed.
*/
public void showSnackbar(String snackbarText, boolean isWarning) {
if (isWarning) {
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
} else {
if (decorator.getStylesheets().size() > 1) {
decorator.getStylesheets().remove(1);
}
}
jfxSnackbar.show(snackbarText, 1500);
}
private void closeAll() {
if (stage!= null) stage.close();
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");
}
LobbyController lobbyController = loader.getController();
if (disableReadyButton) {
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);
stage.setMinHeight(500);
stage.setMinWidth(800);
stage.setTitle("Party Parrots At Sea");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
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();
}
public void showErrorSnackBar(String msg){
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
JFXSnackbar bar = new JFXSnackbar(decorator);
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
}
public Stage getStage() {
return stage;
}
}
@@ -0,0 +1,58 @@
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.model.ClientYacht;
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;
private BoatMeshType boatType;
public PlayerCell(Integer playerId, ClientYacht yacht) {
this.playerId = playerId;
this.name = yacht.getBoatName();
this.boatColor = yacht.getColour();
this.boatType = yacht.getBoatType();
}
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(boatType, 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,180 @@
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.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.PointLight;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
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.fxObjects.assets_3D.BoatMeshType;
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
public class BoatCustomizeController implements Initializable{
//--------FXML BEGIN--------//
@FXML
private JFXColorPicker colorPicker;
@FXML
private ProgressBar speedBar;
@FXML
private ProgressBar accelBar;
@FXML
private ProgressBar handleBar;
@FXML
private JFXButton submitBtn;
@FXML
private JFXTextField boatName;
@FXML
private Pane boatPane;
@FXML
void colorChanged() {
refreshBoat();
}
//---------FXML END---------//
private ClientToServerThread socketThread;
private LobbyController lobbyController;
private BoatMeshType currentBoat;
private Double maxSpeedMultiplier = 1.0;
private Double maxTurnRateMultiplier = 1.0;
private Double maxAccelerationMultiplier = 1.0;
@Override
public void initialize(URL location, ResourceBundle resources) {
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
findMaxStats();
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
playerNameReqValidator.setMessage("Player name required.");
FieldLengthValidator playerNameLengthValidator = new FieldLengthValidator(20);
playerNameLengthValidator.setMessage("Player name too long.");
boatName.setValidators(playerNameLengthValidator, playerNameReqValidator);
boatPane.setBackground(
new Background(new BackgroundFill(Color.SKYBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
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);
socketThread.sendCustomizationRequest(CustomizeRequestType.SHAPE, currentBoat.toString().getBytes());
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;
}
public void setCurrentBoat(String boatType) {
currentBoat = BoatMeshType.valueOf(boatType);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
public void nextBoat() {
currentBoat = BoatMeshType.getNextBoatType(currentBoat);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
public void prevBoat() {
currentBoat = BoatMeshType.getPrevBoatType(currentBoat);
displayCurrentBoat();
refreshStatBars(currentBoat);
}
private void displayCurrentBoat() {
boatPane.getChildren().clear();
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
group.getChildren().add(bo.getAssets());
group.getChildren().add(new PointLight());
}
private void refreshBoat() {
boatPane.getChildren().clear();
Group group = new Group();
boatPane.getChildren().add(group);
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
group.getChildren().add(bo.getAssets());
refreshStatBars(currentBoat);
}
private void findMaxStats() {
for (BoatMeshType bmt: BoatMeshType.values()) {
if (bmt.turnStep > maxTurnRateMultiplier) {
maxTurnRateMultiplier = bmt.turnStep;
}
if (bmt.maxSpeedMultiplier > maxSpeedMultiplier) {
maxSpeedMultiplier = bmt.maxSpeedMultiplier;
}
if (bmt.accelerationMultiplier > maxAccelerationMultiplier) {
maxAccelerationMultiplier = bmt.accelerationMultiplier;
}
}
}
private void refreshStatBars(BoatMeshType bo) {
speedBar.setProgress((bo.maxSpeedMultiplier) / maxSpeedMultiplier);
accelBar.setProgress(bo.accelerationMultiplier / maxAccelerationMultiplier);
handleBar.setProgress(bo.turnStep / maxTurnRateMultiplier);
}
}
@@ -0,0 +1,70 @@
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 DirectConnectController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private JFXTextField serverAddress;
@FXML
private JFXTextField portNumber;
@FXML
private JFXButton submitBtn;
//---------FXML END---------//
public void initialize(URL location, ResourceBundle resources) {
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Too long.");
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
fieldRequiredValidator.setMessage("Required.");
serverAddress.setValidators(fieldLengthValidator, fieldRequiredValidator);
portNumber.setValidators(fieldLengthValidator, fieldRequiredValidator);
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
connectToServer();
});
}
/**
* connects to the server
*/
private void connectToServer() {
//TODO fix port number validation
try{
Integer.parseInt(portNumber.getText());
}
catch (NumberFormatException e){
ViewManager.getInstance().showErrorSnackBar("You need to enter a valid port number");
return;
}
ViewManager.getInstance().getGameClient()
.runAsClient(serverAddress.getText(), Integer.parseInt(portNumber.getText()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -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,195 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXToggleButton;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import seng302.model.GameKeyBind;
import seng302.model.KeyAction;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.ViewManager;
public class KeyBindingDialogController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private Label keyBindingDialogHeader;
@FXML
private Label closeLabel;
@FXML
private JFXButton zoomInbtn;
@FXML
private JFXButton zoomOutBtn;
@FXML
private JFXButton vmgBtn;
@FXML
private JFXButton sailInOutBtn;
@FXML
private JFXButton tackGybeBtn;
@FXML
private JFXButton upwindBtn;
@FXML
private JFXButton downwindBtn;
@FXML
private JFXButton resetBtn;
@FXML
private Label upwindLabel;
@FXML
private Label downwindLabel;
@FXML
private JFXToggleButton turningToggle;
@FXML
private JFXButton viewButton;
@FXML
private JFXButton rightButton;
@FXML
private JFXButton leftButton;
@FXML
private JFXButton forwardButton;
@FXML
private JFXButton backwardButton;
//---------FXML END---------//
private GameKeyBind gameKeyBind;
private List<JFXButton> buttons = new ArrayList<>();
private Map<Button, KeyAction> buttonActionMap;
private GameClient gameClient; // to send turning mode packet
@Override
public void initialize(URL location, ResourceBundle resources) {
gameKeyBind = GameKeyBind.getInstance();
buttons = new ArrayList<>();
Collections.addAll(buttons,
zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn,
viewButton, rightButton, leftButton, forwardButton, backwardButton);
bindButtonWithAction();
loadKeyBind();
buttons.forEach(button -> {
button.setOnMouseEntered(event -> mouseEnter(button));
button.setOnMousePressed(event -> buttonPressed(button));
button.setOnMouseExited(event -> mouseExit(button));
button.setOnKeyPressed(event -> keyPressed(event, button));
});
turningToggle.setOnMouseClicked(event -> toggleTurningMode());
resetBtn.setOnMouseClicked(event -> {
gameKeyBind.setToDefault();
loadKeyBind();
showSnackBar("All keys reset!", false);
});
closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
}
/**
* Set buttons' label according to GameKeyBind settings
*/
private void loadKeyBind() {
buttons.forEach(
button -> button
.setText(gameKeyBind.getKeyCode(buttonActionMap.get(button)).getName()));
turningToggle.setSelected(gameKeyBind.isContinuouslyTurning());
if (gameKeyBind.isContinuouslyTurning()) {
upwindLabel.setText("ClOCKWISE TURNING");
downwindLabel.setText("ANTICLOCKWISE TURNING");
} else {
upwindLabel.setText("UPWIND");
downwindLabel.setText("DOWNWIND");
}
}
/**
* Bind buttons with specific action in a map.
*/
private void bindButtonWithAction() {
buttonActionMap = new HashMap<>();
for (int i = 0; i < 12; i++) {
buttonActionMap.put(buttons.get(i), KeyAction.getType(i + 1));
}
}
/**
* Prompt success / failure message for reassigning key action
*/
private void showSnackBar(String message, Boolean isWarning) {
ViewManager.getInstance().showSnackbar(message, isWarning);
}
/**
* When a mouse enters the button, the color and font size should change to highlight
* @param button
*/
private void mouseEnter(Button button) {
button.setStyle(""
+ "-fx-background-color: -fx-pp-theme-color;"
+ "-fx-text-fill: -fx-pp-front-color;"
+ "-fx-font-size: 15;");
}
/**
* Prompt "press key..." to inform users assign a new key bind by pressing a key
* @param button
*/
private void buttonPressed(Button button) {
button.setText("PRESS KEY...");
}
/**
* When mouse leaves the button, return the button to the normal state in terms of text,
* color and font size
* @param button
*/
private void mouseExit(Button button) {
button.setText(GameKeyBind.getInstance().getKeyCode(buttonActionMap.get(button)).getName());
button.setStyle(""
+ "-fx-background-color: -fx-pp-front-color; "
+ "-fx-text-fill: -fx-pp-theme-color; "
+ "-fx-font-size: 13;");
keyBindingDialogHeader.requestFocus();
}
/**
* When a key is pressed, check if the new binding conflicts to any existed settings, if not
* assign the selected action with the new key binding to GameKeyBind.
* @param event
* @param button
*/
private void keyPressed(KeyEvent event, Button button) {
event.consume();
KeyAction buttonAction = buttonActionMap.get(button);
if (gameKeyBind.bindKeyToAction(event.getCode(), buttonAction)) {
showSnackBar(button.getId() + " is set to " + event.getCode().getName(), false);
button.setText(gameKeyBind.getKeyCode(buttonAction).getName());
} else {
loadKeyBind();
showSnackBar(event.getCode().getName() + " is already in use", true);
}
}
/**
* When the turning mode is toggled, update gameKeyBind and send out packet to notify the server
*/
private void toggleTurningMode() {
gameKeyBind.toggleTurningMode();
gameClient.sendToggleTurningModePacket();
loadKeyBind();
}
public void setGameClient(GameClient gameClient) {
this.gameClient = gameClient;
}
}
@@ -0,0 +1,56 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
public class PopupDialogController implements Initializable {
@FXML
private Label headerLabel;
@FXML
private Label contentLabel;
@FXML
private Label closeLabel;
@FXML
private JFXButton optionButton;
@FXML
private JFXDialog popupDialog;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setContent(String content) {
this.contentLabel.setText(content);
}
public void setHeader(String header) {
this.headerLabel.setText(header);
}
public void setOptionButton(JFXButton jfxButton) {
this.optionButton = jfxButton;
}
public void setOptionButtonText(String text) {
this.optionButton.setText(text);
}
public void setOptionButtonEventHandler(EventHandler<? super MouseEvent> eventHandler) {
this.optionButton.setOnMouseClicked(eventHandler);
}
public void setPopupDialog(JFXDialog popupDialog) {
this.popupDialog = popupDialog;
this.closeLabel.setOnMouseClicked(event -> this.popupDialog.close());
}
}
@@ -0,0 +1,179 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.MapMaker;
import seng302.visualiser.controllers.ServerListController.ServerCreationDialogListener;
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
private Label closeLabel;
@FXML
private JFXButton nextMapButton;
@FXML
private JFXButton lastMapButton;
@FXML
private Label mapNameLabel;
@FXML
private JFXSlider legsSlider;
@FXML
private Label legsSliderLabel;
@FXML
private JFXCheckBox pickupsCheckBox;
@FXML
private AnchorPane mapHolder;
private MapMaker mapMaker = MapMaker.getInstance();
//---------FXML END---------//
private List<ServerCreationDialogListener> serverCreationDialogListeners;
public void initialize(URL location, ResourceBundle resources) {
maxPlayersSlider.valueProperty().addListener(
(observable, oldValue, newValue) -> updateMaxPlayerLabel()
);
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
legsSlider.valueProperty().addListener(
(obs, oldVal, newVal) -> updateLegSliderLabel()
);
legsSlider.setMax(10);
updateMaxPlayerLabel();
updateLegSliderLabel();
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();
});
nextMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
nextMap();
});
lastMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
lastMap();
});
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
pickupsCheckBox.setSelected(true);
//closeLabel.setOnMouseClicked(event -> notifyListeners());
}
/**
* 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(), mapMaker.getCurrentRacePath(), (int) legsSlider.getValue(), pickupsCheckBox.isSelected());
if (serverDescription == null){
ViewManager.getInstance().getGameClient().getServerThread().closeSocket();
ViewManager.getInstance().getGameClient().stopGame();
return;
}
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("Max players: %.0f", maxPlayersSlider.getValue()));
}
private void updateLegSliderLabel() {
legsSlider.setValue(Math.floor(legsSlider.getValue()));
legsSliderLabel.setText(
String.format("A section of the race will repeat %.0f times", legsSlider.getValue())
);
}
public void playButtonHoverSound() {
Sounds.playHoverSound();
}
private void nextMap() {
mapMaker.next();
updateMap();
}
private void lastMap() {
mapMaker.previous();
updateMap();
}
private void updateMap() {
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
}
public void setListener(List<ServerCreationDialogListener> serverCreationDialogListeners) {
this.serverCreationDialogListeners = serverCreationDialogListeners;
}
public void notifyListeners() {
for (ServerCreationDialogListener serverCreationDialogListener : serverCreationDialogListeners) {
serverCreationDialogListener.notifyClosure();
}
}
}
@@ -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);
}
}

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