243 Commits

Author SHA1 Message Date
Felix Koppe
799bd096a0 Merge development 2024-12-01 21:56:52 +01:00
Felix Koppe
638cb93d7b Merge dev/client_beck 2024-12-01 21:51:01 +01:00
Cedric Beck
da2b1af698 fixed shutdown guiHandler bug 2024-12-01 20:45:24 +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
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
Daniel Grigencha
bf38a7e00c added a new method 'disconnectClient' in 'ServerSender' interface 2024-12-01 18:21:54 +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
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
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
Hanno Fleischer
6b2d775534 added 2 methods for Test usage in Board and PlayerData 2024-11-16 13:31:56 +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
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
488 changed files with 146970 additions and 2278 deletions

View File

@@ -6,7 +6,7 @@
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.mdga.client.Board.*" />
<option name="PATTERN" value="pp.mdga.client.board.Outline.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
@@ -14,4 +14,4 @@
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>

View File

@@ -9,6 +9,12 @@ 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

View File

@@ -1,290 +0,0 @@
package pp.mdga.client.Acoustic;
import com.jme3.system.NanoTimer;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.MdgaState;
import java.util.*;
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;
private NanoTimer fadeTimer = new NanoTimer();
private final float FADE_DURATION = 3.0f;
private final float CROSSFADE_DURATION = 1.5f;
private float mainVolume = 1.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
private GameMusic scheduled = null;
private GameMusic playing = null;
private ArrayList<GameSound> sounds = new ArrayList<>();
public AcousticHandler(MdgaApp app) {
this.app = app;
}
/**
* 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, 2.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.size() > 0) : "no more game music available";
asset = gameTracks.remove(0);
break;
case CEREMONY:
playGame = false;
asset = MusicAsset.CEREMONY;
break;
}
assert(null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop());
}
/**
* 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 current volume and handles track crossfading logic.
* This method is responsible for fading out the currently playing track,
* fading in the scheduled track, and handling crossfade between the two tracks.
*/
private void updateVolumeAndTrack() {
if (playing == null && scheduled != null && !fading) {
playing = scheduled;
scheduled = null;
playing.play();
return;
}
if (scheduled != null && !fading) {
fading = true;
fadeTimer.reset();
}
if (fading) {
float time = fadeTimer.getTimeInSeconds();
if (time <= FADE_DURATION) {
float t = Math.min(time / FADE_DURATION, 1.0f);
float oldVolume = lerp(1.0f, 0.0f, t);
if (playing != null) {
playing.update(getMusicVolumeTotal()* oldVolume);
}
}
if (time > FADE_DURATION && time <= FADE_DURATION + CROSSFADE_DURATION) {
float t = Math.min((time - FADE_DURATION) / CROSSFADE_DURATION, 1.0f);
float newVolume = lerp(0.0f, 1.0f, t);
if (!scheduled.isPlaying()) {
scheduled.play();
}
scheduled.update(getMusicVolumeTotal() * newVolume);
}
if (time > FADE_DURATION + CROSSFADE_DURATION) {
if (playing != null) {
playing.pause();
}
playing = scheduled;
scheduled = null;
fading = false;
}
} else if (playing != null) {
playing.update(getMusicVolumeTotal());
}
}
/**
* 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(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());
}
}
/**
* 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;
}
/**
* Sets the music volume level.
*
* @param musicVolume The desired music volume level.
*/
public void setMusicVolume(float musicVolume) {
this.musicVolume = musicVolume;
}
/**
* Sets the sound volume level.
*
* @param soundVolume The desired sound volume level.
*/
public void setSoundVolume(float soundVolume) {
this.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

@@ -1,4 +0,0 @@
package pp.mdga.client.Animation;
public enum MdgaAnimation {
}

View File

@@ -0,0 +1,134 @@
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,
cir("Models/cir/cir_newOrigin.obj"),
heer("Models/heer/heer_newOrigin.obj"),
jet,
lw("Models/lw/lw_newOrigin.obj"),
marine("Models/marine/marine_newOrigin.obj"),
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("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,
ship(0.8f),
smallTent,
tank,
world("Models/world_new/world_export_new.obj", "Models/world_new/world_new_diff.png", 1.2f),
shield_ring("Models/shield_ring/shield_ring.obj", null),
tree_small("Models/tree_small/tree_small.obj", "Models/tree_small/tree_small_diff.png"),
tree_big("Models/tree_big/tree_big.obj", "Models/tree_big/tree_big_diff.png"),
turboCard("Models/turboCard/turboCard.obj", "Models/turboCard/turboCard_diff.png"),
turboSymbol("Models/turboCard/turboSymbol.obj", "Models/turboCard/turboCard_diff.png"),
swapCard("Models/swapCard/swapCard.obj", "Models/swapCard/swapCard_diff.png"),
swapSymbol("Models/swapCard/swapSymbol.obj", "Models/swapCard/swapCard_diff.png"),
shieldCard("Models/shieldCard/shieldCard.obj", "Models/shieldCard/shieldCard_diff.png"),
shieldSymbol("Models/shieldCard/shieldSymbol.obj", "Models/shieldCard/shieldCard_diff.png"),
dice("Models/dice/dice.obj", "Models/dice/dice_diff.jpeg")
;
private final String modelPath;
private final String diffPath;
private final float size;
private static final String root = "Models/";
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* Gets the scaling factor for the asset.
*
* @return The size of the asset.
*/
public float getSize() {
return size;
}
}

View File

@@ -1,3 +0,0 @@
package pp.mdga.client.Board;
record AssetOnMap(BoardAsset boardAsset, int x, int y){}

View File

@@ -1,63 +0,0 @@
package pp.mdga.client.Board;
enum BoardAsset {
bigTent,
cardStack,
cir,
heer,
jet,
lw,
marine,
node_home_blue("./node_home/node_home.j3o", "./node_home/node_home_blue_diff.png"),
node_home_black("./node_home/node_home.j3o", "./node_home/node_home_black_diff.png"),
node_home_green("./node_home/node_home.j3o", "./node_home/node_home_green_diff.png"),
node_home_yellow("./node_home/node_home.j3o", "./node_home/node_home_yellow_diff.png"),
node_normal,
node_start("./node_normal/node_normal.j3o", "./node_normal/node_start_diff.png"),
node_bonus("./node_normal/node_normal.j3o", "./node_normal/node_bonus_diff.png"),
radar,
shieldCard,
ship,
smallTent,
swapCard,
tank,
turboCard,
world(1.2f);
private final String modelPath;
private final String diffPath;
private final float size;
BoardAsset(){
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = 1f;
}
BoardAsset(String modelPath, String diffPath){
this.modelPath = modelPath;
this.diffPath = diffPath;
this.size = 1f;
}
BoardAsset(float size){
String folderFileName = "./" + name() + "/" + name();
this.modelPath = folderFileName + ".j3o";
this.diffPath = folderFileName + "_diff.png";
this.size = size;
}
public String getModelPath() {
return modelPath;
}
public String getDiffPath() {
return diffPath;
}
public float getSize(){
return size;
}
}

View File

@@ -1,95 +0,0 @@
package pp.mdga.client.Board;
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.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import pp.mdga.client.MdgaApp;
import java.util.ArrayList;
import java.util.List;
public class BoardView {
private static final float GRID_SIZE = 1.72f;
private static final float GRID_ELEVATION = 0.0f;
private static final String MAP_NAME = "circle_map.mdga";
private final MdgaApp app;
private PileControl drawPile = null;
private PileControl discardPile = null;
private ArrayList<NodeControl> infield = new ArrayList<NodeControl>(40);
private ArrayList<PieceControl> pieces;
public BoardView(MdgaApp app) {
assert(app != null) : "app is null";
this.app = app;
pieces = new ArrayList<PieceControl>(4 * 4);
initMap();
initCamera();
}
private void initCamera() {
app.getFlyByCamera().setEnabled(true);
int zoom = 20;
app.getCamera().setLocation(new Vector3f(zoom,0,zoom));
app.getCamera().lookAt(new Vector3f(0,0,0), new Vector3f(0,0,1));
DirectionalLight sun = new DirectionalLight();
sun.setColor(ColorRGBA.White);
sun.setDirection(new Vector3f(-1,0,-1));
app.getRootNode().addLight(sun);
AmbientLight ambient = new AmbientLight();
ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1));
app.getRootNode().addLight(ambient);
final int SHADOWMAP_SIZE= 1024 * 8;
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(app.getAssetManager(), SHADOWMAP_SIZE, 4);
dlsr.setLight(sun);
app.getViewPort().addProcessor(dlsr);
}
private void initMap() {
List<AssetOnMap> assetsOnMap = MapLoader.loadMap(MAP_NAME);
for (AssetOnMap aom : assetsOnMap){
int x = aom.x();
int y = aom.y();
Vector3f pos = gridToWorld(x,y);
if(aom.boardAsset().name().contains("node")) {
infield.add(new NodeControl(app, pos, aom.boardAsset()));
} else {
Spatial model = createModel(aom.boardAsset());
model.setLocalTranslation(pos);
}
}
}
private Spatial createModel(BoardAsset boardAsset){
String modelName = boardAsset.getModelPath();
String texName = boardAsset.getDiffPath();
Spatial model = app.getAssetManager().loadModel(modelName);
model.scale(boardAsset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
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);
app.getRootNode().attachChild(model);
return model;
}
private static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
}

