Compare commits

...

580 Commits

Author SHA1 Message Date
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
Alistair McIntyre a501b21d66 Merge branch 'develop' into 'master'
Sprint 6.0 Merge

Merge for sprint 6.0 tag to master.

See merge request !67
2017-08-17 14:59:01 +12:00
Alistair McIntyre 5fb8a0c2c1 Merge branch 'issue47_disconnect_crash_rebranch' into 'develop'
Issue47 disconnect crash rebranch

Fix for all disconnection issues. Fix is not robust. Need consistent interface between disconnection of ServerToClientThreads and MainServerThread.

See merge request !66
2017-08-17 14:55:45 +12:00
Alistair McIntyre d867a4b7a2 Fixed bug, should fix disconnection issues.
tags : #issue[47]
2017-08-17 14:53:54 +12:00
Michael Rausch 3c91db59f3 Changed wind change variation 2017-08-17 14:49:39 +12:00
Alistair McIntyre 72a390b484 Fixed build bug.
tags : #issue[47]
2017-08-17 14:47:20 +12:00
Alistair McIntyre 978def4cf7 Merge remote-tracking branch 'origin/develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-17 14:36:16 +12:00
Alistair McIntyre 2331942bf9 Merge branch 'Story1124_Sparkline' into 'develop'
Fixing Ordering

# Changes 
* Fixed ordering which was broken from a previous commit 
* Removed Sparkline as we could not get it working in time 

# Testing 
* Manual testlog only 

See merge request !65
2017-08-17 14:25:32 +12:00
Calum d41bdfebbc Merged Boat customization onto develop.
#bug #merge #refactor
2017-08-17 14:21:06 +12:00
Calum d0565503e8 Merge branch 'develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-17 14:15:27 +12:00
Haoming Yin f0c48e76e1 Merge branch 'Story80_BoatCustomization' into 'develop'
Story 80: Boat Customization

# Implementation
- Added Messages for Boat Customization Requests and Responses.
- Added functionality to handle Name and Boat Color Customization. (Shapes would be a next sprint thing)

# Testing
- Manually tested personally, will test correctly when boat shapes have also been implemented.


See merge request !64
2017-08-17 14:11:04 +12:00
Calum 3639e0d3ce Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists.
#bug #implement
2017-08-17 14:10:38 +12:00
Calum 0276911b88 Fixed disconnection issues. Fix is only temporary until a consistent interface for disconnections exists.
#bug #implement
2017-08-17 14:02:18 +12:00
Alistair McIntyre af3a400841 Fixed build bug.
tags : #story[1142]
2017-08-17 13:44:34 +12:00
Zhi You Tan 5a2a78a7d5 Bug fixes for final commit 2017-08-17 13:43:31 +12:00
Alistair McIntyre 5fc7442359 Merged Develop into branch.
tags : #story[1142]
2017-08-17 13:40:00 +12:00
Alistair McIntyre 39cfaf6780 Merge remote-tracking branch 'origin/develop' into Story80_BoatCustomization
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerPacketParser.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/gameServer/messages/ChatterMessage.java
#	src/main/java/seng302/gameServer/messages/MarkRoundingMessage.java
#	src/main/java/seng302/model/ServerYacht.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
2017-08-17 13:31:27 +12:00
Zhi You Tan 7a11b5eb77 Merge branch 'develop' into Story1124_Sparkline 2017-08-17 13:14:08 +12:00
Alistair McIntyre faf4600f51 Fixed some weird bug with the messages package, trying to add some small QoL changes with potential interaction bugs with the customization/lobby menu.
tags : #story[1142]
2017-08-17 13:13:07 +12:00
Zhi You Tan 3e383465a9 Attempted to fix sparklines, temporarily disabled them for end of sprint. 2017-08-17 13:12:09 +12:00
Alistair McIntyre ac279583df Small documentation changes.
tags: #story[1142]
2017-08-17 12:31:15 +12:00
Calum ef2659a7b9 Merge branch 'develop' into issue47_disconnect_crash_rebranch
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
#	src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java
2017-08-17 12:16:14 +12:00
Calum 769d1956b3 Moved client side disconnection handling to this branch and reimplemented it with develop functionality.
#refactor #issue[47]
2017-08-17 12:11:36 +12:00
Haoming Yin 8ca455ed24 Fixed:
-  Fixed regularPacketsTest unit test, which checks how regularly packets are sent from C2S thread.
- Removed misused GameClient in main server thread.

tags: #story[1124]
2017-08-17 12:03:43 +12:00
Haoming Yin 7bda2bc580 Fixed:
-  Fixed regularPacketsTest unit test, which checks how regularly packets are sent from C2S thread.
- Removed misused GameClient in main server thread.

tags: #story[1124]
2017-08-17 11:46:14 +12:00
Haoming Yin d7a290478d Merge remote-tracking branch 'origin/develop' into develop 2017-08-17 11:02:54 +12:00
alistairjmcintyre d73e4f8ec5 Fixed a build error.
tags: #story[1142]
2017-08-17 01:23:34 +12:00
alistairjmcintyre 6e02d3e533 Added some general UI improvements such as autofilling the color field of the form, and looked at how best to deal with response packets, as it should be part of the spec.
tags: #story[1142]
2017-08-17 01:20:16 +12:00
Calum 7b4a70817b Merged onto develop.
#mergre #refactor
2017-08-17 01:03:14 +12:00
Calum 87acce71ea Merge branch 'develop' into story1118_map_arrows
# Conflicts:
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/RaceState.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-17 00:52:41 +12:00
Calum 95b1c8a01f Arrows now work as intended. Case added for interior angles.
#implement #story[1118]
2017-08-17 00:43:07 +12:00
Zhi You Tan dfd421c3b7 Merge branch '1124b_Fixing_Update_Order' into 'develop'
Updating Yacht Positions / Finish Screen

# Change Log 
* Yachts now update their position on the side bar as they pass marks 
* Finish screen is now displayed correctly at end of race, populated by races 

# Testing 
* Manual test log performed


See merge request !63
2017-08-17 00:11:13 +12:00
William Muir 4596f1e0f8 Finish screen all done, updates during race
tags: #story[1124] #pair[wmu16, cir27]
2017-08-16 23:42:07 +12:00
William Muir a59d06fbbf Finish screen 2017-08-16 23:36:45 +12:00
William Muir 65286f273b Working ordering for in game AND race finish screen.
List in RaceState holds a sorted list of yachts.
A Listener is added to this list, listening for a permutation change, on perm change, sorts the list by comparitor of leg number of each yacht (stable sort)
2017-08-16 23:22:58 +12:00
William Muir 9727e86249 Merge remote-tracking branch 'origin/story1118_map_arrows' into 1124_Fixing_Order_And_Finish_Screen
# Conflicts:
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/main/resources/server_config/xml_templates/race.ftlh
2017-08-16 22:40:32 +12:00
William Muir db614fe845 Initial commit for Fixing order 2017-08-16 21:16:27 +12:00
Calum 85899e3fbe Merged with develop functionality. 2017-08-16 21:11:34 +12:00
Michael Rausch 87d6799c10 Re-added wind speed & direction variations
- Wind speed  & direction are randomly updated
- Removed two duplicate threads ;)

Tags: #story[1124]
2017-08-16 20:48:37 +12:00
Alistair McIntyre 67f0c213c2 Everything works. Needs a bit of polish, and possibly look at boat shapes next.
tags: #story[1142]
2017-08-16 20:38:11 +12:00
Calum c103595bba Merge branch 'develop' into story1118_map_arrows
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/RaceState.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-16 20:34:27 +12:00
Calum 86a7c2565c Arrows now rendered in correct orientation but rounding arc sometimes inverted.
#implement #story[1118]
2017-08-16 20:29:56 +12:00
Calum 21e6819f16 Arrows now rendered in correct orientation but rounding arc sometimes inverted.
#implement #story[1118]
2017-08-16 20:17:25 +12:00
William Muir 28569506f0 Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 20:09:20 +12:00
William Muir dad2ebf693 Fixed double thread issue! :D 2017-08-16 20:09:05 +12:00
Alistair McIntyre 02a7b804c1 Boat Names Change. Colors Change too. Updates clients during lobby when a change is made.
tags: #story[1142]
2017-08-16 19:31:27 +12:00
Peter Galloway 9dbb31dcef Merge branch 'Story40_Zooming' into 'develop'
Story40 zooming

# Zooming and Tracking
## Changes
* Boats can now be selected by clicking and selecting on the drop down menu
* Selected boats can now be tracked when the zoom level is increased
* Zoom in with the 'z' key and zoom out with the 'x' key

## Testing
* Manual testing logged in doc
* No cucumber tests made for this story

## Bug Fixes
* Fixed the sails animation bug

See merge request !62
2017-08-16 18:25:58 +12:00
Kusal Ekanayake a932f41cc2 Removed unused boolean in boat objects 2017-08-16 18:19:55 +12:00
Kusal Ekanayake 3570a3d2eb Removed unneeded imports 2017-08-16 18:16:55 +12:00
Kusal Ekanayake 85852df176 Modified limits to zooming
#story[1121]
2017-08-16 18:06:39 +12:00
Alistair McIntyre 6d045e9976 Merge branch 'develop' into Story80_BoatCustomization
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-08-16 17:59:11 +12:00
Alistair McIntyre 4fc00f8a3c Boat names now change, just the issue of the lobby not updating unless another person joins/leaves to try and fix.
tags: #story[1142]
2017-08-16 17:57:22 +12:00
Kusal Ekanayake 9c2bac36b6 Merge branch 'develop' into Story40_Zooming 2017-08-16 17:27:09 +12:00
Kusal Ekanayake 8501fc0b6d Added limits to zooming
#story[1121]
2017-08-16 17:25:51 +12:00
Michael Rausch e9e7f306cd Merge branch 'Story1117_Course_Boundary_Collision' into 'develop'
Checked if a boat has crossed the boundary/course limit, if so, bounce the boat back.

# Boundary crossing detection
* if a boat has crossed the boundary, it will be bounced back to where it has came from

# Testing
* Manual testing has been done.
* Boats have bounced back when collides with other boats, mark or boundary. 

#story[1117] #pair[hyi25, zyt10]

See merge request !60
2017-08-16 17:25:14 +12:00
Michael Rausch fa68a5fdff Fixed commented out test
#story[1117]
2017-08-16 17:23:52 +12:00
William Muir 07a39722fb Minor bug fix to velocity calculation 2017-08-16 17:23:14 +12:00
William Muir 1d4ab0e1fa Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 17:22:14 +12:00
William Muir 653651f97f Minor bug fix to velocity calculation 2017-08-16 17:22:00 +12:00
Haoming Yin b1ba6e729a Merge branch 'StartScreen' into 'develop'
Start screen

## Changes
* Race timer now works
* Lobby switches to race view at T-5 seconds
* Updated lobby title to show race name

## Testing
* Manual testing completed
* No unit or cucumber testing as this is mostly UI code

See merge request !61
2017-08-16 17:12:17 +12:00
Haoming Yin 99685d76a9 Merge remote-tracking branch 'origin/develop' into develop 2017-08-16 16:55:53 +12:00
Michael Rausch 711c94001b Fixed game crash, and improved timer
- Fixed the null pointer exception that happened on slower computers
- Made the timer start counting down when the host clicks ready

