629 Commits

Author SHA1 Message Date
Hanno Fleischer
5a9f7a8118 added AnimationEndMessages to 'RollDiceState', 'MovePieceState' and 'PlayPowerCardState' 2024-12-06 18:45:14 +01:00
Hanno Fleischer
2e1fe3c050 fixed a missing method call ind TurnState and removed debug sout statements ind 'RollDiceMessage' 2024-12-06 15:19:04 +01:00
Hanno Fleischer
308b592b65 Merge branch 'development' into 'dev/model'
Development merge

See merge request progproj/gruppen-ht24/Gruppe-01!36
2024-12-06 09:55:19 +00:00
Felix Koppe
c4e7fb1d41 Fix logic in modelSyncronizer 2024-12-06 10:54:02 +01:00
Felix Koppe
aacc0440b3 Update .gitignore 2024-12-06 10:33:13 +01:00
Hanno Fleischer
43c0e3bcc7 Merge branch 'development' into 'dev/model'
Development

See merge request progproj/gruppen-ht24/Gruppe-01!35
2024-12-06 09:09:29 +00:00
Felix Koppe
95635f5fb7 Fix ownColor in gameView 2024-12-06 09:59:28 +01:00
Felix Koppe
e337b1f888 Fix card select issue 2024-12-06 09:57:28 +01:00
Felix Koppe
0237bcc4be Fix card select issue 2024-12-06 09:51:17 +01:00
Felix Koppe
26836d16cc Fix card select issue 2024-12-06 09:48:24 +01:00
Felix Koppe
a6c8cc33f4 Make cardSelect trigger needCconfirm 2024-12-06 09:31:02 +01:00
Felix Koppe
7f5f4b8c68 Fix broken sound 2024-12-06 09:24:49 +01:00
Felix Koppe
7f3483aa6b Add broken sound 2024-12-06 09:22:10 +01:00
Felix Koppe
8422b7be1e Merge branch 'development' into dev/client_koppe2 2024-12-06 09:11:09 +01:00
Felix Koppe
f0b23ab9c2 Readd broken files 2024-12-06 09:10:47 +01:00
Felix Koppe
78f1dbb3d3 Remove broken files 2024-12-06 09:08:52 +01:00
Daniel Grigencha
4904b32ea3 Updated 'ChoosePieceState' class.
Updated the 'ChoosePieceState' class by adding the 'RequestMoveMessage' handling to it.
2024-12-06 08:58:51 +01:00
Daniel Grigencha
b00219c4fb Updated 'PlayPowerCardState' class.
Updated the 'PlayPowerCardState' class by adding the 'AnimationEndMessage' handling to it.
2024-12-06 08:58:01 +01:00
Daniel Grigencha
12cf5f3e71 Updated 'PowerCardState' class.
Updated the 'PowerCardState' class by adding the 'SelectedPiecesMessage' handling to it.
2024-12-06 08:57:08 +01:00
Daniel Grigencha
77b0207214 Updated 'TurnState' class.
Updated the 'TurnState' class by adding the 'SelectedPiecesMessage', 'NoPowerCardMessage', 'RequestDieMessage' and 'ReuqestMoveMessage' handling to it.
2024-12-06 08:56:21 +01:00
Daniel Grigencha
a18165bc02 Updated 'Game' class.
Updated the 'Game' class by commenting out the creation of turbo and shield cards. This is only for testing purposes.
2024-12-06 08:55:01 +01:00
Felix Koppe
9e758e4417 Merge branch 'dev/model' into dev/client_koppe2 2024-12-06 08:11:37 +01:00
Daniel Grigencha
62ceff822f Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by adding logic for the roll again event to the 'RequestDieMessage' handling.
2024-12-06 04:54:14 +01:00
Hanno Fleischer
33afc4ab3b added selectDice method in RollDiceState 2024-12-06 04:48:05 +01:00
Daniel Grigencha
322d539cfd Merge branch 'dev/model' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/model 2024-12-06 04:28:40 +01:00
Daniel Grigencha
9c4f2387ee Updated 'FirstRollState', 'SecondRollState' and 'ThirdROllState' classes.
Updated the 'FirstRollState', 'SecondRollState' and 'ThirdROllState' class by adding the 'received(RequestDieMessage msg, int from)' method to them.
2024-12-06 04:28:33 +01:00
Daniel Grigencha
9d1430e488 Updated 'AnimationState' class.
Updated the 'AnimationState' class by setting the data type of 'messageReceived' from 'Map' to 'Set'.
2024-12-06 04:27:10 +01:00
Daniel Grigencha
dd2146d417 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by setting the data type of 'messageReceived' from 'Map' to 'Set'.
2024-12-06 04:26:38 +01:00
Daniel Grigencha
d9ad0f0a4b Updated 'LobbyState' class.
Updated the 'LobbyState' class by setting the first waiting piece on the start node.
2024-12-06 04:25:45 +01:00
Hanno Fleischer
0368ec8541 implemented the correct transition, when the player can play no powercard 2024-12-06 04:18:58 +01:00
Daniel Grigencha
72f0bc5a2f Updated 'RollDiceState' class.
Updated the 'RollDiceState' class by adding the 'received(RequestDieMessage msg, int from)' method to it.
2024-12-06 04:17:29 +01:00
Daniel Grigencha
23ae4a3080 Updated 'DetermineStartPlayaerState' class.
Updated the 'DetermineStartPlayerState' class by calling the right method of the 'entry' inside the 'RequestDieMessage' handling.
2024-12-06 04:14:42 +01:00
Daniel Grigencha
765b1884fe Updated 'AnimationState' class.
Updated the 'AnimationState' class by removing the unused 'DiceNowMessage' call from it.
2024-12-06 03:57:42 +01:00
Daniel Grigencha
e3febd6ba1 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by removing the 'rolls' attribute and its usage from it.
2024-12-06 03:49:39 +01:00
Daniel Grigencha
1a562a8d38 Updated 'DeterminStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by adding the multi roll support to it. In Addition, the server model will be updated correctly.
2024-12-06 03:29:11 +01:00
Daniel Grigencha
39ed4238b5 Updated 'AnimationState' class.
Updated the 'AnimationState' class by adding a missing semicolon.
2024-12-06 03:27:55 +01:00
Daniel Grigencha
620063e894 Updated 'AnimationState' class.
Updated the 'AnimationState' class by updating the 'messageReceived' attribute in it. In Addition, the 'AnimationEndMessage' handling was updated.
2024-12-06 03:22:25 +01:00
Daniel Grigencha
2d0788eb72 Updated 'TurnState' class.
Updated the 'TurnState' class by updating the imports in it.
2024-12-06 03:20:28 +01:00
Daniel Grigencha
8470a96908 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by updating the 'AnimationEndMessage' handling in it. In Addition, the 'RequestDieMessage' handling was updated as well.
2024-12-06 02:55:14 +01:00
Hanno Fleischer
a6205c982a now sending AnimationEndMessage when in Waitranking State 2024-12-06 02:40:16 +01:00
Daniel Grigencha
58b9298c91 Merge branch 'dev/model' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/model 2024-12-06 02:38:53 +01:00
Daniel Grigencha
1e6856744b Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by adding the 'received(AnimationEndMessage msg, int from)' method to it.
2024-12-06 02:38:46 +01:00
Hanno Fleischer
f713e00c36 made all 'PowerCards' serializable 2024-12-06 02:30:13 +01:00
Daniel Grigencha
81ae896ae8 Updated 'TurnState' class.
Updated the 'TurnState' class by removing the start state of this state machine of the constructor.
2024-12-06 02:22:46 +01:00
Daniel Grigencha
5b55d39c9a Updated 'AnimationState' class.
Updated the 'AnimationState' class by solving the transition error due to the separat state change.
2024-12-06 02:22:07 +01:00
Hanno Fleischer
f36e2ff7bb adjusted the getter for the card in the intro to get BonusCards instead of PowerCards 2024-12-06 02:16:58 +01:00
Hanno Fleischer
16afe95aa6 Merge branch 'dev/model' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/model 2024-12-06 02:13:08 +01:00
Hanno Fleischer
55d398b428 adjusted all useages of the SelectedPieces consturctor to transfer the right parameters 2024-12-06 02:12:59 +01:00
Daniel Grigencha
affa2ecd7e Added 'ChoosePowerCardState', 'ShieldCardState', 'SwapCardState' and 'TurboCardState' classes.
Updated the 'ChoosePowerCardState', 'ShieldCardState', 'SwapCardState' and 'TurboCardState' classes to this project. They will be used inside the power card state. In Addition, the abstract 'PowerCardAutomatonState' class was added.
2024-12-06 02:11:32 +01:00
Hanno Fleischer
0a1bd1f503 adjusted all State containing 'received(PlayCardMessage msg)' to work with the new message 2024-12-06 02:04:55 +01:00
Daniel Grigencha
a2867fc88a Added 'ServerCardVisitor' class.
Added the 'ServerCardVisitor' class to this project. It will be used as a visitor on the server to differentiate between all types of power cards.
2024-12-06 01:41:45 +01:00
Daniel Grigencha
c6761d91d1 Updated 'PowerCardState' class.
Updated the 'PowerCardState' class by updating the content of the 'enter' in it.
2024-12-06 01:37:24 +01:00
Daniel Grigencha
5708ee6ffe Updated 'PlayCardMessage' class.
Updated the 'PlayCardMessage' class by removing the 'ownPieceID' and 'enemyPieceID' attributes and their getter methods from it. In Addition, the 'pieces' attribute and its getter method was added.
2024-12-06 01:28:44 +01:00
Hanno Fleischer
d61b68aa41 overrode hachCode and equals method of Piece 2024-12-06 01:15:06 +01:00
Daniel Grigencha
e98418b274 Merge branch 'dev/model' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/model 2024-12-06 01:06:20 +01:00
Daniel Grigencha
66dc9c02ea Updated 'PowerCardState' class.
Updated the 'PowerCardState' class by adding the 'currentState', 'choosePowerCardState', 'shieldCardState', 'swapCardState' and 'turboCardState' attributes and its getter methods to it. In Addition, logic was written into the 'enter' method.
2024-12-06 00:53:11 +01:00
Daniel Grigencha
dd95356abd Updated 'TurnState' class.
Updated the 'TurnState' class by adding the 'player' attribute and its getter method to it.
2024-12-06 00:50:48 +01:00
Daniel Grigencha
84776c71b2 Updated 'SelectedPiecesMessage' class.
Updated the 'SelectedPiecesMessage' class by removing 'pieceIdentifier' attribute and its getter method from it. In Addition, the 'pieces' attribute and its getter method was added.
2024-12-06 00:47:33 +01:00
Daniel Grigencha
d07eee6251 Updated 'Piece' class.
Updated the 'Piece' class by overwriting the 'toString' method in it.
2024-12-06 00:45:56 +01:00
Hanno Fleischer
b601ff2cf7 renamed 'PossibleCardMEssage' to 'PossibleCardsMessage' and completed JavaDocs for method 'getPowerCardByType' in 'Player' 2024-12-06 00:36:06 +01:00
Hanno Fleischer
04119d2f3e modified 'ChoosePowerCardState' to work with PowerCards as well as adjusted 'SelectcardMessage' to use 'PowerCards' 2024-12-06 00:31:12 +01:00
Hanno Fleischer
a92c06a70e added a method in 'Player' to return the first 'PowerCard' of a specific Type 2024-12-06 00:30:08 +01:00
Daniel Grigencha
50f9c0ef0c Updated 'PossibleCardMessage' class.
Updated the 'PossibleCardMessage' class by replacing all 'BonusCard' with 'PowerCard' in it.
2024-12-06 00:19:11 +01:00
Daniel Grigencha
f7f246daaa Merge branch 'dev/model' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into dev/model 2024-12-06 00:16:42 +01:00
Daniel Grigencha
8a738a3633 Updated 'Game' class.
Updated the 'Game' class by replacing all 'BonusCard' with 'PowerCard' in it.
2024-12-06 00:16:38 +01:00
Daniel Grigencha
3a32a7ebf7 Updated 'Player' class.
Updated the 'Player' class by replacing all 'BonusCard' with 'PowerCard' in it.
2024-12-06 00:11:50 +01:00
Daniel Grigencha
6894802c00 Added 'HiddenCard', 'ShieldCard', 'SwapCard' and 'TurboCard' classes.
Updated the 'HiddenCard', 'ShieldCard', 'SwapCard' and 'TurboCard' classes to this project. They will be used to display different types of power cards.
2024-12-06 00:04:21 +01:00
Daniel Grigencha
a09211da5f Updated abstract 'PowerCard' class.
Updated the abstract 'PowerCard' class by setting the 'card' attribute to 'protected' in it.
2024-12-06 00:03:16 +01:00
Daniel Grigencha
c9c9c5dcf6 Added abstract 'PowerCard' class.
Added the abstract 'PowerCard' class to this project. It will be used to display different types of hand cards.
2024-12-05 23:58:43 +01:00
Daniel Grigencha
1e46b1dc59 Added 'Visitor' interface.
Added the 'Visitor' interface to this project. It will be used to handle all types of power cards.
2024-12-05 23:54:55 +01:00
Hanno Fleischer
6c74acc334 added a getter for the BonusCrad in PowerCard 2024-12-05 23:07:22 +01:00
Hanno Fleischer
421231aa12 made BonusCard Serializable and added the method calls to display handcards during the intro 2024-12-05 22:46:48 +01:00
Daniel Grigencha
d8bb458e9c Updated 'LobbyState' class.
Updated the 'LobbyState' class by updating the 'initializeGame' method in it.
2024-12-05 23:45:24 +01:00
Hanno Fleischer
f0080118d0 Changed and :
removed the selectAnimationEnd Method from Waitranking and moved logic into the receivedActivePlayerMessage. Added in selectStart to always send a StartGameMessage in order to trigger incorrectRequestMEssage
2024-12-05 22:34:53 +01:00
Daniel Grigencha
8a438ab069 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by updating the whole start player determination process in it.
2024-12-05 23:29:21 +01:00
Daniel Grigencha
0ce8184069 Updated 'Game' class.
Updated the 'Game' class by adding the 'draw' method to it. In Addition, the 'initializeDrawPile' method was updated by shuffling the 'drawPile' attribute after filling it.
2024-12-05 23:27:06 +01:00
Felix Koppe
2c524477d7 Merge branch 'dev/model' into dev/client_koppe2 2024-12-05 22:18:00 +01:00
Felix Koppe
587af466e8 Merge branch 'development' into dev/client_koppe2
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
2024-12-05 22:17:53 +01:00
Felix Koppe
8d398450f1 Minor changes 2024-12-05 22:15:59 +01:00
Felix Koppe
0a96dd6f9f Repair sounds 2024-12-05 22:07:36 +01:00
Felix Koppe
e8a556de27 Add missle 2024-12-05 22:02:02 +01:00
Hanno Fleischer
1214d3c87c added an getter for the error id in the IncorrectrequestMessage and implemeted how to handle it in the client game logic. 2024-12-05 21:11:40 +01:00
Daniel Grigencha
f2c34aee2d Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by updating the 'received(RequestDieMessage msg, int from)' method in it.
2024-12-05 22:03:48 +01:00
Daniel Grigencha
2da1fec7dd Updated 'RankingResponseMessage' class.
Updated the 'RankingResponseMessage' class by removing the 'startingPlayerId' attribute and its getter method from it. In Addition the 'rankingResults' attribute and its getter method were added.
2024-12-05 22:01:05 +01:00
Daniel Grigencha
5d76a89b95 Updated 'LobbyState' class.
Updated the 'LobbyState' class by updating the 'JoinedLobbyMessage' and 'LobbyReadyMessage' handlings in it.
2024-12-05 21:58:20 +01:00
Daniel Grigencha
60ebef3518 Updated 'LobbyState' class.
Updated the 'LobbyState' class by adding an 'IncorrectRequestMessage' to the start game handling.
2024-12-05 19:58:23 +01:00
Felix Koppe
a399b14291 Improve interrupt 2024-12-05 18:11:57 +01:00
Felix Koppe
9d21e2ce87 Merge commit 2024-12-05 18:00:27 +01:00
Felix Koppe
2255bfd648 Remove test binding 2024-12-05 18:00:04 +01:00
Hanno Fleischer
74194d8514 Merge branch 'development' into 'dev/model'
Development

See merge request progproj/gruppen-ht24/Gruppe-01!34
2024-12-05 16:58:50 +00:00
Fleischer Hanno
cabd98a24a adjusted a broadcast in lobby state to a send to reduce traffic (original commit f1124f32) and removed souts as well as added comments back into the code 2024-12-05 17:51:18 +01:00
Cedric Beck
f3816cb2a5 added particle_cir.png 2024-12-05 17:49:56 +01:00
Cedric Beck
997c4c589e removed setOwnColor 2024-12-05 17:23:15 +01:00
Cedric Beck
d14a0aef86 fixed ownColor sync problem 2024-12-05 17:22:37 +01:00
Felix Koppe
ac5d7ed74b Add sounds 2024-12-05 17:11:07 +01:00
Felix Koppe
f1124f3245 Fix missing ready update from server on join of new player in LobbyState 2024-12-05 16:38:09 +01:00
Felix Koppe
fc4a357e9e Merge branch 'dev/model' into dev/client_koppe2 2024-12-05 16:25:23 +01:00
Felix Koppe
a8b02faa96 Merge branch 'development' into dev/client_koppe2
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
2024-12-05 16:25:11 +01:00
Felix Koppe
4a7c23708c Add jetAnimation 2024-12-05 16:21:13 +01:00
Hanno Fleischer
4478291852 added the movement of pieces in the intro state to be also done in the model 2024-12-05 15:22:38 +01:00
Hanno Fleischer
0622c35303 fixed state transitions and implemented the Intro state to move the pieces to the correct start setup
added some logic so that the client only transitions to the intro state when the animation has finished at it received the new active Player, and after animating the setup it switches to the corresponding state baserd on  the active player and displays the now new active player.
2024-12-05 14:02:02 +01:00
Daniel Grigencha
3b0cd9ebdb Updated the JavaDocs in multiple classes, to improve readability. 2024-12-05 05:21:33 +01:00
Daniel Grigencha
e81aa67d36 Updated the JavaDocs in multiple classes, to improve readability. 2024-12-05 05:09:15 +01:00
Daniel Grigencha
4fb848420b Updated the 'DisconnectedMessage' class.
Updated the 'DisconnectedMessage' class by adding JavaDocs.
2024-12-05 05:03:39 +01:00
Daniel Grigencha
07a833afe7 Updated the 'ClientInterpreter' interface.
Updated the 'ClientInterpreter' interface by adjusting the JavaDocs.
2024-12-05 05:00:29 +01:00
Daniel Grigencha
6576250113 Updated the 'Player' class.
Updated the 'Player' class by adjusting the JavaDocs and writing the logic for the 'isFinished()' method.
2024-12-05 04:54:12 +01:00
Daniel Grigencha
154efccf31 Updated 'Game' class.
Updated the 'Game' class by adjusting the JavaDocs and rewriting the constructor for maintainability and readability.
2024-12-05 04:52:59 +01:00
Daniel Grigencha
f90aed7bbb Updated 'Color' enum.
Updated the 'Color' enum by adjusting the JavaDocs and adding a new static method 'getColor(int)'
2024-12-05 04:51:55 +01:00
Daniel Grigencha
3a86837307 Updated the JavaDocs in multiple classes, to improve readability. 2024-12-05 04:50:34 +01:00
Daniel Grigencha
da0756452c Updated the JavaDocs in multiple classes, to improve readability. 2024-12-05 04:49:49 +01:00
Daniel Grigencha
bfe8a20f92 Updated 'Board' class.
Updated the 'Board' class by rewriting the constructor, to make it maintainable and scalable.
2024-12-05 04:46:01 +01:00
Felix Koppe
0e6a2499b7 Fix fullscreen issue 2024-12-04 18:38:35 +01:00
Hanno Fleischer
354cdc0a9c added Intro state and its logic 2024-12-04 17:03:57 +01:00
Felix Koppe
00d86c5c10 Improve ceremonyView 2024-12-04 15:33:34 +01:00
Felix Koppe
990e476753 Add Q/E rotation 2024-12-04 15:27:33 +01:00
Felix Koppe
71fc08a05c Add interrupt 2024-12-04 15:11:31 +01:00
Cedric Beck
9e1ca584c7 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-04 13:56:08 +01:00
Cedric Beck
9199fbffd8 removed tests, fixed null exception in CardLayer 2024-12-04 13:56:03 +01:00
Felix Koppe
5c71531277 Merge branch 'development' into dev/client_koppe2 2024-12-04 13:43:33 +01:00
Felix Koppe
6f7c5346d2 Merge commit 2024-12-04 13:34:14 +01:00
Felix Koppe
ef1ce63db6 Try to make server stop on leave 2024-12-04 13:33:39 +01:00
Hanno Fleischer
e8d1442e5b made the model checkstyle compliant 2024-12-04 12:10:48 +01:00
Hanno Fleischer
bdacc4aad3 created the Intro state and renamed all packages to be checkstyle compliant 2024-12-04 12:08:38 +01:00
Cedric Beck
44ef21e6af added handCard Num to playerName display + added remove card in guiHandler 2024-12-04 11:38:35 +01:00
Hanno Fleischer
8e8104b672 forgot to add StartGameMessage in previous commit 2024-12-04 11:10:26 +01:00
Hanno Fleischer
de899cef35 removed from every Message the getInfoTest method and only overrode the toString method in messages with content, in every other message it is handled through the parent class 2024-12-04 11:07:55 +01:00
Felix Koppe
11d6dd4500 Improve video dialog 2024-12-04 10:00:41 +01:00
Felix Koppe
d71f824ca6 Add fullscreen option 2024-12-04 09:41:08 +01:00
Felix Koppe
8e6cb27662 Fix lobbyView ready behavior on tskChange 2024-12-04 07:53:18 +01:00
Daniel Grigencha
29711d6210 Updated 'ClientStartGameMessage' class.
Updated the 'ClientStartGameMesssage' class by preparing it for the correct BPMN diagram.
2024-12-04 02:46:07 +01:00
Daniel Grigencha
29d8e791f6 Updated 'Player' class.
Updated the 'Player' class by adding the 'setPieceInHome' method to it.
2024-12-04 02:44:28 +01:00
Daniel Grigencha
4440341f79 Updated 'LobbyState' class.
Updated the 'LobbyState' class by updating the creation of the 'ServerStartGameMessage' object. In Addition, the start process if all players are ready was removed.
2024-12-04 02:44:03 +01:00
Daniel Grigencha
de5c8bf44c Updated 'Game' class.
Updated the 'Game' class by adding the 'getPlayersAsList' method to it.
2024-12-04 02:43:29 +01:00
Daniel Grigencha
ab5cece1b3 Updated 'StartGameMessage' class.
Updated the 'StartGameMessage' class by removing the unused 'forceStartGame' attribute from it.
2024-12-04 02:42:57 +01:00
Daniel Grigencha
b8ed5060d6 Updated 'Board' class.
Updated the 'Board' class by removing the 'playerData' attribute and its getter method from it.
2024-12-04 02:42:16 +01:00
Daniel Grigencha
c0b72ae4da Updated 'ServerStartGameMessage' class.
Updated the 'ServerStartGameMessage' class by adding the 'players' attribute and its getter method to it.
2024-12-04 02:41:44 +01:00
Daniel Grigencha
c1b4caa82b Updated 'LobbyState' class.
Updated the 'LobbyState' class by updating the 'received(ServerStartGameMessage msg)' method in it after updating the 'ServerStartGameMessage' class.
2024-12-04 02:22:58 +01:00
Daniel Grigencha
a757158477 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by removing the serializer registration in it.
2024-12-04 02:09:20 +01:00
Daniel Grigencha
964ff87b11 Updated client states.
Updated the client states by removing all references to the 'PlayerData' class.
2024-12-04 01:47:31 +01:00
Hanno Fleischer
7053b163e5 adjusted LobbyState in the client to use the correct Data 2024-12-03 18:19:55 +01:00
Hanno Fleischer
81cb2f33ff adjusted all constuctors of nodes so that if someone creates a node the piece will be null and the option for a constuctor without arguments is still given for serialization purposes 2024-12-03 17:56:39 +01:00
Hanno Fleischer
69865bb504 added the playeringamenotification to be created from the right dataset 2024-12-03 16:48:08 +01:00
Felix Koppe
db50986f3f Fix serialisation issue 2024-12-03 15:38:13 +01:00
Felix Koppe
a0a088a0c4 Fix minor error in notification processing 2024-12-03 15:00:00 +01:00
Cedric Beck
c4d11ff961 added window title 2024-12-03 09:15:14 +01:00
Daniel Grigencha
bb51976127 Updated 'Node' class.
Updated the 'Node' class by overload the 'isOccupied' method in it.
2024-12-03 04:57:30 +01:00
Daniel Grigencha
0db1f08f3c Updated abstract 'GameAutomatonState' class.
Updated the abstract 'GameAutomatonState' class by adding the 'getGameAutomaton' method to it.
2024-12-03 04:49:16 +01:00
Daniel Grigencha
336f1ec316 Updated 'Resources' class.
Updated the 'Resources' class by adding the 'MAX_EYES' constant to it.
2024-12-03 04:29:16 +01:00
Daniel Grigencha
a1e687912a Updated abstract 'TurnAutomatonState' class.
Updated the abstract 'TurnAutomatonState' class by adding the 'getTurnAutomaton' method to it.
2024-12-03 04:09:35 +01:00
Daniel Grigencha
2248d044c1 Updated 'AnimationState' class.
Updated the 'AnimationState' class by updating the content inside the 'received(AnimationEndMessage msg, int from) method in it.
2024-12-03 03:41:58 +01:00
Daniel Grigencha
79bf1c16e8 Updated 'Game' class.
Updated the 'Game' class by adding the 'getActivePlayerId' method to it.
2024-12-03 03:40:24 +01:00
Daniel Grigencha
3353a890d3 Updated 'Game' class.
Updated the 'Game' class by adding the 'getPlayerIdByColor' method to it.
2024-12-03 03:36:22 +01:00
Daniel Grigencha
a012402a85 Updated abstract 'TurnAutomatonState' class.
Updated the abstract 'TurnAutomatonState' class by updating the JavaDoc text of the constructor.
2024-12-03 02:15:10 +01:00
Daniel Grigencha
5aaf8d4850 Updated 'TurnState' class.
Updated the 'TurnState' class by setting the start state in it.
2024-12-03 01:31:22 +01:00
Daniel Grigencha
35ab777f04 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by fixing the logic inside the received(RequestDieMessage msg, int from)' method in it.
2024-12-03 01:30:39 +01:00
Daniel Grigencha
c707abc465 Updated 'Die' class.
Updated the 'Die' class by adding another constructor for test cases to it.
2024-12-03 01:04:02 +01:00
Daniel Grigencha
2a84e7cf65 Updated 'Player' class.
Updated the 'Player' class by moving all content of 'PlayerData' class in 'Player' class.
2024-12-03 00:51:45 +01:00
Daniel Grigencha
3a02edb944 Updated 'PlayerData' class.
Updated the 'PlayerData' class by updating the 'Piece' creation inside the constructor.
2024-12-03 00:50:53 +01:00
Daniel Grigencha
1870d4fe0e Updated 'ShieldState' enumeration.
Updated the 'ShieldState' enumeration by removing unused methods from it.
2024-12-03 00:49:09 +01:00
Daniel Grigencha
5cf9746931 Updated 'PieceState' enumeration.
Updated the 'PieceState' enumeration by removing unused methods from it.
2024-12-03 00:48:39 +01:00
Daniel Grigencha
5b9bc7aa36 Updated 'Piece' class.
Updated the 'Piece' class by removing the unused 'id' parameter from the constructor.
2024-12-03 00:38:24 +01:00
Daniel Grigencha
abe66aff5d Updated 'LobbyState' class.
Updated 'LobbyState' class by removed unused imports in it.
2024-12-03 00:37:24 +01:00
Fleischer Hanno
eea566cc8b added the logic for server shutdown 2024-12-02 23:25:54 +01:00
Daniel Grigencha
bd07a44607 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 23:24:02 +01:00
Daniel Grigencha
1f64676d31 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by adding the 'shutdown' message to it.
2024-12-02 23:23:56 +01:00
Daniel Grigencha
838f59b9aa Updated 'ServerState' class.
Updated the 'ServerState' class by filling the 'received(LeaveGameMessage msg, int from)' in it.
2024-12-02 23:22:45 +01:00
Daniel Grigencha
002a42be38 Updated 'LobbyState' class.
Updated the 'LobbyState' class by removing the 'received(LeaveGameMessage msg, int from)' from it.
2024-12-02 23:21:42 +01:00
Daniel Grigencha
a1d10521ac Updated 'ServerSener' interface.
Updated the 'ServerSender' interface by adding the 'shutdown' method to it.
2024-12-02 23:14:29 +01:00
Fleischer Hanno
4e6a272e7a added that when the client is in the game state and recieves the ceremony message it always changes to ceremony state 2024-12-02 23:08:56 +01:00
Daniel Grigencha
516848a67e Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 23:06:49 +01:00
Daniel Grigencha
659d69d3eb Updated 'GameState' class.
Updated the 'GameState' class by sending a broadcast message after a player left the game and only one player is remaining.
2024-12-02 23:06:44 +01:00
Daniel Grigencha
fb6cbeaaf5 Updated 'ServerStartGameMessage' class.
Updated the 'ServerStartGameMessage' class by adding the 'board' attribute and its getter method to it.
2024-12-02 23:00:39 +01:00
Cedric Beck
25f750c8b6 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 22:58:58 +01:00
Cedric Beck
3f49b432c4 added SSAO and FXAA 2024-12-02 22:58:52 +01:00
Daniel Grigencha
252c37ae9a Updated 'LobbyState' class.
Updated the 'LobbyState' class by sending the 'ServerStartGameMessage' with a 'Board' object.
2024-12-02 22:58:46 +01:00
Fleischer Hanno
4566d4c9a8 Merge remote-tracking branch 'origin/development' into development 2024-12-02 22:46:12 +01:00
Fleischer Hanno
e9ba888651 changed the logic so that the isHost is not decided by the client and instead by the server 2024-12-02 22:45:44 +01:00
Cedric Beck
cbbb98037b Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 22:43:16 +01:00
Cedric Beck
aa651ec62f added trees 2024-12-02 22:43:03 +01:00
Daniel Grigencha
e163b87cc4 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by updating the logic inside the 'connectionAdded' method in it.
2024-12-02 22:41:17 +01:00
Daniel Grigencha
1eb24b7a66 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by updating the logic inside the 'connectionAdded' method in it.
2024-12-02 22:37:46 +01:00
Daniel Grigencha
492f7422f5 Updated 'LobbyAcceptMessage' class.
Updated the 'LobbyAcceptMessage' class by adding the 'host' attribute and its getter method to it.
2024-12-02 22:36:48 +01:00
Daniel Grigencha
27f8af70f5 Updated 'Game' class.
Updated the 'Game' class by setting the default value of 'host' attribute. In Addition, the 'isHost' method was added.
2024-12-02 22:34:45 +01:00
Fleischer Hanno
5910fcc701 added the client logic to receive the LobbyAccept and LobbyDeny message 2024-12-02 21:52:07 +01:00
Felix Koppe
e94ed1e019 Fix syntax error 2024-12-02 21:51:09 +01:00
Felix Koppe
7d54a906dd Add some more names 2024-12-02 21:48:23 +01:00
Felix Koppe
5ae65921bf Add more random names 2024-12-02 21:42:12 +01:00
Daniel Grigencha
468e4005dc Updated 'LobbyState' class.
Updated the 'LobbyState' class by sending a broadcast update the new ready state of the client.
2024-12-02 21:23:53 +01:00
Daniel Grigencha
72321eab9a Updated 'LobbyState' class.
Updated the 'LobbyState' class by updating all received methods in it.
2024-12-02 21:22:49 +01:00
Daniel Grigencha
951c92d890 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 21:12:03 +01:00
Felix Koppe
e87eb569c2 Add showInfo to MdgaView 2024-12-02 21:16:50 +01:00
Felix Koppe
baa967ecfc Merge commit 2024-12-02 21:12:56 +01:00
Felix Koppe
8d39d61c71 Add infoNotification 2024-12-02 21:12:43 +01:00
Daniel Grigencha
7fcee3cac0 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by register the 'IncorrectRequestMessage' class to the serializer.
2024-12-02 21:11:59 +01:00
Daniel Grigencha
06d4b322e7 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 21:04:41 +01:00
Daniel Grigencha
1cf14f65bb Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by updating the 'connectionAdded' method in it. In Addition, the JavaDoc text for this method was addded.
2024-12-02 21:04:36 +01:00
Fleischer Hanno
ebb9f839c7 added JavaDocs in Resources.java 2024-12-02 20:58:00 +01:00
Daniel Grigencha
3eef4b2a02 Updated 'PlayerData' class.
Updated the 'PlayerData' class by replacing the magic constants with the 'Resources' class. In Addition, some JavaDoc texts were updated.
2024-12-02 20:56:59 +01:00
Daniel Grigencha
c1fa679261 Updated 'Resources' class.
Updated the 'Resources' class by adding the 'MAX_PIECES' constant to it.
2024-12-02 20:45:07 +01:00
Daniel Grigencha
c48f924ead Updated 'Resources' class.
Updated the 'Resources' class by adding the 'MAX_PLAYERS' constant to it.
2024-12-02 20:43:47 +01:00
Fleischer Hanno
73859d8c81 added methods for getting Boolean, String, Double and int 2024-12-02 20:34:52 +01:00
Felix Koppe
1918aa80ff Merge commit 2024-12-02 20:33:36 +01:00
Felix Koppe
d062b9dabc Add forceStartGameButton to host in lobby 2024-12-02 20:32:46 +01:00
Fleischer Hanno
7ddcdc3f48 added the first error.messages and adjusted 2024-12-02 20:24:17 +01:00
Fleischer Hanno
2cefc2c293 Merge remote-tracking branch 'origin/development' into development 2024-12-02 20:19:44 +01:00
Daniel Grigencha
c4304ae99a Updated 'Game' class.
Updated the 'Game' class by removing the 'allReady' attribute in it. In Addtion, the 'areAllReady' method was added.
2024-12-02 20:19:13 +01:00
Fleischer Hanno
005df94114 added Resources calss to access teh properties 2024-12-02 19:55:18 +01:00
Daniel Grigencha
44f893ccef Updated 'Game' class.
Updated the 'Game' class by setting the 'die' attribute correctly inside the constructor.
2024-12-02 19:45:11 +01:00
Daniel Grigencha
0d9a922f55 Removed 'PlayerDataMessage' and 'StartBriefingMessage'. 2024-12-02 19:16:01 +01:00
Daniel Grigencha
289158cf35 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by removing the 'PlayerDataMessage' and 'StartBriefingMessage' from the serializer.
2024-12-02 19:14:11 +01:00
Fleischer Hanno
0a0762b6c9 removed all instances of PlayerDataMEssage and StartBriefingMessage 2024-12-02 19:07:58 +01:00
Fleischer Hanno
90a21087df added logic for incorrectRequest message and removed messages playerdata and startbriefing and created javadocs 2024-12-02 19:02:00 +01:00
Daniel Grigencha
294ecdc56f Updated 'IncorrectRequestMessage' class.
Updated the 'IncorrectRequestMessage' class by updating the content inside the 'accept' method in it.
2024-12-02 19:00:21 +01:00
Daniel Grigencha
347ed152b8 Added 'IncorrectRequestMessage' class.
Added the 'IncorrectRequestMessage' class to this project. It will be used to send the client an incorrect request message to show they did something wrong.
2024-12-02 18:58:15 +01:00
Daniel Grigencha
3daafde9f1 Updated 'ServerInterpreter' class.
Updated the 'ServerInterpreter' class by adding the 'received(IncorrectRequestMessage msg)' method to it.
2024-12-02 18:50:42 +01:00
Daniel Grigencha
09fda6b167 Updated 'Game' class.
Updated the 'Game' class by adding the 'isColorTaken' method to it.
2024-12-02 18:18:04 +01:00
Cedric Beck
f21fd9b0a6 added setPauseOnLostFocus(false) 2024-12-02 17:28:37 +01:00
Cedric Beck
41e204b1f2 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 17:12:00 +01:00
Cedric Beck
208261c6bf added clickDice Action + modelSync 2024-12-02 17:11:55 +01:00
Hanno Fleischer
0411f2ead4 fixed state transitions in gamestateclient automaton 2024-12-02 17:08:46 +01:00
Cedric Beck
eb819d4d5e merge development 2024-12-02 16:49:37 +01:00
Cedric Beck
a2856bb157 added rollRankingResults 2024-12-02 16:47:28 +01:00
Daniel Grigencha
bcf17a0651 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 16:35:49 +01:00
Daniel Grigencha
eee6fccde5 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by setting the start state correctly and enter it.
2024-12-02 16:35:40 +01:00
Felix Koppe
2118c72891 Adjust RollDiceNotification 2024-12-02 16:34:55 +01:00
Daniel Grigencha
cb362c4d0c Used auto-reformate code. 2024-12-02 16:31:57 +01:00
Daniel Grigencha
1bcb73cff7 Updated 'PlayerData' class.
Updated the 'PlayerData' class by adding the 'isFinished' method to it. In Addition, the empty constructor was optimized to initalize all class attributes for serializable cases.
2024-12-02 16:29:32 +01:00
Daniel Grigencha
167d898a3c Updated 'StartNode' class.
Updated the 'StartNode' class by reverting all enumerations to its origin types in it.
2024-12-02 16:25:33 +01:00
Daniel Grigencha
6c136b78b8 Updated 'Piece' class.
Updated the 'Piece' class by reverting all enumeration to its orgin types.
2024-12-02 16:22:50 +01:00
Felix Koppe
82234a7ff9 Merge commit 2024-12-02 15:44:41 +01:00
Felix Koppe
92d2e74748 Fix error regarding color in lobbyState 2024-12-02 13:06:59 +01:00
Felix Koppe
4561a962d4 Fix lobby isSelf logic and no longer assign color on join 2024-12-02 12:52:07 +01:00
Hanno Fleischer
5db7b64cef fixed bug with seriliazation of Board, now sending playerdata seperate from teh board 2024-12-02 12:16:53 +01:00
Felix Koppe
bb1b721e77 Fix lobby in serverAutomaton and adjust TskUpdateMessage 2024-12-02 11:48:54 +01:00
Cedric Beck
206cad2f79 removed test card 2024-12-02 11:01:02 +01:00
Cedric Beck
3717e7b794 added javadocs to board 2024-12-02 03:48:11 +01:00
Cedric Beck
a15d7932d4 fixed bug 2024-12-02 03:15:18 +01:00
Cedric Beck
d649e41e75 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 03:13:48 +01:00
Cedric Beck
6d0552f5a7 added javadocs to animation; moved outline package 2024-12-02 03:13:42 +01:00
Fleischer Hanno
47f9f46277 Merge remote-tracking branch 'origin/development' into development 2024-12-02 03:03:47 +01:00
Felix Koppe
b87f5de5fb Fix error 2024-12-02 03:03:54 +01:00
Fleischer Hanno
c434bcb684 minor changes 2024-12-02 03:03:36 +01:00
Daniel Grigencha
b9617c0a14 Merge remote-tracking branch 'origin/development' into development 2024-12-02 02:53:05 +01:00
Daniel Grigencha
0aa73ca6ee added the received method for a Shutdown Message 2024-12-02 02:52:48 +01:00
Cedric Beck
fbc7246037 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 02:52:41 +01:00
Cedric Beck
2703084df1 added move/swap/throw animation; reworked waitingNodes logic in BoardHandler 2024-12-02 02:51:52 +01:00
Daniel Grigencha
31b1d535ac added a new 'ShutdownMessage' for the server 2024-12-02 02:32:50 +01:00
Daniel Grigencha
b3fb2f8fa4 added a new 'ShutdownMessage' for the server 2024-12-02 02:31:55 +01:00
Daniel Grigencha
8fcac9b809 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 02:15:29 +01:00
Daniel Grigencha
6985d988f4 Updated 'DetermineStartPlayerState' class.
Updated the 'DetermineStartPlayerState' class by updating the logic inside the 'received(RequestDieMessage msg, int from)' method in it.
2024-12-02 02:15:22 +01:00
Fleischer Hanno
6c3103b2ed fixing serialization error 2024-12-02 02:14:45 +01:00
Fleischer Hanno
3658c88d72 made all enums look like the color enum 2024-12-02 02:11:40 +01:00
Fleischer Hanno
bfc812b003 minas please help no serialization 2024-12-02 02:07:02 +01:00
Felix Koppe
413f35d7bf Merge commit 2024-12-02 01:59:49 +01:00
Felix Koppe
c5cb3d4dd0 Add restart on resolution change 2024-12-02 01:59:37 +01:00
Daniel Grigencha
efb4439431 Updated 'Game' class.
Updated the 'Game' class by adding the 'setDie' method to it.
2024-12-02 01:53:10 +01:00
Felix Koppe
601366f08d Merge commit 2024-12-02 01:40:58 +01:00
Felix Koppe
918c1f2a8a Add videoSettings 2024-12-02 01:40:45 +01:00
Daniel Grigencha
bf75b8afc9 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 01:39:07 +01:00
Daniel Grigencha
0b45727bd7 Updated 'Die' class.
Updated the 'Die' class by adding a third constructor for test cases.
2024-12-02 01:39:01 +01:00
Daniel Grigencha
a19ac2fc51 Updated 'GameState' class.
Updated the 'GameState' class by changing the data types of 'determineStartPlayerState, 'animationState' and 'turnState' attributes in it.
2024-12-02 01:27:21 +01:00
Daniel Grigencha
79f0e55c52 default value of playerData in 'Board' 2024-12-02 01:19:41 +01:00
Fleischer Hanno
44378486d4 Merge remote-tracking branch 'origin/development' into development 2024-12-02 01:17:14 +01:00
Fleischer Hanno
149931d2cb fixed PlayerData mistake 2024-12-02 01:16:33 +01:00
Daniel Grigencha
1250500558 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 01:01:49 +01:00
Daniel Grigencha
177bfe3001 Updated 'DetermineStartPlayerState' class.
Updated the 'DeterminStartPlayerState' class by removing the 'getDiceResults' method from it. In Addition, the logic inside the 'received(RequestDieMessage msg, int from)' was added.
2024-12-02 01:01:42 +01:00
Felix Koppe
138444439d Merge remote-tracking branch 'origin/development' into development 2024-12-02 00:53:58 +01:00
Felix Koppe
702f96b8db Add videoSettings 2024-12-02 00:53:11 +01:00
Fleischer Hanno
1b6407b75b fixed bug 2024-12-02 00:52:28 +01:00
Fleischer Hanno
c649b8f3ae creating MapCreation Notification 2024-12-02 00:44:11 +01:00
Daniel Grigencha
41a669d44d Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 00:42:17 +01:00
Daniel Grigencha
4b6b12c0ac Updated 'Board' class.
Updated the 'Board' class by adding the 'addPlayerData' method to it.
2024-12-02 00:42:07 +01:00
Daniel Grigencha
c1280ba089 Updated 'LobbyState' class.
Updated the 'LobbyState' class by adding 'initializeGame' method to it.
2024-12-02 00:40:21 +01:00
Felix Koppe
02f285a258 Remove cheat button 2024-12-02 00:39:58 +01:00
Daniel Grigencha
bcb0ebc0f8 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 00:29:21 +01:00
Daniel Grigencha
a19902f819 Updated 'LobbyState' class.
Updated the 'LobbyState' class by changing the method call 'isActive' to 'isReady'.
2024-12-02 00:28:59 +01:00
Fleischer Hanno
585687055c Merge remote-tracking branch 'origin/development' into development 2024-12-02 00:28:41 +01:00
Fleischer Hanno
a86319082c included the LobbyPlayerJoined MEssage parameter of isHost 2024-12-02 00:28:04 +01:00
Daniel Grigencha
7acc55fe25 Updated 'MdgaServer' class.
Updated the 'MdgaServer' class by saving the host into the 'Game' class.
2024-12-02 00:28:01 +01:00
Daniel Grigencha
3e56de2a17 Updated 'LobbyState' class.
Updated the 'LobbyState' class by changing the method call 'isActive' to 'isReady'.
2024-12-02 00:23:03 +01:00
Daniel Grigencha
468cd97374 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-02 00:20:41 +01:00
Daniel Grigencha
dee6bf9f9c Updated 'Game' class.
Updated the 'Game' class by adding the 'host' attribute and its getter method to it.
2024-12-02 00:19:46 +01:00
Daniel Grigencha
ea6431faa4 Updated 'LobbyPlayerJoinedMessage' class.
Updated the 'LobbyPlayerJoinedMessage' class by adding the 'host' attribute and its getter method to.
2024-12-02 00:12:46 +01:00
Cedric Beck
f272fd6f08 fixed bug again 2024-12-02 00:10:58 +01:00
Cedric Beck
bfc74ee126 fixed bug 2024-12-02 00:04:48 +01:00
Cedric Beck
d890d11978 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:58:57 +01:00
Daniel Grigencha
9af20346f1 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:58:44 +01:00
Cedric Beck
f853b0b54f Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:57:59 +01:00
Cedric Beck
9dd2d3f58b added finish text; edited Notifications 2024-12-01 23:57:53 +01:00
Daniel Grigencha
06e43903e6 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:57:39 +01:00
Fleischer Hanno
00a04c2403 fixed a bug 2024-12-01 23:57:39 +01:00
Daniel Grigencha
92cbd9202a Updated 'LobbyState' class.
Updated the 'LobbyState' class by fixing the error by sending the 'ServerStartGameMessage' after all players are ready.
2024-12-01 23:57:26 +01:00
Felix Koppe
9a06afe998 Merge commit 2024-12-01 23:56:33 +01:00
Felix Koppe
289390c528 Add comment 2024-12-01 23:56:24 +01:00
Fleischer Hanno
7712a23d00 added transition from Lobby to Game 2024-12-01 23:50:37 +01:00
Fleischer Hanno
8137a727f5 Merge remote-tracking branch 'origin/development' into development 2024-12-01 23:49:30 +01:00
Felix Koppe
d904a28ee0 Remove mock 2024-12-01 23:48:18 +01:00
Fleischer Hanno
2f2d9c7479 Merge remote-tracking branch 'origin/development' into development 2024-12-01 23:42:56 +01:00
Fleischer Hanno
c2b6e6e9e9 made all classes for ServerStartGameMEssage serializiable 2024-12-01 23:42:32 +01:00
Daniel Grigencha
697b974ce1 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:42:09 +01:00
Felix Koppe
f0cab64910 Minor change 2024-12-01 23:46:46 +01:00
Daniel Grigencha
adcf65d7aa Updated 'LobbyState' class.
Updated the 'LobbyState' class by sending a broadcast message after all players are ready. In Addition, the 'ServerStartGameMessage' getting the 'Board' object.
2024-12-01 23:41:10 +01:00
Daniel Grigencha
8f53b76a3e Updated 'ServerStartGameMessage' class.
Updated the 'ServerStartGameMessage' class by adding the 'board' attribute and its getter method to it.
2024-12-01 23:39:36 +01:00
Cedric Beck
eaef4b20ba Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:34:02 +01:00
Cedric Beck
d3fe2fec14 added show/hide functionality for select button 2024-12-01 23:33:28 +01:00
Daniel Grigencha
02d7ef1dd8 Updated 'ChoosePieceState' class.
Updated the 'ChoosePieceState' to work correctly as a state automaton.
2024-12-01 23:28:50 +01:00
Daniel Grigencha
23aa2db714 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:27:50 +01:00
Daniel Grigencha
e68369074f Updated 'RollDiceState' class.
Updated the 'RollDiceState' class by removing a spelling mistake.
2024-12-01 23:27:45 +01:00
Daniel Grigencha
aafc804c3f Updated 'ChoosePieceState' class.
Updated the 'ChoosePieceState' to work correctly as a state automaton.
2024-12-01 23:26:53 +01:00
Felix Koppe
bec199036c Minor change 2024-12-01 23:10:25 +01:00
Fleischer Hanno
a7e048d4b4 Merge remote-tracking branch 'origin/development' into development 2024-12-01 23:08:50 +01:00
Felix Koppe
bcd510804f Merge commit 2024-12-01 23:12:46 +01:00
Felix Koppe
7319f7a62c Process startMenuNotification in lobby 2024-12-01 23:12:36 +01:00
Fleischer Hanno
a1e51fb2f4 added logic to the leave game method 2024-12-01 23:08:25 +01:00
Daniel Grigencha
f15d0eb5d8 Merge branch 'development' of https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-01 into development 2024-12-01 23:06:31 +01:00
Daniel Grigencha
f484a4abc8 Updated server states.
Updated server stats by adding the logger to all states. In Addition, new joined clients should be updated correctly.
2024-12-01 23:06:02 +01:00
Felix Koppe
5905a232a5 Adjust state 2024-12-01 23:03:26 +01:00
Cedric Beck
12cb87ef70 added outline package back 2024-12-01 22:56:09 +01:00
Cedric Beck
9cb7a156bb Merge branch 'development' into dev/client_beck 2024-12-01 22:47:48 +01:00
Fleischer Hanno
76e2606847 reverted the client state machine 2024-12-01 22:47:13 +01:00
Cedric Beck
7762b97303 added init & taktical view by ownColor 2024-12-01 22:46:17 +01:00
Daniel Grigencha
eaf46f3e14 updated the project to pass the checkstyle 2024-12-01 22:37:49 +01:00
Fleischer Hanno
92be52b62a fixed server join logic 2024-12-01 22:33:30 +01:00
Fleischer Hanno
7a0a3589a5 trying to fix server ip to use outward ip 2024-12-01 22:14:09 +01:00
Cedric Beck
661c28f096 merge to dev/client_beck from development 2024-12-01 22:06:30 +01:00
Cedric Beck
4a94e85aac Merge branch 'dev/client' into dev/client_beck 2024-12-01 21:55:37 +01:00
Felix Koppe
799bd096a0 Merge development 2024-12-01 21:56:52 +01:00
Fleischer Hanno
5a68d3b146 fixed a method call for getMoveIndices 2024-12-01 21:52:28 +01:00
Cedric Beck
63fa692910 removed comments 2024-12-01 21:52:28 +01:00
Felix Koppe
638cb93d7b Merge dev/client_beck 2024-12-01 21:51:01 +01:00
Fleischer Hanno
133f900ec7 added the server and network functionality for mdga and fixed the communication in the Lobby 2024-12-01 21:50:28 +01:00
Fleischer Hanno
33ddea4221 Merge remote-tracking branch 'origin/development' into development 2024-12-01 21:49:30 +01:00
Daniel Grigencha
977a7294ad updated server state diagram and added missing classes 2024-12-01 21:49:11 +01:00
Cedric Beck
7db75a2ca1 added converted assets 2024-12-01 21:48:40 +01:00
Daniel Grigencha
71d5701cc7 added javadoc for the classes in the notification package 2024-12-01 21:44:35 +01:00
Fleischer Hanno
42feca466d Merge remote-tracking branch 'origin/development' into development 2024-12-01 21:05:41 +01:00
Daniel Grigencha
95a1f8d858 removed all inheritance of the automaton for testing purposes 2024-12-01 21:05:22 +01:00
Fleischer Hanno
76757c19a9 Merge remote-tracking branch 'origin/development' into development 2024-12-01 21:02:11 +01:00
Daniel Grigencha
c3fdcf4dc7 added the states of the server automaton for testing purposes 2024-12-01 21:01:37 +01:00
Fleischer Hanno
8ffab12f49 Merge remote-tracking branch 'origin/development' into development 2024-12-01 20:57:57 +01:00
Cedric Beck
da2b1af698 fixed shutdown guiHandler bug 2024-12-01 20:45:24 +01:00
Daniel Grigencha
8369797120 added JavaDocs for the 'InterruptState' classe 2024-12-01 20:41:30 +01:00
Daniel Grigencha
ef450a23f5 added logger to server state chart 2024-12-01 20:35:40 +01:00
Cedric Beck
e5b007accd debug commit 2024-12-01 20:24:26 +01:00
Daniel Grigencha
17f0aa0209 added the 'Die' class to the 'Game' class 2024-12-01 19:46:59 +01:00
Fleischer Hanno
1d8eff8ea9 Merge remote-tracking branch 'origin/development' into development 2024-12-01 19:45:06 +01:00
Daniel Grigencha
ff31335a98 added a new class 'Die' to handle the dice. added this class to the class 'Game' 2024-12-01 19:38:26 +01:00
Daniel Grigencha
3467dd2f04 added a new method getColorByIndex(int) and next() method to the enum 'Color' 2024-12-01 19:37:40 +01:00
Daniel Grigencha
fb6cfaa518 added JavaDocs for the classes in the package 'game' 2024-12-01 19:36:58 +01:00
Cedric Beck
c08c81ea46 edited map 2024-12-01 18:42:57 +01:00
Fleischer Hanno
e9d45f241f Merge remote-tracking branch 'origin/development' into development 2024-12-01 18:22:29 +01:00
Daniel Grigencha
bf38a7e00c added a new method 'disconnectClient' in 'ServerSender' interface 2024-12-01 18:21:54 +01:00
Fleischer Hanno
5d638a1da7 Merge remote-tracking branch 'origin/development' into development 2024-12-01 18:20:16 +01:00
Fleischer Hanno
b83279e835 Merge remote-tracking branch 'origin/development' into development 2024-12-01 18:16:05 +01:00
Daniel Grigencha
eb54cfbc80 adjusted constructor for mdga server 2024-12-01 18:15:56 +01:00
Cedric Beck
453aacfe1a fixed missing messages 2024-12-01 18:12:31 +01:00
Daniel Grigencha
1513576291 added javadocs to the 'ServerState' class 2024-12-01 18:03:46 +01:00
Cedric Beck
2732a89da6 fixed error with SelectObjectOutliner 2024-12-01 17:47:04 +01:00
Felix Koppe
5b8032bed9 Prepare for model 2024-12-01 17:44:37 +01:00
Felix Koppe
21f98de3e6 Minor adjustment 2024-12-01 17:32:05 +01:00
Cedric Beck
b099972656 merge from dev/client_beck 2024-12-01 17:10:04 +01:00
Fleischer Hanno
5206b03966 added java docs 2024-12-01 17:03:56 +01:00
Cedric Beck
eb703cbd2c fixed bad performancegit status!; edited GameNotification; fixed error because of changed 'Color' enum 2024-12-01 17:03:09 +01:00
Daniel Grigencha
8c03b282b3 deleted the color attribute and their usages 2024-12-01 16:58:37 +01:00
Daniel Grigencha
a29e942191 added logic to the 'MdgaServer' class 2024-12-01 16:57:48 +01:00
Daniel Grigencha
efc7a2f09d added logic to the 'LobbyState' class
- for leaving the lobby
2024-12-01 16:49:05 +01:00
Daniel Grigencha
121d668bf2 added logic to the 'LobbyState' class 2024-12-01 16:08:14 +01:00
Fleischer Hanno
ba5b9dc4b4 added the getter for the forcestartGame value in teh corresponding message 2024-12-01 16:04:55 +01:00
Daniel Grigencha
772c7a51e0 updated enum 'Color' 2024-12-01 15:54:06 +01:00
Fleischer Hanno
ed04bc1119 changed all messages to work with a UUID of a piece instead of a string identifier 2024-12-01 15:45:30 +01:00
Fleischer Hanno
7712ee2e7c renamed JoinServerMessage to JoinedLobbyMessage and wrote a getter for the name in the message 2024-12-01 15:14:42 +01:00
Fleischer Hanno
1d5733a4b9 added a method for getting a piece through a uuid 2024-12-01 15:05:26 +01:00
Fleischer Hanno
dfea7e8736 added java docs for choosepowercard 2024-12-01 14:37:04 +01:00
Felix Koppe
2c81665f80 Update startmenu.png 2024-12-01 14:22:18 +01:00
Felix Koppe
150e4e4c22 Merge development 2024-12-01 14:18:44 +01:00
Felix Koppe
4ff84e64ed Merge development 2024-12-01 14:11:10 +01:00
Felix Koppe
e6fb78507c Remove garbarge files 2024-12-01 14:10:04 +01:00
Felix Koppe
9067a9b04c Add javadoc 2024-12-01 14:08:33 +01:00
Felix Koppe
6758abd60e Add javadoc to buttons 2024-12-01 13:56:45 +01:00
Fleischer Hanno
e70331d85d changed parameter of DieMessage and included the force resumgame logic in intterrupt 2024-12-01 13:56:31 +01:00
Felix Koppe
c3ad8fe79a Gerneral improvements 2024-12-01 13:35:14 +01:00
Fleischer Hanno
26d2d0587d added the remaining logic for GameState and its substatemachines 2024-12-01 12:36:44 +01:00
Fleischer Hanno
00b3ef1d80 added selectNext in CLG 2024-12-01 12:12:37 +01:00
Fleischer Hanno
789868863f added some more client game logic 2024-12-01 12:06:06 +01:00
Fleischer Hanno
bdc527b83e added more logic to the client (choosepiece and powercard) 2024-12-01 08:40:16 +01:00
Fleischer Hanno
5ff56ed9d8 added all necessary logic for the turn waiting class and adjusted some messages 2024-11-30 22:23:40 +01:00
Fleischer Hanno
81d037d232 made the determinstartplayer machine fully functional 2024-11-30 20:50:57 +01:00
Fleischer Hanno
422e94ec48 Dialog state machine is now fully functional 2024-11-30 19:49:52 +01:00
Fleischer Hanno
b3d754e77f added all current State getter in every client state machine 2024-11-30 16:42:24 +01:00
Fleischer Hanno
0297193be1 added more functionality to the client state machine and implemeted the first notifications 2024-11-30 16:23:09 +01:00
Felix Koppe
a630ade2e1 Fix lobbyView leave 2024-11-30 14:21:45 +01:00
Felix Koppe
b197d70d44 Fix errors 2024-11-30 14:18:00 +01:00
Felix Koppe
67d120c278 Fix error 2024-11-30 14:10:42 +01:00
Felix Koppe
d53067f21a Merge dev/client 2024-11-30 14:03:54 +01:00
Felix Koppe
4313468a0c Finish merge 2024-11-30 14:00:42 +01:00
Felix Koppe
0393e9b534 Fixing errors 2024-11-30 13:47:34 +01:00
Felix Koppe
f3bc6bc2f0 Fixing errors 2024-11-30 13:38:08 +01:00
Felix Koppe
12abe081c9 Minor changes 2024-11-30 13:21:31 +01:00
Felix Koppe
02b536aa82 102IQ 2024-11-30 13:17:53 +01:00
Felix Koppe
99ffee749e 101IQ 2024-11-30 13:14:41 +01:00
Felix Koppe
6f71a8b16d Work# 2024-11-30 13:12:51 +01:00
Felix Koppe
a78b3acacd 100IQ 2024-11-30 13:10:46 +01:00
Fleischer Hanno
0487ff0238 modified messages to work with piece uuid instead of an identifier. 2024-11-30 13:02:23 +01:00
Felix Koppe
6a85aca970 Readd .run after some fool deleted it 2024-11-30 12:56:12 +01:00
Felix Koppe
c2cfd8c175 Merge remote-tracking branch 'origin/dev/client_beck' into dev/client_beck
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
#	Projekte/mdga/model/src/main/java/pp.mdga/server/Lobby.java
2024-11-30 12:48:49 +01:00
Felix Koppe
af5d4a95cd Remove notifications 2024-11-30 12:45:08 +01:00
Felix Koppe
129ce54dd8 Merge branch 'refs/heads/development' into dev/client_beck
# Conflicts:
#	Projekte/mdga/model/src/main/java/pp.mdga/server/DetermineStartPlayer.java
#	Projekte/mdga/model/src/main/java/pp.mdga/server/Lobby.java
#	Projekte/mdga/model/src/main/java/pp.mdga/server/Turn.java
#	Projekte/mdga/model/src/main/java/pp/mdga/notification/SelectableMoveNotification.java
#	Projekte/mdga/model/src/main/java/pp/mdga/notification/SelectablePiecesNotification.java
#	Projekte/mdga/model/src/main/java/pp/mdga/notification/SelectableSwapNotification.java
2024-11-30 12:44:04 +01:00
Felix Koppe
8c8bd4db0d Add sounds 2024-11-30 12:37:18 +01:00
Cedric Beck
1c222ce0e0 tried mergee with development 2024-11-30 12:36:09 +01:00
Cedric Beck
f7a34d0d59 tried merge with dev/client 2024-11-30 12:34:39 +01:00
Cedric Beck
cbb21e92f8 added init/shutdown 2024-11-30 12:07:42 +01:00
Fleischer Hanno
4d6cd63a3d made all notifications constructors public 2024-11-30 11:24:46 +01:00
Felix Koppe
36d31e99e9 Fix error 2024-11-30 11:19:54 +01:00
Felix Koppe
28a2c9a448 Add confirm button 2024-11-30 11:17:42 +01:00
Felix Koppe
dd8b16f1ac Merge commit 2024-11-30 10:52:44 +01:00
Felix Koppe
be15b3bd63 Minor changes 2024-11-30 10:51:35 +01:00
Cedric Beck
07f0f55192 added further notification implemenation 2024-11-30 00:33:10 +01:00
Cedric Beck
13690cf73d added skybox; reworked guiHandler 2024-11-29 21:28:50 +01:00
Cedric Beck
220d8ff47e added activePlayer+diceNum text display 2024-11-29 17:26:04 +01:00
Cedric Beck
67bb30d124 added new notifications for client-view communication 2024-11-29 15:23:34 +01:00
Felix Koppe
184410ba31 Merge commit 2024-11-29 14:38:23 +01:00
Felix Koppe
3147f5b7a3 Fix some errors 2024-11-29 14:37:46 +01:00
Felix Koppe
0e79f35cb0 Merge dev/client_koppe2 2024-11-29 14:02:59 +01:00
Felix Koppe
f024ba4866 Add preferences and sounds 2024-11-29 14:01:18 +01:00
Felix Koppe
35270cce7b Fix ceremony statistics error 2024-11-29 12:49:07 +01:00
Felix Koppe
b0761082ce Fix error after merge 2024-11-29 12:42:03 +01:00
Felix Koppe
46182b33a8 Merge dev/client_koppe2 and development 2024-11-29 12:36:07 +01:00
Felix Koppe
457023ad93 Work 2024-11-29 12:30:59 +01:00
Felix Koppe
c3055a0646 Work 2024-11-29 12:29:20 +01:00
Felix Koppe
ae1ec74056 Merge dev/client_beck 2024-11-29 10:48:05 +01:00
Felix Koppe
24cc81d9d4 Merge remote-tracking branch 'origin/dev/client_beck' into dev/client_koppe2
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/NotificationSynchronizer.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/view/GameView.java
2024-11-29 10:13:15 +01:00
Felix Koppe
c25c12d0d6 Work on ceremony2 2024-11-29 09:45:36 +01:00
Felix Koppe
7b689d6bf6 Work on ceremony 2024-11-28 21:39:08 +01:00
Cedric Beck
2099e02567 added board dice; edited DiceControl; added high-res font; working on ActionTextHandler 2024-11-28 20:07:15 +01:00
Fleischer Hanno
7690340b8f added mor client state transitions
and renamed every client state with state as its suffix and renamed every message with messag as its suffix
2024-11-28 18:28:55 +01:00
Cedric Beck
3dcdbdf489 added display of 'colorÃ' to playerName 2024-11-28 17:29:59 +01:00
Felix Koppe
88cb87d4cd Merge commit 2024-11-28 16:25:57 +01:00
Felix Koppe
827e7aac86 Add stuff 2024-11-28 16:25:13 +01:00
Felix Koppe
eec68188ee Make use of parallelProjection 2024-11-28 16:08:33 +01:00
Hanno Fleischer
476ca82bda created the first part of the cliejnt state machine with its corresponding logic 2024-11-28 15:27:31 +01:00
Felix Koppe
3235a46788 Rewrite gui 2024-11-28 15:00:53 +01:00
Cedric Beck
7da388af37 added improved pieces model; added functionality to notifications + todos 2024-11-28 02:08:51 +01:00
Cedric Beck
304acf17a3 added full highlight functionality for bonuscards; added bonus Symbols display 2024-11-28 01:21:15 +01:00
Cedric Beck
9736dc828b added highlight to bonuscards 2024-11-27 19:50:29 +01:00
Hanno Fleischer
cbbef16374 added two more todos 2024-11-27 09:20:22 +01:00
Hanno Fleischer
c9362a7a95 fixed bugs so the programm would start and added some Todo where code is missing or was fraudulent 2024-11-27 09:19:57 +01:00
Cedric Beck
0b9fc90274 added dice, started adding notification implementation, added hover, highlight and select functionality for pieces 2024-11-27 03:05:35 +01:00
Daniel Grigencha
20c9000d56 Merge remote-tracking branch 'origin/development' into development 2024-11-26 23:29:02 +01:00
Daniel Grigencha
5a911326ba minor changes to the server state automaton 2024-11-26 23:28:49 +01:00
Cedric Beck
6528e5c2b6 started adding notifications 2024-11-26 20:53:29 +01:00
Hanno Fleischer
621bb9efae fixed bugs inside of the client state machine and the message UpdateTSK 2024-11-26 20:31:16 +01:00
Fleischer Hanno
84c289cfd1 implemented all methods required for the state pattern in the client and adjusted the messages to work with player ids instead of names 2024-11-26 20:04:58 +01:00
Fleischer Hanno
f827757ad1 added next method for Color 2024-11-26 18:16:19 +01:00
Daniel Grigencha
e4a9b16fd5 Merge remote-tracking branch 'origin/development' into development 2024-11-26 18:12:32 +01:00
Daniel Grigencha
d63d0cc2a0 changed default constructor of 'Player' class 2024-11-26 18:12:08 +01:00
Hanno Fleischer
a127ee524a Merge remote-tracking branch 'origin/development' into development
# Conflicts:
#	Projekte/.run/MdgaApp.run.xml
2024-11-26 18:05:37 +01:00
Hanno Fleischer
5c2df2430d reworked the client state machine and removed the seperate classes for the statemachines
these machines are now directly included in the parent states
2024-11-26 18:04:56 +01:00
Daniel Grigencha
2e76c41d3a added new 'Disconnect' client message and updated 'Player' and 'Game' classes 2024-11-26 18:02:19 +01:00
Cedric Beck
c204a3a4cb change shader folder structure 2024-11-26 17:05:38 +01:00
Cedric Beck
d794ba9d03 added smooth outline 2024-11-26 16:54:40 +01:00
Cedric Beck
e97147d0e9 merge from koppe 2024-11-26 16:47:24 +01:00
Cedric Beck
1b151aabdd added select nodes, cards & pieces 2024-11-26 16:45:13 +01:00
Felix
3e7d3dbc1b Fix 2024-11-26 16:18:33 +01:00
Cedric Beck
7a482b74ab fixed shaders in/out 2024-11-26 13:29:40 +01:00
Cedric Beck
5cc1888794 merge dev/client_koppe into dev/client_beck for camera movement 2024-11-26 13:13:32 +01:00
Cedric Beck
9dd70a96a0 added outline for bonuscards 2024-11-26 12:56:26 +01:00
Cedric Beck
80f5c4ce90 tried adding shield outline, but there is a bug -> black screen 2024-11-26 07:40:41 +01:00
Fleischer Hanno
7eafa3da39 refactored the whole client package structure 2024-11-25 17:43:29 +01:00
Fleischer Hanno
7178935553 refactored the model to incoporate a correct folder structure 2024-11-25 16:41:44 +01:00
Daniel Grigencha
0b538efbfb Merge remote-tracking branch 'origin/development' into development 2024-11-25 16:36:53 +01:00
Daniel Grigencha
f2d5221328 added import statement with the refactored server messages 2024-11-25 16:36:40 +01:00
Daniel Grigencha
06195854d8 added mdga server controller 2024-11-25 16:35:49 +01:00
Daniel Grigencha
321909387d added new server state chart 2024-11-25 16:35:27 +01:00
Hanno Fleischer
933c7ecad7 Merge branch 'dev/test' into 'development'
merge for refactoring of project structure

See merge request progproj/gruppen-ht24/Gruppe-01!15
2024-11-25 15:34:42 +00:00
Felix
806a149bfc Merge dev/client_koppe 2024-11-25 16:33:04 +01:00
Benjamin Feyer
1803fb5549 edted some tests with null tests 2024-11-25 16:32:51 +01:00
Felix
96489f2454 Minor work 2024-11-25 16:32:16 +01:00
Felix
439231aecf Add rotateable cam 2024-11-25 16:15:59 +01:00
Daniel Grigencha
9759b6871b reverted server messages 2024-11-25 16:14:15 +01:00
Daniel Grigencha
52d9fff493 deleted server state automaton 2024-11-25 15:43:29 +01:00
Hanno Fleischer
31b449662f added two Pieces in RequestPlayCard in order to differentiate between own and enemy pieces 2024-11-25 15:09:24 +01:00
Hanno Fleischer
3be037d590 added two lists in PossiblePiece in order to differentiate between own and enemy pieces 2024-11-25 15:07:43 +01:00
Felix
8982662f5b Remove debug statement 2024-11-25 14:01:06 +01:00
Felix
d70efb32ea Fix audio settings 2024-11-25 13:59:55 +01:00
Hanno Fleischer
d15242f816 added static methods to construct a PlayCard message for each card type 2024-11-25 13:42:00 +01:00
Hanno Fleischer
4705512648 added getter for ArrayList Player in Game and created a flag for ready status in Player 2024-11-25 12:52:22 +01:00
Hanno Fleischer
7efb89c634 Merge remote-tracking branch 'origin/development' into development 2024-11-25 11:20:09 +01:00
Hanno Fleischer
15d1c36dad added an ArrayList of Player in game and added the received methods in clientgamelogic 2024-11-25 11:19:17 +01:00
Benjamin Feyer
ce528457a5 added javadocs 2024-11-25 02:30:43 +01:00
Benjamin Feyer
2cfa328c4c added some more testmethods 2024-11-25 01:50:02 +01:00
Benjamin Feyer
928304fb4b corrected testmethods in clientStateTest 2024-11-25 01:24:11 +01:00
Benjamin Feyer
aa88dff566 added tests in the serverStateTest
added the testmethods for rolldice and movepiece
2024-11-25 00:36:59 +01:00
Benjamin Feyer
f7bd9a0f38 edited some tests in ServerStateTest 2024-11-25 00:20:47 +01:00
Daniel Grigencha
cc800a8dd7 added default constructor for serialization purposes 2024-11-24 23:55:48 +01:00
Daniel Grigencha
7c5720cb9d fixed sonarlint errors and deleted map playerConnectionID 2024-11-24 23:06:30 +01:00
Cedric Beck
b9986ded87 try push 2024-11-24 22:41:17 +01:00
Cedric Beck
fd09460d61 try push 2024-11-24 22:40:50 +01:00
Cedric Beck
e5dca013d1 try push 2024-11-24 22:38:23 +01:00
Cedric Beck
7bfc6e7c3e try push 2024-11-24 22:37:37 +01:00
Benjamin Feyer
1821a222e9 minor changes 2024-11-24 22:24:39 +01:00
Benjamin Feyer
2e302ddc75 Merge branch 'development' into 'dev/test'
merge changes into the testbranch

See merge request progproj/gruppen-ht24/Gruppe-01!14
2024-11-24 21:23:23 +00:00
Fleischer Hanno
ed47883281 refactored ceremony message 2024-11-24 22:20:04 +01:00
Fleischer Hanno
599b3e8f47 corrected refactoring mistake, ich which RankingResponce was renamed to RankingResponse 2024-11-24 22:18:48 +01:00
Fleischer Hanno
040b8830ab added getter for dialogstatemachine in dialogs 2024-11-24 22:03:19 +01:00
Benjamin Feyer
6fa3e8c00d Merge branch 'development' into 'dev/test'
Development

See merge request progproj/gruppen-ht24/Gruppe-01!13
2024-11-24 20:58:39 +00:00
Fleischer Hanno
83c79af31a made all con structors of clients states public 2024-11-24 21:56:32 +01:00
Daniel Grigencha
653c3fb962 added javadocs to all server messages 2024-11-24 20:51:03 +01:00
Daniel Grigencha
8932c8a8cd added javadocs to all client messages 2024-11-24 19:56:51 +01:00
Cedric Beck
10428deedd added resource restructuring 2024-11-24 19:54:29 +01:00
Cedric Beck
a196d3d7f2 added basic player name display 2024-11-24 19:49:54 +01:00
Benjamin Feyer
4c078ab0e2 added some testmethods and corrected other in the clientStatemachineTests 2024-11-24 19:37:03 +01:00
Benjamin Feyer
2a97ede985 added some testmethods for the client testing the statechanges in the dialogs 2024-11-24 19:03:19 +01:00
Benjamin Feyer
56492cdda6 added some mor testcases for the clientstatemachine 2024-11-24 18:40:10 +01:00
Benjamin Feyer
a71619612a added the empty testmethods in serverstateTest and edited the testmethods for substates of choocePiece in Client into MovePiece 2024-11-24 18:21:26 +01:00
Benjamin Feyer
f18a62b089 Merge branch 'development' into 'dev/test'
Testmerge

See merge request progproj/gruppen-ht24/Gruppe-01!12
2024-11-24 15:09:07 +00:00
Benjamin Feyer
c204984a74 edited a test in the clientStateTest 2024-11-24 16:03:12 +01:00
Fleischer Hanno
1d4048cf16 added the constructors for all client states and their statemachines 2024-11-24 16:02:04 +01:00
Benjamin Feyer
2095ea5866 editet tests for the server and client statemachines 2024-11-24 15:49:01 +01:00
Daniel Grigencha
f425eff26b added more logic for the server state diagram 2024-11-24 15:27:10 +01:00
Hanno Fleischer
cd22ece485 Merge branch 'dev/client' into 'development'
Merge results from dev/client into development

See merge request progproj/gruppen-ht24/Gruppe-01!11
2024-11-24 09:45:30 +00:00
Cedric Beck
16dc28013e merge development into dev/client 2024-11-24 10:39:28 +01:00
Cedric Beck
9ab71bfaf6 merge dev/client_koppe into dev/client 2024-11-24 10:34:48 +01:00
Cedric Beck
ccad47f95a Merge branch 'dev/client' into dev/client_koppe 2024-11-24 10:32:00 +01:00
Cedric Beck
f830aec8ce Merge branch 'dev/client_beck' into 'dev/client'
Added basic display of bonusCards for presentation on Monday

See merge request progproj/gruppen-ht24/Gruppe-01!10
2024-11-24 09:31:33 +00:00
Cedric Beck
c8c0188452 merge dev/client into dev/client_koppe 2024-11-24 10:30:37 +01:00
Cedric Beck
a5b7488e92 Merge branch 'dev/client' into dev/client_beck 2024-11-24 09:23:57 +01:00
Cedric Beck
11794b6ac7 added GuiHandler and showing bonuscards 2024-11-24 09:21:44 +01:00
Cedric Beck
a8d80fd3f4 Merge branch 'dev/client_beck' into 'dev/client'
Added all basic functionalitites to BoardHandler

See merge request progproj/gruppen-ht24/Gruppe-01!9
2024-11-23 21:12:14 +00:00
Fleischer Hanno
806f0d7d9d added message contents to the messages
addedn the conentents for all messages regarding the BPMN diagramm and own interpretation.
also created an identifier for pieces to be used for network communication between server and client so that they talk about the same piece.
2024-11-23 12:26:20 +01:00
Cedric Beck
7368014b10 merge dev/client into dev/client_beck 2024-11-22 20:38:00 +01:00
Cedric Beck
ebdedc6494 added working piece outline; added final tweaks for boardHandler 2024-11-22 19:53:32 +01:00
Benjamin Feyer
df2a7151f0 Merge branch 'development' into 'dev/test'
Merge Test to Development

See merge request progproj/gruppen-ht24/Gruppe-01!8
2024-11-22 12:56:51 +00:00
Cedric Beck
00014eeb09 tried fixing outline problem 2024-11-22 13:56:43 +01:00
Daniel Grigencha
89232901a7 added more logic for the server state diagram 2024-11-22 09:37:49 +01:00
Felix
a79a315f83 Minor work 2024-11-21 16:44:28 +01:00
Felix
c470c205e4 Add host&join menu and work on modelSyncronizer 2024-11-21 16:16:19 +01:00
Felix
982ca00b55 Make lobby buttons colored 2024-11-21 16:16:19 +01:00
Felix
c1f4ea480c Add host&join menu and work on modelSyncronizer 2024-11-21 16:14:06 +01:00
Cedric Beck
a8a725611f added highlighting -> bug all assets are transparent 2024-11-21 09:56:40 +01:00
Felix
02ce0df614 Make lobby buttons colored 2024-11-20 15:21:07 +01:00
Felix Koppe
6e57e309cc Merge branch 'dev/client_koppe' into 'dev/client'
Merge work

See merge request progproj/gruppen-ht24/Gruppe-01!7
2024-11-20 12:50:00 +00:00
Felix
d87acd46cc Fix camera init/shutdown 2024-11-20 13:48:59 +01:00
Felix
58395fe3cb Merge dev/client_beck 2024-11-20 13:42:10 +01:00
Felix
5f54eff038 Add inputHandler 2024-11-20 13:32:50 +01:00
Felix
184b565526 Add volume controll 2024-11-20 12:40:51 +01:00
Felix
176efc2aca Add settings menus 2024-11-20 09:24:01 +01:00
Felix
bb491a2682 Make button size dynamic 2024-11-19 19:27:04 +01:00
Felix
51e83c5ae4 Implement notificationSyncronizer 2024-11-19 19:01:52 +01:00
Felix
47f0e44d0a Merge development 2024-11-19 18:40:08 +01:00
Felix
80fab1ffdc Begin work on actionHandler 2024-11-19 18:36:49 +01:00
Felix
03eef66332 Finish buttons 2024-11-19 18:28:37 +01:00
Felix
34415bc9f2 Add left/right buttons 2024-11-19 14:16:01 +01:00
Felix
030842d251 Some work 2024-11-19 12:52:43 +01:00
Hanno Fleischer
a3a9f0d88d Merge branch 'development' into 'dev/test'
TestMerge 3

See merge request progproj/gruppen-ht24/Gruppe-01!6
2024-11-19 11:48:06 +00:00
Benjamin Feyer
6790be782e added empty serverstatetests 2024-11-19 12:42:11 +01:00
Cedric Beck
56256fb9c0 fixed rotation and added functionality für BoardHandler; added shiel visual 2024-11-18 21:30:30 +01:00
Felix
c6438d75cc Fix acousticHandler error 2024-11-18 16:40:01 +01:00
Felix
728530a8f2 Finish loop 2024-11-18 16:16:56 +01:00
Felix
7c09107d28 Work on Dialogs 2024-11-18 15:58:30 +01:00
Cedric Beck
2399bf2678 added world and trees for tree positioning 2024-11-18 12:40:40 +01:00
Cedric Beck
06d795b3e6 working on asset simplification 2024-11-18 12:03:42 +01:00
Cedric Beck
8d9a923970 added rotation for move piece, fixed problems because rotation on the z axis is reversed 2024-11-18 01:58:24 +01:00
Cedric Beck
c95beaeb14 working on pieceMovement 2024-11-17 23:57:36 +01:00
Daniel Grigencha
04501de11c Merge remote-tracking branch 'origin/development' into development 2024-11-17 21:18:38 +01:00
Fleischer Hanno
9cd9cc871c fixed a bug with use of addLast method for ArrayList which doesnot exist in Java 20 2024-11-17 21:18:19 +01:00
Fleischer Hanno
f69a2a9fda added a method to check if a player has pieces in his waiting area 2024-11-17 21:04:13 +01:00
Daniel Grigencha
e83ed1c835 Merge remote-tracking branch 'origin/development' into development 2024-11-17 20:17:29 +01:00
Daniel Grigencha
f379a6b638 added more logic for the server state diagram 2024-11-17 20:17:19 +01:00
Hanno Fleischer
90fb6e4133 added the method tryMove and the methods used by it into serverstate 2024-11-17 17:52:25 +01:00
Hanno Fleischer
9662e1f684 fixed a Bug where the import statement thought a package was a class because they where written the same 2024-11-17 15:34:39 +01:00
Daniel Grigencha
aae7ed9a87 added classes for client and server state machine
- a client state machine consits out of a 'ClientState' (every state of the machine) and a 'ClientStateMachine' (every state, which consists out of states), the machine starts with the ClientAutomaton
- analog for server
- started to implement logic for the server, transition from 'Lobby' to 'GameState'
2024-11-17 15:27:09 +01:00
Hanno Fleischer
07bd7dfa3d Merge branch 'development' into 'dev/test'
Testmerge

See merge request progproj/gruppen-ht24/Gruppe-01!5
2024-11-17 14:20:53 +00:00
Felix
b15dd96a86 Improve music fade and mock 2024-11-17 14:28:48 +01:00
Felix
ada90e787d Improve mock 2024-11-17 13:03:54 +01:00
Felix
cbeb296a44 Mock differen Views 2024-11-17 12:42:20 +01:00
Hanno Fleischer
a9fd13caab Merge branch 'dev/client' into 'development'
Client work of the Week

See merge request progproj/gruppen-ht24/Gruppe-01!4
2024-11-17 11:24:45 +00:00
Hanno Fleischer
a926554709 adjusted for check style 2024-11-17 12:01:00 +01:00
Cedric Beck
bbd84dd961 added code + checkstyle 2024-11-17 11:26:45 +01:00
Cedric Beck
e22f675f8b fixed merge conflict 2024-11-17 11:07:53 +01:00
Cedric Beck
956e9dd2fd merge from dev/client 2024-11-17 10:57:05 +01:00
Hanno Fleischer
020aa92cab added 4 more Notifications for Model -> View interaction 2024-11-17 10:39:36 +01:00
Felix Koppe
bb55ba7273 Merge branch 'dev/client_koppe' into 'dev/client'
Work of the week

See merge request progproj/gruppen-ht24/Gruppe-01!3
2024-11-17 09:09:13 +00:00
Cedric Beck
a2c7b9e299 added all assets; added node array; added node/piece_control saving 2024-11-16 23:05:30 +01:00
Hanno Fleischer
0845aa80f9 adjusted the model infield creation to represent the current view postion regarding the placement of the teams on the board 2024-11-16 22:31:11 +01:00
Hanno Fleischer
44b623f9fd added all Notifications for the model view communication
added all required notifications with their content and getter methods.
2024-11-16 18:58:33 +01:00
Felix
bf678000b8 Minor changes 2024-11-16 18:21:02 +01:00
Felix
e128d9e4cc Add docs to Acoustic 2024-11-16 16:33:21 +01:00
Felix
8c10e69eff General improvements 2024-11-16 16:15:08 +01:00
Hanno Fleischer
6b2d775534 added 2 methods for Test usage in Board and PlayerData 2024-11-16 13:31:56 +01:00
Felix
b509ab772d Add subVolume and delay 2024-11-16 13:07:39 +01:00
Cedric Beck
d0be65323e added rotation compability to map.mdga 2024-11-15 22:47:23 +01:00
Cedric Beck
81facb869f added playerMap for location playerAssets 2024-11-15 21:26:23 +01:00
Felix
b317bb957a Add sound 2024-11-15 16:52:40 +01:00
Felix
202cd7c0e6 Add music credits 2024-11-15 14:57:36 +01:00
Felix
e345edc38e Add game_3 music 2024-11-15 14:42:52 +01:00
Felix
133bffbd6b Add game_6 music 2024-11-15 14:42:41 +01:00
Felix
2287fdc88f Add game_5 music 2024-11-15 14:42:27 +01:00
Felix
976002ddd0 Add game_2 music 2024-11-15 14:42:10 +01:00
Felix
205cb654c7 Add game_1 music 2024-11-15 14:41:52 +01:00
Felix
d8544cc042 Add game_4 music 2024-11-15 14:41:29 +01:00
Felix
0aa7e83d62 Add main menu music 2024-11-15 14:40:59 +01:00
Felix
289c285adf Add lobby music 2024-11-15 14:40:34 +01:00
Felix
a4ac52b591 Add ceremony music 2024-11-15 14:40:09 +01:00
Felix
0bb4706fda Add GameMusic 2024-11-15 14:39:10 +01:00
Felix
a3c5af58d7 Remove grid extend limits 2024-11-14 23:24:12 +01:00
Felix
28960e8961 Activate assertions 2024-11-14 23:10:36 +01:00
Felix
bb7baf44bd Add circle_map.mdga 2024-11-14 22:47:22 +01:00
Felix
206c0d543e Merge commit 2024-11-14 22:14:19 +01:00
Felix
a68ad80402 Remove state classes 2024-11-14 22:13:57 +01:00
Cedric Beck
1cca5a9c6b added MapLoader and map for map view 2024-11-14 21:21:01 +01:00
Hanno Fleischer
ee59807395 Replaced in the notifications Card with BonusCard 2024-11-14 19:58:26 +01:00
Hanno Fleischer
37511d6334 Merge branch 'dev/client_koppe' into 'development'
Dev/client_koppe

See merge request progproj/gruppen-ht24/Gruppe-01!1
2024-11-14 18:45:24 +00:00
Felix
c83baac763 Fix error 2024-11-14 19:36:53 +01:00
Felix
6c7b58b415 Add notification constructors 2024-11-14 19:33:03 +01:00
Benjamin Feyer
33dbbdbe5c initial test commit,
added all testclasses except Playertest, Viewtest, Cameratest, SettingsTest, SoundTest, ReactionTest and ClientStateTest. And filled all created testclasses with empty testmethods, except ServerStateTest.
2024-11-14 18:07:36 +01:00
Felix Koppe
5693817f9b Add notifications 2024-11-14 17:56:43 +01:00
Felix Koppe
94ee786dc8 Minor work 2024-11-14 17:13:56 +01:00
Felix Koppe
92941f903c Merge commit 2024-11-14 16:51:22 +01:00
Hanno Fleischer
9bafd749b6 removed card and fixed codes style
removed card because it was unnecessary and reworked all classes containing it to implement the change
2024-11-14 15:49:22 +01:00
Hanno Fleischer
c7b65d949a added java docs to model/game and wrote the remaining getter and setter 2024-11-14 15:32:27 +01:00
Felix
921933722a Minor work 2024-11-14 15:23:59 +01:00
Felix
7fa45f39c6 Merge remote-tracking branch 'origin/dev_client' into dev_client
# Conflicts:
#	Projekte/mdga/client/src/main/java/pp/mdga/client/Animation/Animation.java
#	Projekte/mdga/client/src/main/java/pp/mdga/client/Animation/AnimationHandler.java
2024-11-14 15:04:41 +01:00
Felix
6849a0a023 Minor work on animation 2024-11-14 14:58:54 +01:00
Daniel Grigencha
e96cd40302 improved code style 2024-11-14 14:51:29 +01:00
Felix
d9a827697d First steps 2024-11-13 19:52:29 +01:00
Felix
0f39ea0dcc Add files 2024-11-13 17:57:14 +01:00
Felix
6e844287dd Delete garbarge 2024-11-13 17:23:07 +01:00
Felix
3465cdaa19 Merge branch 'view_assets' into dev_client
# Conflicts:
#	Projekte/mdga/client/build.gradle
2024-11-13 17:21:24 +01:00
Felix Koppe
d1a343aaa9 Add MdgaApp.java and run config 2024-11-13 11:55:09 +01:00
506 changed files with 34345 additions and 1443 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.run/
.gradle
build/
#!gradle/wrapper/gradle-wrapper.jar

View File

@@ -1,18 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false"
nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp"/>
<module name="Projekte.mdga.client.main"/>
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
<configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
<option name="ALTERNATIVE_JRE_PATH" value="temurin-20" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp" />
<module name="Projekte.mdga.client.main" />
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties -ea" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.mdga.client.*"/>
<option name="ENABLED" value="true"/>
<option name="PATTERN" value="pp.mdga.client.board.outline.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true"/>
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -5,9 +5,16 @@
description = 'mdga client'
dependencies {
implementation project(":jme-common")
implementation project(":mdga:model")
implementation libs.jme3.desktop
implementation libs.jme3.core
implementation libs.jme3.lwjgl3
implementation libs.jme3.lwjgl
implementation libs.jme3.desktop
implementation libs.jme3.effects
runtimeOnly libs.jme3.awt.dialogs
runtimeOnly libs.jme3.plugins
@@ -18,4 +25,4 @@ implementation project(":mdga:model")
application {
mainClass = 'pp.mdga.client.MdgaApp'
applicationName = 'MDGA'
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,10 @@
package pp.mdga.client;
/**
* Represents different assets in the application. Each asset may have an associated model path,
* diffuse texture path, and a size factor. The enum provides multiple constructors to handle
* varying levels of detail for different assets.
*/
public enum Asset {
bigTent,
cardStack,
@@ -8,56 +13,123 @@ public enum Asset {
jet,
lw,
marine,
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue.png"),
node_home_black("./node_home/node_home.j3o", "./node_home/node_home_black.png"),
node_home_green("./node_home/node_home.j3o", "./node_home/node_home_green.png"),
node_home_yellow("./node_home/node_home.j3o", "./node_home/node_home_yellow.png"),
node_home_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
node_wait_blue("Models/node_home/node_home.j3o", "Models/node_home/node_home_blue_diff.png"),
node_home_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
node_wait_black("Models/node_home/node_home.j3o", "Models/node_home/node_home_black_diff.png"),
node_home_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
node_wait_green("Models/node_home/node_home.j3o", "Models/node_home/node_home_green_diff.png"),
node_home_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
node_wait_yellow("Models/node_home/node_home.j3o", "Models/node_home/node_home_orange_diff.png"),
node_normal,
node_start("./node_normal/node_normal.j3o", "./node_normal/node_normal_start.png"),
node_bonus("./node_normal/node_normal.j3o", "./node_normal/node_normal_bonus.png"),
node_start("Models/node_normal/node_normal.j3o", "Models/node_normal/node_start_diff.png"),
node_bonus("Models/node_normal/node_normal.j3o", "Models/node_normal/node_bonus_diff.png"),
radar,
shieldCard,
ship,
ship(0.8f),
smallTent,
swapCard,
tank,
world(1.2f),
shieldRing("Models/shieldRing/shieldRing.j3o", null),
treeSmall(1.2f),
treeBig(1.2f),
turboCard,
world(1.1f);
turboSymbol("Models/turboCard/turboSymbol.j3o", "Models/turboCard/turboCard_diff.png"),
swapCard,
swapSymbol("Models/swapCard/swapSymbol.j3o", "Models/swapCard/swapCard_diff.png"),
shieldCard,
shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
dice,
missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
;
private final String modelPath;
private final String diffPath;
private final float size;
private static final String ROOT = "Models/";
Asset(){
String folderFileName = "./" + name() + "/" + name();
/**
* Default constructor. Initializes modelPath and diffPath based on the enum name and sets default size to 1.0.
*/
Asset() {
String folderFileName = "./" + ROOT + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = 1f;
}
Asset(String modelPath, String diffPath){
/**
* Constructor with specific model path and diffuse texture path.
*
* @param modelPath Path to the 3D model file.
* @param diffPath Path to the diffuse texture file.
*/
Asset(String modelPath, String diffPath) {
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = 1f;
}
Asset(float size){
String folderFileName = "./" + name() + "/" + name();
/**
* Constructor with specific model path. Diffuse texture path is derived based on enum name.
*
* @param modelPath Path to the 3D model file.
*/
Asset(String modelPath) {
String folderFileName = "./" + ROOT + name() + "/" + name();
this.modelPath = modelPath;
this.diffPath = folderFileName + "_diff.png";;
this.size = 1f;
}
/**
* Constructor with specific size. Model and texture paths are derived based on enum name.
*
* @param size Scaling factor for the asset.
*/
Asset(float size) {
String folderFileName = "./" + ROOT + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = size;
}
/**
* Constructor with specific model path, diffuse texture path, and size.
*
* @param modelPath Path to the 3D model file.
* @param diffPath Path to the diffuse texture file.
* @param size Scaling factor for the asset.
*/
Asset(String modelPath, String diffPath, float size){
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = size;
}
/**
* Gets the model path for the asset.
*
* @return Path to the 3D model file.
*/
public String getModelPath() {
return modelPath;
}
/**
* Gets the diffuse texture path for the asset.
*
* @return Path to the diffuse texture file, or null if not applicable.
*/
public String getDiffPath() {
return diffPath;
}
public float getSize(){
/**
* Gets the scaling factor for the asset.
*
* @return The size of the asset.
*/
public float getSize() {
return size;
}
}

View File

@@ -0,0 +1,48 @@
package pp.mdga.client;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
* An abstract control class that serves as a base for initializing spatial objects
* in jMonkeyEngine. This class overrides the controlUpdate and controlRender methods
* from the AbstractControl class, providing default empty implementations,
* and adds the ability to initialize spatial objects when they are set.
*/
public abstract class InitControl extends AbstractControl {
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
/**
* Sets the spatial object to be controlled. This method also initializes the spatial
* if it is being set for the first time.
*
* @param spatial The spatial object to control.
*/
@Override
public void setSpatial(Spatial spatial) {
if (this.spatial == null && spatial != null) {
super.setSpatial(spatial);
initSpatial();
}
}
/**
* Initializes the spatial object. This method can be overridden by subclasses
* to define custom initialization logic for the spatial.
* This method is called automatically when the spatial is set for the first time.
*/
protected void initSpatial() {
// Default empty implementation. Override to add initialization logic.
}
}

View File

@@ -0,0 +1,362 @@
package pp.mdga.client;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import pp.mdga.client.board.NodeControl;
import pp.mdga.client.board.OutlineControl;
import pp.mdga.client.board.PieceControl;
import pp.mdga.client.gui.CardControl;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.client.view.GameView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.game.Piece;
import pp.mdga.notification.FinishNotification;
import pp.mdga.notification.MovePieceNotification;
import pp.mdga.notification.SelectableCardsNotification;
import java.util.List;
import java.util.UUID;
public class InputSynchronizer {
private MdgaApp app;
private InputManager inputManager;
protected boolean rightMousePressed = false;
private float rotationAngle = 180f;
private int scrollValue = 0;
private CardControl hoverCard;
private PieceControl hoverPiece;
private boolean clickAllowed = true;
private boolean isRotateLeft = false;
private boolean isRotateRight = false;
/**
* Constructor initializes the InputSynchronizer with the application context.
* Sets up input mappings and listeners for user interactions.
*
* @param app The application instance
*/
InputSynchronizer(MdgaApp app) {
this.app = app;
this.inputManager = app.getInputManager();
hoverCard = null;
hoverPiece = null;
setupInput();
}
public void update(float tpf) {
if(isRotateLeft && isRotateRight) {
return;
}
if(isRotateLeft) {
rotationAngle += 180 * tpf;
}
if(isRotateRight) {
rotationAngle -= 180 * tpf;
}
}
/**
* Configures input mappings for various actions and binds them to listeners.
*/
private void setupInput() {
inputManager.addMapping("Settings", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_Q));
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_E));
inputManager.addMapping("RotateRightMouse", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping("MouseLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); // Left movement
inputManager.addMapping("MouseRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); // Right movement
inputManager.addMapping("MouseScrollUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); // Scroll up
inputManager.addMapping("MouseScrollDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); // Scroll down
inputManager.addMapping("Test", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "Settings", "Forward", "RotateRightMouse", "Click", "Left", "Right", "Test");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
UUID p = null;
/**
* Handles action-based input events such as key presses and mouse clicks.
*/
private final ActionListener actionListener = new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("Settings") && isPressed) {
app.getView().pressEscape();
}
if (name.equals("Forward") && isPressed) {
app.getView().pressForward();
}
if (name.equals("RotateRightMouse")) {
rightMousePressed = isPressed;
}
if(name.equals("Click") && isPressed) {
if(!clickAllowed) {
return;
}
if (app.getView() instanceof GameView gameView) {
DiceControl diceSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), DiceControl.class);
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
OutlineControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineControl.class);
if(diceSelect != null) {
app.getModelSynchronize().rolledDice();
}
else if(cardLayerSelect != null) {
//cardSelect
if(cardLayerSelect.isSelectable()) gameView.getGuiHandler().selectCard(cardLayerSelect);
}
else if(boardSelect != null) {
//boardSelect
if(boardSelect instanceof PieceControl pieceControl){
if(pieceControl.isSelectable()) gameView.getBoardHandler().pieceSelect(pieceControl);
}
if(boardSelect instanceof NodeControl nodeControl){
//
}
}
else {
//both null
}
}
}
if (name.equals("Left")) {
isRotateLeft = !isRotateLeft;
}
if (name.equals("Right")) {
isRotateRight = !isRotateRight;
}
if(name.equals("Test2") &&isPressed){
if(app.getView() instanceof GameView gameView){
if(p == null) {
p = UUID.randomUUID();
gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
gameView.getBoardHandler().movePieceStartAnim(p,0);
//gameView.getBoardHandler().movePieceAnim(p,0, 8);
} else {
gameView.getBoardHandler().throwMissileAnim(p);
//gameView.getBoardHandler().movePieceStartAnim(p,0);
}
// gameView.getGuiHandler().rollRankingResult(Color.AIRFORCE, 1);
// gameView.getGuiHandler().rollRankingResult(Color.ARMY, 2);
// gameView.getGuiHandler().rollRankingResult(Color.NAVY, 3);
// gameView.getGuiHandler().rollRankingResult(Color.CYBER, 4);
// gameView.getGuiHandler().showDice();
// UUID p1 = UUID.randomUUID();
// gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p1,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
// gameView.getBoardHandler().movePieceStartAnim(p1,0);
//gameView.getGuiHandler().drawCard(Color.ARMY);
//gameView.getGuiHandler().addCardOwn(BonusCard.SHIELD);
//gameView.getGuiHandler().playCardOwn(BonusCard.SHIELD);
}
}
}
};
/**
* Handles analog-based input events such as mouse movement and scrolling.
*/
private final AnalogListener analogListener = new AnalogListener() {
@Override
public void onAnalog(String name, float value, float tpf) {
if (name.equals("MouseLeft") && rightMousePressed) {
rotationAngle -= value * 360f;
}
else if (name.equals("MouseRight") && rightMousePressed) {
rotationAngle += value * 360f;
}
else if (name.equals("MouseScrollUp")) {
scrollValue = Math.max(1, scrollValue - 5);
}
else if (name.equals("MouseScrollDown")) {
scrollValue = Math.min(100, scrollValue + 5);
}
else if (name.equals("MouseLeft") || name.equals("MouseRight") || name.equals("MouseVertical")){
hoverPiece();
hoverCard();
}
}
};
/**
* Detects the hovered piece and updates its hover state.
*/
private <T extends AbstractControl> T checkHover(Camera cam, Node root, Class<T> controlType) {
if(cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Ray ray = new Ray(cam.getLocation(), getMousePos(cam).subtract(cam.getLocation()).normalize());
root.collideWith(ray, results);
for(CollisionResult collisionResult : results){
if(collisionResult.getGeometry().getControl(controlType) != null) return collisionResult.getGeometry().getControl(controlType);
}
return null;
}
/**
* Detects the hovered card and updates its hover state.
*/
private <T extends AbstractControl> T checkHoverOrtho(Camera cam, Node root, Class<T> controlType) {
if(cam == null || root == null || controlType == null) return null;
CollisionResults results = new CollisionResults();
Vector3f mousePos = getMousePos(cam);
mousePos.setZ(cam.getLocation().getZ());
Ray ray = new Ray(mousePos, getMousePos(cam).subtract(mousePos).normalize());
root.collideWith(ray, results);
if (results.size() > 0) {
for(CollisionResult res : results ){
T control = res.getGeometry().getControl(controlType);
if(control != null) return control;
}
}
return null;
}
/**
* Handles the hover state for a piece in the game.
* Checks if a piece is being hovered over, updates the hover state, and triggers hover effects.
*/
private void hoverPiece() {
if (app.getView() instanceof GameView gameView) {
PieceControl control = checkPiece();
if (control != null) {
if (control != hoverPiece) {
pieceOff();
hoverPiece = control;
hoverPiece.hover();
}
} else {
pieceOff();
}
}
}
/**
* Handles the hover state for a card in the game.
* Checks if a card is being hovered over, updates the hover state, and triggers hover effects.
*/
private void hoverCard() {
if (app.getView() instanceof GameView gameView) {
CardControl control = checkCard(gameView);
if (control != null) {
if (control != hoverCard) {
cardOff();
hoverCard = control;
hoverCard.hover();
}
} else {
cardOff();
}
}
}
/**
* Checks if a piece is being hovered over in the 3D game world.
*
* @return The PieceControl of the hovered piece, or null if no piece is hovered.
*/
private PieceControl checkPiece() {
return checkHover(app.getCamera(), app.getRootNode(), PieceControl.class);
}
/**
* Checks if a card is being hovered over in the 2D card layer.
*
* @param gameView The current game view.
* @return The CardControl of the hovered card, or null if no card is hovered.
*/
private CardControl checkCard(GameView gameView) {
return checkHoverOrtho(
gameView.getGuiHandler().getCardLayerCamera(),
gameView.getGuiHandler().getCardLayerRootNode(),
CardControl.class
);
}
/**
* Disables the hover effect on the currently hovered piece, if any.
*/
private void pieceOff() {
if (hoverPiece != null) hoverPiece.hoverOff();
hoverPiece = null;
}
/**
* Disables the hover effect on the currently hovered card, if any.
*/
private void cardOff() {
if (hoverCard != null) hoverCard.hoverOff();
hoverCard = null;
}
/**
* Retrieves the current mouse position in the 3D world using the specified camera.
*
* @param cam The camera used for determining the mouse position.
* @return A Vector3f representing the mouse position in the 3D world.
*/
private Vector3f getMousePos(Camera cam) {
Vector2f mousePositionScreen = inputManager.getCursorPosition();
Vector3f world = cam.getWorldCoordinates(mousePositionScreen, 0);
if (cam.isParallelProjection()) world.setZ(0);
return world;
}
/**
* Gets the current rotation angle of the game element.
*
* @return The rotation angle in degrees, normalized to 360 degrees.
*/
public float getRotation() {
return (rotationAngle / 2) % 360;
}
public void setRotation(float rotationAngle){
this.rotationAngle = rotationAngle;
}
/**
* Gets the current scroll value.
*
* @return The scroll value as an integer.
*/
public int getScroll() {
return scrollValue;
}
public void setClickAllowed(boolean allowed) {
clickAllowed = allowed;
}
public boolean isClickAllowed() {
return clickAllowed;
}
}

View File

@@ -1,90 +1,326 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.simsilica.lemur.GuiGlobals;
import com.sun.tools.javac.Main;
import pp.mdga.client.acoustic.AcousticHandler;
import com.jme3.system.AppSettings;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.view.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.prefs.Preferences;
/**
* Main application class for the MdgaApp game.
* This class extends {@link SimpleApplication} and manages the game's lifecycle, states, and main components.
*/
public class MdgaApp extends SimpleApplication {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/** Handles acoustic effects and state-based sounds. */
private AcousticHandler acousticHandler;
/** Synchronizes notifications throughout the application. */
private NotificationSynchronizer notificationSynchronizer;
/** Manages input events and synchronization. */
private InputSynchronizer inputSynchronizer;
/** Synchronizes game models. */
private ModelSynchronizer modelSynchronizer;
/** The currently active view in the application. */
private MdgaView view = null;
/** The current state of the application. */
private MdgaState state = null;
/** Scale for rendering images. */
private final float imageScale = prefs.getInt("scale", 1);
/** The main menu view. */
private MainView mainView;
/** The lobby view. */
private LobbyView lobbyView;
/** The game view. */
private GameView gameView;
/** The ceremony view. */
private CeremonyView ceremonyView;
/** The client game logic. */
private final ClientGameLogic clientGameLogic;
private ExecutorService executor;
private ServerConnection networkConnection;
public MdgaApp() {
networkConnection = new NetworkSupport(this);
this.clientGameLogic = new ClientGameLogic(networkConnection);
}
/**
* Main entry point for the application.
* Configures settings and starts the application.
*
* @param args command-line arguments (not used)
*/
public static void main(String[] args) {
MdgaApp app = new MdgaApp();
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
if(prefs.getBoolean("fullscreen", false)) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
settings.setResolution(screenWidth, screenHeight);
settings.setFullscreen(true);
} else {
settings.setWidth(prefs.getInt("width", 1280));
settings.setHeight(prefs.getInt("height", 720));
}
settings.setCenterWindow(true);
settings.setWidth(1300);
settings.setHeight(1000);
settings.setVSync(false);
settings.setTitle("MDGA");
settings.setVSync(true);
MdgaApp app = new MdgaApp();
app.setSettings(settings);
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.setDisplayStatView(false);
app.start();
}
/**
* Initializes the application by setting up handlers, views, and entering the default state.
*/
@Override
public void simpleInitApp() {
flyCam.setEnabled(true);
int zoom = 20;
cam.setLocation(new Vector3f(zoom,0,zoom));
cam.lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
GuiGlobals.initialize(this);
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(-1,0,-1));
rootNode.addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
rootNode.addLight(ambient);
inputManager.deleteMapping("SIMPLEAPP_Exit");
final int SHADOWMAP_SIZE=1024*8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
viewPort.addProcessor(dlsr);
flyCam.setEnabled(false);
createModel(Asset.lw).setLocalTranslation(new Vector3f(0,-10,0));
createModel(Asset.cir).setLocalTranslation(new Vector3f(0,-8,0));
createModel(Asset.marine).setLocalTranslation(new Vector3f(0,-6,0));
createModel(Asset.heer).setLocalTranslation(new Vector3f(0,-4,0));
createModel(Asset.node_normal).setLocalTranslation(new Vector3f(0,-2.5f,0));
createModel(Asset.node_home_blue).setLocalTranslation(new Vector3f(0,-1,0));
createModel(Asset.smallTent).setLocalTranslation(new Vector3f(0,1,0));
createModel(Asset.tank).setLocalTranslation(new Vector3f(0,5,0));
createModel(Asset.jet).setLocalTranslation(new Vector3f(0,12,0));
createModel(Asset.ship).setLocalTranslation(new Vector3f(0,17,0));
createModel(Asset.radar).setLocalTranslation(new Vector3f(0,20,0));
acousticHandler = new AcousticHandler(this);
notificationSynchronizer = new NotificationSynchronizer(this);
inputSynchronizer = new InputSynchronizer(this);
modelSynchronizer = new ModelSynchronizer(this);
createModel(Asset.world);
mainView = new MainView(this);
lobbyView = new LobbyView(this);
gameView = new GameView(this);
ceremonyView = new CeremonyView(this);
System.out.println(Asset.node_normal.getModelPath());
System.out.println(Asset.node_normal.getDiffPath());
}
private Spatial createModel(Asset asset){
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
Spatial model = assetManager.loadModel(modelName);
model.scale(asset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", assetManager.loadTexture(texName));
model.setMaterial(mat);
rootNode.attachChild(model);
return model;
enter(MdgaState.MAIN);
}
/**
* Updates the application on each frame. Updates the view, acoustic handler, and notifications.
*
* @param tpf time per frame, used for smooth updating
*/
@Override
public void simpleUpdate(float tpf) {
//this method will be called every game tick and can be used to make updates
view.update(tpf);
acousticHandler.update();
notificationSynchronizer.update();
inputSynchronizer.update(tpf);
}
@Override
public void simpleRender(RenderManager rm) {
//add render code here (if any)
/**
* Transitions the application to a new state.
*
* @param state the new state to enter
* @throws RuntimeException if attempting to enter the {@link MdgaState#NONE} state
*/
public void enter(MdgaState state) {
if (null != view) {
view.leave();
}
this.state = state;
switch (state) {
case MAIN:
view = mainView;
break;
case LOBBY:
view = lobbyView;
break;
case GAME:
view = gameView;
break;
case CEREMONY:
view = ceremonyView;
break;
case NONE:
throw new RuntimeException("Cannot enter state NONE");
}
acousticHandler.playState(state);
view.enter();
}
/**
* Gets the acoustic handler.
*
* @return the {@link AcousticHandler} instance
*/
public AcousticHandler getAcousticHandler() {
return acousticHandler;
}
/**
* Gets the current state of the application.
*
* @return the current {@link MdgaState}
*/
public MdgaState getState() {
return state;
}
/**
* Gets the image scaling factor.
*
* @return the image scale as a float
*/
public float getImageScale() {
return imageScale;
}
/**
* Gets the currently active view.
*
* @return the active {@link MdgaView}
*/
public MdgaView getView() {
return view;
}
/**
* Gets the model synchronizer.
*
* @return the {@link ModelSynchronizer} instance
*/
public ModelSynchronizer getModelSynchronize() {
return modelSynchronizer;
}
/**
* Gets the input synchronizer.
*
* @return the {@link InputSynchronizer} instance
*/
public InputSynchronizer getInputSynchronize() {
return inputSynchronizer;
}
/**
* Gets the notification synchronizer.
*
* @return the {@link NotificationSynchronizer} instance
*/
public NotificationSynchronizer getNotificationSynchronizer() {
return notificationSynchronizer;
}
/**
* Prepares the app for a new game cycle.
*/
public void setup() {
}
public ClientGameLogic getGameLogic() {
return clientGameLogic;
}
public ExecutorService getExecutor() {
if (this.executor == null) {
this.executor = Executors.newCachedThreadPool();
}
return this.executor;
}
public ServerConnection getNetworkSupport(){
return networkConnection;
}
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if(isFullscreen) {
int baseWidth = 1280;
int baseHeight = 720;
float baseAspectRatio = (float) baseWidth / baseHeight;
float newAspectRatio = (float) width / height;
float scaleFactor = Math.max((float) width / baseWidth, (float) height / baseHeight);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
settings.setResolution(screenWidth, screenHeight);
settings.setFullscreen(true);
prefs.putFloat("scale", scaleFactor);
prefs.putBoolean("fullscreen", true);
} else {
prefs.putInt("width", width);
prefs.putInt("height", height);
prefs.putFloat("scale", imageFactor);
prefs.putBoolean("fullscreen", false);
}
}
public static void restartApp() {
try {
String javaBin = System.getProperty("java.home") + "/bin/java";
String classPath = System.getProperty("java.class.path");
String className = System.getProperty("sun.java.command");
ProcessBuilder builder = new ProcessBuilder(
javaBin, "-cp", classPath, className
);
builder.start();
System.exit(0);
} catch (Exception e) {
throw new RuntimeException("restart failed");
}
}
public void afterGameCleanup() {
MainView main = (MainView) mainView;
main.getJoinDialog().disconnect();
main.getHostDialog().shutdownServer();
ceremonyView.afterGameCleanup();
}
public GameView getGameView(){
return gameView;
}
}

View File

@@ -0,0 +1,35 @@
package pp.mdga.client;
/**
* Enum representing the various states of the MdgaApp application.
* Each state corresponds to a distinct phase or mode of the application.
*/
public enum MdgaState {
/**
* Represents an undefined or uninitialized state.
* This state should not be entered during normal application execution.
*/
NONE,
/**
* Represents the main menu state.
* This is typically the first state entered when the application starts.
*/
MAIN,
/**
* Represents the lobby state where players can prepare or wait before starting a game.
*/
LOBBY,
/**
* Represents the main gameplay state where the core game mechanics take place.
*/
GAME,
/**
* Represents the ceremony state, typically used for post-game events or celebrations.
*/
CEREMONY;
}

View File

@@ -0,0 +1,156 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.server.MdgaServer;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.message.client.LobbyReadyMessage;
import pp.mdga.notification.AcquireCardNotification;
import pp.mdga.notification.DrawCardNotification;
import pp.mdga.notification.TskSelectNotification;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ModelSynchronizer {
private static final Logger LOGGER = Logger.getLogger(ModelSynchronizer.class.getName());
private MdgaApp app;
private UUID a;
private UUID b;
private BonusCard card;
private boolean swap;
ModelSynchronizer(MdgaApp app) {
this.app = app;
swap = false;
}
public void animationEnd() {
app.getGameLogic().selectAnimationEnd();
}
public void select(UUID a, UUID b){
if(swap) selectSwap(a,b);
else selectPiece(a);
}
public void selectSwap(UUID a, UUID b) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "selectPiece");
this.a = a;
this.b = b;
GameView gameView = (GameView) app.getView();
if(a != null && b != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
public void selectPiece(UUID piece) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "selectPiece");
this.a = piece;
GameView gameView = (GameView) app.getView();
if(piece != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
}
}
public void selectCard(BonusCard card) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "selectCard");
this.card = card;
GameView gameView = (GameView) app.getView();
if(card == null) {
gameView.needConfirm();
} else {
gameView.needNoPower();
}
}
public void confirm() {
LOGGER.log(Level.INFO, "confirm");
GameView gameView = (GameView) app.getView();
if(a != null && b != null) {
app.getGameLogic().selectPiece(a);
app.getGameLogic().selectPiece(b);
gameView.getBoardHandler().clearSelectable();
} else if (a != null) {
app.getGameLogic().selectPiece(a);
gameView.getBoardHandler().clearSelectable();
} else {
if(null == card) {
app.getGameLogic().selectCard(null);
} else {
app.getGameLogic().selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
}
}
gameView.noConfirm();
gameView.noNoPower();
}
public void selectTsk(Color color) {
app.getGameLogic().selectTsk(color);
}
public void unselectTsk(Color color) {
app.getGameLogic().deselectTSK(color);
}
public void rolledDice() {
app.getGameLogic().selectDice();
}
public void setName(String name) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "setName: {0}", name);
app.getGameLogic().selectName(name);
}
public void setReady(boolean ready) {
app.getGameLogic().selectReady(ready);
}
public void setHost(int port) {
app.getGameLogic().selectJoin("");
}
public void setJoin(String ip, int port) {
app.getGameLogic().selectJoin(ip);
}
public void leave() {
app.getGameLogic().selectLeave();
}
public void enter(MdgaState state) {
LOGGER.log(Level.INFO, "enter: {0}", state);
//app.enter(state);
}
public void setSwap(boolean swap){
this.swap = swap;
}
public void force() {
}
}

View File

@@ -0,0 +1,91 @@
package pp.mdga.client;
import com.jme3.network.*;
import pp.mdga.message.client.ClientMessage;
import pp.mdga.message.server.ServerMessage;
import java.io.IOException;
public class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
private static final System.Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
private final MdgaApp app;
private Client client;
public NetworkSupport(MdgaApp app) {
this.app = app;
}
public MdgaApp getApp() {
return this.app;
}
public boolean isConnected() {
return this.client != null && this.client.isConnected();
}
public void connect() {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
} else {
try {
this.initNetwork("localhost", 2345);
} catch (IOException e) {
LOGGER.log(System.Logger.Level.ERROR, "could not connect to server", e);
}
}
}
public void disconnect() {
if (this.client != null) {
this.client.close();
this.client = null;
LOGGER.log(System.Logger.Level.INFO, "client closed");
}
}
public void initNetwork(String host, int port) throws IOException {
if (this.client != null) {
throw new IllegalStateException("trying to join a game again");
} else {
this.client = Network.connectToServer(host, port);
this.client.start();
this.client.addMessageListener(this);
this.client.addClientStateListener(this);
}
}
public void messageReceived(Client client, Message message) {
LOGGER.log(System.Logger.Level.INFO, "message received from server: {0}", new Object[]{message});
if (message instanceof ServerMessage serverMessage) {
this.app.enqueue(() -> serverMessage.accept(this.app.getGameLogic()));
}
}
public void clientConnected(Client client) {
LOGGER.log(System.Logger.Level.INFO, "Client connected: {0}", new Object[]{client});
}
public void clientDisconnected(Client client, ClientStateListener.DisconnectInfo disconnectInfo) {
LOGGER.log(System.Logger.Level.INFO, "Client {0} disconnected: {1}", new Object[]{client, disconnectInfo});
if (this.client != client) {
throw new IllegalArgumentException("parameter value must be client");
} else {
LOGGER.log(System.Logger.Level.INFO, "client still connected: {0}", new Object[]{client.isConnected()});
this.client = null;
this.disconnect();
}
}
@Override
public void send(ClientMessage message) {
LOGGER.log(System.Logger.Level.INFO, "sending {0}", new Object[]{message});
if (this.client == null) {
LOGGER.log(System.Logger.Level.WARNING, "client not connected");
} else {
this.client.send(message);
}
}
}

View File

@@ -0,0 +1,206 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.Color;
import pp.mdga.notification.*;
import java.util.ArrayList;
public class NotificationSynchronizer {
private final MdgaApp app;
private ArrayList<Notification> notifications = new ArrayList<>();
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
public void update() {
Notification n = app.getGameLogic().getNotification();
while (n != null) {
if(n instanceof InfoNotification infoNotification) {
app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
return;
}
if(n != null) {
switch (app.getState()) {
case MAIN:
handleMain(n);
break;
case LOBBY:
handleLobby(n);
break;
case GAME:
handleGame(n);
break;
case CEREMONY:
handleCeremony(n);
break;
case NONE:
throw new RuntimeException("no notification expected: " + n.getClass().getName());
}
}
n = app.getGameLogic().getNotification();
}
}
private void handleMain(Notification notification) {
if (notification instanceof LobbyDialogNotification) {
app.enter(MdgaState.LOBBY);
} else if (notification instanceof StartDialogNotification) {
//nothing
} else {
throw new RuntimeException("notification not expected in main: "+ notification.getClass().getName());
}
}
private void handleLobby(Notification notification) {
LobbyView lobbyView = (LobbyView) app.getView();
if (notification instanceof TskSelectNotification n) {
lobbyView.setTaken(n.getColor(), true, n.isSelf(), n.getName());
} else if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof TskUnselectNotification n) {
lobbyView.setTaken(n.getColor(), false, false, null);
} else if(notification instanceof LobbyReadyNotification lobbyReadyNotification) {
lobbyView.setReady(lobbyReadyNotification.getColor(), lobbyReadyNotification.isReady());
} else if (notification instanceof GameNotification n) {
app.getGameView().setOwnColor(n.getOwnColor());
app.enter(MdgaState.GAME);
} else {
throw new RuntimeException("notification not expected in lobby: " + notification.getClass().getName());
}
}
private void handleGame(Notification notification) {
GameView gameView = (GameView) app.getView();
GuiHandler guiHandler = gameView.getGuiHandler();
BoardHandler boardHandler = gameView.getBoardHandler();
ModelSynchronizer modelSynchronizer = app.getModelSynchronize();
if (notification instanceof AcquireCardNotification n) {
guiHandler.addCardOwn(n.getBonusCard());
app.getAcousticHandler().playSound(MdgaSound.BONUS);
} else if (notification instanceof ActivePlayerNotification n) {
gameView.getGuiHandler().setActivePlayer(n.getColor());
boardHandler.showDice(n.getColor());
app.getAcousticHandler().playSound(MdgaSound.UI90);
} else if (notification instanceof CeremonyNotification ceremonyNotification) {
app.enter(MdgaState.CEREMONY);
CeremonyView ceremonyView = (CeremonyView) app.getView();
int size = ceremonyNotification.getNames().size();
if (ceremonyNotification.getPiecesThrown().size() != size ||
ceremonyNotification.getPiecesLost().size() != size ||
ceremonyNotification.getBonusCardsPlayed().size() != size ||
ceremonyNotification.getSixes().size() != size ||
ceremonyNotification.getNodesMoved().size() != size ||
ceremonyNotification.getBonusNodes().size() != size) {
throw new IllegalArgumentException("All data lists in CeremonyNotification must have the same size.");
}
for (int i = 0; i < size; i++) {
Color color = ceremonyNotification.getColors().get(i);
String name = ceremonyNotification.getNames().get(i);
int v1 = ceremonyNotification.getPiecesThrown().get(i);
int v2 = ceremonyNotification.getPiecesLost().get(i);
int v3 = ceremonyNotification.getBonusCardsPlayed().get(i);
int v4 = ceremonyNotification.getSixes().get(i);
int v5 = ceremonyNotification.getNodesMoved().get(i);
int v6 = ceremonyNotification.getBonusNodes().get(i);
ceremonyView.addCeremonyParticipant(color, i, name);
ceremonyView.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
}
} else if (notification instanceof DiceNowNotification) {
guiHandler.showDice();
} else if (notification instanceof DrawCardNotification n) {
guiHandler.drawCard(n.getColor());
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
} else if (notification instanceof InterruptNotification notification1) {
gameView.enterInterrupt(notification1.getColor());
} else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) {
//StartMove
boardHandler.movePieceStartAnim(n.getPiece(), n.getMoveIndex());
}
else {
//InfieldMove
boardHandler.movePieceAnim(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
}
guiHandler.hideText();
} else if (notification instanceof ThrowPieceNotification n) {
boardHandler.throwBombAnim(n.getPieceId());
} else if (notification instanceof NoShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceId());
} else if (notification instanceof PlayCardNotification n) {
if(n.getColor() == gameView.getOwnColor()) guiHandler.playCardOwn(n.getCard());
else guiHandler.playCardEnemy(n.getColor(), n.getCard());
} else if (notification instanceof PlayerInGameNotification n) {
boardHandler.addPlayer(n.getColor(),n.getPiecesList());
guiHandler.addPlayer(n.getColor(),n.getName());
} else if (notification instanceof ResumeNotification) {
gameView.leaveInterrupt();
} else if (notification instanceof RollDiceNotification n) {
gameView.getGuiHandler().hideText();
if(n.getColor() == gameView.getOwnColor()){
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
}
else {
boardHandler.hideDice();
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
else guiHandler.showRolledDice(n.getEyes(), n.getColor());
}
} else if (notification instanceof SelectableCardsNotification n) {
guiHandler.setSelectableCards(n.getCards());
gameView.needNoPower();
} else if (notification instanceof ShieldActiveNotification n) {
boardHandler.shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification n) {
boardHandler.suppressShield(n.getPieceId());
} else if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else if (notification instanceof SwapPieceNotification n) {
// boardHandler.swapPieces(n.getFirstPiece(), n.getSecondPiece());
guiHandler.swap();
} else if (notification instanceof WaitMoveNotification) {
//TODO ???
} else if (notification instanceof SelectableMoveNotification n) {
boardHandler.outlineMove(n.getPieces(), n.getMoveIndices(), n.getHomeMoves());
modelSynchronizer.setSwap(false);
} else if (notification instanceof SelectableSwapNotification n) {
boardHandler.outlineSwap(n.getOwnPieces(), n.getEnemyPieces());
modelSynchronizer.setSwap(true);
} else if (notification instanceof SelectableShieldNotification n) {
boardHandler.outlineShield(n.getPieces());
modelSynchronizer.setSwap(false);
} else if (notification instanceof TurboActiveNotification){
guiHandler.turbo();
} else if (notification instanceof FinishNotification n){
guiHandler.finish(n.getColorFinished());
} else {
throw new RuntimeException("notification not expected in game: " + notification.getClass().getName());
}
}
private void handleCeremony(Notification notification) {
if (notification instanceof StartDialogNotification) {
app.afterGameCleanup();
app.enter(MdgaState.MAIN);
} else {
throw new RuntimeException("notification not expected in ceremony: " + notification.getClass().getName());
}
}
}

View File

@@ -0,0 +1,468 @@
package pp.mdga.client.acoustic;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import java.util.*;
import java.util.prefs.Preferences;
public class AcousticHandler {
private MdgaApp app;
private MdgaState state = MdgaState.NONE;
private boolean playGame = false;
private ArrayList<MusicAsset> gameTracks = new ArrayList<>();
private NanoTimer trackTimer = new NanoTimer();
private boolean fading = false; // Indicates if a fade is in progress
private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
private static final float FADE_DURATION = 3.0f; // Duration for outfade
private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade
private GameMusic playing = null; // Currently playing track
private GameMusic scheduled = null; // Scheduled track to play next
private GameMusic old = null; // Old track being faded out
private float mainVolume = 0.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
private ArrayList<GameSound> sounds = new ArrayList<>();
private Preferences prefs = Preferences.userNodeForPackage(AcousticHandler.class);
public AcousticHandler(MdgaApp app) {
this.app = app;
mainVolume = prefs.getFloat("mainVolume", 1.0f);
musicVolume = prefs.getFloat("musicVolume", 1.0f);
soundVolume = prefs.getFloat("soundVolume", 1.0f);
}
/**
* This method updates the acousticHandler and should be called every frame
*/
public void update() {
updateVolumeAndTrack();
if (playGame) {
updateGameTracks();
}
Iterator<GameSound> iterator = sounds.iterator();
while (iterator.hasNext()) {
GameSound s = iterator.next();
s.update(getSoundVolumeTotal());
if (!s.isPlaying()) {
iterator.remove();
}
}
}
/**
* This method instantly plays a sound
*
* @param sound the sound to be played
*/
public void playSound(MdgaSound sound) {
ArrayList<SoundAssetDelayVolume> assets = new ArrayList<SoundAssetDelayVolume>();
switch (sound) {
case LOST:
assets.add(new SoundAssetDelayVolume(SoundAsset.LOST, 1.0f, 0.0f));
break;
case VICTORY:
assets.add(new SoundAssetDelayVolume(SoundAsset.VICTORY, 1.0f, 0.0f));
break;
case BUTTON_PRESSED:
assets.add(new SoundAssetDelayVolume(SoundAsset.BUTTON_PRESS, 0.7f, 0.0f));
break;
case WRONG_INPUT:
assets.add(new SoundAssetDelayVolume(SoundAsset.ERROR, 1.0f, 0.0f));
break;
case UI_CLICK:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_CLICK, 0.8f, 0.0f));
break;
case START:
assets.add(new SoundAssetDelayVolume(SoundAsset.START, 0.8f, 0.5f));
break;
case THROW:
assets.add(new SoundAssetDelayVolume(SoundAsset.LAUGHT, 1.0f, 0.2f));
break;
case POWERUP:
assets.add(new SoundAssetDelayVolume(SoundAsset.POWERUP, 1.0f, 0.2f));
break;
case SELF_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.ROBOT_READY, 1.0f, 0.0f));
break;
case OTHER_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.UNIT_READY, 1.0f, 0.0f));
break;
case OTHER_CONNECTED:
assets.add(new SoundAssetDelayVolume(SoundAsset.CONNECTED, 1.0f, 0.0f));
break;
case NOT_READY:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND, 1.0f, 0.0f));
break;
case LEAVE:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI_SOUND2, 0.6f, 0.0f));
break;
case JET:
assets.add(new SoundAssetDelayVolume(SoundAsset.JET, 1.0f, 0.0f));
break;
case EXPLOSION:
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 0f));
break;
case LOSE:
assets.add(new SoundAssetDelayVolume(SoundAsset.LOSE, 1.0f, 0.0f));
break;
case BONUS:
assets.add(new SoundAssetDelayVolume(SoundAsset.BONUS, 1.0f, 0.0f));
break;
case UI90:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI90, 1.0f, 0.0f));
break;
case MISSILE:
assets.add(new SoundAssetDelayVolume(SoundAsset.MISSILE, 1.0f, 0.0f));
break;
case MATRIX:
assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
break;
default:
break;
}
for (SoundAssetDelayVolume sawd : assets) {
GameSound gameSound = new GameSound(app, sawd.asset(), getSoundVolumeTotal(), sawd.subVolume(), sawd.delay());
sounds.add(gameSound);
}
}
/**
* This method fades the played music to fit the state.
*
* @param state the state of which the corresponding music should be played to be played
*/
public void playState(MdgaState state) {
if (this.state == state) {
return;
}
MusicAsset asset = null;
switch (state) {
case MAIN:
playGame = false;
asset = MusicAsset.MAIN_MENU;
break;
case LOBBY:
playGame = false;
asset = MusicAsset.LOBBY;
break;
case GAME:
addGameTracks();
playGame = true;
assert (!gameTracks.isEmpty()) : "no more game music available";
asset = gameTracks.remove(0);
break;
case CEREMONY:
playGame = false;
asset = MusicAsset.CEREMONY;
break;
case NONE:
throw new RuntimeException("no music for state NONE");
}
assert (null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f);
}
/**
* Performs linear interpolation between two float values.
*
* @param start The starting value.
* @param end The ending value.
* @param t The interpolation factor, typically between 0 and 1.
* @return The interpolated value between start and end.
*/
private float lerp(float start, float end, float t) {
return start + t * (end - start);
}
/**
* Updates the state of audio playback, handling track transitions and volume adjustments.
*
* This method ensures smooth transitions between tracks using fade-in and fade-out effects.
* It also handles cases where no track is playing, starting a scheduled track immediately at full volume.
* The method prioritizes the latest scheduled track if multiple scheduling occurs quickly.
*
* Behavior:
* 1. If nothing is scheduled and no track is playing, it exits early.
* 2. If a scheduled track exists and no track is playing, the scheduled track starts immediately at full volume.
* 3. If a scheduled track exists while a track is playing, it initiates a fade-out for the currently playing track
* and prepares for the new track to fade in.
* 4. If a track transition is in progress (fading), it processes the fade-out and fade-in states.
* If a new track is scheduled during this process, it interrupts the current transition and prioritizes the new track.
* 5. If no fading is needed and a track is playing, it ensures the track's volume is updated.
*
* Special cases:
* - If no track is playing and a new track is scheduled, it starts the track immediately without fading.
* - If a new track is scheduled during fading, it resets the transition to prioritize the new track.
*/
private void updateVolumeAndTrack() {
if (scheduled == null && !fading && playing == null) {
// Nothing to do, early exit
return;
}
if (scheduled != null && playing == null && !fading) {
// No current track, start scheduled track immediately at full volume
playing = scheduled;
scheduled = null;
playing.play();
playing.update(getMusicVolumeTotal()); // Set volume to full
return;
}
if (scheduled != null && !fading) {
// Initiate a fade process if a new track is scheduled
fading = true;
fadeTimer.reset();
old = playing; // The currently playing track becomes the old track
playing = null; // Clear the playing track during the fade process
}
if (fading) {
handleFadeProcess();
// Handle any interruptions due to newly scheduled tracks
if (scheduled != null && playing != null && playing != scheduled) {
// Interrupt the current infade and switch to the new scheduled track
old = playing; // Treat the currently infading track as the old track
playing = null; // Reset playing to allow switching
fadeTimer.reset(); // Restart fade timer for the new track
}
} else if (playing != null) {
// Update volume for the currently playing track
playing.update(getMusicVolumeTotal());
} else if (scheduled != null) {
// If no track is playing and one is scheduled, start it immediately at full volume
playing = scheduled;
scheduled = null;
playing.play();
playing.update(getMusicVolumeTotal()); // Set volume to full
}
}
/**
* Manages the fading process during audio track transitions.
*
* This method handles the fade-out of the currently playing (old) track, manages any pause between the fade-out
* and fade-in, and initiates the fade-in for the new track if applicable. It ensures smooth transitions between
* tracks while maintaining the correct volume adjustments.
*
* Behavior:
* 1. **Outfade:** Gradually decreases the volume of the `old` track over the duration of `FADE_DURATION`.
* Once the outfade completes, the `old` track is paused and cleared.
* 2. **Pause Handling:** Waits for a defined pause (if applicable) before initiating the infade for the next track.
* 3. **Infade:** If a `scheduled` track exists and the outfade and pause are complete, it begins playing
* the new track (`playing`) and initiates the infade process.
*
* Key Details:
* - The outfade volume adjustment is interpolated linearly from full volume to zero using the `lerp` function.
* - The pause duration is retrieved from the scheduled track if it is specified.
* - If a new track is scheduled during the fade process, it is handled by external logic to prioritize transitions.
*
* Preconditions:
* - `fading` is expected to be `true` when this method is called.
* - The method is invoked as part of the `updateVolumeAndTrack` process.
*/
private void handleFadeProcess() {
float time = fadeTimer.getTimeInSeconds();
// Handle outfade for the old track
if (old != null && time <= FADE_DURATION) {
float t = Math.min(time / FADE_DURATION, 1.0f);
float oldVolume = lerp(1.0f, 0.0f, t);
old.update(getMusicVolumeTotal() * oldVolume);
}
if (old != null && time > FADE_DURATION) {
// Complete outfade
old.pause();
old = null;
}
// Handle pause duration before infade
float pause = (scheduled != null) ? scheduled.getPause() : 0.0f;
if (time > FADE_DURATION + pause) {
if (playing == null && scheduled != null) {
// Begin infade for the new track
playing = scheduled;
scheduled = null;
playing.play(); // Start playing the new track
}
handleInfade(time - FADE_DURATION - pause);
}
}
/**
* Manages the fade-in process for the currently playing track.
*
* This method gradually increases the volume of the `playing` track from zero to full volume
* over the duration of `CROSSFADE_DURATION`. It ensures a smooth transition into the new track.
*
* Behavior:
* 1. If no track is set as `playing`, the method exits early, as there is nothing to fade in.
* 2. Linearly interpolates the volume of the `playing` track from 0.0 to 1.0 based on the elapsed
* `infadeTime` and the specified `CROSSFADE_DURATION`.
* 3. Once the fade-in is complete (when `infadeTime` exceeds `CROSSFADE_DURATION`), the method:
* - Marks the fade process (`fading`) as complete.
* - Ensures the `playing` track is updated to its full volume.
*
* Key Details:
* - Uses the `lerp` function to calculate the volume level for the `playing` track during the fade-in.
* - Ensures the volume is always a value between 0.0 and 1.0.
* - The `infadeTime` parameter should be relative to the start of the fade-in process.
*
* Preconditions:
* - The `playing` track must be initialized and actively fading in for this method to have an effect.
* - The method is invoked as part of the `updateVolumeAndTrack` process.
*
* @param infadeTime The elapsed time (in seconds) since the fade-in process started.
*/
private void handleInfade(float infadeTime) {
if (playing == null) {
// Nothing to infade
return;
}
// Proceed with the infade for the current playing track
float t = Math.min(infadeTime / CROSSFADE_DURATION, 1.0f);
float newVolume = lerp(0.0f, 1.0f, t);
playing.update(getMusicVolumeTotal() * newVolume);
if (infadeTime > CROSSFADE_DURATION) {
// Infade is complete, finalize state
fading = false;
playing.update(getMusicVolumeTotal()); // Ensure full volume
}
}
/**
* Adds a list of game tracks to the gameTracks collection and shuffles them.
* This method adds predefined game tracks to the track list and shuffles the order.
*/
private void addGameTracks() {
Random random = new Random();
for (int i = 1; i <= 6; i++) {
gameTracks.add(MusicAsset.valueOf("GAME_" + i));
}
Collections.shuffle(gameTracks, random);
}
/**
* Updates the current game tracks. If the currently playing track is nearing its end,
* a new track will be scheduled to play. If the list of game tracks is empty, it will be refreshed.
*/
private void updateGameTracks() {
if(null == playing) {
return;
}
if (playing.nearEnd(10)) {
if (gameTracks.isEmpty()) {
addGameTracks();
}
}
if (playing != null && playing.nearEnd(3) && trackTimer.getTimeInSeconds() > 20) {
trackTimer.reset();
MusicAsset nextTrack = gameTracks.remove(0);
scheduled = new GameMusic(app, nextTrack, getMusicVolumeTotal(), nextTrack.getSubVolume(), nextTrack.getLoop(), 0.0f);
}
}
/**
* Retrieves the main volume level.
*
* @return The current main volume level.
*/
public float getMainVolume() {
return mainVolume;
}
/**
* Retrieves the music volume level.
*
* @return The current music volume level.
*/
public float getMusicVolume() {
return musicVolume;
}
/**
* Retrieves the sound volume level.
*
* @return The current sound volume level.
*/
public float getSoundVolume() {
return soundVolume;
}
/**
* Sets the main volume level.
*
* @param mainVolume The desired main volume level.
*/
public void setMainVolume(float mainVolume) {
this.mainVolume = mainVolume;
prefs.putFloat("mainVolume", mainVolume);
}
/**
* Sets the music volume level.
*
* @param musicVolume The desired music volume level.
*/
public void setMusicVolume(float musicVolume) {
this.musicVolume = musicVolume;
prefs.putFloat("musicVolume", musicVolume);
}
/**
* Sets the sound volume level.
*
* @param soundVolume The desired sound volume level.
*/
public void setSoundVolume(float soundVolume) {
this.soundVolume = soundVolume;
prefs.putFloat("soundVolume", soundVolume);
}
/**
* Calculates the total music volume by multiplying the music volume by the main volume.
*
* @return The total music volume.
*/
float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume();
}
/**
* Calculates the total sound volume by multiplying the sound volume by the main volume.
*
* @return The total sound volume.
*/
float getSoundVolumeTotal() {
return getSoundVolume() * getMainVolume();
}
}

View File

@@ -0,0 +1,116 @@
package pp.mdga.client.acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.mdga.client.MdgaApp;
/**
* Represents a game music track, including its playback controls and volume settings.
* This class manages the playback of a music track, allowing for playing, pausing,
* volume adjustment, and tracking the current status of the music.
*/
class GameMusic {
private float volume;
private final float subVolume;
private final AudioNode music;
private float pause;
/**
* Constructs a new GameMusic object.
*
* @param app The instance of the application, used to access the asset manager.
* @param asset The music asset to be played.
* @param volume The total volume of the music, adjusted by the main volume.
* @param subVolume A relative volume that modifies the base music volume, typically a percentage.
* @param loop A flag indicating whether the music should loop once it finishes.
*/
GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop, float pause) {
this.volume = volume;
this.subVolume = subVolume;
this.pause = pause;
music = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Stream);
music.setPositional(false);
music.setDirectional(false);
music.setVolume(volume * subVolume);
music.setLooping(loop);
}
/**
* Plays the current music track.
* If the music is already initialized, it starts playback.
* If the music is not available, no action is performed.
*/
void play() {
if (null == music) {
return;
}
music.play();
}
/**
* Pauses the current music track.
* If the music is not available or is not playing, no action is performed.
*/
void pause() {
if (null == music) {
return;
}
music.stop();
}
/**
* Checks if the current music track is playing.
*
* @return true if the music is playing, false otherwise.
*/
boolean isPlaying() {
return music.getStatus() == AudioSource.Status.Playing;
}
/**
* Checks if the current music track is near the end.
*
* @param thresholdSeconds The threshold in seconds. If the remaining time is less than or equal to this value,
* the track is considered near the end.
* @return true if the track is near its end (within the threshold), false otherwise.
*/
boolean nearEnd(float thresholdSeconds) {
if (music == null || !isPlaying()) {
return false;
}
float currentTime = music.getPlaybackTime(); // Current playback time in seconds
float duration = music.getAudioData().getDuration(); // Total duration in seconds
if (duration <= 0) {
return false;
}
float remainingTime = duration - currentTime;
return remainingTime <= thresholdSeconds;
}
/**
* Updates the volume of the music.
* If the volume has changed, it will adjust the music's volume accordingly.
*
* @param newVolume The new total volume for the music.
*/
void update(float newVolume) {
if (volume != newVolume) {
volume = newVolume;
music.setVolume(volume * subVolume);
}
}
float getPause() {
return pause;
}
}

View File

@@ -0,0 +1,83 @@
package pp.mdga.client.acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp;
/**
* Represents a game sound effect, with control over playback, volume, and timing.
* This class manages the playback of a sound effect, including starting playback after a delay,
* adjusting volume, and tracking whether the sound has finished playing.
*/
class GameSound {
private float volume;
final private float subVolume;
private final AudioNode sound;
private boolean playing = false;
private boolean finished = false;
private float delay = 0.0f;
private NanoTimer timer = null;
/**
* Constructs a new GameSound object.
*
* @param app The instance of the application, used to access the asset manager.
* @param asset The sound asset to be played.
* @param volume The total volume of the sound, adjusted by the main volume.
* @param subVolume A relative volume that modifies the base sound volume, typically a percentage.
* @param delay The delay before the sound starts playing, in seconds.
*/
GameSound(MdgaApp app, SoundAsset asset, float volume, float subVolume, float delay) {
this.volume = volume;
this.subVolume = subVolume;
this.delay = delay;
sound = new AudioNode(app.getAssetManager(), asset.getPath(), AudioData.DataType.Buffer);
sound.setPositional(false);
sound.setDirectional(false);
sound.setLooping(false);
sound.setVolume(volume * subVolume);
timer = new NanoTimer();
}
/**
* Checks if the sound is currently playing.
*
* @return true if the sound is playing, false otherwise.
*/
boolean isPlaying() {
return !finished;
}
/**
* Updates the sound playback, adjusting the volume if necessary, and starting
* the sound after the specified delay.
*
* @param newVolume The new total volume for the sound.
*/
void update(float newVolume) {
if (!playing && timer.getTimeInSeconds() > delay) {
sound.play();
playing = true;
}
if (!playing) {
return;
}
if (volume != newVolume) {
volume = newVolume;
sound.setVolume(volume * subVolume);
}
if (sound != null && sound.getStatus() == AudioSource.Status.Playing) {
finished = true;
}
}
}

View File

@@ -0,0 +1,41 @@
package pp.mdga.client.acoustic;
/**
* Enum representing the various sound effects used in the game.
* Each sound corresponds to an event or action in the game and may consist of one or more
* audio files, potentially with time delays between them.
* <p>
* These sounds are used to play specific audio cues, such as when a dice is rolled,
* a turn starts or ends, a piece is moved or lost, and various other events in the game.
*/
public enum MdgaSound {
DICE_ROLL,
TURN_START,
TURN_END,
PIECE_END,
PIECE_MOVE,
PIECE_LOST,
SELECT,
DESELECT,
HURRY,
VICTORY,
LOST,
BUTTON_PRESSED,
WRONG_INPUT,
UI_CLICK,
START,
THROW,
POWERUP,
SELF_READY,
OTHER_READY,
OTHER_CONNECTED,
NOT_READY,
LEAVE,
JET,
EXPLOSION,
LOSE,
BONUS,
UI90,
MISSILE,
MATRIX,
}

View File

@@ -0,0 +1,77 @@
package pp.mdga.client.acoustic;
/**
* Enum representing various music assets used in the game.
* Each constant corresponds to a specific music track, along with its properties such as file path,
* looping behavior, and relative volume (subVolume).
* These music assets are used to control the music that plays in different parts of the game, such as menus and in-game music.
*/
enum MusicAsset {
MAIN_MENU("Spaceship.wav", true, 1.0f),
LOBBY("DeadPlanet.wav", true, 1.0f),
CEREMONY("80s,Disco,Life.wav", true, 1.0f),
GAME_1("NeonRoadTrip.wav", 1.0f),
GAME_2("NoPressureTrance.wav", 1.0f),
GAME_3("TheSynthRave.wav", 1.0f),
GAME_4("LaserParty.wav", 1.0f),
GAME_5("RetroNoir.wav", 1.0f),
GAME_6("SpaceInvaders.wav", 1.0f);
private final String path;
private final boolean loop;
private final float subVolume;
private static final String ROOT = "Music/";
/**
* Constructs a new MusicAsset object with the specified name and sub-volume.
* The track will not loop by default.
*
* @param name The name of the music file.
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
*/
MusicAsset(String name, float subVolume) {
this.path = ROOT + name;
this.loop = false;
this.subVolume = subVolume;
}
/**
* Constructs a new MusicAsset object with the specified name, loop flag, and sub-volume.
*
* @param name The name of the music file.
* @param loop If true, the track will loop; otherwise, it will play once.
* @param subVolume A relative volume that modifies the base volume of the track (typically a percentage).
*/
MusicAsset(String name, boolean loop, float subVolume) {
this.path = ROOT + name;
this.loop = loop;
this.subVolume = subVolume;
}
/**
* Gets the file path of the music track.
*
* @return The path to the music file (relative to the music folder).
*/
public String getPath() {
return path;
}
/**
* Gets whether the music track should loop.
*
* @return true if the track should loop, false otherwise.
*/
public boolean getLoop() {
return loop;
}
/**
* Gets the relative volume (subVolume) for the music track.
*
* @return The relative volume for the track, typically a value between 0.0 and 1.0.
*/
public float getSubVolume() {
return subVolume;
}
}

View File

@@ -0,0 +1,61 @@
package pp.mdga.client.acoustic;
/**
* Enum representing various sound assets used in the game.
* Each constant corresponds to a specific sound effect used throughout the game.
* These sounds are associated with various events and actions, such as dice rolls,
* turn changes, piece movements, and game outcomes.
*/
enum SoundAsset {
DICE_ROLL(""),
TURN_START(""),
TURN_END(""),
PIECE_END(""),
PIECE_MOVE(""),
PIECE_LOST(""),
SELECT(""),
DESELECT(""),
HURRY(""),
VICTORY("LevelUp2.wav"),
LOST("GameOver.wav"),
BUTTON_PRESS("menu_button.ogg"),
ERROR("buzzer.wav"),
UI_SOUND("ui_sound.ogg"),
UI_SOUND2("ui_swoosch.wav"),
UI_CLICK("uiclick.ogg"),
START("gamestart.ogg"),
LAUGHT("laughter.wav"),
POWERUP("powerup.wav"),
ROBOT_READY("robotReady.wav"),
UNIT_READY("unitReady.wav"),
JET("jet-overhead.wav"),
EXPLOSION_1("exp.ogg"),
EXPLOSION_2("exp2.ogg"),
THUNDER("thunder.ogg"),
UI90("ui90.ogg"),
BONUS("bonus.ogg"),
LOSE("lose.ogg"),
MISSILE("missile.ogg"),
MATRIX("matrix.wav"),
CONNECTED("connected.wav");
private final String path;
/**
* Constructs a new SoundAsset object with the specified name.
*
* @param name The name of the sound file.
*/
SoundAsset(String name) {
this.path = "Sounds/" + name;
}
/**
* Gets the file path of the sound effect.
*
* @return The path to the sound file (relative to the sound folder).
*/
public String getPath() {
return path;
}
}

View File

@@ -0,0 +1,7 @@
package pp.mdga.client.acoustic;
/**
* A record that encapsulates a sound asset along with its playback settings:
* the relative volume (subVolume) and a delay before it starts playing.
*/
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {}

View File

@@ -0,0 +1,125 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
public class Explosion {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f location;
private ParticleEmitter fire;
private ParticleEmitter smoke;
private boolean triggered = false;
private final Material mat;
/**
* Konstruktor für die Explosion.
*
* @param app Die Hauptanwendung.
* @param rootNode Der Root-Knoten, an den die Explosion angefügt wird.
* @param location Der Ort der Explosion in World-Koordinaten.
*/
public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
this.rootNode = rootNode;
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
}
/**
* Initialisiert den Partikel-Emitter für die Explosion.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", Type.Triangle,50);
fire.setMaterial(mat);
fire.setStartColor(ColorRGBA.Yellow);
fire.setEndColor(ColorRGBA.Red);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f,0.2f,4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.1f);
fire.setEndSize(0.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(2.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", Type.Triangle,40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,0.7f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.2f);
smoke.setEndSize(0.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(5.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Löst die Explosion aus.
*/
public void trigger() {
if (!triggered) {
triggered = true;
initializeEmitter();
}
rootNode.attachChild(fire);
fire.emitAllParticles();
fire.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(fire);
fire.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
});
rootNode.attachChild(smoke);
smoke.emitAllParticles();
smoke.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(smoke);
smoke.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
});
}
}

View File

@@ -0,0 +1,173 @@
package pp.mdga.client.animation;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.view.GameView;
import java.util.UUID;
public class JetAnimation {
private final MdgaApp app; // Referenz auf die Hauptanwendung
private final Node rootNode; // Root-Knoten, an dem die Animation hängt
private Spatial jetModel; // Das Model des "jet"
private final Vector3f spawnPoint; // Spawnpunkt des Jets
private final Vector3f nodePoint; // Punkt des überflogenen Knotens
private final Vector3f despawnPoint; // Punkt, an dem der Jet despawnt
private final float curveHeight; // Maximale Höhe der Kurve
private final float animationDuration; // Dauer der Animation
private Explosion explosion;
private final UUID id;
/**
* Konstruktor für die ThrowAnimation-Klasse.
*
* @param app Die Hauptanwendung
* @param rootNode Der Root-Knoten, an dem der Jet angefügt wird
* @param uuid Die UUID des pieces
* @param targetPoint Der Punkt, an dem der Jet spawnt
* @param curveHeight Die maximale Höhe der Flugkurve
* @param animationDuration Die Gesamtdauer der Animation in Sekunden
*/
public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint, float curveHeight, float animationDuration) {
Vector3f spawnPoint = targetPoint.add(170, 50, 50);
Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
Vector3f despawnPoint = targetPoint.add(-100, -100, 40);
this.app = app;
this.rootNode = rootNode;
this.spawnPoint = spawnPoint;
this.nodePoint = controlPoint;
this.despawnPoint = despawnPoint;
this.curveHeight = curveHeight;
this.animationDuration = animationDuration;
id = uuid;
explosion = new Explosion(app, rootNode, targetPoint);
}
/**
* Startet die Animation.
*/
public void start() {
app.getAcousticHandler().playSound(MdgaSound.JET);
spawnJet();
animateJet();
}
/**
* Spawnt den Jet an der spezifizierten Position.
*/
private void spawnJet() {
jetModel = app.getAssetManager().loadModel(Asset.jet.getModelPath());
jetModel.setLocalTranslation(spawnPoint);
jetModel.scale(Asset.jet.getSize());
jetModel.rotate(FastMath.HALF_PI, 0, 0);
jetModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.jet.getDiffPath()));
jetModel.setMaterial(mat);
rootNode.attachChild(jetModel);
}
/**
* Animiert den Jet entlang einer Kurve und lässt ihn anschließend verschwinden.
*/
private void animateJet() {
Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
Vector3f controlPoint2 = nodePoint.add(0, curveHeight, 0);
BezierCurve3f curve = new BezierCurve3f(spawnPoint, controlPoint1, controlPoint2, despawnPoint);
app.getRootNode().addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
float progress = elapsedTime / animationDuration;
if(elapsedTime > 4.2f) {
explosion.trigger();
}
if (progress > 1) {
rootNode.detachChild(jetModel);
this.spatial.removeControl(this);
} else {
Vector3f currentPos = curve.interpolate(progress);
Vector3f direction = curve.interpolateDerivative(progress).normalizeLocal();
jetModel.setLocalTranslation(currentPos);
jetModel.lookAt(currentPos.add(direction), Vector3f.UNIT_Z);
jetModel.rotate(-FastMath.HALF_PI, 0, (float) Math.toRadians(-25));
}
if (elapsedTime > 6.0f) {
GameView gameView = (GameView) app.getView();
BoardHandler boardHandler = gameView.getBoardHandler();
boardHandler.throwPieceAnim(id);
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// Wird hier nicht benötigt
}
});
}
/**
* Repräsentiert eine 3D-Bezier-Kurve mit vier Kontrollpunkten.
*/
private static class BezierCurve3f {
private final Vector3f p0, p1, p2, p3;
public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p0 = p0;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
public Vector3f interpolate(float t) {
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3f point = p0.mult(uuu);
point = point.add(p1.mult(3 * uu * t));
point = point.add(p2.mult(3 * u * tt));
point = point.add(p3.mult(ttt));
return point;
}
public Vector3f interpolateDerivative(float t) {
float u = 1 - t;
float tt = t * t;
Vector3f derivative = p0.mult(-3 * u * u);
derivative = derivative.add(p1.mult(3 * u * u - 6 * u * t));
derivative = derivative.add(p2.mult(6 * u * t - 3 * tt));
derivative = derivative.add(p3.mult(3 * tt));
return derivative;
}
}
}

View File

@@ -0,0 +1,138 @@
package pp.mdga.client.animation;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import java.util.UUID;
public class MissileAnimation {
private final Node rootNode; // Root-Knoten, an den die Animation gehängt wird
private final MdgaApp app; // Referenz auf die Hauptanwendung
private final Vector3f start; // Startpunkt der Rakete
private final Vector3f target; // Zielpunkt der Rakete
private final float flightTime; // Gesamtdauer des Flugs
private Explosion explosion;
private Spatial missileModel; // 3D-Modell der Rakete
private UUID id;
/**
* Konstruktor für die MissileAnimation.
*
* @param app Die Hauptanwendung.
* @param rootNode Der Root-Knoten, an den die Animation gehängt wird.
* @param target Der Zielpunkt der Rakete.
* @param flightTime Die Zeit, die die Rakete für den gesamten Flug benötigt.
*/
public MissileAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f target, float flightTime) {
this.app = app;
this.rootNode = rootNode;
this.flightTime = flightTime;
explosion = new Explosion(app, rootNode, target);
id = uuid;
this.target = target.add(new Vector3f(1.5f, -1, 0));
start = BoardHandler.gridToWorld(12, 0);
start.add(new Vector3f(0, 0, 0));
}
/**
* Startet die Raketenanimation.
*/
public void start() {
loadMissile();
app.getAcousticHandler().playSound(MdgaSound.MISSILE);
animateMissile();
}
/**
* Lädt das Raketenmodell und setzt es auf den Startpunkt.
*/
private void loadMissile() {
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath()); // Lade das Missile-Modell
missileModel.scale(Asset.missile.getSize());
missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
missileModel.setMaterial(mat);
missileModel.setLocalTranslation(start); // Setze Startposition
rootNode.attachChild(missileModel); // Füge das Modell zur Szene hinzu
}
/**
* Animiert die Rakete entlang einer Parabel.
*/
private void animateMissile() {
missileModel.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
float progress = elapsedTime / flightTime;
if (progress >= 0.95f) {
explosion.trigger();
}
if (progress >= 1) {
explosion.trigger();
// Flug abgeschlossen
rootNode.detachChild(missileModel); // Entferne Rakete nach dem Ziel
this.spatial.removeControl(this); // Entferne die Steuerung
return;
}
// Berechne die aktuelle Position entlang der Parabel
Vector3f currentPosition = computeParabolicPath(start, target, progress);
missileModel.setLocalTranslation(currentPosition);
// Passe die Ausrichtung an (Nase der Rakete zeigt in Flugrichtung)
Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
.subtract(currentPosition)
.normalizeLocal();
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y); // Z ist oben, Y ist "Up"
missileModel.rotate(0, FastMath.HALF_PI, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// Keine Render-Logik benötigt
}
});
}
/**
* Berechnet eine Parabelbewegung von `start` zu `target`.
*
* @param start Der Startpunkt der Rakete.
* @param target Der Zielpunkt der Rakete.
* @param t Der Fortschritt des Flugs (0 bis 1).
* @return Die Position der Rakete entlang der Parabel.
*/
private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
Vector3f midPoint = start.add(target).multLocal(0.5f); // Berechne die Mitte zwischen Start und Ziel
midPoint.addLocal(0, 0, 20); // Erhöhe den Scheitelpunkt der Parabel entlang der Z-Achse
// Quadratische Interpolation (Parabel)
Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
return FastMath.interpolateLinear(t, startToMid, midToTarget);
}
}

View File

@@ -0,0 +1,109 @@
package pp.mdga.client.animation;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
/**
* A control that smoothly moves a spatial from an initial position to an end position
* using a quadratic interpolation, with the option to perform an action after the movement is complete.
* The movement path includes an intermediate "middle" position at a specified height.
*
* <p>Movement speed can be adjusted by modifying the MOVE_SPEED constant. The movement easing follows
* an ease-in-out curve to create a smooth start and stop effect.
* </p>
*/
public class MoveControl extends InitControl {
private boolean moving;
private final Vector3f initPos;
private final Vector3f endPos;
private final Vector3f middlePos;
private final static float HEIGHT = 2;
private final static float MOVE_SPEED = 1f;
private float progress = 0;
private final Runnable actionAfter;
/**
* Creates a new MoveControl with specified initial and end positions, and an action to run after the movement.
* The movement follows a path with a midpoint at a fixed height.
*
* @param initPos The starting position of the spatial.
* @param endPos The target position of the spatial.
* @param actionAfter A Runnable that will be executed after the movement finishes.
*/
public MoveControl(Vector3f initPos, Vector3f endPos, Runnable actionAfter){
moving = false;
this.initPos = initPos;
this.endPos = endPos;
middlePos = new Vector3f(
(initPos.x + endPos.x) / 2,
(initPos.y + endPos.y) / 2,
HEIGHT
);
this.actionAfter = actionAfter;
}
/**
* Initializes the movement by resetting the progress and setting the moving flag to true.
* This is called automatically when the spatial is set.
*/
@Override
protected void initSpatial() {
moving = true;
progress = 0;
}
/**
* Updates the movement of the spatial by interpolating its position along the defined path.
* The movement is smoothed using an easing function.
* Once the movement reaches the target, the {@link #end()} method is called to finish the movement.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if(!moving) return;
progress += tpf * MOVE_SPEED;
if(progress > 1) progress = 1;
spatial.setLocalTranslation(quadInt(initPos,middlePos,endPos, easeInOut(progress)));
if(progress == 1) end();
}
/**
* Ends the movement by stopping the interpolation, running the action after the movement,
* and removing this control from the spatial.
*/
private void end(){
moving = false;
actionAfter.run();
spatial.removeControl(this);
}
/**
* Performs quadratic interpolation between three points.
*
* @param p1 The initial point.
* @param p2 The middle point.
* @param p3 The final point.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated point.
*/
private Vector3f quadInt(Vector3f p1, Vector3f p2, Vector3f p3, float t) {
// Quadratic interpolation: (1-t)^2 * p1 + 2 * (1-t) * t * p2 + t^2 * p3
float oneMinusT = 1 - t;
return p1.mult(oneMinusT * oneMinusT)
.add(p2.mult(2 * oneMinusT * t))
.add(p3.mult(t * t));
}
/**
* A smooth ease-in-out function for interpolation.
* It accelerates and decelerates the interpolation for a smoother effect.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The adjusted interpolation value.
*/
private float easeInOut(float x){
return x < 0.5 ? 4 * x * x * x : (float) (1 - Math.pow(-2 * x + 2, 3) / 2);
}
}

View File

@@ -0,0 +1,213 @@
package pp.mdga.client.animation;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import pp.mdga.client.InitControl;
import pp.mdga.game.BonusCard;
/**
* A control that manages the animation of symbols representing different bonus card states.
* The symbol can animate with zoom, rotation, and translation effects based on the state of the bonus card.
*
* <p>The control supports three main states: SHIELD, SWAP, and TURBO. Each state has its own specific animation logic:
* <ul>
* <li>SHIELD: Zooms in and out, with a scaling effect.</li>
* <li>SWAP: Rotates the symbol 360 degrees.</li>
* <li>TURBO: Moves the symbol along the Y-axis with a zoom effect.</li>
* </ul>
* </p>
*/
public class SymbolControl extends InitControl {
private boolean zoomingIn = false;
private boolean zoomingOut = false;
private float zoomSpeed = 1f;
private float zoomFactor = 3f;
private float progress = 0;
private BonusCard state;
private float rotationSpeed = 0.8f;
private Quaternion initialRotation = null;
private float y = 5;
/**
* Updates the symbol animation based on the current bonus card state.
* The method calls the corresponding update method for each state (SHIELD, SWAP, TURBO).
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if (state == null) return;
switch (state) {
case SHIELD -> shieldUpdate(tpf);
case SWAP -> swapUpdate(tpf);
case TURBO -> turboUpdate(tpf);
case HIDDEN -> throw new RuntimeException("forbidden state");
}
}
/**
* Updates the symbol when the state is SHIELD. The symbol zooms in and then zooms out.
* When the zooming out finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void shieldUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
if (progress > 1) {
zoomingIn = false;
spatial.removeFromParent();
state = null;
progress = 0;
}
}
}
/**
* Updates the symbol when the state is SWAP. The symbol rotates 360 degrees.
* After the rotation finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void swapUpdate(float tpf) {
if (initialRotation == null) {
initialRotation = spatial.getLocalRotation().clone();
}
progress += tpf * rotationSpeed;
if (progress < 0) return;
float angle = lerp(0, 360, easeInOut(progress));
Quaternion newRotation = new Quaternion();
newRotation.fromAngleAxis((float) Math.toRadians(angle), new Vector3f(0, 1, 0));
spatial.setLocalRotation(initialRotation.mult(newRotation));
if (progress >= 1.2f) {
state = null;
initialRotation = null;
progress = 0;
spatial.removeFromParent();
}
}
/**
* Updates the symbol when the state is TURBO. The symbol moves along the Y-axis with a zoom effect.
* After the movement finishes, the symbol is removed from the parent spatial.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
private void turboUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
float y = lerp(-this.y, 0, easeOut(progress));
spatial.setLocalTranslation(0, y, 0);
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
float y = lerp(0, this.y, easeIn(progress));
spatial.setLocalTranslation(0, y, 0);
if (progress > 1) {
zoomingIn = false;
spatial.removeFromParent();
state = null;
}
}
}
/**
* Starts the SHIELD animation by zooming the symbol in and out.
* The symbol will first zoom in and then zoom out, and will be removed from the parent spatial once done.
*/
public void shield() {
if (state != null) throw new RuntimeException("another state is avtive");
state = BonusCard.SHIELD;
zoomingIn = true;
zoomingOut = false;
progress = 0;
spatial.setLocalScale(1f);
}
/**
* Starts the SWAP animation by rotating the symbol 360 degrees.
* The symbol will rotate once and then be removed from the parent spatial.
*/
public void swap() {
if (state != null) throw new RuntimeException("another state is avtive");
spatial.setLocalScale(3);
state = BonusCard.SWAP;
progress = -0.2f;
}
/**
* Starts the TURBO animation by moving the symbol along the Y-axis.
* The symbol will move upwards and then return to its initial position.
*/
public void turbo() {
if (state != null) throw new RuntimeException("another state is avtive");
spatial.setLocalScale(2);
state = BonusCard.TURBO;
zoomingIn = true;
zoomingOut = false;
progress = 0;
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
/**
* Ease-out function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private static float easeOut(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
/**
* Ease-in-out function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private float easeInOut(float t) {
if (t > 1) t = 1;
return (float) -(Math.cos(Math.PI * t) - 1) / 2;
}
/**
* Ease-in function for smoothing the interpolation.
*
* @param t The interpolation parameter (0 <= t <= 1).
* @return The eased value.
*/
private float easeIn(float t) {
return t * t * t * t;
}
}

View File

@@ -0,0 +1,110 @@
package pp.mdga.client.animation;
import pp.mdga.client.InitControl;
/**
* A control that applies a zoom effect to a spatial, smoothly scaling it in and out.
* The zoom effect can be customized with speed and scaling factor.
*
* <p>The control supports zooming in and out with ease-in and ease-out transitions.
* It starts by zooming in, and once complete, it zooms out, eventually removing the spatial from its parent when the animation ends.</p>
*/
public class ZoomControl extends InitControl {
private boolean zoomingIn = false;
private boolean zoomingOut = false;
private float progress = 0;
private float zoomSpeed = 1f;
private float zoomFactor = 1f;
/**
* Constructs a new ZoomControl with the default zoom speed.
*/
public ZoomControl() {
}
/**
* Constructs a new ZoomControl with a specified zoom speed.
*
* @param speed The speed at which the zoom effect occurs.
*/
public ZoomControl(float speed) {
zoomSpeed = speed;
}
/**
* Initializes the spatial for the zoom effect. This method is called when the control is added to the spatial.
* It sets the zooming state to zooming in.
*/
@Override
protected void initSpatial() {
zoomingIn = true;
}
/**
* Updates the zoom effect over time, either zooming in or zooming out.
*
* @param tpf Time per frame, the time elapsed since the last frame.
*/
@Override
protected void controlUpdate(float tpf) {
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
spatial.setLocalScale(lerp(0, zoomFactor, easeOut(progress)));
if (progress >= 1) {
zoomingIn = false;
zoomingOut = true;
progress = 0;
}
} else if (zoomingOut) {
progress += tpf * zoomSpeed;
spatial.setLocalScale(lerp(zoomFactor, 0, easeIn(progress)));
if (progress > 1) {
zoomingOut = false;
end();
}
}
}
/**
* Ends the zoom animation by removing the spatial from its parent and the control from the spatial.
*/
private void end() {
spatial.removeFromParent();
spatial.removeControl(this);
}
/**
* Performs linear interpolation between two values.
*
* @param start The starting value.
* @param end The target value.
* @param t The interpolation parameter (0 <= t <= 1).
* @return The interpolated value.
*/
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
/**
* Ease-out function for smoothing the zoom-in transition.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The eased value.
*/
private float easeOut(float x) {
return x == 1 ? 1 : (float) (1 - Math.pow(2, -10 * x));
}
/**
* Ease-in function for smoothing the zoom-out transition.
*
* @param x The interpolation parameter (0 <= x <= 1).
* @return The eased value.
*/
private float easeIn(float x) {
return x == 0 ? 0 : (float) Math.pow(2, 10 * x - 10);
}
}

View File

@@ -0,0 +1,8 @@
package pp.mdga.client.board;
import pp.mdga.client.Asset;
/**
* Record for holding Asset information
*/
record AssetOnMap(Asset asset, int x, int y, float rot) {}

View File

@@ -0,0 +1,803 @@
package pp.mdga.client.board;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.animation.MissileAnimation;
import pp.mdga.client.animation.MoveControl;
import pp.mdga.client.animation.JetAnimation;
import pp.mdga.client.gui.DiceControl;
import pp.mdga.game.Color;
import java.util.*;
/**
* BoardHandler is responsible for managing the game board in the MDGA client, including handling
* the initialization, movement, and management of game pieces and assets.
* It works closely with the MdgaApp to create and manipulate 3D models of assets, track player pieces,
* and manage movement across the game board.
*/
public class BoardHandler {
// Constants defining the grid size and elevation of the board
private static final float GRID_SIZE = 1.72f;
private static final float GRID_ELEVATION = 0.0f;
private static final String MAP_NAME = "Maps/map.mdga";
// The main application instance for accessing game assets and logic
private final MdgaApp app;
// Collection of in-game assets and board elements
private ArrayList<NodeControl> infield;
private Map<UUID, PieceControl> pieces;
private Map<Color, List<AssetOnMap>> colorAssetsMap;
private Map<Color, List<NodeControl>> homeNodesMap;
private Map<Color, List<NodeControl>> waitingNodesMap;
private Map<Color, List<PieceControl>> waitingPiecesMap;
private Map<Color, Map<UUID, NodeControl>> waitingNodes;
private Map<UUID, Color> pieceColor;
private final Node rootNodeBoard;
private final Node rootNode;
private final FilterPostProcessor fpp;
private boolean isInitialised;
// Flags and lists for handling piece selection and movement
private List<PieceControl> selectableOwnPieces;
private List<PieceControl> selectableEnemyPieces;
private List<NodeControl> outlineNodes;
private PieceControl selectedOwnPiece;
private PieceControl selectedEnemyPiece;
private DiceControl diceControl;
/**
* Creates a new BoardHandler.
*
* @param app The main application instance
* @param rootNode The root node where the board will be attached
* @param fpp The post-processor for effects like shadows or filters
* @throws RuntimeException if the app is null
*/
public BoardHandler(MdgaApp app, Node rootNode, FilterPostProcessor fpp) {
if(app == null) throw new RuntimeException("app is null");
this.app = app;
this.fpp = fpp;
rootNodeBoard = new Node("Board Root Node");
this.rootNode = rootNode;
isInitialised = false;
}
/**
* Initializes the game board by setting up the pieces and nodes.
*/
public void init() {
isInitialised = true;
selectableOwnPieces = new ArrayList<>();
selectableEnemyPieces = new ArrayList<>();
outlineNodes = new ArrayList<>();
selectedOwnPiece = null;
selectedEnemyPiece = null;
initMap();
rootNode.attachChild(rootNodeBoard);
}
/**
* Shuts down the board handler by detaching all board-related nodes and clearing selected pieces.
*/
public void shutdown(){
clearSelectable();
isInitialised = false;
rootNode.detachChild(rootNodeBoard);
}
/**
* Adds an asset to the map of player assets, ensuring that the player does not have too many assets.
*
* @param col The color of the player
* @param assetOnMap The asset to be added
* @throws RuntimeException if there are too many assets for the player
*/
private void addFigureToPlayerMap(Color col, AssetOnMap assetOnMap) {
List<AssetOnMap> inMap = addItemToMapList(colorAssetsMap, col, assetOnMap);
if (inMap.size() > 4) throw new RuntimeException("to many assets for " + col);
}
/**
* Initializes the map with the assets loaded from the map file and corresponding nodes.
*/
private void initMap() {
pieces = new HashMap<>();
colorAssetsMap = new HashMap<>();
infield = new ArrayList<>(40);
homeNodesMap = new HashMap<>();
waitingNodesMap = new HashMap<>();
waitingPiecesMap = new HashMap<>();
pieceColor = new HashMap<>();
diceControl = new DiceControl(app.getAssetManager());
diceControl.create(new Vector3f(0,0,0), 0.7f, true);
waitingNodes = new HashMap<>();
waitingNodes.put(Color.AIRFORCE, new HashMap<>());
waitingNodes.put(Color.ARMY, new HashMap<>());
waitingNodes.put(Color.NAVY, new HashMap<>());
waitingNodes.put(Color.CYBER, new HashMap<>());
List<AssetOnMap> assetOnMaps = MapLoader.loadMap(MAP_NAME);
for (AssetOnMap assetOnMap : assetOnMaps) {
switch (assetOnMap.asset()) {
case lw -> addFigureToPlayerMap(assetToColor(Asset.lw), assetOnMap);
case heer -> addFigureToPlayerMap(assetToColor(Asset.heer), assetOnMap);
case cir -> addFigureToPlayerMap(assetToColor(Asset.cir), assetOnMap);
case marine -> addFigureToPlayerMap(assetToColor(Asset.marine), assetOnMap);
case node_normal, node_bonus, node_start ->
infield.add(displayAndControl(assetOnMap, new NodeControl(app, fpp)));
case node_home_black -> addHomeNode(homeNodesMap, Color.AIRFORCE, assetOnMap);
case node_home_blue -> addHomeNode(homeNodesMap, Color.NAVY, assetOnMap);
case node_home_green -> addHomeNode(homeNodesMap, Color.ARMY, assetOnMap);
case node_home_yellow -> addHomeNode(homeNodesMap, Color.CYBER, assetOnMap);
case node_wait_black -> addHomeNode(waitingNodesMap, Color.AIRFORCE, assetOnMap);
case node_wait_blue -> addHomeNode(waitingNodesMap, Color.NAVY, assetOnMap);
case node_wait_green -> addHomeNode(waitingNodesMap, Color.ARMY, assetOnMap);
case node_wait_yellow -> addHomeNode(waitingNodesMap, Color.CYBER, assetOnMap);
default -> displayAsset(assetOnMap);
}
}
}
/**
* Converts an asset to its corresponding color.
*
* @param asset The asset to be converted
* @return The color associated with the asset
* @throws RuntimeException if the asset is invalid
*/
private Color assetToColor(Asset asset) {
return switch (asset) {
case lw -> Color.AIRFORCE;
case heer -> Color.ARMY;
case marine -> Color.NAVY;
case cir -> Color.CYBER;
default -> throw new RuntimeException("invalid asset");
};
}
/**
* Creates a 3D model of an asset and adds it to the board.
*
* @param asset The asset to be displayed
* @param pos The position of the asset on the board
* @param rot The rotation of the asset
* @return The Spatial representation of the asset
*/
private Spatial createModel(Asset asset, Vector3f pos, float rot) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
Spatial model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(rot));
model.setLocalTranslation(pos);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
rootNodeBoard.attachChild(model);
return model;
}
/**
* Converts grid coordinates to world space.
*
* @param x The x-coordinate on the grid
* @param y The y-coordinate on the grid
* @return The corresponding world position
*/
public static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
/**
* Displays an asset on the map at the given position with the specified rotation.
*
* @param assetOnMap The asset to be displayed.
* @return A spatial representation of the asset at the specified location and rotation.
*/
private Spatial displayAsset(AssetOnMap assetOnMap) {
int x = assetOnMap.x();
int y = assetOnMap.y();
return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
}
private <T extends AbstractControl> T displayAndControl(AssetOnMap assetOnMap, T control) {
Spatial spatial = displayAsset(assetOnMap);
spatial.addControl(control);
return control;
}
private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl){
pieceControl.setLocation(nodeControl.getLocation());
}
private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap){
List<NodeControl> homeNodes = addItemToMapList(map, color, displayAndControl(assetOnMap, new NodeControl(app, fpp)));
if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
}
/**
* Calculates the rotation angle required to move a piece from one position to another.
*
* @param prev The previous position.
* @param next The target position.
* @return The rotation angle in degrees.
*/
private float getRotationMove(Vector3f prev, Vector3f next) {
Vector3f direction = next.subtract(prev).normalizeLocal();
//I had to reverse dir.y, because then it worked.
float newRot = (float) Math.toDegrees(Math.atan2(direction.x, -direction.y));
if(newRot < 0) newRot += 360;
return newRot;
}
/**
* Recursively moves a piece from its current index to the destination index,
* to keep track of the piece rotation.
*
* @param uuid The UUID of the piece to move.
* @param curIndex The current index of the piece.
* @param moveIndex The target index to move the piece to.
*/
private void movePieceRek(UUID uuid, int curIndex, int moveIndex){
if (curIndex == moveIndex) return;
curIndex = (curIndex + 1) % infield.size();
PieceControl pieceControl = pieces.get(uuid);
NodeControl nodeControl = infield.get(curIndex);
pieceControl.setRotation(getRotationMove(pieceControl.getLocation(),nodeControl.getLocation()));
movePieceToNode(pieceControl, nodeControl);
movePieceRek(uuid, curIndex, moveIndex);
}
private <T, E> List<T> addItemToMapList(Map<E,List<T>> map, E key, T item){
List<T> list = map.getOrDefault(key, new ArrayList<>());
list.add(item);
map.put(key, list);
return list;
}
private <T, E> void removeItemFromMapList(Map<E,List<T>> map, E key, T item){
List<T> list = map.getOrDefault(key, new ArrayList<>());
list.remove(item);
map.put(key, list);
}
private Vector3f getWaitingPos(Color color){
return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
}
/**
* Gets the mean position of a list of vectors.
*
* @param vectors The list of vectors.
* @return The mean position as a Vector3f.
*/
public static Vector3f getMeanPosition(List<Vector3f> vectors) {
if (vectors.isEmpty()) return new Vector3f(0, 0, 0);
Vector3f sum = new Vector3f(0, 0, 0);
for (Vector3f v : vectors) {
sum.addLocal(v);
}
return sum.divide(vectors.size());
}
/**
* Adds a player to the game by associating a color and a list of UUIDs to corresponding assets and waiting nodes.
*
* @param color the color of the player
* @param uuid the list of UUIDs representing the player's assets
* @throws RuntimeException if the number of assets or waiting nodes does not match the provided UUIDs
*/
public void addPlayer(Color color, List<UUID> uuid) {
List<AssetOnMap> playerAssets = colorAssetsMap.get(color);
if (playerAssets == null) throw new RuntimeException("Assets for Player color are not defined");
if (uuid.size() != playerAssets.size()) throw new RuntimeException("UUID array and playerAssets are not the same size");
List<NodeControl> waitNodes = waitingNodesMap.get(color);
if (waitNodes.size() != playerAssets.size()) throw new RuntimeException("waitNodes size does not match playerAssets size");
for (int i = 0; i < playerAssets.size(); i++){
AssetOnMap assetOnMap = playerAssets.get(i);
UUID pieceUuid = uuid.get(i);
// Initialize PieceControl
PieceControl pieceControl = displayAndControl(assetOnMap, new PieceControl(assetOnMap.rot(), app.getAssetManager(), app, fpp));
pieceControl.setRotation(assetOnMap.rot());
// Assign piece to waiting node
NodeControl waitNode = getNextWaitingNode(color);
waitingNodes.get(color).put(pieceUuid, waitNode);
// Move piece to node
movePieceToNode(pieceControl, waitNode);
// Update mappings
pieces.put(pieceUuid, pieceControl);
pieceColor.put(pieceUuid, color);
addItemToMapList(waitingPiecesMap, color, pieceControl);
}
}
/**
* Moves a piece to its corresponding home node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param index the index of the home node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or if the home nodes are not properly defined
*/
private void moveHomePiece(UUID uuid, int index){
Color color = pieceColor.get(uuid);
if(color == null) throw new RuntimeException("uuid is not mapped to a color");
List<NodeControl> homeNodes = homeNodesMap.get(color);
if(homeNodesMap.size() != 4) throw new RuntimeException("HomeNodes for" + color + " are not properly defined");
PieceControl pieceControl = pieces.get(uuid);
NodeControl nodeControl = homeNodes.get(index);
movePieceToNode(pieceControl, nodeControl);
//rotate piece in direction of homeNodes
NodeControl firstHomeNode = homeNodes.get(0);
NodeControl lastHomeNode = homeNodes.get(homeNodes.size()-1);
pieceControl.setRotation(getRotationMove(firstHomeNode.getLocation(), lastHomeNode.getLocation()));
app.getModelSynchronize().animationEnd();
}
/**
* Starts the movement of a piece to a target node based on the given index.
*
* @param uuid the UUID of the piece to move
* @param nodeIndex the index of the target node to move the piece to
* @throws RuntimeException if the UUID is not mapped to a color or the piece control is not found
* @throws IllegalArgumentException if the node index is invalid
*/
private void movePieceStart(UUID uuid, int nodeIndex){
// Farbe des Pieces abrufen
Color color = pieceColor.get(uuid);
if (color == null) throw new RuntimeException("UUID is not mapped to a color");
// PieceControl abrufen
PieceControl pieceControl = pieces.get(uuid);
if (pieceControl == null) throw new RuntimeException("PieceControl not found for UUID: " + uuid);
// Zielknoten abrufen und prüfen
if (nodeIndex < 0 || nodeIndex >= infield.size()) {
throw new IllegalArgumentException("Invalid nodeIndex: " + nodeIndex);
}
NodeControl targetNode = infield.get(nodeIndex);
movePieceToNode(pieceControl, targetNode);
removeItemFromMapList(waitingPiecesMap, color, pieceControl);
waitingNodes.get(color).remove(uuid);
app.getModelSynchronize().animationEnd();
}
/**
* Moves a piece from its current position to the target position based on the given indexes.
*
* @param uuid the UUID of the piece to move
* @param curIndex the current index of the piece
* @param moveIndex the target index of the move
*/
private void movePiece(UUID uuid, int curIndex, int moveIndex){
movePieceRek(uuid, curIndex, moveIndex);
app.getModelSynchronize().animationEnd();
}
/**
* Throws a piece to the next available waiting node and updates the waiting node mapping.
*
* @param uuid the UUID of the piece to throw
* @throws RuntimeException if the UUID is not mapped to a color or if no available waiting nodes are found
*/
private void throwPiece(UUID uuid){
// Farbe des Pieces abrufen
Color color = pieceColor.get(uuid);
if (color == null) throw new RuntimeException("UUID is not mapped to a color");
// PieceControl abrufen
PieceControl pieceControl = pieces.get(uuid);
if (pieceControl == null) throw new RuntimeException("PieceControl not found for UUID: " + uuid);
// Nächste freie Waiting Node abrufen
NodeControl nextWaitNode = getNextWaitingNode(color);
if (nextWaitNode == null) {
throw new IllegalStateException("No available waiting nodes for color: " + color);
}
// Bewegung durchführen
movePieceToNode(pieceControl, nextWaitNode);
// Waiting Nodes aktualisieren
waitingNodes.get(color).put(uuid, nextWaitNode);
// Synchronisation oder Animation
pieceControl.rotateInit();
app.getAcousticHandler().playSound(MdgaSound.LOSE);
app.getModelSynchronize().animationEnd();
}
/**
* Activates the shield for the specified piece.
*
* @param uuid the UUID of the piece to shield
*/
public void shieldPiece(UUID uuid){
pieces.get(uuid).activateShield();
}
/**
* Deactivates the shield for the specified piece.
*
* @param uuid the UUID of the piece to unshield
*/
public void unshieldPiece(UUID uuid){
pieces.get(uuid).deactivateShield();
}
/**
* Suppresses the shield for the specified piece.
*
* @param uuid the UUID of the piece to suppress the shield
*/
public void suppressShield(UUID uuid){
pieces.get(uuid).suppressShield();
}
/**
* Swaps the positions and rotations of two pieces.
*
* @param p1 the first piece to swap
* @param p2 the second piece to swap
* @param loc1 the original location of the first piece
* @param rot1 the original rotation of the first piece
* @param loc2 the original location of the second piece
* @param rot2 the original rotation of the second piece
*/
private void swapPieces(PieceControl p1, PieceControl p2, Vector3f loc1, float rot1, Vector3f loc2, float rot2){
p1.setLocation(loc2);
p2.setLocation(loc1);
p1.setRotation(rot2);
p2.setRotation(rot1);
app.getModelSynchronize().animationEnd();
}
/**
* Outlines the possible move nodes for a list of pieces based on the move indices and whether it's a home move.
*
* @param pieces the list of UUIDs representing the pieces to outline
* @param moveIndexe the list of indices for the target move nodes
* @param homeMoves the list indicating whether the move is a home move
* @throws RuntimeException if the sizes of the input lists do not match
*/
public void outlineMove(List<UUID> pieces, List<Integer> moveIndexe, List<Boolean> homeMoves) {
if(pieces.size() != moveIndexe.size() || pieces.size() != homeMoves.size()) throw new RuntimeException("arrays are not the same size");
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
selectedOwnPiece = null;
selectedEnemyPiece = null;
for (int i = 0; i < pieces.size(); i++) {
UUID uuid = pieces.get(i);
PieceControl pieceControl = this.pieces.get(uuid);
NodeControl nodeControl;
if (homeMoves.get(i)) {
Color color = pieceColor.get(uuid);
nodeControl = homeNodesMap.get(color).get(moveIndexe.get(i));
}
else {
nodeControl = infield.get(moveIndexe.get(i));
}
nodeControl.highlight();
pieceControl.highlight(false);
pieceControl.setHoverable(true);
pieceControl.setSelectable(true);
outlineNodes.add(nodeControl);
selectableOwnPieces.add(pieceControl);
}
}
/**
* Outlines the pieces that can be swapped based on the provided own and enemy pieces.
*
* @param ownPieces the list of UUIDs representing the player's pieces
* @param enemyPieces the list of UUIDs representing the enemy's pieces
*/
public void outlineSwap(List<UUID> ownPieces, List<UUID> enemyPieces){
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
selectedOwnPiece = null;
selectedEnemyPiece = null;
for(UUID uuid : ownPieces) {
PieceControl p = pieces.get(uuid);
p.highlight(false);
p.setHoverable(true);
p.setSelectable(true);
selectableOwnPieces.add(p);
}
for(UUID uuid : enemyPieces) {
PieceControl p = pieces.get(uuid);
p.highlight(true);
p.setHoverable(true);
p.setSelectable(true);
selectableEnemyPieces.add(p);
}
}
/**
* Outlines the pieces that can be shielded based on the provided list of pieces.
*
* @param pieces the list of UUIDs representing the pieces to be shielded
*/
public void outlineShield(List<UUID> pieces){
selectableOwnPieces.clear();
selectableEnemyPieces.clear();
selectedOwnPiece = null;
selectedEnemyPiece = null;
for (UUID uuid : pieces){
PieceControl p = this.pieces.get(uuid);
p.highlight(false);
p.setHoverable(true);
p.setSelectable(true);
selectableOwnPieces.add(p);
}
}
/**
* Selects a piece from either the own or enemy pieces based on the input and deselects others if needed.
*
* @param pieceSelected the PieceControl instance representing the piece selected by the user
*/
public void pieceSelect(PieceControl pieceSelected) {
boolean isSelected = pieceSelected.isSelected();
if(selectableOwnPieces.contains(pieceSelected)){
for(PieceControl p : selectableOwnPieces) {
p.unSelect();
}
if (!isSelected) {
pieceSelected.select();
selectedOwnPiece = pieceSelected;
}
else {
pieceSelected.unSelect();
selectedOwnPiece = null;
}
}
else if(selectableEnemyPieces.contains(pieceSelected)) {
for(PieceControl p : selectableEnemyPieces) {
p.unSelect();
}
if (!isSelected) {
pieceSelected.select();
selectedEnemyPiece = pieceSelected;
}
else {
pieceSelected.unSelect();
selectedEnemyPiece = null;
}
}
else throw new RuntimeException("pieceSelected is not in own/enemySelectablePieces");
app.getModelSynchronize().select(getKeyByValue(pieces, selectedOwnPiece), getKeyByValue(pieces, selectedEnemyPiece));
}
/**
* Clears all highlighted, selectable, and selected pieces and nodes.
*/
public void clearSelectable(){
for(PieceControl p : selectableEnemyPieces) {
p.unSelect();
p.unHighlight();
p.setSelectable(false);
}
for(PieceControl p : selectableOwnPieces) {
p.unSelect();
p.unHighlight();
p.setSelectable(false);
}
for(NodeControl n : outlineNodes){
n.deOutline();
}
outlineNodes.clear();
selectableEnemyPieces.clear();
selectableOwnPieces.clear();
selectedEnemyPiece = null;
selectedOwnPiece = null;
}
/**
* Displays the dice for the specified color at the appropriate position.
*
* @param color the color of the player whose dice should be displayed
*/
public void showDice(Color color){
rootNodeBoard.attachChild(diceControl.getSpatial());
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4)));
diceControl.spin();
}
/**
* Hides the dice from the view.
*/
public void hideDice(){
diceControl.hide();
}
private <K, V> K getKeyByValue(Map<K, V> map, V value) {
for (Map.Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
return entry.getKey();
}
}
return null;
}
/**
* Animates the movement of a piece from its current index to a target index.
*
* @param uuid the UUID of the piece to animate
* @param curIndex the current index of the piece
* @param moveIndex the target index to animate the piece to
*/
public void movePieceAnim(UUID uuid, int curIndex, int moveIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl(
infield.get(curIndex).getLocation(),
infield.get(moveIndex).getLocation(),
()->movePiece(uuid,curIndex,moveIndex)));
}
/**
* Animates the movement of a piece to its home position based on the given home index.
*
* @param uuid the UUID of the piece to animate
* @param homeIndex the index of the home node to move the piece to
*/
public void movePieceHomeAnim(UUID uuid, int homeIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(),
homeNodesMap.get(pieceColor.get(uuid)).get(homeIndex).getLocation(),
()->moveHomePiece(uuid,homeIndex)));
}
/**
* Animates the start of the movement of a piece to a target index.
*
* @param uuid the UUID of the piece to animate
* @param moveIndex the target index to animate the piece to
*/
public void movePieceStartAnim(UUID uuid, int moveIndex){
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(),
infield.get(moveIndex).getLocation(),
()->movePieceStart(uuid, moveIndex)
));
}
/**
* Animates the throwing of a piece to the next available waiting node.
*
* @param uuid the UUID of the piece to animate
*/
public void throwPieceAnim(UUID uuid){
pieces.get(uuid).getSpatial().addControl(new MoveControl(
pieces.get(uuid).getLocation(), getNextWaitingNode(pieceColor.get(uuid)).getLocation(), ()->throwPiece(uuid))
);
}
/**
* Animates the throwing of a piece to the next available waiting node and plays jet animation.
*
* @param uuid the UUID of the piece to animate
*/
public void throwBombAnim(UUID uuid){
Vector3f targetPoint = pieces.get(uuid).getLocation();
JetAnimation anim = new JetAnimation(app, rootNode, uuid, targetPoint, 40, 6);
anim.start();
}
/**
* Animates the throwing of a piece to the next available waiting node and plays ship animation.
*
* @param uuid the UUID of the piece to animate
*/
public void throwMissileAnim(UUID uuid){
Vector3f targetPoint = pieces.get(uuid).getLocation();
MissileAnimation anim = new MissileAnimation(app, rootNode, uuid, targetPoint, 2);
anim.start();
}
/**
* Animates the swapping of two pieces by swapping their positions and rotations.
*
* @param piece1 the UUID of the first piece
* @param piece2 the UUID of the second piece
*/
public void swapPieceAnim(UUID piece1, UUID piece2){
PieceControl piece1Control = pieces.get(piece1);
PieceControl piece2Control = pieces.get(piece2);
Vector3f loc1 = piece1Control.getLocation().clone();
Vector3f loc2 = piece2Control.getLocation().clone();
float rot1 = piece1Control.getRotation();
float rot2 = piece2Control.getRotation();
piece1Control.getSpatial().addControl(new MoveControl(
piece1Control.getLocation().clone(),
piece2Control.getLocation().clone(),
()->{}
));
piece2Control.getSpatial().addControl(new MoveControl(
piece2Control.getLocation().clone(),
piece1Control.getLocation().clone(),
()->swapPieces(piece1Control,piece2Control,loc1,rot1,loc2,rot2)
));
}
/**
* Retrieves the next available waiting node for the specified color.
*
* @param color the color of the player to get the next waiting node for
* @return the next available NodeControl for the specified color
* @throws IllegalStateException if no available waiting nodes are found for the color
*/
private NodeControl getNextWaitingNode(Color color) {
List<NodeControl> nodes = waitingNodesMap.get(color);
if (nodes == null || nodes.isEmpty()) {
throw new IllegalStateException("Keine verfügbaren Warteschleifen-Knoten für die Farbe " + color);
}
for (NodeControl node : nodes) {
if (!waitingNodes.getOrDefault(color, new HashMap<>()).containsValue(node)) {
return node;
}
}
throw new IllegalStateException("Keine freien Nodes im Wartebereich für die Farbe " + color);
}
}

View File

@@ -0,0 +1,182 @@
package pp.mdga.client.board;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.util.SkyFactory;
import com.jme3.util.SkyFactory.EnvMapType;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
/**
* Handles the camera position, rotation, and lighting effects for the game.
* Provides methods for camera initialization, updates based on user input, and shutdown operations.
*/
public class CameraHandler {
MdgaApp app;
private DirectionalLight sun;
private AmbientLight ambient;
private static final int SHADOWMAP_SIZE = 1024 * 8;
private Vector3f defaultCameraPosition;
private Quaternion defaultCameraRotation;
FilterPostProcessor fpp;
DirectionalLightShadowFilter dlsf;
Spatial sky;
private Color ownColor;
private boolean init;
private boolean initRot;
private SSAOFilter ssaoFilter;
private FXAAFilter fxaaFilter;
/**
* Constructor for the CameraHandler. Initializes the camera settings and lighting.
*
* @param app The main application instance that provides the camera and root node.
* @param fpp The FilterPostProcessor used for post-processing effects.
*/
public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
init = false;
initRot = false;
this.app = app;
this.fpp = fpp;
// Save the default camera state
this.defaultCameraPosition = app.getCamera().getLocation().clone();
this.defaultCameraRotation = app.getCamera().getRotation().clone();
sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(0.3f, 0, -1));
ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 1);
dlsf.setLight(sun);
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
dlsf.setShadowIntensity(0.7f);
ssaoFilter = new SSAOFilter(6, 10f, 0.33f, 0.61f);
// ssaoFilter = new SSAOFilter();
fxaaFilter = new FXAAFilter();
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI*1,0,FastMath.HALF_PI*0.2f);
}
/**
* Initializes the camera with a specific color orientation.
* Adds lights, sky, and shadow filters to the scene.
*
* @param ownColor The color that defines the initial camera view angle.
*/
public void init(Color ownColor) {
app.getRootNode().addLight(sun);
app.getRootNode().addLight(ambient);
app.getRootNode().attachChild(sky);
fpp.addFilter(dlsf);
fpp.addFilter(ssaoFilter);
fpp.addFilter(fxaaFilter);
init = true;
initRot = true;
this.ownColor = ownColor;
app.getInputSynchronize().setRotation(getInitAngleByColor(ownColor)*2);
}
/**
* Shuts down the camera handler by removing all lights, sky, and filters,
* and resets the camera position and rotation to its default state.
*/
public void shutdown() {
app.getRootNode().removeLight(sun);
app.getRootNode().removeLight(ambient);
app.getRootNode().detachChild(sky);
// Reset the camera to its default state
app.getCamera().setLocation(defaultCameraPosition);
app.getCamera().setRotation(defaultCameraRotation);
fpp.removeFilter(dlsf);
}
/**
* Updates the camera position and rotation based on user input (scroll and rotation).
* Adjusts the vertical angle and radius based on zoom and rotation values.
*
* @param scroll The scroll input, determining zoom level.
* @param rotation The rotation input, determining camera orientation.
*/
public void update(float scroll, float rotation) {
if(!init) return;
float scrollValue = Math.max(0, Math.min(scroll, 100));
float rotationValue = rotation % 360;
if (rotationValue < 0) {
rotationValue += 360;
}
float radius;
float verticalAngle;
if (scroll < 100f) {
verticalAngle = 20f + (scrollValue / 100f) * 45f;
radius = 30f;
} else {
verticalAngle = 90f;
rotationValue = getAngleByColor(ownColor);
radius = 50f;
}
float verticalAngleRadians = FastMath.DEG_TO_RAD * verticalAngle;
float z = radius * FastMath.sin(verticalAngleRadians);
float x = radius * FastMath.cos(verticalAngleRadians) * FastMath.sin(FastMath.DEG_TO_RAD * rotationValue);
float y = radius * FastMath.cos(verticalAngleRadians) * FastMath.cos(FastMath.DEG_TO_RAD * rotationValue);
Vector3f cameraPosition = new Vector3f(x, y, z);
app.getCamera().setLocation(cameraPosition);
app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Z);
}
/**
* Returns the camera angle based on the specified color.
*
* @param color The color used to determine the camera angle.
* @return The camera angle in degrees.
*/
private float getAngleByColor(Color color){
return switch (color){
case ARMY -> 0;
case AIRFORCE -> 90;
case NAVY -> 270;
case CYBER -> 180;
default -> throw new RuntimeException("None is not allowed");
};
}
/**
* Returns the initial camera angle based on the specified color.
*
* @param color The color used to determine the camera angle.
* @return The initial camera angle in degrees.
*/
private float getInitAngleByColor(Color color){
return (getAngleByColor(color) + 180) % 360;
}
}

View File

@@ -0,0 +1,115 @@
package pp.mdga.client.board;
import pp.mdga.client.Asset;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* A utility class for loading and parsing map data from a file.
* The map contains asset names and coordinates for objects placed on the map.
*/
class MapLoader {
/**
* Private constructor to prevent instantiation.
*/
private MapLoader() {
}
/**
* Loads a map file and parses its contents into a list of assets and their positions.
* Each line in the map file defines an asset, its coordinates, and its rotation.
*
* @param mapName The name of the map file to load. The file is expected to be located in the resources directory.
* @return A list of {@link AssetOnMap} objects representing the assets placed on the map.
* @throws IOException If an error occurs while reading the map file.
* @throws IllegalArgumentException If the map file contains invalid data.
*/
public static List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (
InputStream inputStream = MapLoader.class.getClassLoader().getResourceAsStream(mapName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
) {
while (true) {
String entry = reader.readLine();
if (entry == null) break;
entry = entry.trim();
if (entry.isEmpty()) continue;
if (entry.charAt(0) == '#') continue;
String[] parts = entry.trim().split(" ");
assert (parts.length == 3) : "MapLoader: line has not 3 parts";
String assetName = parts[0];
String[] coordinates = parts[1].split(",");
assert (coordinates.length == 2) : "MapLoade: coordinates are wrong";
int x = Integer.parseInt(coordinates[0]);
int y = Integer.parseInt(coordinates[1]);
float rot = Float.parseFloat(parts[2]);
Asset asset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(asset, x, y, rot));
}
}
catch (IOException e) {
e.printStackTrace();
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
return assetsOnMap;
}
/**
* Returns the corresponding {@link Asset} for a given asset name.
*
* @param assetName The name of the asset to load.
* @return The {@link Asset} associated with the given name.
* @throws IllegalStateException If the asset name is unrecognized.
*/
private static Asset getLoadedAsset(String assetName) {
return switch (assetName) {
case "lw" -> Asset.lw;
case "cir" -> Asset.cir;
case "marine" -> Asset.marine;
case "heer" -> Asset.heer;
case "node" -> Asset.node_normal;
case "node_start" -> Asset.node_start;
case "node_bonus" -> Asset.node_bonus;
case "node_home_blue" -> Asset.node_home_blue;
case "node_home_yellow" -> Asset.node_home_yellow;
case "node_home_black" -> Asset.node_home_black;
case "node_home_green" -> Asset.node_home_green;
case "node_wait_blue" -> Asset.node_wait_blue;
case "node_wait_yellow" -> Asset.node_wait_yellow;
case "node_wait_black" -> Asset.node_wait_black;
case "node_wait_green" -> Asset.node_wait_green;
case "world" -> Asset.world;
case "jet" -> Asset.jet;
case "big_tent" -> Asset.bigTent;
case "small_tent" -> Asset.smallTent;
case "radar" -> Asset.radar;
case "ship" -> Asset.ship;
case "tank" -> Asset.tank;
case "treeSmall" -> Asset.treeSmall;
case "treeBig" -> Asset.treeBig;
default -> throw new IllegalStateException("Unexpected value: " + assetName);
};
}
}

View File

@@ -0,0 +1,48 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
/**
* A control that adds highlighting functionality to a node in the game.
* This class extends {@link OutlineControl} to add an outline effect when the node is highlighted.
*/
public class NodeControl extends OutlineControl {
private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
/**
* Constructs a {@link NodeControl} with the specified application and post processor.
* This constructor sets up the necessary elements for highlighting functionality.
*
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
super(app, fpp);
}
/**
* Returns the location of the node in 3D space.
* This is the node's local translation in the scene.
*
* @return The {@link Vector3f} representing the node's location.
*/
public Vector3f getLocation(){
return this.getSpatial().getLocalTranslation();
}
/**
* Highlights the node by applying an outline effect.
* The outline color and width are predefined as white and 6, respectively.
*/
public void highlight() {
super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
}

View File

@@ -0,0 +1,67 @@
package pp.mdga.client.board;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.InitControl;
import pp.mdga.client.outline.SelectObjectOutliner;
/**
* A control that provides outline functionality to a spatial object.
* This class is responsible for adding an outline effect to a spatial
* object, allowing it to be highlighted or deselected.
*/
public class OutlineControl extends InitControl {
/** The {@link SelectObjectOutliner} responsible for managing the outline effect. */
private final SelectObjectOutliner outlineOwn;
private static final int THICKNESS_DEFAULT = 6;
private MdgaApp app;
public OutlineControl(MdgaApp app, FilterPostProcessor fpp){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera(), app);
}
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thickness){
this.app = app;
outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
/**
* Applies an outline to the spatial object with the given color.
*
* @param color The {@link ColorRGBA} representing the color of the outline.
*/
public void outline(ColorRGBA color){
outlineOwn.select(spatial, color);
}
/**
* Applies an outline to the spatial object with the given color and width.
*
* @param color The {@link ColorRGBA} representing the color of the outline.
* @param width The width of the outline.
*/
public void outline(ColorRGBA color, int width){
deOutline();
outlineOwn.select(spatial, color, width);
}
/**
* Removes the outline effect from the spatial object.
*/
public void deOutline(){
outlineOwn.deselect(spatial);
}
public MdgaApp getApp() {
return app;
}
}

View File

@@ -0,0 +1,281 @@
package pp.mdga.client.board;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
/**
* A control that manages the behavior and properties of a game piece, such as its rotation,
* position, shield activation, and highlighting. This class extends {@link OutlineControl}
* to provide outline functionality and includes additional features like shield effects,
* hover states, and selection states.
*/
public class PieceControl extends OutlineControl {
private final float initRotation;
private final AssetManager assetManager;
private Spatial shieldRing;
private final Material shieldMat;
private static final float SHIELD_SPEED = 1f;
private static final float SHIELD_TRANSPARENCY = 0.6f;
private static final ColorRGBA SHIELD_COLOR = new ColorRGBA(0, 0.9f, 1, SHIELD_TRANSPARENCY);
private static final ColorRGBA SHIELD_SUPPRESSED_COLOR = new ColorRGBA(1f, 0.5f, 0, SHIELD_TRANSPARENCY);
private static final float SHIELD_Z = 0f;
private static final ColorRGBA OUTLINE_OWN_COLOR = ColorRGBA.White;
private static final ColorRGBA OUTLINE_ENEMY_COLOR = ColorRGBA.Red;
private static final ColorRGBA OUTLINE_OWN_HOVER_COLOR = ColorRGBA.Yellow;
private static final ColorRGBA OUTLINE_ENEMY_HOVER_COLOR = ColorRGBA.Green;
private static final ColorRGBA OUTLINE_OWN_SELECT_COLOR = ColorRGBA.Cyan;
private static final ColorRGBA OUTLINE_ENEMY_SELECT_COLOR = ColorRGBA.Orange;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 8;
private static final int OUTLINE_HOVER_WIDTH = 8;
private static final int OUTLINE_SELECT_WIDTH = 10;
private final Node parentNode;
private boolean enemy;
private boolean hoverable;
private boolean highlight;
private boolean selectable;
private boolean select;
/**
* Constructs a {@link PieceControl} with the specified initial rotation, asset manager,
* application, and post-processor.
*
* @param initRotation The initial rotation of the piece in degrees.
* @param assetManager The {@link AssetManager} used for loading models and materials.
* @param app The {@link MdgaApp} instance to use for the application context.
* @param fpp The {@link FilterPostProcessor} to apply post-processing effects.
*/
public PieceControl(float initRotation, AssetManager assetManager, MdgaApp app, FilterPostProcessor fpp){
super(app, fpp);
this.parentNode = new Node();
this.initRotation = initRotation;
this.assetManager = assetManager;
this.shieldRing = null;
this.shieldMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
this.shieldMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
enemy = false;
hoverable = false;
highlight = false;
selectable = false;
select = false;
}
/**
* Gets the current rotation of the piece in degrees.
*
* @return The rotation of the piece in degrees.
*/
public float getRotation() {
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1)));
}
/**
* Sets the rotation of the piece to the specified value in degrees.
*
* @param rot The rotation in degrees to set.
*/
public void setRotation(float rot){
if(rot < 0) rot =- 360;
Quaternion quaternion = new Quaternion();
quaternion.fromAngleAxis((float) Math.toRadians(rot), new Vector3f(0,0,1));
spatial.setLocalRotation(quaternion);
}
/**
* Gets the current location (position) of the piece.
*
* @return The location of the piece as a {@link Vector3f}.
*/
public Vector3f getLocation(){
return spatial.getLocalTranslation();
}
/**
* Updates the piece control every frame. If the shield is active, it will rotate.
*
* @param delta The time difference between frames (time per frame).
*/
@Override
protected void controlUpdate(float delta) {
if(shieldRing != null){
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
}
}
/**
* Sets the location (position) of the piece.
*
* @param loc The location to set as a {@link Vector3f}.
*/
public void setLocation(Vector3f loc){
this.spatial.setLocalTranslation(loc);
}
/**
* Initializes the spatial object and sets its rotation.
* This also moves the spatial to a new parent node for organizational purposes.
*/
@Override
public void initSpatial(){
setRotation(this.initRotation);
Node oldParent = spatial.getParent();
this.parentNode.setName(spatial.getName() + " Parent");
oldParent.detachChild(this.getSpatial());
this.parentNode.attachChild(this.getSpatial());
oldParent.attachChild(this.parentNode);
}
public void rotateInit() {
// rotate(rotation - initRotation);
}
/**
* Activates the shield around the piece.
* This adds a visual shield effect in the form of a rotating ring.
*/
public void activateShield(){
shieldRing = assetManager.loadModel(Asset.shieldRing.getModelPath());
shieldRing.scale(1f);
shieldRing.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(0));
shieldRing.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,0,SHIELD_Z)));
shieldRing.setQueueBucket(RenderQueue.Bucket.Transparent); // Render in the transparent bucket
shieldMat.setColor("Color", SHIELD_COLOR);
shieldRing.setMaterial(shieldMat);
parentNode.attachChild(shieldRing);
}
/**
* Deactivates the shield by removing the shield ring from the scene.
*/
public void deactivateShield(){
parentNode.detachChild(shieldRing);
shieldRing = null;
}
/**
* Suppresses the shield, changing its color to a suppressed state.
*/
public void suppressShield(){
assert(shieldRing != null) : "PieceControl: shieldRing is not set";
shieldMat.setColor("Color", SHIELD_SUPPRESSED_COLOR);
}
public void setMaterial(Material mat){
spatial.setMaterial(mat);
}
public Material getMaterial(){
return ((Geometry) getSpatial()).getMaterial();
}
/**
* Highlights the piece with the appropriate outline color based on whether it is an enemy or not.
*
* @param enemy True if the piece is an enemy, false if it is owned by the player.
*/
public void highlight(boolean enemy) {
this.enemy = enemy;
highlight = true;
super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
/**
* Removes the highlight effect from the piece.
*/
public void unHighlight(){
highlight = false;
deOutline();
}
/**
* Applies a hover effect on the piece if it is hoverable.
*/
public void hover(){
if(!hoverable) return;
super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
}
/**
* Removes the hover effect from the piece.
*/
public void hoverOff(){
if(!hoverable) return;
if(select) select();
else if(highlight) highlight(enemy);
else deOutline();
}
/**
* Deselects the piece and removes the selection outline. If the piece was highlighted,
* it will be re-highlighted. Otherwise, the outline is removed.
*/
public void unSelect(){
select = false;
if(highlight) highlight(enemy);
else deOutline();
}
/**
* Selects the piece and applies the selection outline. If the piece is an enemy, it will
* be outlined with the enemy selection color; otherwise, the own selection color will be used.
*/
public void select(){
if(!selectable) return;
select = true;
super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
}
/**
* Sets whether the piece is selectable.
*
* @param selectable True if the piece can be selected, false otherwise.
*/
public void setSelectable(boolean selectable){
this.selectable = selectable;
}
/**
* Checks if the piece is selected.
*
* @return True if the piece is selected, false otherwise.
*/
public boolean isSelected() { return select; }
/**
* Checks if the piece is selectable.
*
* @return True if the piece is selectable, false otherwise.
*/
public boolean isSelectable() {
return selectable;
}
/**
* Sets whether the piece is hoverable.
*
* @param hoverable True if the piece can be hovered over, false otherwise.
*/
public void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
}

View File

@@ -0,0 +1,165 @@
package pp.mdga.client.button;
import com.jme3.font.BitmapFont;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.jme3.ui.Picture;
import pp.mdga.client.MdgaApp;
/**
* Represents an abstract base class for creating customizable button components in a graphical user interface.
* This class provides the framework for rendering buttons with different visual states, such as normal and pressed,
* and supports position adjustments and font customization.
*
* <p>Subclasses must implement the {@link #show()} and {@link #hide()} methods to define how the button
* is displayed and hidden in the application.</p>
*/
public abstract class AbstractButton {
/**
* Color representing the normal state of the button.
*/
public static final ColorRGBA BUTTON_NORMAL = ColorRGBA.fromRGBA255(233, 236, 239, 255);
/**
* Color representing the pressed state of the button.
*/
public static final ColorRGBA BUTTON_PRESSED = ColorRGBA.fromRGBA255(105, 117, 89, 255);
/**
* Color representing the normal state of the button text.
*/
public static final ColorRGBA TEXT_NORMAL = ColorRGBA.Black;
/**
* Color representing the pressed state of the button text.
*/
public static final ColorRGBA TEXT_PRESSED = ColorRGBA.fromRGBA255(180, 195, 191, 255);
/**
* The image representing the normal state of the button.
*/
protected Picture pictureNormal = new Picture("normalButton");
/**
* The image representing the hover state of the button.
*/
protected Picture pictureHover = new Picture("normalButton");
/**
* The number of horizontal divisions for calculating relative sizes.
*/
public static final float HORIZONTAL = 16;
/**
* The number of vertical divisions for calculating relative sizes.
*/
public static final float VERTICAL = 9;
/**
* The font used for rendering text on the button.
*/
protected BitmapFont font;
/**
* Reference to the application instance for accessing assets and settings.
*/
protected final MdgaApp app;
/**
* Node in the scene graph to which the button belongs.
*/
protected final Node node;
/**
* The position of the button in 2D space.
*/
protected Vector2f pos;
/**
* Factor for scaling the font size.
*/
protected float fontSizeFactor = 1.0f;
/**
* Computed font size based on scaling factor and screen dimensions.
*/
protected float fontSize;
/**
* Computed horizontal step size based on screen dimensions.
*/
protected float horizontalStep;
/**
* Computed vertical step size based on screen dimensions.
*/
protected float verticalStep;
/**
* Computed height step size based on vertical steps.
*/
protected float heightStep;
/**
* Computed width step size based on horizontal steps.
*/
protected float widthStep;
/**
* Flag indicating whether adjustments are applied to the button.
*/
protected boolean adjust = false;
/**
* Constructs an AbstractButton instance with the specified application context and scene node.
* Initializes the button's visual elements and font.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button is attached
*/
public AbstractButton(MdgaApp app, Node node) {
this.app = app;
this.node = node;
pictureNormal.setImage(app.getAssetManager(), "Images/General_Button_normal.png", true);
pictureHover.setImage(app.getAssetManager(), "Images/General_Button_hover.png", true);
font = app.getAssetManager().loadFont("Fonts/Gunplay.fnt");
}
/**
* Displays the button. Implementation must define how the button is rendered on the screen.
*/
public abstract void show();
/**
* Hides the button. Implementation must define how the button is removed from the screen.
*/
public abstract void hide();
/**
* Sets the position of the button in 2D space.
*
* @param pos the position to set
*/
public void setPos(Vector2f pos) {
this.pos = pos;
}
/**
* Calculates relative sizes and dimensions for the button based on the screen resolution.
*/
protected void calculateRelative() {
fontSize = fontSizeFactor * 15 * (float) app.getCamera().getWidth() / 720;
horizontalStep = (float) app.getCamera().getWidth() / HORIZONTAL;
verticalStep = (float) app.getCamera().getHeight() / VERTICAL;
heightStep = verticalStep / 2;
widthStep = horizontalStep / 2;
}
public Vector2f getPos() {
return pos;
}
}

View File

@@ -0,0 +1,46 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import com.jme3.ui.Picture;
/**
* Represents a specific implementation of a clickable button positioned on the left side.
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
* It also includes placeholder methods for handling hover events, which can be customized as needed.
*/
public class ButtonLeft extends ClickButton {
/**
* Constructs a ButtonLeft instance with the specified properties.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label to display on the button
* @param narrowFactor a factor to adjust position of the button
*/
public ButtonLeft(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(0.5f * narrowFactor, 1.8f));
}
/**
* Called when the button is hovered over by the pointer.
* Subclasses can override this method to define specific hover behavior.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button.
* Subclasses can override this method to define specific unhover behavior.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -0,0 +1,48 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
/**
* Represents a specific implementation of a clickable button positioned on the right side.
* This class extends {@link ClickButton} and provides a predefined position and size for the button.
* It includes placeholder methods for handling hover events, which can be customized as needed.
*/
public class ButtonRight extends ClickButton {
/**
* Constructs a ButtonRight instance with the specified properties.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label to display on the button
* @param narrowFactor a factor to adjust the position of the button
*/
public ButtonRight(MdgaApp app, Node node, Runnable action, String label, int narrowFactor) {
super(app, node, action, label, new Vector2f(5, 2), new Vector2f(HORIZONTAL - 0.5f * narrowFactor, 1.8f));
// Enable adjustments specific to this button
adjust = true;
}
/**
* Called when the button is hovered over by the pointer.
* Subclasses can override this method to define specific hover behavior.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button.
* Subclasses can override this method to define specific unhover behavior.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -0,0 +1,251 @@
package pp.mdga.client.button;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
/**
* Represents a button used in a ceremony screen, with 3D model integration, customizable
* appearance based on type, and interactive behavior. The button can rotate and display
* different positions such as FIRST, SECOND, THIRD, and LOST.
*/
public class CeremonyButton extends ClickButton {
/**
* Enum representing the possible positions of the button in the ceremony screen.
*/
public enum Pos {
FIRST,
SECOND,
THIRD,
LOST,
}
/**
* Fixed width of the button in the UI layout.
*/
static final float WIDTH = 4.0f;
/**
* Node to which the 3D model associated with this button is attached.
*/
private final Node node3d;
/**
* Flag to determine if the button's 3D model should rotate.
*/
private boolean rotate = false;
/**
* The 3D model associated with the button.
*/
private Spatial model;
/**
* Current rotation angle of the button's 3D model.
*/
private float rot = 180;
/**
* The taken state of the button (default is NOT taken).
*/
private LobbyButton.Taken taken = LobbyButton.Taken.NOT;
/**
* A label associated with the button for displaying additional information.
*/
private LabelButton label;
/**
* Constructs a CeremonyButton with specified attributes such as type, position, and label.
* The button supports both 2D and 3D components for UI and visual effects.
*
* @param app the application instance for accessing resources and settings
* @param node the node in the scene graph to which the button belongs
* @param node3d the node for 3D scene components associated with this button
* @param tsk the type/color associated with the button
* @param pos the position of the button in the ceremony layout
* @param name the label or name displayed on the button
*/
public CeremonyButton(MdgaApp app, Node node, Node node3d, Color tsk, Pos pos, String name) {
super(app, node, () -> {}, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
this.node3d = node3d;
label = new LabelButton(app, node, name, new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
final float mid = HORIZONTAL / 2;
final float uiSpacing = 1.4f;
final float figSpacingX = 0.9f;
final float figSpacingY = 0.25f;
float uiX = mid;
float uiY = 6;
float figX = 0;
float figY = -0.32f;
Asset asset = switch (tsk) {
case CYBER -> {
instance.setText("CIR");
yield Asset.cir;
}
case AIRFORCE -> {
instance.setText("Luftwaffe");
yield Asset.lw;
}
case ARMY -> {
instance.setText("Heer");
yield Asset.heer;
}
case NAVY -> {
instance.setText("Marine");
yield Asset.marine;
}
default -> throw new RuntimeException("None is not valid");
};
switch (pos) {
case FIRST:
rotate = true;
uiX = 0;
figY -= 1 * figSpacingY;
break;
case SECOND:
adjust = true;
label.adjust = true;
uiX -= uiSpacing;
uiY -= 1;
figX -= figSpacingX;
figY -= 2 * figSpacingY;
figY -= 0.1f;
break;
case THIRD:
uiX += uiSpacing;
uiY -= 1.5f;
figX += figSpacingX;
figY -= 3 * figSpacingY;
figY -= 0.07f;
break;
case LOST:
adjust = true;
label.adjust = true;
uiX -= 2 * uiSpacing + 0.4f;
uiX -= WIDTH / 2;
uiY -= 2.0f;
figX -= 2.5f * figSpacingX + 0.05f;
figY -= 4.5f * figSpacingY;
break;
}
setPos(new Vector2f(uiX, uiY));
label.setPos(new Vector2f(uiX, uiY + 1));
createModel(asset, new Vector3f(figX, figY, 6));
}
/**
* Handles hover behavior by changing the button's background appearance.
*/
@Override
public void onHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
buttonNormal.a = 0.1f;
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
}
/**
* Handles unhover behavior by resetting the button's background appearance.
*/
@Override
public void onUnHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
buttonNormal.a = 0.1f;
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
}
/**
* Displays the button along with its 3D model and associated label.
*/
@Override
public void show() {
release();
calculateRelative();
setRelative();
node.attachChild(instance);
node3d.attachChild(model);
label.show();
}
/**
* Hides the button along with its 3D model and associated label.
*/
@Override
public void hide() {
node.detachChild(instance);
node3d.detachChild(model);
label.hide();
}
/**
* Updates the rotation of the button's 3D model over time.
*
* @param tpf time per frame, used for smooth rotation calculations
*/
public void update(float tpf) {
if (rotate) {
rot += 140.0f * tpf;
rot %= 360;
} else {
rot = 180;
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
/**
* Creates a 3D model associated with the button and applies its materials and position.
*
* @param asset the asset representing the 3D model and texture
* @param pos the initial position of the model in 3D space
*/
private void createModel(Asset asset, Vector3f pos) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
model.setLocalTranslation(pos);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
}
}

View File

@@ -0,0 +1,212 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.ui.Picture;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* Abstract base class for creating interactive buttons with click functionality.
* This class extends {@link AbstractButton} and provides additional behavior such as
* click handling, hover effects, and alignment management.
*/
public abstract class ClickButton extends AbstractButton {
/**
* The action to be executed when the button is clicked.
*/
protected final Runnable action;
/**
* The label or text displayed on the button.
*/
protected String label;
/**
* The size of the button in relative units.
*/
protected Vector2f size;
/**
* The instance of the button being managed.
*/
protected Button instance;
/**
* Constructs a ClickButton with the specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute on button click
* @param label the text label displayed on the button
* @param size the size of the button
* @param pos the position of the button in relative units
*/
ClickButton(MdgaApp app, Node node, Runnable action, String label, Vector2f size, Vector2f pos) {
super(app, node);
this.action = action;
this.label = label;
this.pos = pos;
this.size = size;
instance = new Button(label);
// Add click behavior
instance.addClickCommands((button) -> {
app.getAcousticHandler().playSound(MdgaSound.BUTTON_PRESSED);
action.run();
});
// Set text alignment
instance.setTextHAlignment(HAlignment.Center);
instance.setTextVAlignment(VAlignment.Center);
// Add hover commands
instance.addCommands(Button.ButtonAction.HighlightOn, (button) -> click());
instance.addCommands(Button.ButtonAction.HighlightOff, (button) -> release());
// Set font and colors
instance.setFont(font);
instance.setFocusColor(TEXT_NORMAL);
calculateRelative();
setRelative();
}
/**
* Displays the button by attaching it and its background image to the node.
*/
@Override
public void show() {
node.attachChild(pictureNormal);
release();
calculateRelative();
setRelative();
setImageRelative(pictureNormal);
node.attachChild(instance);
}
/**
* Hides the button by detaching it and its background images from the node.
*/
@Override
public void hide() {
node.detachChild(instance);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
}
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
}
}
/**
* Abstract method to define hover behavior. Must be implemented by subclasses.
*/
protected abstract void onHover();
/**
* Abstract method to define unhover behavior. Must be implemented by subclasses.
*/
protected abstract void onUnHover();
/**
* Handles the button click behavior, including visual feedback and sound effects.
*/
protected void click() {
instance.setColor(TEXT_PRESSED);
instance.setHighlightColor(TEXT_PRESSED);
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_PRESSED);
instance.setBackground(background);
app.getAcousticHandler().playSound(MdgaSound.UI_CLICK);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
setImageRelative(pictureHover);
node.attachChild(pictureHover);
}
onHover();
}
/**
* Resets the button to its normal state after a click or hover event.
*/
protected void release() {
instance.setColor(TEXT_NORMAL);
instance.setHighlightColor(TEXT_NORMAL);
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
instance.setBackground(background);
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
setImageRelative(pictureNormal);
node.attachChild(pictureNormal);
}
onUnHover();
}
/**
* Sets the relative size and position of the button based on screen dimensions.
*/
protected void setRelative() {
instance.setFontSize(fontSize);
instance.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = instance.getPreferredSize().x;
}
instance.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (instance.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - instance.getPreferredSize().y / 2;
if (0 == pos.x) {
instance.setLocalTranslation(horizontalMid, instance.getLocalTranslation().y, instance.getLocalTranslation().z);
}
if (0 == pos.y) {
instance.setLocalTranslation(instance.getLocalTranslation().x, verticalMid, instance.getLocalTranslation().z);
}
}
/**
* Sets the relative size and position of the button's background image.
*
* @param picture the background image to set
*/
protected void setImageRelative(Picture picture) {
if (null == picture) {
return;
}
final float larger = 10;
picture.setWidth(instance.getPreferredSize().x + larger);
picture.setHeight(instance.getPreferredSize().y + larger);
picture.setLocalTranslation(
instance.getLocalTranslation().x - larger / 2,
(instance.getLocalTranslation().y - picture.getHeight()) + larger / 2,
instance.getLocalTranslation().z + 0.01f
);
}
}

View File

@@ -0,0 +1,166 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents an input button with a label and a text field, allowing users to input text.
* The button is designed for graphical user interfaces and supports configurable
* size, position, and character limit.
*/
public class InputButton extends AbstractButton {
/**
* The label associated with the input field, displayed above or beside the text field.
*/
private Label label;
/**
* The text field where users input their text.
*/
private TextField field;
/**
* A container to hold the label and the text field for layout management.
*/
private Container container = new Container();
/**
* The maximum allowed length of the input text.
*/
private final int maxLenght;
/**
* The size of the input button in relative units.
*/
protected Vector2f size;
/**
* Constructs an InputButton with the specified label, character limit, and other properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the input button belongs
* @param label the label displayed with the input field
* @param maxLenght the maximum number of characters allowed in the input field
*/
public InputButton(MdgaApp app, Node node, String label, int maxLenght) {
super(app, node);
this.label = new Label(label);
this.maxLenght = maxLenght;
// Configure label properties
this.label.setColor(TEXT_NORMAL);
// Configure text field properties
field = new TextField("");
field.setColor(TEXT_NORMAL);
field.setTextHAlignment(HAlignment.Left);
field.setTextVAlignment(VAlignment.Center);
// Set background for the text field
QuadBackgroundComponent grayBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
field.setBackground(grayBackground);
// Set fonts for label and text field
this.label.setFont(font);
field.setFont(font);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(5.5f, 1);
// Add components to the container
container.addChild(this.label);
container.addChild(field);
}
/**
* Displays the input button by attaching it to the scene graph node.
*/
@Override
public void show() {
calculateRelative();
setRelative();
node.attachChild(container);
}
/**
* Hides the input button by detaching it from the scene graph node.
*/
@Override
public void hide() {
node.detachChild(container);
}
/**
* Updates the input field, enforcing the character limit.
* Trims the text if it exceeds the maximum allowed length.
*/
public void update() {
String text = field.getText();
int length = text.length();
if (length > maxLenght) {
field.setText(text.substring(0, maxLenght));
}
}
/**
* Adjusts the relative size and position of the input button based on the screen resolution.
*/
protected void setRelative() {
this.label.setFontSize(fontSize);
field.setFontSize(fontSize);
field.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = container.getPreferredSize().x;
}
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
if (0 == pos.x) {
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
}
if (0 == pos.y) {
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
}
}
/**
* Retrieves the text currently entered in the input field.
*
* @return the current text in the input field
*/
public String getString() {
return field.getText();
}
/**
* Sets the text of the input field to the specified string.
*
* @param string the text to set in the input field
*/
public void setString(String string) {
field.setText(string);
}
/**
* Resets the input field by clearing its text.
*/
public void reset() {
field.setText("");
}
}

View File

@@ -0,0 +1,131 @@
package pp.mdga.client.button;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* A specialized button that can function as a label or a clickable button.
* It inherits from {@link ClickButton} and allows for flexible usage with or without button-like behavior.
*/
public class LabelButton extends ClickButton {
/**
* The color of the text displayed on the label or button.
*/
private ColorRGBA text = TEXT_NORMAL;
/**
* The color of the button's background.
*/
private ColorRGBA button = BUTTON_NORMAL;
/**
* Flag indicating whether this component functions as a button.
*/
private boolean isButton;
/**
* Constructs a LabelButton with specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param label the text displayed on the label or button
* @param size the size of the label or button
* @param pos the position of the label or button in relative units
* @param isButton whether this component acts as a button or a simple label
*/
public LabelButton(MdgaApp app, Node node, String label, Vector2f size, Vector2f pos, boolean isButton) {
super(app, node, () -> {}, label, size, pos);
this.isButton = isButton;
// Use the same image for hover and normal states
pictureHover = pictureNormal;
}
/**
* Displays the label or button, attaching it to the scene graph.
* If the component is a button, it also attaches the background image.
*/
@Override
public void show() {
if (isButton) {
node.attachChild(pictureNormal);
}
release();
calculateRelative();
setRelative();
setImageRelative(pictureNormal);
instance.setFontSize(fontSize / 2);
node.attachChild(instance);
}
/**
* Hides the label or button, detaching it from the scene graph.
*/
@Override
public void hide() {
node.detachChild(instance);
if (node.hasChild(pictureNormal)) {
node.detachChild(pictureNormal);
}
if (node.hasChild(pictureHover)) {
node.detachChild(pictureHover);
}
}
/**
* Handles hover behavior, updating the colors of the text and background.
*/
@Override
public void onHover() {
instance.setColor(text);
instance.setHighlightColor(text);
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
instance.setBackground(background);
}
/**
* Handles unhover behavior, restoring the colors of the text and background.
*/
@Override
public void onUnHover() {
instance.setColor(text);
instance.setHighlightColor(text);
QuadBackgroundComponent background = new QuadBackgroundComponent(button);
instance.setBackground(background);
}
/**
* Sets the text displayed on the label or button.
*
* @param text the text to display
*/
public void setText(String text) {
instance.setText(text);
}
/**
* Sets the colors of the text and background, and refreshes the label or button.
*
* @param text the color of the text
* @param button the color of the button's background
*/
public void setColor(ColorRGBA text, ColorRGBA button) {
this.text = text;
this.button = button;
hide();
show();
}
}

View File

@@ -0,0 +1,333 @@
package pp.mdga.client.button;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
/**
* Represents a button in a multiplayer lobby screen. The button supports multiple states
* (not taken, self, other) and displays a 3D model alongside its label. It can also indicate readiness
* and interactively respond to hover and click events.
*/
public class LobbyButton extends ClickButton {
/**
* Enum representing the possible ownership states of the lobby button.
*/
public enum Taken {
NOT, // The button is not taken
SELF, // The button is taken by the user
OTHER // The button is taken by another user
}
/**
* Color for a lobby button that is taken by another user.
*/
static final ColorRGBA LOBBY_TAKEN = ColorRGBA.fromRGBA255(193, 58, 59, 100);
/**
* Color for a lobby button that is ready but not hovered.
*/
static final ColorRGBA LOBBY_READY = ColorRGBA.fromRGBA255(55, 172, 190, 100);
/**
* Color for a lobby button that is ready and hovered.
*/
static final ColorRGBA LOBBY_READY_HOVER = ColorRGBA.fromRGBA255(17, 211, 218, 100);
/**
* Color for a lobby button owned by the user in normal state.
*/
static final ColorRGBA LOBBY_SELF_NORMAL = ColorRGBA.fromRGBA255(0, 151, 19, 100);
/**
* Color for a lobby button owned by the user when hovered.
*/
static final ColorRGBA LOBBY_SELF_HOVER = ColorRGBA.fromRGBA255(0, 230, 19, 100);
/**
* Fixed width for the lobby button.
*/
static final float WIDTH = 4.0f;
/**
* Node to which the 3D model associated with this button is attached.
*/
private final Node node3d;
/**
* Indicates whether the 3D model should rotate.
*/
private boolean rotate = false;
/**
* The 3D model displayed alongside the button.
*/
private Spatial model;
/**
* The rotation angle of the 3D model.
*/
private float rot = 180;
/**
* The current ownership state of the lobby button.
*/
private Taken taken = Taken.NOT;
/**
* Label displayed on the lobby button.
*/
private LabelButton label;
/**
* Indicates whether the button represents a ready state.
*/
private boolean isReady = false;
/**
* Constructs a LobbyButton with specified properties, including a 3D model and label.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param node3d the node for 3D scene components associated with this button
* @param action the action to execute when the button is clicked
* @param tsk the type or category of the button (e.g., CYBER, AIRFORCE)
*/
public LobbyButton(MdgaApp app, Node node, Node node3d, Runnable action, Color tsk) {
super(app, node, action, "", new Vector2f(WIDTH, 7), new Vector2f(0, 0));
this.node3d = node3d;
label = new LabelButton(app, node, "- leer -", new Vector2f(WIDTH, 1), new Vector2f(0, 0), true);
final float mid = HORIZONTAL / 2;
final float uiSpacing = 0.4f;
final float figSpacing = 0.51f;
float uiX = mid;
float figX = 0;
Asset asset = null;
// Configure the button based on its type
switch (tsk) {
case CYBER:
adjust = true;
label.adjust = true;
uiX -= 3 * uiSpacing;
uiX -= WIDTH / 2;
asset = Asset.cir;
figX -= 3 * figSpacing;
instance.setText("CIR");
break;
case AIRFORCE:
adjust = true;
label.adjust = true;
uiX -= uiSpacing;
asset = Asset.lw;
figX -= figSpacing;
instance.setText("Luftwaffe");
break;
case ARMY:
uiX += uiSpacing;
asset = Asset.heer;
figX += figSpacing;
instance.setText("Heer");
break;
case NAVY:
uiX += 3 * uiSpacing;
uiX += WIDTH / 2;
asset = Asset.marine;
figX += 3 * figSpacing;
instance.setText("Marine");
break;
}
setPos(new Vector2f(uiX, 6));
label.setPos(new Vector2f(uiX, 7));
createModel(asset, new Vector3f(figX, -0.55f, 6));
}
/**
* Handles hover behavior, updating the button's color and enabling rotation.
*/
@Override
public void onHover() {
ColorRGBA buttonPressed = BUTTON_PRESSED.clone();
switch (taken) {
case NOT:
buttonPressed.a = 0.3f;
break;
case SELF:
buttonPressed = LOBBY_SELF_HOVER;
break;
case OTHER:
buttonPressed = LOBBY_TAKEN;
break;
}
if (isReady) {
buttonPressed = LOBBY_READY_HOVER;
}
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonPressed);
instance.setBackground(background);
rotate = true;
}
/**
* Handles unhover behavior, restoring the button's color and disabling rotation.
*/
@Override
public void onUnHover() {
ColorRGBA buttonNormal = BUTTON_NORMAL.clone();
switch (taken) {
case NOT:
buttonNormal.a = 0.3f;
break;
case SELF:
buttonNormal = LOBBY_SELF_NORMAL;
break;
case OTHER:
buttonNormal = LOBBY_TAKEN;
break;
}
if (isReady) {
buttonNormal = LOBBY_READY;
}
QuadBackgroundComponent background = new QuadBackgroundComponent(buttonNormal);
instance.setBackground(background);
rotate = false;
}
/**
* Displays the lobby button and its associated components.
*/
@Override
public void show() {
release();
calculateRelative();
setRelative();
node.attachChild(instance);
node3d.attachChild(model);
label.show();
}
/**
* Hides the lobby button and its associated components.
*/
@Override
public void hide() {
node.detachChild(instance);
node3d.detachChild(model);
label.hide();
}
/**
* Updates the 3D model's rotation if the button is being hovered.
*
* @param tpf time per frame, used for smooth rotation calculations
*/
public void update(float tpf) {
if (rotate) {
rot += 140.0f * tpf;
rot %= 360;
} else {
rot = 180;
}
model.setLocalRotation(new Quaternion().fromAngles(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
));
}
/**
* Creates the 3D model associated with the lobby button and applies textures and positioning.
*
* @param asset the asset representing the 3D model
* @param pos the initial position of the 3D model
*/
private void createModel(Asset asset, Vector3f pos) {
String modelName = asset.getModelPath();
String texName = asset.getDiffPath();
model = app.getAssetManager().loadModel(modelName);
model.scale(asset.getSize() / 2);
model.rotate(
(float) Math.toRadians(90),
(float) Math.toRadians(rot),
(float) Math.toRadians(180)
);
model.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
model.setLocalTranslation(pos);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(texName));
model.setMaterial(mat);
}
/**
* Gets the current ownership state of the lobby button.
*
* @return the current state of the button
*/
public Taken getTaken() {
return taken;
}
/**
* Sets the ownership state of the lobby button and updates its label accordingly.
*
* @param taken the new ownership state
* @param name the name to display on the button
*/
public void setTaken(Taken taken, String name) {
this.taken = taken;
if (taken == Taken.NOT) {
label.setText("- leer -");
isReady = false;
} else {
label.setText(name);
}
onUnHover();
}
/**
* Sets the ready state of the lobby button and updates its appearance.
*
* @param isReady whether the button represents a ready state
*/
public void setReady(boolean isReady) {
this.isReady = isReady;
onUnHover();
}
}

View File

@@ -0,0 +1,43 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
/**
* Represents a button used in menu screens for navigation or executing actions.
* Inherits from {@link ClickButton} and provides customizable text and actions.
*/
public class MenuButton extends ClickButton {
/**
* Constructs a MenuButton with specified properties.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
* @param label the text label displayed on the button
*/
public MenuButton(MdgaApp app, Node node, Runnable action, String label) {
super(app, node, action, label, new Vector2f(5.5f, 2), new Vector2f(0, 0));
}
/**
* Called when the button is hovered over. Can be overridden to define hover-specific behavior.
* Currently, no additional behavior is implemented.
*/
@Override
public void onHover() {
// Placeholder for hover behavior
}
/**
* Called when the pointer stops hovering over the button. Can be overridden to define unhover-specific behavior.
* Currently, no additional behavior is implemented.
*/
@Override
public void onUnHover() {
// Placeholder for unhover behavior
}
}

View File

@@ -0,0 +1,52 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import com.simsilica.lemur.HAlignment;
import com.simsilica.lemur.VAlignment;
import com.simsilica.lemur.component.IconComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents a button in the settings menu, designed to display an icon and handle hover effects.
* Inherits from {@link ClickButton} and customizes its behavior for settings functionality.
*/
public class SettingsButton extends ClickButton {
/**
* The icon displayed on the button, which changes based on hover state.
*/
private IconComponent icon;
/**
* Constructs a SettingsButton with a predefined size and position.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the button belongs
* @param action the action to execute when the button is clicked
*/
public SettingsButton(MdgaApp app, Node node, Runnable action) {
super(app, node, action, "", new Vector2f(2, 2), new Vector2f(HORIZONTAL - 0.5f, VERTICAL - 0.5f));
// Enable adjustment for positioning
adjust = true;
pictureNormal.setImage(app.getAssetManager(), "Images/Settings_Button_normal.png", true);
pictureHover.setImage(app.getAssetManager(), "Images/Settings_Button_hover.png", true);
}
/**
* Handles hover behavior by changing the icon to the hover state.
*/
@Override
public void onHover() {
}
/**
* Handles unhover behavior by restoring the icon to the normal state.
*/
@Override
public void onUnHover() {
}
}

View File

@@ -0,0 +1,163 @@
package pp.mdga.client.button;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
/**
* Represents a slider button component with a label, providing functionality for
* adjusting values in a graphical user interface. It includes increment, decrement,
* and thumb buttons, allowing for interactive adjustments.
*/
public class SliderButton extends AbstractButton {
/**
* The label displayed next to the slider.
*/
private Label label;
/**
* The slider component for adjusting values.
*/
private Slider slider;
/**
* A container to hold the label and slider for layout management.
*/
private Container container = new Container();
/**
* The size of the slider button in relative units.
*/
protected Vector2f size;
/**
* Constructs a SliderButton with the specified label.
*
* @param app the application instance for accessing resources
* @param node the node in the scene graph to which the slider button belongs
* @param label the text label displayed alongside the slider
*/
public SliderButton(MdgaApp app, Node node, String label) {
super(app, node);
this.label = new Label(label);
this.label.setColor(TEXT_NORMAL);
// Configure the slider
slider = new Slider("slider");
// Configure decrement button
slider.getDecrementButton().setText(" - ");
slider.getDecrementButton().setFont(font);
slider.getDecrementButton().setFocusColor(TEXT_NORMAL);
slider.getDecrementButton().setTextVAlignment(VAlignment.Bottom);
slider.getDecrementButton().setColor(TEXT_NORMAL);
slider.getDecrementButton().setHighlightColor(TEXT_NORMAL);
// Configure increment button
slider.getIncrementButton().setText(" + ");
slider.getIncrementButton().setFont(font);
slider.getIncrementButton().setFocusColor(TEXT_NORMAL);
slider.getIncrementButton().setTextVAlignment(VAlignment.Bottom);
slider.getIncrementButton().setColor(TEXT_NORMAL);
slider.getIncrementButton().setHighlightColor(TEXT_NORMAL);
// Configure thumb button
slider.getThumbButton().setText("X");
slider.getThumbButton().setFont(font);
slider.getThumbButton().setFocusColor(TEXT_NORMAL);
slider.getThumbButton().setColor(TEXT_NORMAL);
slider.getThumbButton().setHighlightColor(TEXT_NORMAL);
// Set slider background
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
slider.setBackground(background);
// Configure the label font
this.label.setFont(font);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(5.5f, 1);
// Add label and slider to container
container.addChild(this.label);
container.addChild(slider);
}
/**
* Displays the slider button by attaching its container to the scene graph.
*/
@Override
public void show() {
calculateRelative();
setRelative();
node.attachChild(container);
}
/**
* Hides the slider button by detaching its container from the scene graph.
*/
@Override
public void hide() {
node.detachChild(container);
}
/**
* Sets the relative size and position of the slider button based on screen resolution.
*/
protected void setRelative() {
this.label.setFontSize(fontSize);
// Set font sizes for slider components
slider.getDecrementButton().setFontSize(fontSize);
slider.getIncrementButton().setFontSize(fontSize);
slider.getThumbButton().setFontSize(fontSize);
// Set slider size
slider.setPreferredSize(new Vector3f(size.x * widthStep, size.y * heightStep, 0));
float xAdjust = 0.0f;
if (adjust) {
xAdjust = container.getPreferredSize().x;
}
// Set container position
container.setLocalTranslation(pos.x * horizontalStep - xAdjust, pos.y * verticalStep, -1);
final float horizontalMid = ((float) app.getCamera().getWidth() / 2) - (container.getPreferredSize().x / 2);
final float verticalMid = ((float) app.getCamera().getHeight() / 2) - container.getPreferredSize().y / 2;
if (0 == pos.x) {
container.setLocalTranslation(horizontalMid, container.getLocalTranslation().y, -1);
}
if (0 == pos.y) {
container.setLocalTranslation(container.getLocalTranslation().x, verticalMid, -1);
}
}
/**
* Retrieves the current percentage value of the slider.
*
* @return the current percentage value as a float
*/
public float getPercent() {
return (float) slider.getModel().getPercent();
}
/**
* Sets the slider to the specified percentage value.
*
* @param percent the percentage value to set
*/
public void setPercent(float percent) {
slider.getModel().setPercent(percent);
}
}

View File

@@ -0,0 +1,80 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.button.SliderButton;
import pp.mdga.client.view.MdgaView;
public class AudioSettingsDialog extends Dialog {
private final MdgaView view;
private SliderButton mainVolume;
private SliderButton musicVolume;
private SliderButton soundVolume;
private MenuButton backButton;
private boolean active = false;
public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
this.view = view;
mainVolume = new SliderButton(app, node, "Gesamt Lautstärke");
musicVolume = new SliderButton(app, node, "Musik Lautstärke");
soundVolume = new SliderButton(app, node, "Effekt Lautstärke");
backButton = new MenuButton(app, node, view::leaveAudioSettings, "Zurück");
float offset = 2.8f;
mainVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.0f;
musicVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.0f;
soundVolume.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
backButton.setPos(new Vector2f(0, 1.8f));
}
@Override
protected void onShow() {
active = true;
mainVolume.setPercent(app.getAcousticHandler().getMainVolume());
musicVolume.setPercent(app.getAcousticHandler().getMusicVolume());
soundVolume.setPercent(app.getAcousticHandler().getSoundVolume());
backButton.show();
mainVolume.show();
musicVolume.show();
soundVolume.show();
}
@Override
protected void onHide() {
active = false;
backButton.hide();
mainVolume.hide();
musicVolume.hide();
soundVolume.hide();
}
public void update() {
if(!active) {
return;
}
app.getAcousticHandler().setMainVolume(mainVolume.getPercent());
app.getAcousticHandler().setMusicVolume(musicVolume.getPercent());
app.getAcousticHandler().setSoundVolume(soundVolume.getPercent());
}
}

View File

@@ -0,0 +1,105 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import java.util.ArrayList;
public class CeremonyDialog extends Dialog {
private ArrayList<ArrayList<LabelButton>> labels;
float offsetX;
public CeremonyDialog(MdgaApp app, Node node) {
super(app, node);
prepare();
}
@Override
protected void onShow() {
for (ArrayList<LabelButton> row : labels) {
for (LabelButton b : row) {
b.show();
}
}
}
@Override
protected void onHide() {
for (ArrayList<LabelButton> row : labels) {
for (LabelButton b : row) {
b.hide();
}
}
}
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
float offsetYSmall = 0.5f;
ArrayList<LabelButton> row = new ArrayList<>();
Vector2f sizeSmall = new Vector2f(4, 1.2f);
row.add(new LabelButton(app, node, name, sizeSmall, new Vector2f(), true));
row.add(new LabelButton(app, node, "" + v1, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v2, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v3, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v4, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v5, sizeSmall, new Vector2f(), false));
row.add(new LabelButton(app, node, "" + v6, sizeSmall, new Vector2f(), false));
ColorRGBA colorText = AbstractButton.TEXT_NORMAL.clone();
colorText.a = 0.2f;
ColorRGBA colorButton = AbstractButton.BUTTON_NORMAL.clone();
colorButton.a = 0.2f;
int j = 0;
for (LabelButton b : row) {
if(j > 0) {
b.setColor(colorText, colorButton);
}
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetYSmall));
offsetYSmall += 0.8f;
j++;
}
offsetX += 2.3f;
labels.add(row);
}
public void prepare() {
offsetX = 0.5f;
labels = new ArrayList<>();
ArrayList<LabelButton> first = new ArrayList<>();
Vector2f size = new Vector2f(4, 1.2f);
first.add(new LabelButton(app, node, "", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Figuren geworfen", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Figuren verloren", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gespielte Bonuskarten", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gewürfelte 6en", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Gelaufene Felder", size, new Vector2f(), true));
first.add(new LabelButton(app, node, "Bonusfeldern erreicht", size, new Vector2f(), true));
float offsetY = 0.5f;
for (LabelButton b : first) {
b.setPos(new Vector2f(offsetX, MenuButton.VERTICAL - offsetY));
offsetY += 0.8f;
}
offsetX += 2.3f;
labels.add(first);
}
}

View File

@@ -0,0 +1,33 @@
package pp.mdga.client.dialog;
import com.jme3.scene.Node;
import com.simsilica.lemur.Container;
import pp.mdga.client.MdgaApp;
public abstract class Dialog {
protected final MdgaApp app;
protected final Node node = new Node();
private final Node root;
Dialog(MdgaApp app, Node node) {
this.app = app;
this.root = node;
}
public void show() {
root.attachChild(node);
onShow();
}
public void hide() {
root.detachChild(node);
onHide();
}
protected abstract void onShow();
protected abstract void onHide();
}

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.prefs.Preferences;
public class HostDialog extends NetworkDialog {
private InputButton portInput;
private ButtonRight hostButton;
private ButtonLeft backButton;
private final MainView view;
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
public HostDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
this.view = view;
portInput = new InputButton(app, node, "Port: ", 5);
portInput.setString(prefs.get("hostPort", "11111"));
hostButton = new ButtonRight(app, node, view::forward, "Spiel hosten", 10);
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
float offset = 2.8f;
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
}
@Override
protected void onShow() {
portInput.show();
hostButton.show();
backButton.show();
}
@Override
protected void onHide() {
portInput.hide();
hostButton.hide();
backButton.hide();
}
public void update() {
portInput.update();
}
public String getPort() {
prefs.put("hostPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
public void resetPort() {
portInput.reset();
prefs.put("hostPort", "11111");
}
public void hostServer() {
startServer();
}
public void connectServerAsClient() {
connectServer();
}
}

View File

@@ -0,0 +1,63 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import pp.mdga.game.Color;
public class InterruptDialog extends Dialog {
private ButtonRight forceButton;
private LabelButton label;
private String text = "";
public InterruptDialog(MdgaApp app, Node node) {
super(app, node);
forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
float offset = 2.8f;
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
}
@Override
protected void onShow() {
if(app.getGameLogic().isHost()) {
forceButton.show();
}
label.show();
}
@Override
protected void onHide() {
forceButton.hide();
label.hide();
}
public void setColor(Color color) {
switch (color) {
case AIRFORCE:
text = "Luftwaffe";
break;
case ARMY:
text = "Heer";
break;
case NAVY:
text = "Marine";
break;
case CYBER:
text = "CIR";
break;
}
}
}

View File

@@ -0,0 +1,107 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.acoustic.AcousticHandler;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.prefs.Preferences;
public class JoinDialog extends NetworkDialog {
private InputButton ipInput;
private InputButton portInput;
private ButtonRight joinButton;
private ButtonLeft backButton;
private final MainView view;
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
public JoinDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
this.view = view;
ipInput = new InputButton(app, node, "Ip: ", 15);
portInput = new InputButton(app, node, "Port: ", 5);
portInput.setString(prefs.get("joinPort", "11111"));
ipInput.setString(prefs.get("joinIp", ""));
joinButton = new ButtonRight(app, node, view::forward, "Spiel beitreten", 10);
backButton = new ButtonLeft(app, node, view::back, "Zurück", 10);
float offset = 2.8f;
ipInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
portInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.5f;
}
@Override
protected void onShow() {
ipInput.show();
portInput.show();
joinButton.show();
backButton.show();
}
@Override
protected void onHide() {
ipInput.hide();
portInput.hide();
joinButton.hide();
backButton.hide();
}
public void update() {
ipInput.update();
portInput.update();
}
public String getIpt() {
prefs.put("joinIp", ipInput.getString());
setHostname(ipInput.getString());
return ipInput.getString();
}
public void resetIp() {
ipInput.reset();
prefs.put("joinIp", "");
}
public String getPort() {
prefs.put("joinPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
public void resetPort() {
portInput.reset();
prefs.put("joinPort", "11111");
}
public void connectToServer() {
connectServer();
}
public void disconnect() {
NetworkSupport network = getNetwork();
if (network != null) {
try {
network.disconnect();
} catch (Exception e) {
System.err.println("Error while disconnecting: " + e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,103 @@
package pp.mdga.client.dialog;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.NetworkSupport;
import pp.mdga.client.server.MdgaServer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public abstract class NetworkDialog extends Dialog {
private NetworkSupport network;
private String hostname;
private int portNumber;
private Future<Object> connectionFuture;
private MdgaServer serverInstance;
private Thread serverThread;
public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
super(app, node);
this.network = network;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
protected Object initNetwork() {
try {
this.network.initNetwork(this.hostname, this.portNumber);
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void connectServer() {
try {
connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
} catch (NumberFormatException var2) {
throw new NumberFormatException("Port must be a number");
}
}
protected void startServer() {
serverThread = new Thread(() -> {
try {
serverInstance = new MdgaServer(portNumber);
serverInstance.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
serverThread.start();
}
public void shutdownServer() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread was interrupted: " + e.getMessage());
}
if (serverInstance != null) {
serverInstance.shutdown();
serverInstance = null;
}
if (serverThread != null && serverThread.isAlive()) {
serverThread.interrupt();
try {
serverThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
serverThread = null;
}
}
public void update(float delta) {
if (this.connectionFuture != null && this.connectionFuture.isDone()) {
try {
this.connectionFuture.get();
} catch (ExecutionException ignored) {
// TODO: implement
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public NetworkSupport getNetwork() {
return network;
}
}

View File

@@ -0,0 +1,50 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import pp.mdga.client.view.MdgaView;
public class SettingsDialog extends Dialog {
private MenuButton videoButton;
private MenuButton audioButton;
private MenuButton backButton;
private final MdgaView view;
public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
this.view = view;
videoButton = new MenuButton(app, node, view::enterVideoSettings, "Video");
audioButton = new MenuButton(app, node, view::enterAudioSettings, "Audio");
backButton = new MenuButton(app, node, view::leaveSettings, "Zurück");
float offset = 2.8f;
videoButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
audioButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
backButton.setPos(new Vector2f(0, 1.8f));
}
@Override
protected void onShow() {
videoButton.show();
audioButton.show();
backButton.show();
}
@Override
protected void onHide() {
videoButton.hide();
audioButton.hide();
backButton.hide();
}
}

View File

@@ -0,0 +1,291 @@
package pp.mdga.client.dialog;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.InputButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MainView;
import java.util.Random;
import java.util.random.RandomGenerator;
public class StartDialog extends Dialog {
private InputButton nameInput;
private MenuButton hostButton;
private MenuButton joinButton;
private MenuButton endButton;
private final MainView view;
public StartDialog(MdgaApp app, Node node, MainView view) {
super(app, node);
this.view = view;
nameInput = new InputButton(app, node, "Name: ", 16);
hostButton = new MenuButton(app, node, () -> view.forward(true), "Spiel hosten");
joinButton = new MenuButton(app, node, () -> view.forward(false), "Spiel beitreten");
endButton = new MenuButton(app, node, app::stop, "Spiel beenden");
float offset = 2.8f;
nameInput.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.15f;
hostButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
joinButton.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
offset += 1.25f;
endButton.setPos(new Vector2f(0, 1.8f));
}
@Override
protected void onShow() {
nameInput.show();
hostButton.show();
joinButton.show();
endButton.show();
}
@Override
protected void onHide ()
{
nameInput.hide();
hostButton.hide();
joinButton.hide();
endButton.hide();
}
public void update() {
nameInput.update();
}
public String getName() {
String name = nameInput.getString();
if (name == null || name.trim().isEmpty()) {
String[] names = {
"PixelPirat",
"NoobJäger",
"LagMeister",
"KnopfDrücker",
"SpawnCamper",
"AFKHeld",
"RageQuitter",
"GameOverPro",
"Checkpoint",
"RespawnHeld",
"Teebeutel",
"GlitchHexer",
"QuickScope",
"LootSammler",
"EpicLauch",
"KartoffelPro",
"StilleKlinge",
"TastenHeld",
"PixelKrieger",
"HacknSlash",
"JoystickJoe",
"SpawnFalle",
"OneHitWanda",
"CamperKing",
"GameGenie",
"HighPing",
"CheesePro",
"Speedy",
"GigaGamer",
"LevelNoob",
"SkillTobi",
"HeadshotMax",
"PentaPaul",
"CritKarl",
"ManaLeerer",
"Nachlader",
"ClutchKönig",
"FriendlyFe",
"ZonenHeld",
"SchleichKatze",
"ShotgunPro",
"SniperUdo",
"BossHunter",
"HeldenNoob",
"KillFranz",
"FragKarl",
"TeamNiete",
"LootPaul",
"UltraNoob",
"ProfiScout",
"PunkteKlaus",
"KrüppelKill",
"PixelNinja",
"NoobCrusher",
"LagBoss",
"SpawnKing",
"AFKSlayer",
"RespawnPro",
"Killjoy",
"GameBreaker",
"FastFingers",
"LootKing",
"QuickFlick",
"SilentShot",
"HackGod",
"GlitchHero",
"SpeedyBot",
"AimWizard",
"FragMaster",
"OneTapPro",
"KnifeLord",
"MetaHunter",
"PingWarrior",
"KeyBash",
"ClutchPro",
"ScopeBot",
"TrollMage",
"PowerLooter",
"TankHero",
"CampLord",
"SmurfSlayer",
"SkillThief",
"SniperGod",
"LevelHack",
"GhostAim",
"BossTamer",
"ShotgunJoe",
"AimRider",
"KillCount",
"PixelManiac",
"TrollOver",
"SneakPro",
"ReloadKing",
"SpawnTrap",
"LagLover",
"MetaHater",
"BoomMaker",
"WipeLord",
"CarryPro",
"ProBaiter",
"GameWarden",
"KartoffelKönig",
"SaufenderWolf",
"WurstGriller",
"Flitzekacke",
"BratwurstBub",
"Hoppeldoppels",
"BananenMensch",
"KlopapierGuru",
"SchnitzelKing",
"NerdNomade",
"Dönertänzer",
"GlitzerGurke",
"SchinkenShrek",
"KäseKalle",
"SchokoSchnecke",
"KeksKämpfer",
"QuarkPiraten",
"Müslimonster",
"KnuddelNase",
"FantaFighter",
"SchnapsSaurier",
"Wackelpudding",
"ZitronenZock",
"FettWurst",
"PlüschPanda",
"Zuckerschnur",
"FluffiKopf",
"DonutDöner",
"VollpfostenX",
"Schraubenschlüssel",
"Witzepumper",
"ToastTraum",
"FroschFighter",
"KrümelTiger",
"RegenWolke",
"PuddingPower",
"KoffeinKrieger",
"SpeckSchlumpf",
"SuperSuppe",
"BierBärchen",
"FischBär",
"Flauschi",
"Schokomonster",
"ChaosKäse",
"FlitzLappen",
"WurstWombat",
"KrümelMensch",
"PuddingBär",
"ZickZack",
"Schwabel",
"Fluffi",
"RülpsFrosch",
"PommesPapa",
"QuarkBär",
"KnusperKönig",
"ToastBrot",
"Ploppster",
"Schleimschwein",
"Äpfelchen",
"Knallbonbon",
"KaffeeKopf",
"WackelWurst",
"RennKeks",
"BröselBub",
"ZockerBrot",
"BierWurm",
"StinkFlummi",
"SchlumpfKing",
"PurzelBär",
"FlinkFluff",
"PloppPudel",
"Schnorchel",
"FliegenKopf",
"PixelPommes",
"SchwipsWürst",
"WutzBär",
"KnuddelKeks",
"FantaFlumm",
"ZockerKäse",
"LachHäufchen",
"GurkenGuru",
"PonySchnitzel",
"NudelNinja",
"VulkanKeks",
"WasserToast",
"MenschSalat",
"KampfKohlenhydrate",
"SockenZirkus",
"SchwimmBärchen",
"TanzenderDachgepäckträger",
"PizzamarktMensch",
"ZahnarztZocker",
"RollerCoasterTester",
"WaschmaschinenPilot",
"WitzigeZwiebel",
"Pillenschlucker",
"ZwiebelReiter",
"HüpfenderKaktus",
"KochenderAsteroid",
"ChaosKarotte",
"WolkenFurz",
"SchnitzelPartikel",
"WackelBiene",
};
Random random = new Random();
name = names[random.nextInt(names.length)];
}
return name;
}
}

View File

@@ -0,0 +1,116 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.AbstractButton;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import java.util.prefs.Preferences;
public class VideoSettingsDialog extends Dialog {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
private ButtonRight fullscreenButton;
private MenuButton backButton;
private ButtonRight restartButton;
private ButtonLeft hdButton9;
private ButtonLeft fullHdButton9;
private ButtonLeft wqhdButton9;
private ButtonRight hdButton10;
private ButtonRight fullHdButton10;
private ButtonRight wqhdButton10;
private final MdgaView view;
private boolean active = false;
public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
this.view = view;
backButton = new MenuButton(app, node, view::leaveVideoSettings, "Zurück");
restartButton = new ButtonRight(app, node, MdgaApp::restartApp, "Neustart", 1);
fullscreenButton = new ButtonRight(app, node, () -> updateResolution(0, 0, 0, true), "Vollbild", 1);
hdButton9 = new ButtonLeft(app, node, () -> updateResolution(1280, 720, 1.0f, false), "hd 16:9", 10);
fullHdButton9 = new ButtonLeft(app, node, () -> updateResolution(1920, 1080, 2.25f, false), "full hd 16:9", 10);
wqhdButton9 = new ButtonLeft(app, node, () -> updateResolution(2560, 1440, 4.0f, false), "wqhd 16:9", 10);
hdButton10 = new ButtonRight(app, node, () -> updateResolution(1280, 800, 1.0f, false), "hd 16:10", 10);
fullHdButton10 = new ButtonRight(app, node, () -> updateResolution(1920, 1200, 2.25f, false), "full hd 16:10", 10);
wqhdButton10 = new ButtonRight(app, node, () -> updateResolution(2560, 1600, 4.0f, false), "wqhd 16:10", 10);
float offset = 2.8f;
hdButton9.setPos(new Vector2f(hdButton9.getPos().x, MenuButton.VERTICAL - offset));
hdButton10.setPos(new Vector2f(hdButton10.getPos().x, MenuButton.VERTICAL - offset));
fullscreenButton.setPos(new Vector2f(fullscreenButton.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
fullHdButton9.setPos(new Vector2f(fullHdButton9.getPos().x, MenuButton.VERTICAL - offset));
fullHdButton10.setPos(new Vector2f(fullHdButton10.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
wqhdButton9.setPos(new Vector2f(wqhdButton9.getPos().x, MenuButton.VERTICAL - offset));
wqhdButton10.setPos(new Vector2f(wqhdButton10.getPos().x, MenuButton.VERTICAL - offset));
offset += 1.5f;
backButton.setPos(new Vector2f(0, 1.8f));
}
@Override
protected void onShow() {
active = true;
hdButton9.show();
fullHdButton9.show();
wqhdButton9.show();
hdButton10.show();
fullHdButton10.show();
wqhdButton10.show();
fullscreenButton.show();
backButton.show();
}
@Override
protected void onHide() {
active = false;
hdButton9.hide();
fullHdButton9.hide();
wqhdButton9.hide();
hdButton10.hide();
fullHdButton10.hide();
wqhdButton10.hide();
fullscreenButton.hide();
backButton.hide();
restartButton.hide();
}
public void update() {
if(!active) {
return;
}
}
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if(width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
restartButton.show();
}
app.updateResolution(width, height, imageFactor, isFullscreen);
}
}

View File

@@ -0,0 +1,156 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import pp.mdga.client.animation.ZoomControl;
import pp.mdga.game.Color;
class ActionTextHandler {
private Node root;
private BitmapFont font;
private AppSettings appSettings;
private int ranking;
ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
root = new Node("actionTextRoot");
guiNode.attachChild(root);
root.setLocalTranslation(center(appSettings.getWidth(), appSettings.getHeight(), Vector3f.ZERO));
font = assetManager.loadFont("Fonts/Gunplay.fnt");
this.appSettings = appSettings;
ranking = 0;
}
private Node createTextWithSpacing(String[] textArr, float spacing, float size, ColorRGBA[] colorArr) {
if(textArr.length != colorArr.length) throw new RuntimeException("text and color are not the same length");
Node textNode = new Node("TextWithSpacing");
Node center = new Node();
float xOffset = 0;
for(int i = 0; i < textArr.length; i++){
String text = textArr[i];
ColorRGBA color = colorArr[i];
for (char c : text.toCharArray()) {
BitmapText letter = new BitmapText(font);
letter.setColor(color);
letter.setSize(size);
letter.setText(Character.toString(c));
letter.setLocalTranslation(xOffset, letter.getHeight()/2, 0);
center.attachChild(letter);
xOffset += letter.getLineWidth() + spacing;
}
}
center.setLocalTranslation(new Vector3f(-xOffset/2,0,0));
textNode.attachChild(center);
return textNode;
}
private Node createTextWithSpacing(String text, float spacing, float size, ColorRGBA color) {
return createTextWithSpacing(new String[]{text}, spacing, size, new ColorRGBA[]{color});
}
private Vector3f center(float width, float height, Vector3f pos){
return new Vector3f(pos.x+width/2, pos.y+height/2,0);
}
private Node createTopText(String name, float spacing, float size, ColorRGBA color, float top){
return createTopText(new String[]{name}, spacing, size, new ColorRGBA[]{color}, top);
}
private Node createTopText(String[] name, float spacing, float size, ColorRGBA color[], float top){
Node text = createTextWithSpacing(name, spacing, size, color);
text.setLocalTranslation(0, (appSettings.getHeight()/2f)*0.8f-top,0);
root.attachChild(text);
return text;
}
private Vector3f centerText(float width, float height, Vector3f pos){
return center(-width, height, pos);
}
void activePlayer(String name, Color color){
createTopText(new String[]{name," ist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void ownActive(Color color){
createTopText(new String[]{"Du"," bist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void diceNum(int diceNum, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 100);
}
void diceNumMult(int diceNum,int mult, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 100);
}
void ownDice(int diceNum){
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
}
void ownDiceMult(int diceNum, int mult){
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 0);
}
void drawCard(String name, Color color){
createTopText(new String[]{name," erhält eine Bonuskarte"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void drawCardOwn(Color color){
createTopText(new String[]{"Du"," erhälst eine Bonuskarte"}, 5,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void finishText(String name, Color color){
createTopText(new String[]{name," ist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
void finishTextOwn(Color color){
createTopText(new String[]{"Du", " bist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
private ColorRGBA playerColorToColorRGBA(Color color){
return switch (color){
case ARMY -> ColorRGBA.Green;
case NAVY -> ColorRGBA.Blue;
case CYBER -> ColorRGBA.Orange;
case AIRFORCE -> ColorRGBA.Black;
default -> throw new RuntimeException("None is not valid");
};
}
void hide(){
ranking = 0;
root.detachAllChildren();
}
float paddingRanked = 100;
void rollRankingResult(String name, Color color, int eye){
createTopText(new String[]{name,": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
ranking++;
}
void rollRankingResultOwn(Color color, int eye){
createTopText(new String[]{"Du",": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
ranking++;
}
void diceNow(){
createTopText("Klicke zum Würfeln", 5, 80, ColorRGBA.White, 0);
}
}

View File

@@ -0,0 +1,150 @@
package pp.mdga.client.gui;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Sphere;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.board.OutlineControl;
import java.awt.*;
public class CardControl extends OutlineControl {
private static final ColorRGBA OUTLINE_COLOR = ColorRGBA.Yellow;
private static final ColorRGBA HIGHLIGHT_COLOR = ColorRGBA.Yellow;
private static final int HIGHLIGHT_WIDTH = 9;
private static final ColorRGBA HOVER_COLOR = ColorRGBA.Green;
private static final int HOVER_WIDTH = 12;
private static final ColorRGBA SELECT_COLOR = ColorRGBA.Blue;
private static final int SELECT_WIDTH = 13;
private static final int OUTLINE_THICKNESS = 9;
private boolean hoverable;
private boolean highlight;
private boolean selectable;
private boolean select;
private Node root;
private BitmapText num;
public CardControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, Node root){
super(app, fpp, cam, OUTLINE_THICKNESS);
this.root = root;
Node rootNum = createNum();
rootNum.setLocalTranslation(new Vector3f(0.35f,0.8f,0));
root.attachChild(rootNum);
}
private Node createNum(){
Node rootNum = new Node("root Num");
Geometry circle = new Geometry("circle", new Sphere(20,20,1));
circle.setLocalTranslation(new Vector3f(0.03f,0.01f,1));
circle.setLocalScale(0.2f);
Material mat = new Material(getApp().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
circle.setMaterial(mat);
// root.attachChild(circle);
BitmapFont guiFont = getApp().getAssetManager().loadFont("Fonts/Gunplay.fnt");
num = new BitmapText(guiFont);
num.setSize(0.3f);
num.setText("1");
num.setColor(ColorRGBA.White);
num.setLocalTranslation(-num.getLineWidth() / 2, num.getHeight() / 2, 2);
rootNum.attachChild(circle);
rootNum.attachChild(num);
return rootNum;
}
public void setNumCard(int num){
this.num.setText(String.valueOf(num));
}
public Node getRoot() {
return root;
}
@Override
public void initSpatial(){
}
public void outline(){
super.outline(OUTLINE_COLOR);
}
private final static Vector3f HIGHLIGHT_Y = new Vector3f(0,0.4f,0);
public void setHighlight() {
this.highlight = true;
root.setLocalTranslation(root.getLocalTranslation().add(HIGHLIGHT_Y));
highlight();
}
public void highlight() {
super.outline(HIGHLIGHT_COLOR, HIGHLIGHT_WIDTH);
}
public void unHighlight(){
highlight = false;
root.setLocalTranslation(root.getLocalTranslation().subtract(HIGHLIGHT_Y));
deOutline();
}
public void hover(){
if(!hoverable) return;
super.outline(HOVER_COLOR, HOVER_WIDTH);
}
public void hoverOff(){
if(!hoverable) return;
if(select) select();
else if(highlight) highlight();
else deOutline();
}
public void select(){
if(!selectable) return;
select = true;
super.outline(SELECT_COLOR, SELECT_WIDTH);
}
public void unSelect(){
if(!selectable) return;
select = false;
if(highlight) highlight();
else deOutline();
}
public void setSelectable(boolean selectable){
this.selectable = selectable;
}
public boolean isSelected() {
return select;
}
public boolean isSelectable() {
return selectable;
}
public void setHoverable(boolean hoverable) {
this.hoverable = hoverable;
}
}

View File

@@ -0,0 +1,112 @@
package pp.mdga.client.gui;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.ComposeFilter;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import java.util.*;
public class CardLayer extends AbstractAppState {
public static final int SHADOWMAP_SIZE = 1024 * 8;
private Node root;
private Application app;
private boolean init;
private List<Spatial> cardBuffer;
private final FilterPostProcessor fpp;
private final Camera overlayCam;
Texture2D backTexture;
public CardLayer(FilterPostProcessor fpp, Camera overlayCam, Texture2D backTexture) {
this.overlayCam = overlayCam;
this.fpp = fpp;
this.cardBuffer = new ArrayList<>();
init = false;
this.backTexture = backTexture;
}
@Override
public void initialize(AppStateManager stateManager, Application app) {
this.app = app;
root = new Node("Under gui viewport Root");
ViewPort view = app.getRenderManager().createMainView("Under gui ViewPort", overlayCam);
view.setEnabled(true);
view.setClearFlags(true, true, true);
view.attachScene(root);
fpp.setFrameBufferFormat(Image.Format.RGBA8);
fpp.addFilter(new ComposeFilter(backTexture));
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(.5f, -.5f, -1));
root.addLight(sun);
DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 3);
dlsf.setLight(sun);
dlsf.setEnabled(true);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
dlsf.setShadowIntensity(.5f);
fpp.addFilter(dlsf);
view.addProcessor(fpp);
if (!init) init = true;
}
public void shutdown() {
cardBuffer.clear();
root.detachAllChildren();
}
@Override
public void render(RenderManager rm) {
root.updateGeometricState();
}
@Override
public void update(float tpf) {
if (init && !cardBuffer.isEmpty()) {
for (Spatial spatial : cardBuffer) {
root.attachChild(spatial);
}
cardBuffer.clear();
}
root.updateLogicalState(tpf);
}
public void addSpatial(Spatial card) {
if(root == null) cardBuffer.add(card);
else root.attachChild(card);
}
public void deleteSpatial(Spatial spatial) {
root.detachChild(spatial);
}
public Camera getOverlayCam() {
return overlayCam;
}
public Node getRootNode() {
return root;
}
}

View File

@@ -0,0 +1,232 @@
package pp.mdga.client.gui;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.Texture2D;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.animation.SymbolControl;
import pp.mdga.game.BonusCard;
import java.util.*;
public class CardLayerHandler {
private static final Vector3f START = new Vector3f(-1.8f, -3.5f, 0);
private static final Vector3f MARGIN = new Vector3f(1.8f, 0, 0);
private static final float CARDLAYER_CAMERA_ZOOM = 4;
private final MdgaApp app;
private final FilterPostProcessor fpp;
private final Texture2D backTexture;
private Camera cardLayerCamera;
private CardLayer cardLayer;
private DiceControl diceControl;
private final Map<BonusCard, CardControl> bonusCardControlMap = new HashMap<>();
private final Map<BonusCard, Integer> bonusCardIntegerMap = new HashMap<>();
private final Set<CardControl> selectableCards = new HashSet<>();
private BonusCard cardSelect = null;
public CardLayerHandler(MdgaApp app, Texture2D backTexture) {
this.app = app;
this.fpp = new FilterPostProcessor(app.getAssetManager());
this.backTexture = backTexture;
}
public void init() {
cardLayerCamera = createOverlayCam();
cardLayer = new CardLayer(fpp, cardLayerCamera, backTexture);
app.getStateManager().attach(cardLayer);
diceControl = new DiceControl(app.getAssetManager());
diceControl.create(new Vector3f(0, 0, 0), 1f, false);
}
public void shutdown() {
if (cardLayer != null) {
cardLayer.shutdown();
clearSelectableCards();
}
cardLayer = null;
}
public void rollDice(int rollNum, Runnable actionAfter) {
if (!(1 <= rollNum && rollNum <= 6)) throw new RuntimeException("rollNum is not in the range [1,6]");
diceControl.rollDice(rollNum, actionAfter);
}
public void showDice() {
cardLayer.addSpatial(diceControl.getSpatial());
diceControl.spin();
}
public void hideDice() {
diceControl.hide();
}
public void addCard(BonusCard card) {
if (card == BonusCard.HIDDEN) throw new RuntimeException("Can't add hidden card to GUI");
if (!bonusCardControlMap.containsKey(card)) {
CardControl control = createCard(bonusToAsset(card), nextPos());
bonusCardControlMap.put(card, control);
cardLayer.addSpatial(control.getRoot());
}
int newNum = bonusCardIntegerMap.getOrDefault(card, 0) + 1;
bonusCardIntegerMap.put(card, newNum);
bonusCardControlMap.get(card).setNumCard(newNum);
}
public void removeCard(BonusCard card){
if(bonusCardControlMap.containsKey(card)){
bonusCardIntegerMap.put(card, bonusCardIntegerMap.get(card) - 1);
if(bonusCardIntegerMap.get(card) <= 0){
cardLayer.deleteSpatial(bonusCardControlMap.get(card).getRoot());
bonusCardIntegerMap.remove(card);
bonusCardControlMap.remove(card);
}
}
}
public void clearSelectableCards() {
for (CardControl control : selectableCards) {
control.setSelectable(false);
control.setHoverable(false);
control.unHighlight();
control.unSelect();
}
selectableCards.clear();
cardSelect = null;
}
public void setSelectableCards(List<BonusCard> select) {
for (BonusCard card : select) {
selectableCards.add(bonusCardControlMap.get(card));
}
for (CardControl control : selectableCards) {
control.setSelectable(true);
control.setHoverable(true);
control.setHighlight();
}
}
public void selectCard(CardControl cardControl) {
if (cardControl.isSelected()) {
cardControl.unSelect();
cardSelect = null;
} else {
for (CardControl control : selectableCards) {
control.unSelect();
}
cardControl.select();
cardSelect = getKeyByValue(bonusCardControlMap, cardControl);
}
app.getModelSynchronize().selectCard(cardSelect);
}
public Camera getCardLayerCamera() {
return cardLayerCamera;
}
public void shield(){
SymbolControl control = createSymbol(Asset.shieldSymbol);
cardLayer.addSpatial(control.getSpatial());
control.shield();
}
public void swap(){
SymbolControl control = createSymbol(Asset.swapSymbol);
cardLayer.addSpatial(control.getSpatial());
control.swap();
}
public void turbo(){
SymbolControl control = createSymbol(Asset.turboSymbol);
cardLayer.addSpatial(control.getSpatial());
control.turbo();
}
private Asset bonusToAsset(BonusCard card) {
return switch (card) {
case TURBO -> Asset.turboCard;
case SHIELD -> Asset.shieldCard;
case SWAP -> Asset.swapCard;
case HIDDEN -> throw new RuntimeException("HIDDEN is not allowed in GUI");
};
}
private Vector3f nextPos() {
return START.add(MARGIN.mult(bonusCardControlMap.size()));
}
private Camera createOverlayCam() {
Camera originalCam = app.getCamera();
Camera overlayCam = new Camera(originalCam.getWidth(), originalCam.getHeight());
overlayCam.setParallelProjection(true);
float aspect = (float) originalCam.getWidth() / originalCam.getHeight();
float size = CARDLAYER_CAMERA_ZOOM;
overlayCam.setFrustum(-1000, 1000, -aspect * size, aspect * size, size, -size);
overlayCam.setLocation(new Vector3f(0, 0, 10));
overlayCam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
return overlayCam;
}
private <K, V> K getKeyByValue(Map<K, V> map, V value) {
for (Map.Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
return entry.getKey();
}
}
return null;
}
public void test() {
addCard(BonusCard.SHIELD);
addCard(BonusCard.SHIELD);
addCard(BonusCard.TURBO);
addCard(BonusCard.SWAP);
}
private CardControl createCard(Asset card, Vector3f pos){
Node rootCard = new Node("Root Card");
Spatial spatial = app.getAssetManager().loadModel(card.getModelPath());
rootCard.attachChild(spatial);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(card.getDiffPath()));
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
rootCard.setLocalTranslation(pos);
spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
CardControl control = new CardControl(app, fpp, cardLayer.getOverlayCam(), rootCard);
spatial.addControl(control);
return control;
}
private SymbolControl createSymbol(Asset asset){
Spatial spatial = app.getAssetManager().loadModel(asset.getModelPath());
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(asset.getDiffPath()));
spatial.setMaterial(mat);
spatial.setLocalScale(1f);
spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
SymbolControl control = new SymbolControl();
spatial.addControl(control);
return control;
}
public CardLayer getCardLayer(){
return cardLayer;
}
}

View File

@@ -0,0 +1,171 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import java.util.Random;
import static com.jme3.material.Materials.LIGHTING;
import static com.jme3.material.Materials.UNSHADED;
public class DiceControl extends AbstractControl {
private Quaternion targetRotation;
private final Vector3f angularVelocity = new Vector3f();
private float deceleration = 0.5f;
private float timeElapsed = 0.0f;
private float rollDuration = 1f;
private static final int ANGULAR_MIN = 5;
private static final int ANGULAR_MAX = 15;
private static final int ANGULAR_SPIN = 10;
private boolean isRolling = false;
private boolean slerp = false;
private boolean spin = false;
private final AssetManager assetManager;
private Runnable actionAfter;
public DiceControl(AssetManager assetManager){
this.assetManager = assetManager;
}
@Override
protected void controlUpdate(float tpf) {
if (isRolling) {
if(!slerp) {
// Apply rotational velocity to the dice
spinWithAngularVelocity(tpf);
// Gradually reduce rotational velocity (simulate deceleration)
angularVelocity.subtractLocal(
angularVelocity.mult(deceleration * tpf)
);
// Stop rolling when angular velocity is close to zero
if (angularVelocity.lengthSquared() < 3f) {
slerp = true;
}
}
else {
timeElapsed += tpf * rollDuration;
if (timeElapsed > 1.0f) timeElapsed = 1.0f;
Quaternion interpolated = spatial.getLocalRotation().clone();
interpolated.slerp(targetRotation, timeElapsed);
spatial.setLocalRotation(interpolated);
// Stop rolling once duration is complete
if (timeElapsed >= 1.0f) {
isRolling = false;
slerp = false;
actionAfter.run();
}
}
}else if(spin){
spinWithAngularVelocity(tpf);
}
}
private void spinWithAngularVelocity(float tpf){
Quaternion currentRotation = spatial.getLocalRotation();
Quaternion deltaRotation = new Quaternion();
deltaRotation.fromAngles(
angularVelocity.x * tpf,
angularVelocity.y * tpf,
angularVelocity.z * tpf
);
spatial.setLocalRotation(currentRotation.mult(deltaRotation));
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public void rollDice(int diceNum, Runnable actionAfter) {
if (isRolling) return;
spin = false;
slerp = false;
this.actionAfter = actionAfter;
angularVelocity.set(
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX),
FastMath.nextRandomInt(ANGULAR_MIN,ANGULAR_MAX)
);
targetRotation = getRotationForDiceNum(diceNum);
isRolling = true;
}
private Quaternion getRotationForDiceNum(int diceNum) {
return switch (diceNum) {
case 1 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_X);
case 2 -> new Quaternion().fromAngleAxis((float) (1 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 3 -> new Quaternion().fromAngleAxis((float) (0 * (Math.PI / 2)), Vector3f.UNIT_X);
case 4 -> new Quaternion().fromAngleAxis((float) (2 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 5 -> new Quaternion().fromAngleAxis((float) (3 * (Math.PI / 2)), Vector3f.UNIT_Y);
case 6 -> new Quaternion().fromAngleAxis((float) (3 * (Math.PI / 2)), Vector3f.UNIT_X);
default -> throw new IllegalArgumentException("Invalid dice number: " + diceNum);
};
}
public static float lerp(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
public void randomRotation() {
Quaternion randomRotation = new Quaternion();
randomRotation.fromAngles(
FastMath.nextRandomFloat() * FastMath.TWO_PI, // Random X rotation
FastMath.nextRandomFloat() * FastMath.TWO_PI, // Random Y rotation
FastMath.nextRandomFloat() * FastMath.TWO_PI // Random Z rotation
);
spatial.setLocalRotation(randomRotation);
}
public void spin(){
angularVelocity.set(ANGULAR_SPIN,ANGULAR_SPIN,ANGULAR_SPIN);
spin = true;
}
public void hide(){
spatial.removeFromParent();
spin = false;
isRolling = false;
slerp = false;
}
public void create(Vector3f pos, float scale, boolean shadow){
Spatial spatial = assetManager.loadModel(Asset.dice.getModelPath());
Material mat;
if(shadow){
mat = new Material(assetManager, LIGHTING);
mat.setTexture("DiffuseMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
}
else{
mat = new Material(assetManager, UNSHADED);
mat.setTexture("ColorMap", assetManager.loadTexture(Asset.dice.getDiffPath()));
}
spatial.setMaterial(mat);
spatial.setLocalScale(scale);
spatial.setLocalTranslation(pos);
spatial.rotate((float)Math.toRadians(90), (float)Math.toRadians(180), (float)Math.toRadians(180));
spatial.addControl(this);
}
public void setPos(Vector3f pos){
spatial.setLocalTranslation(pos);
}
}

View File

@@ -0,0 +1,171 @@
package pp.mdga.client.gui;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import pp.mdga.client.MdgaApp;
import pp.mdga.game.Color;
import pp.mdga.game.BonusCard;
import java.util.List;
public class GuiHandler {
private final MdgaApp app;
private final CardLayerHandler cardLayerHandler;
private final PlayerNameHandler playerNameHandler;
private final ActionTextHandler actionTextHandler;
private Color ownColor;
private FrameBuffer backFrameBuffer;
public GuiHandler(MdgaApp app, Node guiNode) {
this.app = app;
this.ownColor = ownColor;
backFrameBuffer = new FrameBuffer(app.getCamera().getWidth(), app.getCamera().getHeight(), 1);
Texture2D backTexture = new Texture2D(app.getCamera().getWidth(), app.getCamera().getHeight(), Image.Format.RGBA8);
backFrameBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.Depth));
backFrameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(backTexture));
cardLayerHandler = new CardLayerHandler(app, backTexture);
playerNameHandler = new PlayerNameHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
actionTextHandler = new ActionTextHandler(guiNode, app.getAssetManager(), app.getContext().getSettings());
}
public void init(Color ownColor) {
cardLayerHandler.init();
playerNameHandler.show();
this.ownColor = ownColor;
app.getViewPort().setOutputFrameBuffer(backFrameBuffer);
}
public void shutdown() {
cardLayerHandler.shutdown();
app.getViewPort().setOutputFrameBuffer(null);
}
public void rollDice(int rollNum, int mult) {
cardLayerHandler.rollDice(rollNum, ()->{
if(mult == -1) actionTextHandler.ownDice(rollNum);
else actionTextHandler.ownDiceMult(rollNum, mult);
hideDice();
app.getModelSynchronize().animationEnd();
});
}
public void showRolledDiceMult(int rollNum, int mult, Color color) {
String name = playerNameHandler.getName(color);
if(mult == -1) actionTextHandler.diceNum(rollNum, name, color);
else actionTextHandler.diceNumMult(rollNum, mult, name, color);
}
public void showRolledDice(int rollNum, Color color) {
actionTextHandler.diceNum(rollNum, playerNameHandler.getName(color), color);
}
public void showDice() {
cardLayerHandler.showDice();
actionTextHandler.diceNow();
}
public void hideDice() {
cardLayerHandler.hideDice();
}
//add own handCard
public void addCardOwn(BonusCard card) {
cardLayerHandler.addCard(card);
playerNameHandler.addCard(ownColor);
actionTextHandler.drawCardOwn(ownColor);
}
public void playCardOwn(BonusCard card){
getEffectByCard(card);
cardLayerHandler.removeCard(card);
playerNameHandler.removeCard(ownColor);
}
public void playCardEnemy(Color color, BonusCard card) {
getEffectByCard(card);
playerNameHandler.removeCard(color);
}
private void getEffectByCard(BonusCard bonus){
switch(bonus){
case SWAP -> swap();
case TURBO -> turbo();
case SHIELD -> shield();
default -> throw new RuntimeException("invalid card");
}
}
public void clearSelectableCards() {
cardLayerHandler.clearSelectableCards();
}
public void setSelectableCards(List<BonusCard> select) {
cardLayerHandler.setSelectableCards(select);
}
public void selectCard(CardControl cardControl) {
cardLayerHandler.selectCard(cardControl);
}
public Camera getCardLayerCamera() {
return cardLayerHandler.getCardLayerCamera();
}
public Node getCardLayerRootNode(){
return cardLayerHandler.getCardLayer().getRootNode();
}
public void addPlayer(Color color, String name) {
playerNameHandler.addPlayer(color, name, color == ownColor);
}
public void setActivePlayer(Color color) {
playerNameHandler.setActivePlayer(color);
if (ownColor == color) actionTextHandler.ownActive(color);
else actionTextHandler.activePlayer(playerNameHandler.getName(color), color);
}
public void shield(){
cardLayerHandler.shield();
}
public void swap(){
cardLayerHandler.swap();
}
public void turbo(){
cardLayerHandler.turbo();
}
public void hideText(){
actionTextHandler.hide();
}
//addCard Enemy (DrawCardNotification)
public void drawCard(Color color) {
//Color != ownColor
actionTextHandler.drawCard(playerNameHandler.getName(color), color);
playerNameHandler.addCard(color);
}
public void finish(Color color){
if(ownColor == color) actionTextHandler.finishTextOwn(color);
else actionTextHandler.finishText(playerNameHandler.getName(color), color);
}
public void rollRankingResult(Color color, int eye){
if(ownColor == color) actionTextHandler.rollRankingResultOwn(color, eye);
else actionTextHandler.rollRankingResult(playerNameHandler.getName(color), color, eye);
}
}

View File

@@ -0,0 +1,171 @@
package pp.mdga.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.ui.Picture;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
public class PlayerNameHandler {
private final BitmapFont playerFont;
private final Node playerNameNode;
private final List<Color> playerOrder;
private final Map<Color, String> colorNameMap;
private final Map<Color, Integer> colorCardMap;
private final AppSettings appSettings;
private final AssetManager assetManager;
private Color ownColor;
private static final float PADDING_TOP = 35;
private static final float PADDING_LEFT = 50;
private static final float MARGIN_NAMES = 50;
private static final float IMAGE_SIZE = 50;
private static final float TEXT_SIZE = 28;
private static final ColorRGBA NORMAL_COLOR = ColorRGBA.White;
private static final ColorRGBA ACTIVE_COLOR = ColorRGBA.Blue;
private static final ColorRGBA OWN_COLOR = ColorRGBA.Cyan;
private final Node guiNode;
public PlayerNameHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
this.guiNode = guiNode;
playerFont = assetManager.loadFont("Fonts/Gunplay.fnt");
playerNameNode = new Node("player name node");
playerOrder = new ArrayList<>();
colorNameMap = new HashMap<>();
colorCardMap = new HashMap<>();
this.appSettings = appSettings;
this.assetManager = assetManager;
}
public void show() {
guiNode.attachChild(playerNameNode);
}
public void hide() {
guiNode.detachChild(playerNameNode);
}
private void drawPlayers(){
playerNameNode.detachAllChildren();
for(int i = 0; i < playerOrder.size(); i++) {
Color color = playerOrder.get(i);
if(!colorNameMap.containsKey(color)) throw new RuntimeException(color + " isn't mapped to a name");
Node nameParent = new Node("nameParent");
nameParent.attachChild(createColor(color));
BitmapText name = createName(colorNameMap.get(color), i == 0, color == ownColor);
nameParent.attachChild(name);
if(colorCardMap.getOrDefault(color, 0) > 0){
Picture pic = createHandCard(name.getLineWidth());
nameParent.attachChild(pic);
nameParent.attachChild(createCardNum(colorCardMap.get(color), pic.getWidth(), pic.getLocalTranslation().getX()));
}
nameParent.setLocalTranslation(50,appSettings.getWindowHeight()-PADDING_TOP- MARGIN_NAMES *i,0);
playerNameNode.attachChild(nameParent);
}
}
private Spatial createCardNum(int num, float lastWidth, float lastX ) {
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(NORMAL_COLOR);
hudText.setText(String.valueOf(num));
hudText.setLocalTranslation(lastX + lastWidth + 20,hudText.getHeight()/2, 0);
return hudText;
}
private Picture createHandCard(float width) {
Picture pic = new Picture("HUD Picture");
pic.setImage(assetManager, "./Images/handcard.png", true);
pic.setWidth(IMAGE_SIZE);
pic.setHeight(IMAGE_SIZE);
pic.setPosition(-pic.getWidth()/2 + width + PADDING_LEFT * 2 ,-pic.getHeight()/2);
return pic;
}
private String imagePath(Color color){
String root = "./Images/name_pictures/";
return switch(color){
case ARMY -> root+"HEER_IMAGE.png";
case NAVY -> root+"MARINE_IMAGE.png";
case CYBER -> root+"CIR_IMAGE.png";
case AIRFORCE -> root+"LW_IMAGE.png";
default -> throw new RuntimeException("None is not valid");
};
}
private Spatial createColor(Color color) {
Picture pic = new Picture("HUD Picture");
pic.setImage(assetManager, imagePath(color), true);
pic.setWidth(IMAGE_SIZE);
pic.setHeight(IMAGE_SIZE);
pic.setPosition(-pic.getWidth()/2,-pic.getHeight()/2);
return pic;
}
private BitmapText createName(String name, boolean first, boolean own){
BitmapText hudText = new BitmapText(playerFont);
//renderedSize = 45
hudText.setSize(TEXT_SIZE);
hudText.setColor(first ? ACTIVE_COLOR : own ? OWN_COLOR : NORMAL_COLOR);
hudText.setText(name);
hudText.setLocalTranslation(PADDING_LEFT,hudText.getHeight()/2, 0);
return hudText;
}
public void addPlayer(Color color, String name, boolean own){
if(own) ownColor = color;
colorNameMap.put(color, name);
playerOrder.add(color);
drawPlayers();
}
public void setActivePlayer(Color color) {
Color lastFirst = playerOrder.remove(0);
playerOrder.remove(color);
playerOrder.add(0, color);
playerOrder.add(lastFirst);
drawPlayers();
}
public String getName(Color color){
if(!colorNameMap.containsKey(color)) throw new RuntimeException("color is not in colorNameMap");
return colorNameMap.get(color);
}
public void addCard(Color color){
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) + 1);
drawPlayers();
}
public void removeCard(Color color){
if(colorCardMap.containsKey(color)){
colorCardMap.put(color, colorCardMap.getOrDefault(color, 0) - 1);
if(colorCardMap.get(color) <= 0) colorCardMap.remove(color);
}
drawPlayers();
}
}

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
public class OutlineFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
public OutlineFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/Outline.j3md");
material = new Material(matDef);
material.setVector2("Resolution", new Vector2f(w, h));
material.setColor("OutlineColor", outlineColor);
material.setFloat("OutlineWidth", outlineWidth);
}
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
// System.out.println("OutlineFilter.preFrame()");
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
// material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
// System.out.println("OutlineFilter.postFrame()");
}
@Override
protected Material getMaterial() {
return material;
}
public ColorRGBA getOutlineColor() {
return outlineColor;
}
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
public float getOutlineWidth() {
return outlineWidth;
}
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -0,0 +1,67 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
public class OutlinePreFilter extends Filter {
private Pass normalPass;
private RenderManager renderManager;
/**
* Creates a OutlinePreFilter
*/
public OutlinePreFilter() {
super("OutlinePreFilter");
}
@Override
protected boolean isRequiresDepthTexture() {
return true;
}
@Override
protected void postQueue(RenderQueue queue) {
Renderer r = renderManager.getRenderer();
r.setFrameBuffer(normalPass.getRenderFrameBuffer());
renderManager.getRenderer().clearBuffers(true, true, false);
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
}
@Override
protected Material getMaterial() {
return material;
}
public Texture getOutlineTexture() {
return normalPass.getRenderedTexture();
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
this.renderManager = renderManager;
normalPass = new Pass();
normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
material = new Material(manager, "MatDefs/SelectObjectOutliner/OutlinePre.j3md");
}
@Override
protected void cleanUpFilter(Renderer r) {
normalPass.cleanup(r);
}
}

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.MaterialDef;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
public class OutlineProFilter extends Filter {
private OutlinePreFilter outlinePreFilter;
private ColorRGBA outlineColor = new ColorRGBA(0, 1, 0, 1);
private float outlineWidth = 1;
public OutlineProFilter(OutlinePreFilter outlinePreFilter) {
super("OutlineFilter");
this.outlinePreFilter = outlinePreFilter;
}
@Override
protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) {
MaterialDef matDef = (MaterialDef) assetManager.loadAsset("MatDefs/SelectObjectOutliner/OutlinePro.j3md");
material = new Material(matDef);
material.setVector2("Resolution", new Vector2f(w, h));
material.setColor("OutlineColor", outlineColor);
material.setFloat("OutlineWidth", outlineWidth);
}
@Override
protected void preFrame(float tpf) {
super.preFrame(tpf);
material.setTexture("OutlineDepthTexture", outlinePreFilter.getOutlineTexture());
// System.out.println("OutlineFilter.preFrame()");
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer);
// material.setTexture("OutlineDepthTexture", outlinePreFilter.getDefaultPassDepthTexture());
// System.out.println("OutlineFilter.postFrame()");
}
@Override
protected Material getMaterial() {
return material;
}
public ColorRGBA getOutlineColor() {
return outlineColor;
}
public void setOutlineColor(ColorRGBA outlineColor) {
this.outlineColor = outlineColor;
if (material != null) {
material.setColor("OutlineColor", outlineColor);
}
}
public float getOutlineWidth() {
return outlineWidth;
}
public void setOutlineWidth(float outlineWidth) {
this.outlineWidth = outlineWidth;
if (material != null) {
material.setFloat("OutlineWidth", outlineWidth);
}
}
public OutlinePreFilter getOutlinePreFilter() {
return outlinePreFilter;
}
}

View File

@@ -0,0 +1,88 @@
package pp.mdga.client.outline;
import com.jme3.asset.AssetManager;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import pp.mdga.client.MdgaApp;
public class SelectObjectOutliner {
private final FilterPostProcessor fpp;
private final RenderManager renderManager;
private final AssetManager assetManager;
private final Camera cam;
private final int width;
private boolean selected;
private ViewPort outlineViewport = null;
// private OutlineFilter outlineFilter = null;
private OutlineProFilter outlineFilter = null;
private final MdgaApp app;
public SelectObjectOutliner(int width, FilterPostProcessor fpp, RenderManager renderManager, AssetManager assetManager, Camera cam, MdgaApp app) {
this.selected = false;
this.fpp = fpp;
this.renderManager = renderManager;
this.assetManager = assetManager;
this.cam = cam;
this.width = width;
this.app = app;
}
public void deselect(Spatial model) {
if(selected){
selected = false;
hideOutlineFilterEffect(model);
}
}
public void select(Spatial model, ColorRGBA color) {
if(!selected){
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
public void select(Spatial model, ColorRGBA color, int width) {
if(!selected){
selected = true;
showOutlineFilterEffect(model, width, color);
}
}
private void hideOutlineFilterEffect(Spatial model) {
// app.enqueue(() -> {
outlineFilter.setEnabled(false);
outlineFilter.getOutlinePreFilter().setEnabled(false);
fpp.removeFilter(outlineFilter);
outlineViewport.detachScene(model);
outlineViewport.clearProcessors();
renderManager.removePreView(outlineViewport);
outlineViewport = null;
// return null;
// });
}
private void showOutlineFilterEffect(Spatial model, int width, ColorRGBA color) {
// app.enqueue(() -> {
outlineViewport = renderManager.createPreView("outlineViewport", cam);
FilterPostProcessor outlineFpp = new FilterPostProcessor(assetManager);
OutlinePreFilter outlinePreFilter = new OutlinePreFilter();
outlineFpp.addFilter(outlinePreFilter);
outlineViewport.attachScene(model);
outlineViewport.addProcessor(outlineFpp);
outlineFilter = new OutlineProFilter(outlinePreFilter);
outlineFilter.setOutlineColor(color);
outlineFilter.setOutlineWidth(width);
fpp.addFilter(outlineFilter);
// return null;
// });
}
}

View File

@@ -0,0 +1,327 @@
package pp.mdga.client.server;
import com.jme3.network.*;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.serializing.serializers.EnumSerializer;
import pp.mdga.Resources;
import pp.mdga.game.*;
import pp.mdga.game.card.*;
import pp.mdga.message.client.*;
import pp.mdga.message.server.*;
import pp.mdga.server.ServerGameLogic;
import pp.mdga.server.ServerSender;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.LogManager;
/**
* Server implementing the visitor pattern as MessageReceiver for ClientMessages
*/
public class MdgaServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
private static final Logger LOGGER = System.getLogger(MdgaServer.class.getName());
private Server myServer;
private static int port;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
try {
manager.readConfiguration(new FileInputStream("logging.properties"));
LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
} catch (IOException e) {
LOGGER.log(Level.INFO, e.getMessage());
}
}
/**
* Constructor.
*
* @param port as the port for this server
*/
public MdgaServer(int port) {
MdgaServer.port = port;
LOGGER.log(Level.INFO, "Creating MdgaServer"); //NON-NLS
logic = new ServerGameLogic(this, new Game());
}
/**
*
*/
public void run() {
startServer();
while (true)
processNextMessage();
}
/**
*
*/
private void startServer() {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(port);
initializeSerializables();
myServer.start();
registerListeners();
LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
} catch (IOException e) {
LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
exit(1);
}
}
private void processNextMessage() {
try {
pendingMessages.take().process(logic);
} catch (InterruptedException ex) {
LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
Thread.currentThread().interrupt();
}
}
private void initializeSerializables() {
Serializer.registerClass(UUID.class, new UUIDSerializer());
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(ClientStartGameMessage.class);
Serializer.registerClass(DeselectTSKMessage.class);
Serializer.registerClass(ForceContinueGameMessage.class);
Serializer.registerClass(StartGameMessage.class);
Serializer.registerClass(JoinedLobbyMessage.class);
Serializer.registerClass(LeaveGameMessage.class);
Serializer.registerClass(LobbyNotReadyMessage.class);
Serializer.registerClass(LobbyReadyMessage.class);
Serializer.registerClass(NoPowerCardMessage.class);
Serializer.registerClass(RequestBriefingMessage.class);
Serializer.registerClass(RequestDieMessage.class);
Serializer.registerClass(RequestMoveMessage.class);
Serializer.registerClass(RequestPlayCardMessage.class);
Serializer.registerClass(SelectCardMessage.class);
Serializer.registerClass(SelectedPiecesMessage.class);
Serializer.registerClass(SelectTSKMessage.class);
Serializer.registerClass(ActivePlayerMessage.class);
Serializer.registerClass(AnyPieceMessage.class);
Serializer.registerClass(BriefingMessage.class);
Serializer.registerClass(CeremonyMessage.class);
Serializer.registerClass(DieMessage.class);
Serializer.registerClass(DiceAgainMessage.class);
Serializer.registerClass(DiceNowMessage.class);
Serializer.registerClass(EndOfTurnMessage.class);
Serializer.registerClass(LobbyAcceptMessage.class);
Serializer.registerClass(LobbyDenyMessage.class);
Serializer.registerClass(LobbyPlayerJoinedMessage.class);
Serializer.registerClass(LobbyPlayerLeaveMessage.class);
Serializer.registerClass(MoveMessage.class);
Serializer.registerClass(NoTurnMessage.class);
Serializer.registerClass(PauseGameMessage.class);
Serializer.registerClass(PlayCardMessage.class);
Serializer.registerClass(PossibleCardsMessage.class);
Serializer.registerClass(PossiblePieceMessage.class);
Serializer.registerClass(RankingResponseMessage.class);
Serializer.registerClass(RankingRollAgainMessage.class);
Serializer.registerClass(ReconnectBriefingMessage.class);
Serializer.registerClass(ResumeGameMessage.class);
Serializer.registerClass(ServerStartGameMessage.class);
Serializer.registerClass(ShutdownMessage.class);
Serializer.registerClass(StartPieceMessage.class);
Serializer.registerClass(UpdateReadyMessage.class);
Serializer.registerClass(UpdateTSKMessage.class);
Serializer.registerClass(WaitPieceMessage.class);
Serializer.registerClass(IncorrectRequestMessage.class);
Serializer.registerClass(Player.class);
Serializer.registerClass(Statistic.class);
Serializer.registerClass(Board.class);
Serializer.registerClass(Node.class);
Serializer.registerClass(Piece.class);
Serializer.registerClass(BonusNode.class);
Serializer.registerClass(StartNode.class);
Serializer.registerClass(HomeNode.class);
Serializer.registerClass(PowerCard.class);
Serializer.registerClass(TurboCard.class);
Serializer.registerClass(SwapCard.class);
Serializer.registerClass(ShieldCard.class);
Serializer.registerClass(HiddenCard.class);
Serializer.registerClass(Color.class, new EnumSerializer());
Serializer.registerClass(PieceState.class, new EnumSerializer());
Serializer.registerClass(ShieldState.class, new EnumSerializer());
Serializer.registerClass(BonusCard.class, new EnumSerializer());
}
private void registerListeners() {
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addMessageListener(this, ClientStartGameMessage.class);
myServer.addMessageListener(this, DeselectTSKMessage.class);
myServer.addMessageListener(this, DisconnectedMessage.class);
myServer.addMessageListener(this, ForceContinueGameMessage.class);
myServer.addMessageListener(this, StartGameMessage.class);
myServer.addMessageListener(this, JoinedLobbyMessage.class);
myServer.addMessageListener(this, LeaveGameMessage.class);
myServer.addMessageListener(this, LobbyNotReadyMessage.class);
myServer.addMessageListener(this, LobbyReadyMessage.class);
myServer.addMessageListener(this, NoPowerCardMessage.class);
myServer.addMessageListener(this, RequestBriefingMessage.class);
myServer.addMessageListener(this, RequestDieMessage.class);
myServer.addMessageListener(this, RequestMoveMessage.class);
myServer.addMessageListener(this, RequestPlayCardMessage.class);
myServer.addMessageListener(this, SelectCardMessage.class);
myServer.addMessageListener(this, SelectedPiecesMessage.class);
myServer.addMessageListener(this, SelectTSKMessage.class);
myServer.addConnectionListener(this);
}
/**
* This method will be used to receive network messages from the given source parameter.
* It will check if the given message parameter is a ClientMessage object. If yes it will call the messageReceived
* method with the casted ClientMessage object.
*
* @param source as the connection which sends the message as a HostedConnection object.
* @param message as the received message as a Message object.
*/
@Override
public void messageReceived(HostedConnection source, Message message) {
if (message instanceof ClientMessage) {
this.messageReceived(source, (ClientMessage) message);
}
}
/**
* This method will be used to received network messages from the given source parameter.
* It will add the given message parameter to the pendingMessage attribute of MdgaServer after creating
* a ReceivedMessage object with it and its id.
*
* @param source as the connection which sends the message as a HostedConnection object.
* @param message as the received message as a Message object.
*/
private void messageReceived(HostedConnection source, ClientMessage message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
pendingMessages.add(new ReceivedMessage(message, source.getId()));
}
/**
* This method will be used to handle all connections which are connected to the server.
* It will check if the maximum number of connected clients are already reached. If yes it will send a
* LobbyDenyMessage to the given hostedConnection parameter and close it, otherwise it will send a
* LobbyAcceptMessage to the given hostedConnection parameter. In Addition, if the number of connected clients is
* equal to 1 it will set the host of the game to the id of the given hostedConnection parameter.
*
* @param server as the server which is contains all connections as a Server object.
* @param hostedConnection as the connection which is added to the server as a HostedConnection object.
*/
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
System.out.println("new connection " + hostedConnection); //NON-NLS
LOGGER.log(Level.DEBUG, "new connection {0}", hostedConnection); //NON-NLS
if (this.myServer.getConnections().size() > Resources.MAX_PLAYERS) {
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyDenyMessage());
hostedConnection.close("");
} else {
if (hostedConnection.getAddress().contains("127.0.0.1") && this.logic.getGame().getHost() == -1) {
this.logic.getGame().setHost(hostedConnection.getId());
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyAcceptMessage(hostedConnection.getId()));
} else {
this.logic.getServerSender().send(hostedConnection.getId(), new LobbyAcceptMessage());
}
}
}
@Override
public void connectionRemoved(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
final Player player = logic.getGame().getPlayerById(hostedConnection.getId());
if (player == null)
LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
else { //NON-NLS
LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
// exit(0);
this.handleDisconnect(hostedConnection.getId());
}
}
/**
* This method will be used to handle unintentional disconnections from players.
*
* @param id as the id of the disconnected player.
*/
public void handleDisconnect(int id) {
this.logic.received(new DisconnectedMessage(), id);
}
public void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null)
for (HostedConnection client : myServer.getConnections()) //NON-NLS
if (client != null) client.close("Game over"); //NON-NLS
System.exit(exitValue);
}
/**
* Send the specified message to the specified connection.
*
* @param id the connection id
* @param message the message
*/
@Override
public void send(int id, ServerMessage message) {
if (myServer == null || !myServer.isRunning()) {
LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
return;
}
final HostedConnection connection = myServer.getConnection(id);
if (connection != null)
connection.send(message);
else
LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
}
/**
* This method will be used to send the given message parameter to all connected players which are saved inside the
* players attribute of Game class.
*
* @param message as the message which will be sent to all players as a ServerMessage.
*/
@Override
public void broadcast(ServerMessage message) {
for (Map.Entry<Integer, Player> entry : this.logic.getGame().getPlayers().entrySet()) {
this.send(entry.getKey(), message);
}
}
/**
* This method will be used to diconenect the client depending on the given id parameter.
*
* @param id as the connection id of the client as an Integer.
*/
@Override
public void disconnectClient(int id) {
this.myServer.getConnection(id).close("");
}
/**
* This method will be used to shut down the server.
* It will iterate threw all connections of myServer attribute and check if they are equal to null. If not they will
* be closed. After that the myServer attribute will be closed and this program will be exited with the exit code 0.
*/
@Override
public void shutdown() {
for (HostedConnection client : this.myServer.getConnections()) {
if (client != null) {
client.close("Host closed the server.");
}
}
this.myServer.close();
this.exit(0);
}
}

View File

@@ -0,0 +1,10 @@
package pp.mdga.client.server;
import pp.mdga.message.client.ClientInterpreter;
import pp.mdga.message.client.ClientMessage;
public record ReceivedMessage(ClientMessage msg, int from) {
void process(ClientInterpreter interpreter) {
msg.accept(interpreter, from);
}
}

View File

@@ -0,0 +1,26 @@
package pp.mdga.client.server;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;
import com.jme3.network.serializing.Serializer;
public class UUIDSerializer extends Serializer
{
@Override
public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException
{
byte[] uuid = new byte[36];
data.get(uuid);
return (T) UUID.fromString(new String(uuid));
}
@Override
public void writeObject(ByteBuffer buffer, Object object) throws IOException
{
UUID uuid = (UUID) object;
buffer.put(uuid.toString().getBytes());
}
}

View File

@@ -0,0 +1,224 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.CeremonyButton;
import pp.mdga.client.dialog.CeremonyDialog;
import pp.mdga.game.Color;
import java.util.ArrayList;
public class CeremonyView extends MdgaView {
private enum SubState {
AWARD_CEREMONY,
STATISTICS,
}
private SubState state;
private Geometry background;
private Geometry podest;
private ButtonLeft backButton;
private ButtonRight continueButton;
private ArrayList<CeremonyButton> ceremonyButtons;
private CeremonyDialog ceremonyDialog;
private AmbientLight ambient = new AmbientLight();
public CeremonyView(MdgaApp app) {
super(app);
backButton = new ButtonLeft(app, guiNode, this::back, "Zurück", 1);
continueButton = new ButtonRight(app, guiNode, this::forward, "Weiter", 1);
ceremonyButtons = new ArrayList<>(4);
ceremonyDialog = new CeremonyDialog(app, guiNode);
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
}
@Override
public void onEnter() {
rootNode.addLight(ambient);
app.getAcousticHandler().playSound(MdgaSound.VICTORY);
float screenWidth = app.getCamera().getWidth();
float screenHeight = app.getCamera().getHeight();
float aspectRatio = screenWidth / screenHeight;
float scale = 3.5f;
float distanceFromCamera = 5f;
float verticalSize = (float) (2 * Math.tan(Math.toRadians(app.getCamera().getFov() / 2)) * distanceFromCamera * scale);
float horizontalSize = verticalSize * aspectRatio;
Quad backgroundQuad = new Quad(horizontalSize, verticalSize);
background = new Geometry("LobbyBackground", backgroundQuad);
TextureKey backgroundKey = new TextureKey("Images/lobby.png", true);
Texture backgroundTexture = app.getAssetManager().loadTexture(backgroundKey);
Material backgroundMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
backgroundMaterial.setTexture("ColorMap", backgroundTexture);
background.setMaterial(backgroundMaterial);
background.setLocalTranslation(-horizontalSize / 2, -verticalSize / 2, -distanceFromCamera);
rootNode.attachChild(background);
verticalSize *= 0.99f;
Quad overlayQuad = new Quad(horizontalSize, verticalSize * 0.8f);
podest = new Geometry("TransparentOverlay", overlayQuad);
TextureKey overlayKey = new TextureKey("Images/Ceremony.png", true);
Texture overlayTexture = app.getAssetManager().loadTexture(overlayKey);
Material overlayMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
overlayMaterial.setTexture("ColorMap", overlayTexture);
overlayMaterial.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
podest.setMaterial(overlayMaterial);
float overlayDistance = distanceFromCamera - 0.1f;
podest.setLocalTranslation(-horizontalSize / 2, -verticalSize * 0.415f, -overlayDistance);
enterSub(SubState.AWARD_CEREMONY);
}
@Override
public void onLeave() {
backButton.hide();
continueButton.hide();
if(null != background) {
guiNode.detachChild(background);
}
ceremonyButtons.clear();
rootNode.removeLight(ambient);
ceremonyDialog.prepare();
rootNode.detachChild(background);
}
@Override
protected void onEnterOverlay(Overlay overlay) {
if(rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
}
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
enterSub(state);
}
@Override
protected void onUpdate(float tpf) {
for (CeremonyButton c : ceremonyButtons) {
c.update(tpf);
}
}
private void awardCeremony() {
continueButton.show();
rootNode.attachChild(podest);
for (CeremonyButton c : ceremonyButtons) {
c.show();
}
}
private void statistics() {
//background = createBackground("Images/b2.png");
//guiNode.attachChild(background);
backButton.show();
continueButton.show();
ceremonyDialog.show();
}
private void enterSub(SubState state) {
this.state = state;
if(rootNode.hasChild(podest)) {
rootNode.detachChild(podest);
}
backButton.hide();
continueButton.hide();
for (CeremonyButton c : ceremonyButtons) {
c.hide();
}
ceremonyDialog.hide();
switch (state) {
case AWARD_CEREMONY:
awardCeremony();
break;
case STATISTICS:
statistics();
break;
}
}
public void forward() {
switch (state) {
case AWARD_CEREMONY:
enterSub(SubState.STATISTICS);
break;
case STATISTICS:
app.getModelSynchronize().enter(MdgaState.MAIN);
break;
}
}
private void back() {
switch (state) {
case AWARD_CEREMONY:
//nothing
break;
case STATISTICS:
enterSub(SubState.AWARD_CEREMONY);
break;
}
}
public void addCeremonyParticipant(Color color, int pos, String name) {
CeremonyButton button = new CeremonyButton(app, guiNode, rootNode, color, CeremonyButton.Pos.values()[pos - 1], name);
ceremonyButtons.add(button);
if(state.equals(SubState.AWARD_CEREMONY)) {
button.hide();
button.show();
}
}
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
ceremonyDialog.addStatisticsRow(name, v1, v2, v3, v4, v5, v6);
ceremonyDialog.hide();
ceremonyDialog.show();
}
public void afterGameCleanup() {
ceremonyDialog.prepare();
}
}

View File

@@ -0,0 +1,158 @@
package pp.mdga.client.view;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Node;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.board.CameraHandler;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.dialog.InterruptDialog;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.game.Color;
public class GameView extends MdgaView {
private BoardHandler boardHandler;
private CameraHandler camera;
private GuiHandler guiHandler;
private ButtonLeft leaveButton;
private ButtonRight confirmButton;
private ButtonRight noPowerButton;
private Color ownColor = null;
private InterruptDialog interruptDialog;
private FilterPostProcessor fpp;
private Node guiHandlerNode = new Node();
public GameView(MdgaApp app) {
super(app);
leaveButton = new ButtonLeft(app, settingsNode, () -> app.getModelSynchronize().leave(), "Spiel verlassen", 1);
confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
noPowerButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Verzichten", 1);
interruptDialog = new InterruptDialog(app, guiNode);
fpp = new FilterPostProcessor(app.getAssetManager());
this.camera = new CameraHandler(app, fpp);
this.boardHandler = new BoardHandler(app, rootNode, fpp);
guiHandler = new GuiHandler(app, guiHandlerNode);
guiNode.attachChild(guiHandlerNode);
}
@Override
public void onEnter() {
camera.init(ownColor);
boardHandler.init();
guiHandler.init(ownColor);
app.getViewPort().addProcessor(fpp);
app.getAcousticHandler().playSound(MdgaSound.START);
}
@Override
public void onLeave() {
boardHandler.shutdown();
guiHandler.shutdown();
camera.shutdown();
confirmButton.hide();
noPowerButton.hide();
app.getViewPort().removeProcessor(fpp);
}
@Override
public void onUpdate(float tpf) {
camera.update(app.getInputSynchronize().getScroll(), app.getInputSynchronize().getRotation());
}
@Override
protected void onEnterOverlay(Overlay overlay) {
if(overlay == Overlay.SETTINGS) {
leaveButton.show();
}
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
if(overlay == Overlay.SETTINGS) {
leaveButton.hide();
}
}
private void leaveGame() {
app.getModelSynchronize().leave();
}
public BoardHandler getBoardHandler() {
return boardHandler;
}
public GuiHandler getGuiHandler() {
return guiHandler;
}
public void setOwnColor(Color ownColor) {
this.ownColor = ownColor;
}
public Color getOwnColor() {
return ownColor;
}
public void needConfirm() {
noPowerButton.hide();
confirmButton.show();
}
public void noConfirm() {
confirmButton.hide();
}
public void needNoPower() {
confirmButton.hide();
noPowerButton.show();
}
public void noNoPower() {
noPowerButton.show();
}
public void enterInterrupt(Color color) {
enterOverlay(Overlay.INTERRUPT);
guiNode.detachChild(guiHandlerNode);
app.getGuiNode().attachChild(guiNode);
app.getInputSynchronize().setClickAllowed(false);
interruptDialog.setColor(color);
interruptDialog.show();
}
public void leaveInterrupt() {
leaveOverlay(Overlay.INTERRUPT);
app.getGuiNode().detachChild(guiNode);
guiNode.attachChild(guiHandlerNode);
app.getInputSynchronize().setClickAllowed(true);
app.getAcousticHandler().playSound(MdgaSound.START);
interruptDialog.hide();
}
}

View File

@@ -0,0 +1,267 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.light.AmbientLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import com.jme3.util.SkyFactory;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.ButtonLeft;
import pp.mdga.client.button.ButtonRight;
import pp.mdga.client.button.LobbyButton;
import pp.mdga.client.button.SettingsButton;
import pp.mdga.game.Color;
import pp.mdga.message.client.StartGameMessage;
import pp.mdga.notification.GameNotification;
public class LobbyView extends MdgaView {
private Geometry background;
private ButtonLeft leaveButton;
private ButtonRight readyButton;
private ButtonRight startButton;
private LobbyButton cyberButton;
private LobbyButton airforceButton;
private LobbyButton armyButton;
private LobbyButton navyButton;
private AmbientLight ambient = new AmbientLight();
private boolean isReady = false;
private Color own = null;
public LobbyView(MdgaApp app) {
super(app);
leaveButton = new ButtonLeft(app, guiNode, this::leaveLobby, "Verlassen", 1);
readyButton = new ButtonRight(app, guiNode, this::ready, "Bereit", 1);
startButton = new ButtonRight(app, guiNode, () -> app.getGameLogic().selectStart(), "Starten", 7);
cyberButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.CYBER), Color.CYBER);
airforceButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.AIRFORCE), Color.AIRFORCE);
armyButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.ARMY), Color.ARMY);
navyButton = new LobbyButton(app, guiNode, rootNode, () -> toggleTsk(Color.NAVY), Color.NAVY);
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1));
}
@Override
public void onEnter() {
app.getCamera().setParallelProjection(true);
float aspect = (float) app.getCamera().getWidth() / app.getCamera().getHeight();
float size = 1.65f;
app.getCamera().setFrustum(-1000, 1000, -aspect * size, aspect * size, size, -size);
leaveButton.show();
readyButton.show();
if(app.getGameLogic().isHost()) {
startButton.show();
}
cyberButton.show();
airforceButton.show();
armyButton.show();
navyButton.show();
rootNode.addLight(ambient);
float screenWidth = app.getCamera().getWidth();
float screenHeight = app.getCamera().getHeight();
float aspectRatio = screenWidth / screenHeight;
float scale = 3.5f;
float quadWidth = scale * aspectRatio;
float quadHeight = scale;
Quad quad = new Quad(quadWidth, quadHeight);
background = new Geometry("LobbyBackground", quad);
TextureKey key = new TextureKey("Images/lobby.png", true);
Texture texture = app.getAssetManager().loadTexture(key);
Material material = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
material.setTexture("ColorMap", texture);
background.setMaterial(material);
background.setLocalTranslation(-quadWidth / 2, -quadHeight / 2, -5);
rootNode.attachChild(background);
}
@Override
public void onLeave() {
leaveButton.hide();
readyButton.hide();
startButton.hide();
airforceButton.hide();
armyButton.hide();
navyButton.hide();
cyberButton.hide();
rootNode.removeLight(ambient);
app.getCamera().setParallelProjection(false);
app.getCamera().setFrustumPerspective(
45.0f,
(float) app.getCamera().getWidth() / app.getCamera().getHeight(),
0.1f,
1000.0f
);
app.getCamera().setLocation(new Vector3f(0, 0, 10));
app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
airforceButton.setReady(false);
armyButton.setReady(false);
navyButton.setReady(false);
cyberButton.setReady(false);
airforceButton.setTaken(LobbyButton.Taken.NOT, null);
armyButton.setTaken(LobbyButton.Taken.NOT, null);
navyButton.setTaken(LobbyButton.Taken.NOT, null);
cyberButton.setTaken(LobbyButton.Taken.NOT, null);
rootNode.detachChild(background);
}
@Override
protected void onUpdate(float tpf) {
airforceButton.update(tpf);
armyButton.update(tpf);
navyButton.update(tpf);
cyberButton.update(tpf);
}
@Override
protected void onEnterOverlay(Overlay overlay) {
}
@Override
protected void onLeaveOverlay(Overlay overlay)
{
}
public void setTaken(Color color, boolean isTaken, boolean isSelf, String name) {
LobbyButton.Taken taken;
if(isTaken) {
if(isSelf) {
own = color;
taken = LobbyButton.Taken.SELF;
} else {
taken = LobbyButton.Taken.OTHER;
}
} else {
if(isSelf) {
own = null;
}
taken = LobbyButton.Taken.NOT;
}
switch (color) {
case CYBER:
cyberButton.setTaken(taken, name);
break;
case AIRFORCE:
airforceButton.setTaken(taken, name);
break;
case ARMY:
armyButton.setTaken(taken, name);
break;
case NAVY:
navyButton.setTaken(taken, name);
break;
}
}
public void setReady(Color color, boolean isReady) {
LobbyButton button = switch (color) {
case CYBER -> cyberButton;
case AIRFORCE -> airforceButton;
case ARMY -> armyButton;
case NAVY -> navyButton;
default -> throw new RuntimeException("None is not valid");
};
button.setReady(isReady);
if (button.getTaken() == LobbyButton.Taken.SELF) {
this.isReady = isReady;
}
if(!isReady) {
app.getAcousticHandler().playSound(MdgaSound.NOT_READY);
} else {
if(button.getTaken() != LobbyButton.Taken.SELF) {
app.getAcousticHandler().playSound(MdgaSound.OTHER_READY);
}
}
}
private void toggleTsk(Color color) {
LobbyButton.Taken taken = LobbyButton.Taken.NOT;
switch (color) {
case CYBER:
taken = cyberButton.getTaken();
break;
case AIRFORCE:
taken = airforceButton.getTaken();
break;
case ARMY:
taken = armyButton.getTaken();
break;
case NAVY:
taken = navyButton.getTaken();
break;
}
if(isReady) {
setReady(own, false);
}
switch (taken) {
case NOT:
app.getModelSynchronize().selectTsk(color);
break;
case SELF:
app.getModelSynchronize().unselectTsk(color);
break;
case OTHER:
//nothing
break;
}
}
public void ready() {
if(own == null) {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
if(!isReady) {
app.getAcousticHandler().playSound(MdgaSound.SELF_READY);
}
app.getModelSynchronize().setReady(!isReady);
}
private void leaveLobby() {
app.getModelSynchronize().leave();
}
}

View File

@@ -0,0 +1,236 @@
package pp.mdga.client.view;
import com.jme3.scene.Geometry;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.dialog.HostDialog;
import pp.mdga.client.dialog.JoinDialog;
import pp.mdga.client.dialog.StartDialog;
public class MainView extends MdgaView {
private enum SubState {
HOST,
JOIN,
MAIN,
}
private SubState state;
private Geometry background;
private StartDialog startDialog;
private JoinDialog joinDialog;
private HostDialog hostDialog;
public MainView(MdgaApp app) {
super(app);
startDialog = new StartDialog(app, guiNode, this);
joinDialog = new JoinDialog(app, guiNode, this);
hostDialog = new HostDialog(app, guiNode, this);
background = createBackground("Images/startmenu.png");
}
@Override
public void onEnter() {
app.setup();
guiNode.attachChild(background);
enterSub(SubState.MAIN);
}
@Override
public void onLeave() {
startDialog.hide();
joinDialog.hide();
hostDialog.hide();
guiNode.detachChild(background);
}
@Override
public void onUpdate(float tpf) {
startDialog.update();
joinDialog.update();
hostDialog.update();
}
@Override
protected void onEnterOverlay(Overlay overlay) {
guiNode.detachChild(background);
settingsNode.attachChild(background);
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
settingsNode.detachChild(background);
guiNode.attachChild(background);
}
private void joinMenu() {
startDialog.hide();
hostDialog.hide();
joinDialog.show();
}
private void hostMenu() {
startDialog.hide();
joinDialog.hide();
hostDialog.show();
}
private void mainMenu() {
joinDialog.hide();
hostDialog.hide();
startDialog.show();
}
private void tryHost() {
int port = 0;
String text = hostDialog.getPort();
app.getGameLogic().selectHost("");
try {
port = Integer.parseInt(text);
if(port >= 1 && port <= 65535) {
app.getModelSynchronize().setName(startDialog.getName());
hostDialog.hostServer();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
hostDialog.connectServerAsClient();
app.getModelSynchronize().setHost(port);
//app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
} catch (NumberFormatException ignored) {
}
hostDialog.resetPort();
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
private void tryJoin() {
int port = 0;
String ip = joinDialog.getIpt();
String portText = joinDialog.getPort();
app.getGameLogic().selectJoin("");
try {
// Validate the port
port = Integer.parseInt(portText);
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Invalid port");
}
joinDialog.setPortNumber(port);
// Validate the IP address
if (isValidIpAddress(ip)) {
app.getModelSynchronize().setName(startDialog.getName());
joinDialog.setHostname(ip);
joinDialog.connectToServer();
return;
}
} catch (IllegalArgumentException e) {
// Invalid input, fall through to reset
}
joinDialog.resetPort();
joinDialog.resetIp();
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
private boolean isValidIpAddress(String ip) {
String ipRegex =
"^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$";
return ip != null && ip.matches(ipRegex);
}
private void enterSub(SubState state) {
this.state = state;
if(null != background) {
rootNode.detachChild(background);
}
switch (state) {
case HOST:
hostMenu();
break;
case JOIN:
joinMenu();
break;
case MAIN:
mainMenu();
break;
}
}
public void forward() {
switch (state) {
case HOST:
tryHost();
break;
case JOIN:
tryJoin();
break;
case MAIN:
throw new RuntimeException("call forward(boolean host) insted of forward()");
}
}
public void forward(boolean host) {
switch (state) {
case HOST:
tryHost();
break;
case JOIN:
tryJoin();
break;
case MAIN:
if(host) {
enterSub(SubState.HOST);
//TODO playSound
} else {
enterSub(SubState.JOIN);
//TODO: playSound
}
break;
}
}
public void back() {
switch (state) {
case HOST:
enterSub(SubState.MAIN);
//TODO: playSound
break;
case JOIN:
enterSub(SubState.MAIN);
//TODO: playSound
break;
case MAIN:
//nothing
break;
}
}
public JoinDialog getJoinDialog() {
return joinDialog;
}
public HostDialog getHostDialog() {
return hostDialog;
}
}

View File

@@ -0,0 +1,227 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.system.NanoTimer;
import com.jme3.texture.Texture;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.button.*;
import pp.mdga.client.dialog.AudioSettingsDialog;
import pp.mdga.client.dialog.SettingsDialog;
import pp.mdga.client.dialog.VideoSettingsDialog;
public abstract class MdgaView {
public enum Overlay {
INTERRUPT,
SETTINGS,
}
protected MdgaApp app;
protected Node rootNode = new Node("View Root");
protected Node guiNode = new Node("View Root GUI");
protected Node settingsNode = new Node("View Root Overlay");
private SettingsButton settingsButton;
private SettingsDialog settingsDialog;
private VideoSettingsDialog videoSettingsDialog;
private AudioSettingsDialog audioSettingsDialog;
protected LabelButton infoLabel = null;
protected NanoTimer infoTimer = new NanoTimer();
private int settingsDepth = 0;
public MdgaView(MdgaApp app) {
this.app = app;
settingsButton = new SettingsButton(app, guiNode, this::enterSettings);
settingsDialog = new SettingsDialog(app, settingsNode, this);
videoSettingsDialog = new VideoSettingsDialog(app, settingsNode, this);
audioSettingsDialog = new AudioSettingsDialog(app, settingsNode, this);
}
public void enter() {
app.getRootNode().attachChild(rootNode);
app.getGuiNode().attachChild(guiNode);
settingsButton.show();
onEnter();
}
public void leave() {
onLeave();
settingsButton.hide();
while (settingsDepth > 0) {
pressEscape();
}
app.getRootNode().detachChild(rootNode);
app.getGuiNode().detachChild(guiNode);
}
public void enterOverlay(Overlay overlay) {
app.getGuiNode().detachChild(guiNode);
onEnterOverlay(overlay);
}
public void leaveOverlay(Overlay overlay) {
app.getGuiNode().attachChild(guiNode);
onLeaveOverlay(overlay);
}
public void update(float tpf) {
videoSettingsDialog.update();
audioSettingsDialog.update();
if (null != infoLabel && infoTimer.getTimeInSeconds() > 5) {
infoLabel.hide();
infoLabel = null;
}
onUpdate(tpf);
}
protected abstract void onEnter();
protected abstract void onLeave();
protected void onUpdate(float tpf) {
}
protected abstract void onEnterOverlay(Overlay overlay);
protected abstract void onLeaveOverlay(Overlay overlay);
protected Geometry createBackground(String texturePath) {
TextureKey key = new TextureKey(texturePath, true);
Texture backgroundTexture = app.getAssetManager().loadTexture(key);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", backgroundTexture);
Quad quad = new Quad(app.getCamera().getWidth(), app.getCamera().getHeight());
Geometry background = new Geometry("Background", quad);
background.setMaterial(mat);
background.setLocalTranslation(0, 0, -2);
return background;
}
public void enterSettings() {
enterOverlay(Overlay.SETTINGS);
app.getGuiNode().attachChild(settingsNode);
settingsDialog.show();
settingsDepth++;
}
public void leaveSettings() {
leaveOverlay(Overlay.SETTINGS);
app.getGuiNode().detachChild(settingsNode);
settingsDialog.hide();
settingsDepth--;
}
public void enterVideoSettings() {
settingsDialog.hide();
videoSettingsDialog.show();
settingsDepth++;
}
public void leaveVideoSettings() {
settingsDialog.show();
videoSettingsDialog.hide();
settingsDepth--;
}
public void enterAudioSettings() {
settingsDialog.hide();
audioSettingsDialog.show();
settingsDepth++;
}
public void leaveAudioSettings() {
settingsDialog.show();
audioSettingsDialog.hide();
settingsDepth--;
}
private void leaveAdvanced() {
settingsDialog.show();
audioSettingsDialog.hide();
videoSettingsDialog.hide();
settingsDepth--;
}
public void pressEscape() {
if (settingsDepth == 0) {
enterSettings();
} else if (settingsDepth == 1) {
leaveSettings();
} else {
leaveAdvanced();
}
}
public void pressForward() {
if (this instanceof MainView mainView) {
mainView.forward(false);
app.getAcousticHandler().playSound(MdgaSound.BUTTON_PRESSED);
}
if (this instanceof LobbyView lobbyView) {
lobbyView.ready();
}
if (this instanceof GameView gameView) {
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
if (this instanceof CeremonyView ceremonyView) {
ceremonyView.forward();
}
}
public void showInfo(String error, boolean isError) {
infoTimer.reset();
if(null != infoLabel) {
infoLabel.hide();
}
infoLabel = new LabelButton(app, guiNode, error, new Vector2f(5.5f, 2), new Vector2f(0.5f, AbstractButton.VERTICAL - 0.5f), false);
ColorRGBA color;
if(isError) {
color = ColorRGBA.Red.clone();
} else {
color = ColorRGBA.Green.clone();
}
infoLabel.setColor(ColorRGBA.Black, color);
}
}

View File

@@ -0,0 +1,195 @@
info face=null size=178 bold=0 italic=0 charset=ASCII unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
common lineHeight=214 base=26 scaleW=2048 scaleH=2048 pages=1 packed=0
page id=0 file="Gunplay.png"
chars count=255
char id=9 x=0 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=10 x=6 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=13 x=12 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=-1 page=0 chnl=0
char id=32 x=18 y=46 width=6 height=220 xoffset=0 yoffset=0 xadvance=59 page=0 chnl=0
char id=33 x=24 y=46 width=40 height=220 xoffset=8 yoffset=0 xadvance=49 page=0 chnl=0
char id=34 x=64 y=46 width=62 height=220 xoffset=8 yoffset=0 xadvance=71 page=0 chnl=0
char id=35 x=126 y=46 width=95 height=220 xoffset=3 yoffset=0 xadvance=95 page=0 chnl=0
char id=36 x=221 y=46 width=84 height=220 xoffset=4 yoffset=0 xadvance=86 page=0 chnl=0
char id=37 x=305 y=46 width=148 height=220 xoffset=6 yoffset=0 xadvance=153 page=0 chnl=0
char id=38 x=453 y=46 width=127 height=220 xoffset=6 yoffset=0 xadvance=131 page=0 chnl=0
char id=39 x=580 y=46 width=31 height=220 xoffset=8 yoffset=0 xadvance=39 page=0 chnl=0
char id=40 x=611 y=46 width=44 height=220 xoffset=4 yoffset=0 xadvance=44 page=0 chnl=0
char id=41 x=655 y=46 width=44 height=220 xoffset=4 yoffset=0 xadvance=44 page=0 chnl=0
char id=42 x=699 y=46 width=71 height=220 xoffset=4 yoffset=0 xadvance=71 page=0 chnl=0
char id=43 x=770 y=46 width=86 height=220 xoffset=3 yoffset=0 xadvance=85 page=0 chnl=0
char id=44 x=856 y=46 width=38 height=220 xoffset=7 yoffset=0 xadvance=43 page=0 chnl=0
char id=45 x=894 y=46 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=46 x=959 y=46 width=39 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=47 x=998 y=46 width=102 height=220 xoffset=1 yoffset=0 xadvance=97 page=0 chnl=0
char id=48 x=1100 y=46 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=49 x=1207 y=46 width=61 height=220 xoffset=2 yoffset=0 xadvance=64 page=0 chnl=0
char id=50 x=1268 y=46 width=104 height=220 xoffset=6 yoffset=0 xadvance=111 page=0 chnl=0
char id=51 x=1372 y=46 width=100 height=220 xoffset=6 yoffset=0 xadvance=106 page=0 chnl=0
char id=52 x=1472 y=46 width=111 height=220 xoffset=3 yoffset=0 xadvance=109 page=0 chnl=0
char id=53 x=1583 y=46 width=98 height=220 xoffset=7 yoffset=0 xadvance=105 page=0 chnl=0
char id=54 x=1681 y=46 width=104 height=220 xoffset=8 yoffset=0 xadvance=112 page=0 chnl=0
char id=55 x=1785 y=46 width=92 height=220 xoffset=6 yoffset=0 xadvance=93 page=0 chnl=0
char id=56 x=1877 y=46 width=100 height=220 xoffset=7 yoffset=0 xadvance=107 page=0 chnl=0
char id=57 x=0 y=266 width=105 height=220 xoffset=7 yoffset=0 xadvance=112 page=0 chnl=0
char id=58 x=105 y=266 width=36 height=220 xoffset=8 yoffset=0 xadvance=43 page=0 chnl=0
char id=59 x=141 y=266 width=37 height=220 xoffset=7 yoffset=0 xadvance=43 page=0 chnl=0
char id=60 x=178 y=266 width=59 height=220 xoffset=1 yoffset=0 xadvance=58 page=0 chnl=0
char id=61 x=237 y=266 width=92 height=220 xoffset=8 yoffset=0 xadvance=97 page=0 chnl=0
char id=62 x=329 y=266 width=59 height=220 xoffset=8 yoffset=0 xadvance=61 page=0 chnl=0
char id=63 x=388 y=266 width=96 height=220 xoffset=4 yoffset=0 xadvance=98 page=0 chnl=0
char id=64 x=484 y=266 width=132 height=220 xoffset=4 yoffset=0 xadvance=136 page=0 chnl=0
char id=65 x=616 y=266 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=66 x=736 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=67 x=843 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=68 x=950 y=266 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=69 x=1057 y=266 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=70 x=1144 y=266 width=87 height=220 xoffset=8 yoffset=0 xadvance=91 page=0 chnl=0
char id=71 x=1231 y=266 width=105 height=220 xoffset=8 yoffset=0 xadvance=114 page=0 chnl=0
char id=72 x=1336 y=266 width=105 height=220 xoffset=8 yoffset=0 xadvance=114 page=0 chnl=0
char id=73 x=1441 y=266 width=41 height=220 xoffset=8 yoffset=0 xadvance=50 page=0 chnl=0
char id=74 x=1482 y=266 width=63 height=220 xoffset=3 yoffset=0 xadvance=67 page=0 chnl=0
char id=75 x=1545 y=266 width=114 height=220 xoffset=8 yoffset=0 xadvance=118 page=0 chnl=0
char id=76 x=1659 y=266 width=83 height=220 xoffset=8 yoffset=0 xadvance=87 page=0 chnl=0
char id=77 x=1742 y=266 width=142 height=220 xoffset=8 yoffset=0 xadvance=151 page=0 chnl=0
char id=78 x=1884 y=266 width=111 height=220 xoffset=8 yoffset=0 xadvance=121 page=0 chnl=0
char id=79 x=0 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=80 x=107 y=486 width=105 height=220 xoffset=8 yoffset=0 xadvance=108 page=0 chnl=0
char id=81 x=212 y=486 width=107 height=220 xoffset=6 yoffset=0 xadvance=113 page=0 chnl=0
char id=82 x=319 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=113 page=0 chnl=0
char id=83 x=426 y=486 width=112 height=220 xoffset=6 yoffset=0 xadvance=117 page=0 chnl=0
char id=84 x=538 y=486 width=107 height=220 xoffset=3 yoffset=0 xadvance=105 page=0 chnl=0
char id=85 x=645 y=486 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=86 x=752 y=486 width=117 height=220 xoffset=1 yoffset=0 xadvance=111 page=0 chnl=0
char id=87 x=869 y=486 width=169 height=220 xoffset=1 yoffset=0 xadvance=165 page=0 chnl=0
char id=88 x=1038 y=486 width=123 height=220 xoffset=3 yoffset=0 xadvance=124 page=0 chnl=0
char id=89 x=1161 y=486 width=119 height=220 xoffset=2 yoffset=0 xadvance=115 page=0 chnl=0
char id=90 x=1280 y=486 width=101 height=220 xoffset=5 yoffset=0 xadvance=104 page=0 chnl=0
char id=91 x=1381 y=486 width=37 height=220 xoffset=8 yoffset=0 xadvance=44 page=0 chnl=0
char id=92 x=1418 y=486 width=102 height=220 xoffset=1 yoffset=0 xadvance=97 page=0 chnl=0
char id=93 x=1520 y=486 width=37 height=220 xoffset=8 yoffset=0 xadvance=44 page=0 chnl=0
char id=94 x=1557 y=486 width=95 height=220 xoffset=4 yoffset=0 xadvance=96 page=0 chnl=0
char id=95 x=1652 y=486 width=125 height=220 xoffset=-1 yoffset=0 xadvance=115 page=0 chnl=0
char id=96 x=1777 y=486 width=57 height=220 xoffset=-2 yoffset=0 xadvance=56 page=0 chnl=0
char id=97 x=1834 y=486 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=98 x=1932 y=486 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=99 x=0 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=100 x=91 y=706 width=90 height=220 xoffset=4 yoffset=0 xadvance=96 page=0 chnl=0
char id=101 x=181 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=102 x=272 y=706 width=70 height=220 xoffset=1 yoffset=0 xadvance=66 page=0 chnl=0
char id=103 x=342 y=706 width=89 height=220 xoffset=6 yoffset=0 xadvance=96 page=0 chnl=0
char id=104 x=431 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=105 x=522 y=706 width=37 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=106 x=559 y=706 width=57 height=220 xoffset=-14 yoffset=0 xadvance=44 page=0 chnl=0
char id=107 x=616 y=706 width=98 height=220 xoffset=8 yoffset=0 xadvance=101 page=0 chnl=0
char id=108 x=714 y=706 width=37 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=109 x=751 y=706 width=138 height=220 xoffset=8 yoffset=0 xadvance=147 page=0 chnl=0
char id=110 x=889 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=111 x=980 y=706 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=112 x=1071 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=113 x=1162 y=706 width=90 height=220 xoffset=5 yoffset=0 xadvance=96 page=0 chnl=0
char id=114 x=1252 y=706 width=65 height=220 xoffset=8 yoffset=0 xadvance=69 page=0 chnl=0
char id=115 x=1317 y=706 width=91 height=220 xoffset=6 yoffset=0 xadvance=96 page=0 chnl=0
char id=116 x=1408 y=706 width=73 height=220 xoffset=1 yoffset=0 xadvance=68 page=0 chnl=0
char id=117 x=1481 y=706 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=118 x=1572 y=706 width=99 height=220 xoffset=1 yoffset=0 xadvance=95 page=0 chnl=0
char id=119 x=1671 y=706 width=146 height=220 xoffset=3 yoffset=0 xadvance=144 page=0 chnl=0
char id=120 x=1817 y=706 width=102 height=220 xoffset=4 yoffset=0 xadvance=103 page=0 chnl=0
char id=121 x=1919 y=706 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0
char id=122 x=0 y=926 width=84 height=220 xoffset=5 yoffset=0 xadvance=86 page=0 chnl=0
char id=123 x=84 y=926 width=67 height=220 xoffset=3 yoffset=0 xadvance=68 page=0 chnl=0
char id=124 x=151 y=926 width=27 height=220 xoffset=8 yoffset=0 xadvance=36 page=0 chnl=0
char id=125 x=178 y=926 width=67 height=220 xoffset=6 yoffset=0 xadvance=68 page=0 chnl=0
char id=126 x=245 y=926 width=93 height=220 xoffset=12 yoffset=0 xadvance=110 page=0 chnl=0
char id=161 x=338 y=926 width=40 height=220 xoffset=8 yoffset=0 xadvance=49 page=0 chnl=0
char id=162 x=378 y=926 width=82 height=220 xoffset=8 yoffset=0 xadvance=90 page=0 chnl=0
char id=163 x=460 y=926 width=93 height=220 xoffset=5 yoffset=0 xadvance=95 page=0 chnl=0
char id=164 x=553 y=926 width=80 height=220 xoffset=12 yoffset=0 xadvance=97 page=0 chnl=0
char id=165 x=633 y=926 width=119 height=220 xoffset=4 yoffset=0 xadvance=120 page=0 chnl=0
char id=166 x=752 y=926 width=27 height=220 xoffset=8 yoffset=0 xadvance=35 page=0 chnl=0
char id=167 x=779 y=926 width=95 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=168 x=874 y=926 width=68 height=220 xoffset=8 yoffset=0 xadvance=78 page=0 chnl=0
char id=169 x=942 y=926 width=153 height=220 xoffset=7 yoffset=0 xadvance=157 page=0 chnl=0
char id=170 x=1095 y=926 width=68 height=220 xoffset=7 yoffset=0 xadvance=75 page=0 chnl=0
char id=171 x=1163 y=926 width=77 height=220 xoffset=8 yoffset=0 xadvance=88 page=0 chnl=0
char id=172 x=1240 y=926 width=102 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=174 x=1342 y=926 width=93 height=220 xoffset=5 yoffset=0 xadvance=95 page=0 chnl=0
char id=175 x=1435 y=926 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=176 x=1500 y=926 width=70 height=220 xoffset=6 yoffset=0 xadvance=73 page=0 chnl=0
char id=177 x=1570 y=926 width=93 height=220 xoffset=12 yoffset=0 xadvance=110 page=0 chnl=0
char id=178 x=1663 y=926 width=55 height=220 xoffset=8 yoffset=0 xadvance=63 page=0 chnl=0
char id=179 x=1718 y=926 width=53 height=220 xoffset=8 yoffset=0 xadvance=60 page=0 chnl=0
char id=180 x=1771 y=926 width=60 height=220 xoffset=9 yoffset=0 xadvance=61 page=0 chnl=0
char id=182 x=1831 y=926 width=73 height=220 xoffset=14 yoffset=0 xadvance=92 page=0 chnl=0
char id=183 x=1904 y=926 width=39 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=184 x=1943 y=926 width=44 height=220 xoffset=8 yoffset=0 xadvance=48 page=0 chnl=0
char id=185 x=1987 y=926 width=33 height=220 xoffset=9 yoffset=0 xadvance=46 page=0 chnl=0
char id=186 x=0 y=1146 width=69 height=220 xoffset=7 yoffset=0 xadvance=78 page=0 chnl=0
char id=187 x=69 y=1146 width=77 height=220 xoffset=8 yoffset=0 xadvance=88 page=0 chnl=0
char id=188 x=146 y=1146 width=128 height=220 xoffset=10 yoffset=0 xadvance=139 page=0 chnl=0
char id=189 x=274 y=1146 width=126 height=220 xoffset=9 yoffset=0 xadvance=140 page=0 chnl=0
char id=190 x=400 y=1146 width=135 height=220 xoffset=3 yoffset=0 xadvance=138 page=0 chnl=0
char id=191 x=535 y=1146 width=97 height=220 xoffset=4 yoffset=0 xadvance=98 page=0 chnl=0
char id=192 x=632 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=193 x=752 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=194 x=872 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=195 x=992 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=196 x=1112 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=197 x=1232 y=1146 width=120 height=220 xoffset=1 yoffset=0 xadvance=114 page=0 chnl=0
char id=198 x=1352 y=1146 width=131 height=220 xoffset=1 yoffset=0 xadvance=133 page=0 chnl=0
char id=199 x=1483 y=1146 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=200 x=1590 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=201 x=1677 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=202 x=1764 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=203 x=1851 y=1146 width=87 height=220 xoffset=8 yoffset=0 xadvance=93 page=0 chnl=0
char id=204 x=1938 y=1146 width=56 height=220 xoffset=-3 yoffset=0 xadvance=50 page=0 chnl=0
char id=205 x=0 y=1366 width=60 height=220 xoffset=2 yoffset=0 xadvance=50 page=0 chnl=0
char id=206 x=60 y=1366 width=69 height=220 xoffset=-6 yoffset=0 xadvance=50 page=0 chnl=0
char id=207 x=129 y=1366 width=68 height=220 xoffset=-5 yoffset=0 xadvance=50 page=0 chnl=0
char id=208 x=197 y=1366 width=117 height=220 xoffset=5 yoffset=0 xadvance=123 page=0 chnl=0
char id=209 x=314 y=1366 width=111 height=220 xoffset=8 yoffset=0 xadvance=121 page=0 chnl=0
char id=210 x=425 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=211 x=532 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=212 x=639 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=213 x=746 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=214 x=853 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=215 x=960 y=1366 width=77 height=220 xoffset=8 yoffset=0 xadvance=85 page=0 chnl=0
char id=216 x=1037 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=115 page=0 chnl=0
char id=217 x=1144 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=218 x=1251 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=219 x=1358 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=220 x=1465 y=1366 width=107 height=220 xoffset=8 yoffset=0 xadvance=116 page=0 chnl=0
char id=221 x=1572 y=1366 width=119 height=220 xoffset=2 yoffset=0 xadvance=115 page=0 chnl=0
char id=222 x=1691 y=1366 width=84 height=220 xoffset=8 yoffset=0 xadvance=92 page=0 chnl=0
char id=223 x=1775 y=1366 width=99 height=220 xoffset=7 yoffset=0 xadvance=106 page=0 chnl=0
char id=224 x=1874 y=1366 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=225 x=0 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=226 x=98 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=227 x=196 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=228 x=294 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=229 x=392 y=1586 width=98 height=220 xoffset=7 yoffset=0 xadvance=103 page=0 chnl=0
char id=230 x=490 y=1586 width=148 height=220 xoffset=7 yoffset=0 xadvance=155 page=0 chnl=0
char id=231 x=638 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=232 x=729 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=233 x=820 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=234 x=911 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=235 x=1002 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=98 page=0 chnl=0
char id=236 x=1093 y=1586 width=56 height=220 xoffset=-5 yoffset=0 xadvance=46 page=0 chnl=0
char id=237 x=1149 y=1586 width=61 height=220 xoffset=8 yoffset=0 xadvance=46 page=0 chnl=0
char id=238 x=1210 y=1586 width=69 height=220 xoffset=-8 yoffset=0 xadvance=46 page=0 chnl=0
char id=239 x=1279 y=1586 width=67 height=220 xoffset=-7 yoffset=0 xadvance=46 page=0 chnl=0
char id=240 x=1346 y=1586 width=105 height=220 xoffset=8 yoffset=0 xadvance=112 page=0 chnl=0
char id=241 x=1451 y=1586 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=242 x=1542 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=243 x=1633 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=244 x=1724 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=245 x=1815 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=246 x=1906 y=1586 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=247 x=0 y=1806 width=65 height=220 xoffset=8 yoffset=0 xadvance=75 page=0 chnl=0
char id=248 x=65 y=1806 width=91 height=220 xoffset=7 yoffset=0 xadvance=99 page=0 chnl=0
char id=249 x=156 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=250 x=247 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=251 x=338 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=252 x=429 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=100 page=0 chnl=0
char id=253 x=520 y=1806 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0
char id=254 x=619 y=1806 width=91 height=220 xoffset=8 yoffset=0 xadvance=96 page=0 chnl=0
char id=255 x=710 y=1806 width=99 height=220 xoffset=2 yoffset=0 xadvance=96 page=0 chnl=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@@ -0,0 +1,66 @@
world 0,0
# jet 0,0
#Nodes für Map
node_start 8,0
node 8,1
node 8,2
node 7,3
node_bonus 6,4
node 5,5
node 4,6
node 3,7
node 2,8
node 1,8
node_start 0,8
node -1,8
node -2,8
node -3,7
node_bonus -4,6
node -5,5
node -6,4
node -7,3
node -8,2
node -8,1
node_start -8,0
node -8,-1
node -8,-2
node -7,-3
node_bonus -6,-4
node -5,-5
node -4,-6
node -3,-7
node -2,-8
node -1,-8
node_start 0,-8
node 1,-8
node 2,-8
node 3,-7
node_bonus 4,-6
node 5,-5
node 6,-4
node 7,-3
node 8,-2
node 8,-1
#Node Home
node_home_blue 7,0
node_home_blue 6,0
node_home_blue 5,0
node_home_blue 4,0
node_home_black 0,4
node_home_black 0,5
node_home_black 0,6
node_home_black 0,7
node_home_yellow 0,-4
node_home_yellow 0,-5
node_home_yellow 0,-6
node_home_yellow 0,-7
node_home_green -4,0
node_home_green -5,0
node_home_green -6,0
node_home_green -7,0

View File

@@ -0,0 +1,272 @@
world 0,0 90
#Marine Pos
marine 4,-5 270
marine 4,-4 270
marine 5,-4 270
marine 5,-5 270
#Blue (Marine) wait Node
node_wait_blue 4,-5 0
node_wait_blue 4,-4 0
node_wait_blue 5,-4 0
node_wait_blue 5,-5 0
#Lw Pos
lw -5,4 90
lw -4,4 90
lw -4,5 90
lw -5,5 90
#Black (Lw) wait Node
node_wait_black -5,4 0
node_wait_black -4,4 0
node_wait_black -4,5 0
node_wait_black -5,5 0
#Heer Pos
heer -4,-5 180
heer -4,-4 180
heer -5,-4 180
heer -5,-5 180
#Green (Heer) Wait Node
node_wait_green -4,-5 0
node_wait_green -4,-4 0
node_wait_green -5,-4 0
node_wait_green -5,-5 0
#CIR Pos
cir 4,5 0
cir 4,4 0
cir 5,4 0
cir 5,5 0
#Assets
jet -12,0 45
jet -17,-2 55
small_tent -9,7 45
small_tent -10,5 60
small_tent -7,8 30
ship 11,0 169
small_tent 6,8 340
small_tent 8,7 320
big_tent -10,-9 130
big_tent 9,-10 225
radar 0,10 -20
tank -1,-10 135
tank 0,-18 180
tank 3,-18 180
tank -3,-18 180
#Yellow (CIR) wait Node
node_wait_yellow 4,5 0
node_wait_yellow 4,4 0
node_wait_yellow 5,4 0
node_wait_yellow 5,5 0
#Nodes für Map
node_start -5,1 0
node -4,1 0
node -3,1 0
node -2,1 0
node_bonus -1,1 0
node -1,2 0
node -1,3 0
node -1,4 0
node -1,5 0
node 0,5 0
node_start 1,5 0
node 1,4 0
node 1,3 0
node 1,2 0
node_bonus 1,1 0
node 2,1 0
node 3,1 0
node 4,1 0
node 5,1 0
node 5,0 0
node_start 5,-1 0
node 4,-1 0
node 3,-1 0
node 2,-1 0
node_bonus 1,-1 0
node 1,-2 0
node 1,-3 0
node 1,-4 0
node 1,-5 0
node 0,-5 0
node_start -1,-5 0
node -1,-4 0
node -1,-3 0
node -1,-2 0
node_bonus -1,-1 0
node -2,-1 0
node -3,-1 0
node -4,-1 0
node -5,-1 0
node -5,0 0
#Node Home
node_home_black -4,0 0
node_home_black -3,0 0
node_home_black -2,0 0
node_home_black -1,0 0
node_home_green 0,-4 0
node_home_green 0,-3 0
node_home_green 0,-2 0
node_home_green 0,-1 0
node_home_yellow 0,4 0
node_home_yellow 0,3 0
node_home_yellow 0,2 0
node_home_yellow 0,1 0
node_home_blue 4,0 0
node_home_blue 3,0 0
node_home_blue 2,0 0
node_home_blue 1,0 0
# Randomly Distributed Trees within Radius 12 to 40
treeSmall 10,15 180
treeBig -15,12 45
treeSmall -8,-22 270
treeBig 22,8 90
treeSmall -18,-10 135
treeBig 9,24 300
treeSmall 17,-9 60
treeBig -20,5 330
treeSmall -14,18 200
treeBig 25,-7 120
treeBig -12,-18 150
treeSmall 19,-16 45
treeBig 7,10 90
treeBig -19,-9 270
treeSmall 21,4 110
treeBig -11,17 300
treeSmall 3,-21 360
treeSmall -23,14 100
treeBig 4,26 330
treeSmall 12,13 270
treeBig -18,8 45
treeBig 11,-10 135
treeSmall 16,5 180
treeBig -13,-17 330
treeSmall -2,14 270
#treeBig 7,9 300
treeSmall 23,-10 240
treeBig -6,18 180
treeSmall 5,27 270
treeBig 14,-11 60
treeSmall 9,-16 180
treeBig -12,22 240
treeBig 18,7 360
treeSmall -24,-4 200
treeBig -8,21 300
treeSmall 12,-19 120
treeBig 6,-12 180
treeSmall -11,10 75
treeBig 9,6 270
treeSmall 8,-14 150
treeBig 3,18 30
treeSmall 17,13 100
treeBig -9,20 90
treeBig 6,-22 330
treeSmall -20,7 45
treeBig 21,11 150
treeSmall 15,-18 270
treeBig -3,-12 200
treeBig 12,-28 330
treeSmall -17,-7 120
treeBig -10,9 300
treeSmall 2,-14 240
treeBig 24,2 360
treeSmall 4,-13 300
treeBig -19,20 90
#treeSmall -11,5 45
treeBig 15,9 180
treeSmall -6,10 240
treeBig 3,15 30
treeSmall 9,-19 150
treeBig -21,-4 330
treeSmall 19,11 270
treeSmall 12,24 110
treeBig -13,15 45
treeSmall 7,-15 240
treeBig 26,-8 300
treeSmall -16,14 120
treeBig 14,18 360
treeSmall 8,21 100
treeBig -8,-18 240
treeSmall 9,15 180
treeBig 10,-20 270
treeSmall 2,27 90
treeBig 18,12 300
treeSmall -10,-14 150
treeBig -15,16 330
treeSmall -9,19 45
treeBig 17,-14 120
treeSmall 5,-25 180
treeBig 7,23 30
treeSmall -14,-12 200
treeBig 6,-16 300
treeSmall -20,-8 100
treeBig 4,11 240
treeSmall 24,-15 90
treeSmall -19,-19 360
treeBig 20,8 45
treeSmall 3,22 270
treeBig 13,-9 180
treeSmall -11,18 150
treeBig -17,-4 300
treeSmall 5,-14 240
treeBig 9,17 330
treeSmall 15,13 90
treeBig -21,18 30
treeSmall 6,20 100
treeBig -16,22 180
treeSmall -5,18 360
treeBig 22,11 45
treeSmall 10,-23 240
treeBig -10,-16 300
treeSmall -17,14 120
treeBig 20,4 150
treeSmall 11,-22 180
treeBig -24,-11 200
treeSmall 14,17 150
treeBig -8,-12 300
treeSmall 7,-18 100
treeBig -5,16 330
treeSmall 16,-14 200
treeBig 18,-8 90
treeSmall -23,-9 45
treeBig 24,10 300
treeSmall -4,19 180
treeBig 12,-5 330
treeSmall -19,16 100
treeBig 14,20 150
treeSmall 9,12 180
treeBig -22,8 60
treeSmall 6,18 360
treeBig 25,-9 45
treeBig -10,12 240
treeSmall 19,-17 100
treeSmall -13,19 90
treeSmall 16,-12 120
treeBig 22,-6 45
treeSmall -18,15 200
treeBig 14,-10 300
treeBig 6,10 330
treeSmall 17,18 90
treeBig -20,4 180
treeBig 19,-16 300
treeSmall -15,9 270
treeBig 12,22 360

View File

@@ -0,0 +1,40 @@
// Samplers for textures
uniform sampler2D m_Texture;
uniform sampler2D m_OutlineDepthTexture;
uniform sampler2D m_DepthTexture;
// Input texture coordinates from the vertex shader
in vec2 texCoord;
// Resolution of the screen and outline settings
uniform vec2 m_Resolution;
uniform vec4 m_OutlineColor;
uniform float m_OutlineWidth;
// Output color of the fragment
out vec4 fragColor;
void main() {
// Sample depth textures
vec4 depth = texture(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
// Sample the main texture
vec4 color = texture(m_Texture, texCoord);
// Determine whether to apply the outline color
if (depth == vec4(0.0) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
fragColor = m_OutlineColor; // Apply outline color
} else {
fragColor = color; // Use the original texture color
}
}

View File

@@ -0,0 +1,21 @@
MaterialDef Cartoon Edge {
MaterialParameters {
Int NumSamples
Int NumSamplesDepth
Texture2D Texture
Texture2D OutlineDepthTexture
Texture2D DepthTexture
Vector2 Resolution
Color OutlineColor
Float OutlineWidth
}
Technique {
VertexShader GLSL150: MatDefs/SelectObjectOutliner/Post15.vert
FragmentShader GLSL150: MatDefs/SelectObjectOutliner/Outline.frag
WorldParameters {
}
}
}

View File

@@ -0,0 +1,18 @@
// Use 'in' instead of 'varying' for inputs from the vertex shader
in vec2 texCoord;
// Declare a custom output variable for the fragment color
out vec4 fragColor;
// Uniform samplers
uniform sampler2D m_Texture;
uniform sampler2D m_NormalsTexture;
uniform sampler2D m_DepthTexture;
void main() {
// Sample the texture at the given texture coordinates
vec4 color = texture(m_Texture, texCoord);
// Assign the color to the output variable
fragColor = color;
}

View File

@@ -0,0 +1,19 @@
MaterialDef Cartoon Edge {
MaterialParameters {
Int NumSamples
Int NumSamplesDepth
Texture2D Texture
Texture2D NormalsTexture
Texture2D DepthTexture
}
Technique {
VertexShader GLSL150: MatDefs/SelectObjectOutliner/Post15.vert
FragmentShader GLSL150: MatDefs/SelectObjectOutliner/OutlinePre.frag
WorldParameters {
}
}
}

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