View File

@@ -1,66 +0,0 @@
package pp.mdga.client.Board;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
class MapLoader {
static public List<AssetOnMap> loadMap(String mapName) {
List<AssetOnMap> assetsOnMap = new ArrayList<>();
try (InputStream inputStream = ClassLoader.getSystemClassLoader().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 == 2) : "parts.lenghth != 2";
String assetName = parts[0];
String[] coordinates = parts[1].split(",");
assert(coordinates.length == 2) : "coordinates.lenghth != 2";
int x = Integer.parseInt(coordinates[0]);
int y = Integer.parseInt(coordinates[1]);
BoardAsset boardAsset = getLoadedAsset(assetName);
assetsOnMap.add(new AssetOnMap(boardAsset, x, y));
}
} catch (Exception e) {
e.printStackTrace();
}
return assetsOnMap;
}
static private BoardAsset getLoadedAsset(String assetName) {
return switch(assetName){
case "node" -> BoardAsset.node_normal;
case "node_start" -> BoardAsset.node_start;
case "node_bonus" -> BoardAsset.node_bonus;
case "node_home_blue" -> BoardAsset.node_home_blue;
case "node_home_yellow" -> BoardAsset.node_home_yellow;
case "node_home_black" -> BoardAsset.node_home_black;
case "node_home_green" -> BoardAsset.node_home_green;
case "world" -> BoardAsset.world;
case "tent_big" -> BoardAsset.bigTent;
case "tent_small" -> BoardAsset.smallTent;
case "stack" -> BoardAsset.cardStack;
case "jet" -> BoardAsset.jet;
case "radar" -> BoardAsset.radar;
case "ship" -> BoardAsset.ship;
case "tank" -> BoardAsset.tank;
default -> throw new IllegalStateException("Unexpected asset in .mdga file: " + assetName);
};
}
}

View File