Tags: #story[1109]
2017-08-16 16:34:31 +12:00
Zhi You Tan 7f3d66d01d Checked if a boat has crossed the boundary/course limit, if so, bounce the boat back.
#story[1117] #pair[hyi25, zyt10]
2017-08-16 16:33:14 +12:00
Alistair McIntyre 1db75a8ae4 Request Messages now sending to server, need to update game and send response packet.
tags: #story[1142]
2017-08-16 14:54:50 +12:00
Kusal Ekanayake 7b9d28ade9 Progress made on the improper tracking of boats (boats aren't being correctly centered). 2017-08-16 14:51:52 +12:00
Michael Rausch 5843fc9212 The merge went well!!
Tags: #story[1109]
2017-08-16 14:51:44 +12:00
Calum 3542c646f8 Marker class can now store and show multiple arrows sequentially
#implement #story[1118]
2017-08-16 14:51:05 +12:00
Michael Rausch 8fb5ea2223 Merge branch 'develop' into StartScreen
# 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/utilities/StreamParser.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-08-16 14:31:14 +12:00
Kusal Ekanayake 958c1e216f Fixed sail toggling issues and test. 2017-08-16 14:30:37 +12:00
Kusal Ekanayake 100689a20b Merged develop onto zooming for easier merge later on.
#story[1121]
2017-08-16 13:15:15 +12:00
Kusal Ekanayake 2da887e677 Merge branch 'develop' into Story40_Zooming
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/test/java/steps/ToggleSailSteps.java
2017-08-16 13:13:39 +12:00
Alistair McIntyre bd213bcd9e Merge branch 'develop' into Story80_BoatCustomization 2017-08-16 13:06:31 +12:00
William Muir bf016356a6 Finish screen now displays correctly on finishing.
GameState update now checks for finishing the race
Can now go back to main menu from finish screen
Labeled all threads for better debugging
Still need to implement race list ordering and finish screen ordering
2017-08-16 13:04:34 +12:00
William Muir 76a750a764 Moved sendRaceStatus Message out of S2C Thread into MS Thread (minor refactor) 2017-08-16 11:53:29 +12:00
William Muir 000d562ffe SailIn / out animation on client is now correct again 2017-08-16 11:27:46 +12:00
Haoming Yin d02f2385dd Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/model/ClientYacht.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/test/java/seng302/visualiser/map/BoatSailAnimationToggleTest.java
#	src/test/java/steps/ToggleSailSteps.java
2017-08-16 10:11:58 +12:00
Haoming Yin a5261494cd Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message 2017-08-16 10:06:25 +12:00
Calum ac47e9d88a Game state now updates based on boat position. Arrows drawn as boat travels course. Currently do not point in correct direction, also the sparkline does not work.
#bug #refactor #implement #story[1118]
2017-08-16 03:51:48 +12:00
Calum 7329f7dc65 Merge branch 'develop' into story1118_map_arrows 2017-08-16 01:35:15 +12:00
Calum 7c5f146b11 Merge to integrate develop with separated Yacht classes. Functionality from this branch is not yet completed.
#refactor
2017-08-16 01:23:38 +12:00
Calum e3fbbd4590 Fixed sails in and out test.
#bug #test
2017-08-16 01:19:34 +12:00
Calum dc8baa09a3 Re-implemented collision tests.
#test #bug
2017-08-16 01:17:40 +12:00
Calum 720ce0ae5b Merged with develop. Moved all collision logic into game state.
#refactor
2017-08-16 01:04:16 +12:00
Calum a7a667b4bc Merge branch 'develop' into 1124_switching_to_finish_screen
# 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/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
#	src/test/java/seng302/visualiser/ClientToServerTests/RegularPacketsTest.java
2017-08-15 23:45:50 +12:00
Calum 4e68cf31cf Fixed finish screen switch
tags: #fix
2017-08-15 23:30:27 +12:00
Zhi You Tan d03460d69e Implemented boat tracking when boat is selected by either clicking or selecting from sidebar combo box. Clicking tracked boat will cancel tracking, but using combobox will always result in one boat being selected.
#story[1121] #pair[wmu16, zyt10]
2017-08-15 23:16:25 +12:00
Peter Galloway 50baf6f85b implemented race finish functionality, finish screen not loading properly yet #story[1124] 2017-08-15 20:48:51 +12:00
William Muir 23a04facbe ServerYachts now send now have their own boat status.
This allows clients to know when this boat has finished

tags: #story[1124]
2017-08-15 20:24:51 +12:00
Kusal Ekanayake 7fbecc8b3d Fixed juttery movements when zooming and sometimes tracking.
Need to look into getting the boat propery fixed in the center.

#story[1121]
2017-08-15 19:14:16 +12:00
Alistair McIntyre 9cb5956f3c Updated lobby controller to pass the player ID through, and the lobby view to have a customize button.
tags: #story[1142]
2017-08-15 16:48:23 +12:00
Alistair McIntyre 366ebb3adb Merge branch 'Story66_Collision' into 'develop'
Collision branch merging to develop

# Change log
* Added yacht-mark collision and yacht will be pushed back upon collision.
* Added yacht-yacht collision and both yachts will be pushed back upon collision.
* Updated yacht map position initialisation. Yachts will all be spawned behind start line with a distance apart from each other.
* Added a collision visual alert when collision packet is received.
* Added two extra colours to support for 8 boats.

# Testing
* Junit tests for yacht-yacht collision.
* Manual testing for yacht map position initialisation
    * Yacht spawned apart from each other.
    * Yacht will spawn behind start line even if start line is different direction.

See merge request !59
2017-08-15 15:53:36 +12:00
Haoming Yin 458bd5408f Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message 2017-08-15 15:21:10 +12:00
Calum c00d165f47 Merge remote-tracking branch 'origin/1124_broadcast_mark_rounding_message' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-08-15 15:13:40 +12:00
Calum d2bb15471a Changed the velocity equation for boats to handle outlying scenarios such as 0 velocity.
#bug
2017-08-15 15:12:39 +12:00
Zhi You Tan c0cd260610 Fixed junit fail 2017-08-15 15:06:11 +12:00
Haoming Yin 6a9357f598 Merged Develop onto refactored yacht branch
tags: #story[1124] #pair[hyi25, wmu16]
2017-08-15 15:04:35 +12:00
Haoming Yin 6909f99773 Merge remote-tracking branch 'origin/develop' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameView.java
2017-08-15 14:58:13 +12:00
Zhi You Tan ce5424cc79 Fixed develop merge and now collision works 2017-08-15 14:53:15 +12:00
Haoming Yin c125708a4a Final commit for yacht refactor
tags: #story[1124] #pair[hyi25, wmu16]
2017-08-15 14:49:16 +12:00
Haoming Yin 2dc0ba07d9 WIP: Second commit for seperating server and client yacht classes
tags: #story[1124]
2017-08-15 14:30:39 +12:00
Zhi You Tan 2a3231d334 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# 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/model/Yacht.java
#	src/main/java/seng302/model/mark/MarkOrder.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-08-15 14:30:01 +12:00
Haoming Yin d6a436d2eb WIP: Initial commit for seperating server and client yacht classes
tags: #story[1124]
2017-08-15 14:18:48 +12:00
William Muir baacd8a9c0 Refactor. Taken Rounding logic out of yacht and into game state.
tags: #story[1124]
2017-08-14 23:52:06 +12:00
William Muir e1dddd317d WIP: Minor fixes 2017-08-14 17:57:41 +12:00
Peter Galloway b9ae9c4730 made server send appropriate racestatus flags in the race status packet #pair[mra106, ptg19] #story[1109] 2017-08-14 17:55:53 +12:00
Kusal Ekanayake fab5f9229f Got the boat's to track and center correctly.
As well as allowing selecting different boats to track them, doesn't work as intuitively as I would like but will need to sort out a boat selection feature first made specifically for the selection of only one boat.

 #story[1121]
2017-08-14 17:27:14 +12:00
Peter Galloway ff92262a78 fixed race clock countdown/count up #story[1109] 2017-08-14 16:32:50 +12:00
William Muir a4547e12cb Merged develop into this branch and resolved conflicts
tags: #story[1124] #pair[wmu16, hyi25]
2017-08-14 16:21:18 +12:00
William Muir 1a39b6e4a3 Merge branch 'develop' into 1124_broadcast_mark_rounding_message
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-08-14 16:16:04 +12:00
William Muir 58446ffaed MarkRounding Message now sent out correctly.
Added submark seqID attribute to each mark of a compound mark from parsing xml
Added Rounding side attribute to each individual mark as read from the xml
RoundingSide enum now has a method to get Enum from String literal
Now store the closest mark to each yacht in each update for purpose of knowing which mark they round
Minor code tidying, Added logger to serverToClientThread, removed 'serverLog' method
removed obsolete code
2017-08-14 16:11:32 +12:00
Alistair McIntyre 1f09005147 Message types for Request and Response created, and Messages for them also created.
tags: #story[1142]
2017-08-14 15:30:42 +12:00
Alistair McIntyre 3754b71f4d Merged updated develop into branch.
tags: #story[1142]
2017-08-14 14:31:02 +12:00
Alistair McIntyre 1d2222b599 Merge branch 'develop' into Story80_BoatCustomization 2017-08-14 14:23:05 +12:00
Alistair McIntyre 89ceab0c4b Merge branch 'Story64_SailsAnimations' into 'develop'
Story64 sails animations

The sail animations for the boats. 

The sail should follow the boat when the boats heading nears downwind and upwind. The shift key should make the sail have a squiggly animation and flow into the wind. A cucumber test has also been created and could be looked into for more stories.

Unintentionally merged in parts of the zooming story. (Basic zooming in and out)

See merge request !56
2017-08-14 14:19:00 +12:00
Alistair McIntyre 76dabb8138 Merge remote-tracking branch 'origin/Story64_SailsAnimations' into Story64_SailsAnimations
# Conflicts:
#	src/test/java/steps/ToggleSailSteps.java
2017-08-14 14:17:10 +12:00
Alistair McIntyre 8b543488e3 Fixed faulty develop merge and completed manual testing before merging back into develop.
tags: #story[1111]
2017-08-14 14:15:34 +12:00
Alistair McIntyre c52c345e53 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 14:04:53 +12:00
Alistair McIntyre 9420717b44 Added Messages to MessageType, continued working on request.
tags: #story[1142]
2017-08-14 13:38:41 +12:00
Kusal Ekanayake 9c1fe72f6b Merge and tests fix 2017-08-14 13:35:18 +12:00
Kusal Ekanayake 2e892f35ea Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 13:32:57 +12:00
Alistair McIntyre bedd742c93 Started on Request/Response packets for the Boat customization to begin the boat customization process.
tags: #story[1142]
2017-08-14 13:26:51 +12:00
Kusal Ekanayake add6856727 Merge remote-tracking branch 'origin/Story64_SailsAnimations' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-14 13:22:56 +12:00
Kusal Ekanayake 4dc6b2af2b Merge fix 2017-08-14 13:20:15 +12:00
Kusal Ekanayake e65558b8ac Merge branch 'develop' into Story40_Zooming
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-14 13:17:39 +12:00
William Muir e4cc5a0950 Fixing develop (sort of) 2017-08-14 12:52:58 +12:00
William Muir 4b4f8a25a3 Merge branch 'issue38_irregular_key_event_packets' into 'develop'
Issue38 irregular key event packets

A timed loop is now created on button press for sending out packets when a button is held down.
Removed the lists in LobbyController and replaced them with text objects to avoid the nested lists of single element lists.
Unit tests are in tests.java.seng302.visualiser.ClientToServerTets.RegularPacketsTest

**EDIT -** Tests fail due to some kind of error with reading from sockets in VM environment. Unable to fix at present.

See merge request !57
2017-08-14 12:49:52 +12:00
Zhi You Tan c77adf385e Terminating client thread before server thread to prevent alert box popping up 2017-08-14 11:21:33 +12:00
Zhi You Tan 9b7f4a93d8 Merge remote-tracking branch 'origin/issue38_irregular_key_event_packets' into issue38_irregular_key_event_packets 2017-08-14 11:02:19 +12:00
Zhi You Tan e780b47160 Fix javafx error on ci 2017-08-14 11:02:07 +12:00
Haoming Yin 028fc44dce Yacht now extends observable and can notify server to client thread to broadcast message.
#story[1124]
2017-08-14 10:48:11 +12:00
cir27 a185c9dc96 Implemented a factory class that creates mark arrows as needed. Needs to integrated with the real world data and tested to work. Works with all test data so far.
#implement #story[1118]
2017-08-14 03:44:46 +12:00
Calum 52e10997f1 Build issues caused by vm unable to open socket connections. Leaving as is for now.
#bug
2017-08-13 21:07:21 +12:00
Calum f2f750298c Added print statements for determining where javaFX is accessed from in the test. 2017-08-13 21:05:28 +12:00
Calum 1755b00079 Unncommented test to see if it breaks the build at remote repo. 2017-08-13 20:59:03 +12:00
Calum a844585faf Empty tests to check where bug is occuring 2017-08-13 20:54:13 +12:00
Calum b7fe79a5cc Attempted fix for build issues due to javaFX toolkit not being initialized. 2017-08-13 20:44:23 +12:00
Calum 2d5492601f Fixed build issues caused by changes to a yacht step turn constant.
#bug
2017-08-13 20:40:18 +12:00
Calum 0ee12021e2 Fixed bug that stopped clients from getting updated xml data.
#issue[46] #bug
2017-08-13 20:29:10 +12:00
Calum 6218d5506b Fixed bug that stopped clients from getting updated xml data.
#issue[48] #bug
2017-08-13 20:24:03 +12:00
Calum d79b0421c2 Fixed merged conflicts with merge onto develop.
#bug
2017-08-13 19:00:06 +12:00
Zhi You Tan 0b978593d4 Solved the fps counter zoom together with canvas problem. Workaround used was changing fps counter on game view to race view.
#story[1117] #pair[ptg19, zyt10]
2017-08-13 18:52:42 +12:00
Calum 8cb5b8caec Merge branch 'develop' into issue38_irregular_key_event_packets
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerPacketParser.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-13 18:44:34 +12:00
Calum 2093e79b6a Rather than a high frequency loop sending all packets a low frequency loop is made then destroyed on button press for sending turn packets. This means a fast response time on button press but fewer packets sent.
#implement #issue[38]
2017-08-13 18:38:24 +12:00
Kusal Ekanayake 8ec6490627 Started trying to get the zoomed in gave view to follow the boat.
Can easily make the boat stay put at the origin. can make it perfectly center, also weary of the weird jittery effect that is present when tracking. Tracking is active when the zoom scale is greater than 1.

#story[1121]
2017-08-13 17:02:35 +12:00
Kusal Ekanayake 0a7a9018f3 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-13 15:27:25 +12:00
Zhi You Tan 93cb0ca600 Updated yacht-yacht collision to push back both yacht.
Tweaked acceleration to increase time to top speed.

#story[1117] #pair[ptg19, zyt10]
2017-08-13 15:14:14 +12:00
Kusal Ekanayake 25c2de63a2 Working cucumber tests for sails toggle.
When sails in will send a sails out message when sails out will send a sails in message and then checks for the new status of the sail

#story[1111]
2017-08-13 14:55:33 +12:00
Kusal Ekanayake 2411a3cc2c Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-13 14:34:42 +12:00
Zhi You Tan fda233f5ad Merge branch 'Story71_TackAndGybeSmoothly' into 'develop'
Story 71 Tack/Gybe, Story 65 VMG Autopilot

# Implementation
- Added autopilot functionality to move boat towards a given heading with a single function call.
- Added functionality to move boat from one side of the wind to the other (tack/gybe) with a single button press.
- Added functionality to move boat to correct VMG from polars with single button press.
- Pressing a button a second time will disable the autopilot, as will pressing the Upwind/Downwind keys.

# Testing
- Fairly simple JUnit testing for a set of beginning headings and expected ends for the autopilot
- Unsure how cucumber testing would actually be better than simple junit testing in this case.

See merge request !55
2017-08-13 14:22:28 +12:00
Zhi You Tan c58cb1a476 WIP: Researched and implemented a simple right of way calculation (might be wrong).
#story[1117]
2017-08-11 23:57:27 +12:00
Michael Rausch d2fd9ebaea Started work on start screen
- Server now sends out race start status messages during lobby & pre-start stages
- Added timer in lobby

Tags: #story[1109]
2017-08-11 19:03:57 +12:00
Zhi You Tan fda6625256 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/mark/MarkOrder.java
#	src/main/java/seng302/visualiser/GameClient.java
2017-08-10 20:23:56 +12:00
alistairjmcintyre 6ddaaa0dfa Removed extra print statement from test. 2017-08-10 20:18:36 +12:00
William Muir 55c22fa7e2 Merge branch 'MessageExtensions' into 'develop'
Message extensions

## Changes
* Implemented new Client<->Server handshake protocol

## Testing
* Manually tested as tests cannot be simulated in code. The following cases were tested
    * Client connects normally with empty lobby
    * Client connects normally with one other player in lobby
    * Client responds to lobby full error
    * Client responds to general error 

See merge request !54
2017-08-10 20:17:50 +12:00
Michael Rausch a7b8b0dbc3 Removed redundant input & output streams 2017-08-10 20:15:08 +12:00
Zhi You Tan 7e1686a980 Packets are sent out when collision happens and receiver is able to interpret and display as visual alert. Updated visual alert to looks better.
- Created yacht event code message to be sent out as packet.
- Added observers to main server thread on yacht so when collision detected, main server thread will send out yacht event message to all server to client threads.
- Updated collision visual alert using circle and animation timer.

#story[1117]
2017-08-10 19:50:30 +12:00
Alistair McIntyre 32b231e78a Added testing for the vmg autopilot
tags: #story[1107]
2017-08-10 19:19:41 +12:00
Michael Rausch 3ad37faedc Merge branch 'develop' into MessageExtensions 2017-08-10 19:01:46 +12:00
Michael Rausch 09c4f98056 Implemented new client - server handshake protocol
- Implemented new packet types
- Changed server & client logic to use new protocol

Tags: #story[1124] (Issue 39)
2017-08-10 19:01:30 +12:00
Alistair McIntyre 1c2870649a Added testing for the tack/gybe settings.
tags: #story[1105]
2017-08-10 18:58:38 +12:00
Alistair McIntyre a2ee4411be Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:59:19 +12:00
Alistair McIntyre a23352ef85 Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:59:10 +12:00
Alistair McIntyre f9d5bd10b1 Merge branch 'develop' into Story71_TackAndGybeSmoothly 2017-08-10 17:58:19 +12:00
William Muir 0b8ad137b3 Minor fixes for merge
#story[1124] #pair[wmu16, hyi25]
2017-08-10 17:47:24 +12:00
Alistair McIntyre a746191dba VMG works correctly, auto pilot is interupted by a repeated keypress or an upwind/downwind press.
tags: #story[1105]
2017-08-10 17:46:28 +12:00
Michael Rausch 1772d1c05e Merge branch '1124_Mark_Rounding_Implementation' into 'develop'
1124 mark rounding implementation

# ChangeLog 

* Implemented Mark Rounding Algorithm so the server acknowledges when boats pass around and through marks / gates 
* Compound Mark class changed so that it has one constructor which takes a list of Mark objects
* GeoUtility has had many methods added to it for purposes of deducing mark rounding

# Testing 

* JUnit tests done for GeoUtility Class
* JUnit tests done for CompoundMark Class
* Test class removed for Yacht Class as no longer testable
* Manual test-log entry made for mark rounding in game

See merge request !53
2017-08-10 17:20:33 +12:00
William Muir 1d7b527130 Tidied code. Added tests
#story[1124] #pair[wmu16, hyi25]
2017-08-10 16:45:30 +12:00
Alistair McIntyre 430779c943 Boat auto pilots correctly for tacking/gybing. Needs proper testing.
#story[1105]
2017-08-10 14:53:24 +12:00
William Muir 9c79897e01 Tidied code, added MidPoint to CompoundMark class
Compound Mark class is now constructed with a list of marks.
A mid point is created on its construction for use in Geo Calculations

#story[1124] #pair[wmu16, hyi25]
2017-08-10 13:58:32 +12:00
Michael Rausch 07386ed2db Improved boat bounce-back calculation
- Changed boat bounce back send the boat n meters in the opposite direction.
- Improved test to use the minimum of yacht and mark collision distances

Tags: #story[1117]
2017-08-10 13:01:31 +12:00
William Muir abb15f6edf Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:08:03 +12:00
William Muir 87f2f1fe63 Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:07:47 +12:00
William Muir 249ad9e5c0 Fixed Mark rounding Algorithm
Algorithm now knows when a player has to round a gate or just pass right through

#story[1124] #pair[wmu16, hyi25]
2017-08-10 12:02:19 +12:00
alistairjmcintyre 9d02d2fbea Implemented a fairly simple auto pilot setting for the yacht, on update if the boat is set to autopilot it will adjust the heading towards the desired heading. Needs some refinement.
#story[1105]
2017-08-10 02:04:51 +12:00
Michael Rausch b1598ccb0f Changed testUpdateYachtWithCollision to use MARK_COLLISION_DISTANCE
Changed testUpdateYachtWithCollision to use MARK_COLLISION_DISTANCE constant.

#story[1117]
2017-08-09 22:36:34 +12:00
Michael Rausch 08304f9c3e Fixed failing test 2017-08-09 22:05:49 +12:00
Michael Rausch 4cc48a355e Added mark collisions
- Boats now collide with marks
- Added method to MarkOrder to get all marks
- Reduced the frequency at which collisions are detected. This fixed some performance issues
- Added method to bounce the boat off a mark

Tags: #story[1117]
2017-08-09 21:57:50 +12:00
Calum 2c5fddb695 Implemented zooming and clipping of race border. Implementation is still hackish.
#pair[ptg19, cir27] #story[1121]
2017-08-09 16:56:46 +12:00
Calum 126d8ea870 Added factory class for producing mark arrows.
#story[1118]
2017-08-09 15:24:32 +12:00
Calum 30a6cb98ec Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-09 14:03:21 +12:00
Zhi You Tan 8813d06010 Created a simple red blink on a top of a yacht given source id.
Created and updated methods reading yacht event packet to translate to collision alert on visualiser.
WIP: sending yacht event packet to inform collision

#story[1117]
2017-08-09 01:26:59 +12:00
Kusal Ekanayake 0fbca89030 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/model/Yacht.java
2017-08-08 19:45:08 +12:00
Kusal Ekanayake 421ef3c65a Started progress on zooming, can now use z and x keys to zoom into map.
Nothing scales correctly asides from the map itself (the boats stay in the same position).

#story[1121]
2017-08-08 19:41:51 +12:00
Zhi You Tan 5937f8b640 Merge remote-tracking branch 'origin/develop' into Story66_Collision
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/model/Yacht.java
2017-08-08 17:04:34 +12:00
Zhi You Tan 5ec67d0b80 Added junit for update yacht (after collision implementation). Removed another version of initialise boat positions.
#story[1117]
2017-08-08 16:51:29 +12:00
Kusal Ekanayake 941febaf62 Tried to fix cucumber tests, getting closer.
#story[1111]
2017-08-08 16:31:27 +12:00
William Muir a545e9dbc3 Removing '>' characters from docstrings to fix build
Note: This is probably a XML style guid thing and is stupid. Can probably fix this
2017-08-08 16:14:13 +12:00
William Muir b0e7dddaf3 Fixed gate passing algorithm
boats now must pass through the correct way. This works for start in-race and finish gates
Refactored yacht algorithm code for better readability
Logging function added or seeing mark roundings occur

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-08 15:58:13 +12:00
Haoming Yin ed0a783374 Fixed the bug that boats could round over a gate but still "across" it. Added unit test to ensure the algorithm works.
tags: #story[1124]
2017-08-08 10:42:36 +12:00
Kusal Ekanayake 97696cc95b Started work on cucumber tests. Trying to sort through threads, getting inconsistent results.
#story[1111] #pair[kre39,ptg19]
2017-08-07 17:54:34 +12:00
William Muir 4375b73257 Implemented algorithm for checking if boat passes through a mark.
Mark rounding works for whole course (WITH BUGS)
Still some gate logic to work out.
Moved gate function to GeoUtil class

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-07 17:28:12 +12:00
Kusal Ekanayake f97b18d594 Sailsin graphics moving and working as expected.
#story[1111] #pair[kre39,ptg19]
2017-08-07 16:48:30 +12:00
Kusal Ekanayake c7857872ce Luffing sails animation working correctly, working on sailsin animation now.
#story[1111] #pair[kre39,ptg19]
2017-08-07 15:24:01 +12:00
Zhi You Tan 79105a1bdc Created a simple collision detection by iterating each boats per update. Working but sequential checks can be costly.
#story[1117]
2017-08-07 12:01:26 +12:00
Zhi You Tan a4b22190c0 Changed the spawn point to behind start line and calculated quadrant to make sure yachts spawn behind start line in different map scenario.
#story[1117]
2017-08-07 11:50:07 +12:00
Calum a3ce5998ff Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-07 10:58:07 +12:00
William Muir 7f0329dda6 WIP: Implemented basic mark rounding algorithm.
Removed RacePosition class. Instead marks are just grabbed from the mark order class when necessary.
No marks are stored as an attribute in the yacht class but the 'currentMarkSeqID' which is used to get current, and surrounding marks.
Works for all marks in between but not including starting and finishing gate as no angle can be made with them. Still to work out how to implement this

 #story[1124]
2017-08-07 00:23:54 +12:00
Calum 8a40119a98 Action packets now sent at regular 20ms intervals
#issue[38] #implement
2017-08-06 22:17:08 +12:00
Zhi You Tan a470cb66a2 Updated initialise boat function so it can now initialise boats with distance apart in meters.
Created a second prototype function which is more testable compared to the initial design. New function takes in parameters (starting marks, yacht starting position, yacht) and initialise yacht correctly with position.

#story[1117]
2017-08-06 21:16:14 +12:00
Kusal Ekanayake ecf2c52cfa Added tests, and sails to all clients.
#story[1111]
2017-08-06 15:43:22 +12:00
Haoming Yin 43788bd153 Added a method to test if a geo point is located in a triangle which is formed by other three geo points.
The method helps to check the mark rounding.

Also unit tests have been done for this method.

tags: #story[1124]
2017-08-06 12:36:57 +12:00
Zhi You Tan 81c2a8e0fd WIP: Added test initialise boat position test. Corrected ColorsTest after addition of two new colours. 2017-08-05 23:59:58 +12:00
William Muir e90a0ce435 Merge branch '1124_Mark_Sequence_From_RaceXML' into 'develop'
Loading mark sequence from RaceXML

# Loading mark sequence from RaceXML

## Change Log
1. Added MarkOrder class
* Mark order is read from the generated RaceXML and stored
* Added .getNextMark() to get the next mark in the race
* Added .equals() and .hashCode() for Marks
* NEW: Added RacePosition class to hold players position in the race
* NEW: Fixed issue where the duplicates weren't stored in the mark order

## Testing
* Unit tests in models/MarkOrderTest.java

## Acceptance Criteria
* Use the mark sequence in the raceXML
    * Met by change log item (1)

* Store relevant mark details with each participating boat (Last mark, next mark)
    * Method in MarkOrder to get next mark, however the last mark and next mark will need to be stored by whoever implements the second task.

See merge request !52
2017-08-05 15:18:26 +12:00
Zhi You Tan a727014fcb Implemented boats spawning in parallel at the start line with spacing.
Added two more colours to support up to eight boats.

#story[1117]
2017-08-05 00:31:36 +12:00
Kusal Ekanayake ae28ccf228 Made sails luff in the right direction.
#story[1111]
2017-08-04 16:24:34 +12:00
Michael Rausch 281ce2d842 Loading course mark order from RaceXML
- Re-engineered code to work with the new marks
- Fixed bug where race order wasn't correct (added RacePosition class to fix)
- Rewrote tests to work with new RacePosition class

Tags: #story[1124] (Task 1)
2017-08-04 13:20:50 +12:00
Kusal Ekanayake f8af9cc259 Works for clients and server.
Due to the information being sent and received, it only currently works on client side boats.

#story[1111]
2017-08-03 19:07:30 +12:00
Zhi You Tan 8af80e6c3a WIP: Connected game client to main server thread to pass compound mark variable.
Boats are initialised in main server thread behind start line before game starts.

#story[1117]
2017-08-03 18:39:15 +12:00
William Muir 874cdec654 Added booleans: has entered rounding zone, has crossed first line, has crossed second line
All for purposes of checking mark rounding.

Currently not yet finished

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-03 17:39:07 +12:00
Kusal Ekanayake 99d5545ed3 Made the sails work properly by toggling.
Need to remove the unneeded code I added.

#story[1111]
2017-08-03 16:33:51 +12:00
William Muir 423f1acdb6 Merge remote-tracking branch 'origin/develop' into develop 2017-08-03 16:29:22 +12:00
William Muir 454e9ac9f1 Added an attribute to each yacht: 'DistanceToNextMark'
This attribute is calculated at each update of the boat as prompted by the game state regularly
Removed the lat and lng attribute from the Yacht class and replaced its usage with the GeoPoint object instead

Removed redundant test files and merged GeoUtility and testGeoUtil test classes into one

tags: #story[1124] #pair[hyi25, wmu16]
2017-08-03 16:29:12 +12:00
Kusal Ekanayake a8e70b3631 Merge branch 'develop' into Story64_SailsAnimations
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
#	src/main/java/seng302/model/Yacht.java
2017-08-03 15:19:13 +12:00
Kusal Ekanayake eb83e9dcc5 Working idle sail animation.
Need to work on getting sails to toggle properly. Have the 2 animations almost working but unable to switch between the two.

#story[1111]
2017-08-03 14:50:56 +12:00
Zhi You Tan 1ab849fd0d Added cucumber dependency. 2017-08-03 14:02:52 +12:00
William Muir db078538ff Minor cleaning in yacht class
tags: #story[1124]
2017-08-03 13:53:30 +12:00
Calum 1c0d869894 Corrected a value in the race.xml 2017-08-03 13:49:53 +12:00
Calum f9e6df46c1 Fixed issues caused by merge.
#bug
2017-08-03 13:23:43 +12:00
Calum 5228c078bc Merge branch 'develop' into 1124_Mark_Sequence_From_RaceXML
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/server/simulator/Simulator.java
#	src/main/java/seng302/models/mark/Mark.java
#	src/main/java/seng302/visualiser/map/CanvasMap.java
2017-08-03 12:27:11 +12:00
William Muir f5f73ede6b Merge branch 'story61_player_perspective' into 'develop'
Big boy merge request. Issue 34. Issue 35.

# This merge request is to fix **Issue 34** and **Issue 35**
*This is will require some further work but is being added to develop as is*

# Changelog

## Mark classes from server now used in client also.
The mark setup client side was poor because all marks were of type AbstractMark but they all had different methods so you had to identify what kind of mark they were anyway. The server side implementation
used the same calls for all Mark types and had the bonus of a GeoPoint class with utilities for distance and calculations.

## Yacht Class now contains observable values
The yacht attributes used by the model are now observable.

## StreamParser now static utils class.
Data no longer needs to be stored in the staticly available thread. Can now be processed in a more direct fashion by classes calling the parsing utils when needed.

## XMLParser now static utils class.
XMLParser now converts XML formatted doc objects and returns the containing data, this removes the need for the class to store multiples kinds of XML data and can instead just return it for use elsewhere.

## Data storage classes added to java/seng302/model/stream/parser
Rather than the StreamParser updating the Model directly it just processes the data into basic data types and shoves it all into objects for processing elsewhere. This avoids the static StreamParser class needing to know about Model objects.

## All static ultilities moved to their own package to make them easier to locate. Any child classes moved to their own files and added to packages in the model package.

## All visualiser code moved to the a new visualiser package

## Grouped all high level logic for the app and data management.
Previously all the high level logic (e.g. when to start a race, what fxml to show, how to process data from the stream) was in a number of classes. Mainly StreamParser, Controller, StartScreenController, RaceViewController, BoatGroup and LobbyController. All of this has been grouped into GameClient.
* This reduces the need to share data between these classes. Previously all the controllers needed references to one another to work so they were essentially the same class anyways.
* This makes it easier to find specific logic.
* Everything is now accessabile from GameClient, or from something GameClient knows about, making it easier to pass information if needed.

## Logic removed from LobbyController. Now only displays information

## Annotation box now a generic case for a rectange containg any number or type of annotation.
This means that if any new annotation needs to be added you don't have to hard code a specific case for it. Just initialize the annotation in the box at anytime.

## GameClient now observes the state of Yacht and Marker classes for position changes and updates them accordingly.
This avoids having to pass data to the GameView and having to poll for changes.

## CanvasController renamed GameView, now only concerned with displaying the race.
Moved all unnecessary logic to GameClient. Also fixed some issues where it polled RaceViewController for data instead of being updated by RaceViewController.

## MarkerObjects now a simple circle bound to the location of a given Mark. To display a gate lines are bound between the circles bound to Marks if a CompoundMark has more than one Mark.
Made all the logic here much more straight forward.

## Replaced most public static data classes with listeners.
Removed a bunch of polling. Made sharing data simplier and more readable.

# Known Issues & Limitaitons

* Pretty much everything that wasn't working at the end of sprint 5.
* Because listeners are triggering stuff on the JavaFX thread you have to use `Platform.runLater(() -> {//Do stuff});` whenever you want to access FX stuff from other threads. You can't just be lazy and put everything in it because then the JavaFX thread goes really slow. This is actually good practice anyways since you want to process data off of them FX thread (so not anything that's not triggered by a controller or an event) to keep JavaFX responsive.
* There is a 100% chance stuff has broken with this refactor that I'm not aware of. Some of it I am aware of.

# Testing

Good joke. I did manual testing though.

# Acceptance criteria

It works more or less.

See merge request !50
2017-08-03 12:11:20 +12:00
William Muir 1160f274ee Reformatted code in appropriate style
Build fails server side only due to some dependency issue. Internet problem server side?? (M I C H A E L?)

tags: #issue[34]
2017-08-03 12:06:07 +12:00
William Muir 53f5d63f15 Fixed build. (Actually this time) ((NOW WERE READY TO MERGE)))
All doc string annotations were required to be fixed for all methods

tags: #issue[34]
2017-08-03 11:50:07 +12:00
William Muir 1150ec3e43 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
2017-08-03 11:48:54 +12:00
William Muir c655cb3fab Fixed build. (Actually this time)
All doc string annotations were required to be fixed for all methods

tags: #issue[34]
2017-08-03 11:45:14 +12:00
William Muir 8e24c204fd Fixed build.
Google map does not work

tags: #issue[34]
2017-08-03 11:26:44 +12:00
Michael Rausch 7885b3fae2 Loading course mark order from RaceXML
- Mark order is read from the generated RaceXML and stored
- Added .getNextMark() to get the next mark in the race
- Added .equals() and .hashCode() for Marks

Tags: #story[1124] (Task 1)
2017-08-02 22:03:10 +12:00
Michael Rausch b01d39f19f Merge remote-tracking branch 'origin/develop' into develop 2017-08-02 20:42:22 +12:00
Michael Rausch bbf494e9a1 Merge branch 'master' into 'develop'
Merge Master into develop



See merge request !51
2017-08-02 20:42:07 +12:00
Michael Rausch eae201cb4b Merge remote-tracking branch 'origin/develop' into develop 2017-08-02 20:32:43 +12:00
Calum 87ef37a689 Fixed connecting to hosts. Fixed issue34 and 35 to the point where they can be developed off of.
#refactor #bug #issue[34, 35]
2017-08-02 00:26:57 +12:00
Alistair McIntyre b2c7b65191 Merge branch 'Logging' into 'master'
Added dependencies for slf4j



See merge request !49
2017-08-01 15:20:41 +12:00
Calum 908c0749cf Boats now move on screen as intended.
TODO - Make the client connect to the server.
2017-08-01 02:37:55 +12:00
Calum 47c5e6f155 Reverting some new graphics classes back to how they were on master. 2017-07-31 23:35:28 +12:00
Michael Rausch 9deba732b0 Added dependencies for slf4j 2017-07-31 18:34:55 +12:00
Calum b82d0d0137 Boats and map are now updated using the observer pattern.
#implement
2017-07-31 05:23:41 +12:00
Calum f1ad03e913 Refactored the setup for MarkObjects (now renamed Markers) and made the CompoundMark + Mark + GeoPoint classes the standard across all classes instead of GateMark + SingleMark + Mark.
#refactor
2017-07-31 02:19:19 +12:00
Calum 6cae338c1e Began fixing bugs with caused by asynchronous listener calls.
#bug
2017-07-30 20:12:19 +12:00
Calum 7894e31926 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/client/ClientState.java
#	src/main/java/seng302/client/ClientStateQueryingRunnable.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/model/stream/StreamReceiver.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/test/java/seng302/model/stream/StreamReceiverTest.java
2017-07-30 18:07:28 +12:00
Michael Rausch 25d8c8f9c4 Minor bug fixes.
Server will now only save incoming connections if in lobbying mode as it should
Commented out boat status printing

tags: #story[1047]
2017-07-27 14:15:55 +12:00
Zhi You Tan 1d9dd76356 For client's lobby view, it now can shows the connected host IP
#story[1055]
2017-07-27 13:12:53 +12:00
Zhi You Tan c2c34705d5 removed an unnecessary print statement and caught an exception 2017-07-27 13:01:04 +12:00
Zhi You Tan 96ed5e445e Replaced print stack trace with print statements 2017-07-27 12:47:18 +12:00
Kusal Ekanayake 870dc07fd2 Slight improvements to hosting.
Allow a host/client to disconnect and reconnect/make lobby, leave lobby and play the game.

#pair[kre39,hyi25] #story[1047]
2017-07-27 12:45:22 +12:00
Michael Rausch ecbb3f6658 Merge remote-tracking branch 'origin/develop' into develop 2017-07-27 11:06:00 +12:00
Haoming Yin 34704bd93d Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 23:32:32 +12:00
Haoming Yin 201c88a253 Fixed a bug that program crashes when it receive regatta xml during the race, as it doesn't know how to handle the xml.
#story[984]
2017-07-26 23:32:16 +12:00
Peter Galloway 6c4da58d9d Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 22:42:48 +12:00
Peter Galloway b56fa5cba3 refactored to make boat trails work again #fix #refactor 2017-07-26 22:42:37 +12:00
Peter Galloway 9cedbeb6f6 Fixed latency issues caused by clientside movement packets queueing up but currently the trails aren't working anymore #fix #refactor 2017-07-26 22:02:46 +12:00
Michael Rausch 4c6d107102 Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 20:28:21 +12:00
Michael Rausch 7917a2584b Fixed alignment for wind direction 2017-07-26 20:28:13 +12:00
Alistair McIntyre f99f8a0d7c Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 20:13:09 +12:00
Alistair McIntyre 7db716f51c Boat should move towards optimal angle upwind and downwind when pressing spacebar (VMG)
#story[988]
2017-07-26 20:12:57 +12:00
Haoming Yin 592e5a088f Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 19:58:45 +12:00
Haoming Yin 2d6850950c Merge branch 'develop' into Documenting_client_and_server
# Conflicts:
#	src/main/java/seng302/client/ClientToServerThread.java
2017-07-26 19:57:43 +12:00
Haoming Yin ef6821a0cd Updated and added more documentations
#story[1047]
2017-07-26 19:55:35 +12:00
Michael Rausch 6e9535d78f Fixed race timer & Added boats to team position list
- Race status messages are sent at regular intervals instead of once at race start
- Boat positions are initialised on the Team Position list
- Timer counts up from when host clicks ready