@@ -1,30 +0,0 @@
package pp.mdga.client.Board;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import pp.mdga.client.MdgaApp;
class NodeControl {
private final MdgaApp app;
private Spatial model;
NodeControl(MdgaApp app, Vector3f pos, BoardAsset boardAsset) {
this.app = app;
String modelName = boardAsset.getModelPath();
String texName = boardAsset.getDiffPath();
Spatial model = app.getAssetManager().loadModel(modelName);
model.scale(boardAsset.getSize());
model.rotate((float) Math.toRadians(0), 0, (float) Math.toRadians(90));
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);
app.getRootNode().attachChild(model);
model.setLocalTranslation(pos);
}
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Board;
class PieceControl {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Board;
class PileControl {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Dialog;
public class Dialog {
}

View File

@@ -1,26 +0,0 @@
package pp.mdga.client.Dialog;
import pp.dialog.DialogManager;
import pp.mdga.client.MdgaApp;
public class DialogView {
private MdgaApp app;
private DialogManager dialogManager = new DialogManager(app);
private StartDialog dialog;
public DialogView(MdgaApp app) {
this.app = app;
}
DialogManager getDialogManager() {
return dialogManager;
}
public void mainMenu() {
//dialogManager = new DialogManager(app);
//di
//MainMenuDialog mainMenuDialog = new MainMenuDialog(app);
}
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Dialog;
public class MenuDialog {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Dialog;
public class NetworkDialog {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Dialog;
public class SoundDialog {
}

View File

@@ -1,35 +0,0 @@
package pp.mdga.client.Dialog;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.dialog.DialogBuilder;
import pp.dialog.SimpleDialog;
import pp.mdga.client.MdgaApp;
public class StartDialog extends SimpleDialog {
StartDialog(MdgaApp app) {
super(app.getDialogView().getDialogManager());
Checkbox serverHost = new Checkbox("sdgfsdg");
serverHost.setChecked(false);
//serverHost.addClickCommands(s -> toggleServerHost());
final Container input = new Container(new SpringGridLayout());
input.addChild(new Label("sdgsgsdg"));
//input.addChild(host, 1);
input.addChild(new Label("sdfdsgsdgsdg"));
//input.addChild(port, 1);
input.addChild(serverHost);
DialogBuilder.simple(app.getDialogView().getDialogManager())
.setTitle("server.dialog")
.setOkButton("button.connect")
.setNoButton("button.cancel")
.setOkClose(false)
.setNoClose(false)
.build(this);
}
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Dialog;
public class VideoDialog {
}

View File

@@ -1,5 +0,0 @@
package pp.mdga.client.Gui;
public class GuiView {
}

View File

@@ -0,0 +1,288 @@
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.view.GameView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.game.Piece;
import pp.mdga.notification.SelectableCardsNotification;
import java.util.List;
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;
/**
* 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();
}
/**
* 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("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("MouseVertical", new MouseAxisTrigger(MouseInput.AXIS_Y, false), new MouseAxisTrigger(MouseInput.AXIS_Y, true)); //Mouse Up Down 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", "Test");
inputManager.addListener(analogListener, "MouseLeft", "MouseRight", "MouseScrollUp", "MouseScrollDown");
}
private boolean test = false;
/**
* 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 (app.getView() instanceof GameView gameView) {
CardControl cardLayerSelect = checkHover(gameView.getGuiHandler().getCardLayerCamera(), gameView.getGuiHandler().getCardLayerRootNode(), CardControl.class);
OutlineControl boardSelect = checkHover(app.getCamera(), app.getRootNode(), OutlineControl.class);
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("Test") &&isPressed){
if(app.getView() instanceof GameView gameView){
app.getNotificationSynchronizer().addTestNotification(new SelectableCardsNotification(List.of(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) {
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) {
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;
}
/**
* Gets the current scroll value.
*
* @return The scroll value as an integer.
*/
public int getScroll() {
return scrollValue;
}
}

View File

@@ -1,29 +1,67 @@
package pp.mdga.client;
import com.jme3.app.SimpleApplication;
import com.jme3.system.NanoTimer;
import pp.mdga.client.Acoustic.AcousticHandler;
import pp.mdga.client.Acoustic.MdgaSound;
import pp.mdga.client.Animation.AnimationHandler;
import com.simsilica.lemur.GuiGlobals;
import pp.mdga.client.acoustic.AcousticHandler;
import pp.mdga.client.animation.AnimationHandler;
import com.jme3.system.AppSettings;
import pp.mdga.client.Board.BoardView;
import pp.mdga.client.Dialog.DialogView;
import pp.mdga.client.view.*;
/**
* 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 {
/** Handles animations in the application. */
private AnimationHandler animationHandler;
/** Handles acoustic effects and state-based sounds. */
private AcousticHandler acousticHandler;
private BoardView boardView;
private DialogView dialogView;
NanoTimer test = new NanoTimer();
private MdgaState testState = MdgaState.MAIN;
/** 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 static final float IMAGE_SCALE = 1.5f;
/** The main menu view. */
private MdgaView mainView;
/** The lobby view. */
private MdgaView lobbyView;
/** The game view. */
private MdgaView gameView;
/** The ceremony view. */
private MdgaView ceremonyView;
/**
* 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) {
AppSettings settings = new AppSettings(true);
settings.setSamples(128);
settings.setCenterWindow(true);
settings.setWidth(1280);
settings.setHeight(720);
settings.setWidth(1920);
settings.setHeight(1080);
settings.setVSync(false);
MdgaApp app = new MdgaApp();
app.setSettings(settings);
@@ -31,55 +69,155 @@ public static void main(String[] args) {
app.start();
}
/**
* Initializes the application by setting up handlers, views, and entering the default state.
*/
@Override
public void simpleInitApp() {
GuiGlobals.initialize(this);
inputManager.deleteMapping("SIMPLEAPP_Exit");
flyCam.setEnabled(false);
animationHandler = new AnimationHandler(this);
acousticHandler = new AcousticHandler(this);
boardView = new BoardView(this);
dialogView = new DialogView(this);
notificationSynchronizer = new NotificationSynchronizer(this);
inputSynchronizer = new InputSynchronizer(this);
modelSynchronizer = new ModelSynchronizer(this);
//dialogView.mainMenu();
//acousticHandler.playState(MdgaState.GAME);
mainView = new MainView(this);
lobbyView = new LobbyView(this);
gameView = new GameView(this);
ceremonyView = new CeremonyView(this);
acousticHandler.playSound(MdgaSound.LOST);
acousticHandler.playSound(MdgaSound.VICTORY);
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) {
view.update(tpf);
acousticHandler.update();
//test.reset();
if(test.getTimeInSeconds() > 10){
if(testState == MdgaState.MAIN) {
testState = MdgaState.LOBBY;
acousticHandler.playState(MdgaState.MAIN);
} else if (testState == MdgaState.LOBBY) {
testState = MdgaState.CEREMONY;
acousticHandler.playState(MdgaState.LOBBY);
}
else {
testState = MdgaState.MAIN;
acousticHandler.playState(MdgaState.CEREMONY);
}
test.reset();
}
notificationSynchronizer.update();
}
/**
* 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 animation handler.
*
* @return the {@link AnimationHandler} instance
*/
public AnimationHandler getAnimationHandler() {
return animationHandler;
}
/**
* Gets the acoustic handler.
*
* @return the {@link AcousticHandler} instance
*/
public AcousticHandler getAcousticHandler() {
return acousticHandler;
}
public BoardView getBoardView() {
return boardView;
/**
* Gets the current state of the application.
*
* @return the current {@link MdgaState}
*/
public MdgaState getState() {
return state;
}
public DialogView getDialogView() {
return dialogView;
/**
* Gets the image scaling factor.
*
* @return the image scale as a float
*/
public float getImageScale() {
return IMAGE_SCALE;
}
/**
* 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() {
}
}

View File

@@ -1,48 +1,35 @@
package pp.mdga.client;
import pp.mdga.notification.Notification;
import pp.mdga.notification.PieceInGameNotification;
import pp.mdga.notification.PlayerInGameNotification;
/**
* Enum representing the various states of the MdgaApp application.
* Each state corresponds to a distinct phase or mode of the application.
*/
public enum MdgaState {
NONE {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
MAIN {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
LOBBY {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
},
GAME {
@Override
void handleNotification(MdgaApp app, Notification notification) {
if(notification instanceof PlayerInGameNotification) {
//TODO
}
else if(notification instanceof PieceInGameNotification){
//TODO
}
else {
throw new RuntimeException("unexpected notification");
}
}
},
CEREMONY {
@Override
void handleNotification(MdgaApp app, Notification notification) {
throw new RuntimeException("unexpected notification");
}
};
abstract void handleNotification(MdgaApp app, Notification notification);
/**
* 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,187 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
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;
ModelSynchronizer(MdgaApp app) {
this.app = app;
}
private Color testColor;
private int test = 0;
public void animationEnd() {
}
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.noConfirm();
}
}
public void confirm() {
LOGGER.log(Level.INFO, "confirm");
GameView gameView = (GameView) app.getView();
if(a != null && b != null) {
//swap
gameView.getBoardHandler().clearSelectable();
} else if (a != null) {
//piece
gameView.getBoardHandler().clearSelectable();
} else if (card != null){
//card
gameView.getGuiHandler().clearSelectableCards();
} else {
throw new RuntimeException("nothing to confirm");
}
gameView.noConfirm();
}
public void selectTsk(Color color) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "selectTsk: {0}", color);
LobbyView view = (LobbyView) app.getView();
view.setTaken(color, true, true, "OwnPlayerName");
testColor = color;
}
public void unselectTsk() {
// TODO call from somewhere
LOGGER.log(Level.INFO, "unselectTsk");
}
public void rolledDice() {
// TODO call from somewhere
LOGGER.log(Level.INFO, "rolledDice");
}
public void setName(String name) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "setName: {0}", name);
}
public void setReady(boolean ready) {
LOGGER.log(Level.INFO, "setReady");
LobbyView view = (LobbyView) app.getView();
view.setReady(testColor, ready);
test++;
if(test > 2) {
testColor = null;
test = 0;
enter(MdgaState.GAME);
}
}
public void setHost(int port) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "setHost: {0}", port);
enter(MdgaState.LOBBY);
}
public void setJoin(String ip, int port) {
// TODO call from somewhere
LOGGER.log(Level.INFO, "setJoin with IP: {0}, Port: {1}", new Object[]{ip, port});
enter(MdgaState.LOBBY);
}
public void leave() {
LOGGER.log(Level.INFO, "leave");
app.getAcousticHandler().playSound(MdgaSound.LEAVE);
enter(MdgaState.MAIN);
}
public void enter(MdgaState state) {
LOGGER.log(Level.INFO, "enter: {0}", state);
app.enter(state);
if (state == MdgaState.CEREMONY) {
CeremonyView ceremonyView = (CeremonyView) app.getView();
ceremonyView.addCeremonyParticipant(Color.AIRFORCE, 1, "ugidffdg");
ceremonyView.addCeremonyParticipant(Color.ARMY, 2, "ugidffdg");
ceremonyView.addCeremonyParticipant(Color.NAVY, 3, "ugidffdg");
ceremonyView.addCeremonyParticipant(Color.CYBER, 4, "ugidffdg");
ceremonyView.addStatisticsRow("player sdgsd", 1, 2, 3, 4, 5, 6);
ceremonyView.addStatisticsRow("player sdgsd", 1, 2, 3, 4, 5, 6);
ceremonyView.addStatisticsRow("player sdgsd", 1, 2, 3, 4, 5, 6);
ceremonyView.addStatisticsRow("player sdgsd", 1, 2, 3, 4, 5, 6);
ceremonyView.addStatisticsRow("Gesamt", 1, 2, 3, 4, 5, 6);
}
if (state == MdgaState.GAME) {
GameView gameView = (GameView) app.getView();
//app.getNotificationSynchronizer().addTestNotification(new DrawCardNotification(Color.AIRFORCE, BonusCard.SHIELD));
selectPiece(UUID.randomUUID());
}
if (state == MdgaState.LOBBY) {
LobbyView lobbyView = (LobbyView) app.getView();
app.getNotificationSynchronizer().addTestNotification(new TskSelectNotification(Color.CYBER, "blablabupp", false));
app.getNotificationSynchronizer().addTestNotification(new TskSelectNotification(Color.ARMY, "Spieler 2", false));
lobbyView.setReady(Color.ARMY, true);
}
}
}

View File

@@ -1,23 +1,187 @@
package pp.mdga.client;
import pp.mdga.notification.Notification;
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 MdgaState state = MdgaState.MAIN;
private ArrayList<Notification> notifications = new ArrayList<>();
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
void update() {
ArrayList<Notification> notifications = new ArrayList<>();
//TODO fetch model notifications
public void addTestNotification(Notification n) {
notifications.add(n);
}
public void update() {
//TODO fetch model notifications
for (Notification n : notifications) {
state.handleNotification(app, n);
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.toString());
}
}
notifications.clear();
}
private void handleMain(Notification notification) {
if (notification instanceof LobbyDialogNotification) {
app.enter(MdgaState.LOBBY);
} else {
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
private void handleLobby(Notification notification) {
LobbyView lobbyView = (LobbyView) app.getView();
if (notification instanceof TskSelectNotification n) {
lobbyView.setTaken(n.getColor(), true, n.isSelf(), n.getName());
lobbyView.setTaken(n.getColor(), true, false, n.getName());
} 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.enter(MdgaState.GAME);
} else {
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
private void handleGame(Notification notification) {
GameView gameView = (GameView) app.getView();
GuiHandler guiHandler = gameView.getGuiHandler();
BoardHandler boardHandler = gameView.getBoardHandler();
if (notification instanceof AcquireCardNotification n) {
guiHandler.addCard(n.getBonusCard());
} else if (notification instanceof ActivePlayerNotification n) {
gameView.getGuiHandler().setActivePlayer(n.getColor());
} 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 DicingNotification n) {
//TODO ???
} else if (notification instanceof DrawCardNotification n) {
guiHandler.drawCard(n.getColor());
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.moveHomePiece(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
} else if (notification instanceof InterruptNotification) {
app.enter(MdgaState.LOBBY);
} else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) {
//StartMove
boardHandler.movePieceStart(n.getPiece(), n.getMoveIndex());
}
else {
//InfieldMove
boardHandler.movePiece(n.getPiece(), n.getStartIndex(), n.getMoveIndex());
}
} else if (notification instanceof ThrowPieceNotification n) {
boardHandler.throwPiece(n.getPieceId());
} else if (notification instanceof NoShieldNotification n) {
boardHandler.unshieldPiece(n.getPieceId());
} else if (notification instanceof PlayCardNotification n) {
switch(n.getCard()){
case SWAP -> guiHandler.swap();
case TURBO -> guiHandler.turbo();
case SHIELD -> guiHandler.shield();
default -> throw new RuntimeException("invalid card");
}
} else if (notification instanceof PlayerInGameNotification n) {
boardHandler.addPlayer(n.getColor(),n.getPiecesList());
guiHandler.addPlayer(n.getColor(),n.getName());
} else if (notification instanceof ResumeNotification) {
//TODO
} else if (notification instanceof RollDiceNotification n) {
if(n.getColor() == gameView.getOwnColor()){
guiHandler.rollDice(n.getEyes(), n.isTurbo() ? n.getMultiplier() : -1);
}
else {
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());
} 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.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.getMoveIndexe(), n.getHomeMoves());
} else if (notification instanceof SelectableSwapNotification n) {
boardHandler.outlineSwap(n.getOwnPieces(), n.getEnemyPieces());
} else if (notification instanceof SelectableShieldNotification n) {
boardHandler.outlineShield(n.getPieces());
} else if (notification instanceof TurboActiveNotification){
guiHandler.turbo();
} else {
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
private void handleCeremony(Notification notification) {
if (notification instanceof StartDialogNotification) {
app.enter(MdgaState.MAIN);
} else {
throw new RuntimeException("notification not expected: " + notification.toString());
}
}
}

View File

@@ -0,0 +1,445 @@
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;
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

@@ -1,4 +1,4 @@
package pp.mdga.client.Acoustic;
package pp.mdga.client.acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
@@ -14,19 +14,21 @@ 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 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.
* @param loop A flag indicating whether the music should loop once it finishes.
*/
GameMusic(MdgaApp app, MusicAsset asset, float volume, float subVolume, boolean loop) {
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);
@@ -42,7 +44,7 @@ class GameMusic {
* If the music is not available, no action is performed.
*/
void play() {
if(null == music) {
if (null == music) {
return;
}
@@ -54,7 +56,7 @@ void play() {
* If the music is not available or is not playing, no action is performed.
*/
void pause() {
if(null == music) {
if (null == music) {
return;
}
@@ -102,9 +104,13 @@ boolean nearEnd(float thresholdSeconds) {
* @param newVolume The new total volume for the music.
*/
void update(float newVolume) {
if(volume != newVolume) {
if (volume != newVolume) {
volume = newVolume;
music.setVolume(volume * subVolume);
}
}
float getPause() {
return pause;
}
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Acoustic;
package pp.mdga.client.acoustic;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
@@ -26,11 +26,11 @@ class GameSound {
/**
* 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 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.
* @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;
@@ -62,21 +62,21 @@ boolean isPlaying() {
* @param newVolume The new total volume for the sound.
*/
void update(float newVolume) {
if(!playing && timer.getTimeInSeconds() > delay) {
if (!playing && timer.getTimeInSeconds() > delay) {
sound.play();
playing = true;
}
if(!playing) {
if (!playing) {
return;
}
if(volume != newVolume) {
if (volume != newVolume) {
volume = newVolume;
sound.setVolume(volume * subVolume);
}
if(sound != null && sound.getStatus() == AudioSource.Status.Playing) {
if (sound != null && sound.getStatus() == AudioSource.Status.Playing) {
finished = true;
}
}

View File

@@ -1,10 +1,10 @@
package pp.mdga.client.Acoustic;
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.
*/
@@ -19,5 +19,16 @@ public enum MdgaSound {
DESELECT,
HURRY,
VICTORY,
LOST;
LOST,
BUTTON_PRESSED,
WRONG_INPUT,
UI_CLICK,
START,
THROW,
POWERUP,
SELF_READY,
OTHER_READY,
OTHER_CONNECTED,
NOT_READY,
LEAVE,
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Acoustic;
package pp.mdga.client.acoustic;
/**
* Enum representing various music assets used in the game.
@@ -7,29 +7,30 @@
* 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", 1.0f),
LOBBY("DeadPlanet.wav", 1.0f),
CEREMONY("80s,Disco,Life.wav", 1.0f),
GAME_1("NeonRoadTrip.wav", false, 1.0f),
GAME_2("NoPressureTrance.wav", false, 1.0f),
GAME_3("TheSynthRave.wav", false, 1.0f),
GAME_4("LaserParty.wav", false, 1.0f),
GAME_5("RetroNoir.wav", false, 1.0f),
GAME_6("SpaceInvaders.wav", false, 1.0f);
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 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 = "music/" + name;
this.path = root + name;
this.loop = false;
this.subVolume = subVolume;
}
@@ -37,12 +38,12 @@ enum MusicAsset {
/**
* 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 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 = "music/" + name;
this.path = root + name;
this.loop = loop;
this.subVolume = subVolume;
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Acoustic;
package pp.mdga.client.acoustic;
/**
* Enum representing various sound assets used in the game.
@@ -17,7 +17,18 @@ enum SoundAsset {
DESELECT(""),
HURRY(""),
VICTORY("LevelUp2.wav"),
LOST("GameOver.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"),
CONNECTED("connected.wav");
private final String path;
@@ -27,7 +38,7 @@ enum SoundAsset {
* @param name The name of the sound file.
*/
SoundAsset(String name) {
this.path = "sound/" + name;
this.path = "Sounds/" + name;
}
/**

View File

@@ -1,7 +1,7 @@
package pp.mdga.client.Acoustic;
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) { }
record SoundAssetDelayVolume(SoundAsset asset, float subVolume, float delay) {}

View File

@@ -1,7 +1,10 @@
package pp.mdga.client.Animation;
package pp.mdga.client.animation;
abstract class Animation {
abstract void play();
abstract void stop();
abstract boolean isOver();
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Animation;
package pp.mdga.client.animation;
import pp.mdga.client.MdgaApp;
@@ -16,11 +16,11 @@ public void playAnimation(MdgaAnimation type) {
}
public void update() {
if(null == animation) {
if (null == animation) {
return;
}
if(animation.isOver()) {
if (animation.isOver()) {
animation = null;
//trigger next state in model

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Animation;
package pp.mdga.client.animation;
class EmptyAnimation extends Animation {
@Override

View File

@@ -0,0 +1,4 @@
package pp.mdga.client.animation;
public enum MdgaAnimation {
}

View File

@@ -0,0 +1,5 @@
package pp.mdga.client.board;
import pp.mdga.client.Asset;
record AssetOnMap(Asset asset, int x, int y, float rot) {}

View File

@@ -0,0 +1,481 @@
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.gui.DiceControl;
import pp.mdga.game.Color;
import java.util.*;
public class BoardHandler {
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";
private final MdgaApp app;
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<UUID, Color> pieceColor;
private Node rootNodeBoard;
private final Node rootNode;
private final FilterPostProcessor fpp;
private boolean isInitialised;
private List<PieceControl> selectableOwnPieces;
private List<PieceControl> selectableEnemyPieces;
private List<NodeControl> outlineNodes;
private PieceControl selectedOwnPiece;
private PieceControl selectedEnemyPiece;
private DiceControl diceControl;
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;
}
public void init() {
isInitialised = true;
selectableOwnPieces = new ArrayList<>();
selectableEnemyPieces = new ArrayList<>();
outlineNodes = new ArrayList<>();
selectedOwnPiece = null;
selectedEnemyPiece = null;
initMap();
rootNode.attachChild(rootNodeBoard);
}
public void shutdown(){
clearSelectable();
isInitialised = false;
rootNode.detachChild(rootNodeBoard);
}
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);
}
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);
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);
}
}
}
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");
};
}
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;
}
private static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
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);
}
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;
}
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());
}
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());
}
//public methods****************************************************************************************************
public void addPlayer(Color color, List<UUID> uuid) {
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
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);
PieceControl pieceControl = displayAndControl(assetOnMap, new PieceControl(assetOnMap.rot(), app.getAssetManager(), app, fpp));
pieceControl.setRotation(assetOnMap.rot());
movePieceToNode(pieceControl, waitNodes.get(i));
pieces.put(uuid.get(i), pieceControl);
pieceColor.put(uuid.get(i), color);
addItemToMapList(waitingPiecesMap, color, pieceControl);
}
}
public void moveHomePiece(UUID uuid, int index){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
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()));
}
public void movePieceStart(UUID uuid, int nodeIndex){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
Color color = pieceColor.get(uuid);
if(color == null) throw new RuntimeException("uuid is not mapped to a color");
PieceControl pieceControl = pieces.get(uuid);
movePieceToNode(pieceControl, infield.get(nodeIndex));
removeItemFromMapList(waitingPiecesMap, color, pieceControl);
}
public void movePiece(UUID uuid, int curIndex, int moveIndex){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
movePieceRek(uuid, curIndex, moveIndex);
}
public void throwPiece(UUID uuid){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
Color color = pieceColor.get(uuid);
if(color == null) throw new RuntimeException("uuid is not mapped to a color");
PieceControl pieceControl = pieces.get(uuid);
List<NodeControl> waitNodes = waitingNodesMap.get(color);
List<PieceControl> waitPieces = waitingPiecesMap.get(color);
movePieceToNode(pieceControl, waitNodes.get(waitPieces.size()));
pieceControl.rotateInit();
}
public void shieldPiece(UUID uuid){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
pieces.get(uuid).activateShield();
}
public void unshieldPiece(UUID uuid){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
pieces.get(uuid).deactivateShield();
}
public void suppressShield(UUID uuid){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
pieces.get(uuid).suppressShield();
}
public void swapPieces(UUID piece1, UUID piece2){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
PieceControl piece1Control = pieces.get(piece1);
PieceControl piece2Control = pieces.get(piece2);
if(piece1Control == null) throw new RuntimeException("piece1 UUID is not valid");
if(piece2Control == null) throw new RuntimeException("piece2 UUID is not valid");
float rot1 = piece1Control.getRotation();
float rot2 = piece2Control.getRotation();
piece1Control.setRotation(rot2);
piece2Control.setRotation(rot1);
Vector3f pos1 = piece1Control.getLocation().clone();
Vector3f pos2 = piece2Control.getLocation().clone();
piece1Control.setLocation(pos2);
piece2Control.setLocation(pos1);
}
public void highlight(UUID uuid, boolean bool){
if (!isInitialised) throw new RuntimeException("BoardHandler is not initialized");
pieces.get(uuid).highlight(bool);
pieces.get(uuid).setSelectable(bool);
}
//called when (dice) moveNum is received from server to display the movable pieces and corresponding moveNodes
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);
}
}
//called when swap notification is received to highlight and select own/enemy 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);
}
}
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);
}
}
//called from inputSynchronizer when a piece is selectable
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");
}
//called when view is no longer needed to select pieces
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;
}
public void enableHover(UUID uuid){
pieces.get(uuid).setHoverable(true);
}
public void showDice(Color color){
rootNodeBoard.attachChild(diceControl.getSpatial());
diceControl.setPos(getWaitingPos(color).add(new Vector3f(0,0,4)));
diceControl.spin();
}
public void hideDice(){
diceControl.hide();
}
}

View File

@@ -0,0 +1,110 @@
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.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;
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;
public CameraHandler(MdgaApp app, FilterPostProcessor fpp) {
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);
sky = SkyFactory.createSky(app.getAssetManager(), "Images/sky/sky.dds", EnvMapType.EquirectMap).rotate(FastMath.HALF_PI*1,0,FastMath.HALF_PI*0.2f);
}
public void init() {
app.getRootNode().addLight(sun);
app.getRootNode().addLight(ambient);
app.getRootNode().attachChild(sky);
fpp.addFilter(dlsf);
}
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);
}
public void update(float scroll, float rotation) {
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 = 270f;
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);
}
}

View File

@@ -0,0 +1,92 @@
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;
class MapLoader {
private MapLoader() {
}
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;
}
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 "tree_small" -> Asset.tree_small;
case "tree_big" -> Asset.tree_big;
default -> throw new IllegalStateException("Unexpected value: " + assetName);
};
}
}

View File

@@ -0,0 +1,27 @@
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;
public class NodeControl extends OutlineControl {
private static final ColorRGBA OUTLINE_HIGHLIGHT_COLOR = ColorRGBA.White;
private static final int OUTLINE_HIGHLIGHT_WIDTH = 6;
public NodeControl(MdgaApp app, FilterPostProcessor fpp) {
super(app, fpp);
}
public Vector3f getLocation(){
return this.getSpatial().getLocalTranslation();
}
public void highlight() {
super.outline(OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
}

View File

@@ -0,0 +1,79 @@
package pp.mdga.client.board.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.board.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.board.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.board.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,75 @@
package pp.mdga.client.board;
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 com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.board.Outline.SelectObjectOutliner;
public class OutlineControl extends AbstractControl {
private static final int THICKNESS_DEFAULT = 6;
private final SelectObjectOutliner outlineOwn;
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);
}
public void outline(ColorRGBA color){
outlineOwn.select(spatial, color);
}
public void outline(ColorRGBA color, int width){
deOutline();
outlineOwn.select(spatial, color, width);
}
public void deOutline(){
outlineOwn.deselect(spatial);
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public void initSpatial(){
}
@Override
public void setSpatial(Spatial spatial){
if(this.spatial == null && spatial != null){
super.setSpatial(spatial);
initSpatial();
}
else{
super.setSpatial(spatial);
}
}
public MdgaApp getApp() {
return app;
}
}

View File

@@ -0,0 +1,185 @@
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;
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;
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;
}
public float getRotation() {
return (float) Math.toDegrees(spatial.getLocalRotation().toAngleAxis(new Vector3f(0,0,1)));
}
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);
}
public Vector3f getLocation(){
return spatial.getLocalTranslation();
}
@Override
protected void controlUpdate(float delta) {
if(shieldRing != null){
shieldRing.rotate(0, 0, delta * SHIELD_SPEED);
}
}
public void setLocation(Vector3f loc){
this.spatial.setLocalTranslation(loc);
}
@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);
}
public void activateShield(){
shieldRing = assetManager.loadModel(Asset.shield_ring.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);
}
public void deactivateShield(){
parentNode.detachChild(shieldRing);
shieldRing = null;
}
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();
}
public void highlight(boolean enemy) {
this.enemy = enemy;
highlight = true;
super.outline(enemy ? OUTLINE_ENEMY_COLOR : OUTLINE_OWN_COLOR, OUTLINE_HIGHLIGHT_WIDTH);
}
public void unHighlight(){
highlight = false;
deOutline();
}
public void hover(){
if(!hoverable) return;
super.outline(enemy ? OUTLINE_ENEMY_HOVER_COLOR : OUTLINE_OWN_HOVER_COLOR, OUTLINE_HOVER_WIDTH);
}
public void hoverOff(){
if(!hoverable) return;
if(select) select();
else if(highlight) highlight(enemy);
else deOutline();
}
public void unSelect(){
select = false;
if(highlight) highlight(enemy);
else deOutline();
}
public void select(){
if(!selectable) return;
select = true;
super.outline(enemy ? OUTLINE_ENEMY_SELECT_COLOR : OUTLINE_OWN_SELECT_COLOR, OUTLINE_SELECT_WIDTH);
}
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,5 @@
package pp.mdga.client.board;
class PileControl {
}

View File

@@ -0,0 +1,12 @@
package pp.mdga.client.board;
public enum Rotation {
UP,
RIGHT,
DOWN,
LEFT,
UP_LEFT,
UP_RIGHT,
DOWN_RIGHT,
DOWN_LEFT
}

View File

@@ -0,0 +1,161 @@
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;
}
}

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,334 @@
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,75 @@
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;
}
/**
* Displays the settings button by attaching it to the scene graph.
*/
@Override
public void show() {
release();
calculateRelative();
setRelative();
setImageRelative(pictureNormal);
node.attachChild(instance);
}
/**
* Handles hover behavior by changing the icon to the hover state.
*/
@Override
public void onHover() {
icon = new IconComponent("Images/Settings_Button_hover.png");
icon.setIconScale(0.02f * app.getImageScale());
icon.setHAlignment(HAlignment.Center);
icon.setVAlignment(VAlignment.Center);
instance.setIcon(icon);
}
/**
* Handles unhover behavior by restoring the icon to the normal state.
*/
@Override
public void onUnHover() {
icon = new IconComponent("Images/Settings_Button_normal.png");
icon.setIconScale(0.02f * app.getImageScale());
icon.setHAlignment(HAlignment.Center);
icon.setVAlignment(VAlignment.Center);
instance.setIcon(icon);
}
}

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,32 @@
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,68 @@
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.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 Dialog {
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);
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());
return portInput.getString();
}
public void resetPort() {
portInput.reset();
prefs.put("hostPort", "11111");
}
}

View File

@@ -1,4 +1,4 @@
package pp.mdga.client.Dialog;
package pp.mdga.client.dialog;
public class InterruptDialog {
}

View File

@@ -0,0 +1,88 @@
package pp.mdga.client.dialog;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;
import pp.mdga.client.MdgaApp;
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 Dialog {
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);
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());
return ipInput.getString();
}
public void resetIp() {
ipInput.reset();
prefs.put("joinIp", "");
}
public String getPort() {
prefs.put("joinPort", portInput.getString());
return portInput.getString();
}
public void resetPort() {
portInput.reset();
prefs.put("joinPort", "11111");
}
}

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,188 @@
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",
};
Random random = new Random();
name = names[random.nextInt(names.length)];
}
return name;
}
}

View File

@@ -0,0 +1,47 @@
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.view.MdgaView;
public class VideoSettingsDialog extends Dialog {
private MenuButton backButton;
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");
float offset = 2.8f;
backButton.setPos(new Vector2f(0, 1.8f));
}
@Override
protected void onShow() {
active = true;
backButton.show();
}
@Override
protected void onHide() {
active = false;
backButton.hide();
}
public void update() {
if(!active) {
return;
}
}
}

View File

@@ -0,0 +1,129 @@
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.Asset;
import pp.mdga.game.Color;
public class ActionTextHandler {
private Node root;
private BitmapFont font;
private AppSettings appSettings;
public 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;
}
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);
}
public void activePlayer(String name, Color color){
createTopText(new String[]{name," ist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
public void ownActive(Color color){
createTopText(new String[]{"Du"," bist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
public 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);
}
public 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);
}
public void ownDice(int diceNum){
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
}
public 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);
}
public 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());
}
public void drawCardOwn(Color color){
createTopText(new String[]{"Du"," erhälst eine Bonuskarte"}, 5,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");
};
}
public void hide(){
root.detachAllChildren();
}
}

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,115 @@
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.material.Material;
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.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import pp.mdga.client.Asset;
import java.util.*;
public class CardLayer extends AbstractAppState {
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);
final int SHADOWMAP_SIZE=1024*8;
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){
cardBuffer.add(card);
}
public void deleteSpatial(Spatial spatial){
root.detachChild(spatial);
}
public Camera getOverlayCam(){
return overlayCam;
}
public Node getRootNode(){
return root;
}
}

View File

@@ -0,0 +1,216 @@
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.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 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);
}
}
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,132 @@
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 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();
//TODO send Model finished
});
}
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();
}
public void hideDice() {
cardLayerHandler.hideDice();
}
public void addCard(pp.mdga.game.BonusCard card) {
cardLayerHandler.addCard(card);
}
public void clearSelectableCards() {
cardLayerHandler.clearSelectableCards();
}
public void setSelectableCards(List<pp.mdga.game.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();
}
public void drawCard(Color color) {
if (ownColor == color) actionTextHandler.drawCardOwn(color);
else actionTextHandler.drawCard(playerNameHandler.getName(color), color);
}
}

View File

@@ -0,0 +1,127 @@
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.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.ui.Picture;
import pp.mdga.game.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PlayerNameHandler {
private final BitmapFont playerFont;
private final Node playerNameNode;
private final List<Color> playerOrder;
private final Map<Color, String> colorNameMap;
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<>();
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(createName(colorNameMap.get(color), i == 0, color == ownColor));
nameParent.attachChild(createColor(color));
nameParent.setLocalTranslation(50,appSettings.getWindowHeight()-PADDING_TOP- MARGIN_NAMES *i,0);
playerNameNode.attachChild(nameParent);
}
}
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 Spatial 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){
return colorNameMap.get(color);
}
}

View File

@@ -0,0 +1,147 @@
package pp.mdga.client.gui;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.game.BonusCard;
public class SymbolControl extends AbstractControl {
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;
@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");
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
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;
}
}
}
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();
}
}
private void turboUpdate(float tpf){
if (zoomingIn) {
progress += tpf * zoomSpeed;
if (progress > 1) progress = 1;
float y = lerp(-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,Y, easeIn(progress));
spatial.setLocalTranslation(0,y,0);
if (progress > 1) {
zoomingIn = false;
spatial.removeFromParent();
state = null;
}
}
}
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);
}
public void swap(){
if(state != null) throw new RuntimeException("another state is avtive");
spatial.setLocalScale(3);
state = BonusCard.SWAP;
progress = -0.2f;
}
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;
}
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
private static float easeOut(float t) {
return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
}
private float easeInOut(float t) {
if(t>1) t=1;
return (float) -(Math.cos(Math.PI * t) - 1) / 2;
}
private float easeIn(float t) {
return t * t * t * t;
}
}