Tags: #story[377]
2017-07-26 19:35:59 +12:00
Alistair McIntyre 72a45f5984 Fixed a bug where if boatMaxSpeed was 0 and sails were up, boat would not decelerate.
#story[986]
2017-07-26 17:11:34 +12:00
Alistair McIntyre de600fa062 Merge remote-tracking branch 'origin/develop' into develop 2017-07-26 17:10:29 +12:00
Haoming Yin 87e3b4f246 Merge branch 'story61_player_highlighting' into 'develop'
Story61 player highlighting

Added some highlighting for the players boat and a random name generator to help distinguish clients. The highlighting implementation is pretty hackish since they will be replaced by the visualiser overhaul anyways. To avoid refactoring a lot of code I just made some unused javaFX elements invisible rather than removing them, I can do it properly if requested.

See merge request !48
2017-07-26 16:31:53 +12:00
Alistair McIntyre 7b47d72ef0 Merge branch 'develop' of /home/cosc/student/ajm412/Documents/SENG302/team-13 with conflicts. 2017-07-26 16:22:01 +12:00
Calum 7392bdb80d Added documentation
#document
2017-07-26 16:03:11 +12:00
Zhi You Tan af9f1417f1 Documented client packet parser, client state, client state querying runnable, client to server thread and replaced e.printStackTrace() with client log messages. 2017-07-26 16:01:41 +12:00
Calum 8fc00fd750 Enabled boat path for only the player.
#story[987]
2017-07-26 16:01:01 +12:00
Calum 4e5b67abfa Merge branch 'develop' into story61_player_highlighting 2017-07-26 15:52:29 +12:00
Calum 84e8ac89fc Added random name generator until players can chose how to identify themselves. 2017-07-26 15:52:02 +12:00
Calum 12d081a1af Added highlighting for the player and moved all their assets to the foreground
#story[987]
2017-07-26 15:49:00 +12:00
William Muir 5e6b402bf5 Minor structural changes. GameState now has a thread which updates itself so its update
rate can be independent of sending packets to the client in the MainServerThread

#story[986] #pair[wmu16, ptg19]
2017-07-26 14:48:32 +12:00
Alistair McIntyre 2bfa6cb038 Adjusted the velocity calculations to allow for Acceleration/Deceleration of the boats, rather than just coming to a complete halt when the sails are pulled in.
#story[986]
2017-07-26 14:01:51 +12:00
Calum 8ac44d13df Began fixing conflicts with LobbyController
#bug
2017-07-26 12:05:03 +12:00
William Muir d99055901f Removed Music 2017-07-26 11:38:29 +12:00
Calum 9c9f6e4e80 Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Yacht.java
#	src/main/java/seng302/visualiser/controllers/LobbyController.java
2017-07-26 02:56:46 +12:00
Calum 08e369f1ae Merged with develop. Fixed many bugs in Visualiser.
#bugs
2017-07-26 02:49:31 +12:00
Michael Rausch b0e99ab444 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	src/main/java/seng302/server/ServerThread.java
2017-07-25 23:10:41 +12:00
Haoming Yin c77a48f589 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 23:06:06 +12:00
Haoming Yin 0135426dfe Changed lobby player profile to a lower risk pictures
#story[1055]
2017-07-25 23:05:52 +12:00
William Muir 37c745c139 Polar velocities should now work as intended.
Snapping to VMG still needs to be implemented.
Still an issue of not being able to pass the total upwind or downwind point

tags: #story[986]
2017-07-25 22:27:26 +12:00
William Muir 7880039801 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 22:19:19 +12:00
William Muir a56dac1e87 Polar velocities should now work as intended.
Snapping to VMG still needs to be implemented.
Still an issue of not being able to pass the total upwind or downwind point

tags: #story[986]
2017-07-25 22:19:03 +12:00
Haoming Yin 4ae422b47b When a player connects to the server, lobby will now show their user id and profile instead of showing all the profile gif at the beginning,
#story[1055] #pair[hyi25, zyt10]
2017-07-25 21:50:23 +12:00
Calum acd54dec7a Merge branch 'develop' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.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/model/Boat.java
#	src/main/java/seng302/models/stream/XMLParser.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/main/resources/views/LobbyView.fxml
#	src/main/resources/views/MainView.fxml
2017-07-25 21:05:15 +12:00
Calum 6242ab0b2e Implemented observer and strategy pattern in BoatAnnotations, now renamed AnnotationsBox.
Also implemented various other small fixes and further refactored code.

#refactor
2017-07-25 20:45:27 +12:00
Michael Rausch c8a96dcce9 Fixed XML Loading error, used VMG to calculate boat velocity
- XML read from stream instead of file
- Started implementing VMG to calculate boat velocity dynamically

Tags: #pair[wmu16, mra106] #story[986]
2017-07-25 20:14:50 +12:00
Kusal Ekanayake 4f2dca7ecf Added proper the starting locations for marks and boats. 2017-07-25 18:48:33 +12:00
Zhi You Tan e569574c01 Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 17:17:45 +12:00
Zhi You Tan f544734b4d Fixed sending wrong race xml when a player disconnected because xml is getting data from gamestate yacht but the yachts are not updated is player disconnect.
Heartbeat packet was sent out at wrong rate which cause the player disconnect detection to be slow. Heartbeat packet is send out every 200ms now.

#story[1055] #pair[hyi25, zyt10]
2017-07-25 17:17:36 +12:00
Kusal Ekanayake 80b439470b Merge remote-tracking branch 'origin/develop' into develop 2017-07-25 15:22:56 +12:00
Kusal Ekanayake 539197cef5 Added a visual indicator of wind speed during race (text box)
#pair[kre39,mra106] #story[1040]
2017-07-25 15:22:46 +12:00
Zhi You Tan 1a867be387 Added keystroke frequency limit
Updated client and server log format

#story[988] #pair[hyi25, zyt10]
2017-07-25 15:13:48 +12:00
Kusal Ekanayake 3785cd705f Fixed wind speed and direction being sent correctly.
#pair[kre39,mra106] #story[1036]
2017-07-25 15:08:10 +12:00
Kusal Ekanayake 5d7a438080 Changed default ip back to non local host. 2017-07-25 14:28:21 +12:00
Kusal Ekanayake 2f12f3e34f Fixed bug to let multiple people play at the same time. 2017-07-25 14:27:52 +12:00
Zhi You Tan 52bfa3ad34 Merge remote-tracking branch 'origin/Story1055_Send_Race_Status_When_Host_Ready' into develop
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
2017-07-25 00:03:58 +12:00
Zhi You Tan d1d659b698 Lobby view will switch to race view when received race status packet with race start type.
Ready button can only be pressed by host. Once pressed, it will send out race status packet with race start to all clients.

#story[1055]
2017-07-25 00:01:59 +12:00
William Muir cdb9337aed Deleted GameServerThread after being re merged in
#chore
2017-07-24 23:20:47 +12:00
Zhi You Tan 8b0af5bb62 Updated observer so it sends out updated boats.xml when client disconnects
#story[1047] #pair[wmu16, zyt10]
2017-07-24 21:35:31 +12:00
Zhi You Tan 83232a935e Merge remote-tracking branch 'origin/Story984_Send_XML_After_Clients_Connect' into develop
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 21:30:12 +12:00
William Muir a30a1aa7c7 Tweaking to server loop making it send packets at 5Hz
Commented out some smoothing code in BoatGroup that was dependend on FPS screwing with movement
2017-07-24 21:14:17 +12:00
William Muir 07cebb6c5b Updated velocity in yacht constructor so the boat can be seen properly working for test purposes 2017-07-24 16:47:17 +12:00
Kusal Ekanayake 45bf65a3d3 Made clients receive new xml after new clients connect.
#pair[kre39,zyt10] #story[984]
2017-07-24 16:15:19 +12:00
Kusal Ekanayake 60f5a99b0c Merge branch 'Merging_GameLoop_with_Broadcast' into develop
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
2017-07-24 15:32:52 +12:00
Kusal Ekanayake 526c12127f Merging game lobby with game state broadcast. Merge conflicts resolved.
Port numbers updated to 4942.
2017-07-24 15:26:51 +12:00
Kusal Ekanayake 1daac842f2 Merging game lobby with game state broadcast 2017-07-24 15:01:07 +12:00
Kusal Ekanayake 3e4a6f0f2e Merge remote-tracking branch 'origin/Story1055_Lobby_View_Update' into Merging_GameState_With_Lobby
# Conflicts:
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/client/ClientToServerThread.java
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 14:58:14 +12:00
Kusal Ekanayake c1e937049e Merge remote-tracking branch 'origin/Story62_Swtich_To_RaceView_When_Race_Start' into Merging_GameLoop_with_Broadcast
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-24 14:37:56 +12:00
Zhi You Tan 8f8d5c7384 Removed a test sending xml
#story[1055]
2017-07-24 14:24:54 +12:00
Calum aad93d8913 Parsing classes now static utilities. Data now moved to model via controller class. Race logic shifted out of grpahics classes. Several improvements to code readability.
#story[986] #refactor
2017-07-24 12:14:08 +12:00
Zhi You Tan 027c7a1480 Updated start screen controller to connect to itself after setting up server.
#story[1047]
2017-07-24 12:09:23 +12:00
Zhi You Tan df2efa3329 Lobby controller list view is able to react to changes in client state boats and update the list view appropriately.
#story[1055]
2017-07-24 11:15:10 +12:00
Zhi You Tan 9d754c8819 Implemented list views initialisation which will set the first pane to be your source id (after three way handshake) and the remaining pane to be the source id of other players based on boats.xml received.
Updated client parser and client state to save a list of player's boat

WIP: refresh list view to show the latest update in players

#story[1055]
2017-07-23 20:42:21 +12:00
Zhi You Tan e11ceed28c Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story1055_Lobby_View_Update
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-23 19:59:30 +12:00
Michael Rausch 8b8b6e4afa Fixed map glitch when race starts, and race XML bug
- Race XML wasn't being sent to clients, this was causing a null ptr exception
- Boat location was being set to an invalid lat/lng

Tags: #story[1047]
2017-07-23 18:20:13 +12:00
Kusal Ekanayake ed2a22b573 Tried to merge game loop with the broadcast. Minor error in canvas. need to fix.
#story[1047]
2017-07-23 17:37:45 +12:00
Kusal Ekanayake 41851ee925 Merge branch 'Story62_Creating_Game_Loop' into Merging_GameLoop_with_Broadcast
# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerListenThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/models/Yacht.java
2017-07-23 17:34:24 +12:00
Kusal Ekanayake ffc61942a9 Fixed broken pipe error.
Was caused by boat ids being over 1000 and when turned into xml having a comma placed between the hundreds and the thousands? So to fix, I just mad the max id number 999 so there should not be any issues regarding that if another broken pipe error occurs. Also switched to use the correct yacht constructor.

#story[1047]
2017-07-23 17:10:18 +12:00
Kusal Ekanayake 2e4382bff6 Trying to fix the boat information that is being sent over.
Current issue is that a broken pipe error keeps occurring potentially due to how the messages are being sent so quickly that the previous message may have not finished sending. Also only caused the xm packets to be sent on trigger when a new client connects.

#story[1047]
2017-07-23 16:22:59 +12:00
Zhi You Tan f542dbb61e Changed the competitors list view to eight individual list view.
Added eight individual image view to support future player icon implementation.

#story[1055]
2017-07-23 03:00:29 +12:00
William Muir 2869d139a3 Three way handshake implemented client and server side and functioning
Server generates a new Id for connections (Size of connections + 1)
Client now stores an id allocated to it by the server
The id is currently being saved in the clientToServer thread client side

tags: #story[987] #implement
2017-07-22 17:44:37 +12:00
William Muir 3ec930491f Minor refactor, threads now start themselves
tags: #story[989] #refactor
2017-07-22 16:45:24 +12:00
William Muir a0005064ac Fixed the Yacht clas so it now works.
Lists and Maps are instantiated as they should be in GameState which were creating NullPointers
Introduced ServerLog to server to client threads for bug reporting
Introduced some print statements to test the game state updating upon receiving key presses

tags: #story[989] #refactor #fix
2017-07-22 16:32:05 +12:00
Michael Rausch 33fae9d69a Added race boats to XML Generator
Tags: #story[1047]
2017-07-21 16:56:46 +12:00
Peter Galloway 913e5fee7b Hooked up key press actions to the GameState, applying the relevant maths to update headings etc.
tags: #story[989]  #pair[ptg19, wmu16]
2017-07-21 16:50:09 +12:00
Zhi You Tan 3992073303 Set race started state in client state when packet is passed in client packet parser.
#story[1055]
2017-07-21 16:36:56 +12:00
Zhi You Tan 797a99f632 Merge remote-tracking branch 'origin/develop' into Story62_Swtich_To_RaceView_When_Race_Start
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-07-21 16:35:04 +12:00
Zhi You Tan e891ed8a64 LobbyView now can change to RaceView upon race start packet received.
Added port number text field in start screen controller.
Created a client state.

#story[1055] #pair[hyi25, zyt10]
2017-07-21 16:14:45 +12:00
Michael Rausch e8c2cf809b Fixed Null Ptr Exception in GameState 2017-07-21 15:42:20 +12:00
Michael Rausch ec761893c7 Merge branch 'Story62_Reading_Keystrokes' into 'develop'
Story62 reading keystrokes

Big merge

See merge request !44
2017-07-21 15:39:39 +12:00
Kusal Ekanayake 5df7efda03 Started implementing the gameState broadcasts.
Initial xml files are almost broad casted, just need to create them (or import the for the regatta). Started the setup for sending boat location packets, should work once we get at least the boat xml working when being sent.

#story[1047]
2017-07-21 13:16:43 +12:00
Kusal Ekanayake 2fff73c075 Merge remote-tracking branch 'origin/984_Xml_Generation' into Broadcasting_GameState
# Conflicts:
#	src/main/java/seng302/controllers/FinishScreenViewController.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/gameServer/GameServerThread.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/models/Yacht.java
2017-07-21 12:31:35 +12:00
Zhi You Tan d37cbd263e Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
2017-07-21 11:13:40 +12:00
Calum 3ec1242a9a Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
2017-07-21 11:13:04 +12:00
Calum 881f7f8e30 Changed package heirachy. Merged Controller and StartScreenController.
#refactor
2017-07-21 09:22:55 +12:00
Peter Galloway 12c2f31af9 Merge remote-tracking branch 'origin/Story62_Creating_Game_Loop' into Story62_Creating_Game_Loop
# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
2017-07-20 19:38:07 +12:00
Peter Galloway 49c0c029c3 adjusted the way the server is receiving key presses to enable them be passed through to the game state #pair[ptg19, wmu16] #story[989] 2017-07-20 19:35:59 +12:00
Alistair McIntyre da7a34fc55 Started adding functionality to calculate yacht velocity from the wind speed and direction using polar tables. Also began writing tests to cover this functionality, as it can't currently be tested within the game itself.
#story[986]
2017-07-20 14:30:13 +12:00
Michael Rausch 322ff740e2 Added race start time to race XML
- Added race start time to race XML
- Added documentation
- Removed test code in MainServerThread

Tags: #story[984]
2017-07-20 14:28:59 +12:00
Kusal Ekanayake b1575e57df Host also can be it's own client.
The host can connect to itself to become a client, packets are also sending from the host to client, update method should be ready to fully implemented. Added chatter packets to packet types to be used mostly for testing but can be further implemented for proper use in the future.

#story[1055]
2017-07-20 13:53:53 +12:00
Michael Rausch 82b219cdba Boat and race XML now generated dynamically
- Removed course from XML Generator as it was not needed
- Boat and race XML added
- Method names in Yacht class updated to follow JavaBean standard so they can be read by the template engine

Tags: #story[984]
2017-07-20 13:30:55 +12:00
Peter Galloway e317de7562 Added mock yachts to the game state for each client #story[1047] 2017-07-20 13:11:37 +12:00
Calum 9ecaa7c3b3 Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective 2017-07-20 13:05:26 +12:00
Calum 037b0db01b Refactoring client for more atomic classes, will mimic the socket, game state, logic thread layout used by the server.
#refactor
2017-07-20 13:04:29 +12:00
Zhi You Tan 1e80d76acd Retrieve local host ip address and show it on lobby view.
#story[1055] #pair[hyi25, zyt10]
2017-07-20 12:52:53 +12:00
Calum 360c55fdb9 Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective
# Conflicts:
#	src/main/java/seng302/controllers/GameViewController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
2017-07-20 12:20:12 +12:00
Peter Galloway 176d65e0b2 Merge branch 'Story62_Reading_Keystrokes' into Story62_Creating_Game_Loop 2017-07-20 12:01:53 +12:00
William Muir 0c08f5a03c Refactoring to remove all superflous classes related to the server
GameServerThread --> MainServerThread
All server classes consolidated into the gameServer package and all others removed

tags: #story[1055] #refactor #fix
2017-07-20 11:46:06 +12:00
William Muir e257602b78 Merge remote-tracking branch 'origin/Story62_Reading_Keystrokes' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/java/seng302/controllers/LobbyController.java
#	src/main/java/seng302/gameServer/MainServerThread.java
2017-07-20 11:35:27 +12:00
William Muir 8f00f3a80c Refactoring for server package, Changed GameServerThread to MainServerThread.
All Server classes now in single gameServer package

tags: #story[1055]
2017-07-20 11:22:30 +12:00
Peter Galloway 63d24c001f Added position update in yacht #story[1047] 2017-07-19 19:41:07 +12:00
Peter 67668fe1fc Merge branch 'Story62_Reading_Keystrokes' into Story62_Creating_Game_Loop 2017-07-19 18:13:58 +12:00
Kusal Ekanayake dbbb41e12f Multiple clients are now shown in lobby of host.
When multiple clients connect to the host, the lobby table now fills up with the names of the threads (will need to be changed later). Re-enabled multiple people connecting and the lobby table. Formatting changes in the lobby screen also made.

#story[1047]
2017-07-19 16:05:21 +12:00
Michael Rausch 45053ba507 Added XML Generation
- Implemented a wrapper for Apache Freemake
- Implemented the regatta XML generator

Tags: #story[984]
2017-07-19 14:47:16 +12:00
Calum d9f5f7a137 Refactoring view for game development
#story[987]
2017-07-18 14:00:24 +12:00
William Muir b301ce5d27 fixed build (i think?)
tags: #story[1055] pair[wmu16, zyt10]
2017-07-18 12:26:47 +12:00
William Muir f02bd3b3f8 Merge remote-tracking branch 'origin/Story62_Reading_Keystrokes' into Story62_Reading_Keystrokes 2017-07-18 12:23:09 +12:00
William Muir e83eaa38e1 Upon hosting, and then creating a new instance and connecting to that IP, button transmissions work and print out on server!! :D
Took the send method out of the Message class as it didnt make sense to have it there. This meant taking it out of all subclasses too

tags: #story[1055] pair[wmu16, zyt10]
2017-07-18 12:22:58 +12:00
Kusal Ekanayake 102b5f3ca1 Added a program icon that I snipped from the logo using photoshop. 2017-07-18 10:46:41 +12:00
Haoming Yin 63958a6717 WIP: Implemented a temporary workaround to send an instance test to client server upon connection.
Still needs reengineering to change socket channels for sending to ouput stream in the message class.
Only client to server "working".

#story[1047] #pair[hyi25, wmu16] #pair[cir27, zyt10]
2017-07-17 17:00:04 +12:00
Haoming Yin 4b8ac32ca9 Merge branch '1047_Hosting_Game' into Story62_Reading_Keystrokes 2017-07-17 10:55:17 +12:00
Haoming Yin 00b29a1890 Merge remote-tracking branch 'origin/develop' into Story62_Reading_Keystrokes 2017-07-17 10:55:08 +12:00
Haoming Yin c7e5f93bc4 Merged GeoUtility and GeometryUtils classes
#story[1047]
2017-07-16 21:58:40 +12:00
Haoming Yin e4d87c91a2 Merge branch 'develop' into 1047_Hosting_Game 2017-07-16 21:54:08 +12:00
Zhi You Tan f84091e54e Removed (fxml) table view and its table column from lobby controller because the table view is removed from LobbyView.fxml
#story[988]
2017-07-14 18:27:54 +12:00
Zhi You Tan e03e8825b2 Merge remote-tracking branch 'origin/1047_Hosting_Game' into Story62_Reading_Keystrokes 2017-07-14 18:23:28 +12:00
Zhi You Tan 355f8543f5 Implemented a more reliable way for keystroke input and added boat action packet type so stream parser is able to read and decode the message appropriately.
#story[988] #pair[hyi25, zyt10]
2017-07-14 18:23:07 +12:00
William Muir 77e7db79cc WIP: Worked on new server thread class that would create and store multiple THREADS of connections. Researched Authorative server structures.
Fixed the current structure of the server to work with the old StreamReciever style and hook up to the Stream Parser

tags: #story[1047] pair[wmu16, mra106]
2017-07-14 17:09:33 +12:00
Zhi You Tan 2809d0d832 Merge branch '1047_Hosting_Game' into Story62_Reading_Keystrokes
# Conflicts:
#	src/main/resources/views/RaceView.fxml
2017-07-14 16:28:42 +12:00
William Muir 5b908ec355 Merge remote-tracking branch 'origin/1047_Hosting_Game' into 1047_Hosting_Game
# Conflicts:
#	src/main/java/seng302/gameServer/GameServerThread.java
2017-07-13 22:55:49 +12:00
William Muir c480fca72a WIP: Worked on new server thread class that would create and store multiple THREADS of connections. Researched Authorative server structures
tags: #story[1047]
2017-07-13 22:55:03 +12:00
Michael Rausch c19f66a6a4 Added garbage collection for disconnected players
- Heartbeat messages are sent out from their own thread to each player
- If a heartbeat message can't be sent to a player, they are removed from the list of players
- Added equals method for players
Tags: #story[1047]
2017-07-13 22:07:03 +12:00
Michael Rausch 1e6fd1af09 Fixed bug where players were being added to the GameState twice
#story[1047]
2017-07-13 19:27:47 +12:00
Michael Rausch 55db2c9961 Fixed buffer overflow in message header
- Fixed buffer overflow by adding a reset method that  clears the buffer and sets the position to zero before re-writing the header
Tags: #story[1047]
2017-07-13 19:15:45 +12:00
Haoming Yin 6ec8b0c3c5 Terminated the game server socket when click exit lobby button
- the whole server thread should be terminated instead. To be fixed in the future.

#story[1047] #issue[28]
2017-07-13 17:51:25 +12:00
Kusal Ekanayake 78557a4536 Key presses are transmitted to a host (but there is no host currently connected)
#pair[kre39,zyt10] #story[988]
2017-07-13 15:39:48 +12:00
William Muir 8090cd7985 WIP: Adapted the old server thread class to the GameServerThread class to allow multiple clients to connect
tags: #story[1047]  #pair[wmu16]
2017-07-13 14:40:11 +12:00
Kusal Ekanayake 5ce34bed92 Key presses now assigned to enum and empty packet class is constructed.
#pair[kre39,zyt10] #story[988]
2017-07-13 14:35:41 +12:00
William Muir 035841f221 WIP: Adapted the old server thread class to the GameServerThread class to allow multiple clients to connect
tags: #story[1047]  #pair[wmu16, mra106]
2017-07-11 17:03:32 +12:00
Kusal Ekanayake ef61a687d6 Researched and implemented a way for the game to listen for key presses. When one of the valid key controls are pressed, feedback is given in the console. Has yet to be connected to a method which will create and send a message to the server.
#pair[kre39,zyt10] #story[988]
2017-07-11 16:32:15 +12:00
Haoming Yin fcb1e5e593 Removed unnecessary Position and GeoPoint classes to clear the code base.
- put utility classes in a package

#story[1047]
2017-07-10 23:51:01 +12:00
William Muir 752863a0d3 WIP: Created some basic controllers for the UI and started implementing backend for server stuff
Created GameState Class. Static, contains all info about current state of game: list of players and their respective details (coords etc) also Host info

Clients will observe this GameState class

Upon pressing host, new GameState is created and new GameConnectionListener created which listens for connections

Player class created which will be used to take each connection and store it as a player with other info about them regarding the game in the GameState class