View File

@@ -0,0 +1,82 @@
package pp.mdga.client.gui;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
public class ZoomControl extends AbstractControl {
private boolean zoomingIn = false;
private boolean zoomingOut = false;
private float progress = 0;
private float zoomSpeed = 1f;
private float zoomFactor = 1f;
public ZoomControl(){}
public ZoomControl(float speed) {
zoomSpeed = speed;
}
@Override
public void setSpatial(Spatial spatial){
if(this.spatial == null && spatial != null){
super.setSpatial(spatial);
initSpatial();
}
}
private void initSpatial() {
zoomingIn = true;
}
@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();
}
}
}
private void end(){
spatial.removeFromParent();
spatial.removeControl(this);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
private static float lerp(float start, float end, float t) {
return (1 - t) * start + t * end;
}
// private static float easeOut(float t) {
// return (float) Math.sqrt(1 - Math.pow(t - 1, 2));
// }
private float easeOut(float x) {
return x == 1 ? 1 : (float) (1 - Math.pow(2, -10 * x));
}
// private float easeIn(float t) {
// return t * t * t * t;
// }
private float easeIn(float x) {
return x == 0 ? 0 : (float) Math.pow(2, 10 * x - 10);
}
}

View File

@@ -0,0 +1,264 @@
package pp.mdga.client.server;
import com.jme3.network.*;
import com.jme3.network.serializing.Serializer;
import pp.mdga.game.Game;
import pp.mdga.game.Player;
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.sql.Connection;
import java.util.Map;
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(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(PossibleCardMessage.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(StartPieceMessage.class);
Serializer.registerClass(UpdateReadyMessage.class);
Serializer.registerClass(UpdateTSKMessage.class);
Serializer.registerClass(WaitPieceMessage.class);
}
private void registerListeners() {
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addMessageListener(this, ClientStartGameMessage.class);
myServer.addMessageListener(this, DeselectTSKMessage.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()));
}
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
}
@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("");
}
}

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,216 @@
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.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);
}
}