tags: #story[1047]  #pair[wmu16, zyt10]
2017-07-10 16:03:13 +12:00
William Muir 1a3e330eb4 Created some basic UI for new start screen (host and connect) and a lobby
tags: #story[1047]  #pair[wmu16, zyt10]
2017-07-10 13:43:25 +12:00
William Muir 5f9da6b40a Fixed the bug where the polar file could not be read after being packaged
tags: #story[955]  #pair[wmu16, zyt10]
2017-07-10 12:36:32 +12:00
Michael Rausch aee62c29fe Merge remote-tracking branch 'origin/develop' into develop 2017-05-26 09:07:22 +12:00
Michael Rausch 3bd8added4 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 14:56:11 +12:00
Michael Rausch ba527a1979 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 14:54:28 +12:00
Michael Rausch 945acb6071 Merge remote-tracking branch 'origin/develop' into develop 2017-05-25 13:57:07 +12:00
Michael Rausch 6f132f1e38 Merge remote-tracking branch 'origin/develop' into develop 2017-05-24 15:22:30 +12:00
Michael Rausch a1e8d29b9c Merge remote-tracking branch 'origin/develop' into develop 2017-05-24 14:36:04 +12:00
Michael Rausch e26f2af93d Merge branch 'develop' of https://eng-git.canterbury.ac.nz/seng302-2017/team-13 into develop 2017-05-22 13:30:48 +12:00
Michael Rausch 6a6ed3ed44 Server sends mark locations to test
- Added a timer to send boat location messages containing the mark locations to test the receiver

#story[891]
2017-05-18 13:32:24 +12:00
309 changed files with 16723 additions and 6948 deletions
+4 -1
View File
@@ -7,7 +7,6 @@
.mtj.tmp/ .mtj.tmp/
# Package Files # # Package Files #
*.jar
*.war *.war
*.ear *.ear
@@ -180,3 +179,7 @@ local.properties
.recommenders/ .recommenders/
Makefile Makefile
infer-out/
infer.txt
log.log
+1 -1
View File
@@ -23,5 +23,5 @@ Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz> Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz> Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com> Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com> Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz> Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
+1 -1
View File
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
- Configuration file - Configuration file
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file. We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file.
To read external files, "Json-simple" library has been used to parse information. To read external files, "Json-simple" library has been used to parse information.
By using this library, we did not have to write our json parser and benefited from the flexibility of json files. By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
+1 -1
View File
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
## The config file ## The config file
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second). The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second).
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc. The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
@@ -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
+87
View File
@@ -11,6 +11,7 @@
<properties> <properties>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
@@ -36,6 +37,78 @@
<version>2.7.13</version> <version>2.7.13</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.26-incubating</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.lwjgl/lwjgl -->
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>de.javagl</groupId>
<artifactId>obj</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>com.interactivemesh</groupId>
<artifactId>jimStlMeshImporter</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>com.interactivemesh</groupId>
<artifactId>jimColModelImporter</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>com.jfoenix</groupId>
<artifactId>jfoenix</artifactId>
<version>1.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.jmdns/jmdns -->
<dependency>
<groupId>javax.jmdns</groupId>
<artifactId>jmdns</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -118,4 +191,18 @@
</plugin> </plugin>
</plugins> </plugins>
</reporting> </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> </project>
+61 -73
View File
@@ -1,92 +1,80 @@
package seng302; package seng302;
import ch.qos.logback.classic.Level;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage; import javafx.stage.Stage;
import seng302.models.PolarTable; import org.apache.commons.cli.CommandLine;
import seng302.models.stream.StreamParser; import org.apache.commons.cli.CommandLineParser;
import seng302.models.stream.StreamReceiver; import org.apache.commons.cli.DefaultParser;
import seng302.server.ServerThread; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.visualiser.controllers.ViewManager;
public class App extends Application { public class App extends Application {
private static Logger logger = LoggerFactory.getLogger(App.class);
public static void parseArgs(String[] args) throws ParseException {
Options options = new Options();
CommandLineParser parser = new DefaultParser();
CommandLine cmd;
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
.getLogger(Logger.ROOT_LOGGER_NAME);
options.addOption("debugLevel", true, "Set the application debug level");
cmd = parser.parse(options, args);
if (cmd.hasOption("debugLevel")) {
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "INFO":
rootLogger.setLevel(Level.INFO);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
}
}
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile()); ViewManager.getInstance().initialStartView(primaryStage);
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root, 1530, 960));
primaryStage.setMaxWidth(1530);
primaryStage.setMaxHeight(960);
// primaryStage.setMaximized(true);
primaryStage.show();
primaryStage.setOnCloseRequest(e -> {
StreamParser.appClose();
StreamReceiver.noMoreBytes();
System.exit(0);
});
} }
public static void main(String[] args) { public static void main(String[] args) {
try { try {
parseArgs(args);
StreamReceiver sr = null; } catch (ParseException e) {
logger.error("Could not parse command line arguments");
new ServerThread("Racevision Test Server");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (args.length == 1 && args[0].equals("-standalone")) {
return;
}
if (args.length == 3 && args[0].equals("-server")) {
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
} else if (args.length == 2 && args[0].equals("-server")) {
switch (args[1]) {
case "internal":
sr = new StreamReceiver("localhost", 4949, "RaceStream");
break;
case "staffserver":
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
break;
case "official":
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
break;
}
}
//Change the StreamReceiver in this else block to change the default data source.
else {
sr = new StreamReceiver("livedata.americascup.com", 4940, "RaceStream");
}
sr.start();
StreamParser streamParser = new StreamParser("StreamParser");
streamParser.start();
}
catch (Exception e){
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Information Dialog");
alert.setHeaderText("Fatal Error");
alert.setContentText("There was an error connecting to the AC35 stream");
alert.showAndWait();
} }
launch(args); launch(args);
} }
} }
-63
View File
@@ -1,63 +0,0 @@
package seng302;
import javafx.geometry.Point2D;
/**
* A Class for performing geometric calculations on the canvas
* Created by wmu16 on 24/05/17.
*/
public final class GeometryUtils {
/**
* Performs the line function on two points of a line and a test point to test which side of the line that point is
* on. If the return value is
* return 1, then the point is on one side of the line,
* return -1 then the point is on the other side of the line
* return 0 then the point is exactly on the line.
* @param linePoint1 One point of the line
* @param linePoint2 Second point of the line
* @param testPoint The point to test with this line
* @return A return value indicating which side of the line the point is on
*/
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
Double x = testPoint.getX();
Double y = testPoint.getY();
Double x1 = linePoint1.getX();
Double y1 = linePoint1.getY();
Double x2 = linePoint2.getX();
Double y2 = linePoint2.getY();
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
if (result > 0) {
return 1;
}
else if (result < 0) {
return -1;
}
else {
return 0;
}
}
/**
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
* point
* @param originPoint The point with which to use as the base for our vector addition
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
* @param vectorLength The length out on this angle from the origin point to create the new point
* @return a Point2D
*/
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
return new Point2D(endPointX, endPointY);
}
}
@@ -1,587 +0,0 @@
package seng302.controllers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import seng302.fxObjects.BoatAnnotations;
import seng302.fxObjects.BoatGroup;
import seng302.fxObjects.Wake;
import seng302.models.Colors;
import seng302.models.Yacht;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.fxObjects.MarkGroup;
import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark;
import seng302.models.map.Boundary;
import seng302.models.map.CanvasMap;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import seng302.models.stream.packets.BoatPositionPacket;
import seng302.server.simulator.GeoUtility;
import seng302.server.simulator.mark.Position;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
/**
* Created by ptg19 on 15/03/17.
* Modified by Haoming Yin (hyi25) on 20/3/2017.
*/
public class CanvasController {
@FXML
private AnchorPane canvasPane;
private RaceViewController raceViewController;
private ResizableCanvas canvas;
private Group group;
private GraphicsContext gc;
private ImageView mapImage;
private final int BUFFER_SIZE = 50;
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private final int PANEL_HEIGHT = 960;
private final int CANVAS_WIDTH = 720;
private final int CANVAS_HEIGHT = 720;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private Mark minLatPoint;
private Mark minLonPoint;
private Mark maxLatPoint;
private Mark maxLonPoint;
private double referencePointX;
private double referencePointY;
private double metersPerPixelX;
private double metersPerPixelY;
private List<MarkGroup> markGroups = new ArrayList<>();
private List<BoatGroup> boatGroups = new ArrayList<>();
private Text FPSdisplay = new Text();
private Polygon raceBorder = new Polygon();
//FRAME RATE
private Double frameRate = 60.0;
private final long[] frameTimes = new long[30];
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
public AnimationTimer timer;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public void setup(RaceViewController raceViewController) {
this.raceViewController = raceViewController;
}
public void initialize() {
raceViewController = new RaceViewController();
canvas = new ResizableCanvas();
group = new Group();
// create image view for map, bind panel size to image
mapImage = new ImageView();
canvasPane.getChildren().add(mapImage);
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
canvasPane.getChildren().add(canvas);
canvasPane.getChildren().add(group);
// Bind canvas size to stack pane size.
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
}
public void initializeCanvas() {
gc = canvas.getGraphicsContext2D();
gc.setGlobalAlpha(0.5);
fitMarksToCanvas();
drawGoogleMap();
FPSdisplay.setLayoutX(5);
FPSdisplay.setLayoutY(20);
FPSdisplay.setStrokeWidth(2);
group.getChildren().add(FPSdisplay);
group.getChildren().add(raceBorder);
initializeMarks();
initializeBoats();
timer = new AnimationTimer() {
private long lastTime = 0;
private int FPSCount = 30;
@Override
public void handle(long now) {
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.intValue());
}
raceViewController.updateSparkLine();
}
updateGroups();
if (StreamParser.isRaceFinished()) {
this.stop();
}
lastTime = now;
}
}
if (StreamParser.isRaceFinished()) {
this.stop();
switchToFinishScreen();
}
}
};
}
private void switchToFinishScreen() {
try {
// canvas view -> anchor pane -> grid pane -> main view
GridPane gridPane = (GridPane) canvasPane.getParent().getParent();
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren().addAll(
(Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml")));
} catch (javafx.fxml.LoadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 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.getLatitude(), minLonPoint.getLongitude());
// 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
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
Position originPos = GeoUtility
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
// distance from origin corner to bottom right corner of the panel
double distanceFromOriginToBottomRight = Math.sqrt(
Math.pow(PANEL_HEIGHT * metersPerPixelY, 2) + Math
.pow(PANEL_WIDTH * metersPerPixelX, 2));
double bearingFromOriginToBottomRight = Math
.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
Position 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());
}
/**
* Adds border marks to the canvas, taken from the XML file
*
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and
* CompoundMark which are named the same as those in the model package but are, however not the
* same, so they do not have things such as a type and must be derived from the number of marks
* in a compound mark etc..
*/
private void addRaceBorder() {
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
raceBorder.setStrokeWidth(3);
raceBorder.setFill(new Color(0,0,0,0));
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : courseLimits) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
private void updateGroups() {
for (BoatGroup boatGroup : boatGroups) {
// some raceObjects will have multiple ID's (for instance gate marks)
//checking if the current "ID" has any updates associated with it
if (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
if (boatGroup.isStopped()) {
updateBoatGroup(boatGroup);
}
}
boatGroup.move();
}
for (MarkGroup markGroup : markGroups) {
for (Long id : markGroup.getRaceIds()) {
if (StreamParser.markLocations.containsKey(id)) {
updateMarkGroup(id, markGroup);
}
}
}
checkForCourseChanges();
}
private void checkForCourseChanges() {
if (StreamParser.isNewRaceXmlReceived()){
addRaceBorder();
}
}
private void updateBoatGroup(BoatGroup boatGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatLocations.get(boatGroup.getRaceId());
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
if (movementQueue.size() > 0) {
try {
BoatPositionPacket positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
double heading = 360.0 / 0xffff * positionPacket.getHeading();
boatGroup.setDestination(
p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
positionPacket.getTimeValid(), frameRate);
} catch (InterruptedException e){
e.printStackTrace();
}
// }
}
}
void updateMarkGroup (long raceId, MarkGroup markGroup) {
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.markLocations.get(raceId);
if (movementQueue.size() > 0){
try {
BoatPositionPacket positionPacket = movementQueue.take();
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Draws all the boats.
*/
private void initializeBoats() {
Map<Integer, Yacht> boats = StreamParser.getBoats();
Group wakes = new Group();
Group trails = new Group();
Group annotations = new Group();
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
for (Yacht boat : boats.values()) {
if (participantIDs.contains(boat.getSourceID())) {
boat.setColour(Colors.getColor());
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
boatGroups.add(boatGroup);
trails.getChildren().add(boatGroup.getTrail());
wakes.getChildren().add(boatGroup.getWake());
annotations.getChildren().add(boatGroup.getAnnotations());
}
}
group.getChildren().addAll(trails);
group.getChildren().addAll(wakes);
group.getChildren().addAll(annotations);
group.getChildren().addAll(boatGroups);
}
private void initializeMarks() {
List<Mark> allMarks = StreamParser.getXmlObject().getRaceXML().getNonDupCompoundMarks();
for (Mark mark : allMarks) {
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
SingleMark sMark = (SingleMark) mark;
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
markGroups.add(markGroup);
} else {
GateMark gMark = (GateMark) mark;
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()),
findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
markGroups.add(markGroup);
}
}
group.getChildren().addAll(markGroups);
}
class ResizableCanvas extends Canvas {
ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
}
@Override
public boolean isResizable() {
return true;
}
@Override
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
}
private void drawFps(int fps){
if (raceViewController.isDisplayFps()){
FPSdisplay.setVisible(true);
FPSdisplay.setText(String.format("%d FPS", fps));
} else {
FPSdisplay.setVisible(false);
}
}
/**
* Calculates x and y location for every marker that fits it to the canvas the race will be
* drawn on.
*/
private void fitMarksToCanvas() {
//Check is called once to avoid unnecessarily change the course limits once the race is running
StreamParser.isNewRaceXmlReceived();
findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
//givePointsXY();
addRaceBorder();
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker
* with the leftmost marker, rightmost marker, southern most marker and northern most marker
* respectively.
*/
private void findMinMaxPoint() {
List<Limit> sortedPoints = new ArrayList<>();
for (Limit limit : StreamParser.getXmlObject().getRaceXML().getCourseLimit()) {
sortedPoints.add(limit);
}
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
Limit minLatMark = sortedPoints.get(0);
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
//If the course is on a point on the earth where longitudes wrap around.
Limit minLonMark = sortedPoints.get(0);
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 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) {
Mark referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += BUFFER_SIZE;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
} else {
referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = BUFFER_SIZE;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
}
if(horizontalInversion) {
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
}
}
/**
* 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(Mark.calculateHeadingRad(minLatPoint, maxLatPoint));
double vertDistance =
Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint);
double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(Mark unscaled) {
return findScaledXY(unscaled.getLatitude(), unscaled.getLongitude());
}
public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
int xAxisLocation = (int) referencePointX;
int yAxisLocation = (int) referencePointY;
angleFromReference = Mark
.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat,
unscaledLon);
distanceFromReference = Mark
.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat,
unscaledLon);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += (int) Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += (int) Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= (int) Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= (int) Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= (int) Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += (int) Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if(horizontalInversion) {
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
/**
* Find the number of meters per pixel.
*/
private void findMetersPerPixel() {
Point2D p1, p2;
Mark m1, m2;
double theta, distance, dx, dy, dHorizontal, dVertical;
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
p1 = findScaledXY(m1);
p2 = findScaledXY(m2);
theta = Mark.calculateHeadingRad(m1, m2);
distance = Mark.calculateDistance(m1, m2);
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;
}
List<BoatGroup> getBoatGroups() {
return boatGroups;
}
List<MarkGroup> getMarkGroups() {
return markGroups;
}
}
@@ -1,39 +0,0 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import seng302.models.stream.StreamParser;
public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
private void setContentPane(String jfxUrl) {
try {
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml");
StreamParser.boatLocations.clear();
}
}
@@ -1,640 +0,0 @@
package seng302.controllers;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
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.Slider;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import javafx.util.StringConverter;
import seng302.GeometryUtils;
import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate;
import seng302.controllers.annotations.ImportantAnnotationsState;
import seng302.fxObjects.BoatGroup;
import seng302.fxObjects.MarkGroup;
import seng302.models.*;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser;
import java.io.IOException;
import java.util.*;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import java.util.stream.Collectors;
/**
* Created by ptg19 on 29/03/17.
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private LineChart raceSparkLine;
@FXML
private NumberAxis sparklineYAxis;
@FXML
private VBox positionVbox;
@FXML
private CheckBox toggleFps;
@FXML
private Text timerLabel;
@FXML
private AnchorPane contentAnchorPane;
@FXML
private Text windArrowText, windDirectionText;
@FXML
private Slider annotationSlider;
@FXML
private Button selectAnnotationBtn;
@FXML
private ComboBox boatSelectionComboBox;
@FXML
private CanvasController includedCanvasController;
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps;
private Timeline timerTimeline;
private Stage stage;
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat;
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.getYAxis().setAutoRanging(false);
sparklineYAxis.setTickMarkVisible(false);
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
}
/**
* The important annotations have been changed, update this view
*
* @param importantAnnotationsState The current state of the selected annotations
*/
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
this.importantAnnotations = importantAnnotationsState;
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
}
/**
* Loads the "select annotations" view in a new window
*/
private void loadSelectAnnotationView() {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Stage stage = new Stage();
// Set controller
ImportantAnnotationController controller = new ImportantAnnotationController(this,
stage);
fxmlLoader.setController(controller);
// Load FXML and set CSS
fxmlLoader
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
stage.show();
controller.loadState(importantAnnotations);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initialiseFPSCheckBox() {
displayFps = true;
toggleFps.selectedProperty().addListener(
(observable, oldValue, newValue) -> displayFps = !displayFps);
}
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.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int) annotationSlider.getValue()));
annotationSlider.setValue(2);
}
/**
* Used to add any new boats into the race that may have started late or not have had data received yet
*/
void updateSparkLine(){
// Collect the racing boats that aren't already in the chart
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
// Obtain the qualifying boats to set the max on the Y axis
racingBoats = startingBoats.stream().filter(yacht ->
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
// Create a new data series for new boats
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getBoatName());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
sparkLineData.put(yacht.getSourceID(), yachtData);
});
// Lambda function to sort the series in order of leg (later legs shown more to the right)
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
Collections.sort(positions, (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)
raceSparkLine.setCreateSymbols(false);
positions.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()));
});
}
/**
* Updates the yachts sparkline of the desired boat 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
*/
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
}
/**
* gets the rgb string of the boats colour to use for the chart via css
* @param boatName boat passed in to get the boats colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String boatName){
Color color = Color.WHITE;
for (Yacht yacht: startingBoats){
if (Objects.equals(yacht.getBoatName(), boatName)){
color = yacht.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 ) );
}
/**
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
* orderings etc.. which are dependent on the info from the stream parser constantly.
* Updates of each of these attributes are called ONCE EACH SECOND
*/
private void initializeUpdateTimer() {
timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second
timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
updateRaceTime();
updateWindDirection();
updateOrder();
updateBoatSelectionComboBox();
})
);
// Start the timer
timerTimeline.playFromStart();
}
/**
* Iterates over all corners until ones SeqID matches with the boats current leg number.
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
* Returns null if no next mark found.
* @param bg The BoatGroup to find the next mark of
* @return The next Mark or null if none found
*/
private Mark getNextMark(BoatGroup bg) {
Integer legNumber = bg.getBoat().getLegNumber();
List<XMLParser.RaceXMLObject.Corner> markSequence = StreamParser.getXmlObject().getRaceXML().getCompoundMarkSequence();
if (legNumber == 0) {
return null;
} else if (legNumber == markSequence.size() - 1) {
return null;
}
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
if (legNumber + 2 == corner.getSeqID()) {
Integer thisCompoundMarkID = corner.getCompoundMarkID();
for (Mark mark : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
return mark;
}
}
}
}
return null;
}
/**
* Updates the wind direction arrow and text as from info from the StreamParser
*/
private void updateWindDirection() {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
}
/**
* Updates the clock for the race
*/
private void updateRaceTime() {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(getTimeSinceStartOfRace());
}
}
/**
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
* in the boat selection combo box
*/
private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections
.observableArrayList(StreamParser.getBoatsPos().values());
boatSelectionComboBox.setItems(observableBoats);
}
/**
* Updates the order of the boats as from the StreamParser and sets them in the boat order
* section
*/
private void updateOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
if (StreamParser.isRaceStarted()) {
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
positionVbox.getChildren().add(textToAdd);
} else {
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
} else {
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
}
private void updateLaylines(BoatGroup bg) {
Mark nextMark = getNextMark(bg);
Boolean isUpwind = null;
// Can only calc leg direction if there is a next mark and it is a gate mark
if (nextMark != null) {
if (nextMark instanceof GateMark) {
if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
isUpwind = true;
} else {
isUpwind = false;
}
for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
mg.removeLaylines();
if (mg.getMainMark().getId() == nextMark.getId()) {
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
HashMap<Double, Double> angleAndSpeed;
if (isUpwind) {
angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
} else {
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
}
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Line rightLayline = new Line();
Line leftLayline = new Line();
if (lineFuncResult == 1) {
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
} else if (lineFuncResult == -1) {
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
}
leftLayline.setStrokeWidth(0.5);
leftLayline.setStroke(bg.getBoat().getColour());
rightLayline.setStrokeWidth(0.5);
rightLayline.setStroke(bg.getBoat().getColour());
bg.setLaylines(leftLayline, rightLayline);
mg.addLaylines(leftLayline, rightLayline);
}
}
}
}
}
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
return new Point2D(newX, newY);
}
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
return line;
}
/**
* Initialised the combo box with any boats currently in the race and adds the required listener
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
updateBoatSelectionComboBox();
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
//This listener is fired whenever the combo box changes. This means when the values are updated
//We dont want to set the selected value if the values are updated but nothing clicked (null)
if (newValue != null && newValue != selectedBoat) {
Yacht thisYacht = (Yacht) newValue;
setSelectedBoat(thisYacht);
}
});
}
/**
* Display the list of boats in the order they finished the race
*/
private void loadRaceResultView() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
try {
contentAnchorPane.getChildren().removeAll();
contentAnchorPane.getChildren().clear();
contentAnchorPane.getChildren().addAll((Pane) loader.load());
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
}
/**
* Convert seconds to a string of the format mm:ss
*
* @param time the time in seconds
* @return a formatted string
*/
public String convertTimeToMinutesSeconds(int time) {
if (time < 0) {
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
}
return String.format("%02d:%02d", time / 60, time % 60);
}
private String getTimeSinceStartOfRace() {
String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = "-" + timerMinute + ":" + timerSecond;
} else {
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
timerString = timerMinute + ":" + timerSecond;
}
return timerString;
}
boolean isDisplayFps() {
return displayFps;
}
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
// No Annotations
case 0:
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
bg.setVisibility(false, false, false, false, false, false);
}
break;
// Important Annotations
case 1:
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
bg.setVisibility(
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:
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
bg.setVisibility(true, true, true, true, true, true);
}
break;
}
}
/**
* Sets all the annotations of the selected boat to be visible and all others to be hidden
*
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(Yacht yacht) {
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
updateLaylines(bg);
bg.setIsSelected(true);
selectedBoat = yacht;
} else {
bg.setIsSelected(false);
}
}
}
void setStage(Stage stage) {
this.stage = stage;
}
Stage getStage() {
return stage;
}
/**
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
* @param yachtId
* @return
*/
public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId);
}
}
@@ -1,191 +0,0 @@
package seng302.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
public class StartScreenController implements Initializable {
@FXML
private GridPane gridPane;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
private boolean switchedToRaceView = false;
private void setContentPane(String jfxUrl) {
try {
// get the main controller anchor pane (MainView.fxml)
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1
* second.
*/
public void startStream() {
// reset boolean for switch to race view
switchedToRaceView = false;
if (StreamParser.isStreamStatus()) {
streamButton.setVisible(false);
realTime.setVisible(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN);
timeTillLive.setText("Connecting...");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (StreamParser.isRaceStarted()) {
if (!switchedToRaceView) {
switchToRaceView();
}
timer.cancel();
}
if (StreamParser.isRaceFinished()) {
realTime.setText(StreamParser.getCurrentTimeString());
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0) {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long
.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long
.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = "-" + timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
} else {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long
.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long
.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
}
});
}
}, 0, 1000);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
}
}
public void switchToRaceView() {
StreamParser.boatLocations.clear();
switchedToRaceView = true;
setContentPane("/views/RaceView.fxml");
}
private void updateTeamList() {
ObservableList<Yacht> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
// check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
// add boats to the start screen list
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
} else { // else use StreamParser.getBoats()
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
}
teamList.refresh();
}
}
@@ -1,133 +0,0 @@
package seng302.fxObjects;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* Collection of annotations for boats.
*/
public class BoatAnnotations extends Group{
//Text offset constants
private static final double X_OFFSET_TEXT = 18d;
private static final double Y_OFFSET_TEXT_INIT = -29d;
private static final double Y_OFFSET_PER_TEXT = 12d;
//Background constants
private static final double TEXT_BUFFER = 3;
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
private static final double BACKGROUND_W = 125d;
private static final double BACKGROUND_ARC_SIZE = 10;
private Rectangle background = new Rectangle();
private Text teamNameObject;
private Text velocityObject;
private Text estTimeToNextMarkObject;
private Text legTimeObject;
private Yacht boat;
BoatAnnotations (Yacht boat, Color theme) {
super.setCache(true);
this.boat = boat;
background.setX(BACKGROUND_X);
background.setY(BACKGROUND_Y);
background.setWidth(BACKGROUND_W);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
background.setArcHeight(BACKGROUND_ARC_SIZE);
background.setArcWidth(BACKGROUND_ARC_SIZE);
background.setFill(new Color(1, 1, 1, 0.75));
background.setStroke(theme);
background.setStrokeWidth(2);
background.setCache(true);
background.setCacheHint(CacheHint.SPEED);
teamNameObject = getTextObject(boat.getShortName(), theme);
teamNameObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT);
velocityObject = getTextObject("0 m/s", theme);
velocityObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 2);
estTimeToNextMarkObject = getTextObject("Next mark: ", theme);
estTimeToNextMarkObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 3);
legTimeObject = getTextObject("Last mark: -", theme);
legTimeObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 4);
super.getChildren().addAll(background, teamNameObject, velocityObject, estTimeToNextMarkObject, legTimeObject);
}
/**
* Return a text object with caching and a color applied
*
* @param defaultText The default text to display
* @param fill The text fill color
* @return The text object
*/
private Text getTextObject(String defaultText, Color fill) {
Text text = new Text(defaultText);
text.setFill(fill);
text.setStrokeWidth(2);
text.setCacheHint(CacheHint.SPEED);
text.setCache(true);
return text;
}
void update () {
velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocity())));
if (boat.getTimeTillNext() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format
.format(boat.getTimeTillNext() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
} else {
estTimeToNextMarkObject.setText("Next mark: -");
}
if (boat.getMarkRoundTime() != null) {
DateFormat format = new SimpleDateFormat("mm:ss");
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundTime());
legTimeObject.setText("Last mark: " + elapsedTime);
}else {
legTimeObject.setText("Last mark: - ");
}
}
void setVisibile (boolean nameVisibility, boolean speedVisibility,
boolean estTimeVisibility, boolean lastMarkVisibility) {
int totalVisible = 0;
totalVisible = updateVisibility(nameVisibility, teamNameObject, totalVisible);
totalVisible = updateVisibility(speedVisibility, velocityObject, totalVisible);
totalVisible = updateVisibility(estTimeVisibility, estTimeToNextMarkObject, totalVisible);
totalVisible = updateVisibility(lastMarkVisibility, legTimeObject, totalVisible);
if (totalVisible != 0) {
background.setVisible(true);
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * totalVisible);
} else {
background.setVisible(false);
}
}
private int updateVisibility (boolean visibility, Text text, int totalVisible) {
if (visibility){
totalVisible ++;
text.setVisible(true);
text.setLayoutX(X_OFFSET_TEXT);
text.setLayoutY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * totalVisible);
} else {
text.setVisible(false);
}
return totalVisible;
}
}
@@ -1,356 +0,0 @@
package seng302.fxObjects;
import java.util.ArrayList;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import seng302.models.Yacht;
import seng302.GeometryUtils;
import seng302.controllers.CanvasController;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark;
import seng302.models.stream.StreamParser;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* 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 BoatGroup extends Group {
//Constants for drawing
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
//Variables for boat logic.
private boolean isStopped = true;
private double xIncrement;
private double yIncrement;
private long lastTimeValid = 0;
private Double lastRotation = 0.0;
private long framesToMove;
//Graphical objects
private Yacht boat;
private Group lineGroup = new Group();
private Polygon boatPoly;
private Wake wake;
private Line leftLayLine;
private Line rightLayline;
private Double distanceTravelled = 0.0;
private Point2D lastPoint;
private boolean destinationSet;
private BoatAnnotations boatAnnotations;
private Boolean isSelected = true; //All boats are initialised as selected
/**
* Creates a BoatGroup with the default triangular boat polygon.
*
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
*/
public BoatGroup(Yacht boat, Color color) {
destinationSet = false;
this.boat = boat;
initChildren(color);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0).
*
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
public BoatGroup(Yacht boat, Color color, double... points) {
destinationSet = false;
this.boat = boat;
initChildren(color, points);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren(Color color, double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(color);
boatPoly.setOnMouseEntered(event -> {
boatPoly.setFill(Color.FLORALWHITE);
boatPoly.setStroke(Color.RED);
});
boatPoly.setOnMouseExited(event -> {
boatPoly.setFill(color);
boatPoly.setStroke(Color.BLACK);
});
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
boatAnnotations = new BoatAnnotations(boat, color);
leftLayLine = new Line();
rightLayline = new Line();
wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(boatPoly, boatAnnotations);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param color The colour of the boat polygon and the trailing line.
*/
private void initChildren(Color color) {
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
* Moves the boat and its children annotations from its current coordinates by specified
* amounts.
*
* @param dx The amount to move the X coordinate by
* @param dy The amount to move the Y coordinate by
*/
private void moveGroupBy(double dx, double dy) {
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx);
boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy);
}
/**
* 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
*/
private void moveTo(double x, double y, double rotation) {
rotateTo(rotation);
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
boatAnnotations.setLayoutX(x);
boatAnnotations.setLayoutY(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
wake.rotate(rotation);
}
private void rotateTo(double rotation) {
boatPoly.getTransforms().setAll(new Rotate(rotation));
}
public void move() {
double dx = xIncrement * framesToMove;
double dy = yIncrement * framesToMove;
distanceTravelled += Math.abs(dx) + Math.abs(dy);
moveGroupBy(xIncrement, yIncrement);
framesToMove = framesToMove - 1;
if (framesToMove <= 0) {
isStopped = true;
}
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(boat.getColour());
l.setCache(true);
l.setCacheHint(CacheHint.SPEED);
lineGroup.getChildren().add(l);
}
if (destinationSet) {
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
}
}
wake.updatePosition();
}
/**
* Sets the destination of the boat and the headng it should have once it reaches
*
* @param newXValue The X co-ordinate the boat needs to move to.
* @param newYValue The Y co-ordinate the boat needs to move to.
* @param rotation Rotation to move graphics to.
* @param timeValid the time the position values are valid for
*/
public void setDestination(double newXValue, double newYValue, double rotation,
double groundSpeed, long timeValid, double frameRate) {
if (lastTimeValid == 0) {
lastTimeValid = timeValid - 200;
moveTo(newXValue, newYValue, rotation);
}
framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid))));
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
xIncrement = dx / framesToMove;
yIncrement = dy / framesToMove;
destinationSet = true;
rotateTo(rotation);
wake.setRotation(rotation, groundSpeed);
boat.setVelocity(groundSpeed);
lastTimeValid = timeValid;
isStopped = false;
lastRotation = rotation;
boatAnnotations.update();
}
/**
* 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(CanvasController 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 = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeometryUtils.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.
*/
if (boatLineFuncResult == windLineFuncResult) {
return true;
} else {
return false;
}
}
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
boatAnnotations.setVisible(isSelected);
setLayLinesVisible(isSelected);
}
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime, boolean trail, boolean wake) {
boatAnnotations.setVisibile(teamName, velocity, estTime, legTime);
this.wake.setVisible(wake);
this.lineGroup.setVisible(trail);
}
public void setLineGroupVisible(Boolean visible) {
lineGroup.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 Yacht getBoat() {
return boat;
}
/**
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
*
* @return An array containing all ID's associated with this RaceObject.
*/
public long getRaceId() {
return boat.getSourceID();
}
public Group getWake () {
return wake;
}
public Group getTrail() {
return lineGroup;
}
public Group getAnnotations() {
return boatAnnotations;
}
public Double getBoatLayoutX() {
return boatPoly.getLayoutX();
}
public Double getBoatLayoutY() {
return boatPoly.getLayoutY();
}
public boolean isStopped() {
return isStopped;
}
@Override
public String toString() {
return boat.toString();
}
}
@@ -1,168 +0,0 @@
package seng302.fxObjects;
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.MarkType;
import seng302.models.mark.SingleMark;
import seng302.GeometryUtils;
/**
* Grouping of javaFX objects needed to represent a Mark on screen.
*/
public class MarkGroup extends Group {
private static int MARK_RADIUS = 5;
private static int LINE_THICKNESS = 2;
private static double DASHED_GAP_LEN = 2d;
private static double DASHED_LINE_LEN = 5d;
private List<Mark> marks = new ArrayList<>();
private Mark mainMark;
/**
* Constructor for singleMark groups
* @param mark
* @param points
*/
public MarkGroup (SingleMark mark, Point2D points) {
marks.add(mark);
mainMark = mark;
Color color = Color.BLACK;
if (mark.getName().equals("Start")){
color = Color.GREEN;
} else if (mark.getName().equals("Finish")){
color = Color.RED;
}
Circle markCircle;
markCircle = new Circle(
points.getX(),
points.getY(),
MARK_RADIUS,
color
);
super.getChildren().add(markCircle);
}
public void addLaylines(Line line1, Line line2) {
super.getChildren().addAll(line1, line2);
}
public void removeLaylines() {
ArrayList<Node> toRemove = new ArrayList<>();
for(Node node : super.getChildren()) {
if (node instanceof Line) {
Line layLine = (Line) node;
/***
* OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh
*/
if (layLine.getStrokeWidth() == 0.5){
toRemove.add(layLine);
}
}
}
super.getChildren().removeAll(toRemove);
}
public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
marks.add(mark.getSingleMark1());
marks.add(mark.getSingleMark2());
mainMark = mark;
Color color = Color.BLACK;
if (mark.getName().equals("Start")){
color = Color.GREEN;
} else if (mark.getName().equals("Finish")){
color = Color.RED;
}
Circle markCircle;
markCircle = new Circle(
points1.getX(),
points1.getY(),
MARK_RADIUS,
color
);
super.getChildren().add(markCircle);
markCircle = new Circle(
points2.getX(),
points2.getY(),
MARK_RADIUS,
color
);
super.getChildren().add(markCircle);
Line line = new Line(
points1.getX(),
points1.getY(),
points2.getX(),
points2.getY()
);
line.setStrokeWidth(LINE_THICKNESS);
line.setStroke(color);
if (mark.getMarkType() == MarkType.OPEN_GATE) {
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
}
super.getChildren().add(line);
}
public void moveMarkTo (double x, double y, long raceId)
{
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
Circle markCircle = (Circle) super.getChildren().get(0);
//One of the test streams produced frequent, jittery movements. Added this as a fix.
if (Math.abs(markCircle.getCenterX() - x) > 5 || Math.abs(markCircle.getCenterY() - y) > 5) {
markCircle.setCenterX(x);
markCircle.setCenterY(y);
}
} else {
Circle markCircle1 = (Circle) super.getChildren().get(0);
Circle markCircle2 = (Circle) super.getChildren().get(1);
Line connectingLine = (Line) super.getChildren().get(2);
if (marks.get(0).getId() == raceId) {
if (Math.abs(markCircle1.getCenterX() - x) > 5 || Math.abs(markCircle1.getCenterY() - y) > 5) {
markCircle1.setCenterX(x);
markCircle1.setCenterY(y);
connectingLine.setStartX(markCircle1.getCenterX());
connectingLine.setStartY(markCircle1.getCenterY());
}
} else if (marks.get(1).getId() == raceId) {
if (Math.abs(markCircle2.getCenterX() - x) > 5 || Math.abs(markCircle2.getCenterY() - y) > 5) {
markCircle2.setCenterX(x);
markCircle2.setCenterY(y);
connectingLine.setEndX(markCircle2.getCenterX());
connectingLine.setEndY(markCircle2.getCenterY());
}
}
}
}
public boolean hasRaceId (int... raceIds) {
for (int id : raceIds)
for (Mark mark : marks)
if (id == mark.getId())
return true;
return false;
}
public long[] getRaceIds () {
long[] idArray = new long[marks.size()];
int i = 0;
for (Mark mark : marks)
idArray[i++] = mark.getId();
return idArray;
}
public Mark getMainMark() {
return mainMark;
}
}
@@ -0,0 +1,17 @@
package seng302.gameServer;
import seng302.model.Player;
public interface ClientConnectionDelegate {
/**
* A player has connected to the server
* @param serverToClientThread The player that has connected
*/
void clientConnected(ServerToClientThread serverToClientThread);
/**
* A player has disconnected from the server
* @param player The player that has disconnected
*/
void clientDisconnected(Player player);
}
@@ -0,0 +1,24 @@
package seng302.gameServer;
/**
* An enum describing the states of the game
* Created by wmu16 on 11/07/17.
*/
public enum GameStages {
LOBBYING(0),
PRE_RACE(1),
RACING(2),
FINISHED(3),
CANCELLED(4);
private long code;
GameStages(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,870 @@
package seng302.gameServer;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.*;
import seng302.model.*;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLParser;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.*;
/**
* A Static class to hold information about the current state of the game (model)
* Also contains logic for updating itself on regular time intervals on its own thread
* Created by wmu16 on 10/07/17.
*/
public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
private static Logger logger = LoggerFactory.getLogger(GameState.class);
static final int WARNING_TIME = 10 * -1000;
static final int PREPATORY_TIME = 5 * -1000;
private static final int TIME_TILL_START = 10 * 1000;
private static final Long POWERUP_TIMEOUT_MS = 10_000L;
private static final Integer STATE_UPDATES_PER_SECOND = 60;
private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
private static final Double MARK_COLLISION_DISTANCE = 15d;
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
private static final Double COLLISION_VELOCITY_PENALTY = 0.3;
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static Double speedMultiplier = 1d;
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
private static Boolean playerHasLeftFlag;
private static String hostIpAddress;
private static List<Player> players;
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 Integer maxPlayers = 8;
private static List<Token> allTokens;
private static List<Token> tokensInPlay;
private static List<NewMessageListener> newMessageListeners;
private static Map<Player, String> playerStringMap = new HashMap<>();
public GameState(String hostIpAddress) {
windDirection = 180d;
windSpeed = 10000d;
yachts = new HashMap<>();
tokensInPlay = new ArrayList<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
playerHasLeftFlag = false;
speedMultiplier = 1.0;
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
//set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis();
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
newMessageListeners = new ArrayList<>();
allTokens = makeTokens();
resetStartTime();
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);
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
/**
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
* race ideally
*
* @return A list of possible tokensInPlay for this race
*/
private ArrayList<Token> makeTokens() {
Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154);
Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382);
Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965);
Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214);
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
}
public static List<Player> getPlayers() {
return players;
}
public static List<Token> getTokensInPlay() {
return tokensInPlay;
}
public static void addPlayer(Player player) {
players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
+ " " + player.getYacht().getCountry();
playerStringMap.put(player, playerText);
}
public static void removePlayer(Player player) {
players.remove(player);
playerStringMap.remove(player);
}
public static void addYacht(Integer sourceId, ServerYacht yacht) {
yachts.put(sourceId, yacht);
}
public static void removeYacht(Integer yachtId) {
yachts.remove(yachtId);
}
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
public static GameStages getCurrentStage() {
return currentStage;
}
public static void setCurrentStage(GameStages currentStage) {
GameState.currentStage = currentStage;
}
public static MarkOrder getMarkOrder() {
return markOrder;
}
public static long getStartTime() {
return startTime;
}
public static void resetStartTime(){
startTime = System.currentTimeMillis() + TIME_TILL_START;
}
public static Double getWindDirection() {
return windDirection;
}
public static void setWindDirection(Double newWindDirection) {
windDirection = newWindDirection;
}
public static void setWindSpeed(Double newWindSpeed) {
windSpeed = newWindSpeed;
}
public static Double getWindSpeedMMS() {
return windSpeed;
}
public static Double getWindSpeedKnots() {
return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers
}
public static Map<Integer, ServerYacht> getYachts() {
return yachts;
}
/**
* Generates a new ID based off the size of current players + 1
*
* @return a playerID to be allocated to a new connetion
*/
public static Integer getUniquePlayerID() {
// TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
return yachts.size() + 1;
}
/**
* A thread to have the game state update itself at certain intervals
*/
@Override
public void run() {
while (currentStage != GameStages.FINISHED) {
try {
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
} catch (InterruptedException e) {
System.out.println("[GameState] interrupted exception");
}
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
update();
}
if (currentStage == GameStages.RACING) {
update();
}
}
}
public static void updateBoat(Integer sourceId, BoatAction actionType) {
ServerYacht playerYacht = yachts.get(sourceId);
switch (actionType) {
case VMG:
playerYacht.turnToVMG();
break;
case SAILS_IN:
playerYacht.toggleSailIn();
break;
case SAILS_OUT:
playerYacht.toggleSailIn();
break;
case TACK_GYBE:
playerYacht.tackGybe(windDirection);
break;
case UPWIND:
playerYacht.turnUpwind();
break;
case DOWNWIND:
playerYacht.turnDownwind();
break;
}
}
/**
* Randomly select a subset of tokensInPlay from a pre defined superset
* Broadasts a new race status message to show this update
*/
public static void spawnNewToken() {
Random random = new Random();
tokensInPlay.clear();
tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size())));
}
/**
* Called periodically in this GameState thread to update the GameState values.
* -Updates yachts velocity
* -Updates locations
* -Checks for collisions
* -Checks for progression
*
* -Also checks things like the end of the race and race start time etc
*/
public void update() {
Boolean raceFinished = true;
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);
checkPowerUpTimeout(yacht);
yacht.runAutoPilot();
yacht.updateLocation(timeInterval);
checkCollision(yacht);
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
checkForLegProgression(yacht);
raceFinished = false;
}
}
if (raceFinished) {
currentStage = GameStages.FINISHED;
}
}
private void checkPowerUpTimeout(ServerYacht yacht) {
if (yacht.getPowerUp() != null) {
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) {
yacht.powerDown();
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired");
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
}
}
}
/**
* Check if the yacht has crossed the course limit
*
* @param yacht the yacht to be tested
* @return a boolean value of if there is a boundary collision
*/
private static Boolean checkBoundaryCollision(ServerYacht yacht) {
for (int i = 0; i < courseLimit.size() - 1; i++) {
if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
}
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
}
return false;
}
/**
* Checks all tokensInPlay to see if a yacht has picked one up
* @return Token which was collided with
* @param serverYacht The yacht to check for collision with a token
*/
private static Token checkTokenPickUp(ServerYacht serverYacht) {
for (Token token : tokensInPlay) {
Double distance = GeoUtility.getDistance(token, serverYacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return token;
}
}
return null;
}
/**
* Checks for collision with other in game objects for the given serverYacht. To be called each
* update. If there is a collision, Notifies the server to send the appropriate messages out.
* Checks for these items in turn:
* - Other yachts
* - Marks
* - Boundary
* - Tokens
*
* @param serverYacht The server yacht to check collisions with
*/
public static void checkCollision(ServerYacht serverYacht) {
//Yacht Collision
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
Mark collidedMark = checkMarkCollision(serverYacht);
if (collidedYacht != null) {
GeoPoint originalLocation = serverYacht.getLocation();
serverYacht.setLocation(
calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
collidedYacht.setLocation(
calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
);
collidedYacht.setCurrentVelocity(
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
}
//Mark Collision
else if (collidedMark != null) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
}
//Boundary Collision
else if (checkBoundaryCollision(serverYacht)) {
serverYacht.setLocation(
calculateBounceBack(serverYacht, serverYacht.getLocation(),
BOUNCE_DISTANCE_YACHT)
);
serverYacht.setCurrentVelocity(
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
);
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
);
}
//Token Collision
Token collidedToken = checkTokenPickUp(serverYacht);
if (collidedToken != null) {
sendServerMessage(serverYacht.getSourceId(), serverYacht.getBoatName() + " has picked speed-up token");
tokensInPlay.remove(collidedToken);
serverYacht.powerUp(collidedToken.getTokenType());
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
.getTokenType());
notifyMessageListeners(MessageFactory.getRaceXML());
notifyMessageListeners(
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
}
}
private void updateVelocity(ServerYacht yacht) {
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
if (yacht.getPowerUp() != null) {
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
maxBoatSpeed *= 2;
}
}
Double currentVelocity = yacht.getCurrentVelocity();
// TODO: 15/08/17 remove magic numbers from these equations.
if (yacht.getSailIn()) {
if (currentVelocity < maxBoatSpeed - 500) {
yacht.changeVelocity(maxBoatSpeed / 100);
} else if (currentVelocity > maxBoatSpeed + 500) {
yacht.changeVelocity(-currentVelocity / 200);
} else {
yacht.setCurrentVelocity(maxBoatSpeed);
}
} else {
if (currentVelocity > 3000) {
yacht.changeVelocity(-currentVelocity / 200);
} else if (currentVelocity > 100) {
yacht.changeVelocity(-currentVelocity / 50);
} else if (currentVelocity <= 100) {
yacht.setCurrentVelocity(0d);
}
}
}
/**
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes of
* mark rounding
*
* @return A distance in metres. Returns -1 if there is no next mark
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
*/
private Double calcDistanceToCurrentMark(ServerYacht yacht) throws IndexOutOfBoundsException {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint location = yacht.getLocation();
if (currentMark.isGate()) {
Mark sub1 = currentMark.getSubMark(1);
Mark sub2 = currentMark.getSubMark(2);
Double distance1 = GeoUtility.getDistance(location, sub1);
Double distance2 = GeoUtility.getDistance(location, sub2);
if (distance1 < distance2) {
yacht.setClosestCurrentMark(sub1);
return distance1;
} else {
yacht.setClosestCurrentMark(sub2);
return distance2;
}
} else {
yacht.setClosestCurrentMark(currentMark.getSubMark(1));
return GeoUtility.getDistance(location, currentMark.getSubMark(1));
}
}
/** lobbyController.setPlayerListSource(clientLobbyList);
* 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any
* in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line
*
* @param yacht the current yacht to check for progression
*/
private void checkForLegProgression(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
Boolean hasProgressed;
if (currentMarkSeqID == 0) {
hasProgressed = checkStartLineCrossing(yacht);
} else if (markOrder.isLastMark(currentMarkSeqID)) {
hasProgressed = checkFinishLineCrossing(yacht);
} else if (currentMark.isGate()) {
hasProgressed = checkGateRounding(yacht);
} else {
hasProgressed = checkMarkRounding(yacht);
}
if (hasProgressed) {
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber());
}
yacht.incrementLegNumber();
sendMarkRoundingMessage(yacht);
logMarkRounding(yacht);
yacht.setHasPassedLine(false);
yacht.setHasEnteredRoundingZone(false);
yacht.setHasPassedThroughGate(false);
if (!markOrder.isLastMark(currentMarkSeqID)) {
yacht.incrementMarkSeqID();
}
}
}
/**
* If we pass the start line gate in the correct direction, progress
*
* @param yacht The current yacht to check for
*/
private Boolean checkStartLineCrossing(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.RACING);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed start line");
return true;
}
}
return false;
}
/**
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
* of the yacht if so. A visual representation of this algorithm can be seen on the Wiki under
* 'mark passing algorithm'
*
* @param yacht The current yacht to check for
*/
private Boolean checkMarkRounding(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
GeoPoint nextPoint = markOrder.getNextMark(currentMarkSeqID).getMidPoint();
GeoPoint prevPoint = markOrder.getPreviousMark(currentMarkSeqID).getMidPoint();
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
if (calcDistanceToCurrentMark(yacht) < ROUNDING_DISTANCE) {
yacht.setHasEnteredRoundingZone(true);
}
//In case current mark is a gate, loop through all marks just in case
for (Mark thisCurrentMark : currentMark.getMarks()) {
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
yacht.setHasPassedLine(true);
}
}
return yacht.hasPassedLine() && yacht.hasEnteredRoundingZone();
}
/**
* Checks if a gate line has been crossed and in the correct direction
*
* @param yacht The current yacht to check for
*/
private Boolean checkGateRounding(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
//We have crossed the line
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
//Check we cross the line in the correct direction
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setHasPassedThroughGate(true);
}
}
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
if (yacht.hasPassedThroughGate()) {
//Check if we need to round this gate after passing through
if (prevMarkSide == nextMarkSide) {
return checkMarkRounding(yacht);
} else {
return true;
}
}
return false;
}
/**
* If we pass the finish gate in the correct direction
*
* @param yacht The current yacht to check for
*/
private Boolean checkFinishLineCrossing(ServerYacht yacht) {
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
GeoPoint lastLocation = yacht.getLastLocation();
GeoPoint location = yacht.getLocation();
Mark mark1 = currentMark.getSubMark(1);
Mark mark2 = currentMark.getSubMark(2);
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
if (crossedLine > 0) {
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
yacht.setClosestCurrentMark(mark1);
yacht.setBoatStatus(BoatStatus.FINISHED);
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed finish line");
return true;
}
}
return false;
}
/**
* Handles player customization.
*
* @param playerID The ID of the player being modified.
* @param requestType the type of player customization the player wants
* @param customizeData the data related to the customization (color, name, shape)
*/
public static void customizePlayer(long playerID, CustomizeRequestType requestType,
byte[] customizeData) {
ServerYacht playerYacht = yachts.get((int) playerID);
if (requestType.equals(CustomizeRequestType.NAME)) {
String name = new String(customizeData);
playerYacht.setBoatName(name);
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
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);
}
}
private static Mark checkMarkCollision(ServerYacht yacht) {
Set<Mark> marksInRace = GameState.getMarks();
for (Mark mark : marksInRace) {
if (GeoUtility.getDistance(yacht.getLocation(), mark)
<= MARK_COLLISION_DISTANCE) {
return mark;
}
}
return null;
}
/**
* Calculate the new position of the boat after it has had a collision
*
* @return The boats new position
*/
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith,
Double bounceDistance) {
Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith);
// Invert heading
heading -= 180;
Integer newHeading = Math.floorMod(heading.intValue(), 360);
return GeoUtility
.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
}
/**
* 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.
*
* @return yacht to compare to all other yachts.
*/
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
for (ServerYacht otherYacht : GameState.getYachts().values()) {
if (otherYacht != yacht) {
Double distance = GeoUtility
.getDistance(otherYacht.getLocation(), yacht.getLocation());
if (distance < YACHT_COLLISION_DISTANCE) {
return otherYacht;
}
}
}
return null;
}
private void sendMarkRoundingMessage(ServerYacht yacht) {
Integer sourceID = yacht.getSourceId();
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK;
Mark roundingMark = yacht.getClosestCurrentMark();
// TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status.
Message markRoundingMessage = new MarkRoundingMessage(0, 0,
sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType,
currentMarkSeqID + 1);
notifyMessageListeners(markRoundingMessage);
}
private static void notifyMessageListeners(Message message) {
for (NewMessageListener ml : newMessageListeners) {
ml.notify(message);
}
}
private void logMarkRounding(ServerYacht yacht) {
Mark roundingMark = yacht.getClosestCurrentMark();
logger.debug(
String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(),
roundingMark.getSourceID()));
}
public static void sendServerMessage(Integer messageType, String message) {
notifyMessageListeners(new ChatterMessage(
messageType, "SERVER: " + message
));
}
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
String chatterText = chatterMessage.getMessage();
String[] words = chatterText.split("\\s+");
if (words.length > 2 && isHost) {
switch (words[2].trim()) {
case "/speed":
try {
setSpeedMultiplier(Double.valueOf(words[3]));
sendServerMessage(chatterMessage.getMessage_type(),
"Speed modifier set to x" + words[3]);
} catch (Exception e) {
Logger logger = LoggerFactory.getLogger(GameState.class);
logger.error("cannot parse >speed value");
}
return;
case "/finish":
sendServerMessage(chatterMessage.getMessage_type(),
"Game will now finish");
endRace();
return;
}
}
notifyMessageListeners(chatterMessage);
}
public static void addMessageEventListener(NewMessageListener listener) {
newMessageListeners.add(listener);
}
public static void setCustomizationFlag() {
customizationFlag = true;
}
public static Boolean getCustomizationFlag() {
return customizationFlag;
}
public static void resetCustomizationFlag() {
customizationFlag = false;
}
public static void setPlayerHasLeftFlag(Boolean flag) {
playerHasLeftFlag = flag;
}
public static Boolean getPlayerHasLeftFlag() {
return playerHasLeftFlag;
}
public static Integer getNumberOfPlayers(){
Integer numPlayers = 1;
for(Player p : getPlayers()){
if(p.getSocket().isConnected()){
numPlayers++;
}
}
return numPlayers;
}
public static Integer getCapacity(){
return maxPlayers;
}
public static void setMaxPlayers(Integer newMax){
maxPlayers = newMax;
}
public static void endRace () {
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
currentStage = GameStages.FINISHED;
}
public static void setSpeedMultiplier (double multiplier) {
speedMultiplier = multiplier;
}
public static double getSpeedMultiplier () {
return speedMultiplier;
}
}
@@ -0,0 +1,90 @@
package seng302.gameServer;
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;
/**
* Send Heartbeat messages to connected player at a specified interval
* Will call .clientDisconnected on the delegate when a heartbeat 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;
private Stack<Player> disconnectedPlayers;
public HeartbeatThread(ClientConnectionDelegate delegate){
this.delegate = delegate;
seqNum = 0;
disconnectedPlayers = new Stack<>();
Thread thread = new Thread(this, "HeartBeat");
thread.start();
}
/**
* A player has lost connection to the server
* The player is added to a stack so that the delegate
* can be notified
*
* @param player The player that has disconnected
*/
private void playerLostConnection(Player player){
disconnectedPlayers.push(player);
}
/**
* Sends a heartbeat message to each connected player
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
try {
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()) {
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
try {
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
} catch (IOException e) {
playerLostConnection(player);
}
}
updateDelegate();
seqNum++;
} catch (NullPointerException ne) {
logger.debug("Socket closed between checking for connection and sending heartbeat");
}
}
/**
* Notifies the delegate about
* each disconnected player
*/
private void updateDelegate() {
while (!disconnectedPlayers.empty()){
delegate.clientDisconnected(disconnectedPlayers.pop());
}
}
public void run(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
sendHeartbeatToAllPlayers();
}
}, 0, HEARTBEAT_PERIOD);
}
}
@@ -0,0 +1,368 @@
package seng302.gameServer;
import java.io.IOException;
import java.io.StringReader;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.Message;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17.
*/
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 MAX_WIND_SPEED = 12000;
private static final int MIN_WIND_SPEED = 8000;
private boolean terminated;
private Thread thread;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
private static Integer capacity;
private void startAdvertisingServer() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
XMLGenerator generator = new XMLGenerator();
try {
db = dbf.newDocumentBuilder();
String regatta = generator.getRegattaAsXml();
StringReader stringReader = new StringReader(regatta);
InputSource is = new InputSource(stringReader);
doc = db.parse(is);
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.warn("Couldn't load race regatta");
return;
}
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
Integer capacity = GameState.getCapacity();
Integer numPlayers = GameState.getNumberOfPlayers();
Integer spacesLeft = capacity - numPlayers;
// No spaces left on server
if (spacesLeft < 1) {
return;
}
// Start advertising server
try {
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setCapacity(capacity).setNumberOfPlayers(numPlayers);
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
} catch (IOException e) {
logger.warn("Could not register server");
}
}
public MainServerThread() {
new GameState("localhost");
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
logger.trace("IO error in server thread handler upon trying to make new server socket",
0);
}
startAdvertisingServer();
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMessageEventListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
startUpdatingWind();
startSpawningTokens();
thread.start();
}
public void run() {
new HeartbeatThread(this);
new ServerListenThread(serverSocket, this);
//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) {
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
.getCustomizationFlag()) {
sendSetupMessages();
GameState.resetCustomizationFlag();
}
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
sendBoatLocations();
}
//RACING
if (GameState.getCurrentStage() == GameStages.RACING) {
sendBoatLocations();
}
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
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 {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.terminate();
}
serverSocket.close();
} catch (IOException e) {
System.out.println("IO error in server thread handler upon closing socket");
}
}
private void sendBoatLocations() {
for (ServerYacht serverYacht : GameState.getYachts().values()) {
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
}
}
private void sendSetupMessages() {
broadcastMessage(MessageFactory.getRaceXML());
broadcastMessage(MessageFactory.getRegattaXML());
broadcastMessage(MessageFactory.getBoatXML());
}
private void broadcastMessage(Message message) {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendMessage(message);
}
}
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());
}
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
private static void startUpdatingWind(){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
updateWind();
}
}, 0, 500);
}
/**
* Start spawning coins every 60s after the first minute
*/
private void startSpawningTokens() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
GameState.spawnNewToken();
broadcastMessage(MessageFactory.getRaceXML());
}
}, 10000, 60000);
}
/**
* A client has tried to connect to the server
*
* @param serverToClientThread The player that connected
*/
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
}
/**
* A player has left the game, remove the player from the GameState
*
* @param player The player that left
*/
@Override
public void clientDisconnected(Player player) {
logger.debug("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player);
ServerToClientThread closedConnection = null;
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
if (serverToClientThread.getSocket() == player.getSocket()) {
closedConnection = serverToClientThread;
} else if (GameState.getCurrentStage() != GameStages.RACING){
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(MessageFactory.getRaceStatusMessage());
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
}
}
}, 0, 500);
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
sendSetupMessages();
}
}
public void terminate() {
terminated = true;
}
/**
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
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
double DISTANCE_FACTOR = 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 * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
}
yacht.setLocation(spawnMark);
boatIndex++;
}
}
}
@@ -0,0 +1,131 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.BoatLocationMessage;
import seng302.gameServer.messages.BoatSubMessage;
import seng302.gameServer.messages.RaceStartNotificationType;
import seng302.gameServer.messages.RaceStartStatusMessage;
import seng302.gameServer.messages.RaceStatus;
import seng302.gameServer.messages.RaceStatusMessage;
import seng302.gameServer.messages.RaceType;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
/**
* A Class for interfacing between the data we have in the GameState to the messages we need to send
* through the MainServerThread.
*
* WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started)
* // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯
* Created by wmu16 on 29/08/17.
*/
/*
Ideally this class would be created with an instance of the GameState (I tried implementing this for
a bit) but it was too difficult to properly make GameState non static without doing some proper
re working. To do later.
*/
public class MessageFactory {
private static XMLGenerator xmlGenerator = new XMLGenerator();
public static RaceStartStatusMessage getRaceStartStatusMessage() {
return new RaceStartStatusMessage(
1,
GameState.getStartTime(),
1,
RaceStartNotificationType.SET_RACE_START_TIME);
}
public static RaceStatusMessage getRaceStatusMessage() {
// variables taken from GameServerThread
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
ServerYacht y = player.getYacht();
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
y.getLegNumber(),
0, 0, 1234L,
1234L);
boatSubMessages.add(m);
}
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
raceStatus = RaceStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
raceStatus = RaceStatus.PRESTART;
if (timeTillStart > GameState.WARNING_TIME) {
raceStatus = RaceStatus.WARNING;
}
if (timeTillStart > GameState.PREPATORY_TIME) {
raceStatus = RaceStatus.PREPARATORY;
}
} else {
raceStatus = RaceStatus.STARTED;
}
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
GameState.getWindDirection(),
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages);
}
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
return new BoatLocationMessage(
yacht.getSourceId(),
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
yacht.getLocation().getLat(),
yacht.getLocation().getLng(),
yacht.getHeading(),
yacht.getCurrentVelocity().longValue());
}
public static XMLMessage getRaceXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
XMLMessage raceXMLMessage = new XMLMessage(
xmlGenerator.getRaceAsXml(),
XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
return raceXMLMessage;
}
public static XMLMessage getRegattaXML() {
//@TODO calculate lat/lng values
return new XMLMessage(
xmlGenerator.getRegattaAsXml(),
XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
}
public static XMLMessage getBoatXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new XMLMessage(
xmlGenerator.getBoatsAsXml(),
XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
}
}
@@ -0,0 +1,173 @@
package seng302.gameServer;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* Advertises the game server on the local network
*/
public class ServerAdvertiser {
/*
Our service name & protocol
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
Where Service is unique on the network, and protocol is usually _tcp.
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
the lookup should be performed using an IP multicast query on the local IP network.
Read this before changing any of the following values
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
*/
private static String SERVICE = "_partyatsea";
private static String PROTOCOL = "_tcp";
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
private static ServerAdvertiser instance = null;
private static JmDNS jmdnsInstance = null;
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
private Hashtable<String ,String> props;
private ServerAdvertiser() throws IOException{
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
props = new Hashtable<>();
props.put("map", "");
props.put("spacesLeft", "0");
props.put("capacity", "0");
props.put("players", "0");
}
/**
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
* @return A ServerAdvertiser Instance
* @throws IOException If there was an exception creating the instance
*/
public static ServerAdvertiser getInstance() throws IOException {
if (instance == null){
instance = new ServerAdvertiser();
}
return instance;
}
/**
* Set the map name and broadcast an update on the network
* @param mapName The new map name
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setMapName(String mapName){
props.replace("map", mapName);
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Set the number of players on the server and broadcast an update on the network
* @param numPlayers The number of players on the server
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
props.replace("players", numPlayers.toString());
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Set the max capacity of the server and broadcast an update on the network
* @param capacity The maximum capacity of the server
* @return The current ServerAdvertiser instance
*/
public ServerAdvertiser setCapacity(Integer capacity){
props.replace("capacity", capacity.toString());
if (serviceInfo != null){
serviceInfo.setText(props);
}
return instance;
}
/**
* Register this service on the network
*
* Note: other parameters (map name/spaces left etc) are set after the
* service has been registered
* @param portNo The servers port number
* @param serverName The servers name
*/
public void registerGame(Integer portNo, String serverName) {
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
try {
jmdnsInstance.registerService(serviceInfo);
} catch (IOException e) {
System.out.println("Failed");
}
}
}, 0);
}
/**
* Unregister the service
*/
public void unregister(){
if (serviceInfo != null)
jmdnsInstance.unregisterService(serviceInfo);
}
/**
* Gets the local host ip address.
*
* @return the localhost ip address
*/
public static String getLocalHostIp() {
String ipAddress = null;
try {
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface ni = e.nextElement();
if (ni.isLoopback())
continue;
if(ni.isPointToPoint())
continue;
if(ni.isVirtual())
continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while(addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if(address instanceof Inet4Address) { // skip all ipv6
ipAddress = address.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (ipAddress == null) {
System.out.println("[HOST] Cannot obtain local host ip address.");
}
return ipAddress;
}
}
@@ -0,0 +1,83 @@
package seng302.gameServer;
public class ServerDescription {
private Integer capacity;
private String address;
private Integer portNum;
private String serverName;
private String mapName;
private Integer numPlayers;
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
this.serverName = serverName;
this.mapName = mapName;
this.numPlayers = numPlayers;
this.address = address;
this.portNum = portNum;
this.capacity = capacity;
}
public String getName() {
return serverName;
}
public String getMapName() {
return mapName;
}
public Integer portNumber() {
return portNum;
}
public String getAddress(){
return address;
}
public Integer getNumPlayers() {
return numPlayers;
}
public Integer getCapacity(){
return capacity;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
return false;
}
final ServerDescription other = (ServerDescription) obj;
if (!this.getAddress().equals(other.getAddress()) ) {
return false;
}
if (!this.portNumber().equals(other.portNumber())){
return false;
}
if (!this.getMapName().equals(other.getMapName())){
return false;
}
if (!this.getName().equals(other.getName())){
return false;
}
if (!this.getCapacity().equals(other.getCapacity())){
return false;
}
return true;
}
@Override
public int hashCode() {
return this.getName().hashCode() + this.getAddress().hashCode() +
this.portNumber().hashCode() + this.getMapName().hashCode();
}
}
@@ -0,0 +1,45 @@
package seng302.gameServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* A class for a thread to listen to connections
* Created by wmu16 on 11/07/17.
*/
public class ServerListenThread implements Runnable {
private ServerSocket serverSocket;
private ClientConnectionDelegate delegate;
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
this.serverSocket = serverSocket;
this.delegate = delegate;
Thread thread = new Thread(this, "ServerListen");
thread.start();
}
/**
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
*/
private void acceptConnection() {
try {
Socket thisClient = serverSocket.accept();
if (thisClient != null && GameState.getCurrentStage().equals(GameStages.LOBBYING)) {
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
delegate.clientConnected(thisConnection);
} else {
thisClient.close();
}
} catch (IOException e) {
e.getMessage();
}
}
public void run(){
while (serverSocket != null && !serverSocket.isClosed()){
acceptConnection();
}
}
}
@@ -0,0 +1,46 @@
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;
import seng302.model.stream.packets.StreamPacket;
public class ServerPacketParser {
public static BoatAction extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return BoatAction.getType((int) actionTypeValue);
}
public static ClientType extractClientType(StreamPacket packet){
byte[] payload = packet.getPayload();
long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return ClientType.getClientType((int) value);
}
public static CustomizeRequestType extractCustomizationType(StreamPacket packet) {
byte[] payload = packet.getPayload();
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))
);
}
}
@@ -0,0 +1,347 @@
package seng302.gameServer;
import java.io.BufferedReader;
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.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.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.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
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.RaceXMLTemplate;
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 {
/**
* Called to notify listeners when this thread receives a connection correctly.
*/
@FunctionalInterface
interface ConnectionListener {
void notifyConnection ();
}
// TODO: 17/08/17 this is only temporary disconnects should be handled consistently
@FunctionalInterface
interface DisconnectListener {
void notifyDisconnect (Player player);
}
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
private Thread thread;
private InputStream is;
private OutputStream os;
private Socket socket;
private ByteArrayOutputStream crcBuffer;
private Integer seqNo;
private Integer sourceId;
private ClientType clientType;
private Boolean isRegistered = false;
private Boolean isHost = false;
private XMLGenerator xmlGenerator;
private List<ConnectionListener> connectionListeners = new ArrayList<>();
private DisconnectListener disconnectListener;
private ServerYacht yacht;
private Player player;
public ServerToClientThread(Socket socket) {
this.socket = socket;
seqNo = 0;
try{
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
return;
}
thread = new Thread(this, "ServerToClient");
thread.start();
}
public Integer getSourceId() {
return sourceId;
}
private void setUpPlayer(){
BufferedReader fn;
String fName = "";
BufferedReader ln;
String lName = "";
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()));
ServerYacht yacht = new ServerYacht(
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
);
player = new Player(socket, yacht);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(player);
}
private void completeRegistration(ClientType clientType) throws IOException {
// Fail if not a player
if (!clientType.equals(ClientType.PLAYER)){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
os.write(responseMessage.getBuffer());
return;
}
if (GameState.getPlayers().size() >= GameState.getCapacity()){
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
os.write(responseMessage.getBuffer());
return;
}
Integer sourceId = GameState.getUniquePlayerID();
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
this.clientType = clientType;
this.sourceId = sourceId;
isRegistered = true;
os.write(responseMessage.getBuffer());
setUpPlayer();
for (ConnectionListener listener : connectionListeners) {
listener.notifyConnection();
}
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while (socket.isConnected() && !socket.isClosed()) {
try {
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if (sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = Message.bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION:
BoatAction actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.updateBoat(sourceId, actionType);
break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
CustomizeRequestType requestType = ServerPacketParser
.extractCustomizationType(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.customizePlayer(sourceID, requestType,
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;
}
} else {
logger.warn("Packet has been dropped", 1);
}
}
} catch (Exception e) {
closeSocket();
GameState.setPlayerHasLeftFlag(true);
return;
}
}
GameState.setPlayerHasLeftFlag(true);
logger.warn("Closed serverToClientThread" + thread, 1);
}
public void sendSetupMessages() {
xmlGenerator = new XMLGenerator();
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
xmlGenerator.setRaceTemplate(race);
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
sendMessage(xmlMessage);
}
private void closeSocket() {
try {
socket.close();
} catch (IOException e) {
System.out.println("IO error in server thread upon trying to close socket");
}
}
public Boolean isSocketOpen() {
return !socket.isClosed();
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = is.read();
crcBuffer.write(currentByte);
} catch (SocketException se) {
disconnectListener.notifyDisconnect(this.player);
} catch (IOException e) {
disconnectListener.notifyDisconnect(this.player);
logger.warn("Socket read failed", 1);
}
if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
public void sendMessage(Message message) {
try {
os.write(message.getBuffer());
} catch (SocketException e) {
logger.warn("Player " + sourceId + " side socket disconnected", 1);
} catch (IOException e) {
logger.warn("Message send failed", 1);
}
}
private int getSeqNo() {
seqNo++;
return seqNo;
}
public Thread getThread() {
return thread;
}
public Socket getSocket() {
return socket;
}
public ServerYacht getYacht() {
return yacht;
}
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
public void terminate () {
try {
socket.close();
} catch (IOException ioe) {
logger.warn("IOException attempting to terminate serverToClientThread " + this.thread);
}
}
public void addDisconnectListener(DisconnectListener disconnectListener) {
this.disconnectListener = disconnectListener;
}
public void setAsHost() {
isHost = true;
}
}
@@ -0,0 +1,39 @@
package seng302.gameServer.messages;
import java.util.HashMap;
import java.util.Map;
/**
* Created by kre39 on 12/07/17.
*/
public enum BoatAction {
VMG(1),
SAILS_IN(2),
SAILS_OUT(3),
TACK_GYBE(4),
UPWIND(5),
DOWNWIND(6),
MAINTAIN_HEADING(7);
private final int type;
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
static {
for (BoatAction type : BoatAction.values()) {
intToTypeMap.put(type.getValue(), type);
}
}
BoatAction(int type){
this.type = type;
}
public static BoatAction getType(int value) {
return intToTypeMap.get(value);
}
public int getValue() {
return this.type;
}
}
@@ -0,0 +1,29 @@
package seng302.gameServer.messages;
/**
* Created by kre39 on 12/07/17.
*/
public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1;
private BoatAction actionType;
public BoatActionMessage(BoatAction actionType) {
this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putInt(actionType.getValue(), 1);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -1,9 +1,7 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.IOException;
import java.nio.channels.SocketChannel;
public class BoatLocationMessage extends Message { public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56; private final int MESSAGE_SIZE = 56;
private long messageVersionNumber; private long messageVersionNumber;
@@ -31,6 +29,7 @@ public class BoatLocationMessage extends Message {
/** /**
* Describes the location, altitude and sensor data from the boat. * Describes the location, altitude and sensor data from the boat.
*
* @param sourceId ID of the boat * @param sourceId ID of the boat
* @param sequenceNum Sequence number of the message * @param sequenceNum Sequence number of the message
* @param latitude The boats latitude * @param latitude The boats latitude
@@ -38,8 +37,8 @@ public class BoatLocationMessage extends Message {
* @param heading The boats heading * @param heading The boats heading
* @param boatSpeed The boats speed * @param boatSpeed The boats speed
*/ */
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude,
boatSpeed /= 10; double heading, long boatSpeed) {
messageVersionNumber = 1; messageVersionNumber = 1;
time = System.currentTimeMillis(); time = System.currentTimeMillis();
this.sourceId = sourceId; this.sourceId = sourceId;
@@ -53,7 +52,7 @@ public class BoatLocationMessage extends Message {
this.roll = 0; this.roll = 0;
this.boatSpeed = boatSpeed; this.boatSpeed = boatSpeed;
this.COG = 2; this.COG = 2;
this.SOG = boatSpeed ; this.SOG = boatSpeed;
this.apparentWindSpeed = 0; this.apparentWindSpeed = 0;
this.apparentWindAngle = 0; this.apparentWindAngle = 0;
this.trueWindSpeed = 0; this.trueWindSpeed = 0;
@@ -64,74 +63,10 @@ public class BoatLocationMessage extends Message {
this.rudderAngle = 0; this.rudderAngle = 0;
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
}
/**
* Convert binary latitude or longitude to floating point number
* @param binaryPackedLatLon Binary packed lat OR lon
* @return Floating point lat/lon
*/
public static double binaryPackedToLatLon(long binaryPackedLatLon){
return (double)binaryPackedLatLon * 180.0 / 2147483648.0;
}
/**
* Convert binary packed heading to floating point number
* @param binaryPackedHeading Binary packed heading
* @return heading as a decimal
*/
public static double binaryPackedHeadingToDouble(long binaryPackedHeading){
return (double)binaryPackedHeading * 360.0 / 65536.0;
}
/**
* Convert binary packed wind angle to floating point number
* @param binaryPackedWindAngle Binary packed wind angle
* @return wind angle as a decimal
*/
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){
return (double)binaryPackedWindAngle*180.0/32768.0;
}
/**
* Convert a latitude or longitude to a binary packed long
* @param latLon A floating point latitude/longitude
* @return A binary packed lat/lon
*/
public static long latLonToBinaryPackedLong(double latLon){
return (long)((536870912 * latLon) / 45);
}
/**
* Convert a heading to a binary packed long
* @param heading A floating point heading
* @return A binary packed heading
*/
public static long headingToBinaryPackedLong(double heading){
return (long)((8192*heading)/45);
}
/**
* Convert a wind angle to a binary packed long
* @param windAngle Floating point wind angle
* @return A binary packed wind angle
*/
public static long windAngleToBinaryPackedLong(double windAngle){
return (long)((8192*windAngle)/45);
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException{
allocateBuffer(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
long headingToSend = (long)((heading/360.0) * 65535.0); long headingToSend = (long) ((heading / 360.0) * 65535.0);
putByte((byte) messageVersionNumber); putByte((byte) messageVersionNumber);
putInt(time, 6); putInt(time, 6);
@@ -158,7 +93,70 @@ public class BoatLocationMessage extends Message {
writeCRC(); writeCRC();
rewind(); rewind();
}
outputStream.write(getBuffer()); /**
* Convert binary latitude or longitude to floating point number
*
* @param binaryPackedLatLon Binary packed lat OR lon
* @return Floating point lat/lon
*/
public static double binaryPackedToLatLon(long binaryPackedLatLon) {
return (double) binaryPackedLatLon * 180.0 / 2147483648.0;
}
/**
* Convert binary packed heading to floating point number
*
* @param binaryPackedHeading Binary packed heading
* @return heading as a decimal
*/
public static double binaryPackedHeadingToDouble(long binaryPackedHeading) {
return (double) binaryPackedHeading * 360.0 / 65536.0;
}
/**
* Convert binary packed wind angle to floating point number
*
* @param binaryPackedWindAngle Binary packed wind angle
* @return wind angle as a decimal
*/
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle) {
return (double) binaryPackedWindAngle * 180.0 / 32768.0;
}
/**
* Convert a latitude or longitude to a binary packed long
*
* @param latLon A floating point latitude/longitude
* @return A binary packed lat/lon
*/
public static long latLonToBinaryPackedLong(double latLon) {
return (long) ((536870912 * latLon) / 45);
}
/**
* Convert a heading to a binary packed long
*
* @param heading A floating point heading
* @return A binary packed heading
*/
public static long headingToBinaryPackedLong(double heading) {
return (long) ((8192 * heading) / 45);
}
/**
* Convert a wind angle to a binary packed long
*
* @param windAngle Floating point wind angle
* @return A binary packed wind angle
*/
public static long windAngleToBinaryPackedLong(double windAngle) {
return (long) ((8192 * windAngle) / 45);
}
@Override
public int getSize() {
return MESSAGE_SIZE;
} }
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* The current status of a boat * The current status of a boat
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -0,0 +1,46 @@
package seng302.gameServer.messages;
/**
* Created by kre39 on 20/07/17.
*/
public class ChatterMessage extends Message {
private final long MESSAGE_VERSION_NUMBER = 1;
private final int MESSAGE_SIZE = 3;
private int message_type;
private int message_size = 21;
private String message;
public ChatterMessage(int message_type, String message) {
byte[] byteMessage = message.getBytes();
this.message_type = message_type;
this.message_size = byteMessage.length;
this.message = message;
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putByte((byte) MESSAGE_VERSION_NUMBER);
putInt(message_type, 1);
putInt(message_size, 1);
putBytes(byteMessage);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE + message_size;
}
public String getMessage() {
return message;
}
public int getMessage_type() {
return message_type;
}
}
@@ -0,0 +1,33 @@
package seng302.gameServer.messages;
public enum ClientType {
SPECTATOR(0x00),
PLAYER(0x01),
CONTROL_TUTORIAL(0x02),
GHOST_MODE(0x03);
private int type;
ClientType(int type){
this.type = type;
}
public int getCode(){
return type;
}
public static ClientType getClientType(int typeCode){
switch (typeCode){
case 0x00:
return SPECTATOR;
case 0x01:
return PLAYER;
case 0x02:
return CONTROL_TUTORIAL;
case 0x03:
return GHOST_MODE;
default:
return PLAYER;
}
}
}
@@ -0,0 +1,35 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public class CustomizeRequestMessage extends Message {
private static int MESSAGE_LENGTH = 6;
//Message fields
private CustomizeRequestType customizeType;
private Integer payloadLength;
public CustomizeRequestMessage(CustomizeRequestType customizeType, double sourceID,
byte[] payload) {
payloadLength = payload.length;
setHeader(new Header(MessageType.CUSTOMIZATION_REQUEST, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt((int) sourceID, 4);
putInt((int) customizeType.getType(), 2);
putBytes(payload);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_LENGTH + payloadLength; // placeholder
}
}
@@ -0,0 +1,31 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public enum CustomizeRequestType {
NAME(0x00),
COLOR(0x01),
SHAPE(0x02);
private int type;
CustomizeRequestType(int type) {
this.type = type;
}
int getType() {
return this.type;
}
public static CustomizeRequestType getRequestType(int typeCode) {
switch (typeCode) {
case 0x00:
return NAME;
case 0x01:
return COLOR;
case 0x02:
return SHAPE;
default:
return null;
}
}
}
@@ -0,0 +1,28 @@
package seng302.gameServer.messages;
/**
* Created by ajm412 on 14/08/17.
*/
public class CustomizeResponseMessage extends Message {
private static int MESSAGE_LENGTH = 2;
public CustomizeResponseMessage(CustomizeResponseType responseType) {
setHeader(new Header(MessageType.CUSTOMIZATION_RESPONSE, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(responseType.getType(), 2);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_LENGTH; // placeholder
}
}
@@ -0,0 +1,34 @@
package seng302.gameServer.messages;
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
public enum CustomizeResponseType {
SUCCESS(0x00),
FAILURE(0x01),
FAILURE_MALFORMED_DATA(0x02),
FAILURE_INCOMPATIBLE(0x03);
private int type;
CustomizeResponseType(int type) {
this.type = type;
}
int getType() {
return this.type;
}
public static CustomizeResponseType getResponseType(int typeCode) {
switch (typeCode) {
case 0x00:
return SUCCESS;
case 0x01:
return FAILURE;
case 0x02:
return FAILURE_MALFORMED_DATA;
case 0x03:
return FAILURE_INCOMPATIBLE;
default:
return null;
}
}
}
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
public enum DeviceType { public enum DeviceType {
UNKNOWN(0), UNKNOWN(0),
@@ -1,9 +1,6 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
public class Header { public class Header {
// From API spec // From API spec
@@ -43,10 +40,21 @@ public class Header {
buff.position(buffPos); buff.position(buffPos);
} }
/**
* Reset the buffer
*/
public void reset(){
buffPos = 0;
buff.clear();
buff.position(buffPos);
}
/** /**
* @return a ByteBuffer containing the message header * @return a ByteBuffer containing the message header
*/ */
public ByteBuffer getByteBuffer(){ public ByteBuffer getByteBuffer(){
reset();
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2); putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
@@ -1,32 +1,13 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class Heartbeat extends Message { public class Heartbeat extends Message {
private final int MESSAGE_SIZE = 4; private final int MESSAGE_SIZE = 4;
private int seqNo;
/** /**
* Heartbeat from the AC35 Streaming data spec * Heartbeat from the AC35 Streaming data spec
* @param seqNo Increment every time a message is sent * @param seqNo Increment every time a message is sent
*/ */
public Heartbeat(int seqNo){ public Heartbeat(int seqNo){
this.seqNo = seqNo;
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
allocateBuffer(); allocateBuffer();
@@ -36,7 +17,11 @@ public class Heartbeat extends Message {
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
@Override
public int getSize() {
return MESSAGE_SIZE;
}
} }
@@ -1,10 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class MarkRoundingMessage extends Message{ public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1; private final long MESSAGE_VERSION_NUMBER = 1;
@@ -18,13 +12,21 @@ public class MarkRoundingMessage extends Message{
private RoundingSide roundingSide; private RoundingSide roundingSide;
private long markId; private long markId;
/** /**
* This message is sent when a boat passes a mark, start line, or finish line * This message is sent when a boat passes a mark, start line, or finish line
* The purpose of this is to record the time when yachts cross marks * The purpose of this is to record the time when yachts cross marks
* @param ackNumber ackNumber
* @param raceId raceId
* @param sourceId boatSourceId
* @param roundingBoatStatus roundingBoatStatus
* @param roundingSide roundingSide
* @param markId markId
* @param markType .
*/ */
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
RoundingSide roundingSide, int markId){ RoundingSide roundingSide, MarkType markType, int markId) {
this.time = System.currentTimeMillis() / 1000L; this.time = System.currentTimeMillis();
this.ackNumber = ackNumber; this.ackNumber = ackNumber;
this.raceId = raceId; this.raceId = raceId;
this.sourceId = sourceId; this.sourceId = sourceId;
@@ -33,15 +35,6 @@ public class MarkRoundingMessage extends Message{
this.markId = markId; this.markId = markId;
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -52,11 +45,15 @@ public class MarkRoundingMessage extends Message{
putInt((int) sourceId, 4); putInt((int) sourceId, 4);
putByte((byte) boatStatus.getCode()); putByte((byte) boatStatus.getCode());
putByte((byte) roundingSide.getCode()); putByte((byte) roundingSide.getCode());
putByte((byte) markType.getCode());
putByte((byte) markId); putByte((byte) markId);
writeCRC(); writeCRC();
rewind(); rewind();
}
outputStream.write(getBuffer()); @Override
public int getSize() {
return MESSAGE_SIZE;
} }
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* Types of marks boats can round * Types of marks boats can round
@@ -1,9 +1,7 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays; import java.util.Arrays;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -33,11 +31,6 @@ public abstract class Message {
*/ */
public abstract int getSize(); public abstract int getSize();
/**
* Send the message as through the outputStream
*/
public abstract void send(SocketChannel outputStream) throws IOException;
/** /**
* Allocate byte buffer to correct size * Allocate byte buffer to correct size
*/ */
@@ -45,6 +38,7 @@ public abstract class Message {
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.order(ByteOrder.LITTLE_ENDIAN);
bufferPosition = 0; bufferPosition = 0;
buffer.position(bufferPosition);
} }
/** /**
@@ -161,10 +155,10 @@ public abstract class Message {
} }
/** /**
* @return The current buffer * @return The current buffer as a byte array
*/ */
public ByteBuffer getBuffer(){ public byte[] getBuffer(){
return buffer; return buffer.array();
} }
/** /**
@@ -178,7 +172,7 @@ public abstract class Message {
* Convert an integer to an array of bytes * Convert an integer to an array of bytes
* @param val The value to add * @param val The value to add
* @param len The width of the integer in the buffer * @param len The width of the integer in the buffer
* @return * @return A byte array to be sent
*/ */
public static byte[] intToByteArray(long val, int len){ public static byte[] intToByteArray(long val, int len){
int index = 0; int index = 0;
@@ -193,6 +187,26 @@ public abstract class Message {
return data; return data;
} }
/**
* takes an array of up to 7 bytes in little endian format and
* returns a positive long constructed from the input bytes
*
* @param bytes the bytes to be converted to long
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
public static long bytesToLong(byte[] bytes){
long partialLong = 0;
int index = 0;
for (byte b: bytes){
if (index > 6){
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
/** /**
* Reverse an array of bytes * Reverse an array of bytes
* @param data The byte[] to reverse * @param data The byte[] to reverse
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* Enum containing the types of messages * Enum containing the types of messages
@@ -16,7 +16,13 @@ public enum MessageType {
BOAT_LOCATION(37), BOAT_LOCATION(37),
MARK_ROUNDING(38), MARK_ROUNDING(38),
COURSE_WIND(44), COURSE_WIND(44),
AVERAGE_WIND(47); AVERAGE_WIND(47),
BOAT_ACTION(100),
REGISTRATION_REQUEST(101),
REGISTRATION_RESPONSE(102),
CUSTOMIZATION_REQUEST(103),
CUSTOMIZATION_RESPONSE(104);
private int code; private int code;
@@ -31,4 +37,6 @@ public enum MessageType {
int getCode(){ int getCode(){
return this.code; return this.code;
} }
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* The types of race start status messages * The types of race start status messages
@@ -1,10 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class RaceStartStatusMessage extends Message { public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20; private final int MESSAGE_SIZE = 20;
@@ -32,15 +26,6 @@ public class RaceStartStatusMessage extends Message {
this.raceId = raceId; this.raceId = raceId;
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -53,7 +38,11 @@ public class RaceStartStatusMessage extends Message {
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
@Override
public int getSize() {
return MESSAGE_SIZE;
}
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* The current status of the race * The current status of the race
@@ -1,7 +1,5 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.List; import java.util.List;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -9,12 +7,14 @@ public class RaceStatusMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_VERSION = 2; //Always set to 1
private final int MESSAGE_BASE_SIZE = 24; private final int MESSAGE_BASE_SIZE = 24;
private final double windDirFactor = 0x4000 / 90;
private long currentTime; private long currentTime;
private long raceId; private long raceId;
private RaceStatus raceStatus; private RaceStatus raceStatus;
private long expectedStartTime; private long expectedStartTime;
private WindDirection raceWindDirection; private double raceWindDirection;
private long windSpeed; private long windSpeed;
private long numBoatsInRace; private long numBoatsInRace;
private RaceType raceType; private RaceType raceType;
@@ -33,13 +33,13 @@ public class RaceStatusMessage extends Message{
* @param sourceId The source of this message * @param sourceId The source of this message
* @param boats A list of boat status sub messages * @param boats A list of boat status sub messages
*/ */
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection,
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){ long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
currentTime = System.currentTimeMillis(); currentTime = System.currentTimeMillis();
this.raceId = raceId; this.raceId = raceId;
this.raceStatus = raceStatus; this.raceStatus = raceStatus;
this.expectedStartTime = expectedStartTime; this.expectedStartTime = expectedStartTime;
this.raceWindDirection = raceWindDirection; this.raceWindDirection = raceWindDirection * windDirFactor+100.0;
this.windSpeed = windSpeed; this.windSpeed = windSpeed;
this.numBoatsInRace = numBoatsInRace; this.numBoatsInRace = numBoatsInRace;
this.raceType = raceType; this.raceType = raceType;
@@ -47,22 +47,6 @@ public class RaceStatusMessage extends Message{
crc = new CRC32(); crc = new CRC32();
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
}
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -71,7 +55,7 @@ public class RaceStatusMessage extends Message{
putInt((int) raceId, 4); putInt((int) raceId, 4);
putByte((byte) raceStatus.getCode()); putByte((byte) raceStatus.getCode());
putInt(expectedStartTime, 6); putInt(expectedStartTime, 6);
putInt((int) raceWindDirection.getCode(), 2); putInt((int) this.raceWindDirection, 2);
putInt((int) windSpeed, 2); putInt((int) windSpeed, 2);
putByte((byte) numBoatsInRace); putByte((byte) numBoatsInRace);
putByte((byte) raceType.getCode()); putByte((byte) raceType.getCode());
@@ -82,7 +66,14 @@ public class RaceStatusMessage extends Message{
writeCRC(); writeCRC();
rewind(); rewind();
outputStream.write(getBuffer());
} }
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* Enum containing the types of races * Enum containing the types of races
@@ -0,0 +1,22 @@
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()));
allocateBuffer();
writeHeaderToBuffer();
putInt(type.getCode(), 2);
writeCRC();
}
@Override
public int getSize() {
return MESSAGE_LENGTH;
}
}
@@ -0,0 +1,20 @@
package seng302.gameServer.messages;
public class RegistrationResponseMessage extends Message{
public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){
setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(clientSourceID, 4);
putInt(status.getCode(), 1);
writeCRC();
}
@Override
public int getSize() {
return 5;
}
}
@@ -0,0 +1,44 @@
package seng302.gameServer.messages;
public enum RegistrationResponseStatus {
SUCCESS_SPECTATING(0x00),
SUCCESS_PLAYING(0x01),
SUCCESS_TUTORIAL(0x02),
SUCCESS_GHOSTING(0x03),
FAILURE_GENERAL(0x10),
FAILURE_FULL(0x11);
private int code;
RegistrationResponseStatus(int code){
this.code = code;
}
/**
* Get the message code (From the API Spec)
* @return the message code
*/
int getCode(){
return this.code;
}
public static RegistrationResponseStatus getResponseStatus(int typeCode){
switch (typeCode){
case 0x00:
return SUCCESS_SPECTATING;
case 0x01:
return SUCCESS_PLAYING;
case 0x02:
return SUCCESS_TUTORIAL;
case 0x03:
return SUCCESS_GHOSTING;
case 0x10:
return FAILURE_GENERAL;
case 0x11:
return FAILURE_FULL;
default:
return FAILURE_GENERAL;
}
}
}
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* The status of a boat rounding a mark * The status of a boat rounding a mark
@@ -0,0 +1,52 @@
package seng302.gameServer.messages;
/**
* The side the boat rounded the mark
*/
public enum RoundingSide {
UNKNOWN(0, "Unknown"),
PORT(1, "Port"),
STARBOARD(2, "Stbd"),
SP(3, "SP"),
PS(4, "PS");
private long code;
private String name;
RoundingSide(long code, String name) {
this.code = code;
this.name = name;
}
public long getCode(){
return code;
}
public String getName() {
return name;
}
public static RoundingSide getRoundingSide(String identifier) {
RoundingSide roundingSide = UNKNOWN;
switch (identifier) {
case "Unknown":
roundingSide = UNKNOWN;
break;
case "Port":
roundingSide = PORT;
break;
case "Stbd":
roundingSide = STARBOARD;
break;
case "SP":
roundingSide = SP;
break;
case "PS":
roundingSide = PS;
break;
}
return roundingSide;
}
}
@@ -1,12 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
public class XMLMessage extends Message{ public class XMLMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
@@ -25,6 +17,7 @@ public class XMLMessage extends Message{
* XML Message from the AC35 Streaming data spec * XML Message from the AC35 Streaming data spec
* @param content The XML content * @param content The XML content
* @param type The XML Message Sub Type * @param type The XML Message Sub Type
* @param sequenceNum sequenceNum
*/ */
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){ public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
this.content = content; this.content = content;
@@ -35,20 +28,6 @@ public class XMLMessage extends Message{
sequence = sequenceNum; sequence = sequenceNum;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
}
/**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer(); allocateBuffer();
writeHeaderToBuffer(); writeHeaderToBuffer();
@@ -63,7 +42,12 @@ public class XMLMessage extends Message{
writeCRC(); writeCRC();
rewind(); rewind();
}
outputStream.write(getBuffer()); /**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
} }
} }
@@ -1,4 +1,4 @@
package seng302.server.messages; package seng302.gameServer.messages;
/** /**
* Enum containing the types of XML messages * Enum containing the types of XML messages
@@ -0,0 +1,52 @@
package seng302.gameServer.messages;
/**
* Created by zyt10 on 10/08/17.
*/
public class YachtEventCodeMessage extends Message {
private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE;
private final int MESSAGE_VERSION = 1; //Always set to 1
private final int MESSAGE_SIZE = 22;
// Message fields
private long timeStamp;
private long ack = 0x00; //Unused
private int raceId;
private int destSourceId;
private int incidentId;
private int eventId;
public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) {
timeStamp = System.currentTimeMillis() / 1000L;
ack = 0;
raceId = 1;
destSourceId = subjectId; // collision boat source id
incidentId = 0;
eventId = yachtEventType.getCode();
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putUnsignedByte((byte) MESSAGE_VERSION);
putInt((int) timeStamp, 6);
putInt((int) ack, 2);
putInt((int) raceId, 4);
putInt((int) destSourceId, 4);
putInt((int) incidentId, 4);
putInt((int) eventId, 1);
writeCRC();
rewind();
}
/**
* @return The length of this message
*/
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -0,0 +1,19 @@
package seng302.gameServer.messages;
/**
* Created by wmu16 on 11/09/17.
*/
public enum YachtEventType {
COLLISION(33),
TOKEN(34);
private int code;
YachtEventType(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
@@ -0,0 +1,291 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
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.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 ClientYacht extends Observable {
@FunctionalInterface
public interface YachtLocationListener {
void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading,
Boolean sailsIn, double velocity);
}
@FunctionalInterface
public interface MarkRoundingListener {
void notifyRounding(ClientYacht yacht, int legNumber);
}
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
private String 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 Long estimateTimeAtFinish;
private Boolean sailIn = false;
private Integer currentMarkSeqID = 0;
private Long markRoundTime;
private Long timeTillNext;
private Double heading;
private Integer legNumber = 0;
private GeoPoint location;
private Integer boatStatus;
private Double currentVelocity;
private List<YachtLocationListener> locationListeners = new ArrayList<>();
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
private Color colour;
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.location = new GeoPoint(57.670341, 11.826856);
this.heading = 120.0; //In degrees
this.currentVelocity = 0d;
this.boatStatus = 1;
this.colour = Color.rgb(0, 0, 0, 1.0);
}
/**
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
* rounding package.
*/
@Override
public void addObserver(Observer o) {
super.addObserver(o);
}
public String getBoatType() {
return boatType;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public Integer getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(Integer boatStatus) {
this.boatStatus = boatStatus;
}
public Integer getLegNumber() {
return legNumber;
}
public void setLegNumber(Integer legNumber) {
this.legNumber = legNumber;
}
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
timeTillNext = estimateTimeTillNextMark;
}
public String getEstimateTimeAtFinish() {
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(estimateTimeAtFinish);
}
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
this.estimateTimeAtFinish = estimateTimeAtFinish;
}
public Integer getPlacing() {
return placingProperty.get();
}
public void setPlacing(Integer position) {
placingProperty.set(position);
}
public ReadOnlyIntegerProperty placingProperty() {
return placingProperty.getReadOnlyProperty();
}
public void updateVelocityProperty(double velocity) {
this.velocityProperty.set(velocity);
}
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundTime = markRoundingTime;
}
public ReadOnlyDoubleProperty getVelocityProperty() {
return velocityProperty.getReadOnlyProperty();
}
public ReadOnlyLongProperty timeTillNextProperty() {
return timeTillNextProperty.getReadOnlyProperty();
}
public Long getTimeTillNext() {
return timeTillNext;
}
public Long getMarkRoundTime() {
return markRoundTime;
}
public GeoPoint getLocation() {
return location;
}
public Integer getPosition() {
return position;
}
public void setPosition(Integer position) {
this.position = position;
}
public void toggleSail() {
sailIn = !sailIn;
}
//// TODO: 15/08/17 asd
/**
* Sets the current location of the boat in lat and long whilst preserving the last location
*
* @param lat Latitude
* @param lng Longitude
*/
public void setLocation(Double lat, Double lng) {
location.setLat(lat);
location.setLng(lng);
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
@Override
public String toString() {
return boatName;
}
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
}
public ReadOnlyLongProperty timeSinceLastMarkProperty() {
return timeSinceLastMarkProperty.getReadOnlyProperty();
}
public void setTimeTillNext(Long timeTillNext) {
this.timeTillNext = timeTillNext;
}
public Color getColour() {
return colour;
}
public void setColour(Color colour) {
this.colour = colour;
}
public void updateLocation(double lat, double lng, double heading, double velocity) {
setLocation(lat, lng);
this.heading = heading;
this.currentVelocity = velocity;
updateVelocityProperty(velocity);
for (YachtLocationListener yll : locationListeners) {
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
}
}
public void addLocationListener(YachtLocationListener listener) {
locationListeners.add(listener);
}
public void addMarkRoundingListener(MarkRoundingListener listener) {
markRoundingListeners.add(listener);
}
public void removeMarkRoundingListener(MarkRoundingListener listener) {
markRoundingListeners.remove(listener);
}
public boolean getSailIn () {
return sailIn;
}
public void roundMark(long markRoundTime, long timeSinceLastMark) {
this.markRoundTime = markRoundTime;
timeSinceLastMarkProperty.set(timeSinceLastMark);
legNumber++;
for (MarkRoundingListener listener : markRoundingListeners) {
listener.notifyRounding(this, legNumber);
}
}
public Double getCurrentVelocity() {
return currentVelocity;
}
}
+14
View File
@@ -0,0 +1,14 @@
package seng302.model;
import javafx.scene.paint.Color;
/**
* Enum for generating colours.
*/
public enum Colors {
RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
public static Color getColor(Integer index) {
return Color.valueOf(values()[index].toString());
}
}
@@ -1,18 +1,18 @@
package seng302.server.simulator.mark; package seng302.model;
public class Position { /**
* A class represent Geo location (latitude, lnggitude).
* Created by Haoming on 15/5/2017
*/
public class GeoPoint {
double lat, lng; private double lat, lng;
public Position(double lat, double lng) { public GeoPoint(double lat, double lng) {
this.lat = lat; this.lat = lat;
this.lng = lng; this.lng = lng;
} }
public String toString() {
return String.format("Position at lat:%f lng:%f.", lat, lng);
}
public double getLat() { public double getLat() {
return lat; return lat;
} }
@@ -28,4 +28,9 @@ public class Position {
public void setLng(double lng) { public void setLng(double lng) {
this.lng = lng; this.lng = lng;
} }
@Override
public String toString() {
return "lat: " + lat + " lng: " + lng;
}
} }
+18
View File
@@ -0,0 +1,18 @@
package seng302.model;
/**
* Stores data on the border of a race
*/
public class Limit extends GeoPoint {
private Integer seqID;
public Limit(Integer seqID, Double lat, Double lng) {
super(lat, lng);
this.seqID = seqID;
}
public Integer getSeqID() {
return seqID;
}
}
+68
View File
@@ -0,0 +1,68 @@
package seng302.model;
import java.net.Socket;
/**
* A Class defining a player and their respective details in the game as held by the model
* Created by wmu16 on 10/07/17.
*/
public class Player {
private Socket socket;
private ServerYacht yacht;
private Integer lastMarkPassed;
public Player(Socket socket, ServerYacht yacht) {
this.socket = socket;
this.yacht = yacht;
}
public Socket getSocket() {
return socket;
}
public Integer getLastMarkPassed() {
return lastMarkPassed;
}
public void setLastMarkPassed(Integer lastMarkPassed) {
this.lastMarkPassed = lastMarkPassed;
}
public ServerYacht getYacht() {
return yacht;
}
@Override
public String toString() {
String playerAddress = null;
if (socket == null){
return "Disconnected Player";
}
playerAddress = socket.getRemoteSocketAddress().toString();
return playerAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null){
return false;
}
if (!(obj instanceof Player)){
return false;
}
return ((Player) obj).socket.equals(socket);
}
@Override
public int hashCode(){
return socket.hashCode();
}
}
@@ -1,7 +1,9 @@
package seng302.models; package seng302.model;
import java.io.*; import java.io.BufferedReader;
import java.util.ArrayList; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap; import java.util.HashMap;
/** /**
@@ -24,9 +26,9 @@ public final class PolarTable {
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed. * Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap * These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
* as a value * as a value
* @param file containing the polar csv information * @param polarFile polarFile to be parsed
*/ */
public static void parsePolarFile(String file) { public static void parsePolarFile(InputStream polarFile) {
polarTable = new HashMap<>(); polarTable = new HashMap<>();
upwindOptimal = new HashMap<>(); upwindOptimal = new HashMap<>();
downwindOptimal = new HashMap<>(); downwindOptimal = new HashMap<>();
@@ -34,7 +36,7 @@ public final class PolarTable {
String line; String line;
Boolean isHeaderLine = true; Boolean isHeaderLine = true;
try (BufferedReader br = new BufferedReader(new FileReader(file))) { try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
String[] thisLine = line.split(","); String[] thisLine = line.split(",");
@@ -67,7 +69,7 @@ public final class PolarTable {
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); System.out.println("[PolarTable] IO exception");
} }
} }
@@ -122,7 +124,7 @@ public final class PolarTable {
*/ */
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) { public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return upwindOptimal.get(polarWindSpeed); return upwindOptimal.get(polarWindSpeed);
} }
@@ -134,30 +136,47 @@ public final class PolarTable {
*/ */
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) { public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
Double polarWindSpeed = getClosestMatch(thisWindSpeed); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
return downwindOptimal.get(polarWindSpeed); return downwindOptimal.get(polarWindSpeed);
} }
private static Double getClosestMatch(Double thisWindSpeed) { public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet()); Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
Double lowerVal = windValues.get(0); return polarTable.get(polarWindSpeed).get(polarAngle);
Double upperVal = windValues.get(1);
for(int i = 0; i < windValues.size() - 1; i++) {
lowerVal = windValues.get(i);
upperVal = windValues.get(i+1);
if (thisWindSpeed <= upperVal) {
break;
}
} }
Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
Double upperDiff = Math.abs(upperVal - thisWindSpeed);
return (lowerDiff <= upperDiff) ? lowerVal : upperVal; public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
Double smallestDif = Double.POSITIVE_INFINITY;
Double closestWind = 0d;
for (Double polarWindSpeed : polarTable.keySet()) {
Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
if (difference < smallestDif) {
smallestDif = difference;
closestWind = polarWindSpeed;
} }
}
return closestWind;
}
public static Double getClosestAngleInPolar(HashMap<Double, Double> thisWindSpeedPolar, Double thisHeading) {
Double smallestDif = Double.POSITIVE_INFINITY;
Double closestAngle = 0d;
for (Double polarAngle : thisWindSpeedPolar.keySet()) {
Double difference = Math.abs(polarAngle - thisHeading);
if (difference < smallestDif) {
smallestDif = difference;
closestAngle = polarAngle;
}
}
return closestAngle;
}
} }
+137
View File
@@ -0,0 +1,137 @@
package seng302.model;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TimeZone;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
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.
* Calculates the state of critical race attributes when relevant data is added.
*/
public class RaceState {
@FunctionalInterface
public interface CollisionListener {
void notifyCollision(GeoPoint location);
}
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
private ReadOnlyDoubleWrapper windSpeed = new ReadOnlyDoubleWrapper();
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
private long serverSystemTime;
private long expectedStartTime;
private boolean raceRunning = false;
private boolean gunFired = false;
private boolean raceFinished = false;
long timeTillStart;
private ObservableList<ClientYacht> playerPositions;
private List<ClientYacht> collisions = new ArrayList<>();
private List<CollisionListener> collisionListeners = new ArrayList<>();
public RaceState() {
playerPositions = FXCollections.observableArrayList();
}
public void updateState (RaceStatusData data) {
this.windSpeed.set(data.getWindSpeed());
this.windDirection.set(data.getWindDirection());
this.serverSystemTime = data.getCurrentTime();
this.expectedStartTime = data.getExpectedStartTime();
this.raceRunning = data.isRaceStarted();
}
public void setTimeZone (TimeZone timeZone) {
DATE_TIME_FORMAT.setTimeZone(timeZone);
}
public void updateState (RaceStartData data) {
this.timeTillStart = data.getRaceStartTime();
}
public String getRaceTimeStr () {
long raceTime = serverSystemTime - expectedStartTime;
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);
}
}
public long getTimeTillStart () {
return (expectedStartTime - serverSystemTime);
}
public double getWindSpeed() {
return windSpeed.doubleValue();
}
public ReadOnlyDoubleProperty windSpeedProperty() {
return windSpeed.getReadOnlyProperty();
}
public ReadOnlyDoubleProperty windDirectionProperty() {
return windDirection.getReadOnlyProperty();
}
public long getRaceTime() {
return serverSystemTime;
}
public boolean isRaceStarted () {
return raceRunning;
}
public void setRaceStarted(Boolean value) {
this.raceRunning = value;
}
public void setBoats(Collection<ClientYacht> clientYachts) {
playerPositions.setAll(clientYachts);
}
public void sortPlayers() {
playerPositions.sort((yacht1, yacht2) -> Integer.compare(yacht2.getLegNumber(),
yacht1.getLegNumber()));
}
public ObservableList<ClientYacht> getPlayerPositions() {
return playerPositions;
}
public void storeCollision(ClientYacht yacht) {
collisions.add(yacht);
for (CollisionListener collisionListener : collisionListeners) {
collisionListener.notifyCollision(yacht.getLocation());
}
}
public void addCollisionListener(CollisionListener collisionListener) {
collisionListeners.add(collisionListener);
}
public void removeCollisionListener(CollisionListener collisionListener) {
collisionListeners.remove(collisionListener);
}
public void setRaceFinished() {
raceFinished = true;
}
public Boolean getRaceFinished() {
return raceFinished;
}
}
@@ -0,0 +1,424 @@
package seng302.model;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.GameState;
import seng302.gameServer.messages.BoatStatus;
import seng302.model.mark.Mark;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
/**
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
* not used anymore.
*/
public class ServerYacht {
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
public static final Double TURN_STEP = 5.0;
//Boat info
private String boatType;
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;
private Double heading;
private GeoPoint lastLocation;
private GeoPoint location;
private Double currentVelocity;
private Boolean isAuto;
private Double autoHeading;
private Integer legNumber;
//Mark Rounding
private Integer currentMarkSeqID;
private Boolean hasEnteredRoundingZone;
private Mark closestCurrentMark;
private Boolean hasPassedLine;
private Boolean hasPassedThroughGate;
//PowerUp
private TokenType powerUp;
private Long powerUpStartTime;
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.boatStatus = BoatStatus.PRESTART;
this.sourceId = sourceId;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.sailIn = false;
this.isAuto = false;
this.location = new GeoPoint(57.67046, 11.83751);
this.lastLocation = location;
this.heading = 120.0; //In degrees
this.currentVelocity = 0d; //in mms-1
this.currentMarkSeqID = 0;
this.legNumber = 0;
this.boatColor = Colors.getColor(sourceId - 1);
this.powerUp = null;
this.hasEnteredRoundingZone = false;
this.hasPassedLine = false;
this.hasPassedThroughGate = false;
}
/**
* Changes the boats current currentVelocity by a set amount, positive or negative
*
* @param velocityChange The ammount to change the currentVelocity by, in mms-1
*/
public void changeVelocity(Double velocityChange) {
currentVelocity += velocityChange;
}
/**
* Updates the boat to a new GeoPoint whilst preserving the last location
*
* @param secondsElapsed The seconds elapsed since the last update of this yacht
*/
public void updateLocation(Double secondsElapsed) {
lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
}
public void setLocation(GeoPoint geoPoint) {
location = geoPoint;
}
public void powerUp(TokenType powerUp) {
this.powerUp = powerUp;
powerUpStartTime = System.currentTimeMillis();
}
public void powerDown() {
this.powerUp = null;
}
public Long getPowerUpStartTime() {
return powerUpStartTime;
}
public TokenType getPowerUp() {
return powerUp;
}
/**
* Adjusts the heading of the boat by a given amount, while recording the boats last heading.
*
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) {
Double newVal = heading + amount;
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) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double newVal = (-2 * normalizedHeading) + heading;
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
setAutoPilot(newHeading);
}
}
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
*
* @param thisHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
isAuto = true;
autoHeading = thisHeading;
}
/**
* Disables the auto pilot function.
*/
public void disableAutoPilot() {
isAuto = false;
}
/**
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
* in the event that the boat is within the range of 1 turn step of its goal.
*/
public void runAutoPilot() {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
isAuto = false;
}
}
}
public void toggleSailIn() {
sailIn = !sailIn;
}
public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
}
public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
/**
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
* and uses this to calculate a heading to move the yacht towards.
*/
public void turnToVMG() {
if (isAuto) {
disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double optimalHeading;
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
} else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
}
optimalHeading = optimalPolarMap.keySet().iterator().next();
if (normalizedHeading > 180) {
optimalHeading = 360 - optimalHeading;
}
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
setAutoPilot(optimalHeading);
}
}
/**
* Takes a given heading and rotates the boat towards that heading. This does not care about
* being upwind or downwind, just which direction will reach a given heading faster.
*
* @param newHeading The heading to turn the yacht towards.
*/
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP / 5);
} else {
adjustHeading(-TURN_STEP / 5);
}
}
/**
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
* directly away is 180.
*
* @return The normalized heading accounting for wind direction.
*/
private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
return normalizedHeading;
}
public Integer getSourceId() {
//@TODO Remove and merge with Creating Game Loop
if (sourceId == null) {
return 0;
}
return sourceId;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getHullID() {
if (hullID == null) {
return "";
}
return hullID;
}
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
if (country == null) {
return "";
}
return country;
}
public void setBoatName(String name) {
boatName = name;
shortName = name.split(" ")[0];
}
public GeoPoint getLocation() {
return location;
}
public Double getHeading() {
return heading;
}
public void setHeading(Double heading) {
this.heading = heading;
}
public Boolean getSailIn() {
return sailIn;
}
@Override
public String toString() {
return boatName;
}
public Double getCurrentVelocity() {
return currentVelocity;
}
public void setCurrentVelocity(Double currentVelocity) {
this.currentVelocity = currentVelocity;
}
public Integer getCurrentMarkSeqID() {
return currentMarkSeqID;
}
public GeoPoint getLastLocation() {
return lastLocation;
}
public Mark getClosestCurrentMark() {
return closestCurrentMark;
}
public void setClosestCurrentMark(Mark closestCurrentMark) {
this.closestCurrentMark = closestCurrentMark;
}
public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) {
this.hasEnteredRoundingZone = hasEnteredRoundingZone;
}
public void setHasPassedLine(Boolean hasPassedLine) {
this.hasPassedLine = hasPassedLine;
}
public void setHasPassedThroughGate(Boolean hasPassedThroughGate) {
this.hasPassedThroughGate = hasPassedThroughGate;
}
public BoatStatus getBoatStatus() {
return boatStatus;
}
public void setBoatStatus(BoatStatus boatStatus) {
this.boatStatus = boatStatus;
}
public void incrementMarkSeqID() {
currentMarkSeqID++;
}
public Boolean hasEnteredRoundingZone() {
return hasEnteredRoundingZone;
}
public Boolean hasPassedThroughGate() {
return hasPassedThroughGate;
}
public Boolean hasPassedLine() {
return hasPassedLine;
}
public void incrementLegNumber() {
legNumber++;
}
public Integer getLegNumber() {
return legNumber;
}
public void setBoatColor(Color color) {
this.boatColor = color;
}
public Color getBoatColor() {
return boatColor;
}
}
@@ -0,0 +1,130 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.utilities.GeoUtility;
public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.marks.addAll(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
this.midPoint = marks.get(0);
}
}
/**
* Prints out compoundMark's info and its marks, good for testing
* @return a string showing its details
*/
@Override
public String toString(){
String info = String.format(
"CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString()
);
if (marks.size() > 1) {
info += String.format(", %s", marks.get(1).toString());
}
return info + "]";
}
public int getId() {
return compoundMarkId;
}
public void setId (int markID) {
this.compoundMarkId = markID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setRoundingSide(RoundingSide roundingSide) {;
switch (roundingSide) {
case SP:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
getSubMark(2).setRoundingSide(RoundingSide.PORT);
break;
case PS:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
break;
case PORT:
getSubMark(1).setRoundingSide(RoundingSide.PORT);
break;
case STARBOARD:
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
break;
}
}
/**
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
* @param singleMarkId the id of the desired mark contained in this compound mark.
* @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS)
*/
public Mark getSubMark(int singleMarkId) {
try {
return marks.get(singleMarkId - 1);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/**
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
*
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
*/
public GeoPoint getMidPoint() {
return midPoint;
}
/**
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
* specific singleMark or the list of marks.
*
* @return True if the compound mark is a gate, false otherwise.
*/
public boolean isGate () {
return marks.size() > 1;
}
/**
* Returns the list of marks in the compoundMark
*
* @return All marks contained in this mark.
*/
public List<Mark> getMarks () {
return marks;
}
@Override
public int hashCode() {
int hash = 0;
for (Mark mark : marks) {
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
}
return hash + getName().hashCode() + Integer.hashCode(getId());
}
}
@@ -0,0 +1,35 @@
package seng302.model.mark;
/**
* Stores the data for the cornering of a mark.
*/
public class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
this.seqID = seqID;
this.compoundMarkID = compoundMarkID;
this.rounding = rounding;
this.zoneSize = zoneSize;
}
public Integer getSeqID() {
return seqID;
}
public Integer getCompoundMarkID() {
return compoundMarkID;
}
public String getRounding() {
return rounding;
}
public Integer getZoneSize() {
return zoneSize;
}
}
@@ -0,0 +1,86 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
/**
* An abstract class to represent general marks
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public class Mark extends GeoPoint {
@FunctionalInterface
public interface PositionListener {
void notifyPositionChange(Mark mark, double lat, double lon);
}
private int seqID;
private String name;
private int sourceID;
private List<PositionListener> positionListeners = new ArrayList<>();
private RoundingSide roundingSide;
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
super(lat, lng);
this.name = name;
this.sourceID = sourceID;
this.seqID = seqID;
}
/**
* Prints out mark's info and its geo location, good for testing
* @return a string showing its details
*/
@Override
public String toString() {
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng());
}
public int getSeqID() {
return seqID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSourceID() {
return sourceID;
}
public RoundingSide getRoundingSide() {
return roundingSide;
}
public void setRoundingSide(RoundingSide roundingSide) {
this.roundingSide = roundingSide;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
public void updatePosition (double lat, double lon) {
this.setLat(lat);
this.setLng(lon);
for (PositionListener listener : positionListeners) {
listener.notifyPositionChange(this, lat, lon);
}
}
public void addPositionListener (PositionListener listener) {
positionListeners.add(listener);
}
public void removePositionListener (PositionListener listener) {
positionListeners.remove(listener);
}
}
@@ -0,0 +1,143 @@
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 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.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
*/
public class MarkOrder {
private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private Set<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
}
/**
* @return An ordered list of marks in the race
* OR null if the mark order could not be loaded
*/
public List<CompoundMark> getMarkOrder() {
if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
/**
* @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)
*/
public Boolean isLastMark(Integer seqID) {
return seqID == raceMarkOrder.size() - 1;
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
*/
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID - 1);
}
public CompoundMark getCurrentMark(Integer currentSeqID) {
return raceMarkOrder.get(currentSeqID);
}
/**
* @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
* #isLastMark(Integer)}
*/
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();
// TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data
generator.setRaceTemplate(new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>()));
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -1,13 +1,12 @@
package seng302.models.stream.packets; package seng302.model.stream.packets;
/**
* Created by Kusal on 4/24/2017.
*/
public enum PacketType { public enum PacketType {
HEARTBEAT, HEARTBEAT,
RACE_STATUS, RACE_STATUS,
DISPLAY_TEXT_MESSAGE, DISPLAY_TEXT_MESSAGE,
XML_MESSAGE, RACE_XML,
REGATTA_XML,
BOAT_XML,
RACE_START_STATUS, RACE_START_STATUS,
YACHT_EVENT_CODE, YACHT_EVENT_CODE,
YACHT_ACTION_CODE, YACHT_ACTION_CODE,
@@ -16,9 +15,14 @@ public enum PacketType {
MARK_ROUNDING, MARK_ROUNDING,
COURSE_WIND, COURSE_WIND,
AVG_WIND, AVG_WIND,
OTHER; BOAT_ACTION,
OTHER,
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE,
RACE_CUSTOMIZATION_REQUEST,
RACE_CUSTOMIZATION_RESPONSE;
public static PacketType assignPacketType(int packetType){ public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){ switch(packetType){
case 1: case 1:
return HEARTBEAT; return HEARTBEAT;
@@ -27,7 +31,14 @@ public enum PacketType {
case 20: case 20:
return DISPLAY_TEXT_MESSAGE; return DISPLAY_TEXT_MESSAGE;
case 26: case 26:
return XML_MESSAGE; switch (payload[9]) { //The type of XML message
case 5:
return REGATTA_XML;
case 6:
return RACE_XML;
case 7:
return BOAT_XML;
}
case 27: case 27:
return RACE_START_STATUS; return RACE_START_STATUS;
case 29: case 29:
@@ -44,6 +55,16 @@ public enum PacketType {
return COURSE_WIND; return COURSE_WIND;
case 47: case 47:
return AVG_WIND; return AVG_WIND;
case 100:
return BOAT_ACTION;
case 101:
return RACE_REGISTRATION_REQUEST;
case 102:
return RACE_REGISTRATION_RESPONSE;
case 103:
return RACE_CUSTOMIZATION_REQUEST;
case 104:
return RACE_CUSTOMIZATION_RESPONSE;
default: default:
} }
return OTHER; return OTHER;
@@ -1,4 +1,4 @@
package seng302.models.stream.packets; package seng302.model.stream.packets;
/** /**
* Created by kre39 on 23/04/17. * Created by kre39 on 23/04/17.
@@ -13,7 +13,7 @@ public class StreamPacket {
private byte[] payload; private byte[] payload;
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type); this.type = PacketType.assignPacketType(type, payload);
this.messageLength = messageLength; this.messageLength = messageLength;
this.timeStamp = timeStamp; this.timeStamp = timeStamp;
this.payload = payload; this.payload = payload;
@@ -0,0 +1,36 @@
package seng302.model.stream.parser;
/**
* Simple data wrapper for mark rounding data packet.
*/
public class MarkRoundingData {
private int boatId;
private int markId;
private int roundingSide;
private long timeStamp;
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
this.boatId = boatId;
this.markId = markId;
this.roundingSide = roundingSide;
this.timeStamp = timeStamp;
}
public int getBoatId() {
return boatId;
}
public int getMarkId() {
return markId;
}
public int getRoundingSide() {
return roundingSide;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,50 @@
package seng302.model.stream.parser;
public class PositionUpdateData {
public enum DeviceType {
YACHT_TYPE,
MARK_TYPE
}
private int deviceId;
private DeviceType type;
private double lat;
private double lon;
private double heading;
private double groundSpeed;
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
double heading, double groundSpeed) {
this.deviceId = deviceId;
this.type = type;
this.lat = lat;
this.lon = lon;
this.heading = heading;
this.groundSpeed = groundSpeed;
}
public int getDeviceId() {
return deviceId;
}
public DeviceType getType() {
return type;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getHeading() {
return heading;
}
public double getGroundSpeed() {
return groundSpeed;
}
}
@@ -0,0 +1,35 @@
package seng302.model.stream.parser;
/**
* Class for storing data parsed from race start status packet
*/
public class RaceStartData {
private long raceId;
private long raceStartTime;
private int notificationType;
private long timeStamp;
public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
this.raceId = raceId;
this.raceStartTime = raceStartTime;
this.notificationType = notificationType;
this.timeStamp = timeStamp;
}
public long getRaceId() {
return raceId;
}
public long getRaceStartTime() {
return raceStartTime;
}
public int getNotificationType() {
return notificationType;
}
public long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,65 @@
package seng302.model.stream.parser;
import java.util.ArrayList;
import java.util.List;
/**
* Stores parsed data from race status packets
*/
public class RaceStatusData {
//CONVERSION CONSTANTS
private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees
private static final double MS_TO_KNOTS = 1.94384;
private double windDirection;
private double windSpeed;
private boolean raceStarted = false;
private long currentTime;
private long expectedStartTime;
private List<long[]> boatData = new ArrayList<>();
public RaceStatusData(
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
windDirection = windDir / WIND_DIR_FACTOR;
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
raceStarted = raceStatus == 3;
this.currentTime = currentTime;
this.expectedStartTime = expectedStartTime;
}
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) {
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus});
}
public double getWindDirection() {
return windDirection;
}
public double getWindSpeed() {
return windSpeed;
}
public boolean isRaceStarted() {
return raceStarted;
}
public long getCurrentTime() {
return currentTime;
}
public long getExpectedStartTime() {
return expectedStartTime;
}
/**
* 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, status].
*/
public List<long[]> getBoatData () {
return boatData;
}
}
@@ -0,0 +1,34 @@
package seng302.model.stream.parser;
/**
* Stores parsed data from yacht event code packet
*/
public class YachtEventData {
private Long subjectId;
private Long incidentId;
private Integer eventId;
private Long timeStamp;
public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) {
this.subjectId = subjectId;
this.incidentId = incidentId;
this.eventId = eventId;
this.timeStamp = timeStamp;
}
public Long getSubjectId() {
return subjectId;
}
public Long getIncidentId() {
return incidentId;
}
public Integer getEventId() {
return eventId;
}
public Long getTimeStamp() {
return timeStamp;
}
}
@@ -0,0 +1,57 @@
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;
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;
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens) {
this.yachts = yachts;
this.tokens = tokens;
startTime = LocalDateTime.now();
}
/**
* 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);
}
/**
* 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,85 @@
package seng302.model.stream.xml.generator;
/**
* A Race regatta that can be parsed into XML
*/
public class RegattaXMLTemplate {
private final Double DEFAULT_ALTITUDE = 0d;
private final Integer DEFAULT_REGATTA_ID = 0;
private Integer id;
private String name;
private String courseName;
private Double latitude;
private Double longitude;
private Double altitude;
private Integer utcOffset;
private Double magneticVariation;
public RegattaXMLTemplate(String name, String courseName, Double latitude, Double longitude) {
this.name = name;
this.id = DEFAULT_REGATTA_ID;
this.courseName = courseName;
this.latitude = latitude;
this.longitude = longitude;
this.altitude = DEFAULT_ALTITUDE;
this.utcOffset = 0;
this.magneticVariation = 0d;
}
public void setMagneticVariation(Double magneticVariation){
this.magneticVariation = magneticVariation;
}
public void setUtcOffset(Integer offset){
this.utcOffset = offset;
}
/*
NOTE!! The following getters must follow the JavaBean standard (getPropertyName()), and must be public.
*/
public String getName(){
return name;
}
public String getCourseName(){
return courseName;
}
public Integer getRegattaId(){
return id;
}
public Double getLatitude() {
return latitude;
}
public Double getLongitude() {
return longitude;
}
public Double getAltitude() {
return altitude;
}
public Integer getUtcOffset(){
return utcOffset;
}
public Double getMagneticVariation(){
return magneticVariation;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public void setRegattaName(String regattaName) {
this.name = regattaName;
}
}
@@ -0,0 +1,54 @@
package seng302.model.stream.xml.parser;
import java.util.HashMap;
import java.util.List;
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.
*/
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<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<>();
for (CompoundMark cMark : compoundMarks) {
this.compoundMarks.put(cMark.getId(), cMark);
}
}
public List<Integer> getParticipants() {
return participants;
}
public List<Token> getTokens() {
return tokens;
}
public Map<Integer, CompoundMark> getCompoundMarks() {
return compoundMarks;
}
public List<Corner> getMarkSequence() {
return markSequence;
}
public List<Limit> getCourseLimit() {
return courseLimit;
}
}
@@ -0,0 +1,49 @@
package seng302.model.stream.xml.parser;
/**
* Stores data from regatta xml packet.
*/
public class RegattaXMLData {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
public RegattaXMLData (Integer regattaID, String regattaName, String courseName,
Double centralLat, Double centralLng, Integer utcOffset) {
this.regattaID = regattaID;
this.regattaName = regattaName;
this.courseName = courseName;
this.centralLat = centralLat;
this.centralLng = centralLng;
this.utcOffset = utcOffset;
}
public Integer getRegattaID() {
return regattaID;
}
public String getRegattaName() {
return regattaName;
}
public String getCourseName() {
return courseName;
}
public Double getCentralLat() {
return centralLat;
}
public Double getCentralLng() {
return centralLng;
}
public Integer getUtcOffset() {
return utcOffset;
}
}
@@ -0,0 +1,21 @@
package seng302.model.token;
import seng302.model.GeoPoint;
/**
* A class describing a game token
* Created by wmu16 on 28/08/17.
*/
public class Token extends GeoPoint {
private TokenType tokenType;
public Token(TokenType tokenType, double lat, double lng) {
super(lat, lng);
this.tokenType = tokenType;
}
public TokenType getTokenType() {
return tokenType;
}
}
@@ -0,0 +1,31 @@
package seng302.model.token;
/**
* An enum describing the different types of game objects
* Created by wmu16 on 28/08/17.
*/
public enum TokenType {
BOOST(0),
HANDLING(1);
private int value;
TokenType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static TokenType getToken(int value) {
switch (value) {
case 0:
return BOOST;
case 1:
return HANDLING;
default:
return BOOST;
}
}
}

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