View File

@@ -0,0 +1,169 @@
package pp.mdga.client.view;
import com.jme3.post.FilterPostProcessor;
import pp.mdga.client.MdgaState;
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.gui.GuiHandler;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.notification.AcquireCardNotification;
import pp.mdga.notification.ActivePlayerNotification;
import pp.mdga.notification.DiceNowNotification;
import pp.mdga.notification.GameNotification;
import pp.mdga.notification.MovePieceNotification;
import pp.mdga.notification.PlayerInGameNotification;
import pp.mdga.notification.RollDiceNotification;
import pp.mdga.notification.SelectableCardsNotification;
import pp.mdga.notification.SelectableMoveNotification;
import pp.mdga.notification.ShieldActiveNotification;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class GameView extends MdgaView {
private BoardHandler boardHandler;
private CameraHandler camera;
private GuiHandler guiHandler;
private ButtonRight cheatButton; //TODO
private ButtonLeft leaveButton;
private ButtonRight confirmButton;
private Color ownColor = null;
private FilterPostProcessor fpp;
public GameView(MdgaApp app) {
super(app);
cheatButton = new ButtonRight(app, overlayNode, () -> app.getModelSynchronize().enter(MdgaState.CEREMONY), "CHEAT", 1);
leaveButton = new ButtonLeft(app, overlayNode, () -> app.getModelSynchronize().leave(), "Spiel verlassen", 1);
confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
fpp = new FilterPostProcessor(app.getAssetManager());
this.camera = new CameraHandler(app, fpp);
this.boardHandler = new BoardHandler(app, rootNode, fpp);
guiHandler = new GuiHandler(app, guiNode);
}
@Override
public void onEnter() {
camera.init();
boardHandler.init();
setOwnColor(Color.AIRFORCE);
guiHandler.init(ownColor);
app.getViewPort().addProcessor(fpp);
app.getAcousticHandler().playSound(MdgaSound.START);
//Test
List<UUID> uuid1 = new ArrayList<>();
UUID p1 = UUID.randomUUID();
UUID p2 = UUID.randomUUID();
uuid1.add(p1);
uuid1.add(p2);
uuid1.add(UUID.randomUUID());
uuid1.add(UUID.randomUUID());
List<UUID> uuid2 = new ArrayList<>();
UUID p1_2 = UUID.randomUUID();
UUID p2_2 = UUID.randomUUID();
uuid2.add(p1_2);
uuid2.add(p2_2);
uuid2.add(UUID.randomUUID());
uuid2.add(UUID.randomUUID());
app.getNotificationSynchronizer().addTestNotification(new PlayerInGameNotification(Color.AIRFORCE, uuid1, "Cedric"));
app.getNotificationSynchronizer().addTestNotification(new PlayerInGameNotification(Color.NAVY, uuid2, "Test"));
app.getNotificationSynchronizer().addTestNotification(new MovePieceNotification(p1, 0, true));
app.getNotificationSynchronizer().addTestNotification(new MovePieceNotification(p1_2, 30, true));
// app.getNotificationSynchronizer().addTestNotification(new SelectableMoveNotification(List.of(p1), List.of(4), List.of(false)));
app.getNotificationSynchronizer().addTestNotification(new AcquireCardNotification(BonusCard.SHIELD));
// app.getNotificationSynchronizer().addTestNotification(new SelectableCardsNotification(List.of(BonusCard.SHIELD)));
// app.getNotificationSynchronizer().addTestNotification(new ShieldActiveNotification(p1));
// app.getNotificationSynchronizer().addTestNotification(new ActivePlayerNotification(Color.NAVY));
// app.getNotificationSynchronizer().addTestNotification(new DiceNowNotification());
// app.getNotificationSynchronizer().addTestNotification(new RollDiceNotification(Color.AIRFORCE, 5, true, 2));
p1 = p1;
}
@Override
public void onLeave() {
boardHandler.shutdown();
guiHandler.shutdown();
camera.shutdown();
confirmButton.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();
cheatButton.show();
}
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
if(overlay == Overlay.SETTINGS) {
leaveButton.hide();
cheatButton.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() {
confirmButton.show();
}
public void noConfirm() {
confirmButton.hide();
}
}

View File

@@ -0,0 +1,255 @@
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.notification.GameNotification;
public class LobbyView extends MdgaView {
private Geometry background;
private ButtonLeft leaveButton;
private ButtonRight readyButton;
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);
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();
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();
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;
}
switch (taken) {
case NOT:
app.getModelSynchronize().selectTsk(color);
break;
case SELF:
app.getModelSynchronize().unselectTsk();
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,220 @@
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);
overlayNode.attachChild(background);
}
@Override
protected void onLeaveOverlay(Overlay overlay) {
overlayNode.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();
try {
port = Integer.parseInt(text);
if(port >= 1 && port <= 65535) {
app.getModelSynchronize().setName(startDialog.getName());
app.getModelSynchronize().setHost(port);
//app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
return;
}
} catch (NumberFormatException e) {
//nothing
}
hostDialog.resetPort();
app.getAcousticHandler().playSound(MdgaSound.WRONG_INPUT);
}
private void tryJoin() {
int port = 0;
String ip = joinDialog.getIpt();
String portText = hostDialog.getPort();
try {
// Validate the port
port = Integer.parseInt(portText);
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Invalid port");
}
// Validate the IP address
if (isValidIpAddress(ip)) {
app.getModelSynchronize().setName(startDialog.getName());
app.getModelSynchronize().setJoin(ip, port);
return;
}
} catch (IllegalArgumentException e) {
// Invalid input, fall through to reset
}
hostDialog.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;
}
}
}

View File

@@ -0,0 +1,192 @@
package pp.mdga.client.view;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
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 overlayNode = new Node("View Root Overlay");
private SettingsButton settingsButton;
private SettingsDialog settingsDialog;
private VideoSettingsDialog videoSettingsDialog;
private AudioSettingsDialog audioSettingsDialog;
private int settingsDepth = 0;
public MdgaView(MdgaApp app) {
this.app = app;
settingsButton = new SettingsButton(app, guiNode, this::enterSettings);
settingsDialog = new SettingsDialog(app, overlayNode, this);
videoSettingsDialog = new VideoSettingsDialog(app, overlayNode, this);
audioSettingsDialog = new AudioSettingsDialog(app, overlayNode, 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();
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(overlayNode);
settingsDialog.show();
settingsDepth++;
}
public void leaveSettings() {
leaveOverlay(Overlay.SETTINGS);
app.getGuiNode().detachChild(overlayNode);
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();
}
}
}

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: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 KiB

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